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 +21 -0
- package/README.md +289 -0
- package/bin/speclock.js +2 -0
- package/package.json +59 -0
- package/src/cli/index.js +285 -0
- package/src/core/context.js +201 -0
- package/src/core/engine.js +698 -0
- package/src/core/git.js +110 -0
- package/src/core/storage.js +186 -0
- package/src/mcp/server.js +730 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import {
|
|
4
|
+
speclockDir,
|
|
5
|
+
readBrain,
|
|
6
|
+
newId,
|
|
7
|
+
nowIso,
|
|
8
|
+
appendEvent,
|
|
9
|
+
bumpEvents,
|
|
10
|
+
writeBrain,
|
|
11
|
+
} from "./storage.js";
|
|
12
|
+
import { captureStatus } from "./git.js";
|
|
13
|
+
import { ensureInit } from "./engine.js";
|
|
14
|
+
|
|
15
|
+
// Generate structured context pack as a JavaScript object
|
|
16
|
+
export function generateContextPack(root) {
|
|
17
|
+
const brain = ensureInit(root);
|
|
18
|
+
const status = brain.facts.repo.hasGit ? captureStatus(root) : null;
|
|
19
|
+
|
|
20
|
+
const activeLocks = brain.specLock.items.filter((l) => l.active !== false);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
project: {
|
|
24
|
+
name: brain.project.name,
|
|
25
|
+
root: brain.project.root,
|
|
26
|
+
branch: status ? status.branch : "",
|
|
27
|
+
commit: status ? status.commit : "",
|
|
28
|
+
},
|
|
29
|
+
goal: brain.goal.text || "",
|
|
30
|
+
locks: activeLocks.slice(0, 15).map((l) => ({
|
|
31
|
+
id: l.id,
|
|
32
|
+
text: l.text,
|
|
33
|
+
createdAt: l.createdAt,
|
|
34
|
+
source: l.source,
|
|
35
|
+
})),
|
|
36
|
+
decisions: brain.decisions.slice(0, 12).map((d) => ({
|
|
37
|
+
id: d.id,
|
|
38
|
+
text: d.text,
|
|
39
|
+
createdAt: d.createdAt,
|
|
40
|
+
source: d.source,
|
|
41
|
+
})),
|
|
42
|
+
deployFacts: { ...brain.facts.deploy },
|
|
43
|
+
recentChanges: brain.state.recentChanges.slice(0, 20),
|
|
44
|
+
reverts: brain.state.reverts.slice(0, 10),
|
|
45
|
+
lastSession:
|
|
46
|
+
brain.sessions.history.length > 0 ? brain.sessions.history[0] : null,
|
|
47
|
+
currentSession: brain.sessions.current || null,
|
|
48
|
+
notes: brain.notes
|
|
49
|
+
.filter((n) => n.pinned)
|
|
50
|
+
.slice(0, 10)
|
|
51
|
+
.map((n) => ({ id: n.id, text: n.text })),
|
|
52
|
+
generatedAt: nowIso(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Generate markdown context pack + write to file
|
|
57
|
+
export function generateContext(root) {
|
|
58
|
+
const brain = ensureInit(root);
|
|
59
|
+
const pack = generateContextPack(root);
|
|
60
|
+
const contextPath = path.join(
|
|
61
|
+
speclockDir(root),
|
|
62
|
+
"context",
|
|
63
|
+
"latest.md"
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const lines = [];
|
|
67
|
+
|
|
68
|
+
// Header
|
|
69
|
+
lines.push("# SpecLock Context Pack");
|
|
70
|
+
lines.push(`> Generated: ${pack.generatedAt}`);
|
|
71
|
+
lines.push(`> Project: **${pack.project.name}**`);
|
|
72
|
+
if (pack.project.branch) {
|
|
73
|
+
lines.push(
|
|
74
|
+
`> Repo: branch \`${pack.project.branch}\` @ \`${pack.project.commit}\``
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
lines.push("");
|
|
78
|
+
|
|
79
|
+
// Goal
|
|
80
|
+
lines.push("## Goal");
|
|
81
|
+
lines.push(pack.goal || "*(No goal set — consider setting one)*");
|
|
82
|
+
lines.push("");
|
|
83
|
+
|
|
84
|
+
// SpecLocks — HIGH PRIORITY
|
|
85
|
+
lines.push("## SpecLock (Non-Negotiables)");
|
|
86
|
+
if (pack.locks.length > 0) {
|
|
87
|
+
lines.push(
|
|
88
|
+
"> **These constraints MUST be followed. Do not violate any lock.**"
|
|
89
|
+
);
|
|
90
|
+
for (const lock of pack.locks) {
|
|
91
|
+
lines.push(`- **[LOCK]** ${lock.text} _(${lock.source}, ${lock.createdAt.substring(0, 10)})_`);
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
lines.push("- *(No locks defined — consider adding constraints)*");
|
|
95
|
+
}
|
|
96
|
+
lines.push("");
|
|
97
|
+
|
|
98
|
+
// Decisions
|
|
99
|
+
lines.push("## Key Decisions");
|
|
100
|
+
if (pack.decisions.length > 0) {
|
|
101
|
+
for (const dec of pack.decisions) {
|
|
102
|
+
lines.push(`- **[DEC]** ${dec.text} _(${dec.source}, ${dec.createdAt.substring(0, 10)})_`);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
lines.push("- *(No decisions recorded)*");
|
|
106
|
+
}
|
|
107
|
+
lines.push("");
|
|
108
|
+
|
|
109
|
+
// Deploy Facts
|
|
110
|
+
if (pack.deployFacts.provider !== "unknown") {
|
|
111
|
+
lines.push("## Deploy Facts");
|
|
112
|
+
lines.push(`- Provider: **${pack.deployFacts.provider}**`);
|
|
113
|
+
if (pack.deployFacts.branch)
|
|
114
|
+
lines.push(`- Branch: \`${pack.deployFacts.branch}\``);
|
|
115
|
+
lines.push(`- Auto-deploy: ${pack.deployFacts.autoDeploy ? "Yes" : "No"}`);
|
|
116
|
+
if (pack.deployFacts.url) lines.push(`- URL: ${pack.deployFacts.url}`);
|
|
117
|
+
if (pack.deployFacts.notes) lines.push(`- Notes: ${pack.deployFacts.notes}`);
|
|
118
|
+
lines.push("");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Recent Changes
|
|
122
|
+
lines.push("## Recent Changes");
|
|
123
|
+
if (pack.recentChanges.length > 0) {
|
|
124
|
+
for (const ch of pack.recentChanges.slice(0, 20)) {
|
|
125
|
+
const files =
|
|
126
|
+
ch.files && ch.files.length > 0 ? ` (${ch.files.join(", ")})` : "";
|
|
127
|
+
lines.push(`- [${ch.at.substring(0, 19)}] ${ch.summary}${files}`);
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
lines.push("- *(No changes tracked yet)*");
|
|
131
|
+
}
|
|
132
|
+
lines.push("");
|
|
133
|
+
|
|
134
|
+
// Reverts — CRITICAL
|
|
135
|
+
if (pack.reverts.length > 0) {
|
|
136
|
+
lines.push("## ⚠ Reverts Detected");
|
|
137
|
+
lines.push(
|
|
138
|
+
"> **WARNING: Git reverts/checkouts were detected. Verify current state before proceeding.**"
|
|
139
|
+
);
|
|
140
|
+
for (const rev of pack.reverts) {
|
|
141
|
+
lines.push(
|
|
142
|
+
`- [REVERT] ${rev.kind} to \`${rev.target.substring(0, 12)}\` at ${rev.at.substring(0, 19)}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
lines.push("");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Session History
|
|
149
|
+
if (pack.lastSession) {
|
|
150
|
+
lines.push("## Last Session");
|
|
151
|
+
lines.push(`- Tool: **${pack.lastSession.toolUsed}**`);
|
|
152
|
+
lines.push(`- Ended: ${pack.lastSession.endedAt || "still active"}`);
|
|
153
|
+
if (pack.lastSession.summary)
|
|
154
|
+
lines.push(`- Summary: ${pack.lastSession.summary}`);
|
|
155
|
+
lines.push(
|
|
156
|
+
`- Events in session: ${pack.lastSession.eventsInSession || 0}`
|
|
157
|
+
);
|
|
158
|
+
lines.push("");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Pinned Notes
|
|
162
|
+
if (pack.notes.length > 0) {
|
|
163
|
+
lines.push("## Pinned Notes");
|
|
164
|
+
for (const note of pack.notes) {
|
|
165
|
+
lines.push(`- **[NOTE]** ${note.text}`);
|
|
166
|
+
}
|
|
167
|
+
lines.push("");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Agent Instructions
|
|
171
|
+
lines.push("## Agent Instructions");
|
|
172
|
+
lines.push("1. Follow ALL SpecLock items strictly — they are non-negotiable.");
|
|
173
|
+
lines.push("2. Do not contradict recorded decisions without explicit user approval.");
|
|
174
|
+
lines.push("3. If you detect drift from constraints, stop and flag it.");
|
|
175
|
+
lines.push("4. Call `speclock_detect_drift` proactively to check for constraint violations.");
|
|
176
|
+
lines.push("5. Call `speclock_get_context` to refresh this context at any time.");
|
|
177
|
+
lines.push("6. Call `speclock_session_summary` before ending your session.");
|
|
178
|
+
lines.push("");
|
|
179
|
+
lines.push("---");
|
|
180
|
+
lines.push("*Powered by [SpecLock](https://github.com/sgroy10/speclock) — Developed by Sandeep Roy*");
|
|
181
|
+
lines.push("");
|
|
182
|
+
|
|
183
|
+
const markdown = lines.join("\n");
|
|
184
|
+
fs.writeFileSync(contextPath, markdown);
|
|
185
|
+
|
|
186
|
+
// Record the generation event
|
|
187
|
+
const eventId = newId("evt");
|
|
188
|
+
const event = {
|
|
189
|
+
eventId,
|
|
190
|
+
type: "context_generated",
|
|
191
|
+
at: nowIso(),
|
|
192
|
+
files: [".speclock/context/latest.md"],
|
|
193
|
+
summary: "Generated context pack",
|
|
194
|
+
patchPath: "",
|
|
195
|
+
};
|
|
196
|
+
bumpEvents(brain, eventId);
|
|
197
|
+
appendEvent(root, event);
|
|
198
|
+
writeBrain(root, brain);
|
|
199
|
+
|
|
200
|
+
return markdown;
|
|
201
|
+
}
|