speclock 1.5.0 → 1.6.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 CHANGED
@@ -218,6 +218,35 @@ Result: [HIGH] Conflict detected (confidence: 85%)
218
218
  | `speclock_detect_drift` | Scan changes for constraint violations |
219
219
  | `speclock_health` | Health score + multi-agent timeline |
220
220
 
221
+ ## Auto-Guard: Locks That Actually Work
222
+
223
+ When you add a lock, SpecLock **automatically finds and guards related files**:
224
+
225
+ ```
226
+ speclock lock "Never modify auth files"
227
+ → Auto-guarded 2 related file(s):
228
+ 🔒 src/components/Auth.tsx
229
+ 🔒 src/contexts/AuthContext.tsx
230
+
231
+ speclock lock "Database must always be Supabase"
232
+ → Auto-guarded 1 related file(s):
233
+ 🔒 src/lib/supabase.ts
234
+ ```
235
+
236
+ The guard injects a warning **directly inside the file**. When the AI opens the file to edit it, it sees:
237
+ ```
238
+ // ============================================================
239
+ // SPECLOCK-GUARD — DO NOT MODIFY THIS FILE
240
+ // LOCKED: Never modify auth files
241
+ // THIS FILE IS LOCKED. DO NOT EDIT, CHANGE, OR REWRITE ANY PART OF IT.
242
+ // The user must say "unlock" before this file can be changed.
243
+ // A question is NOT permission. Asking about features is NOT permission.
244
+ // ONLY "unlock" or "remove the lock" is permission to edit this file.
245
+ // ============================================================
246
+ ```
247
+
248
+ Active locks are also embedded in `package.json` — so the AI sees your constraints every time it reads the project config.
249
+
221
250
  ## CLI Commands
222
251
 
223
252
  ```bash
@@ -226,18 +255,20 @@ speclock setup --goal "Build my app" # One-shot: init + rules + context
226
255
 
227
256
  # Memory
228
257
  speclock goal <text> # Set project goal
229
- speclock lock <text> [--tags a,b] # Add a constraint
258
+ speclock lock <text> [--tags a,b] # Add constraint + auto-guard files
230
259
  speclock lock remove <id> # Remove a lock
231
260
  speclock decide <text> # Record a decision
232
261
  speclock note <text> # Add a note
233
262
 
263
+ # Enforcement
264
+ speclock check <text> # Check for lock conflicts
265
+ speclock guard <file> --lock "text" # Manually guard a specific file
266
+ speclock unguard <file> # Remove guard from file
267
+
234
268
  # Tracking
235
269
  speclock log-change <text> --files x # Log a change
236
270
  speclock context # Regenerate context file
237
271
 
238
- # Enforcement
239
- speclock check <text> # Check for lock conflicts
240
-
241
272
  # Other
242
273
  speclock status # Show brain summary
243
274
  speclock serve [--project <path>] # Start MCP server
@@ -287,4 +318,4 @@ MIT License - see [LICENSE](LICENSE) file.
287
318
 
288
319
  ---
289
320
 
290
- *SpecLock v1.5.0 — Because remembering isn't enough. AI needs to respect boundaries.*
321
+ *SpecLock v1.6.0 — Because remembering isn't enough. AI needs to respect boundaries.*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speclock",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "AI constraint engine — MCP server + CLI with active enforcement. Memory + guardrails for AI coding tools. Works with Bolt.new, Claude Code, Cursor, Lovable.",
5
5
  "type": "module",
6
6
  "main": "src/mcp/server.js",
package/src/cli/index.js CHANGED
@@ -14,6 +14,8 @@ import {
14
14
  guardFile,
15
15
  unguardFile,
16
16
  injectPackageJsonMarker,
17
+ syncLocksToPackageJson,
18
+ autoGuardRelatedFiles,
17
19
  } from "../core/engine.js";
18
20
  import { generateContext } from "../core/context.js";
19
21
  import { readBrain } from "../core/storage.js";
@@ -72,7 +74,7 @@ function refreshContext(root) {
72
74
 
73
75
  function printHelp() {
74
76
  console.log(`
