@nick848/fet 1.0.2 → 1.0.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/README.md +20 -17
- package/dist/cli/index.js +333 -94
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -473,7 +473,7 @@ async function exists(path) {
|
|
|
473
473
|
}
|
|
474
474
|
|
|
475
475
|
// src/commands/doctor.ts
|
|
476
|
-
import { stat as stat3 } from "fs/promises";
|
|
476
|
+
import { readFile as readFile6, stat as stat3 } from "fs/promises";
|
|
477
477
|
import { join as join7 } from "path";
|
|
478
478
|
async function doctorCommand(ctx, options = {}) {
|
|
479
479
|
const checks = [];
|
|
@@ -481,6 +481,7 @@ async function doctorCommand(ctx, options = {}) {
|
|
|
481
481
|
checks.push(await checkState(ctx));
|
|
482
482
|
checks.push(await checkFile("agents", join7(ctx.projectRoot, "AGENTS.md"), "AGENTS.md \u7F3A\u5931", "fet update-context"));
|
|
483
483
|
checks.push(await checkFile("config", join7(ctx.projectRoot, "openspec", "config.yaml"), "openspec/config.yaml \u7F3A\u5931", "fet init"));
|
|
484
|
+
checks.push(await checkPlaceholders(join7(ctx.projectRoot, "AGENTS.md")));
|
|
484
485
|
for (const adapter of ctx.toolAdapters) {
|
|
485
486
|
checks.push(...await adapter.doctor(ctx.projectRoot));
|
|
486
487
|
}
|
|
@@ -521,6 +522,20 @@ async function checkState(ctx) {
|
|
|
521
522
|
async function checkFile(id, path, missing, suggestedCommand) {
|
|
522
523
|
return await exists2(path) ? { id, status: "pass", message: `${id} \u5B58\u5728` } : { id, status: "warn", message: missing, suggestedCommand };
|
|
523
524
|
}
|
|
525
|
+
async function checkPlaceholders(path) {
|
|
526
|
+
try {
|
|
527
|
+
const content = await readFile6(path, "utf8");
|
|
528
|
+
const count2 = [...content.matchAll(/\[NEEDS? LLM INPUT\]/g)].length;
|
|
529
|
+
return count2 ? {
|
|
530
|
+
id: "context-placeholders",
|
|
531
|
+
status: "warn",
|
|
532
|
+
message: `AGENTS.md has ${count2} LLM placeholder(s)`,
|
|
533
|
+
suggestedCommand: "fet fill-context"
|
|
534
|
+
} : { id: "context-placeholders", status: "pass", message: "AGENTS.md placeholders resolved" };
|
|
535
|
+
} catch {
|
|
536
|
+
return { id: "context-placeholders", status: "warn", message: "AGENTS.md missing", suggestedCommand: "fet update-context" };
|
|
537
|
+
}
|
|
538
|
+
}
|
|
524
539
|
async function exists2(path) {
|
|
525
540
|
try {
|
|
526
541
|
await stat3(path);
|
|
@@ -530,6 +545,82 @@ async function exists2(path) {
|
|
|
530
545
|
}
|
|
531
546
|
}
|
|
532
547
|
|
|
548
|
+
// src/commands/fill-context.ts
|
|
549
|
+
import { mkdir as mkdir3, readFile as readFile7 } from "fs/promises";
|
|
550
|
+
import { dirname as dirname5, join as join8 } from "path";
|
|
551
|
+
var placeholderPattern = /\[NEEDS? LLM INPUT\]/g;
|
|
552
|
+
async function fillContextCommand(ctx) {
|
|
553
|
+
await withProjectLock(
|
|
554
|
+
ctx.projectRoot,
|
|
555
|
+
{ command: "fill-context", cwd: ctx.cwd, fetVersion: ctx.fetVersion },
|
|
556
|
+
async () => {
|
|
557
|
+
const handoffPath = join8(ctx.projectRoot, ".fet", "fill-context.md");
|
|
558
|
+
await mkdir3(dirname5(handoffPath), { recursive: true });
|
|
559
|
+
await atomicWrite(handoffPath, renderGenericHandoff());
|
|
560
|
+
for (const adapter of ctx.toolAdapters) {
|
|
561
|
+
const plan = await adapter.planInstall(ctx.projectRoot);
|
|
562
|
+
const result = await adapter.install(ctx.projectRoot, plan, ctx.yes);
|
|
563
|
+
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
564
|
+
state.toolAdapters[adapter.tool] = {
|
|
565
|
+
adapterVersion: adapter.adapterVersion,
|
|
566
|
+
installed: true,
|
|
567
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
568
|
+
};
|
|
569
|
+
await ctx.stateStore.writeGlobal(state);
|
|
570
|
+
if (ctx.verbose) {
|
|
571
|
+
ctx.output.info(`Updated ${adapter.tool} adapter`, { written: result.written });
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
);
|
|
576
|
+
const placeholders = await countPlaceholders(join8(ctx.projectRoot, "AGENTS.md"));
|
|
577
|
+
ctx.output.result({
|
|
578
|
+
ok: true,
|
|
579
|
+
command: "fill-context",
|
|
580
|
+
summary: placeholders ? `Found ${placeholders} AGENTS.md placeholder(s). Use your IDE AI to fill them.` : "No AGENTS.md placeholders found. IDE fill-context commands were refreshed.",
|
|
581
|
+
nextSteps: placeholders ? [
|
|
582
|
+
"Cursor: run /fet-fill-context",
|
|
583
|
+
"Codex: run /prompts:fet-fill-context",
|
|
584
|
+
"OpenCode or other IDEs: open .fet/fill-context.md or run fet fill-context for handoff instructions"
|
|
585
|
+
] : ["Run fet doctor to confirm project context health"],
|
|
586
|
+
data: {
|
|
587
|
+
placeholders,
|
|
588
|
+
cursorCommand: "/fet-fill-context",
|
|
589
|
+
codexCommand: "/prompts:fet-fill-context"
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
function renderGenericHandoff() {
|
|
594
|
+
return `<!-- FET:MANAGED
|
|
595
|
+
schemaVersion: 1
|
|
596
|
+
generator: fill-context
|
|
597
|
+
FET:END -->
|
|
598
|
+
|
|
599
|
+
# FET Fill Context
|
|
600
|
+
|
|
601
|
+
Use the IDE AI to complete FET-generated placeholders.
|
|
602
|
+
|
|
603
|
+
1. Read AGENTS.md and openspec/config.yaml.
|
|
604
|
+
2. Inspect README files, package scripts, routes, tests, source layout, and project conventions.
|
|
605
|
+
3. Replace every \`[NEEDS LLM INPUT]\` or \`[NEED LLM INPUT]\` placeholder in AGENTS.md with concrete project-specific content.
|
|
606
|
+
4. Preserve FET managed markers.
|
|
607
|
+
5. Do not modify business code.
|
|
608
|
+
6. Run \`fet doctor\` and confirm no AGENTS.md placeholder warning remains.
|
|
609
|
+
`;
|
|
610
|
+
}
|
|
611
|
+
async function countPlaceholders(path) {
|
|
612
|
+
try {
|
|
613
|
+
const content = await readFile7(path, "utf8");
|
|
614
|
+
return [...content.matchAll(placeholderPattern)].length;
|
|
615
|
+
} catch {
|
|
616
|
+
return 0;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/commands/proxy.ts
|
|
621
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
622
|
+
import { join as join10 } from "path";
|
|
623
|
+
|
|
533
624
|
// src/state/project.ts
|
|
534
625
|
import { execFile } from "child_process";
|
|
535
626
|
import { promisify } from "util";
|
|
@@ -557,8 +648,8 @@ async function git(cwd, args) {
|
|
|
557
648
|
}
|
|
558
649
|
|
|
559
650
|
// src/state/store.ts
|
|
560
|
-
import { mkdir as
|
|
561
|
-
import { join as
|
|
651
|
+
import { mkdir as mkdir4, readFile as readFile8 } from "fs/promises";
|
|
652
|
+
import { join as join9 } from "path";
|
|
562
653
|
|
|
563
654
|
// src/state/schema.ts
|
|
564
655
|
var phases = ["explore", "propose", "implement", "verify", "sync", "archive"];
|
|
@@ -651,7 +742,7 @@ var StateStore = class {
|
|
|
651
742
|
project;
|
|
652
743
|
async readGlobal() {
|
|
653
744
|
try {
|
|
654
|
-
const value = JSON.parse(await
|
|
745
|
+
const value = JSON.parse(await readFile8(this.globalPath(), "utf8"));
|
|
655
746
|
assertGlobalState(value);
|
|
656
747
|
return value;
|
|
657
748
|
} catch (error) {
|
|
@@ -666,13 +757,13 @@ var StateStore = class {
|
|
|
666
757
|
}
|
|
667
758
|
async writeGlobal(state) {
|
|
668
759
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
669
|
-
await
|
|
760
|
+
await mkdir4(join9(this.projectRoot, "openspec"), { recursive: true });
|
|
670
761
|
await atomicWrite(this.globalPath(), `${JSON.stringify(state, null, 2)}
|
|
671
762
|
`);
|
|
672
763
|
}
|
|
673
764
|
async readChange(changeId) {
|
|
674
765
|
try {
|
|
675
|
-
const value = JSON.parse(await
|
|
766
|
+
const value = JSON.parse(await readFile8(this.changePath(changeId), "utf8"));
|
|
676
767
|
assertChangeState(value);
|
|
677
768
|
return value;
|
|
678
769
|
} catch (error) {
|
|
@@ -687,15 +778,15 @@ var StateStore = class {
|
|
|
687
778
|
}
|
|
688
779
|
async writeChange(state) {
|
|
689
780
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
690
|
-
await
|
|
781
|
+
await mkdir4(join9(this.projectRoot, "openspec", "changes", state.changeId), { recursive: true });
|
|
691
782
|
await atomicWrite(this.changePath(state.changeId), `${JSON.stringify(state, null, 2)}
|
|
692
783
|
`);
|
|
693
784
|
}
|
|
694
785
|
globalPath() {
|
|
695
|
-
return
|
|
786
|
+
return join9(this.projectRoot, "openspec", "fet-state.json");
|
|
696
787
|
}
|
|
697
788
|
changePath(changeId) {
|
|
698
|
-
return
|
|
789
|
+
return join9(this.projectRoot, "openspec", "changes", changeId, "fet-state.json");
|
|
699
790
|
}
|
|
700
791
|
};
|
|
701
792
|
function isNotFound(error) {
|
|
@@ -703,11 +794,11 @@ function isNotFound(error) {
|
|
|
703
794
|
}
|
|
704
795
|
|
|
705
796
|
// src/state/tasks.ts
|
|
706
|
-
import { readFile as
|
|
797
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
707
798
|
async function readCompletedTaskIds(tasksPath) {
|
|
708
799
|
let content;
|
|
709
800
|
try {
|
|
710
|
-
content = await
|
|
801
|
+
content = await readFile9(tasksPath, "utf8");
|
|
711
802
|
} catch {
|
|
712
803
|
return [];
|
|
713
804
|
}
|
|
@@ -746,6 +837,8 @@ async function proxyCommand(ctx, command, args) {
|
|
|
746
837
|
await assertVerified(ctx);
|
|
747
838
|
}
|
|
748
839
|
const mapped = await mapOpenSpecCommand(ctx, command, openSpecArgs);
|
|
840
|
+
const targetChangeId = command === "archive" ? mapped.args[0] ?? ctx.changeId ?? null : ctx.changeId ?? null;
|
|
841
|
+
const changelogEntry = command === "archive" && targetChangeId ? await createChangelogEntry(ctx.projectRoot, targetChangeId) : null;
|
|
749
842
|
const result = await ctx.openSpec.run(mapped.command, mapped.args, { cwd: ctx.projectRoot, stdio: ctx.json ? "pipe" : "inherit" });
|
|
750
843
|
if (result.exitCode !== 0) {
|
|
751
844
|
throw new FetError({
|
|
@@ -755,15 +848,25 @@ async function proxyCommand(ctx, command, args) {
|
|
|
755
848
|
recoverable: true
|
|
756
849
|
});
|
|
757
850
|
}
|
|
851
|
+
if (changelogEntry) {
|
|
852
|
+
await appendChangelog(ctx.projectRoot, changelogEntry);
|
|
853
|
+
}
|
|
758
854
|
const inspection = await ctx.openSpec.inspectProject(ctx.projectRoot);
|
|
759
855
|
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
760
856
|
state.openChangeIds = inspection.changes;
|
|
761
|
-
if (
|
|
762
|
-
state.activeChangeId
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
857
|
+
if (command === "archive") {
|
|
858
|
+
if (!state.activeChangeId || state.activeChangeId === targetChangeId || !inspection.changes.includes(state.activeChangeId)) {
|
|
859
|
+
state.activeChangeId = null;
|
|
860
|
+
}
|
|
861
|
+
state.verifyAuthorization = null;
|
|
862
|
+
} else {
|
|
863
|
+
if (ctx.changeId && inspection.changes.includes(ctx.changeId)) {
|
|
864
|
+
state.activeChangeId = ctx.changeId;
|
|
865
|
+
} else if (state.activeChangeId && !inspection.changes.includes(state.activeChangeId)) {
|
|
866
|
+
state.activeChangeId = inspection.changes.length === 1 ? inspection.changes[0] ?? null : null;
|
|
867
|
+
} else if (!state.activeChangeId && inspection.changes.length === 1) {
|
|
868
|
+
state.activeChangeId = inspection.changes[0] ?? null;
|
|
869
|
+
}
|
|
767
870
|
}
|
|
768
871
|
await ctx.stateStore.writeGlobal(state);
|
|
769
872
|
const changeId = ctx.changeId ?? state.activeChangeId;
|
|
@@ -793,6 +896,58 @@ async function proxyCommand(ctx, command, args) {
|
|
|
793
896
|
summary: `fet ${command} \u5B8C\u6210\u3002`
|
|
794
897
|
});
|
|
795
898
|
}
|
|
899
|
+
async function createChangelogEntry(projectRoot, changeId) {
|
|
900
|
+
return {
|
|
901
|
+
updateTime: formatLocalTimestamp(/* @__PURE__ */ new Date()),
|
|
902
|
+
content: await readChangeRequirement(projectRoot, changeId)
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
async function appendChangelog(projectRoot, entry) {
|
|
906
|
+
const changelogPath = join10(projectRoot, "CHANGELOG.md");
|
|
907
|
+
const existing = await readOptional3(changelogPath);
|
|
908
|
+
const block = `updateTime: ${entry.updateTime}
|
|
909
|
+
\u66F4\u65B0\u5185\u5BB9:${entry.content}
|
|
910
|
+
`;
|
|
911
|
+
const next = existing?.trimEnd() ? `${existing.trimEnd()}
|
|
912
|
+
|
|
913
|
+
${block}` : block;
|
|
914
|
+
await atomicWrite(changelogPath, next);
|
|
915
|
+
}
|
|
916
|
+
async function readChangeRequirement(projectRoot, changeId) {
|
|
917
|
+
const changeRoot = join10(projectRoot, "openspec", "changes", changeId);
|
|
918
|
+
const proposal = await readOptional3(join10(changeRoot, "proposal.md"));
|
|
919
|
+
if (proposal) {
|
|
920
|
+
return summarizeMarkdown(proposal);
|
|
921
|
+
}
|
|
922
|
+
const readme = await readOptional3(join10(changeRoot, "README.md"));
|
|
923
|
+
if (readme) {
|
|
924
|
+
return summarizeMarkdown(readme);
|
|
925
|
+
}
|
|
926
|
+
return changeId;
|
|
927
|
+
}
|
|
928
|
+
function summarizeMarkdown(content) {
|
|
929
|
+
const normalized = content.replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("<!--") && !line.startsWith("---")).join(" ");
|
|
930
|
+
return normalized || "\u672A\u63D0\u4F9B\u53D8\u66F4\u9700\u6C42";
|
|
931
|
+
}
|
|
932
|
+
async function readOptional3(path) {
|
|
933
|
+
try {
|
|
934
|
+
return await readFile10(path, "utf8");
|
|
935
|
+
} catch {
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function formatLocalTimestamp(date) {
|
|
940
|
+
const year = date.getFullYear();
|
|
941
|
+
const month = pad(date.getMonth() + 1);
|
|
942
|
+
const day = pad(date.getDate());
|
|
943
|
+
const hours = pad(date.getHours());
|
|
944
|
+
const minutes = pad(date.getMinutes());
|
|
945
|
+
const seconds = pad(date.getSeconds());
|
|
946
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
947
|
+
}
|
|
948
|
+
function pad(value) {
|
|
949
|
+
return String(value).padStart(2, "0");
|
|
950
|
+
}
|
|
796
951
|
async function passthroughCommand(ctx, command, args) {
|
|
797
952
|
const result = await ctx.openSpec.run(command, stripFetOptions(args), { cwd: ctx.projectRoot, stdio: ctx.json ? "pipe" : "inherit" });
|
|
798
953
|
if (result.exitCode !== 0) {
|
|
@@ -826,32 +981,37 @@ function stripFetOptions(args) {
|
|
|
826
981
|
async function mapOpenSpecCommand(ctx, command, args) {
|
|
827
982
|
switch (command) {
|
|
828
983
|
case "propose":
|
|
829
|
-
case "new":
|
|
830
|
-
return { command: "new", args: args[0] === "change" ? args : ["change", ...args] };
|
|
831
984
|
case "continue":
|
|
832
|
-
return { command: "instructions", args: [...withoutUndefined(args[0] ? [args[0]] : ["proposal"]), "--change", await requireChangeId(ctx)] };
|
|
833
985
|
case "ff":
|
|
834
|
-
return { command: "status", args: ["--change", await requireChangeId(ctx)] };
|
|
835
986
|
case "apply":
|
|
836
|
-
return { command: "instructions", args: ["apply", "--change", await requireChangeId(ctx)] };
|
|
837
987
|
case "sync":
|
|
838
|
-
|
|
988
|
+
case "bulk-archive":
|
|
989
|
+
case "explore":
|
|
990
|
+
case "onboard":
|
|
991
|
+
return { command, args: withGlobalChange(ctx, args) };
|
|
992
|
+
case "new":
|
|
993
|
+
return { command: "new", args: args[0] === "change" ? args : ["change", ...args] };
|
|
839
994
|
case "archive":
|
|
840
995
|
return { command: "archive", args: [ctx.changeId ?? args[0] ?? await requireChangeId(ctx), ...args.slice(ctx.changeId ? 0 : 1)] };
|
|
996
|
+
/*
|
|
841
997
|
case "bulk-archive":
|
|
842
998
|
throw new FetError({
|
|
843
|
-
code:
|
|
844
|
-
message: "OpenSpec 1.2.0
|
|
845
|
-
suggestedCommand: "
|
|
999
|
+
code: ErrorCode.InvalidArguments,
|
|
1000
|
+
message: "OpenSpec 1.2.0 不提供 bulk-archive 顶层命令",
|
|
1001
|
+
suggestedCommand: "逐个执行 fet archive --change <change-id>"
|
|
846
1002
|
});
|
|
847
1003
|
case "explore":
|
|
848
|
-
return { command: "
|
|
1004
|
+
return { command: "explore", args: ctx.changeId ? ["--change", ctx.changeId, ...args] : args };
|
|
849
1005
|
case "onboard":
|
|
850
1006
|
return { command: "instructions", args: [] };
|
|
1007
|
+
*/
|
|
851
1008
|
default:
|
|
852
1009
|
return { command, args };
|
|
853
1010
|
}
|
|
854
1011
|
}
|
|
1012
|
+
function withGlobalChange(ctx, args) {
|
|
1013
|
+
return ctx.changeId ? ["--change", ctx.changeId, ...args] : args;
|
|
1014
|
+
}
|
|
855
1015
|
async function requireChangeId(ctx) {
|
|
856
1016
|
if (ctx.changeId) {
|
|
857
1017
|
return ctx.changeId;
|
|
@@ -871,9 +1031,6 @@ async function requireChangeId(ctx) {
|
|
|
871
1031
|
suggestedCommand: "\u6DFB\u52A0 --change <change-id>"
|
|
872
1032
|
});
|
|
873
1033
|
}
|
|
874
|
-
function withoutUndefined(values) {
|
|
875
|
-
return values.filter(Boolean);
|
|
876
|
-
}
|
|
877
1034
|
async function assertVerified(ctx) {
|
|
878
1035
|
const global = await ctx.stateStore.getOrCreateGlobal();
|
|
879
1036
|
const changeId = ctx.changeId ?? global.activeChangeId;
|
|
@@ -906,8 +1063,8 @@ async function assertVerified(ctx) {
|
|
|
906
1063
|
|
|
907
1064
|
// src/commands/verify.ts
|
|
908
1065
|
import { createHash } from "crypto";
|
|
909
|
-
import { mkdir as
|
|
910
|
-
import { join as
|
|
1066
|
+
import { mkdir as mkdir5, readFile as readFile11, stat as stat4 } from "fs/promises";
|
|
1067
|
+
import { join as join11 } from "path";
|
|
911
1068
|
async function verifyCommand(ctx, options) {
|
|
912
1069
|
if (options.auto) {
|
|
913
1070
|
const scan = await ctx.scanner.scan(ctx.projectRoot, {});
|
|
@@ -974,9 +1131,9 @@ async function verifyCommand(ctx, options) {
|
|
|
974
1131
|
async function writeInstructions(ctx, changeId) {
|
|
975
1132
|
await assertChangeExists(ctx, changeId);
|
|
976
1133
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
977
|
-
const dir =
|
|
978
|
-
const instructionsPath =
|
|
979
|
-
await
|
|
1134
|
+
const dir = join11(ctx.projectRoot, "openspec", "changes", changeId, ".fet");
|
|
1135
|
+
const instructionsPath = join11(dir, "verify-instructions.md");
|
|
1136
|
+
await mkdir5(dir, { recursive: true });
|
|
980
1137
|
await atomicWrite(instructionsPath, renderVerifyInstructions(changeId, generatedAt));
|
|
981
1138
|
const state = await ctx.stateStore.getOrCreateChange(changeId, "verify");
|
|
982
1139
|
state.currentPhase = "verify";
|
|
@@ -992,7 +1149,7 @@ async function writeInstructions(ctx, changeId) {
|
|
|
992
1149
|
async function markDone(ctx, changeId) {
|
|
993
1150
|
await assertChangeExists(ctx, changeId);
|
|
994
1151
|
const declaredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
995
|
-
const instructionsPath =
|
|
1152
|
+
const instructionsPath = join11(ctx.projectRoot, "openspec", "changes", changeId, ".fet", "verify-instructions.md");
|
|
996
1153
|
const instructions = await readInstructions(instructionsPath, changeId);
|
|
997
1154
|
const instructionsGeneratedAt = readFrontMatterValue(instructions, "generatedAt") ?? declaredAt;
|
|
998
1155
|
const state = await ctx.stateStore.getOrCreateChange(changeId, "verify");
|
|
@@ -1028,7 +1185,7 @@ async function assertChangeExists(ctx, changeId) {
|
|
|
1028
1185
|
async function readInstructions(path, changeId) {
|
|
1029
1186
|
try {
|
|
1030
1187
|
await stat4(path);
|
|
1031
|
-
const content = await
|
|
1188
|
+
const content = await readFile11(path, "utf8");
|
|
1032
1189
|
const fileChangeId = readFrontMatterValue(content, "changeId");
|
|
1033
1190
|
if (fileChangeId !== changeId) {
|
|
1034
1191
|
throw new FetError({
|
|
@@ -1082,9 +1239,9 @@ async function resolveChangeId(ctx) {
|
|
|
1082
1239
|
import { resolve } from "path";
|
|
1083
1240
|
|
|
1084
1241
|
// src/adapters/codex/index.ts
|
|
1085
|
-
import { mkdir as
|
|
1242
|
+
import { mkdir as mkdir6, readFile as readFile12, stat as stat5 } from "fs/promises";
|
|
1086
1243
|
import { homedir } from "os";
|
|
1087
|
-
import { dirname as
|
|
1244
|
+
import { dirname as dirname6, join as join12 } from "path";
|
|
1088
1245
|
|
|
1089
1246
|
// src/adapters/commands.ts
|
|
1090
1247
|
var FET_WORKFLOW_COMMANDS = [
|
|
@@ -1100,7 +1257,7 @@ var FET_WORKFLOW_COMMANDS = [
|
|
|
1100
1257
|
"bulk-archive",
|
|
1101
1258
|
"onboard"
|
|
1102
1259
|
];
|
|
1103
|
-
var FET_ADAPTER_COMMANDS = [...FET_WORKFLOW_COMMANDS, "passthrough"];
|
|
1260
|
+
var FET_ADAPTER_COMMANDS = [...FET_WORKFLOW_COMMANDS, "fill-context", "passthrough"];
|
|
1104
1261
|
|
|
1105
1262
|
// src/adapters/codex/templates.ts
|
|
1106
1263
|
function codexGuideFile() {
|
|
@@ -1140,6 +1297,9 @@ function codexSlashPromptFiles() {
|
|
|
1140
1297
|
}));
|
|
1141
1298
|
}
|
|
1142
1299
|
function renderCommand(command) {
|
|
1300
|
+
if (command === "fill-context") {
|
|
1301
|
+
return renderFillContextCommand();
|
|
1302
|
+
}
|
|
1143
1303
|
if (command === "passthrough") {
|
|
1144
1304
|
return renderPassthroughCommand();
|
|
1145
1305
|
}
|
|
@@ -1219,6 +1379,9 @@ function renderSlashPrompt(command) {
|
|
|
1219
1379
|
if (command === "onboard") {
|
|
1220
1380
|
return renderOnboardSlashPrompt();
|
|
1221
1381
|
}
|
|
1382
|
+
if (command === "fill-context") {
|
|
1383
|
+
return renderFillContextSlashPrompt();
|
|
1384
|
+
}
|
|
1222
1385
|
if (command === "passthrough") {
|
|
1223
1386
|
return renderPassthroughSlashPrompt();
|
|
1224
1387
|
}
|
|
@@ -1251,6 +1414,62 @@ ${shellCommand}
|
|
|
1251
1414
|
After it completes, summarize the important FET output and next steps.
|
|
1252
1415
|
`;
|
|
1253
1416
|
}
|
|
1417
|
+
function renderFillContextCommand() {
|
|
1418
|
+
return `<!-- FET:MANAGED
|
|
1419
|
+
schemaVersion: 1
|
|
1420
|
+
fetVersion: ${FET_VERSION}
|
|
1421
|
+
generator: codex-adapter
|
|
1422
|
+
adapterVersion: 1
|
|
1423
|
+
command: fet fill-context
|
|
1424
|
+
FET:END -->
|
|
1425
|
+
|
|
1426
|
+
# fet fill-context
|
|
1427
|
+
|
|
1428
|
+
Use this command to complete FET-generated project context placeholders with Codex.
|
|
1429
|
+
|
|
1430
|
+
First run:
|
|
1431
|
+
|
|
1432
|
+
\`\`\`sh
|
|
1433
|
+
fet fill-context
|
|
1434
|
+
\`\`\`
|
|
1435
|
+
|
|
1436
|
+
Then read AGENTS.md and openspec/config.yaml, inspect the project, and replace every [NEEDS LLM INPUT] or [NEED LLM INPUT] placeholder in AGENTS.md with concrete project-specific content. Preserve FET managed markers and do not modify business code.
|
|
1437
|
+
`;
|
|
1438
|
+
}
|
|
1439
|
+
function renderFillContextSlashPrompt() {
|
|
1440
|
+
return renderManagedSlashPrompt(
|
|
1441
|
+
"fet fill-context",
|
|
1442
|
+
"Fill FET AGENTS.md placeholders with Codex",
|
|
1443
|
+
`Complete FET-generated project context placeholders.
|
|
1444
|
+
|
|
1445
|
+
Steps:
|
|
1446
|
+
|
|
1447
|
+
1. Refresh FET IDE handoff files:
|
|
1448
|
+
\`\`\`sh
|
|
1449
|
+
fet fill-context
|
|
1450
|
+
\`\`\`
|
|
1451
|
+
2. Read AGENTS.md and openspec/config.yaml.
|
|
1452
|
+
3. Inspect the project to understand:
|
|
1453
|
+
- source structure and major modules
|
|
1454
|
+
- framework and routing conventions
|
|
1455
|
+
- scripts, test commands, and build commands
|
|
1456
|
+
- coding conventions and project-specific patterns
|
|
1457
|
+
- important docs such as README files
|
|
1458
|
+
4. Replace every \`[NEEDS LLM INPUT]\` or \`[NEED LLM INPUT]\` placeholder in AGENTS.md with concrete, concise project-specific content.
|
|
1459
|
+
5. Preserve FET managed markers such as \`FET:BEGIN AUTO\`, \`FET:END AUTO\`, \`FET:BEGIN USER\`, and \`FET:END USER\`.
|
|
1460
|
+
6. Do not modify business code.
|
|
1461
|
+
7. Run:
|
|
1462
|
+
\`\`\`sh
|
|
1463
|
+
fet doctor
|
|
1464
|
+
\`\`\`
|
|
1465
|
+
Confirm that no AGENTS.md placeholder warning remains.
|
|
1466
|
+
|
|
1467
|
+
Guardrails:
|
|
1468
|
+
- Do not invent facts that cannot be inferred from the repo.
|
|
1469
|
+
- Use [UNKNOWN] only when the repository does not contain enough evidence.
|
|
1470
|
+
- Keep generated context stable and useful for future AI coding sessions.`
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1254
1473
|
function renderNewSlashPrompt() {
|
|
1255
1474
|
return renderManagedSlashPrompt(
|
|
1256
1475
|
"fet new [...args]",
|
|
@@ -1294,11 +1513,11 @@ Input after the slash command should identify the change, for example a change i
|
|
|
1294
1513
|
Steps:
|
|
1295
1514
|
|
|
1296
1515
|
1. Resolve the change id. If ambiguous, ask the user.
|
|
1297
|
-
2.
|
|
1516
|
+
2. Run the native OpenSpec apply flow through FET:
|
|
1298
1517
|
\`\`\`sh
|
|
1299
1518
|
fet apply --change <change-id> --json
|
|
1300
1519
|
\`\`\`
|
|
1301
|
-
3.
|
|
1520
|
+
3. Follow the native apply output. If JSON output is unavailable, read the files referenced by the terminal output and the artifacts under openspec/changes/<change-id>/.
|
|
1302
1521
|
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
1522
|
5. Implement pending tasks one by one:
|
|
1304
1523
|
- Keep code changes minimal and scoped to the task.
|
|
@@ -1502,11 +1721,11 @@ Steps:
|
|
|
1502
1721
|
\`\`\`sh
|
|
1503
1722
|
fet new <change-id>
|
|
1504
1723
|
\`\`\`
|
|
1505
|
-
5.
|
|
1724
|
+
5. Run the native OpenSpec exploration flow through FET so clarification prompts stay interactive:
|
|
1506
1725
|
\`\`\`sh
|
|
1507
1726
|
fet explore --change <change-id>
|
|
1508
1727
|
\`\`\`
|
|
1509
|
-
6. If the user asks to generate or capture the proposal, create openspec/changes/<change-id>/proposal.md using the
|
|
1728
|
+
6. If OpenSpec or the user asks to generate or capture the proposal, create openspec/changes/<change-id>/proposal.md using the interactive answers, conversation, and project context.
|
|
1510
1729
|
|
|
1511
1730
|
Guardrails:
|
|
1512
1731
|
- Do not write application code in explore mode.
|
|
@@ -1532,11 +1751,11 @@ Steps:
|
|
|
1532
1751
|
fet passthrough status --change <change-id> --json
|
|
1533
1752
|
\`\`\`
|
|
1534
1753
|
4. Pick the first artifact whose status is ready, unless the user specified an artifact id.
|
|
1535
|
-
5.
|
|
1754
|
+
5. Run the native OpenSpec continue flow through FET:
|
|
1536
1755
|
\`\`\`sh
|
|
1537
1756
|
fet continue <artifact-id> --change <change-id> --json
|
|
1538
1757
|
\`\`\`
|
|
1539
|
-
6.
|
|
1758
|
+
6. Follow the native continue output. When it provides template, instruction, dependencies, and outputPath, use those fields.
|
|
1540
1759
|
7. Read dependency files before writing.
|
|
1541
1760
|
8. Create the artifact file at outputPath. Do not copy context/rules wrapper text into the artifact; use those fields only as constraints.
|
|
1542
1761
|
9. Verify the file exists, then run:
|
|
@@ -1557,6 +1776,7 @@ Guardrails:
|
|
|
1557
1776
|
}
|
|
1558
1777
|
function renderFastForwardSlashPrompt(command) {
|
|
1559
1778
|
const title = command === "propose" ? "Propose a new FET/OpenSpec change" : "Fast-forward FET/OpenSpec artifact creation";
|
|
1779
|
+
const commandLine = command === "propose" ? "fet propose <change-id-or-description>" : "fet ff --change <change-id>";
|
|
1560
1780
|
return renderManagedSlashPrompt(
|
|
1561
1781
|
`fet ${command} [...args]`,
|
|
1562
1782
|
command === "propose" ? "Create a change and generate required OpenSpec artifacts" : "Generate required OpenSpec artifacts for a change",
|
|
@@ -1567,22 +1787,13 @@ Input after the slash command may be a change id or a description of what the us
|
|
|
1567
1787
|
Steps:
|
|
1568
1788
|
|
|
1569
1789
|
1. Load project context from AGENTS.md and openspec/config.yaml.
|
|
1570
|
-
2. Resolve or
|
|
1571
|
-
|
|
1572
|
-
- If the change already exists, continue it instead of recreating it.
|
|
1573
|
-
3. Check artifact status:
|
|
1790
|
+
2. Resolve the change id or description. If ambiguous, ask the user.
|
|
1791
|
+
3. Run the native OpenSpec ${command} flow through FET:
|
|
1574
1792
|
\`\`\`sh
|
|
1575
|
-
|
|
1793
|
+
${commandLine}
|
|
1576
1794
|
\`\`\`
|
|
1577
|
-
4.
|
|
1578
|
-
|
|
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.
|
|
1795
|
+
4. Follow the native output. If it asks for clarification, ask the user rather than inventing details.
|
|
1796
|
+
5. If the native output includes artifact paths or templates to write, create only those files and preserve OpenSpec structure.
|
|
1586
1797
|
|
|
1587
1798
|
Artifact rules:
|
|
1588
1799
|
- Follow the instruction field from OpenSpec/FET for each artifact.
|
|
@@ -1621,7 +1832,7 @@ var CodexAdapter = class {
|
|
|
1621
1832
|
adapterVersion = 1;
|
|
1622
1833
|
async detect(projectRoot) {
|
|
1623
1834
|
return {
|
|
1624
|
-
detected: await exists3(
|
|
1835
|
+
detected: await exists3(join12(projectRoot, ".codex")) || await exists3(join12(projectRoot, "AGENTS.md")),
|
|
1625
1836
|
reason: "Codex adapter is available for projects that use AGENTS.md"
|
|
1626
1837
|
};
|
|
1627
1838
|
}
|
|
@@ -1660,7 +1871,7 @@ var CodexAdapter = class {
|
|
|
1660
1871
|
if (existing && !existing.includes("FET:MANAGED") && force) {
|
|
1661
1872
|
await createBackup(target);
|
|
1662
1873
|
}
|
|
1663
|
-
await
|
|
1874
|
+
await mkdir6(dirname6(target), { recursive: true });
|
|
1664
1875
|
await atomicWrite(target, file.content);
|
|
1665
1876
|
written.push(displayPath);
|
|
1666
1877
|
}
|
|
@@ -1687,9 +1898,9 @@ var CodexAdapter = class {
|
|
|
1687
1898
|
};
|
|
1688
1899
|
function resolveTarget(projectRoot, file) {
|
|
1689
1900
|
if (file.root === "codex-home") {
|
|
1690
|
-
return
|
|
1901
|
+
return join12(resolveCodexHome(), file.path);
|
|
1691
1902
|
}
|
|
1692
|
-
return
|
|
1903
|
+
return join12(projectRoot, file.path);
|
|
1693
1904
|
}
|
|
1694
1905
|
function displayPathFor(file) {
|
|
1695
1906
|
if (file.root === "codex-home") {
|
|
@@ -1698,11 +1909,11 @@ function displayPathFor(file) {
|
|
|
1698
1909
|
return file.path;
|
|
1699
1910
|
}
|
|
1700
1911
|
function resolveCodexHome() {
|
|
1701
|
-
return process.env.FET_CODEX_HOME ?? process.env.CODEX_HOME ??
|
|
1912
|
+
return process.env.FET_CODEX_HOME ?? process.env.CODEX_HOME ?? join12(homedir(), ".codex");
|
|
1702
1913
|
}
|
|
1703
1914
|
async function readExisting(path) {
|
|
1704
1915
|
try {
|
|
1705
|
-
return await
|
|
1916
|
+
return await readFile12(path, "utf8");
|
|
1706
1917
|
} catch {
|
|
1707
1918
|
return null;
|
|
1708
1919
|
}
|
|
@@ -1717,8 +1928,8 @@ async function exists3(path) {
|
|
|
1717
1928
|
}
|
|
1718
1929
|
|
|
1719
1930
|
// src/adapters/cursor/index.ts
|
|
1720
|
-
import { mkdir as
|
|
1721
|
-
import { dirname as
|
|
1931
|
+
import { mkdir as mkdir7, readFile as readFile13, stat as stat6 } from "fs/promises";
|
|
1932
|
+
import { dirname as dirname7, join as join13 } from "path";
|
|
1722
1933
|
|
|
1723
1934
|
// src/adapters/cursor/templates.ts
|
|
1724
1935
|
function cursorSkillFiles() {
|
|
@@ -1754,6 +1965,31 @@ alwaysApply: false
|
|
|
1754
1965
|
}
|
|
1755
1966
|
function renderSkill(command) {
|
|
1756
1967
|
const usage = command === "passthrough" ? "fet passthrough <openspec-command> [...args]" : `fet ${command}`;
|
|
1968
|
+
if (command === "fill-context") {
|
|
1969
|
+
return `<!-- FET:MANAGED
|
|
1970
|
+
schemaVersion: 1
|
|
1971
|
+
fetVersion: ${FET_VERSION}
|
|
1972
|
+
generator: cursor-adapter
|
|
1973
|
+
adapterVersion: 1
|
|
1974
|
+
command: fet fill-context
|
|
1975
|
+
FET:END -->
|
|
1976
|
+
|
|
1977
|
+
---
|
|
1978
|
+
name: fet-fill-context
|
|
1979
|
+
description: Fill FET AGENTS.md placeholders with Cursor AI
|
|
1980
|
+
disable-model-invocation: false
|
|
1981
|
+
---
|
|
1982
|
+
|
|
1983
|
+
Run \`fet fill-context\` first if the IDE commands need refreshing.
|
|
1984
|
+
|
|
1985
|
+
Then read:
|
|
1986
|
+
|
|
1987
|
+
- AGENTS.md
|
|
1988
|
+
- openspec/config.yaml
|
|
1989
|
+
|
|
1990
|
+
Replace every \`[NEEDS LLM INPUT]\` or \`[NEED LLM INPUT]\` placeholder in AGENTS.md with concrete project-specific content. Inspect README files, package scripts, routes, tests, source layout, and existing conventions before writing. Preserve FET managed markers and do not modify business code.
|
|
1991
|
+
`;
|
|
1992
|
+
}
|
|
1757
1993
|
return `<!-- FET:MANAGED
|
|
1758
1994
|
schemaVersion: 1
|
|
1759
1995
|
fetVersion: ${FET_VERSION}
|
|
@@ -1786,7 +2022,7 @@ var CursorAdapter = class {
|
|
|
1786
2022
|
adapterVersion = 1;
|
|
1787
2023
|
async detect(projectRoot) {
|
|
1788
2024
|
return {
|
|
1789
|
-
detected: await exists4(
|
|
2025
|
+
detected: await exists4(join13(projectRoot, ".cursor")),
|
|
1790
2026
|
reason: "Cursor adapter is available for any project"
|
|
1791
2027
|
};
|
|
1792
2028
|
}
|
|
@@ -1803,7 +2039,7 @@ var CursorAdapter = class {
|
|
|
1803
2039
|
const written = [];
|
|
1804
2040
|
const skipped = [];
|
|
1805
2041
|
for (const file of plan.files) {
|
|
1806
|
-
const target =
|
|
2042
|
+
const target = join13(projectRoot, file.path);
|
|
1807
2043
|
const existing = await readExisting2(target);
|
|
1808
2044
|
if (existing && !existing.includes("FET:MANAGED") && !force) {
|
|
1809
2045
|
throw new FetError({
|
|
@@ -1816,7 +2052,7 @@ var CursorAdapter = class {
|
|
|
1816
2052
|
if (existing && !existing.includes("FET:MANAGED") && force) {
|
|
1817
2053
|
await createBackup(target);
|
|
1818
2054
|
}
|
|
1819
|
-
await
|
|
2055
|
+
await mkdir7(dirname7(target), { recursive: true });
|
|
1820
2056
|
await atomicWrite(target, file.content);
|
|
1821
2057
|
written.push(file.path);
|
|
1822
2058
|
}
|
|
@@ -1826,7 +2062,7 @@ var CursorAdapter = class {
|
|
|
1826
2062
|
const plan = await this.planInstall(projectRoot);
|
|
1827
2063
|
const checks = [];
|
|
1828
2064
|
for (const file of plan.files) {
|
|
1829
|
-
const target =
|
|
2065
|
+
const target = join13(projectRoot, file.path);
|
|
1830
2066
|
const content = await readExisting2(target);
|
|
1831
2067
|
const managed = Boolean(content?.includes("FET:MANAGED"));
|
|
1832
2068
|
const versionMatches = Boolean(content?.includes(`adapterVersion: ${this.adapterVersion}`));
|
|
@@ -1842,7 +2078,7 @@ var CursorAdapter = class {
|
|
|
1842
2078
|
};
|
|
1843
2079
|
async function readExisting2(path) {
|
|
1844
2080
|
try {
|
|
1845
|
-
return await
|
|
2081
|
+
return await readFile13(path, "utf8");
|
|
1846
2082
|
} catch {
|
|
1847
2083
|
return null;
|
|
1848
2084
|
}
|
|
@@ -1862,35 +2098,37 @@ import { promisify as promisify3 } from "util";
|
|
|
1862
2098
|
|
|
1863
2099
|
// src/openspec/inspector.ts
|
|
1864
2100
|
import { readdir, stat as stat7 } from "fs/promises";
|
|
1865
|
-
import { join as
|
|
2101
|
+
import { join as join14 } from "path";
|
|
1866
2102
|
async function inspectOpenSpecProject(projectRoot) {
|
|
1867
|
-
const openspecPath =
|
|
1868
|
-
const changesPath =
|
|
1869
|
-
const
|
|
2103
|
+
const openspecPath = join14(projectRoot, "openspec");
|
|
2104
|
+
const changesPath = join14(openspecPath, "changes");
|
|
2105
|
+
const legacyArchivePath = join14(openspecPath, "archive");
|
|
2106
|
+
const changesArchivePath = join14(changesPath, "archive");
|
|
1870
2107
|
return {
|
|
1871
2108
|
exists: await exists5(openspecPath),
|
|
1872
|
-
changes: await listDirectories(changesPath),
|
|
1873
|
-
archived: await listDirectories(
|
|
2109
|
+
changes: await listDirectories(changesPath, { exclude: ["archive"] }),
|
|
2110
|
+
archived: [.../* @__PURE__ */ new Set([...await listDirectories(legacyArchivePath), ...await listDirectories(changesArchivePath)])]
|
|
1874
2111
|
};
|
|
1875
2112
|
}
|
|
1876
2113
|
async function inspectOpenSpecChange(projectRoot, changeId) {
|
|
1877
|
-
const changePath =
|
|
1878
|
-
const tasksPath =
|
|
1879
|
-
const specsPath =
|
|
2114
|
+
const changePath = join14(projectRoot, "openspec", "changes", changeId);
|
|
2115
|
+
const tasksPath = join14(changePath, "tasks.md");
|
|
2116
|
+
const specsPath = join14(changePath, "specs");
|
|
1880
2117
|
return {
|
|
1881
2118
|
changeId,
|
|
1882
2119
|
exists: await exists5(changePath),
|
|
1883
|
-
hasProposal: await exists5(
|
|
2120
|
+
hasProposal: await exists5(join14(changePath, "proposal.md")),
|
|
1884
2121
|
hasTasks: await exists5(tasksPath),
|
|
1885
2122
|
hasSpecs: await exists5(specsPath),
|
|
1886
2123
|
tasksPath,
|
|
1887
2124
|
changePath
|
|
1888
2125
|
};
|
|
1889
2126
|
}
|
|
1890
|
-
async function listDirectories(path) {
|
|
2127
|
+
async function listDirectories(path, options = {}) {
|
|
1891
2128
|
try {
|
|
1892
2129
|
const entries = await readdir(path, { withFileTypes: true });
|
|
1893
|
-
|
|
2130
|
+
const excluded = new Set(options.exclude ?? []);
|
|
2131
|
+
return entries.filter((entry) => entry.isDirectory() && !excluded.has(entry.name)).map((entry) => entry.name);
|
|
1894
2132
|
} catch {
|
|
1895
2133
|
return [];
|
|
1896
2134
|
}
|
|
@@ -2056,12 +2294,12 @@ function parseCommands(help) {
|
|
|
2056
2294
|
}
|
|
2057
2295
|
|
|
2058
2296
|
// src/scanner/package.ts
|
|
2059
|
-
import { readFile as
|
|
2060
|
-
import { join as
|
|
2297
|
+
import { readFile as readFile14, stat as stat8 } from "fs/promises";
|
|
2298
|
+
import { join as join15 } from "path";
|
|
2061
2299
|
import { parse as parse2 } from "yaml";
|
|
2062
2300
|
async function readPackageJson(projectRoot) {
|
|
2063
2301
|
try {
|
|
2064
|
-
return JSON.parse(await
|
|
2302
|
+
return JSON.parse(await readFile14(join15(projectRoot, "package.json"), "utf8"));
|
|
2065
2303
|
} catch {
|
|
2066
2304
|
return null;
|
|
2067
2305
|
}
|
|
@@ -2127,7 +2365,7 @@ function detectFramework(pkg) {
|
|
|
2127
2365
|
}
|
|
2128
2366
|
async function detectLanguage(projectRoot, pkg) {
|
|
2129
2367
|
const deps = { ...pkg?.dependencies ?? {}, ...pkg?.devDependencies ?? {} };
|
|
2130
|
-
if (deps.typescript || await exists6(
|
|
2368
|
+
if (deps.typescript || await exists6(join15(projectRoot, "tsconfig.json"))) {
|
|
2131
2369
|
return "typescript";
|
|
2132
2370
|
}
|
|
2133
2371
|
return "javascript";
|
|
@@ -2142,7 +2380,7 @@ async function detectWorkspaces(projectRoot, pkg) {
|
|
|
2142
2380
|
return packageWorkspaces;
|
|
2143
2381
|
}
|
|
2144
2382
|
try {
|
|
2145
|
-
const workspace = parse2(await
|
|
2383
|
+
const workspace = parse2(await readFile14(join15(projectRoot, "pnpm-workspace.yaml"), "utf8"));
|
|
2146
2384
|
return (workspace?.packages ?? []).map((path) => ({
|
|
2147
2385
|
name: path,
|
|
2148
2386
|
path,
|
|
@@ -2162,7 +2400,7 @@ async function detectLockManagers(projectRoot) {
|
|
|
2162
2400
|
];
|
|
2163
2401
|
const found = [];
|
|
2164
2402
|
for (const [file, manager] of lockFiles) {
|
|
2165
|
-
if (await exists6(
|
|
2403
|
+
if (await exists6(join15(projectRoot, file))) {
|
|
2166
2404
|
found.push(manager);
|
|
2167
2405
|
}
|
|
2168
2406
|
}
|
|
@@ -2188,12 +2426,12 @@ async function exists6(path) {
|
|
|
2188
2426
|
|
|
2189
2427
|
// src/scanner/routes.ts
|
|
2190
2428
|
import { readdir as readdir2, stat as stat9 } from "fs/promises";
|
|
2191
|
-
import { join as
|
|
2429
|
+
import { join as join16, relative, sep } from "path";
|
|
2192
2430
|
async function scanRoutes(projectRoot) {
|
|
2193
2431
|
const candidates = ["src/routes", "src/pages", "app", "pages"];
|
|
2194
2432
|
const routes = [];
|
|
2195
2433
|
for (const candidate of candidates) {
|
|
2196
|
-
const root =
|
|
2434
|
+
const root = join16(projectRoot, candidate);
|
|
2197
2435
|
if (!await exists7(root)) {
|
|
2198
2436
|
continue;
|
|
2199
2437
|
}
|
|
@@ -2221,7 +2459,7 @@ async function listFiles(root) {
|
|
|
2221
2459
|
const entries = await readdir2(root, { withFileTypes: true });
|
|
2222
2460
|
const files = [];
|
|
2223
2461
|
for (const entry of entries) {
|
|
2224
|
-
const path =
|
|
2462
|
+
const path = join16(root, entry.name);
|
|
2225
2463
|
if (entry.isDirectory()) {
|
|
2226
2464
|
files.push(...await listFiles(path));
|
|
2227
2465
|
} else {
|
|
@@ -2373,6 +2611,7 @@ var program = new Command();
|
|
|
2373
2611
|
program.name("fet").description("Frontend workflow orchestration tool built around OpenSpec.").enablePositionalOptions().version(FET_VERSION).option("--cwd <path>", "\u6307\u5B9A\u9879\u76EE\u6839\u76EE\u5F55").option("--change <id>", "\u6307\u5B9A OpenSpec change").option("--yes", "\u5BF9\u4F4E\u98CE\u9669\u786E\u8BA4\u4F7F\u7528\u9ED8\u8BA4\u540C\u610F").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").option("--verbose", "\u8F93\u51FA\u8BCA\u65AD\u7EC6\u8282").option("--no-color", "\u7981\u7528\u7EC8\u7AEF\u989C\u8272");
|
|
2374
2612
|
addGlobalOptions(program.command("init")).description("\u521D\u59CB\u5316 FET + OpenSpec").action(wrap("init", initCommand));
|
|
2375
2613
|
addGlobalOptions(program.command("update-context")).description("\u66F4\u65B0\u9879\u76EE\u4E0A\u4E0B\u6587").action(wrap("update-context", updateContextCommand));
|
|
2614
|
+
addGlobalOptions(program.command("fill-context")).description("Refresh IDE prompts for filling AGENTS.md placeholders").action(wrap("fill-context", fillContextCommand));
|
|
2376
2615
|
addGlobalOptions(program.command("doctor").description("\u8BCA\u65AD\u72B6\u6001\u3001\u914D\u7F6E\u4E0E\u4E00\u81F4\u6027").option("--fix-lock", "\u6E05\u7406 FET \u9501\u6587\u4EF6")).action(
|
|
2377
2616
|
wrap("doctor", (ctx, options) => doctorCommand(ctx, { fixLock: Boolean(options.fixLock) }))
|
|
2378
2617
|
);
|