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.
@@ -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
+ }