stackmem 1.0.11 → 1.0.13
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/cli.js +127 -21
- package/package.json +1 -1
- package/src/cli.ts +136 -17
- package/src/tools.ts +1 -0
package/dist/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ function usage() {
|
|
|
35
35
|
cm inspect [project] <memory-id>
|
|
36
36
|
cm compress [project]
|
|
37
37
|
cm init <project> <path>
|
|
38
|
+
cm sync
|
|
38
39
|
cm context [--project <name>] <task>
|
|
39
40
|
cm session-end [project] [--summary <text>] [--errors <a,b>] [--files <a,b>] [--approaches <a,b>]
|
|
40
41
|
cm doctor
|
|
@@ -105,8 +106,16 @@ function cmdHelp() {
|
|
|
105
106
|
the stackmem MCP server with Claude Code, write a CLAUDE.md,
|
|
106
107
|
.cursorrules, .windsurfrules, and .stackmem file, and seed
|
|
107
108
|
initial memories from the project scan. .cursorrules and
|
|
108
|
-
.windsurfrules
|
|
109
|
-
already
|
|
109
|
+
.windsurfrules are pre-filled with the project's current
|
|
110
|
+
memories (for Cursor and Windsurf) and skipped if they already
|
|
111
|
+
exist. The post-commit hook keeps them fresh afterward by
|
|
112
|
+
running 'cm sync' on every commit.
|
|
113
|
+
|
|
114
|
+
cm sync
|
|
115
|
+
Regenerate .cursorrules and .windsurfrules from the project's
|
|
116
|
+
current unresolved memories (decay-ranked, capped at a 2000
|
|
117
|
+
token budget) and its 5 most recent open execution_log fixes,
|
|
118
|
+
overwriting both files. Project is read from .stackmem.
|
|
110
119
|
|
|
111
120
|
cm context [--project <name>] <task>
|
|
112
121
|
Return only the top 5 memories most relevant to a specific
|
|
@@ -673,6 +682,15 @@ const savedItems = [];
|
|
|
673
682
|
|
|
674
683
|
const commitMessage = git(["log", "-1", "--pretty=%B"]);
|
|
675
684
|
|
|
685
|
+
// The sync commit below is itself a commit, so it fires this same
|
|
686
|
+
// post-commit hook again — git's --no-verify only skips pre-commit and
|
|
687
|
+
// commit-msg hooks, never post-commit. Without this guard, the hook would
|
|
688
|
+
// save its own commit message as a new decision memory, which changes the
|
|
689
|
+
// rules-file content and triggers another sync commit, indefinitely.
|
|
690
|
+
if (commitMessage.includes("[skip ci]")) {
|
|
691
|
+
process.exit(0);
|
|
692
|
+
}
|
|
693
|
+
|
|
676
694
|
const isFirstCommit = (() => {
|
|
677
695
|
try {
|
|
678
696
|
execFileSync("git", ["rev-parse", "HEAD~1"], { stdio: "pipe" });
|
|
@@ -835,6 +853,22 @@ console.log(\`coding-memory post-commit summary (\${changedFiles.length} file(s)
|
|
|
835
853
|
for (const item of savedItems) {
|
|
836
854
|
console.log(\` - \${item}\`);
|
|
837
855
|
}
|
|
856
|
+
|
|
857
|
+
execFileSync("node", [CLI_PATH, "sync"], { stdio: "inherit" });
|
|
858
|
+
|
|
859
|
+
// cm sync rewrites .cursorrules/.windsurfrules, which would otherwise leave
|
|
860
|
+
// the working directory dirty (breaking things like \`npm version patch\`
|
|
861
|
+
// that require a clean tree). Commit the sync ourselves instead. --no-verify
|
|
862
|
+
// skips pre-commit/commit-msg hooks (not post-commit — see the [skip ci]
|
|
863
|
+
// guard above, which is what actually stops this from recursing).
|
|
864
|
+
try {
|
|
865
|
+
execFileSync("git", ["add", ".cursorrules", ".windsurfrules"], { stdio: "pipe" });
|
|
866
|
+
execFileSync("git", ["commit", "--no-verify", "-m", "chore: sync memory to rules files [skip ci]"], {
|
|
867
|
+
stdio: "pipe",
|
|
868
|
+
});
|
|
869
|
+
} catch {
|
|
870
|
+
// Nothing to commit (rules files unchanged) — fine, ignore.
|
|
871
|
+
}
|
|
838
872
|
`;
|
|
839
873
|
}
|
|
840
874
|
function registerMcpServer(distIndexPath) {
|
|
@@ -881,40 +915,96 @@ cm save ${project} <type> "<content>"
|
|
|
881
915
|
nodeFs.writeFileSync(claudeMdPath, content, "utf-8");
|
|
882
916
|
console.log("CLAUDE.md written.");
|
|
883
917
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
918
|
+
// Section order for the generated rules-file markdown — matches the order
|
|
919
|
+
// memories are presented in everywhere else (cm context's CONTEXT_TYPE_ORDER),
|
|
920
|
+
// not MEMORY_TYPES' declaration order.
|
|
921
|
+
const RULES_FILE_TYPE_ORDER = ["decision", "constraint", "rejection", "discovery"];
|
|
922
|
+
// Builds the markdown written into .cursorrules / .windsurfrules: the
|
|
923
|
+
// project's unresolved memories (decay-ranked, budget-capped so an
|
|
924
|
+
// editor's system prompt never balloons) plus its most recent open fixes.
|
|
925
|
+
// Shared by `cm init` (first write) and `cm sync` (refresh on every commit).
|
|
926
|
+
async function generateRulesContent(project) {
|
|
927
|
+
const { data: memoryRows, error: memoriesError } = await supabase
|
|
928
|
+
.from("memories")
|
|
929
|
+
.select("*")
|
|
930
|
+
.eq("project", project)
|
|
931
|
+
.eq("resolved", false);
|
|
932
|
+
if (memoriesError)
|
|
933
|
+
fail(`Failed to load memories: ${memoriesError.message}`);
|
|
934
|
+
const scoredMemories = (memoryRows ?? [])
|
|
935
|
+
.map((memory) => ({
|
|
936
|
+
...memory,
|
|
937
|
+
decay_score: computeDecayScore(new Date(memory.created_at), memory.access_count ?? 0),
|
|
938
|
+
}))
|
|
939
|
+
.sort((a, b) => b.decay_score - a.decay_score);
|
|
940
|
+
const budgetedMemories = applyContextBudget(scoredMemories, CONTEXT_BUDGET_TOKENS);
|
|
941
|
+
const { data: fixRows, error: fixesError } = await supabase
|
|
942
|
+
.from("execution_log")
|
|
943
|
+
.select("*")
|
|
944
|
+
.eq("project", project)
|
|
945
|
+
.eq("resolved", false)
|
|
946
|
+
.order("created_at", { ascending: false })
|
|
947
|
+
.limit(5);
|
|
948
|
+
if (fixesError)
|
|
949
|
+
fail(`Failed to load execution log: ${fixesError.message}`);
|
|
950
|
+
const fixes = fixRows ?? [];
|
|
951
|
+
const lines = [];
|
|
952
|
+
lines.push("# stackmem — project memory (auto-updated)");
|
|
953
|
+
lines.push(`## Project: ${project}`);
|
|
954
|
+
if (budgetedMemories.length === 0 && fixes.length === 0) {
|
|
955
|
+
lines.push("");
|
|
956
|
+
lines.push("No memories yet. Make a commit to start capturing.");
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
for (const type of RULES_FILE_TYPE_ORDER) {
|
|
960
|
+
const group = budgetedMemories.filter((memory) => memory.type === type);
|
|
961
|
+
if (group.length === 0)
|
|
962
|
+
continue;
|
|
963
|
+
lines.push("");
|
|
964
|
+
lines.push(`### ${TYPE_LABELS[type]}`);
|
|
965
|
+
for (const memory of group) {
|
|
966
|
+
lines.push(`- ${memory.content}`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (fixes.length > 0) {
|
|
970
|
+
lines.push("");
|
|
971
|
+
lines.push("### Recent Fixes");
|
|
972
|
+
for (const fix of fixes) {
|
|
973
|
+
const solution = fix.solution && fix.solution.trim().length > 0
|
|
974
|
+
? fix.solution
|
|
975
|
+
: "(solution pending)";
|
|
976
|
+
lines.push(`- ${fix.problem} → ${solution}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
lines.push("");
|
|
981
|
+
lines.push("---");
|
|
982
|
+
lines.push(`*Last updated: ${new Date().toLocaleString("en-US")}*`);
|
|
983
|
+
lines.push("*Run `cm start` for full details with decay scores*");
|
|
984
|
+
lines.push("");
|
|
985
|
+
return lines.join("\n");
|
|
986
|
+
}
|
|
897
987
|
// Returns true if the file was written, false if it already existed
|
|
898
988
|
// (and was skipped).
|
|
899
|
-
function writeCursorRules(resolvedPath) {
|
|
989
|
+
function writeCursorRules(resolvedPath, content) {
|
|
900
990
|
const cursorRulesPath = nodePath.join(resolvedPath, ".cursorrules");
|
|
901
991
|
if (nodeFs.existsSync(cursorRulesPath)) {
|
|
902
992
|
console.log(".cursorrules already exists — skipping.");
|
|
903
993
|
return false;
|
|
904
994
|
}
|
|
905
|
-
nodeFs.writeFileSync(cursorRulesPath,
|
|
995
|
+
nodeFs.writeFileSync(cursorRulesPath, content, "utf-8");
|
|
906
996
|
console.log(".cursorrules written.");
|
|
907
997
|
return true;
|
|
908
998
|
}
|
|
909
999
|
// Returns true if the file was written, false if it already existed
|
|
910
1000
|
// (and was skipped).
|
|
911
|
-
function writeWindsurfRules(resolvedPath) {
|
|
1001
|
+
function writeWindsurfRules(resolvedPath, content) {
|
|
912
1002
|
const windsurfRulesPath = nodePath.join(resolvedPath, ".windsurfrules");
|
|
913
1003
|
if (nodeFs.existsSync(windsurfRulesPath)) {
|
|
914
1004
|
console.log(".windsurfrules already exists — skipping.");
|
|
915
1005
|
return false;
|
|
916
1006
|
}
|
|
917
|
-
nodeFs.writeFileSync(windsurfRulesPath,
|
|
1007
|
+
nodeFs.writeFileSync(windsurfRulesPath, content, "utf-8");
|
|
918
1008
|
console.log(".windsurfrules written.");
|
|
919
1009
|
return true;
|
|
920
1010
|
}
|
|
@@ -1016,10 +1106,11 @@ async function cmdInit(project, targetPath) {
|
|
|
1016
1106
|
console.log(`coding-memory hook installed for project ${project} at ${targetPath}`);
|
|
1017
1107
|
registerMcpServer(DIST_INDEX_PATH);
|
|
1018
1108
|
writeClaudeMd(project, resolvedPath);
|
|
1019
|
-
const cursorRulesWritten = writeCursorRules(resolvedPath);
|
|
1020
|
-
const windsurfRulesWritten = writeWindsurfRules(resolvedPath);
|
|
1021
1109
|
const seededCount = await seedMemories(project, resolvedPath);
|
|
1022
1110
|
console.log(`Seeded ${seededCount} initial memories from project scan.`);
|
|
1111
|
+
const rulesContent = await generateRulesContent(project);
|
|
1112
|
+
const cursorRulesWritten = writeCursorRules(resolvedPath, rulesContent);
|
|
1113
|
+
const windsurfRulesWritten = writeWindsurfRules(resolvedPath, rulesContent);
|
|
1023
1114
|
const backendOk = await verifyBackendConnection(project);
|
|
1024
1115
|
console.log("");
|
|
1025
1116
|
console.log("✓ Git hook installed");
|
|
@@ -1041,6 +1132,14 @@ async function cmdInit(project, targetPath) {
|
|
|
1041
1132
|
// .stackmem pointed to before this run, not the one being initialized now.
|
|
1042
1133
|
writeProjectFile(project, resolvedPath);
|
|
1043
1134
|
}
|
|
1135
|
+
// --- cm sync ---------------------------------------------------------------
|
|
1136
|
+
async function cmdSync(project) {
|
|
1137
|
+
const resolvedPath = process.cwd();
|
|
1138
|
+
const rulesContent = await generateRulesContent(project);
|
|
1139
|
+
nodeFs.writeFileSync(nodePath.join(resolvedPath, ".cursorrules"), rulesContent, "utf-8");
|
|
1140
|
+
nodeFs.writeFileSync(nodePath.join(resolvedPath, ".windsurfrules"), rulesContent, "utf-8");
|
|
1141
|
+
console.log("Synced memories to .cursorrules and .windsurfrules");
|
|
1142
|
+
}
|
|
1044
1143
|
// --- cm context ------------------------------------------------------------
|
|
1045
1144
|
const CONTEXT_TYPE_ORDER = ["decision", "constraint", "rejection", "discovery"];
|
|
1046
1145
|
const CONTEXT_TOP_N = 5;
|
|
@@ -1530,6 +1629,13 @@ async function main() {
|
|
|
1530
1629
|
await cmdInit(project, targetPath);
|
|
1531
1630
|
break;
|
|
1532
1631
|
}
|
|
1632
|
+
case "sync": {
|
|
1633
|
+
if (args.length > 0)
|
|
1634
|
+
usage();
|
|
1635
|
+
const project = requireProject(null);
|
|
1636
|
+
await cmdSync(project);
|
|
1637
|
+
break;
|
|
1638
|
+
}
|
|
1533
1639
|
case "context": {
|
|
1534
1640
|
const split = splitProjectFromVariadic(args);
|
|
1535
1641
|
const project = requireProject(split.project);
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -54,6 +54,7 @@ function usage(): never {
|
|
|
54
54
|
cm inspect [project] <memory-id>
|
|
55
55
|
cm compress [project]
|
|
56
56
|
cm init <project> <path>
|
|
57
|
+
cm sync
|
|
57
58
|
cm context [--project <name>] <task>
|
|
58
59
|
cm session-end [project] [--summary <text>] [--errors <a,b>] [--files <a,b>] [--approaches <a,b>]
|
|
59
60
|
cm doctor
|
|
@@ -125,8 +126,16 @@ function cmdHelp() {
|
|
|
125
126
|
the stackmem MCP server with Claude Code, write a CLAUDE.md,
|
|
126
127
|
.cursorrules, .windsurfrules, and .stackmem file, and seed
|
|
127
128
|
initial memories from the project scan. .cursorrules and
|
|
128
|
-
.windsurfrules
|
|
129
|
-
already
|
|
129
|
+
.windsurfrules are pre-filled with the project's current
|
|
130
|
+
memories (for Cursor and Windsurf) and skipped if they already
|
|
131
|
+
exist. The post-commit hook keeps them fresh afterward by
|
|
132
|
+
running 'cm sync' on every commit.
|
|
133
|
+
|
|
134
|
+
cm sync
|
|
135
|
+
Regenerate .cursorrules and .windsurfrules from the project's
|
|
136
|
+
current unresolved memories (decay-ranked, capped at a 2000
|
|
137
|
+
token budget) and its 5 most recent open execution_log fixes,
|
|
138
|
+
overwriting both files. Project is read from .stackmem.
|
|
130
139
|
|
|
131
140
|
cm context [--project <name>] <task>
|
|
132
141
|
Return only the top 5 memories most relevant to a specific
|
|
@@ -793,6 +802,15 @@ const savedItems = [];
|
|
|
793
802
|
|
|
794
803
|
const commitMessage = git(["log", "-1", "--pretty=%B"]);
|
|
795
804
|
|
|
805
|
+
// The sync commit below is itself a commit, so it fires this same
|
|
806
|
+
// post-commit hook again — git's --no-verify only skips pre-commit and
|
|
807
|
+
// commit-msg hooks, never post-commit. Without this guard, the hook would
|
|
808
|
+
// save its own commit message as a new decision memory, which changes the
|
|
809
|
+
// rules-file content and triggers another sync commit, indefinitely.
|
|
810
|
+
if (commitMessage.includes("[skip ci]")) {
|
|
811
|
+
process.exit(0);
|
|
812
|
+
}
|
|
813
|
+
|
|
796
814
|
const isFirstCommit = (() => {
|
|
797
815
|
try {
|
|
798
816
|
execFileSync("git", ["rev-parse", "HEAD~1"], { stdio: "pipe" });
|
|
@@ -955,6 +973,22 @@ console.log(\`coding-memory post-commit summary (\${changedFiles.length} file(s)
|
|
|
955
973
|
for (const item of savedItems) {
|
|
956
974
|
console.log(\` - \${item}\`);
|
|
957
975
|
}
|
|
976
|
+
|
|
977
|
+
execFileSync("node", [CLI_PATH, "sync"], { stdio: "inherit" });
|
|
978
|
+
|
|
979
|
+
// cm sync rewrites .cursorrules/.windsurfrules, which would otherwise leave
|
|
980
|
+
// the working directory dirty (breaking things like \`npm version patch\`
|
|
981
|
+
// that require a clean tree). Commit the sync ourselves instead. --no-verify
|
|
982
|
+
// skips pre-commit/commit-msg hooks (not post-commit — see the [skip ci]
|
|
983
|
+
// guard above, which is what actually stops this from recursing).
|
|
984
|
+
try {
|
|
985
|
+
execFileSync("git", ["add", ".cursorrules", ".windsurfrules"], { stdio: "pipe" });
|
|
986
|
+
execFileSync("git", ["commit", "--no-verify", "-m", "chore: sync memory to rules files [skip ci]"], {
|
|
987
|
+
stdio: "pipe",
|
|
988
|
+
});
|
|
989
|
+
} catch {
|
|
990
|
+
// Nothing to commit (rules files unchanged) — fine, ignore.
|
|
991
|
+
}
|
|
958
992
|
`;
|
|
959
993
|
}
|
|
960
994
|
|
|
@@ -1011,23 +1045,88 @@ cm save ${project} <type> "<content>"
|
|
|
1011
1045
|
console.log("CLAUDE.md written.");
|
|
1012
1046
|
}
|
|
1013
1047
|
|
|
1014
|
-
|
|
1048
|
+
// Section order for the generated rules-file markdown — matches the order
|
|
1049
|
+
// memories are presented in everywhere else (cm context's CONTEXT_TYPE_ORDER),
|
|
1050
|
+
// not MEMORY_TYPES' declaration order.
|
|
1051
|
+
const RULES_FILE_TYPE_ORDER: MemoryType[] = ["decision", "constraint", "rejection", "discovery"];
|
|
1052
|
+
|
|
1053
|
+
// Builds the markdown written into .cursorrules / .windsurfrules: the
|
|
1054
|
+
// project's unresolved memories (decay-ranked, budget-capped so an
|
|
1055
|
+
// editor's system prompt never balloons) plus its most recent open fixes.
|
|
1056
|
+
// Shared by `cm init` (first write) and `cm sync` (refresh on every commit).
|
|
1057
|
+
async function generateRulesContent(project: string): Promise<string> {
|
|
1058
|
+
const { data: memoryRows, error: memoriesError } = await supabase
|
|
1059
|
+
.from("memories")
|
|
1060
|
+
.select("*")
|
|
1061
|
+
.eq("project", project)
|
|
1062
|
+
.eq("resolved", false);
|
|
1015
1063
|
|
|
1016
|
-
|
|
1017
|
-
cm start
|
|
1064
|
+
if (memoriesError) fail(`Failed to load memories: ${memoriesError.message}`);
|
|
1018
1065
|
|
|
1019
|
-
|
|
1066
|
+
const scoredMemories = (memoryRows ?? [])
|
|
1067
|
+
.map((memory) => ({
|
|
1068
|
+
...memory,
|
|
1069
|
+
decay_score: computeDecayScore(new Date(memory.created_at), memory.access_count ?? 0),
|
|
1070
|
+
}))
|
|
1071
|
+
.sort((a, b) => b.decay_score - a.decay_score);
|
|
1020
1072
|
|
|
1021
|
-
|
|
1022
|
-
reject an approach, or make a discovery, save it:
|
|
1023
|
-
cm save <type> "<content>"
|
|
1073
|
+
const budgetedMemories = applyContextBudget(scoredMemories, CONTEXT_BUDGET_TOKENS);
|
|
1024
1074
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1075
|
+
const { data: fixRows, error: fixesError } = await supabase
|
|
1076
|
+
.from("execution_log")
|
|
1077
|
+
.select("*")
|
|
1078
|
+
.eq("project", project)
|
|
1079
|
+
.eq("resolved", false)
|
|
1080
|
+
.order("created_at", { ascending: false })
|
|
1081
|
+
.limit(5);
|
|
1082
|
+
|
|
1083
|
+
if (fixesError) fail(`Failed to load execution log: ${fixesError.message}`);
|
|
1084
|
+
|
|
1085
|
+
const fixes = fixRows ?? [];
|
|
1086
|
+
|
|
1087
|
+
const lines: string[] = [];
|
|
1088
|
+
lines.push("# stackmem — project memory (auto-updated)");
|
|
1089
|
+
lines.push(`## Project: ${project}`);
|
|
1090
|
+
|
|
1091
|
+
if (budgetedMemories.length === 0 && fixes.length === 0) {
|
|
1092
|
+
lines.push("");
|
|
1093
|
+
lines.push("No memories yet. Make a commit to start capturing.");
|
|
1094
|
+
} else {
|
|
1095
|
+
for (const type of RULES_FILE_TYPE_ORDER) {
|
|
1096
|
+
const group = budgetedMemories.filter((memory) => memory.type === type);
|
|
1097
|
+
if (group.length === 0) continue;
|
|
1098
|
+
|
|
1099
|
+
lines.push("");
|
|
1100
|
+
lines.push(`### ${TYPE_LABELS[type]}`);
|
|
1101
|
+
for (const memory of group) {
|
|
1102
|
+
lines.push(`- ${memory.content}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
if (fixes.length > 0) {
|
|
1107
|
+
lines.push("");
|
|
1108
|
+
lines.push("### Recent Fixes");
|
|
1109
|
+
for (const fix of fixes) {
|
|
1110
|
+
const solution = fix.solution && fix.solution.trim().length > 0
|
|
1111
|
+
? fix.solution
|
|
1112
|
+
: "(solution pending)";
|
|
1113
|
+
lines.push(`- ${fix.problem} → ${solution}`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
lines.push("");
|
|
1119
|
+
lines.push("---");
|
|
1120
|
+
lines.push(`*Last updated: ${new Date().toLocaleString("en-US")}*`);
|
|
1121
|
+
lines.push("*Run `cm start` for full details with decay scores*");
|
|
1122
|
+
lines.push("");
|
|
1123
|
+
|
|
1124
|
+
return lines.join("\n");
|
|
1125
|
+
}
|
|
1027
1126
|
|
|
1028
1127
|
// Returns true if the file was written, false if it already existed
|
|
1029
1128
|
// (and was skipped).
|
|
1030
|
-
function writeCursorRules(resolvedPath: string): boolean {
|
|
1129
|
+
function writeCursorRules(resolvedPath: string, content: string): boolean {
|
|
1031
1130
|
const cursorRulesPath = nodePath.join(resolvedPath, ".cursorrules");
|
|
1032
1131
|
|
|
1033
1132
|
if (nodeFs.existsSync(cursorRulesPath)) {
|
|
@@ -1035,14 +1134,14 @@ function writeCursorRules(resolvedPath: string): boolean {
|
|
|
1035
1134
|
return false;
|
|
1036
1135
|
}
|
|
1037
1136
|
|
|
1038
|
-
nodeFs.writeFileSync(cursorRulesPath,
|
|
1137
|
+
nodeFs.writeFileSync(cursorRulesPath, content, "utf-8");
|
|
1039
1138
|
console.log(".cursorrules written.");
|
|
1040
1139
|
return true;
|
|
1041
1140
|
}
|
|
1042
1141
|
|
|
1043
1142
|
// Returns true if the file was written, false if it already existed
|
|
1044
1143
|
// (and was skipped).
|
|
1045
|
-
function writeWindsurfRules(resolvedPath: string): boolean {
|
|
1144
|
+
function writeWindsurfRules(resolvedPath: string, content: string): boolean {
|
|
1046
1145
|
const windsurfRulesPath = nodePath.join(resolvedPath, ".windsurfrules");
|
|
1047
1146
|
|
|
1048
1147
|
if (nodeFs.existsSync(windsurfRulesPath)) {
|
|
@@ -1050,7 +1149,7 @@ function writeWindsurfRules(resolvedPath: string): boolean {
|
|
|
1050
1149
|
return false;
|
|
1051
1150
|
}
|
|
1052
1151
|
|
|
1053
|
-
nodeFs.writeFileSync(windsurfRulesPath,
|
|
1152
|
+
nodeFs.writeFileSync(windsurfRulesPath, content, "utf-8");
|
|
1054
1153
|
console.log(".windsurfrules written.");
|
|
1055
1154
|
return true;
|
|
1056
1155
|
}
|
|
@@ -1168,12 +1267,14 @@ async function cmdInit(project: string, targetPath: string) {
|
|
|
1168
1267
|
|
|
1169
1268
|
registerMcpServer(DIST_INDEX_PATH);
|
|
1170
1269
|
writeClaudeMd(project, resolvedPath);
|
|
1171
|
-
const cursorRulesWritten = writeCursorRules(resolvedPath);
|
|
1172
|
-
const windsurfRulesWritten = writeWindsurfRules(resolvedPath);
|
|
1173
1270
|
|
|
1174
1271
|
const seededCount = await seedMemories(project, resolvedPath);
|
|
1175
1272
|
console.log(`Seeded ${seededCount} initial memories from project scan.`);
|
|
1176
1273
|
|
|
1274
|
+
const rulesContent = await generateRulesContent(project);
|
|
1275
|
+
const cursorRulesWritten = writeCursorRules(resolvedPath, rulesContent);
|
|
1276
|
+
const windsurfRulesWritten = writeWindsurfRules(resolvedPath, rulesContent);
|
|
1277
|
+
|
|
1177
1278
|
const backendOk = await verifyBackendConnection(project);
|
|
1178
1279
|
|
|
1179
1280
|
console.log("");
|
|
@@ -1203,6 +1304,18 @@ async function cmdInit(project: string, targetPath: string) {
|
|
|
1203
1304
|
writeProjectFile(project, resolvedPath);
|
|
1204
1305
|
}
|
|
1205
1306
|
|
|
1307
|
+
// --- cm sync ---------------------------------------------------------------
|
|
1308
|
+
|
|
1309
|
+
async function cmdSync(project: string) {
|
|
1310
|
+
const resolvedPath = process.cwd();
|
|
1311
|
+
const rulesContent = await generateRulesContent(project);
|
|
1312
|
+
|
|
1313
|
+
nodeFs.writeFileSync(nodePath.join(resolvedPath, ".cursorrules"), rulesContent, "utf-8");
|
|
1314
|
+
nodeFs.writeFileSync(nodePath.join(resolvedPath, ".windsurfrules"), rulesContent, "utf-8");
|
|
1315
|
+
|
|
1316
|
+
console.log("Synced memories to .cursorrules and .windsurfrules");
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1206
1319
|
// --- cm context ------------------------------------------------------------
|
|
1207
1320
|
|
|
1208
1321
|
const CONTEXT_TYPE_ORDER: MemoryType[] = ["decision", "constraint", "rejection", "discovery"];
|
|
@@ -1731,6 +1844,12 @@ async function main() {
|
|
|
1731
1844
|
await cmdInit(project, targetPath);
|
|
1732
1845
|
break;
|
|
1733
1846
|
}
|
|
1847
|
+
case "sync": {
|
|
1848
|
+
if (args.length > 0) usage();
|
|
1849
|
+
const project = requireProject(null);
|
|
1850
|
+
await cmdSync(project);
|
|
1851
|
+
break;
|
|
1852
|
+
}
|
|
1734
1853
|
case "context": {
|
|
1735
1854
|
const split = splitProjectFromVariadic(args);
|
|
1736
1855
|
const project = requireProject(split.project);
|
package/src/tools.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const test = 1
|