75
- SpecLock v1.5.0 — AI Constraint Engine
77
+ SpecLock v1.6.0 — AI Constraint Engine
76
78
  Developed by Sandeep Roy (github.com/sgroy10)
77
79
 
78
80
  Usage: speclock <command> [options]
@@ -200,16 +202,16 @@ Files created/updated:
200
202
  .speclock/brain.json — Project memory
201
203
  .speclock/context/latest.md — Context for AI (read this)
202
204
  SPECLOCK.md — AI rules (read this)
203
- package.json — SpecLock marker added (AI auto-discovery)
205
+ package.json — Active locks embedded (AI auto-discovery)
204
206
 
205
207
  Next steps:
206
- The AI should read SPECLOCK.md for rules and
207
- .speclock/context/latest.md for project context.
208
-
209
208
  To add constraints: npx speclock lock "Never touch auth files"
210
209
  To check conflicts: npx speclock check "Modifying auth page"
211
210
  To log changes: npx speclock log-change "Built landing page"
212
211
  To see status: npx speclock status
212
+
213
+ Tip: When starting a new chat, tell the AI:
214
+ "Check speclock status and read the project constraints before doing anything"
213
215
  `);
214
216
  return;
215
217
  }
@@ -250,6 +252,8 @@ Next steps:
250
252
  }
251
253
  const result = removeLock(root, lockId);
252
254
  if (result.removed) {
255
+ // Sync updated locks to package.json
256
+ syncLocksToPackageJson(root);
253
257
  refreshContext(root);
254
258
  console.log(`Lock removed: "${result.lockText}"`);
255
259
  } else {
@@ -267,6 +271,22 @@ Next steps:
267
271
  process.exit(1);
268
272
  }
269
273
  const { lockId } = addLock(root, text, parseTags(flags.tags), flags.source || "user");
274
+
275
+ // Auto-guard related files (Solution 1)
276
+ const guardResult = autoGuardRelatedFiles(root, text);
277
+ if (guardResult.guarded.length > 0) {
278
+ console.log(`Auto-guarded ${guardResult.guarded.length} related file(s):`);
279
+ for (const f of guardResult.guarded) {
280
+ console.log(` 🔒 ${f}`);
281
+ }
282
+ }
283
+
284
+ // Sync locks to package.json (Solution 2)
285
+ const syncResult = syncLocksToPackageJson(root);
286
+ if (syncResult.success) {
287
+ console.log(`Synced ${syncResult.lockCount} lock(s) to package.json.`);
288
+ }
289
+
270
290
  refreshContext(root);
271
291
  console.log(`Locked (${lockId}): "${text}"`);
272
292
  return;
@@ -702,85 +702,91 @@ export async function watchRepo(root) {
702
702
  export function createSpecLockMd(root) {
703
703
  const mdContent = `# SpecLock — AI Constraint Engine Active
704
704
 
705
- ## STOP Read this before doing ANYTHING.
705
+ ## CRITICAL: LOCKED FILES CANNOT BE MODIFIED
706
706
 
707
- This project has SpecLock installed. It tracks project memory, goals, constraints (locks), and decisions.
707
+ If a lock says "never modify auth files" then you CANNOT modify those files.
708
+ Not now. Not later. Not if the user asks a question. Not for any reason.
708
709
 
