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.
Files changed (3) hide show
  1. package/dist/cli.js +81 -18
  2. package/package.json +1 -1
  3. 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 and
95
- .stackmem file, and seed initial memories from the project scan.
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 { error } = await supabase.from("memories").insert({
810
- project,
811
- type: "discovery",
812
- content: `tech stack: ${depNames.join(", ")}`,
813
- device_id: getDeviceId(),
814
- });
815
- if (!error)
816
- seededCount++;
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 { error } = await supabase.from("memories").insert({
832
- project,
833
- type: "constraint",
834
- content: `required env vars: ${varNames.join(", ")}`,
835
- device_id: getDeviceId(),
836
- });
837
- if (!error)
838
- seededCount++;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackmem",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
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 and
114
- .stackmem file, and seed initial memories from the project scan.
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 { error } = await supabase.from("memories").insert({
920
- project,
921
- type: "discovery",
922
- content: `tech stack: ${depNames.join(", ")}`,
923
- device_id: getDeviceId(),
924
- });
925
- if (!error) seededCount++;
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 { error } = await supabase.from("memories").insert({
942
- project,
943
- type: "constraint",
944
- content: `required env vars: ${varNames.join(", ")}`,
945
- device_id: getDeviceId(),
946
- });
947
- if (!error) seededCount++;
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");