@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/README.md +353 -118
- package/dist/index.js +262 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/config/linear-mapping.json.ejs +27 -0
- package/templates/root/CLAUDE.md.ejs +2 -0
- package/templates/rules/linear-sync.md.ejs +65 -0
- package/templates/skills/core/complete-task/SKILL.md.ejs +17 -0
- package/templates/skills/core/done/SKILL.md.ejs +18 -0
- package/templates/skills/core/end-session/SKILL.md.ejs +18 -0
- package/templates/skills/core/plan/SKILL.md.ejs +9 -0
- package/templates/skills/core/take-task/SKILL.md.ejs +9 -0
- package/templates/skills/extra/decompose/SKILL.md.ejs +9 -0
- package/templates/skills/extra/sync-linear/SKILL.md.ejs +87 -0
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
|
|
573
|
-
if (
|
|
574
|
-
|
|
575
|
-
|
|
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.
|
|
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
|