709
- **Step 1:** Read \`.speclock/context/latest.md\` this contains the full project memory.
710
- **Step 2:** Follow ALL rules below on every message.
710
+ **The ONLY way to modify a locked file:** The user must explicitly say "unlock" or "remove the lock."
711
+ Asking a question is NOT permission. Talking about a feature is NOT permission.
712
+ ONLY the words "unlock", "remove the lock", or "I changed my mind about the lock" count.
711
713
 
712
- **If a file contains "SPECLOCK-GUARD" at the top → DO NOT EDIT IT. Period.**
714
+ **If a file contains "SPECLOCK-GUARD" at the top → DO NOT EDIT IT. No exceptions.**
713
715
 
714
- ## Returning to This Project? (New Chat Session)
716
+ ## On EVERY message do this FIRST:
717
+
718
+ 1. Read \`.speclock/context/latest.md\` for project memory
719
+ 2. Run \`npx speclock check "what you plan to do"\` BEFORE any code changes
720
+ 3. If conflict found → STOP. Tell user. Do NOT proceed. Do NOT ask "should I proceed?"
721
+ 4. Wait for user to explicitly say "unlock" or "remove the lock" before touching locked files
715
722
 
716
- If you are starting a new chat on this project:
717
- 1. SpecLock is ALREADY installed — do NOT reinstall it
718
- 2. The \`.speclock/\` directory contains all project memory
719
- 3. Run \`npx speclock status\` to see the current state
720
- 4. Read \`.speclock/context/latest.md\` for full context (goals, locks, decisions, history)
721
- 5. Respect ALL existing locks and constraints before making any changes
723
+ ## Returning to This Project? (New Chat Session)
722
724
 
723
- **DO NOT say "SpecLock is not installed" if you can read this file, it IS installed.**
725
+ SpecLock is ALREADY installed. Do NOT reinstall it. Do NOT say "it's not installed."
726
+ 1. Run \`npx speclock status\` to see current locks and decisions
727
+ 2. Read \`.speclock/context/latest.md\` for full project context
728
+ 3. Respect ALL existing locks — they carry over between sessions
724
729
 
725
- ## Your Job (Do This Automatically The User Will NOT Ask)
730
+ ## What Counts as Permission to Bypass a Lock?
726
731
 
727
- ### On EVERY message:
728
- Read \`.speclock/context/latest.md\` first. Always. No exceptions.
732
+ | User says | Is this permission? | What you do |
733
+ |-----------|-------------------|-------------|
734
+ | "add google auth" | NO — lock still active | Say "Auth files are locked. Want me to unlock them first?" |
735
+ | "is speclock initialized?" | NO — just a question | Answer the question. Do NOT change any code. |
736
+ | "yeah go ahead" (after you warned about lock) | NO — too vague | Say "To modify locked files, please confirm: unlock the auth files" |
737
+ | "ok unlock the auth files" | YES — explicit unlock | Run \`npx speclock lock remove <lockId>\`, then make changes |
738
+ | "remove the lock on auth" | YES — explicit unlock | Run \`npx speclock lock remove <lockId>\`, then make changes |
739
+ | "I changed my mind, you can edit auth" | YES — explicit unlock | Run \`npx speclock lock remove <lockId>\`, then make changes |
729
740
 
730
- ### Before changing ANY code:
731
- Run \`npx speclock check "what you plan to do"\` BEFORE writing code.
732
- If conflict → STOP. Tell the user. Do NOT proceed.
741
+ **When in doubt: DO NOT modify locked files. Ask for clarification instead.**
733
742
 
734
- ### Detect constraints from natural language:
735
- Users will NOT say "lock this." They speak naturally. YOU must detect it.
743
+ ## Detect Constraints from Natural Language
736
744
 
737
- Examples of what users say what you do:
745
+ Users will NOT say "lock this." They speak naturally. YOU must detect intent and lock automatically.
738
746
 
739
747
  | User says | You do |
740
748
  |-----------|--------|
741
- | "don't touch the auth" | \`npx speclock lock "Never modify auth files"\` + \`npx speclock guard src/components/Auth.tsx --lock "Never modify auth files"\` |
749
+ | "don't touch the auth" | \`npx speclock lock "Never modify auth files"\` + \`npx speclock guard\` the auth files |
742
750
  | "database is firebase, don't change it" | \`npx speclock lock "Database must always be Firebase"\` |
743
- | "leave the payment system alone" | \`npx speclock lock "Never modify payment system"\` + guard the payment files |
751
+ | "leave the payment system alone" | \`npx speclock lock "Never modify payment system"\` + guard payment files |
744
752
  | "always use Supabase" | \`npx speclock lock "Always use Supabase"\` |
745
- | "the API is done, don't mess with it" | \`npx speclock lock "Never modify API endpoints"\` + guard the API files |
753
+ | "the API is done, don't mess with it" | \`npx speclock lock "Never modify API endpoints"\` + guard API files |
746
754
 
747
755
  When you lock something, ALSO guard the specific files:
748
756
  \`npx speclock guard <filepath> --lock "the constraint"\`
749
- This injects a warning directly into the file so it cannot be missed.
750
757
 
751
- ### When user wants to change something locked:
752
- User might say "actually, let me change auth" or "ok modify the database."
753
- 1. Warn: "This is currently locked: [constraint]. Want me to unlock?"
754
- 2. If they confirm → \`npx speclock lock remove <lockId>\` + \`npx speclock unguard <file>\`
758
+ ## Unlocking (When User Gives Explicit Permission)
759
+
760
+ 1. User says "unlock auth" or "remove the auth lock" or "I changed my mind about auth"
761
+ 2. Run \`npx speclock lock remove <lockId>\` + \`npx speclock unguard <file>\`
755
762
  3. Make the changes
756
- 4. Ask if they want to re-lock: "Want me to lock this again?"
763
+ 4. Ask: "Want me to re-lock this?"
757
764
 
758
- ### After building something:
759
- \`npx speclock log-change "what you built" --files file1.tsx,file2.ts\`
765
+ ## After Building Something
760
766
 
761
- ### When a tech decision is made:
762
- \`npx speclock decide "the decision"\`
767
+ \`npx speclock log-change "what you built" --files file1.tsx,file2.ts\`
763
768
 
764
769
  ## Commands
765
770
 
766
771
  \`\`\`bash
767
- npx speclock status # CHECK IF ALREADY SET UP
772
+ npx speclock status # Check current state
768
773
  npx speclock check "what you plan to do" # ALWAYS RUN BEFORE CHANGES
769
774
  npx speclock lock "constraint" # Lock a constraint
770
775
  npx speclock guard <file> --lock "constraint" # Protect a specific file
771
776
  npx speclock unguard <file> # Remove file protection
772
- npx speclock lock remove <lockId> # Unlock a constraint
777
+ npx speclock lock remove <lockId> # Unlock (only after explicit permission)
773
778
  npx speclock log-change "what changed" # Log changes
774
779
  npx speclock decide "decision" # Record a decision
775
780
  npx speclock context # Refresh context file
776
781
  \`\`\`
777
782
 
778
- ## Key Rule
783
+ ## Key Rules
779
784
 
780
- **The user will NEVER tell you to run these commands. You run them automatically.**
781
- **The user will NEVER say "lock this." You detect the intent and lock it yourself.**
782
- **The user will NEVER say "check constraints." You check before EVERY code change.**
783
- **If this file exists, SpecLock is installed. Do NOT reinstall. Just read the context and follow the rules.**
785
+ 1. **A question is NOT permission to bypass a lock.**
786
+ 2. **Run speclock check before EVERY code change, EVERY time, EVERY message.**
787
+ 3. **If you already warned about a conflict, the lock is STILL active until explicitly removed.**
788
+ 4. **The user will NEVER tell you to run these commands. You run them automatically.**
789
+ 5. **If this file exists, SpecLock is installed. Do NOT reinstall.**
784
790
  `;
