@contextforge/core 0.1.7 → 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 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(projectRoot: string, packName: string, packManifest: PackManifest, packUrl: string, timeoutMs?: number): Promise<InstalledPack>;
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(projectRoot: string, registryUrl: string, packName: string, options?: InstallPackOptions): Promise<InstallPackResult>;
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(packRoot2) {
251
- const manifestPath = path7.join(packRoot2, "pack.json");
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(packRoot2, normalizePackFilePath(file)));
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
- import path13 from "path";
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(projectRoot, registryUrl, packName, options = {}) {
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 path16 from "path";
842
- import fs15 from "fs-extra";
817
+ import path15 from "path";
818
+ import fs14 from "fs-extra";
843
819
 
844
820
  // src/fs/safeWriteFile.ts
845
- import path14 from "path";
846
- import fs13 from "fs-extra";
821
+ import path13 from "path";
822
+ import fs12 from "fs-extra";
847
823
  async function safeWriteFile(filePath, generatedContent) {
848
- const existingContent = await fs13.pathExists(filePath) ? await fs13.readFile(filePath, "utf8") : null;
824
+ const existingContent = await fs12.pathExists(filePath) ? await fs12.readFile(filePath, "utf8") : null;
849
825
  const nextContent = updateGeneratedBlock(existingContent, generatedContent);
850
- await fs13.ensureDir(path14.dirname(filePath));
851
- await fs13.writeFile(filePath, nextContent);
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 path15 from "path";
856
- import fs14 from "fs-extra";
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 = path15.join(root, relativePath);
862
- if (!await fs14.pathExists(filePath)) {
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 fs14.remove(filePath);
842
+ await fs13.remove(filePath);
867
843
  return;
868
844
  }
869
- const content = await fs14.readFile(filePath, "utf8");
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 fs14.remove(filePath);
851
+ await fs13.remove(filePath);
876
852
  return;
877
853
  }
878
- await fs14.writeFile(filePath, `${nextContent}
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 = 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}
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(path16.join(root, output.path), output.content);
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(path16.join(root, output.path), output.content);
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 path17 from "path";
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(path17.join(root, output.path), output.content);
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 path18 from "path";
994
- import fs16 from "fs-extra";
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 = path18.resolve(projectRoot);
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 = path18.resolve(projectRoot);
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
- return loadProjectPacks(projectRoot);
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 fs16.pathExists(path18.join(root, relativePath));
1106
+ return fs15.pathExists(path17.join(root, relativePath));
1118
1107
  }
1119
1108
 
1120
1109
  // src/doctor/doctorProject.ts
1121
- import path19 from "path";
1122
- import fs17 from "fs-extra";
1110
+ import path18 from "path";
1111
+ import fs16 from "fs-extra";
1123
1112
  async function fileExists(root, relativePath) {
1124
- return fs17.pathExists(path19.join(root, relativePath));
1113
+ return fs16.pathExists(path18.join(root, relativePath));
1125
1114
  }
1126
1115
  async function readIfExists(root, relativePath) {
1127
- const filePath = path19.join(root, relativePath);
1128
- if (!await fs17.pathExists(filePath)) {
1116
+ const filePath = path18.join(root, relativePath);
1117
+ if (!await fs16.pathExists(filePath)) {
1129
1118
  return null;
1130
1119
  }
1131
- return fs17.readFile(filePath, "utf8");
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 = path19.resolve(root);
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 (!cachedPackNames.has(packName)) {
1196
+ if (lock && !lock.packs[packName]) {
1210
1197
  issues.push({
1211
- level: "error",
1212
- message: `.contextforge/packs/${packName}/pack.json is missing. Run \`npx @contextforge/cli sync\`.`
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
- const gitWorkflow = cachedPacks.find((pack) => pack.manifest.name === "git-workflow");
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextforge/core",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",