@alxyrgin/agent-forge 3.0.0 → 3.2.0

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.js CHANGED
@@ -470,7 +470,8 @@ var EXTRA_SKILLS = [
470
470
  "security",
471
471
  "spec",
472
472
  "techspec",
473
- "prompts"
473
+ "prompts",
474
+ "sync-linear"
474
475
  ];
475
476
  function getSkillList(preset) {
476
477
  const skills = [];
@@ -525,7 +526,8 @@ var RULES = [
525
526
  "context-loading",
526
527
  "agent-output-format",
527
528
  "quality-gates",
528
- "rollback-protocol"
529
+ "rollback-protocol",
530
+ "linear-sync"
529
531
  ];
530
532
  async function generateRules(ctx, overwrite) {
531
533
  const result = {
@@ -556,6 +558,69 @@ async function generateRules(ctx, overwrite) {
556
558
 
557
559
  // src/generators/claude-md.ts
558
560
  import path7 from "path";
561
+
562
+ // src/utils/merge.ts
563
+ function parseSections(markdown) {
564
+ const lines = markdown.split("\n");
565
+ let preamble = "";
566
+ const sections = [];
567
+ let currentHeader = null;
568
+ let currentContent = "";
569
+ let foundFirstSection = false;
570
+ for (const line of lines) {
571
+ if (/^## /.test(line)) {
572
+ if (foundFirstSection && currentHeader !== null) {
573
+ sections.push({ header: currentHeader, content: currentContent });
574
+ }
575
+ if (!foundFirstSection) {
576
+ preamble = currentContent;
577
+ foundFirstSection = true;
578
+ currentContent = "";
579
+ }
580
+ currentHeader = line;
581
+ currentContent = "";
582
+ } else {
583
+ currentContent += line + "\n";
584
+ }
585
+ }
586
+ if (foundFirstSection && currentHeader !== null) {
587
+ sections.push({ header: currentHeader, content: currentContent });
588
+ } else {
589
+ preamble = currentContent;
590
+ }
591
+ return { preamble, sections };
592
+ }
593
+ function normalizeHeader(header) {
594
+ return header.replace(/^##\s+/, "").trim().toLowerCase();
595
+ }
596
+ function mergeSections(existing, template) {
597
+ if (!existing || !existing.trim()) {
598
+ return template;
599
+ }
600
+ const parsedExisting = parseSections(existing);
601
+ const parsedTemplate = parseSections(template);
602
+ if (parsedExisting.sections.length === 0) {
603
+ return template;
604
+ }
605
+ const templateHeaders = new Set(
606
+ parsedTemplate.sections.map((s) => normalizeHeader(s.header))
607
+ );
608
+ const userSections = parsedExisting.sections.filter(
609
+ (s) => !templateHeaders.has(normalizeHeader(s.header))
610
+ );
611
+ let result = parsedTemplate.preamble;
612
+ for (const section of parsedTemplate.sections) {
613
+ result += section.header + "\n";
614
+ result += section.content;
615
+ }
616
+ for (const section of userSections) {
617
+ result += section.header + "\n";
618
+ result += section.content;
619
+ }
620
+ return result;
621
+ }
622
+
623
+ // src/generators/claude-md.ts
559
624
  async function generateClaudeMd(ctx, overwrite) {
560
625
  const result = {
561
626
  filesCreated: [],
@@ -569,11 +634,19 @@ async function generateClaudeMd(ctx, overwrite) {
569
634
  try {
570
635
  const content = await renderTemplate("root/CLAUDE.md.ejs", templateData);
571
636
  const outputPath = path7.join(ctx.targetDir, ".claude/CLAUDE.md");
572
- const status = await writeFileSafe(outputPath, content, overwrite);
573
- if (status === "skipped") {
574
- result.filesSkipped.push(outputPath);
575
- } else {
637
+ const exists = await fileExists(outputPath);
638
+ if (exists && overwrite) {
639
+ const existingContent = await readFile(outputPath);
640
+ const merged = mergeSections(existingContent, content);
641
+ await writeFileSafe(outputPath, merged, true);
576
642
  result.filesCreated.push(outputPath);
643
+ } else {
644
+ const status = await writeFileSafe(outputPath, content, overwrite);
645
+ if (status === "skipped") {
646
+ result.filesSkipped.push(outputPath);
647
+ } else {
648
+ result.filesCreated.push(outputPath);
649
+ }
577
650
  }
578
651
  } catch (err) {
579
652
  result.errors.push(`CLAUDE.md: ${err}`);
@@ -617,11 +690,24 @@ async function generateInfra(ctx, overwrite) {
617
690
  } catch (err) {
618
691
  result.errors.push(`tasks.json: ${err}`);
619
692
  }
693
+ try {
694
+ const content = await renderTemplate("config/linear-mapping.json.ejs", templateData);
695
+ const outputPath = path8.join(ctx.targetDir, "dev-infra/config/linear-mapping.json");
696
+ const status = await writeFileSafe(outputPath, content, overwrite);
697
+ if (status === "skipped") {
698
+ result.filesSkipped.push(outputPath);
699
+ } else {
700
+ result.filesCreated.push(outputPath);
701
+ }
702
+ } catch (err) {
703
+ result.errors.push(`linear-mapping.json: ${err}`);
704
+ }
620
705
  const dirs = [
621
706
  "dev-infra/sessions",
622
707
  "dev-infra/tests/acceptance",
623
708
  "dev-infra/tests/pmi",
624
- "dev-infra/tests/results"
709
+ "dev-infra/tests/results",
710
+ "dev-infra/config"
625
711
  ];
626
712
  for (const dir of dirs) {
627
713
  try {
@@ -639,14 +725,7 @@ async function generateInfra(ctx, overwrite) {
639
725
  }
640
726
  }
641
727
  try {
642
- const manifest = {
643
- version: "3.0.0",
644
- createdAt: ctx.today,
645
- projectName: ctx.projectName,
646
- agentPreset: ctx.agentPreset,
647
- language: ctx.language,
648
- expectedFiles: getExpectedFiles(ctx)
649
- };
728
+ const manifest = buildManifest(ctx);
650
729
  const outputPath = path8.join(ctx.targetDir, ".claude-forge.json");
651
730
  const status = await writeFileSafe(
652
731
  outputPath,
@@ -663,6 +742,25 @@ async function generateInfra(ctx, overwrite) {
663
742
  }
664
743
  return result;
665
744
  }
745
+ function buildManifest(ctx) {
746
+ return {
747
+ version: "3.2.0",
748
+ createdAt: ctx.today,
749
+ updatedAt: ctx.today,
750
+ projectName: ctx.projectName,
751
+ projectDescription: ctx.projectDescription,
752
+ language: ctx.language,
753
+ agentPreset: ctx.agentPreset,
754
+ stack: ctx.stack,
755
+ framework: ctx.framework,
756
+ testFramework: ctx.testFramework,
757
+ testCommand: ctx.testCommand,
758
+ srcDir: ctx.srcDir,
759
+ testDir: ctx.testDir,
760
+ commitStyle: ctx.commitStyle,
761
+ expectedFiles: getExpectedFiles(ctx)
762
+ };
763
+ }
666
764
  function getExpectedFiles(ctx) {
667
765
  const files = [
668
766
  ".claude/CLAUDE.md",
@@ -677,7 +775,8 @@ function getExpectedFiles(ctx) {
677
775
  "dev-infra/memory/tech-debt.md",
678
776
  "dev-infra/memory/patterns.md",
679
777
  "dev-infra/memory/troubleshooting.md",
680
- "dev-infra/memory/checkpoint.yml"
778
+ "dev-infra/memory/checkpoint.yml",
779
+ "dev-infra/config/linear-mapping.json"
681
780
  ];
682
781
  files.push(".claude/hooks/protect-docs.sh");
683
782
  files.push(
@@ -688,7 +787,8 @@ function getExpectedFiles(ctx) {
688
787
  ".claude/rules/context-loading.md",
689
788
  ".claude/rules/agent-output-format.md",
690
789
  ".claude/rules/quality-gates.md",
691
- ".claude/rules/rollback-protocol.md"
790
+ ".claude/rules/rollback-protocol.md",
791
+ ".claude/rules/linear-sync.md"
692
792
  );
693
793
  const coreSkills = [
694
794
  "start-session",
@@ -713,7 +813,8 @@ function getExpectedFiles(ctx) {
713
813
  "security",
714
814
  "spec",
715
815
  "techspec",
716
- "prompts"
816
+ "prompts",
817
+ "sync-linear"
717
818
  ];
718
819
  const skills = ctx.agentPreset === "full" ? [...coreSkills, ...extraSkills] : coreSkills;
719
820
  for (const skill of skills) {
@@ -942,10 +1043,152 @@ async function doctorCommand() {
942
1043
  }
943
1044
  }
944
1045
 
1046
+ // src/commands/update.ts
1047
+ import path11 from "path";
1048
+ import chalk3 from "chalk";
1049
+ import ora2 from "ora";
1050
+ function today2() {
1051
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1052
+ }
1053
+ function contextFromManifest(manifest, targetDir) {
1054
+ return {
1055
+ projectName: manifest.projectName || targetDir.split("/").pop() || "my-project",
1056
+ projectDescription: manifest.projectDescription || "AI-driven project",
1057
+ stack: manifest.stack || "typescript",
1058
+ framework: manifest.framework || "None",
1059
+ testFramework: manifest.testFramework || "vitest",
1060
+ testCommand: manifest.testCommand || "npx vitest run",
1061
+ srcDir: manifest.srcDir || "src/",
1062
+ testDir: manifest.testDir || "tests/",
1063
+ team: [],
1064
+ milestones: [],
1065
+ agentPreset: manifest.agentPreset || "core",
1066
+ language: manifest.language || "ru",
1067
+ commitStyle: manifest.commitStyle || "standard",
1068
+ today: today2(),
1069
+ targetDir
1070
+ };
1071
+ }
1072
+ async function updateCommand() {
1073
+ const targetDir = process.cwd();
1074
+ console.log();
1075
+ console.log(chalk3.bold(" agent-forge update v3.0.0"));
1076
+ console.log(chalk3.dim(" Updating framework files..."));
1077
+ console.log();
1078
+ const manifestPath = path11.join(targetDir, ".claude-forge.json");
1079
+ const manifestExists = await fileExists(manifestPath);
1080
+ if (!manifestExists) {
1081
+ console.log(chalk3.red(" .claude-forge.json not found"));
1082
+ console.log(
1083
+ chalk3.dim(" This project is not initialized. Run `agent-forge init` first.")
1084
+ );
1085
+ console.log();
1086
+ process.exit(1);
1087
+ }
1088
+ const manifestRaw = await readFile(manifestPath);
1089
+ let existingManifest;
1090
+ try {
1091
+ existingManifest = JSON.parse(manifestRaw);
1092
+ } catch {
1093
+ console.log(chalk3.red(" .claude-forge.json is corrupted. Cannot update."));
1094
+ console.log(
1095
+ chalk3.dim(" Run `agent-forge init --overwrite` to reinitialize.")
1096
+ );
1097
+ console.log();
1098
+ process.exit(1);
1099
+ }
1100
+ const previousVersion = existingManifest.version || "unknown";
1101
+ const ctx = contextFromManifest(existingManifest, targetDir);
1102
+ const spinner = ora2("Updating framework files...").start();
1103
+ try {
1104
+ const result = {
1105
+ filesCreated: [],
1106
+ filesSkipped: [],
1107
+ errors: []
1108
+ };
1109
+ const frameworkGenerators = [
1110
+ generateClaudeMd,
1111
+ generateAgents,
1112
+ generateSkills,
1113
+ generateRules,
1114
+ generateHooks
1115
+ ];
1116
+ for (const generator of frameworkGenerators) {
1117
+ try {
1118
+ const partial = await generator(ctx, true);
1119
+ result.filesCreated.push(...partial.filesCreated);
1120
+ result.filesSkipped.push(...partial.filesSkipped);
1121
+ result.errors.push(...partial.errors);
1122
+ } catch (err) {
1123
+ result.errors.push(`Generator error: ${err}`);
1124
+ }
1125
+ }
1126
+ const userDataGenerators = [
1127
+ generateMemoryBank,
1128
+ generateInfra
1129
+ ];
1130
+ for (const generator of userDataGenerators) {
1131
+ try {
1132
+ const partial = await generator(ctx, false);
1133
+ result.filesCreated.push(...partial.filesCreated);
1134
+ result.filesSkipped.push(...partial.filesSkipped);
1135
+ result.errors.push(...partial.errors);
1136
+ } catch (err) {
1137
+ result.errors.push(`Generator error: ${err}`);
1138
+ }
1139
+ }
1140
+ const updatedManifest = buildManifest(ctx);
1141
+ updatedManifest.createdAt = existingManifest.createdAt || updatedManifest.createdAt;
1142
+ updatedManifest.updatedAt = today2();
1143
+ await writeFileSafe(
1144
+ manifestPath,
1145
+ JSON.stringify(updatedManifest, null, 2) + "\n",
1146
+ true
1147
+ );
1148
+ spinner.succeed(chalk3.green("Framework files updated"));
1149
+ const newFiles = result.filesCreated.filter((f) => !result.filesSkipped.includes(f));
1150
+ const updatedCount = newFiles.length;
1151
+ const skippedCount = result.filesSkipped.length;
1152
+ console.log();
1153
+ if (updatedCount > 0) {
1154
+ console.log(
1155
+ chalk3.green(` + ${updatedCount} files updated/added`)
1156
+ );
1157
+ }
1158
+ if (skippedCount > 0) {
1159
+ console.log(
1160
+ chalk3.yellow(` ~ ${skippedCount} files skipped (user data preserved)`)
1161
+ );
1162
+ }
1163
+ if (result.errors.length > 0) {
1164
+ console.log(chalk3.red(` ! ${result.errors.length} errors:`));
1165
+ for (const err of result.errors) {
1166
+ console.log(chalk3.red(` - ${err}`));
1167
+ }
1168
+ }
1169
+ console.log();
1170
+ if (previousVersion !== updatedManifest.version) {
1171
+ console.log(
1172
+ chalk3.dim(` .claude-forge.json updated: ${previousVersion} -> v${updatedManifest.version}`)
1173
+ );
1174
+ } else {
1175
+ console.log(
1176
+ chalk3.dim(` .claude-forge.json updated (v${updatedManifest.version})`)
1177
+ );
1178
+ }
1179
+ console.log();
1180
+ } catch (err) {
1181
+ spinner.fail(chalk3.red("Failed to update framework files"));
1182
+ console.error(err);
1183
+ process.exit(1);
1184
+ }
1185
+ }
1186
+
945
1187
  // src/index.ts
946
1188
  var program = new Command();
947
- program.name("agent-forge").description("AI-driven Development Framework for Claude Code").version("3.0.0");
1189
+ program.name("agent-forge").description("AI-driven Development Framework for Claude Code").version("3.2.0");
948
1190
  program.command("init").description("Initialize AI-driven development infrastructure in the current project").option("-y, --yes", "Skip prompts and use defaults").option("--overwrite", "Overwrite existing files").action(initCommand);
949
1191
  program.command("doctor").description("Check integrity of the generated structure").action(doctorCommand);
1192
+ program.command("update").description("Update framework files to the latest version (preserves user data)").action(updateCommand);
950
1193
  program.parse();
951
1194
  //# sourceMappingURL=index.js.map