@contextforge/core 0.1.6 → 0.1.8
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 +6 -2
- package/dist/index.js +68 -93
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -327,9 +327,13 @@ declare const LockSchema: z.ZodObject<{
|
|
|
327
327
|
registry: z.ZodString;
|
|
328
328
|
resolvedAt: z.ZodString;
|
|
329
329
|
packs: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
330
|
+
title: z.ZodOptional<z.ZodString>;
|
|
330
331
|
version: z.ZodString;
|
|
332
|
+
topic: z.ZodOptional<z.ZodString>;
|
|
333
|
+
classification: z.ZodOptional<z.ZodString>;
|
|
331
334
|
path: z.ZodString;
|
|
332
335
|
source: z.ZodString;
|
|
336
|
+
files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
333
337
|
}, z.core.$strip>>;
|
|
334
338
|
}, z.core.$strip>;
|
|
335
339
|
type ContextForgeLock = z.infer<typeof LockSchema>;
|
|
@@ -347,7 +351,7 @@ declare function loadConfig(root: string): Promise<ContextForgeConfig>;
|
|
|
347
351
|
declare function saveConfig(root: string, config: ContextForgeConfig): Promise<void>;
|
|
348
352
|
declare function addPackToConfig(config: ContextForgeConfig, packName: string): ContextForgeConfig;
|
|
349
353
|
|
|
350
|
-
declare function downloadPackToContextForge(
|
|
354
|
+
declare function downloadPackToContextForge(_projectRoot: string, _packName: string, packManifest: PackManifest, packUrl: string, timeoutMs?: number): Promise<InstalledPack>;
|
|
351
355
|
type InstallPackOptions = {
|
|
352
356
|
force?: boolean;
|
|
353
357
|
dryRun?: boolean;
|
|
@@ -361,7 +365,7 @@ type InstallPackResult = {
|
|
|
361
365
|
summary?: RegistryPackSummary;
|
|
362
366
|
packUrl?: string;
|
|
363
367
|
};
|
|
364
|
-
declare function installPack(
|
|
368
|
+
declare function installPack(_projectRoot: string, registryUrl: string, packName: string, options?: InstallPackOptions): Promise<InstallPackResult>;
|
|
365
369
|
|
|
366
370
|
declare function compileOutputs(config: ContextForgeConfig, packs: InstalledPack[], analysis: ProjectAnalysis): GeneratedFile[];
|
|
367
371
|
|
package/dist/index.js
CHANGED
|
@@ -247,15 +247,15 @@ async function readOptional(filePath) {
|
|
|
247
247
|
function normalizePackFilePath(file) {
|
|
248
248
|
return file.path.split(/[\\/]/u).join(path7.sep);
|
|
249
249
|
}
|
|
250
|
-
async function loadCachedPack(
|
|
251
|
-
const manifestPath = path7.join(
|
|
250
|
+
async function loadCachedPack(packRoot) {
|
|
251
|
+
const manifestPath = path7.join(packRoot, "pack.json");
|
|
252
252
|
if (!await fs7.pathExists(manifestPath)) {
|
|
253
253
|
return null;
|
|
254
254
|
}
|
|
255
255
|
const manifest = PackManifestSchema.parse(await fs7.readJson(manifestPath));
|
|
256
256
|
const files = {};
|
|
257
257
|
for (const file of manifest.files) {
|
|
258
|
-
const content = await readOptional(path7.join(
|
|
258
|
+
const content = await readOptional(path7.join(packRoot, normalizePackFilePath(file)));
|
|
259
259
|
if (content !== void 0) {
|
|
260
260
|
files[file.type] = content;
|
|
261
261
|
}
|
|
@@ -530,9 +530,13 @@ var LockSchema = z3.object({
|
|
|
530
530
|
packs: z3.record(
|
|
531
531
|
z3.string(),
|
|
532
532
|
z3.object({
|
|
533
|
+
title: z3.string().optional(),
|
|
533
534
|
version: z3.string(),
|
|
535
|
+
topic: z3.string().optional(),
|
|
536
|
+
classification: z3.string().optional(),
|
|
534
537
|
path: z3.string(),
|
|
535
|
-
source: z3.string()
|
|
538
|
+
source: z3.string(),
|
|
539
|
+
files: z3.array(z3.string()).optional()
|
|
536
540
|
})
|
|
537
541
|
)
|
|
538
542
|
});
|
|
@@ -557,9 +561,13 @@ async function updateContextForgeLock(root, registry, installed) {
|
|
|
557
561
|
};
|
|
558
562
|
for (const pack of installed) {
|
|
559
563
|
lock.packs[pack.manifest.name] = {
|
|
564
|
+
title: pack.manifest.title,
|
|
560
565
|
version: pack.manifest.version,
|
|
566
|
+
topic: pack.manifest.topic,
|
|
567
|
+
classification: pack.manifest.classification,
|
|
561
568
|
path: pack.summary?.path ?? `packs/${pack.manifest.name}/pack.json`,
|
|
562
|
-
source: pack.packUrl ?? ""
|
|
569
|
+
source: pack.packUrl ?? "",
|
|
570
|
+
files: pack.manifest.files.map((file) => file.type)
|
|
563
571
|
};
|
|
564
572
|
}
|
|
565
573
|
await saveLock(root, lock);
|
|
@@ -595,28 +603,10 @@ function addPackToConfig(config, packName) {
|
|
|
595
603
|
}
|
|
596
604
|
|
|
597
605
|
// src/registry/installPacks.ts
|
|
598
|
-
|
|
599
|
-
import fs12 from "fs-extra";
|
|
600
|
-
function normalizeRelativePath(relativePath) {
|
|
601
|
-
return relativePath.split(/[\\/]/u).join(path13.sep);
|
|
602
|
-
}
|
|
603
|
-
function packRoot(projectRoot, packName) {
|
|
604
|
-
return path13.join(projectRoot, PROJECT_PACK_CACHE, packName);
|
|
605
|
-
}
|
|
606
|
-
function packFilePath(projectRoot, packName, file) {
|
|
607
|
-
return path13.join(packRoot(projectRoot, packName), normalizeRelativePath(file.path));
|
|
608
|
-
}
|
|
609
|
-
async function downloadPackToContextForge(projectRoot, packName, packManifest, packUrl, timeoutMs) {
|
|
610
|
-
const root = packRoot(projectRoot, packName);
|
|
606
|
+
async function downloadPackToContextForge(_projectRoot, _packName, packManifest, packUrl, timeoutMs) {
|
|
611
607
|
const files = {};
|
|
612
|
-
await fs12.ensureDir(root);
|
|
613
|
-
await fs12.writeFile(path13.join(root, "pack.json"), `${JSON.stringify(packManifest, null, 2)}
|
|
614
|
-
`);
|
|
615
608
|
for (const file of packManifest.files) {
|
|
616
609
|
const content = await fetchPackFile(packUrl, file, timeoutMs);
|
|
617
|
-
const targetPath = packFilePath(projectRoot, packName, file);
|
|
618
|
-
await fs12.ensureDir(path13.dirname(targetPath));
|
|
619
|
-
await fs12.writeFile(targetPath, content);
|
|
620
610
|
files[file.type] = content;
|
|
621
611
|
}
|
|
622
612
|
return {
|
|
@@ -625,31 +615,17 @@ async function downloadPackToContextForge(projectRoot, packName, packManifest, p
|
|
|
625
615
|
files
|
|
626
616
|
};
|
|
627
617
|
}
|
|
628
|
-
async function installPack(
|
|
618
|
+
async function installPack(_projectRoot, registryUrl, packName, options = {}) {
|
|
629
619
|
const registry = await fetchRegistry(registryUrl, options.timeoutMs);
|
|
630
620
|
const summary = findPackSummary(registry, packName);
|
|
631
621
|
if (!summary) {
|
|
632
622
|
throw new Error(`Unknown ContextForge pack: ${packName}`);
|
|
633
623
|
}
|
|
634
|
-
const alreadyInstalled = await fs12.pathExists(path13.join(packRoot(projectRoot, packName), "pack.json"));
|
|
635
624
|
const packUrl = resolvePackUrl(registryUrl, summary.path);
|
|
636
625
|
const manifest = await fetchPackManifest(packUrl, options.timeoutMs);
|
|
637
|
-
if (alreadyInstalled && !options.force) {
|
|
638
|
-
return {
|
|
639
|
-
installed: false,
|
|
640
|
-
alreadyInstalled: true,
|
|
641
|
-
packName,
|
|
642
|
-
manifest,
|
|
643
|
-
summary,
|
|
644
|
-
packUrl
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
if (!options.dryRun) {
|
|
648
|
-
await downloadPackToContextForge(projectRoot, packName, manifest, packUrl, options.timeoutMs);
|
|
649
|
-
}
|
|
650
626
|
return {
|
|
651
627
|
installed: !options.dryRun,
|
|
652
|
-
alreadyInstalled,
|
|
628
|
+
alreadyInstalled: false,
|
|
653
629
|
packName,
|
|
654
630
|
manifest,
|
|
655
631
|
summary,
|
|
@@ -838,44 +814,44 @@ async function compileClaudeMd() {
|
|
|
838
814
|
}
|
|
839
815
|
|
|
840
816
|
// src/compiler/generateToolOutputs.ts
|
|
841
|
-
import
|
|
842
|
-
import
|
|
817
|
+
import path15 from "path";
|
|
818
|
+
import fs14 from "fs-extra";
|
|
843
819
|
|
|
844
820
|
// src/fs/safeWriteFile.ts
|
|
845
|
-
import
|
|
846
|
-
import
|
|
821
|
+
import path13 from "path";
|
|
822
|
+
import fs12 from "fs-extra";
|
|
847
823
|
async function safeWriteFile(filePath, generatedContent) {
|
|
848
|
-
const existingContent = await
|
|
824
|
+
const existingContent = await fs12.pathExists(filePath) ? await fs12.readFile(filePath, "utf8") : null;
|
|
849
825
|
const nextContent = updateGeneratedBlock(existingContent, generatedContent);
|
|
850
|
-
await
|
|
851
|
-
await
|
|
826
|
+
await fs12.ensureDir(path13.dirname(filePath));
|
|
827
|
+
await fs12.writeFile(filePath, nextContent);
|
|
852
828
|
}
|
|
853
829
|
|
|
854
830
|
// src/compiler/cleanupGeneratedFiles.ts
|
|
855
|
-
import
|
|
856
|
-
import
|
|
831
|
+
import path14 from "path";
|
|
832
|
+
import fs13 from "fs-extra";
|
|
857
833
|
function isGeneratedOnlyPath(relativePath) {
|
|
858
834
|
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/");
|
|
859
835
|
}
|
|
860
836
|
async function cleanupFile(root, relativePath) {
|
|
861
|
-
const filePath =
|
|
862
|
-
if (!await
|
|
837
|
+
const filePath = path14.join(root, relativePath);
|
|
838
|
+
if (!await fs13.pathExists(filePath)) {
|
|
863
839
|
return;
|
|
864
840
|
}
|
|
865
841
|
if (isGeneratedOnlyPath(relativePath)) {
|
|
866
|
-
await
|
|
842
|
+
await fs13.remove(filePath);
|
|
867
843
|
return;
|
|
868
844
|
}
|
|
869
|
-
const content = await
|
|
845
|
+
const content = await fs13.readFile(filePath, "utf8");
|
|
870
846
|
if (!getGeneratedBlock(content)) {
|
|
871
847
|
return;
|
|
872
848
|
}
|
|
873
849
|
const nextContent = removeGeneratedBlock(content);
|
|
874
850
|
if (nextContent.trim().length === 0) {
|
|
875
|
-
await
|
|
851
|
+
await fs13.remove(filePath);
|
|
876
852
|
return;
|
|
877
853
|
}
|
|
878
|
-
await
|
|
854
|
+
await fs13.writeFile(filePath, `${nextContent}
|
|
879
855
|
`);
|
|
880
856
|
}
|
|
881
857
|
async function cleanupStaleGeneratedFiles(root, previousFiles, currentFiles) {
|
|
@@ -945,9 +921,9 @@ function withContextForgePreamble2(pack, type, content) {
|
|
|
945
921
|
return ["# ContextForge Git Safety", "", warning, "", content.trim()].join("\n");
|
|
946
922
|
}
|
|
947
923
|
async function writeGeneratedOnlyFile(root, output) {
|
|
948
|
-
const filePath =
|
|
949
|
-
await
|
|
950
|
-
await
|
|
924
|
+
const filePath = path15.join(root, output.path);
|
|
925
|
+
await fs14.ensureDir(path15.dirname(filePath));
|
|
926
|
+
await fs14.writeFile(filePath, output.content.endsWith("\n") ? output.content : `${output.content}
|
|
951
927
|
`);
|
|
952
928
|
}
|
|
953
929
|
function packOutputs(pack, tools) {
|
|
@@ -960,7 +936,7 @@ async function generateToolOutputs(root, packs, config, previousFiles = []) {
|
|
|
960
936
|
if (isGeneratedOnly) {
|
|
961
937
|
await writeGeneratedOnlyFile(root, output);
|
|
962
938
|
} else {
|
|
963
|
-
await safeWriteFile(
|
|
939
|
+
await safeWriteFile(path15.join(root, output.path), output.content);
|
|
964
940
|
}
|
|
965
941
|
}
|
|
966
942
|
const rootOutputs = [];
|
|
@@ -971,7 +947,7 @@ async function generateToolOutputs(root, packs, config, previousFiles = []) {
|
|
|
971
947
|
rootOutputs.push({ path: "CLAUDE.md", content: await compileClaudeMd() });
|
|
972
948
|
}
|
|
973
949
|
for (const output of rootOutputs) {
|
|
974
|
-
await safeWriteFile(
|
|
950
|
+
await safeWriteFile(path15.join(root, output.path), output.content);
|
|
975
951
|
}
|
|
976
952
|
const currentFiles = [...packGeneratedOutputs, ...rootOutputs].map((output) => output.path);
|
|
977
953
|
await cleanupStaleGeneratedFiles(root, previousFiles, currentFiles);
|
|
@@ -979,19 +955,19 @@ async function generateToolOutputs(root, packs, config, previousFiles = []) {
|
|
|
979
955
|
}
|
|
980
956
|
|
|
981
957
|
// src/compiler/writeGeneratedFiles.ts
|
|
982
|
-
import
|
|
958
|
+
import path16 from "path";
|
|
983
959
|
async function writeGeneratedFiles(root, outputs, previousFiles = []) {
|
|
984
960
|
const currentFiles = outputs.map((output) => output.path);
|
|
985
961
|
for (const output of outputs) {
|
|
986
|
-
await safeWriteFile(
|
|
962
|
+
await safeWriteFile(path16.join(root, output.path), output.content);
|
|
987
963
|
}
|
|
988
964
|
await cleanupStaleGeneratedFiles(root, previousFiles, currentFiles);
|
|
989
965
|
return currentFiles;
|
|
990
966
|
}
|
|
991
967
|
|
|
992
968
|
// src/sync.ts
|
|
993
|
-
import
|
|
994
|
-
import
|
|
969
|
+
import path17 from "path";
|
|
970
|
+
import fs15 from "fs-extra";
|
|
995
971
|
async function updateContextForgeConfig(projectRoot, packName, registryUrl, generatedFiles) {
|
|
996
972
|
const existing = await loadOptionalConfig(projectRoot);
|
|
997
973
|
const config = addPackToConfig(
|
|
@@ -1025,8 +1001,11 @@ async function loadInstalledPackFromRegistry(projectRoot, registryUrl, summary,
|
|
|
1025
1001
|
const manifest = await fetchPackManifest(packUrl, timeoutMs);
|
|
1026
1002
|
return downloadPackToContextForge(projectRoot, manifest.name, manifest, packUrl, timeoutMs);
|
|
1027
1003
|
}
|
|
1004
|
+
async function removeLegacyPackCache(root) {
|
|
1005
|
+
await fs15.remove(path17.join(root, ".contextforge/packs"));
|
|
1006
|
+
}
|
|
1028
1007
|
async function syncInstalledPacks(projectRoot, providedConfig) {
|
|
1029
|
-
const root =
|
|
1008
|
+
const root = path17.resolve(projectRoot);
|
|
1030
1009
|
const analysis = await detectProject(root);
|
|
1031
1010
|
const previousConfig = await loadOptionalConfig(root);
|
|
1032
1011
|
const config = providedConfig ?? await loadConfig(root);
|
|
@@ -1041,6 +1020,7 @@ async function syncInstalledPacks(projectRoot, providedConfig) {
|
|
|
1041
1020
|
}
|
|
1042
1021
|
installed.push(await loadInstalledPackFromRegistry(root, config.registry, summary));
|
|
1043
1022
|
}
|
|
1023
|
+
await removeLegacyPackCache(root);
|
|
1044
1024
|
const generatedFiles = await generateToolOutputs(
|
|
1045
1025
|
root,
|
|
1046
1026
|
installed,
|
|
@@ -1078,7 +1058,7 @@ async function syncProject(root, providedConfig) {
|
|
|
1078
1058
|
return syncInstalledPacks(root, providedConfig);
|
|
1079
1059
|
}
|
|
1080
1060
|
async function installPackAndSync(projectRoot, registryUrl, packName, options = {}) {
|
|
1081
|
-
const root =
|
|
1061
|
+
const root = path17.resolve(projectRoot);
|
|
1082
1062
|
const config = await loadOptionalConfig(root);
|
|
1083
1063
|
const result = await installPack(root, registryUrl, packName, options);
|
|
1084
1064
|
if (result.alreadyInstalled && !options.force && config?.installedPacks.includes(packName)) {
|
|
@@ -1111,24 +1091,33 @@ async function installPackAndSync(projectRoot, registryUrl, packName, options =
|
|
|
1111
1091
|
};
|
|
1112
1092
|
}
|
|
1113
1093
|
async function readInstalledPacks(projectRoot) {
|
|
1114
|
-
|
|
1094
|
+
const config = await loadConfig(projectRoot);
|
|
1095
|
+
const registry = await fetchRegistry(config.registry);
|
|
1096
|
+
const installed = [];
|
|
1097
|
+
for (const packName of config.installedPacks) {
|
|
1098
|
+
const summary = findPackSummary(registry, packName);
|
|
1099
|
+
if (summary) {
|
|
1100
|
+
installed.push(await loadInstalledPackFromRegistry(projectRoot, config.registry, summary));
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
return installed;
|
|
1115
1104
|
}
|
|
1116
1105
|
async function pathExists(root, relativePath) {
|
|
1117
|
-
return
|
|
1106
|
+
return fs15.pathExists(path17.join(root, relativePath));
|
|
1118
1107
|
}
|
|
1119
1108
|
|
|
1120
1109
|
// src/doctor/doctorProject.ts
|
|
1121
|
-
import
|
|
1122
|
-
import
|
|
1110
|
+
import path18 from "path";
|
|
1111
|
+
import fs16 from "fs-extra";
|
|
1123
1112
|
async function fileExists(root, relativePath) {
|
|
1124
|
-
return
|
|
1113
|
+
return fs16.pathExists(path18.join(root, relativePath));
|
|
1125
1114
|
}
|
|
1126
1115
|
async function readIfExists(root, relativePath) {
|
|
1127
|
-
const filePath =
|
|
1128
|
-
if (!await
|
|
1116
|
+
const filePath = path18.join(root, relativePath);
|
|
1117
|
+
if (!await fs16.pathExists(filePath)) {
|
|
1129
1118
|
return null;
|
|
1130
1119
|
}
|
|
1131
|
-
return
|
|
1120
|
+
return fs16.readFile(filePath, "utf8");
|
|
1132
1121
|
}
|
|
1133
1122
|
function hasContextForgeBlock(content) {
|
|
1134
1123
|
return Boolean(content && getGeneratedBlock(content));
|
|
@@ -1158,7 +1147,7 @@ function expectedGeneratedOutputs(packName, tools) {
|
|
|
1158
1147
|
async function doctorProject(root, options = {}) {
|
|
1159
1148
|
const checks = [];
|
|
1160
1149
|
const issues = [];
|
|
1161
|
-
const resolvedRoot =
|
|
1150
|
+
const resolvedRoot = path18.resolve(root);
|
|
1162
1151
|
if (!await fileExists(resolvedRoot, CONFIG_PATH)) {
|
|
1163
1152
|
return {
|
|
1164
1153
|
checks,
|
|
@@ -1197,8 +1186,6 @@ async function doctorProject(root, options = {}) {
|
|
|
1197
1186
|
message: `${LOCK_PATH} is missing. Run \`npx @contextforge/cli sync\`.`
|
|
1198
1187
|
});
|
|
1199
1188
|
}
|
|
1200
|
-
const cachedPacks = await loadProjectPacks(resolvedRoot);
|
|
1201
|
-
const cachedPackNames = new Set(cachedPacks.map((pack) => pack.manifest.name));
|
|
1202
1189
|
for (const packName of config.installedPacks) {
|
|
1203
1190
|
if (registryPacks.size > 0 && !registryPacks.has(packName)) {
|
|
1204
1191
|
issues.push({
|
|
@@ -1206,12 +1193,11 @@ async function doctorProject(root, options = {}) {
|
|
|
1206
1193
|
message: `${packName} is installed in config, but no longer exists in the registry.`
|
|
1207
1194
|
});
|
|
1208
1195
|
}
|
|
1209
|
-
if (!
|
|
1196
|
+
if (lock && !lock.packs[packName]) {
|
|
1210
1197
|
issues.push({
|
|
1211
|
-
level: "
|
|
1212
|
-
message:
|
|
1198
|
+
level: "warning",
|
|
1199
|
+
message: `${packName} is installed in config, but missing from ${LOCK_PATH}. Run \`npx @contextforge/cli sync\`.`
|
|
1213
1200
|
});
|
|
1214
|
-
continue;
|
|
1215
1201
|
}
|
|
1216
1202
|
for (const output of expectedGeneratedOutputs(packName, config.tools)) {
|
|
1217
1203
|
if (!await fileExists(resolvedRoot, output)) {
|
|
@@ -1256,16 +1242,13 @@ async function doctorProject(root, options = {}) {
|
|
|
1256
1242
|
message: "CLAUDE.md is large. Root instructions should stay concise; detailed content belongs in pack files."
|
|
1257
1243
|
});
|
|
1258
1244
|
}
|
|
1259
|
-
|
|
1260
|
-
if (gitWorkflow) {
|
|
1245
|
+
if (config.installedPacks.includes("git-workflow")) {
|
|
1261
1246
|
const generatedGitFiles = await Promise.all(
|
|
1262
1247
|
["codex", "claude", "cursor", "copilot"].map(
|
|
1263
1248
|
(tool) => readIfExists(resolvedRoot, `.contextforge/agents/${tool}/git-workflow.md`)
|
|
1264
1249
|
)
|
|
1265
1250
|
);
|
|
1266
1251
|
const gitSummary = [
|
|
1267
|
-
gitWorkflow.files.agents,
|
|
1268
|
-
gitWorkflow.files.claude,
|
|
1269
1252
|
...generatedGitFiles,
|
|
1270
1253
|
agentsMd,
|
|
1271
1254
|
claudeMd
|
|
@@ -1277,14 +1260,6 @@ async function doctorProject(root, options = {}) {
|
|
|
1277
1260
|
});
|
|
1278
1261
|
}
|
|
1279
1262
|
}
|
|
1280
|
-
for (const pack of cachedPacks) {
|
|
1281
|
-
if (!await packMatchesProject(pack, resolvedRoot, packageJson)) {
|
|
1282
|
-
issues.push({
|
|
1283
|
-
level: "warning",
|
|
1284
|
-
message: `${pack.manifest.name} is installed, but its detection hints do not match this project.`
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
1263
|
if (config.installedPacks.includes("test-driven-development") && !hasScript(packageJson, "test")) {
|
|
1289
1264
|
issues.push({
|
|
1290
1265
|
level: "warning",
|