speclock 5.2.6 → 5.3.1
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 +144 -24
- package/package.json +242 -67
- package/src/cli/index.js +137 -7
- package/src/core/auth.js +341 -341
- package/src/core/compliance.js +1 -1
- package/src/core/engine.js +63 -1
- package/src/core/lock-author.js +487 -487
- package/src/core/replay.js +236 -0
- package/src/core/rules-sync.js +553 -0
- package/src/core/templates.js +69 -0
- package/src/dashboard/index.html +2 -2
- package/src/mcp/http-server.js +3 -3
- package/src/mcp/server.js +130 -1
package/src/core/compliance.js
CHANGED
package/src/core/engine.js
CHANGED
|
@@ -85,6 +85,7 @@ import { handleFileEvent } from "./tracking.js";
|
|
|
85
85
|
export async function watchRepo(root) {
|
|
86
86
|
const { default: chokidar } = await import("chokidar");
|
|
87
87
|
const brain = ensureInit(root);
|
|
88
|
+
const activeLocks = (brain.specLock?.items || []).filter((l) => l.active !== false);
|
|
88
89
|
const ignore = [
|
|
89
90
|
"**/node_modules/**",
|
|
90
91
|
"**/.git/**",
|
|
@@ -92,6 +93,39 @@ export async function watchRepo(root) {
|
|
|
92
93
|
];
|
|
93
94
|
|
|
94
95
|
let lastFileEventAt = 0;
|
|
96
|
+
let checksThisSession = 0;
|
|
97
|
+
let blockedThisSession = 0;
|
|
98
|
+
let warnedThisSession = 0;
|
|
99
|
+
const recentActivity = [];
|
|
100
|
+
|
|
101
|
+
function addActivity(icon, message) {
|
|
102
|
+
const time = new Date().toISOString().substring(11, 19);
|
|
103
|
+
recentActivity.unshift({ time, icon, message });
|
|
104
|
+
if (recentActivity.length > 8) recentActivity.length = 8;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function printDashboard() {
|
|
108
|
+
const lines = [];
|
|
109
|
+
lines.push("");
|
|
110
|
+
lines.push(" SpecLock Watch — Live Dashboard");
|
|
111
|
+
lines.push(" " + "=".repeat(50));
|
|
112
|
+
lines.push(` Constraints: ${activeLocks.length} active lock(s)`);
|
|
113
|
+
lines.push(` Session: ${checksThisSession} checks | ${warnedThisSession} warned | ${blockedThisSession} blocked`);
|
|
114
|
+
lines.push(` Project: ${brain.project.name}`);
|
|
115
|
+
lines.push("");
|
|
116
|
+
if (recentActivity.length > 0) {
|
|
117
|
+
lines.push(" Recent Activity:");
|
|
118
|
+
for (const a of recentActivity) {
|
|
119
|
+
lines.push(` ${a.time} [${a.icon}] ${a.message}`);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
lines.push(" Waiting for file changes...");
|
|
123
|
+
}
|
|
124
|
+
lines.push("");
|
|
125
|
+
lines.push(" Press Ctrl+C to stop.");
|
|
126
|
+
lines.push("");
|
|
127
|
+
console.log(lines.join("\n"));
|
|
128
|
+
}
|
|
95
129
|
|
|
96
130
|
const watcher = chokidar.watch(root, {
|
|
97
131
|
ignored: ignore,
|
|
@@ -101,15 +135,41 @@ export async function watchRepo(root) {
|
|
|
101
135
|
|
|
102
136
|
watcher.on("add", (p) => {
|
|
103
137
|
lastFileEventAt = Date.now();
|
|
138
|
+
const rel = path.relative(root, p);
|
|
104
139
|
handleFileEvent(root, brain, "file_created", p);
|
|
140
|
+
checksThisSession++;
|
|
141
|
+
addActivity("ADD", rel);
|
|
142
|
+
printDashboard();
|
|
105
143
|
});
|
|
106
144
|
watcher.on("change", (p) => {
|
|
107
145
|
lastFileEventAt = Date.now();
|
|
146
|
+
const rel = path.relative(root, p);
|
|
108
147
|
handleFileEvent(root, brain, "file_changed", p);
|
|
148
|
+
checksThisSession++;
|
|
149
|
+
// Check if this file is guarded by any lock
|
|
150
|
+
const guarded = activeLocks.some((lock) => {
|
|
151
|
+
const lockLower = (lock.text || "").toLowerCase();
|
|
152
|
+
const fileLower = rel.toLowerCase();
|
|
153
|
+
return lockLower.includes(fileLower) ||
|
|
154
|
+
(fileLower.includes("auth") && lockLower.includes("auth")) ||
|
|
155
|
+
(fileLower.includes("payment") && lockLower.includes("payment")) ||
|
|
156
|
+
(fileLower.includes("database") && lockLower.includes("database"));
|
|
157
|
+
});
|
|
158
|
+
if (guarded) {
|
|
159
|
+
warnedThisSession++;
|
|
160
|
+
addActivity("WARN", `${rel} — touches guarded area`);
|
|
161
|
+
} else {
|
|
162
|
+
addActivity("EDIT", rel);
|
|
163
|
+
}
|
|
164
|
+
printDashboard();
|
|
109
165
|
});
|
|
110
166
|
watcher.on("unlink", (p) => {
|
|
111
167
|
lastFileEventAt = Date.now();
|
|
168
|
+
const rel = path.relative(root, p);
|
|
112
169
|
handleFileEvent(root, brain, "file_deleted", p);
|
|
170
|
+
checksThisSession++;
|
|
171
|
+
addActivity("DEL", rel);
|
|
172
|
+
printDashboard();
|
|
113
173
|
});
|
|
114
174
|
|
|
115
175
|
// Revert detection via HEAD polling
|
|
@@ -139,6 +199,8 @@ export async function watchRepo(root) {
|
|
|
139
199
|
bumpEvents(brain, eventId);
|
|
140
200
|
appendEvent(root, event);
|
|
141
201
|
writeBrain(root, brain);
|
|
202
|
+
addActivity("REVERT", `HEAD → ${head.gitCommit.substring(0, 12)}`);
|
|
203
|
+
printDashboard();
|
|
142
204
|
}
|
|
143
205
|
brain.state.head.gitBranch = head.gitBranch;
|
|
144
206
|
brain.state.head.gitCommit = head.gitCommit;
|
|
@@ -147,7 +209,7 @@ export async function watchRepo(root) {
|
|
|
147
209
|
}, 5000);
|
|
148
210
|
}
|
|
149
211
|
|
|
150
|
-
|
|
212
|
+
printDashboard();
|
|
151
213
|
return watcher;
|
|
152
214
|
}
|
|
153
215
|
|