stackmem 1.0.8 → 1.0.10
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 +81 -18
- package/package.json +1 -1
- package/src/cli.ts +92 -16
package/dist/cli.js
CHANGED
|
@@ -51,6 +51,9 @@ function usage() {
|
|
|
51
51
|
function cmdHelp() {
|
|
52
52
|
console.log(`cm — coding memory CLI
|
|
53
53
|
|
|
54
|
+
Works with Claude Code, Cursor, and Windsurf — 'cm init' writes the
|
|
55
|
+
CLAUDE.md, .cursorrules, and .windsurfrules files each tool reads.
|
|
56
|
+
|
|
54
57
|
[project] is optional everywhere below if a .stackmem file exists in
|
|
55
58
|
the current directory (written by 'cm init'). Otherwise pass it explicitly.
|
|
56
59
|
|
|
@@ -91,8 +94,11 @@ function cmdHelp() {
|
|
|
91
94
|
|
|
92
95
|
cm init <project> <path>
|
|
93
96
|
Install a git post-commit hook in the repo at <path>, register
|
|
94
|
-
the stackmem MCP server with Claude Code, write a CLAUDE.md
|
|
95
|
-
.stackmem file, and seed
|
|
97
|
+
the stackmem MCP server with Claude Code, write a CLAUDE.md,
|
|
98
|
+
.cursorrules, .windsurfrules, and .stackmem file, and seed
|
|
99
|
+
initial memories from the project scan. .cursorrules and
|
|
100
|
+
.windsurfrules (for Cursor and Windsurf) are skipped if they
|
|
101
|
+
already exist.
|
|
96
102
|
|
|
97
103
|
cm context [--project <name>] <task>
|
|
98
104
|
Return only the top 5 memories most relevant to a specific
|
|
@@ -787,6 +793,43 @@ cm save ${project} <type> "<content>"
|
|
|
787
793
|
nodeFs.writeFileSync(claudeMdPath, content, "utf-8");
|
|
788
794
|
console.log("CLAUDE.md written.");
|
|
789
795
|
}
|
|
796
|
+
const EDITOR_RULES_CONTENT = `# stackmem
|
|
797
|
+
|
|
798
|
+
At the start of every session, run:
|
|
799
|
+
cm start
|
|
800
|
+
|
|
801
|
+
and paste the output here before starting work.
|
|
802
|
+
|
|
803
|
+
When you make a decision, discover a constraint,
|
|
804
|
+
reject an approach, or make a discovery, save it:
|
|
805
|
+
cm save <type> "<content>"
|
|
806
|
+
|
|
807
|
+
Types: decision, rejection, constraint, discovery
|
|
808
|
+
`;
|
|
809
|
+
// Returns true if the file was written, false if it already existed
|
|
810
|
+
// (and was skipped).
|
|
811
|
+
function writeCursorRules(resolvedPath) {
|
|
812
|
+
const cursorRulesPath = nodePath.join(resolvedPath, ".cursorrules");
|
|
813
|
+
if (nodeFs.existsSync(cursorRulesPath)) {
|
|
814
|
+
console.log(".cursorrules already exists — skipping.");
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
nodeFs.writeFileSync(cursorRulesPath, EDITOR_RULES_CONTENT, "utf-8");
|
|
818
|
+
console.log(".cursorrules written.");
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
821
|
+
// Returns true if the file was written, false if it already existed
|
|
822
|
+
// (and was skipped).
|
|
823
|
+
function writeWindsurfRules(resolvedPath) {
|
|
824
|
+
const windsurfRulesPath = nodePath.join(resolvedPath, ".windsurfrules");
|
|
825
|
+
if (nodeFs.existsSync(windsurfRulesPath)) {
|
|
826
|
+
console.log(".windsurfrules already exists — skipping.");
|
|
827
|
+
return false;
|
|
828
|
+
}
|
|
829
|
+
nodeFs.writeFileSync(windsurfRulesPath, EDITOR_RULES_CONTENT, "utf-8");
|
|
830
|
+
console.log(".windsurfrules written.");
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
790
833
|
function writeProjectFile(project, resolvedPath) {
|
|
791
834
|
const stackmemPath = nodePath.join(resolvedPath, STACKMEM_FILE);
|
|
792
835
|
nodeFs.writeFileSync(stackmemPath, `${project}\n`, "utf-8");
|
|
@@ -797,6 +840,14 @@ function writeProjectFile(project, resolvedPath) {
|
|
|
797
840
|
// non-interactive). Returns how many memories were actually saved.
|
|
798
841
|
async function seedMemories(project, resolvedPath) {
|
|
799
842
|
let seededCount = 0;
|
|
843
|
+
const { data: existingRows, error: existingError } = await supabase
|
|
844
|
+
.from("memories")
|
|
845
|
+
.select("id, content")
|
|
846
|
+
.eq("project", project)
|
|
847
|
+
.eq("resolved", false);
|
|
848
|
+
if (existingError)
|
|
849
|
+
fail(`Failed to load memories: ${existingError.message}`);
|
|
850
|
+
const existingMemories = existingRows ?? [];
|
|
800
851
|
const packageJsonPath = nodePath.join(resolvedPath, "package.json");
|
|
801
852
|
if (nodeFs.existsSync(packageJsonPath)) {
|
|
802
853
|
try {
|
|
@@ -806,14 +857,18 @@ async function seedMemories(project, resolvedPath) {
|
|
|
806
857
|
...Object.keys(pkg.devDependencies ?? {}),
|
|
807
858
|
];
|
|
808
859
|
if (depNames.length > 0) {
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
860
|
+
const content = `tech stack: ${depNames.join(", ")}`;
|
|
861
|
+
const decision = decideSaveAction(content, existingMemories);
|
|
862
|
+
if (decision.action !== "duplicate") {
|
|
863
|
+
const { error } = await supabase.from("memories").insert({
|
|
864
|
+
project,
|
|
865
|
+
type: "discovery",
|
|
866
|
+
content,
|
|
867
|
+
device_id: getDeviceId(),
|
|
868
|
+
});
|
|
869
|
+
if (!error)
|
|
870
|
+
seededCount++;
|
|
871
|
+
}
|
|
817
872
|
}
|
|
818
873
|
}
|
|
819
874
|
catch {
|
|
@@ -828,14 +883,18 @@ async function seedMemories(project, resolvedPath) {
|
|
|
828
883
|
.filter((line) => /^[A-Z_]+=/.test(line))
|
|
829
884
|
.map((line) => line.split("=")[0]);
|
|
830
885
|
if (varNames.length > 0) {
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
886
|
+
const content = `required env vars: ${varNames.join(", ")}`;
|
|
887
|
+
const decision = decideSaveAction(content, existingMemories);
|
|
888
|
+
if (decision.action !== "duplicate") {
|
|
889
|
+
const { error } = await supabase.from("memories").insert({
|
|
890
|
+
project,
|
|
891
|
+
type: "constraint",
|
|
892
|
+
content,
|
|
893
|
+
device_id: getDeviceId(),
|
|
894
|
+
});
|
|
895
|
+
if (!error)
|
|
896
|
+
seededCount++;
|
|
897
|
+
}
|
|
839
898
|
}
|
|
840
899
|
}
|
|
841
900
|
analyzeCodebase(resolvedPath);
|
|
@@ -869,6 +928,8 @@ async function cmdInit(project, targetPath) {
|
|
|
869
928
|
console.log(`coding-memory hook installed for project ${project} at ${targetPath}`);
|
|
870
929
|
registerMcpServer(DIST_INDEX_PATH);
|
|
871
930
|
writeClaudeMd(project, resolvedPath);
|
|
931
|
+
const cursorRulesWritten = writeCursorRules(resolvedPath);
|
|
932
|
+
const windsurfRulesWritten = writeWindsurfRules(resolvedPath);
|
|
872
933
|
const seededCount = await seedMemories(project, resolvedPath);
|
|
873
934
|
console.log(`Seeded ${seededCount} initial memories from project scan.`);
|
|
874
935
|
const backendOk = await verifyBackendConnection(project);
|
|
@@ -876,6 +937,8 @@ async function cmdInit(project, targetPath) {
|
|
|
876
937
|
console.log("✓ Git hook installed");
|
|
877
938
|
console.log("✓ MCP server registered");
|
|
878
939
|
console.log("✓ CLAUDE.md written");
|
|
940
|
+
console.log(`✓ .cursorrules ${cursorRulesWritten ? "written" : "already exists"}`);
|
|
941
|
+
console.log(`✓ .windsurfrules ${windsurfRulesWritten ? "written" : "already exists"}`);
|
|
879
942
|
console.log(`✓ Seeded ${seededCount} memories from project scan`);
|
|
880
943
|
if (backendOk) {
|
|
881
944
|
console.log("✓ Backend connection verified");
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -70,6 +70,9 @@ function usage(): never {
|
|
|
70
70
|
function cmdHelp() {
|
|
71
71
|
console.log(`cm — coding memory CLI
|
|
72
72
|
|
|
73
|
+
Works with Claude Code, Cursor, and Windsurf — 'cm init' writes the
|
|
74
|
+
CLAUDE.md, .cursorrules, and .windsurfrules files each tool reads.
|
|
75
|
+
|
|
73
76
|
[project] is optional everywhere below if a .stackmem file exists in
|
|
74
77
|
the current directory (written by 'cm init'). Otherwise pass it explicitly.
|
|
75
78
|
|
|
@@ -110,8 +113,11 @@ function cmdHelp() {
|
|
|
110
113
|
|
|
111
114
|
cm init <project> <path>
|
|
112
115
|
Install a git post-commit hook in the repo at <path>, register
|
|
113
|
-
the stackmem MCP server with Claude Code, write a CLAUDE.md
|
|
114
|
-
.stackmem file, and seed
|
|
116
|
+
the stackmem MCP server with Claude Code, write a CLAUDE.md,
|
|
117
|
+
.cursorrules, .windsurfrules, and .stackmem file, and seed
|
|
118
|
+
initial memories from the project scan. .cursorrules and
|
|
119
|
+
.windsurfrules (for Cursor and Windsurf) are skipped if they
|
|
120
|
+
already exist.
|
|
115
121
|
|
|
116
122
|
cm context [--project <name>] <task>
|
|
117
123
|
Return only the top 5 memories most relevant to a specific
|
|
@@ -895,6 +901,50 @@ cm save ${project} <type> "<content>"
|
|
|
895
901
|
console.log("CLAUDE.md written.");
|
|
896
902
|
}
|
|
897
903
|
|
|
904
|
+
const EDITOR_RULES_CONTENT = `# stackmem
|
|
905
|
+
|
|
906
|
+
At the start of every session, run:
|
|
907
|
+
cm start
|
|
908
|
+
|
|
909
|
+
and paste the output here before starting work.
|
|
910
|
+
|
|
911
|
+
When you make a decision, discover a constraint,
|
|
912
|
+
reject an approach, or make a discovery, save it:
|
|
913
|
+
cm save <type> "<content>"
|
|
914
|
+
|
|
915
|
+
Types: decision, rejection, constraint, discovery
|
|
916
|
+
`;
|
|
917
|
+
|
|
918
|
+
// Returns true if the file was written, false if it already existed
|
|
919
|
+
// (and was skipped).
|
|
920
|
+
function writeCursorRules(resolvedPath: string): boolean {
|
|
921
|
+
const cursorRulesPath = nodePath.join(resolvedPath, ".cursorrules");
|
|
922
|
+
|
|
923
|
+
if (nodeFs.existsSync(cursorRulesPath)) {
|
|
924
|
+
console.log(".cursorrules already exists — skipping.");
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
nodeFs.writeFileSync(cursorRulesPath, EDITOR_RULES_CONTENT, "utf-8");
|
|
929
|
+
console.log(".cursorrules written.");
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Returns true if the file was written, false if it already existed
|
|
934
|
+
// (and was skipped).
|
|
935
|
+
function writeWindsurfRules(resolvedPath: string): boolean {
|
|
936
|
+
const windsurfRulesPath = nodePath.join(resolvedPath, ".windsurfrules");
|
|
937
|
+
|
|
938
|
+
if (nodeFs.existsSync(windsurfRulesPath)) {
|
|
939
|
+
console.log(".windsurfrules already exists — skipping.");
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
nodeFs.writeFileSync(windsurfRulesPath, EDITOR_RULES_CONTENT, "utf-8");
|
|
944
|
+
console.log(".windsurfrules written.");
|
|
945
|
+
return true;
|
|
946
|
+
}
|
|
947
|
+
|
|
898
948
|
function writeProjectFile(project: string, resolvedPath: string): void {
|
|
899
949
|
const stackmemPath = nodePath.join(resolvedPath, STACKMEM_FILE);
|
|
900
950
|
nodeFs.writeFileSync(stackmemPath, `${project}\n`, "utf-8");
|
|
@@ -907,6 +957,16 @@ function writeProjectFile(project: string, resolvedPath: string): void {
|
|
|
907
957
|
async function seedMemories(project: string, resolvedPath: string): Promise<number> {
|
|
908
958
|
let seededCount = 0;
|
|
909
959
|
|
|
960
|
+
const { data: existingRows, error: existingError } = await supabase
|
|
961
|
+
.from("memories")
|
|
962
|
+
.select("id, content")
|
|
963
|
+
.eq("project", project)
|
|
964
|
+
.eq("resolved", false);
|
|
965
|
+
|
|
966
|
+
if (existingError) fail(`Failed to load memories: ${existingError.message}`);
|
|
967
|
+
|
|
968
|
+
const existingMemories = existingRows ?? [];
|
|
969
|
+
|
|
910
970
|
const packageJsonPath = nodePath.join(resolvedPath, "package.json");
|
|
911
971
|
if (nodeFs.existsSync(packageJsonPath)) {
|
|
912
972
|
try {
|
|
@@ -916,13 +976,17 @@ async function seedMemories(project: string, resolvedPath: string): Promise<numb
|
|
|
916
976
|
...Object.keys(pkg.devDependencies ?? {}),
|
|
917
977
|
];
|
|
918
978
|
if (depNames.length > 0) {
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
979
|
+
const content = `tech stack: ${depNames.join(", ")}`;
|
|
980
|
+
const decision = decideSaveAction(content, existingMemories);
|
|
981
|
+
if (decision.action !== "duplicate") {
|
|
982
|
+
const { error } = await supabase.from("memories").insert({
|
|
983
|
+
project,
|
|
984
|
+
type: "discovery",
|
|
985
|
+
content,
|
|
986
|
+
device_id: getDeviceId(),
|
|
987
|
+
});
|
|
988
|
+
if (!error) seededCount++;
|
|
989
|
+
}
|
|
926
990
|
}
|
|
927
991
|
} catch {
|
|
928
992
|
// Malformed package.json — skip the tech-stack memory.
|
|
@@ -938,13 +1002,17 @@ async function seedMemories(project: string, resolvedPath: string): Promise<numb
|
|
|
938
1002
|
.map((line) => line.split("=")[0]);
|
|
939
1003
|
|
|
940
1004
|
if (varNames.length > 0) {
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1005
|
+
const content = `required env vars: ${varNames.join(", ")}`;
|
|
1006
|
+
const decision = decideSaveAction(content, existingMemories);
|
|
1007
|
+
if (decision.action !== "duplicate") {
|
|
1008
|
+
const { error } = await supabase.from("memories").insert({
|
|
1009
|
+
project,
|
|
1010
|
+
type: "constraint",
|
|
1011
|
+
content,
|
|
1012
|
+
device_id: getDeviceId(),
|
|
1013
|
+
});
|
|
1014
|
+
if (!error) seededCount++;
|
|
1015
|
+
}
|
|
948
1016
|
}
|
|
949
1017
|
}
|
|
950
1018
|
|
|
@@ -990,6 +1058,8 @@ async function cmdInit(project: string, targetPath: string) {
|
|
|
990
1058
|
|
|
991
1059
|
registerMcpServer(DIST_INDEX_PATH);
|
|
992
1060
|
writeClaudeMd(project, resolvedPath);
|
|
1061
|
+
const cursorRulesWritten = writeCursorRules(resolvedPath);
|
|
1062
|
+
const windsurfRulesWritten = writeWindsurfRules(resolvedPath);
|
|
993
1063
|
|
|
994
1064
|
const seededCount = await seedMemories(project, resolvedPath);
|
|
995
1065
|
console.log(`Seeded ${seededCount} initial memories from project scan.`);
|
|
@@ -1000,6 +1070,12 @@ async function cmdInit(project: string, targetPath: string) {
|
|
|
1000
1070
|
console.log("✓ Git hook installed");
|
|
1001
1071
|
console.log("✓ MCP server registered");
|
|
1002
1072
|
console.log("✓ CLAUDE.md written");
|
|
1073
|
+
console.log(
|
|
1074
|
+
`✓ .cursorrules ${cursorRulesWritten ? "written" : "already exists"}`
|
|
1075
|
+
);
|
|
1076
|
+
console.log(
|
|
1077
|
+
`✓ .windsurfrules ${windsurfRulesWritten ? "written" : "already exists"}`
|
|
1078
|
+
);
|
|
1003
1079
|
console.log(`✓ Seeded ${seededCount} memories from project scan`);
|
|
1004
1080
|
if (backendOk) {
|
|
1005
1081
|
console.log("✓ Backend connection verified");
|