785
791
  const filePath = path.join(root, "SPECLOCK.md");
786
792
  fs.writeFileSync(filePath, mdContent);
@@ -828,9 +834,11 @@ export function guardFile(root, relativeFilePath, lockText) {
828
834
  const warningLines = [
829
835
  `${style.start}${"=".repeat(60)}${style.end || ""}`,
830
836
  `${style.start}${GUARD_TAG} — DO NOT MODIFY THIS FILE${style.end || ""}`,
831
- `${style.start}LOCKED BY SPECLOCK: ${lockText}${style.end || ""}`,
832
- `${style.start}Run "npx speclock check" before ANY changes to this file.${style.end || ""}`,
833
- `${style.start}If you modify this file, you are VIOLATING a project constraint.${style.end || ""}`,
837
+ `${style.start}LOCKED: ${lockText}${style.end || ""}`,
838
+ `${style.start}THIS FILE IS LOCKED. DO NOT EDIT, CHANGE, OR REWRITE ANY PART OF IT.${style.end || ""}`,
839
+ `${style.start}The user must say "unlock" before this file can be changed.${style.end || ""}`,
840
+ `${style.start}A question is NOT permission. Asking about features is NOT permission.${style.end || ""}`,
841
+ `${style.start}ONLY "unlock" or "remove the lock" is permission to edit this file.${style.end || ""}`,
834
842
  `${style.start}${"=".repeat(60)}${style.end || ""}`,
835
843
  "",
836
844
  ];
@@ -841,9 +849,9 @@ export function guardFile(root, relativeFilePath, lockText) {
841
849
  return { success: true };
842
850
  }
843
851
 
844
- // --- Package.json marker injection (makes Bolt.new auto-discover SpecLock) ---
852
+ // --- Package.json lock sync (Solution 2: embed active locks directly in package.json) ---
845
853
 
846
- export function injectPackageJsonMarker(root) {
854
+ export function syncLocksToPackageJson(root) {
847
855
  const pkgPath = path.join(root, "package.json");
848
856
  if (!fs.existsSync(pkgPath)) {
849
857
  return { success: false, error: "No package.json found in project root." };
@@ -852,21 +860,157 @@ export function injectPackageJsonMarker(root) {
852
860
  try {
853
861
  const raw = fs.readFileSync(pkgPath, "utf-8");
854
862
  const pkg = JSON.parse(raw);
863
+ const brain = readBrain(root);
864
+ if (!brain) {
865
+ return { success: false, error: "SpecLock not initialized." };
866
+ }
867
+
868
+ const activeLocks = brain.specLock.items
869
+ .filter((l) => l.active !== false)
870
+ .map((l) => l.text);
855
871
 
856
- // Add speclock marker
857
872
  pkg.speclock = {
858
873
  active: true,
859
- rules: "IMPORTANT: Read SPECLOCK.md before making ANY changes to this project. Read .speclock/context/latest.md for project memory, goals, locks, and decisions. Run 'npx speclock check' before modifying locked files.",
874
+ message: "STOP This project has SpecLock constraints. Read SPECLOCK.md and .speclock/context/latest.md BEFORE making ANY changes. Run 'npx speclock check' before ALL code changes. If a lock below is violated, STOP and ask user to unlock.",
875
+ locks: activeLocks,
876
+ context: ".speclock/context/latest.md",
877
+ rules: "SPECLOCK.md",
860
878
  };
861
879
 
862
- // Write back with same formatting
863
880
  const indent = raw.match(/^(\s+)/m)?.[1] || " ";
864
881
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, indent) + "\n");
865
882
 
866
- return { success: true };
883
+ return { success: true, lockCount: activeLocks.length };
867
884
  } catch (err) {
868
- return { success: false, error: `Failed to modify package.json: ${err.message}` };
885
+ return { success: false, error: `Failed to sync locks to package.json: ${err.message}` };
886
+ }
887
+ }
888
+
889
+ // Backward-compatible alias
890
+ export function injectPackageJsonMarker(root) {
891
+ return syncLocksToPackageJson(root);
892
+ }
893
+
894
+ // --- Auto-guard related files (Solution 1: scan project and guard files matching lock keywords) ---
895
+
896
+ const FILE_KEYWORD_PATTERNS = [
897
+ { keywords: ["auth", "authentication", "login", "signup", "signin", "sign-in", "sign-up"], patterns: ["**/Auth*", "**/auth*", "**/Login*", "**/login*", "**/SignUp*", "**/signup*", "**/SignIn*", "**/signin*", "**/*Auth*", "**/*auth*"] },
898
+ { keywords: ["database", "db", "supabase", "firebase", "mongo", "postgres", "sql", "prisma"], patterns: ["**/supabase*", "**/firebase*", "**/database*", "**/db.*", "**/db/**", "**/prisma/**", "**/*Client*", "**/*client*"] },
899
+ { keywords: ["payment", "pay", "stripe", "billing", "checkout", "subscription"], patterns: ["**/payment*", "**/Payment*", "**/pay*", "**/Pay*", "**/stripe*", "**/Stripe*", "**/billing*", "**/Billing*", "**/checkout*", "**/Checkout*"] },
900
+ { keywords: ["api", "endpoint", "route", "routes"], patterns: ["**/api/**", "**/routes/**", "**/endpoints/**"] },
901
+ { keywords: ["config", "configuration", "settings", "env"], patterns: ["**/config*", "**/Config*", "**/settings*", "**/Settings*"] },
902
+ ];
903
+
904
+ function findRelatedFiles(root, lockText) {
905
+ const lockLower = lockText.toLowerCase();
906
+ const matchedFiles = [];
907
+
908
+ // Find which keyword patterns match this lock text
909
+ const matchingPatterns = [];
910
+ for (const group of FILE_KEYWORD_PATTERNS) {
911
+ const hasMatch = group.keywords.some((kw) => lockLower.includes(kw));
912
+ if (hasMatch) {
913
+ matchingPatterns.push(...group.patterns);
914
+ }
869
915
  }
916
+
917
+ if (matchingPatterns.length === 0) return matchedFiles;
918
+
919
+ // Scan the src/ directory (and common directories) for matching files
920
+ const searchDirs = ["src", "app", "components", "pages", "lib", "utils", "contexts", "hooks", "services"];
921
+
922
+ for (const dir of searchDirs) {
923
+ const dirPath = path.join(root, dir);
924
+ if (!fs.existsSync(dirPath)) continue;
925
+ scanDirForMatches(root, dirPath, matchingPatterns, matchedFiles);
926
+ }
927
+
928
+ // Also check root-level files
929
+ try {
930
+ const rootFiles = fs.readdirSync(root);
931
+ for (const file of rootFiles) {
932
+ const fullPath = path.join(root, file);
933
+ if (!fs.statSync(fullPath).isFile()) continue;
934
+ const ext = path.extname(file).slice(1).toLowerCase();
935
+ if (!GUARD_MARKERS[ext]) continue;
936
+
937
+ for (const pattern of matchingPatterns) {
938
+ const simpleMatch = patternMatchesFile(pattern, file);
939
+ if (simpleMatch) {
940
+ const rel = path.relative(root, fullPath).replace(/\\/g, "/");
941
+ if (!matchedFiles.includes(rel)) matchedFiles.push(rel);
942
+ }
943
+ }
944
+ }
945
+ } catch (_) {}
946
+
947
+ return matchedFiles;
948
+ }
949
+
950
+ function scanDirForMatches(root, dirPath, patterns, results) {
951
+ try {
952
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
953
+ for (const entry of entries) {
954
+ const fullPath = path.join(dirPath, entry.name);
955
+ if (entry.isDirectory()) {
956
+ if (entry.name === "node_modules" || entry.name === ".speclock" || entry.name === ".git") continue;
957
+ scanDirForMatches(root, fullPath, patterns, results);
958
+ } else if (entry.isFile()) {
959
+ const ext = path.extname(entry.name).slice(1).toLowerCase();
960
+ if (!GUARD_MARKERS[ext]) continue;
961
+ const relPath = path.relative(root, fullPath).replace(/\\/g, "/");
962
+ for (const pattern of patterns) {
963
+ if (patternMatchesFile(pattern, relPath) || patternMatchesFile(pattern, entry.name)) {
964
+ if (!results.includes(relPath)) results.push(relPath);
965
+ break;
966
+ }
967
+ }
968
+ }
969
+ }
970
+ } catch (_) {}
971
+ }
972
+
973
+ function patternMatchesFile(pattern, filePath) {
974
+ // Simple glob matching: convert glob to regex
975
+ // Handle ** (any path), * (any chars in segment)
976
+ const clean = pattern.replace(/\\/g, "/");
977
+ const fileLower = filePath.toLowerCase();
978
+ const patternLower = clean.toLowerCase();
979
+
980
+ // Strip leading **/ for simple name matching
981
+ const namePattern = patternLower.replace(/^\*\*\//, "");
982
+
983
+ // Check if pattern is just a name pattern (no path separators)
984
+ if (!namePattern.includes("/")) {
985
+ const fileName = fileLower.split("/").pop();
986
+ // Convert glob * to regex .*
987
+ const regex = new RegExp("^" + namePattern.replace(/\*/g, ".*") + "$");
988
+ if (regex.test(fileName)) return true;
989
+ // Also check if the pattern appears anywhere in the filename
990
+ const corePattern = namePattern.replace(/\*/g, "");
991
+ if (corePattern.length > 2 && fileName.includes(corePattern)) return true;
992
+ }
993
+
994
+ // Full path match
995
+ const regex = new RegExp("^" + patternLower.replace(/\*\*\//g, "(.*/)?").replace(/\*/g, "[^/]*") + "$");
996
+ return regex.test(fileLower);
997
+ }
998
+
999
+ export function autoGuardRelatedFiles(root, lockText) {
1000
+ const relatedFiles = findRelatedFiles(root, lockText);
1001
+ const guarded = [];
1002
+ const skipped = [];
1003
+
1004
+ for (const relFile of relatedFiles) {
1005
+ const result = guardFile(root, relFile, lockText);
1006
+ if (result.success) {
1007
+ guarded.push(relFile);
1008
+ } else {
1009
+ skipped.push({ file: relFile, reason: result.error });
1010
+ }
1011
+ }
1012
+
1013
+ return { guarded, skipped, scannedPatterns: relatedFiles.length };
870
1014
  }
871
1015
 
872
1016
  export function unguardFile(root, relativeFilePath) {
package/src/mcp/server.js CHANGED
@@ -16,6 +16,8 @@ import {
16
16
  endSession,
17
17
  suggestLocks,
18
18
  detectDrift,
19
+ syncLocksToPackageJson,
20
+ autoGuardRelatedFiles,
19
21
  } from "../core/engine.js";
20
22
  import { generateContext, generateContextPack } from "../core/context.js";
21
23
  import {
@@ -171,9 +173,19 @@ server.tool(
171
173
  },
172
174
  async ({ text, tags, source }) => {
173
175
  const { lockId } = addLock(PROJECT_ROOT, text, tags, source);
176
+
177
+ // Auto-guard related files
178
+ const guardResult = autoGuardRelatedFiles(PROJECT_ROOT, text);
179
+ const guardMsg = guardResult.guarded.length > 0
180
+ ? `\nAuto-guarded ${guardResult.guarded.length} file(s): ${guardResult.guarded.join(", ")}`
181
+ : "";
182
+
183
+ // Sync active locks to package.json
184
+ syncLocksToPackageJson(PROJECT_ROOT);
185
+
174
186
  return {
175
187
  content: [
176
- { type: "text", text: `Lock added (${lockId}): "${text}"` },
188
+ { type: "text", text: `Lock added (${lockId}): "${text}"${guardMsg}` },
177
189
  ],
178
190
  };
179
191
  }
@@ -194,6 +206,8 @@ server.tool(
194
206
  isError: true,
195
207
  };
196
208
  }
209
+ // Sync updated locks to package.json
210
+ syncLocksToPackageJson(PROJECT_ROOT);
197
211
  return {
198
212
  content: [
199
213
  {