speclock 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sandeep Roy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,289 @@
1
+ # SpecLock
2
+
3
+ **AI Continuity Engine** — The MCP server that kills AI amnesia.
4
+
5
+ > Developed by **Sandeep Roy** ([github.com/sgroy10](https://github.com/sgroy10))
6
+
7
+ [![npm version](https://img.shields.io/npm/v/speclock.svg)](https://www.npmjs.com/package/speclock)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
+ [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io)
10
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
11
+
12
+ ---
13
+
14
+ ## The Problem
15
+
16
+ Every AI coding tool forgets. Every. Single. Session.
17
+
18
+ - Claude Code forgets the decisions you made yesterday
19
+ - Cursor forgets the constraints you set last week
20
+ - Codex rebuilds what another agent already built
21
+ - Your AI agent violates rules it agreed to 3 sessions ago
22
+
23
+ **AI amnesia is the #1 productivity killer in AI-assisted development.**
24
+
25
+ ## The Solution
26
+
27
+ SpecLock maintains a `.speclock/` directory inside your repo that gives every AI agent perfect memory — across sessions, across tools, across time.
28
+
29
+ ```
30
+ .speclock/
31
+ ├── brain.json # Structured project memory
32
+ ├── events.log # Append-only event ledger (JSONL)
33
+ ├── patches/ # Git diffs captured per event
34
+ └── context/
35
+ └── latest.md # Always-fresh context pack for any AI agent
36
+ ```
37
+
38
+ Any AI tool calls `speclock_session_briefing` → gets **full project context** → works with complete memory → calls `speclock_session_summary` → next session continues seamlessly.
39
+
40
+ **No context is ever lost again.**
41
+
42
+ ## Why SpecLock Wins
43
+
44
+ | Feature | CLAUDE.md / .cursorrules | Chat History | Memory Plugins | **SpecLock** |
45
+ |---------|--------------------------|--------------|----------------|--------------|
46
+ | Structured memory | Static files | Noise-heavy | Generic | **Structured brain.json** |
47
+ | Constraint enforcement | None | None | None | **Active lock checking** |
48
+ | Conflict detection | None | None | None | **Semantic + synonym matching** |
49
+ | Drift detection | None | None | None | **Auto-scan against locks** |
50
+ | Git-aware | No | No | No | **Checkpoints, diffs, reverts** |
51
+ | Multi-agent | No | No | Partial | **Full session timeline** |
52
+ | Auto-suggestions | No | No | No | **AI-powered lock suggestions** |
53
+ | Cross-tool | Tool-specific | Tool-specific | Tool-specific | **Universal MCP** |
54
+
55
+ **Other tools remember. SpecLock enforces.**
56
+
57
+ ## Quick Start
58
+
59
+ ### 1. Install
60
+
61
+ ```bash
62
+ npm install -g speclock
63
+ ```
64
+
65
+ Or use directly with npx:
66
+
67
+ ```bash
68
+ npx speclock init
69
+ ```
70
+
71
+ ### 2. Initialize in Your Project
72
+
73
+ ```bash
74
+ cd your-project
75
+ speclock init
76
+ speclock goal "Ship v1 of the product"
77
+ speclock lock "No breaking changes to public API"
78
+ speclock decide "Use PostgreSQL for persistence"
79
+ ```
80
+
81
+ ### 3. Connect to Your AI Tool
82
+
83
+ **Claude Code** — Add to `.claude/settings.json`:
84
+
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "speclock": {
89
+ "command": "npx",
90
+ "args": ["-y", "speclock", "serve", "--project", "."]
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ **Cursor** — Add to `.cursor/mcp.json`:
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "speclock": {
102
+ "command": "npx",
103
+ "args": ["-y", "speclock", "serve", "--project", "."]
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ **Windsurf / Cline / Any MCP tool** — Same pattern:
110
+
111
+ ```json
112
+ {
113
+ "mcpServers": {
114
+ "speclock": {
115
+ "command": "npx",
116
+ "args": ["-y", "speclock", "serve", "--project", "."]
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### 4. Use It
123
+
124
+ The AI tool now has access to **19 SpecLock tools**. The key workflow:
125
+
126
+ 1. **Start session**: AI calls `speclock_session_briefing` — gets full context + what changed
127
+ 2. **During work**: AI uses `add_decision`, `log_change`, `check_conflict`, `detect_drift`
128
+ 3. **End session**: AI calls `speclock_session_summary` — records what was accomplished
129
+ 4. **Next session (any tool)**: Step 1 repeats — full continuity preserved
130
+
131
+ ## MCP Tools (19)
132
+
133
+ ### Memory Management
134
+ | Tool | Purpose |
135
+ |------|---------|
136
+ | `speclock_init` | Initialize SpecLock in project |
137
+ | `speclock_get_context` | **THE KEY TOOL** — full context pack |
138
+ | `speclock_set_goal` | Set/update project goal |
139
+ | `speclock_add_lock` | Add non-negotiable constraint |
140
+ | `speclock_remove_lock` | Deactivate a lock by ID |
141
+ | `speclock_add_decision` | Record an architectural decision |
142
+ | `speclock_add_note` | Add a pinned note |
143
+ | `speclock_set_deploy_facts` | Record deploy configuration |
144
+
145
+ ### Change Tracking
146
+ | Tool | Purpose |
147
+ |------|---------|
148
+ | `speclock_log_change` | Manually log a significant change |
149
+ | `speclock_get_changes` | Get recent tracked changes |
150
+ | `speclock_get_events` | Get event log (filterable by type/time) |
151
+
152
+ ### Continuity Protection
153
+ | Tool | Purpose |
154
+ |------|---------|
155
+ | `speclock_check_conflict` | Check action against locks (semantic matching) |
156
+ | `speclock_session_briefing` | Start session + full briefing |
157
+ | `speclock_session_summary` | End session + record summary |
158
+
159
+ ### Git Integration
160
+ | Tool | Purpose |
161
+ |------|---------|
162
+ | `speclock_checkpoint` | Create named git tag for rollback |
163
+ | `speclock_repo_status` | Branch, commit, changed files, diff |
164
+
165
+ ### Intelligence (NEW in v1.1)
166
+ | Tool | Purpose |
167
+ |------|---------|
168
+ | `speclock_suggest_locks` | AI-powered lock suggestions from patterns |
169
+ | `speclock_detect_drift` | Scan changes for constraint violations |
170
+ | `speclock_health` | Health score + multi-agent timeline |
171
+
172
+ ## Killer Features
173
+
174
+ ### Semantic Conflict Detection
175
+
176
+ Not just keyword matching — SpecLock understands synonyms, negation, and destructive intent:
177
+
178
+ ```
179
+ Lock: "No breaking changes to public API"
180
+
181
+ Action: "remove the external endpoints"
182
+ Result: [HIGH] Conflict detected (confidence: 85%)
183
+ - synonym match: remove/delete, external/public, endpoints/api
184
+ - lock prohibits this action (negation detected)
185
+ - destructive action against locked constraint
186
+ ```
187
+
188
+ ### Auto-Lock Suggestions
189
+
190
+ SpecLock analyzes your decisions and notes for commitment language and suggests constraints:
191
+
192
+ ```
193
+ Decision: "Always use REST for public endpoints"
194
+ → Suggestion: Promote to lock (contains "always" — strong commitment language)
195
+
196
+ Project mentions "security" but has no security lock
197
+ → Suggestion: "No secrets or credentials in source code"
198
+ ```
199
+
200
+ ### Drift Detection
201
+
202
+ Proactively scans recent changes against your locks:
203
+
204
+ ```
205
+ Lock: "No database schema changes without migration"
206
+ Change: "Modified users table schema directly"
207
+ → [HIGH] Drift detected — review immediately
208
+ ```
209
+
210
+ ### Multi-Agent Timeline
211
+
212
+ Track which AI tools touched your project and what they did:
213
+
214
+ ```
215
+ Health Check — Score: 85/100 (Grade: A)
216
+
217
+ Multi-Agent Timeline:
218
+ - claude-code: 12 sessions, last active 2026-02-24
219
+ - cursor: 5 sessions, last active 2026-02-23
220
+ - codex: 2 sessions, last active 2026-02-20
221
+ ```
222
+
223
+ ## CLI Commands
224
+
225
+ ```
226
+ speclock init Initialize SpecLock
227
+ speclock goal <text> Set project goal
228
+ speclock lock <text> [--tags a,b] Add a SpecLock constraint
229
+ speclock lock remove <id> Remove a lock
230
+ speclock decide <text> Record a decision
231
+ speclock note <text> Add a note
232
+ speclock facts deploy --provider X Set deploy facts
233
+ speclock context Generate and print context pack
234
+ speclock watch Start file watcher
235
+ speclock serve [--project <path>] Start MCP server
236
+ speclock status Show brain summary
237
+ ```
238
+
239
+ ## Why MCP?
240
+
241
+ MCP (Model Context Protocol) is the universal integration standard for AI tools. One SpecLock MCP server works with:
242
+
243
+ - Claude Code
244
+ - Cursor
245
+ - Windsurf
246
+ - Cline
247
+ - Codex
248
+ - Any MCP-compatible tool
249
+
250
+ SpecLock is infrastructure, not a competitor. It makes **every** AI coding tool better.
251
+
252
+ ## Architecture
253
+
254
+ ```
255
+ ┌─────────────────────────────────────────────────────────┐
256
+ │ AI Tool (Claude Code, Cursor, etc.) │
257
+ └────────────────────┬────────────────────────────────────┘
258
+ │ MCP Protocol (stdio)
259
+ ┌────────────────────▼────────────────────────────────────┐
260
+ │ SpecLock MCP Server (19 tools) │
261
+ │ Memory | Tracking | Protection | Git | Intelligence │
262
+ └────────────────────┬────────────────────────────────────┘
263
+
264
+ .speclock/
265
+ ├── brain.json (structured memory)
266
+ ├── events.log (immutable audit trail)
267
+ ├── patches/ (git diffs per event)
268
+ └── context/ (generated context packs)
269
+ ```
270
+
271
+ ## Contributing
272
+
273
+ Contributions welcome! Please open an issue or PR on [GitHub](https://github.com/sgroy10/speclock).
274
+
275
+ ## License
276
+
277
+ MIT License - see [LICENSE](LICENSE) file.
278
+
279
+ ## Author
280
+
281
+ **Developed by Sandeep Roy**
282
+
283
+ - GitHub: [github.com/sgroy10](https://github.com/sgroy10)
284
+ - Repository: [github.com/sgroy10/speclock](https://github.com/sgroy10/speclock)
285
+ - npm: [npmjs.com/package/speclock](https://www.npmjs.com/package/speclock)
286
+
287
+ ---
288
+
289
+ *SpecLock — Because no AI session should ever forget.*
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../src/cli/index.js";
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "speclock",
3
+ "version": "1.1.0",
4
+ "description": "AI continuity engine — MCP server + CLI that kills AI amnesia. Maintains project memory, enforces constraints, and detects drift across AI coding sessions.",
5
+ "type": "module",
6
+ "main": "src/mcp/server.js",
7
+ "bin": {
8
+ "speclock": "./bin/speclock.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/mcp/server.js",
12
+ "serve": "node src/mcp/server.js",
13
+ "test": "node --experimental-vm-modules node_modules/.bin/jest"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "mcp-server",
18
+ "ai",
19
+ "ai-memory",
20
+ "ai-continuity",
21
+ "context",
22
+ "memory",
23
+ "claude",
24
+ "claude-code",
25
+ "cursor",
26
+ "codex",
27
+ "windsurf",
28
+ "cline",
29
+ "speclock",
30
+ "ai-amnesia",
31
+ "model-context-protocol",
32
+ "drift-detection",
33
+ "constraint-enforcement"
34
+ ],
35
+ "author": "Sandeep Roy (https://github.com/sgroy10)",
36
+ "license": "MIT",
37
+ "homepage": "https://github.com/sgroy10/speclock#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/sgroy10/speclock/issues"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/sgroy10/speclock.git"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ },
48
+ "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.26.0",
50
+ "chokidar": "^3.6.0",
51
+ "zod": "^3.25.0"
52
+ },
53
+ "files": [
54
+ "bin/",
55
+ "src/",
56
+ "README.md",
57
+ "LICENSE"
58
+ ]
59
+ }
@@ -0,0 +1,285 @@
1
+ import path from "path";
2
+ import {
3
+ ensureInit,
4
+ setGoal,
5
+ addLock,
6
+ removeLock,
7
+ addDecision,
8
+ addNote,
9
+ updateDeployFacts,
10
+ watchRepo,
11
+ } from "../core/engine.js";
12
+ import { generateContext } from "../core/context.js";
13
+ import { readBrain } from "../core/storage.js";
14
+
15
+ // --- Argument parsing ---
16
+
17
+ function parseArgs(argv) {
18
+ const args = argv.slice(2);
19
+ const cmd = args.shift() || "";
20
+ return { cmd, args };
21
+ }
22
+
23
+ function parseFlags(args) {
24
+ const out = { _: [] };
25
+ for (let i = 0; i < args.length; i++) {
26
+ const a = args[i];
27
+ if (a.startsWith("--")) {
28
+ const key = a.slice(2);
29
+ const next = args[i + 1];
30
+ if (!next || next.startsWith("--")) {
31
+ out[key] = true;
32
+ } else {
33
+ out[key] = next;
34
+ i++;
35
+ }
36
+ } else {
37
+ out._.push(a);
38
+ }
39
+ }
40
+ return out;
41
+ }
42
+
43
+ function parseTags(raw) {
44
+ if (!raw) return [];
45
+ return raw
46
+ .split(",")
47
+ .map((t) => t.trim())
48
+ .filter(Boolean);
49
+ }
50
+
51
+ function rootDir() {
52
+ return process.cwd();
53
+ }
54
+
55
+ // --- Help text ---
56
+
57
+ function printHelp() {
58
+ console.log(`
59
+ SpecLock v1.1.0 — AI Continuity Engine
60
+ Developed by Sandeep Roy (github.com/sgroy10)
61
+
62
+ Usage: speclock <command> [options]
63
+
64
+ Commands:
65
+ init Initialize SpecLock in current directory
66
+ goal <text> Set or update the project goal
67
+ lock <text> [--tags a,b] Add a non-negotiable constraint (SpecLock)
68
+ lock remove <id> Remove a lock by ID
69
+ decide <text> [--tags a,b] Record a decision
70
+ note <text> [--pinned] Add a pinned note
71
+ facts deploy [--provider X] Set deployment facts
72
+ context Generate and print context pack
73
+ watch Start file watcher (auto-track changes)
74
+ serve [--project <path>] Start MCP stdio server
75
+ status Show project brain summary
76
+
77
+ Options:
78
+ --tags <a,b,c> Comma-separated tags
79
+ --source <user|agent> Who created this (default: user)
80
+ --provider <name> Deploy provider
81
+ --branch <name> Deploy branch
82
+ --autoDeploy <true|false> Auto-deploy setting
83
+ --url <url> Deployment URL
84
+ --notes <text> Additional notes
85
+ --project <path> Project root (for serve)
86
+
87
+ Examples:
88
+ speclock init
89
+ speclock goal "Ship v1 of the continuity engine"
90
+ speclock lock "No external database in v1" --tags scope
91
+ speclock decide "Use MCP as primary integration" --tags architecture
92
+ speclock context
93
+ speclock serve --project /path/to/repo
94
+
95
+ MCP Tools (19): init, get_context, set_goal, add_lock, remove_lock,
96
+ add_decision, add_note, set_deploy_facts, log_change, get_changes,
97
+ get_events, check_conflict, session_briefing, session_summary,
98
+ checkpoint, repo_status, suggest_locks, detect_drift, health
99
+ `);
100
+ }
101
+
102
+ // --- Status display ---
103
+
104
+ function showStatus(root) {
105
+ const brain = readBrain(root);
106
+ if (!brain) {
107
+ console.log("SpecLock not initialized. Run: speclock init");
108
+ return;
109
+ }
110
+
111
+ const activeLocks = brain.specLock.items.filter((l) => l.active !== false);
112
+
113
+ console.log(`\nSpecLock Status — ${brain.project.name}`);
114
+ console.log("=".repeat(50));
115
+ console.log(`Goal: ${brain.goal.text || "(not set)"}`);
116
+ console.log(`SpecLocks: ${activeLocks.length} active`);
117
+ console.log(`Decisions: ${brain.decisions.length}`);
118
+ console.log(`Notes: ${brain.notes.length}`);
119
+ console.log(`Events: ${brain.events.count}`);
120
+ console.log(`Deploy: ${brain.facts.deploy.provider}`);
121
+
122
+ if (brain.sessions.current) {
123
+ console.log(`Session: active (${brain.sessions.current.toolUsed})`);
124
+ } else {
125
+ console.log(`Session: none active`);
126
+ }
127
+
128
+ if (brain.sessions.history.length > 0) {
129
+ const last = brain.sessions.history[0];
130
+ console.log(
131
+ `Last session: ${last.toolUsed} — ${last.summary || "(no summary)"}`
132
+ );
133
+ }
134
+
135
+ console.log(`Recent changes: ${brain.state.recentChanges.length}`);
136
+ console.log(`Reverts: ${brain.state.reverts.length}`);
137
+ console.log("");
138
+ }
139
+
140
+ // --- Main ---
141
+
142
+ async function main() {
143
+ const { cmd, args } = parseArgs(process.argv);
144
+ const root = rootDir();
145
+
146
+ if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
147
+ printHelp();
148
+ process.exit(0);
149
+ }
150
+
151
+ if (cmd === "init") {
152
+ ensureInit(root);
153
+ console.log("SpecLock initialized.");
154
+ return;
155
+ }
156
+
157
+ if (cmd === "goal") {
158
+ const text = args.join(" ").trim();
159
+ if (!text) {
160
+ console.error("Error: Goal text is required.");
161
+ console.error("Usage: speclock goal <text>");
162
+ process.exit(1);
163
+ }
164
+ setGoal(root, text);
165
+ console.log(`Goal set: "${text}"`);
166
+ return;
167
+ }
168
+
169
+ if (cmd === "lock") {
170
+ // Check for "lock remove <id>"
171
+ if (args[0] === "remove") {
172
+ const lockId = args[1];
173
+ if (!lockId) {
174
+ console.error("Error: Lock ID is required.");
175
+ console.error("Usage: speclock lock remove <lockId>");
176
+ process.exit(1);
177
+ }
178
+ const result = removeLock(root, lockId);
179
+ if (result.removed) {
180
+ console.log(`Lock removed: "${result.lockText}"`);
181
+ } else {
182
+ console.error(result.error);
183
+ process.exit(1);
184
+ }
185
+ return;
186
+ }
187
+
188
+ const flags = parseFlags(args);
189
+ const text = flags._.join(" ").trim();
190
+ if (!text) {
191
+ console.error("Error: Lock text is required.");
192
+ console.error("Usage: speclock lock <text> [--tags a,b] [--source user]");
193
+ process.exit(1);
194
+ }
195
+ const { lockId } = addLock(root, text, parseTags(flags.tags), flags.source || "user");
196
+ console.log(`SpecLock added (${lockId}): "${text}"`);
197
+ return;
198
+ }
199
+
200
+ if (cmd === "decide") {
201
+ const flags = parseFlags(args);
202
+ const text = flags._.join(" ").trim();
203
+ if (!text) {
204
+ console.error("Error: Decision text is required.");
205
+ console.error("Usage: speclock decide <text> [--tags a,b]");
206
+ process.exit(1);
207
+ }
208
+ const { decId } = addDecision(root, text, parseTags(flags.tags), flags.source || "user");
209
+ console.log(`Decision recorded (${decId}): "${text}"`);
210
+ return;
211
+ }
212
+
213
+ if (cmd === "note") {
214
+ const flags = parseFlags(args);
215
+ const text = flags._.join(" ").trim();
216
+ if (!text) {
217
+ console.error("Error: Note text is required.");
218
+ console.error("Usage: speclock note <text> [--pinned]");
219
+ process.exit(1);
220
+ }
221
+ const pinned = flags.pinned !== false;
222
+ const { noteId } = addNote(root, text, pinned);
223
+ console.log(`Note added (${noteId}): "${text}"`);
224
+ return;
225
+ }
226
+
227
+ if (cmd === "facts") {
228
+ const sub = args.shift();
229
+ if (sub !== "deploy") {
230
+ console.error("Error: Only 'facts deploy' is supported.");
231
+ console.error(
232
+ "Usage: speclock facts deploy --provider X --branch Y"
233
+ );
234
+ process.exit(1);
235
+ }
236
+ const flags = parseFlags(args);
237
+ const payload = {
238
+ provider: flags.provider,
239
+ branch: flags.branch,
240
+ notes: flags.notes,
241
+ url: flags.url,
242
+ };
243
+ if (flags.autoDeploy !== undefined) {
244
+ payload.autoDeploy =
245
+ String(flags.autoDeploy).toLowerCase() === "true";
246
+ }
247
+ updateDeployFacts(root, payload);
248
+ console.log("Deploy facts updated.");
249
+ return;
250
+ }
251
+
252
+ if (cmd === "context") {
253
+ const md = generateContext(root);
254
+ console.log(md);
255
+ return;
256
+ }
257
+
258
+ if (cmd === "watch") {
259
+ await watchRepo(root);
260
+ return;
261
+ }
262
+
263
+ if (cmd === "serve") {
264
+ // Start MCP server — pass through --project if provided
265
+ const flags = parseFlags(args);
266
+ const projectArg = flags.project || root;
267
+ process.env.SPECLOCK_PROJECT_ROOT = projectArg;
268
+ await import("../mcp/server.js");
269
+ return;
270
+ }
271
+
272
+ if (cmd === "status") {
273
+ showStatus(root);
274
+ return;
275
+ }
276
+
277
+ console.error(`Unknown command: ${cmd}`);
278
+ console.error("Run 'speclock --help' for usage.");
279
+ process.exit(1);
280
+ }
281
+
282
+ main().catch((err) => {
283
+ console.error("SpecLock error:", err.message);
284
+ process.exit(1);
285
+ });