@contextforge/core 0.1.5 → 0.1.7
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/index.d.ts +2 -2
- package/dist/index.js +162 -217
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -365,9 +365,9 @@ declare function installPack(projectRoot: string, registryUrl: string, packName:
|
|
|
365
365
|
|
|
366
366
|
declare function compileOutputs(config: ContextForgeConfig, packs: InstalledPack[], analysis: ProjectAnalysis): GeneratedFile[];
|
|
367
367
|
|
|
368
|
-
declare function compileAgentsMd(
|
|
368
|
+
declare function compileAgentsMd(): Promise<string>;
|
|
369
369
|
|
|
370
|
-
declare function compileClaudeMd(
|
|
370
|
+
declare function compileClaudeMd(): Promise<string>;
|
|
371
371
|
|
|
372
372
|
declare function generateToolOutputs(root: string, packs: InstalledPack[], config: ContextForgeConfig, previousFiles?: string[]): Promise<string[]>;
|
|
373
373
|
|
package/dist/index.js
CHANGED
|
@@ -657,6 +657,44 @@ async function installPack(projectRoot, registryUrl, packName, options = {}) {
|
|
|
657
657
|
};
|
|
658
658
|
}
|
|
659
659
|
|
|
660
|
+
// src/fs/updateGeneratedBlock.ts
|
|
661
|
+
var GENERATED_BLOCK_START = "<!-- contextforge:start -->";
|
|
662
|
+
var GENERATED_BLOCK_END = "<!-- contextforge:end -->";
|
|
663
|
+
var blockPattern = new RegExp(
|
|
664
|
+
`${escapeRegExp(GENERATED_BLOCK_START)}[\\s\\S]*?${escapeRegExp(GENERATED_BLOCK_END)}`,
|
|
665
|
+
"m"
|
|
666
|
+
);
|
|
667
|
+
function escapeRegExp(value) {
|
|
668
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
669
|
+
}
|
|
670
|
+
function getGeneratedBlock(content) {
|
|
671
|
+
return content.match(blockPattern)?.[0] ?? null;
|
|
672
|
+
}
|
|
673
|
+
function removeGeneratedBlock(content) {
|
|
674
|
+
return content.replace(blockPattern, "").replace(/\s+$/u, "");
|
|
675
|
+
}
|
|
676
|
+
function wrapGeneratedBlock(content) {
|
|
677
|
+
return `${GENERATED_BLOCK_START}
|
|
678
|
+
${content.trim()}
|
|
679
|
+
${GENERATED_BLOCK_END}`;
|
|
680
|
+
}
|
|
681
|
+
function updateGeneratedBlock(existingContent, generatedContent) {
|
|
682
|
+
const existing = existingContent?.replace(/\s+$/u, "") ?? "";
|
|
683
|
+
const generatedBlock = getGeneratedBlock(generatedContent) ?? wrapGeneratedBlock(generatedContent);
|
|
684
|
+
if (!existing) {
|
|
685
|
+
return `${generatedContent.includes(GENERATED_BLOCK_START) ? generatedContent.trim() : generatedBlock}
|
|
686
|
+
`;
|
|
687
|
+
}
|
|
688
|
+
if (blockPattern.test(existing)) {
|
|
689
|
+
return `${existing.replace(blockPattern, generatedBlock)}
|
|
690
|
+
`;
|
|
691
|
+
}
|
|
692
|
+
return `${existing}
|
|
693
|
+
|
|
694
|
+
${generatedBlock}
|
|
695
|
+
`;
|
|
696
|
+
}
|
|
697
|
+
|
|
660
698
|
// src/compiler/compileOutputs.ts
|
|
661
699
|
function normalizeOutputPath(outputPath) {
|
|
662
700
|
return outputPath.split(/[\\/]/u).join("/");
|
|
@@ -664,11 +702,11 @@ function normalizeOutputPath(outputPath) {
|
|
|
664
702
|
function defaultOutput(packName, type) {
|
|
665
703
|
const defaults = {
|
|
666
704
|
rules: null,
|
|
667
|
-
agents: `.contextforge/
|
|
668
|
-
claude: `.contextforge/
|
|
669
|
-
skill: `.
|
|
670
|
-
cursor: `.cursor
|
|
671
|
-
copilot: `.
|
|
705
|
+
agents: `.contextforge/agents/codex/${packName}.md`,
|
|
706
|
+
claude: `.contextforge/agents/claude/${packName}.md`,
|
|
707
|
+
skill: `.contextforge/skills/${packName}/SKILL.md`,
|
|
708
|
+
cursor: `.contextforge/agents/cursor/${packName}.md`,
|
|
709
|
+
copilot: `.contextforge/agents/copilot/${packName}.md`
|
|
672
710
|
};
|
|
673
711
|
return defaults[type];
|
|
674
712
|
}
|
|
@@ -680,7 +718,7 @@ function shouldGenerateFile(type, tools) {
|
|
|
680
718
|
return tools.includes("claude");
|
|
681
719
|
}
|
|
682
720
|
if (type === "skill") {
|
|
683
|
-
return tools.
|
|
721
|
+
return tools.length > 0;
|
|
684
722
|
}
|
|
685
723
|
if (type === "cursor") {
|
|
686
724
|
return tools.includes("cursor");
|
|
@@ -691,67 +729,37 @@ function shouldGenerateFile(type, tools) {
|
|
|
691
729
|
return false;
|
|
692
730
|
}
|
|
693
731
|
function compileRootAgents(packs, analysis) {
|
|
694
|
-
const summaries = packs.map((pack) => pack.files.agents?.trim()).filter((summary) => Boolean(summary));
|
|
695
|
-
const gitSafetyWarning = packs.some((pack) => pack.manifest.name === "git-workflow") ? [
|
|
696
|
-
"## Git Safety",
|
|
697
|
-
"",
|
|
698
|
-
"Do not commit, push, merge, rebase, reset, delete branches, or rewrite history unless explicitly requested by the user.",
|
|
699
|
-
""
|
|
700
|
-
] : [];
|
|
701
732
|
return [
|
|
702
733
|
"# Project Agent Instructions",
|
|
703
734
|
"",
|
|
704
|
-
|
|
705
|
-
"",
|
|
706
|
-
"## Core Behavior",
|
|
707
|
-
"",
|
|
708
|
-
summaries.length > 0 ? summaries.join("\n\n") : "No pack-specific agent summaries are installed yet.",
|
|
709
|
-
"",
|
|
710
|
-
...gitSafetyWarning,
|
|
711
|
-
"## Installed Packs",
|
|
712
|
-
"",
|
|
713
|
-
...packs.length > 0 ? packs.map((pack) => `- ${pack.manifest.name}: ${pack.manifest.title}`) : ["- No packs installed"],
|
|
714
|
-
"",
|
|
715
|
-
"## Detected Project",
|
|
735
|
+
GENERATED_BLOCK_START,
|
|
736
|
+
"ContextForge is installed for this repo.",
|
|
716
737
|
"",
|
|
717
|
-
|
|
718
|
-
`- Language: ${analysis.language}`,
|
|
719
|
-
`- Package manager: ${analysis.packageManager}`,
|
|
738
|
+
"Before working, read the relevant instruction files in:",
|
|
720
739
|
"",
|
|
721
|
-
"
|
|
740
|
+
"- `.contextforge/agents/codex/`",
|
|
741
|
+
"- `.contextforge/skills/`",
|
|
722
742
|
"",
|
|
723
|
-
"
|
|
724
|
-
"
|
|
725
|
-
|
|
743
|
+
"Follow the installed packs listed in `.contextforge/config.json`.",
|
|
744
|
+
"Do not copy these instructions into this file.",
|
|
745
|
+
GENERATED_BLOCK_END
|
|
726
746
|
].join("\n");
|
|
727
747
|
}
|
|
728
748
|
function compileRootClaude(packs) {
|
|
729
|
-
const summaries = packs.map((pack) => pack.files.claude?.trim()).filter((summary) => Boolean(summary));
|
|
730
|
-
const gitSafetyWarning = packs.some((pack) => pack.manifest.name === "git-workflow") ? [
|
|
731
|
-
"## Git Safety",
|
|
732
|
-
"",
|
|
733
|
-
"Do not commit, push, merge, rebase, reset, delete branches, or rewrite history unless explicitly requested by the user.",
|
|
734
|
-
""
|
|
735
|
-
] : [];
|
|
736
749
|
return [
|
|
737
750
|
"# Claude Code Instructions",
|
|
738
751
|
"",
|
|
739
|
-
|
|
740
|
-
"",
|
|
741
|
-
"## Core Behavior",
|
|
742
|
-
"",
|
|
743
|
-
summaries.length > 0 ? summaries.join("\n\n") : "No pack-specific Claude summaries are installed yet.",
|
|
744
|
-
"",
|
|
745
|
-
...gitSafetyWarning,
|
|
746
|
-
"## Installed Packs",
|
|
752
|
+
GENERATED_BLOCK_START,
|
|
753
|
+
"ContextForge is installed for this repo.",
|
|
747
754
|
"",
|
|
748
|
-
|
|
755
|
+
"Before working, read the relevant instruction files in:",
|
|
749
756
|
"",
|
|
750
|
-
"
|
|
757
|
+
"- `.contextforge/agents/claude/`",
|
|
758
|
+
"- `.contextforge/skills/`",
|
|
751
759
|
"",
|
|
752
|
-
"
|
|
753
|
-
"
|
|
754
|
-
|
|
760
|
+
"Follow the installed packs listed in `.contextforge/config.json`.",
|
|
761
|
+
"Do not copy these instructions into this file.",
|
|
762
|
+
GENERATED_BLOCK_END
|
|
755
763
|
].join("\n");
|
|
756
764
|
}
|
|
757
765
|
function compileOutputs(config, packs, analysis) {
|
|
@@ -762,13 +770,13 @@ function compileOutputs(config, packs, analysis) {
|
|
|
762
770
|
continue;
|
|
763
771
|
}
|
|
764
772
|
const content = pack.files[file.type];
|
|
765
|
-
const outputPath =
|
|
773
|
+
const outputPath = defaultOutput(pack.manifest.name, file.type);
|
|
766
774
|
if (!content || !outputPath) {
|
|
767
775
|
continue;
|
|
768
776
|
}
|
|
769
777
|
outputs.push({
|
|
770
778
|
path: normalizeOutputPath(outputPath),
|
|
771
|
-
content
|
|
779
|
+
content: withContextForgePreamble(pack, file.type, content)
|
|
772
780
|
});
|
|
773
781
|
}
|
|
774
782
|
}
|
|
@@ -780,176 +788,94 @@ function compileOutputs(config, packs, analysis) {
|
|
|
780
788
|
}
|
|
781
789
|
return outputs;
|
|
782
790
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
import fs13 from "fs-extra";
|
|
787
|
-
|
|
788
|
-
// src/fs/updateGeneratedBlock.ts
|
|
789
|
-
var GENERATED_BLOCK_START = "<!-- contextforge:start -->";
|
|
790
|
-
var GENERATED_BLOCK_END = "<!-- contextforge:end -->";
|
|
791
|
-
var blockPattern = new RegExp(
|
|
792
|
-
`${escapeRegExp(GENERATED_BLOCK_START)}[\\s\\S]*?${escapeRegExp(GENERATED_BLOCK_END)}`,
|
|
793
|
-
"m"
|
|
794
|
-
);
|
|
795
|
-
function escapeRegExp(value) {
|
|
796
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
797
|
-
}
|
|
798
|
-
function getGeneratedBlock(content) {
|
|
799
|
-
return content.match(blockPattern)?.[0] ?? null;
|
|
800
|
-
}
|
|
801
|
-
function removeGeneratedBlock(content) {
|
|
802
|
-
return content.replace(blockPattern, "").replace(/\s+$/u, "");
|
|
803
|
-
}
|
|
804
|
-
function wrapGeneratedBlock(content) {
|
|
805
|
-
return `${GENERATED_BLOCK_START}
|
|
806
|
-
${content.trim()}
|
|
807
|
-
${GENERATED_BLOCK_END}`;
|
|
808
|
-
}
|
|
809
|
-
function updateGeneratedBlock(existingContent, generatedContent) {
|
|
810
|
-
const existing = existingContent?.replace(/\s+$/u, "") ?? "";
|
|
811
|
-
const generatedBlock = getGeneratedBlock(generatedContent) ?? wrapGeneratedBlock(generatedContent);
|
|
812
|
-
if (!existing) {
|
|
813
|
-
return `${generatedContent.includes(GENERATED_BLOCK_START) ? generatedContent.trim() : generatedBlock}
|
|
814
|
-
`;
|
|
791
|
+
function withContextForgePreamble(pack, type, content) {
|
|
792
|
+
if (pack.manifest.name !== "git-workflow" || !["agents", "claude", "cursor", "copilot"].includes(type)) {
|
|
793
|
+
return content;
|
|
815
794
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
795
|
+
const warning = "Do not commit, push, merge, rebase, reset, delete branches, or rewrite history unless explicitly requested by the user.";
|
|
796
|
+
if (content.includes(warning)) {
|
|
797
|
+
return content;
|
|
819
798
|
}
|
|
820
|
-
return
|
|
821
|
-
|
|
822
|
-
${generatedBlock}
|
|
823
|
-
`;
|
|
799
|
+
return ["# ContextForge Git Safety", "", warning, "", content.trim()].join("\n");
|
|
824
800
|
}
|
|
825
801
|
|
|
826
802
|
// src/compiler/compileAgentsMd.ts
|
|
827
|
-
|
|
828
|
-
async function readInstructionSummaries(root) {
|
|
829
|
-
const directory = path14.join(root, AGENTS_INSTRUCTIONS_DIR);
|
|
830
|
-
if (!await fs13.pathExists(directory)) {
|
|
831
|
-
return [];
|
|
832
|
-
}
|
|
833
|
-
const files = (await fs13.readdir(directory)).filter((file) => file.endsWith(".md")).sort((a, b) => a.localeCompare(b));
|
|
834
|
-
return Promise.all(files.map((file) => fs13.readFile(path14.join(directory, file), "utf8")));
|
|
835
|
-
}
|
|
836
|
-
async function compileAgentsMd(root, packs) {
|
|
837
|
-
const summaries = await readInstructionSummaries(root);
|
|
838
|
-
const gitSafetyWarning = packs.some((pack) => pack.manifest.name === "git-workflow") ? [
|
|
839
|
-
"## Git Safety",
|
|
840
|
-
"",
|
|
841
|
-
"Do not commit, push, merge, rebase, reset, delete branches, or rewrite history unless explicitly requested by the user.",
|
|
842
|
-
""
|
|
843
|
-
] : [];
|
|
803
|
+
async function compileAgentsMd() {
|
|
844
804
|
return [
|
|
845
805
|
"# Project Agent Instructions",
|
|
846
806
|
"",
|
|
847
807
|
GENERATED_BLOCK_START,
|
|
848
|
-
"
|
|
849
|
-
"",
|
|
850
|
-
"## Core Behavior",
|
|
851
|
-
"",
|
|
852
|
-
summaries.length > 0 ? summaries.map((summary) => summary.trim()).join("\n\n") : "No pack-specific agent summaries are installed yet.",
|
|
808
|
+
"ContextForge is installed for this repo.",
|
|
853
809
|
"",
|
|
854
|
-
|
|
855
|
-
"## Installed Packs",
|
|
810
|
+
"Before working, read the relevant instruction files in:",
|
|
856
811
|
"",
|
|
857
|
-
|
|
858
|
-
"",
|
|
859
|
-
"## Notes",
|
|
860
|
-
"",
|
|
861
|
-
"Detailed Codex skills live in `.agents/skills`.",
|
|
862
|
-
"Pack sources live in `.contextforge/packs`.",
|
|
863
|
-
"Pack-specific agent summaries live in `.contextforge/instructions/agents`.",
|
|
812
|
+
"- `.contextforge/agents/codex/`",
|
|
813
|
+
"- `.contextforge/skills/`",
|
|
864
814
|
"",
|
|
815
|
+
"Follow the installed packs listed in `.contextforge/config.json`.",
|
|
816
|
+
"Do not copy these instructions into this file.",
|
|
865
817
|
GENERATED_BLOCK_END
|
|
866
818
|
].join("\n");
|
|
867
819
|
}
|
|
868
820
|
|
|
869
821
|
// src/compiler/compileClaudeMd.ts
|
|
870
|
-
|
|
871
|
-
import fs14 from "fs-extra";
|
|
872
|
-
var CLAUDE_INSTRUCTIONS_DIR = ".contextforge/instructions/claude";
|
|
873
|
-
async function readInstructionSummaries2(root) {
|
|
874
|
-
const directory = path15.join(root, CLAUDE_INSTRUCTIONS_DIR);
|
|
875
|
-
if (!await fs14.pathExists(directory)) {
|
|
876
|
-
return [];
|
|
877
|
-
}
|
|
878
|
-
const files = (await fs14.readdir(directory)).filter((file) => file.endsWith(".md")).sort((a, b) => a.localeCompare(b));
|
|
879
|
-
return Promise.all(files.map((file) => fs14.readFile(path15.join(directory, file), "utf8")));
|
|
880
|
-
}
|
|
881
|
-
async function compileClaudeMd(root, packs) {
|
|
882
|
-
const summaries = await readInstructionSummaries2(root);
|
|
883
|
-
const gitSafetyWarning = packs.some((pack) => pack.manifest.name === "git-workflow") ? [
|
|
884
|
-
"## Git Safety",
|
|
885
|
-
"",
|
|
886
|
-
"Do not commit, push, merge, rebase, reset, delete branches, or rewrite history unless explicitly requested by the user.",
|
|
887
|
-
""
|
|
888
|
-
] : [];
|
|
822
|
+
async function compileClaudeMd() {
|
|
889
823
|
return [
|
|
890
824
|
"# Claude Code Instructions",
|
|
891
825
|
"",
|
|
892
826
|
GENERATED_BLOCK_START,
|
|
893
|
-
"
|
|
827
|
+
"ContextForge is installed for this repo.",
|
|
894
828
|
"",
|
|
895
|
-
"
|
|
829
|
+
"Before working, read the relevant instruction files in:",
|
|
896
830
|
"",
|
|
897
|
-
|
|
898
|
-
"",
|
|
899
|
-
...gitSafetyWarning,
|
|
900
|
-
"## Installed Packs",
|
|
901
|
-
"",
|
|
902
|
-
...packs.length > 0 ? packs.map((pack) => `- ${pack.manifest.name}: ${pack.manifest.title}`) : ["- No packs installed"],
|
|
903
|
-
"",
|
|
904
|
-
"## Notes",
|
|
905
|
-
"",
|
|
906
|
-
"Keep this file concise.",
|
|
907
|
-
"Detailed pack sources live in `.contextforge/packs`.",
|
|
908
|
-
"Pack-specific Claude summaries live in `.contextforge/instructions/claude`.",
|
|
831
|
+
"- `.contextforge/agents/claude/`",
|
|
832
|
+
"- `.contextforge/skills/`",
|
|
909
833
|
"",
|
|
834
|
+
"Follow the installed packs listed in `.contextforge/config.json`.",
|
|
835
|
+
"Do not copy these instructions into this file.",
|
|
910
836
|
GENERATED_BLOCK_END
|
|
911
837
|
].join("\n");
|
|
912
838
|
}
|
|
913
839
|
|
|
914
840
|
// src/compiler/generateToolOutputs.ts
|
|
915
|
-
import path18 from "path";
|
|
916
|
-
import fs17 from "fs-extra";
|
|
917
|
-
|
|
918
|
-
// src/fs/safeWriteFile.ts
|
|
919
841
|
import path16 from "path";
|
|
920
842
|
import fs15 from "fs-extra";
|
|
843
|
+
|
|
844
|
+
// src/fs/safeWriteFile.ts
|
|
845
|
+
import path14 from "path";
|
|
846
|
+
import fs13 from "fs-extra";
|
|
921
847
|
async function safeWriteFile(filePath, generatedContent) {
|
|
922
|
-
const existingContent = await
|
|
848
|
+
const existingContent = await fs13.pathExists(filePath) ? await fs13.readFile(filePath, "utf8") : null;
|
|
923
849
|
const nextContent = updateGeneratedBlock(existingContent, generatedContent);
|
|
924
|
-
await
|
|
925
|
-
await
|
|
850
|
+
await fs13.ensureDir(path14.dirname(filePath));
|
|
851
|
+
await fs13.writeFile(filePath, nextContent);
|
|
926
852
|
}
|
|
927
853
|
|
|
928
854
|
// src/compiler/cleanupGeneratedFiles.ts
|
|
929
|
-
import
|
|
930
|
-
import
|
|
855
|
+
import path15 from "path";
|
|
856
|
+
import fs14 from "fs-extra";
|
|
931
857
|
function isGeneratedOnlyPath(relativePath) {
|
|
932
|
-
return relativePath.startsWith(".agents/skills/") || relativePath.startsWith(".cursor/rules/");
|
|
858
|
+
return relativePath.startsWith(".agents/skills/") || relativePath.startsWith(".cursor/rules/") || relativePath.startsWith(".github/instructions/") || relativePath.startsWith(".contextforge/instructions/") || relativePath.startsWith(".contextforge/agents/") || relativePath.startsWith(".contextforge/skills/");
|
|
933
859
|
}
|
|
934
860
|
async function cleanupFile(root, relativePath) {
|
|
935
|
-
const filePath =
|
|
936
|
-
if (!await
|
|
861
|
+
const filePath = path15.join(root, relativePath);
|
|
862
|
+
if (!await fs14.pathExists(filePath)) {
|
|
937
863
|
return;
|
|
938
864
|
}
|
|
939
865
|
if (isGeneratedOnlyPath(relativePath)) {
|
|
940
|
-
await
|
|
866
|
+
await fs14.remove(filePath);
|
|
941
867
|
return;
|
|
942
868
|
}
|
|
943
|
-
const content = await
|
|
869
|
+
const content = await fs14.readFile(filePath, "utf8");
|
|
944
870
|
if (!getGeneratedBlock(content)) {
|
|
945
871
|
return;
|
|
946
872
|
}
|
|
947
873
|
const nextContent = removeGeneratedBlock(content);
|
|
948
874
|
if (nextContent.trim().length === 0) {
|
|
949
|
-
await
|
|
875
|
+
await fs14.remove(filePath);
|
|
950
876
|
return;
|
|
951
877
|
}
|
|
952
|
-
await
|
|
878
|
+
await fs14.writeFile(filePath, `${nextContent}
|
|
953
879
|
`);
|
|
954
880
|
}
|
|
955
881
|
async function cleanupStaleGeneratedFiles(root, previousFiles, currentFiles) {
|
|
@@ -962,10 +888,8 @@ async function cleanupStaleGeneratedFiles(root, previousFiles, currentFiles) {
|
|
|
962
888
|
|
|
963
889
|
// src/compiler/generateToolOutputs.ts
|
|
964
890
|
var GENERATED_ONLY_PREFIXES = [
|
|
965
|
-
".agents/
|
|
966
|
-
".
|
|
967
|
-
".github/instructions/",
|
|
968
|
-
".contextforge/instructions/"
|
|
891
|
+
".contextforge/agents/",
|
|
892
|
+
".contextforge/skills/"
|
|
969
893
|
];
|
|
970
894
|
function normalizeOutputPath2(outputPath) {
|
|
971
895
|
return outputPath.split(/[\\/]/u).join("/");
|
|
@@ -973,11 +897,11 @@ function normalizeOutputPath2(outputPath) {
|
|
|
973
897
|
function defaultOutput2(packName, type) {
|
|
974
898
|
const defaults = {
|
|
975
899
|
rules: null,
|
|
976
|
-
agents: `.contextforge/
|
|
977
|
-
claude: `.contextforge/
|
|
978
|
-
skill: `.
|
|
979
|
-
cursor: `.cursor
|
|
980
|
-
copilot: `.
|
|
900
|
+
agents: `.contextforge/agents/codex/${packName}.md`,
|
|
901
|
+
claude: `.contextforge/agents/claude/${packName}.md`,
|
|
902
|
+
skill: `.contextforge/skills/${packName}/SKILL.md`,
|
|
903
|
+
cursor: `.contextforge/agents/cursor/${packName}.md`,
|
|
904
|
+
copilot: `.contextforge/agents/copilot/${packName}.md`
|
|
981
905
|
};
|
|
982
906
|
return defaults[type];
|
|
983
907
|
}
|
|
@@ -989,7 +913,7 @@ function shouldGenerateFile2(type, tools) {
|
|
|
989
913
|
return tools.includes("claude");
|
|
990
914
|
}
|
|
991
915
|
if (type === "skill") {
|
|
992
|
-
return tools.
|
|
916
|
+
return tools.length > 0;
|
|
993
917
|
}
|
|
994
918
|
if (type === "cursor") {
|
|
995
919
|
return tools.includes("cursor");
|
|
@@ -1001,19 +925,29 @@ function shouldGenerateFile2(type, tools) {
|
|
|
1001
925
|
}
|
|
1002
926
|
function generatedFileFromPack(pack, file) {
|
|
1003
927
|
const content = pack.files[file.type];
|
|
1004
|
-
const outputPath =
|
|
928
|
+
const outputPath = defaultOutput2(pack.manifest.name, file.type);
|
|
1005
929
|
if (!content || !outputPath) {
|
|
1006
930
|
return null;
|
|
1007
931
|
}
|
|
1008
932
|
return {
|
|
1009
933
|
path: normalizeOutputPath2(outputPath),
|
|
1010
|
-
content
|
|
934
|
+
content: withContextForgePreamble2(pack, file.type, content)
|
|
1011
935
|
};
|
|
1012
936
|
}
|
|
937
|
+
function withContextForgePreamble2(pack, type, content) {
|
|
938
|
+
if (pack.manifest.name !== "git-workflow" || !["agents", "claude", "cursor", "copilot"].includes(type)) {
|
|
939
|
+
return content;
|
|
940
|
+
}
|
|
941
|
+
const warning = "Do not commit, push, merge, rebase, reset, delete branches, or rewrite history unless explicitly requested by the user.";
|
|
942
|
+
if (content.includes(warning)) {
|
|
943
|
+
return content;
|
|
944
|
+
}
|
|
945
|
+
return ["# ContextForge Git Safety", "", warning, "", content.trim()].join("\n");
|
|
946
|
+
}
|
|
1013
947
|
async function writeGeneratedOnlyFile(root, output) {
|
|
1014
|
-
const filePath =
|
|
1015
|
-
await
|
|
1016
|
-
await
|
|
948
|
+
const filePath = path16.join(root, output.path);
|
|
949
|
+
await fs15.ensureDir(path16.dirname(filePath));
|
|
950
|
+
await fs15.writeFile(filePath, output.content.endsWith("\n") ? output.content : `${output.content}
|
|
1017
951
|
`);
|
|
1018
952
|
}
|
|
1019
953
|
function packOutputs(pack, tools) {
|
|
@@ -1026,18 +960,18 @@ async function generateToolOutputs(root, packs, config, previousFiles = []) {
|
|
|
1026
960
|
if (isGeneratedOnly) {
|
|
1027
961
|
await writeGeneratedOnlyFile(root, output);
|
|
1028
962
|
} else {
|
|
1029
|
-
await safeWriteFile(
|
|
963
|
+
await safeWriteFile(path16.join(root, output.path), output.content);
|
|
1030
964
|
}
|
|
1031
965
|
}
|
|
1032
966
|
const rootOutputs = [];
|
|
1033
967
|
if (config.tools.includes("codex")) {
|
|
1034
|
-
rootOutputs.push({ path: "AGENTS.md", content: await compileAgentsMd(
|
|
968
|
+
rootOutputs.push({ path: "AGENTS.md", content: await compileAgentsMd() });
|
|
1035
969
|
}
|
|
1036
970
|
if (config.tools.includes("claude")) {
|
|
1037
|
-
rootOutputs.push({ path: "CLAUDE.md", content: await compileClaudeMd(
|
|
971
|
+
rootOutputs.push({ path: "CLAUDE.md", content: await compileClaudeMd() });
|
|
1038
972
|
}
|
|
1039
973
|
for (const output of rootOutputs) {
|
|
1040
|
-
await safeWriteFile(
|
|
974
|
+
await safeWriteFile(path16.join(root, output.path), output.content);
|
|
1041
975
|
}
|
|
1042
976
|
const currentFiles = [...packGeneratedOutputs, ...rootOutputs].map((output) => output.path);
|
|
1043
977
|
await cleanupStaleGeneratedFiles(root, previousFiles, currentFiles);
|
|
@@ -1045,19 +979,19 @@ async function generateToolOutputs(root, packs, config, previousFiles = []) {
|
|
|
1045
979
|
}
|
|
1046
980
|
|
|
1047
981
|
// src/compiler/writeGeneratedFiles.ts
|
|
1048
|
-
import
|
|
982
|
+
import path17 from "path";
|
|
1049
983
|
async function writeGeneratedFiles(root, outputs, previousFiles = []) {
|
|
1050
984
|
const currentFiles = outputs.map((output) => output.path);
|
|
1051
985
|
for (const output of outputs) {
|
|
1052
|
-
await safeWriteFile(
|
|
986
|
+
await safeWriteFile(path17.join(root, output.path), output.content);
|
|
1053
987
|
}
|
|
1054
988
|
await cleanupStaleGeneratedFiles(root, previousFiles, currentFiles);
|
|
1055
989
|
return currentFiles;
|
|
1056
990
|
}
|
|
1057
991
|
|
|
1058
992
|
// src/sync.ts
|
|
1059
|
-
import
|
|
1060
|
-
import
|
|
993
|
+
import path18 from "path";
|
|
994
|
+
import fs16 from "fs-extra";
|
|
1061
995
|
async function updateContextForgeConfig(projectRoot, packName, registryUrl, generatedFiles) {
|
|
1062
996
|
const existing = await loadOptionalConfig(projectRoot);
|
|
1063
997
|
const config = addPackToConfig(
|
|
@@ -1092,7 +1026,7 @@ async function loadInstalledPackFromRegistry(projectRoot, registryUrl, summary,
|
|
|
1092
1026
|
return downloadPackToContextForge(projectRoot, manifest.name, manifest, packUrl, timeoutMs);
|
|
1093
1027
|
}
|
|
1094
1028
|
async function syncInstalledPacks(projectRoot, providedConfig) {
|
|
1095
|
-
const root =
|
|
1029
|
+
const root = path18.resolve(projectRoot);
|
|
1096
1030
|
const analysis = await detectProject(root);
|
|
1097
1031
|
const previousConfig = await loadOptionalConfig(root);
|
|
1098
1032
|
const config = providedConfig ?? await loadConfig(root);
|
|
@@ -1144,7 +1078,7 @@ async function syncProject(root, providedConfig) {
|
|
|
1144
1078
|
return syncInstalledPacks(root, providedConfig);
|
|
1145
1079
|
}
|
|
1146
1080
|
async function installPackAndSync(projectRoot, registryUrl, packName, options = {}) {
|
|
1147
|
-
const root =
|
|
1081
|
+
const root = path18.resolve(projectRoot);
|
|
1148
1082
|
const config = await loadOptionalConfig(root);
|
|
1149
1083
|
const result = await installPack(root, registryUrl, packName, options);
|
|
1150
1084
|
if (result.alreadyInstalled && !options.force && config?.installedPacks.includes(packName)) {
|
|
@@ -1180,21 +1114,21 @@ async function readInstalledPacks(projectRoot) {
|
|
|
1180
1114
|
return loadProjectPacks(projectRoot);
|
|
1181
1115
|
}
|
|
1182
1116
|
async function pathExists(root, relativePath) {
|
|
1183
|
-
return
|
|
1117
|
+
return fs16.pathExists(path18.join(root, relativePath));
|
|
1184
1118
|
}
|
|
1185
1119
|
|
|
1186
1120
|
// src/doctor/doctorProject.ts
|
|
1187
|
-
import
|
|
1188
|
-
import
|
|
1121
|
+
import path19 from "path";
|
|
1122
|
+
import fs17 from "fs-extra";
|
|
1189
1123
|
async function fileExists(root, relativePath) {
|
|
1190
|
-
return
|
|
1124
|
+
return fs17.pathExists(path19.join(root, relativePath));
|
|
1191
1125
|
}
|
|
1192
1126
|
async function readIfExists(root, relativePath) {
|
|
1193
|
-
const filePath =
|
|
1194
|
-
if (!await
|
|
1127
|
+
const filePath = path19.join(root, relativePath);
|
|
1128
|
+
if (!await fs17.pathExists(filePath)) {
|
|
1195
1129
|
return null;
|
|
1196
1130
|
}
|
|
1197
|
-
return
|
|
1131
|
+
return fs17.readFile(filePath, "utf8");
|
|
1198
1132
|
}
|
|
1199
1133
|
function hasContextForgeBlock(content) {
|
|
1200
1134
|
return Boolean(content && getGeneratedBlock(content));
|
|
@@ -1204,27 +1138,27 @@ function tooLarge(content) {
|
|
|
1204
1138
|
}
|
|
1205
1139
|
function expectedGeneratedOutputs(packName, tools) {
|
|
1206
1140
|
const outputs = [];
|
|
1207
|
-
if (tools.
|
|
1208
|
-
outputs.push(`.
|
|
1209
|
-
}
|
|
1210
|
-
if (tools.includes("cursor")) {
|
|
1211
|
-
outputs.push(`.cursor/rules/${packName}.mdc`);
|
|
1212
|
-
}
|
|
1213
|
-
if (tools.includes("copilot")) {
|
|
1214
|
-
outputs.push(`.github/instructions/${packName}.instructions.md`);
|
|
1141
|
+
if (tools.length > 0) {
|
|
1142
|
+
outputs.push(`.contextforge/skills/${packName}/SKILL.md`);
|
|
1215
1143
|
}
|
|
1216
1144
|
if (tools.includes("codex")) {
|
|
1217
|
-
outputs.push(`.contextforge/
|
|
1145
|
+
outputs.push(`.contextforge/agents/codex/${packName}.md`);
|
|
1218
1146
|
}
|
|
1219
1147
|
if (tools.includes("claude")) {
|
|
1220
|
-
outputs.push(`.contextforge/
|
|
1148
|
+
outputs.push(`.contextforge/agents/claude/${packName}.md`);
|
|
1149
|
+
}
|
|
1150
|
+
if (tools.includes("cursor")) {
|
|
1151
|
+
outputs.push(`.contextforge/agents/cursor/${packName}.md`);
|
|
1152
|
+
}
|
|
1153
|
+
if (tools.includes("copilot")) {
|
|
1154
|
+
outputs.push(`.contextforge/agents/copilot/${packName}.md`);
|
|
1221
1155
|
}
|
|
1222
1156
|
return outputs;
|
|
1223
1157
|
}
|
|
1224
1158
|
async function doctorProject(root, options = {}) {
|
|
1225
1159
|
const checks = [];
|
|
1226
1160
|
const issues = [];
|
|
1227
|
-
const resolvedRoot =
|
|
1161
|
+
const resolvedRoot = path19.resolve(root);
|
|
1228
1162
|
if (!await fileExists(resolvedRoot, CONFIG_PATH)) {
|
|
1229
1163
|
return {
|
|
1230
1164
|
checks,
|
|
@@ -1324,7 +1258,18 @@ async function doctorProject(root, options = {}) {
|
|
|
1324
1258
|
}
|
|
1325
1259
|
const gitWorkflow = cachedPacks.find((pack) => pack.manifest.name === "git-workflow");
|
|
1326
1260
|
if (gitWorkflow) {
|
|
1327
|
-
const
|
|
1261
|
+
const generatedGitFiles = await Promise.all(
|
|
1262
|
+
["codex", "claude", "cursor", "copilot"].map(
|
|
1263
|
+
(tool) => readIfExists(resolvedRoot, `.contextforge/agents/${tool}/git-workflow.md`)
|
|
1264
|
+
)
|
|
1265
|
+
);
|
|
1266
|
+
const gitSummary = [
|
|
1267
|
+
gitWorkflow.files.agents,
|
|
1268
|
+
gitWorkflow.files.claude,
|
|
1269
|
+
...generatedGitFiles,
|
|
1270
|
+
agentsMd,
|
|
1271
|
+
claudeMd
|
|
1272
|
+
].filter((content) => Boolean(content)).join("\n").toLowerCase();
|
|
1328
1273
|
if (!gitSummary.includes("do not commit") || !gitSummary.includes("push") || !gitSummary.includes("explicitly")) {
|
|
1329
1274
|
issues.push({
|
|
1330
1275
|
level: "warning",
|