@lumenflow/cli 3.14.0 → 3.15.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/dist/chunk-2D2VOCA4.js +37 -0
- package/dist/chunk-2D5KFYGX.js +284 -0
- package/dist/chunk-2GXVIN57.js +14072 -0
- package/dist/chunk-2MQ7HZWZ.js +26 -0
- package/dist/chunk-2UFQ3A3C.js +643 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-4N74J3UT.js +15 -0
- package/dist/chunk-5GTOXFYR.js +392 -0
- package/dist/chunk-5VY6MQMC.js +240 -0
- package/dist/chunk-67XVPMRY.js +1297 -0
- package/dist/chunk-6HO4GWJE.js +164 -0
- package/dist/chunk-6W5XHWYV.js +1890 -0
- package/dist/chunk-6X4EMYJQ.js +64 -0
- package/dist/chunk-6XYXI2NQ.js +772 -0
- package/dist/chunk-7ANSOV6Q.js +285 -0
- package/dist/chunk-A624LFLB.js +1380 -0
- package/dist/chunk-ADN5NHG4.js +126 -0
- package/dist/chunk-B7YJYJKG.js +33 -0
- package/dist/chunk-CCLHCPKG.js +210 -0
- package/dist/chunk-CK36VROC.js +1584 -0
- package/dist/chunk-D3UOFRSB.js +81 -0
- package/dist/chunk-DFR4DJBM.js +230 -0
- package/dist/chunk-DSYBDHYH.js +79 -0
- package/dist/chunk-DWMLTXKQ.js +1176 -0
- package/dist/chunk-E3REJTAJ.js +28 -0
- package/dist/chunk-EA3IVO64.js +633 -0
- package/dist/chunk-EK2AKZKD.js +55 -0
- package/dist/chunk-ELD7JTTT.js +343 -0
- package/dist/chunk-EX6TT2XI.js +195 -0
- package/dist/chunk-EXINSFZE.js +82 -0
- package/dist/chunk-EZ6ZBYBM.js +510 -0
- package/dist/chunk-FBKAPTJ2.js +16 -0
- package/dist/chunk-FVLV5RYH.js +1118 -0
- package/dist/chunk-GDNSBQVK.js +2485 -0
- package/dist/chunk-GPQHMBNN.js +278 -0
- package/dist/chunk-GTFJB67L.js +68 -0
- package/dist/chunk-HANJXVKW.js +1127 -0
- package/dist/chunk-HEVS5YLD.js +269 -0
- package/dist/chunk-HMEVZKPQ.js +9 -0
- package/dist/chunk-HRGSYNLM.js +3511 -0
- package/dist/chunk-ISZR5N4K.js +60 -0
- package/dist/chunk-J6SUPR2C.js +226 -0
- package/dist/chunk-JERYVEIZ.js +244 -0
- package/dist/chunk-JHHWGL2N.js +87 -0
- package/dist/chunk-JONWQUB5.js +775 -0
- package/dist/chunk-K2DIWWDM.js +1766 -0
- package/dist/chunk-KY4PGL5V.js +969 -0
- package/dist/chunk-L737LQ4C.js +1285 -0
- package/dist/chunk-LFTWYIB2.js +497 -0
- package/dist/chunk-LV47RFNJ.js +41 -0
- package/dist/chunk-MKSAITI7.js +15 -0
- package/dist/chunk-MZ7RKIX4.js +212 -0
- package/dist/chunk-NAP6CFSO.js +84 -0
- package/dist/chunk-ND6MY37M.js +16 -0
- package/dist/chunk-NMG736UR.js +683 -0
- package/dist/chunk-NRAXROED.js +32 -0
- package/dist/chunk-NRIZR3A7.js +690 -0
- package/dist/chunk-NX43BG3M.js +233 -0
- package/dist/chunk-O645XLSI.js +297 -0
- package/dist/chunk-OMJD6A3S.js +235 -0
- package/dist/chunk-QB6SJD4T.js +430 -0
- package/dist/chunk-QFSTL4J3.js +276 -0
- package/dist/chunk-QLGDFMFX.js +212 -0
- package/dist/chunk-RIAAGL2E.js +13 -0
- package/dist/chunk-RWO5XMZ6.js +86 -0
- package/dist/chunk-RXRKBBSM.js +149 -0
- package/dist/chunk-RZOZMML6.js +363 -0
- package/dist/chunk-U7I7FS7T.js +113 -0
- package/dist/chunk-UI42RODY.js +717 -0
- package/dist/chunk-UTVMVSCO.js +519 -0
- package/dist/chunk-V6OJGLBA.js +1746 -0
- package/dist/chunk-W2JHVH7D.js +152 -0
- package/dist/chunk-WD3Y7VQN.js +280 -0
- package/dist/chunk-WOCTQ5MS.js +303 -0
- package/dist/chunk-WZR3ZUNN.js +696 -0
- package/dist/chunk-XGI665H7.js +150 -0
- package/dist/chunk-XKY65P2T.js +304 -0
- package/dist/chunk-Y4CQZY65.js +57 -0
- package/dist/chunk-YFEXKLVE.js +194 -0
- package/dist/chunk-YHO3HS5X.js +287 -0
- package/dist/chunk-YLS7AZSX.js +738 -0
- package/dist/chunk-ZE473AO6.js +49 -0
- package/dist/chunk-ZF747T3O.js +644 -0
- package/dist/chunk-ZHCZHZH3.js +43 -0
- package/dist/chunk-ZZNZX2XY.js +87 -0
- package/dist/constants-7QAP3VQ4.js +23 -0
- package/dist/dist-IY3UUMWK.js +33 -0
- package/dist/docs-sync.js +60 -25
- package/dist/docs-sync.js.map +1 -1
- package/dist/gates-runners.js +43 -2
- package/dist/gates-runners.js.map +1 -1
- package/dist/init-templates.js +26 -219
- package/dist/init-templates.js.map +1 -1
- package/dist/invariants-runner-W5RGHCSU.js +27 -0
- package/dist/lane-lock-6J36HD5O.js +35 -0
- package/dist/lumenflow-upgrade.js +60 -0
- package/dist/lumenflow-upgrade.js.map +1 -1
- package/dist/mem-checkpoint-core-EANG2GVN.js +14 -0
- package/dist/mem-signal-core-2LZ2WYHW.js +19 -0
- package/dist/memory-store-OLB5FO7K.js +18 -0
- package/dist/plan-edit.js +19 -24
- package/dist/plan-edit.js.map +1 -1
- package/dist/plan-promote.js +15 -23
- package/dist/plan-promote.js.map +1 -1
- package/dist/plan-resolve.js +111 -0
- package/dist/plan-resolve.js.map +1 -0
- package/dist/public-manifest.js +2 -2
- package/dist/public-manifest.js.map +1 -1
- package/dist/service-6BYCOCO5.js +13 -0
- package/dist/spawn-policy-resolver-NTSZYQ6R.js +17 -0
- package/dist/spawn-task-builder-R4E2BHSW.js +22 -0
- package/dist/sync-templates.js +12 -0
- package/dist/sync-templates.js.map +1 -1
- package/dist/wu-claim-validation.js +9 -1
- package/dist/wu-claim-validation.js.map +1 -1
- package/dist/wu-done-pr-WLFFFEPJ.js +25 -0
- package/dist/wu-done-validation-3J5E36FE.js +30 -0
- package/dist/wu-done.js +42 -1
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-duplicate-id-detector-5S7JHELK.js +232 -0
- package/dist/wu-edit-operations.js +7 -6
- package/dist/wu-edit-operations.js.map +1 -1
- package/dist/wu-edit.js +23 -3
- package/dist/wu-edit.js.map +1 -1
- package/dist/wu-spawn-prompt-builders.js +38 -1
- package/dist/wu-spawn-prompt-builders.js.map +1 -1
- package/package.json +8 -8
- package/packs/sidekick/.turbo/turbo-build.log +1 -1
- package/packs/sidekick/.turbo/turbo-typecheck.log +4 -0
- package/packs/sidekick/package.json +1 -1
- package/packs/software-delivery/.turbo/turbo-build.log +1 -1
- package/packs/software-delivery/.turbo/turbo-typecheck.log +4 -0
- package/packs/software-delivery/package.json +1 -1
- package/templates/core/AGENTS.md.template +19 -0
- package/templates/core/LUMENFLOW.md.template +13 -2
- package/templates/core/UPGRADING.md.template +6 -6
- package/templates/core/ai/onboarding/first-15-mins.md.template +1 -1
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +10 -0
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +11 -8
- package/templates/core/ai/onboarding/starting-prompt.md.template +1 -1
- package/templates/core/ai/onboarding/wu-sizing-guide.md.template +11 -2
- package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +9 -1
- package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +9 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_HALF_LIFE_MS,
|
|
3
|
+
computeDecayScore
|
|
4
|
+
} from "./chunk-ZE473AO6.js";
|
|
5
|
+
import {
|
|
6
|
+
MEMORY_FILE_NAME,
|
|
7
|
+
loadMemory,
|
|
8
|
+
loadMemoryAll
|
|
9
|
+
} from "./chunk-DFR4DJBM.js";
|
|
10
|
+
import {
|
|
11
|
+
LUMENFLOW_MEMORY_PATHS
|
|
12
|
+
} from "./chunk-4N74J3UT.js";
|
|
13
|
+
import {
|
|
14
|
+
ErrorCodes,
|
|
15
|
+
createError
|
|
16
|
+
} from "./chunk-RXRKBBSM.js";
|
|
17
|
+
|
|
18
|
+
// ../memory/dist/decay/archival.js
|
|
19
|
+
import fs from "fs/promises";
|
|
20
|
+
import path from "path";
|
|
21
|
+
var DEFAULT_DECAY_THRESHOLD = 0.1;
|
|
22
|
+
var ARCHIVED_STATUS = "archived";
|
|
23
|
+
var PROTECTED_LIFECYCLE = "project";
|
|
24
|
+
function isArchived(node) {
|
|
25
|
+
return node.metadata?.status === ARCHIVED_STATUS;
|
|
26
|
+
}
|
|
27
|
+
function isProtected(node) {
|
|
28
|
+
return node.lifecycle === PROTECTED_LIFECYCLE;
|
|
29
|
+
}
|
|
30
|
+
function markAsArchived(node, score, threshold, now) {
|
|
31
|
+
const timestamp = new Date(now).toISOString();
|
|
32
|
+
return {
|
|
33
|
+
...node,
|
|
34
|
+
metadata: {
|
|
35
|
+
...node.metadata,
|
|
36
|
+
status: ARCHIVED_STATUS,
|
|
37
|
+
archived_at: timestamp,
|
|
38
|
+
decay: {
|
|
39
|
+
...node.metadata?.decay,
|
|
40
|
+
score,
|
|
41
|
+
reason: `Score ${score.toFixed(4)} below threshold ${threshold}`,
|
|
42
|
+
computed_at: timestamp
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function writeMemoryFile(baseDir, nodes) {
|
|
48
|
+
const filePath = path.join(baseDir, MEMORY_FILE_NAME);
|
|
49
|
+
const content = nodes.map((n) => JSON.stringify(n)).join("\n") + (nodes.length > 0 ? "\n" : "");
|
|
50
|
+
await fs.writeFile(filePath, content, { encoding: "utf-8" });
|
|
51
|
+
}
|
|
52
|
+
async function archiveByDecay(baseDir, options = {}) {
|
|
53
|
+
const { threshold = DEFAULT_DECAY_THRESHOLD, now = Date.now(), halfLifeMs = DEFAULT_HALF_LIFE_MS, dryRun = false } = options;
|
|
54
|
+
const memory = await loadMemory(baseDir, { includeArchived: true });
|
|
55
|
+
const archivedIds = [];
|
|
56
|
+
const retainedIds = [];
|
|
57
|
+
const skippedIds = [];
|
|
58
|
+
const updatedNodes = memory.nodes.map((node) => {
|
|
59
|
+
if (isArchived(node)) {
|
|
60
|
+
skippedIds.push(node.id);
|
|
61
|
+
return node;
|
|
62
|
+
}
|
|
63
|
+
if (isProtected(node)) {
|
|
64
|
+
skippedIds.push(node.id);
|
|
65
|
+
return node;
|
|
66
|
+
}
|
|
67
|
+
const score = computeDecayScore(node, { now, halfLifeMs });
|
|
68
|
+
if (score < threshold) {
|
|
69
|
+
archivedIds.push(node.id);
|
|
70
|
+
return markAsArchived(node, score, threshold, now);
|
|
71
|
+
}
|
|
72
|
+
retainedIds.push(node.id);
|
|
73
|
+
return node;
|
|
74
|
+
});
|
|
75
|
+
if (!dryRun && archivedIds.length > 0) {
|
|
76
|
+
await writeMemoryFile(baseDir, updatedNodes);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
archivedIds,
|
|
80
|
+
retainedIds,
|
|
81
|
+
skippedIds,
|
|
82
|
+
totalProcessed: memory.nodes.length,
|
|
83
|
+
...dryRun ? { dryRun: true } : {}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ../memory/dist/mem-cleanup-core.js
|
|
88
|
+
import fs2 from "fs/promises";
|
|
89
|
+
import path2 from "path";
|
|
90
|
+
import { createRequire } from "module";
|
|
91
|
+
var require2 = createRequire(import.meta.url);
|
|
92
|
+
var ms = require2("ms");
|
|
93
|
+
var LIFECYCLE_POLICY = {
|
|
94
|
+
/** Ephemeral nodes are always removed - scratch pad data */
|
|
95
|
+
ephemeral: { alwaysRemove: true, requiresSummarized: false },
|
|
96
|
+
/** Session nodes removed when session is closed */
|
|
97
|
+
session: { alwaysRemove: false, requiresSummarized: true },
|
|
98
|
+
/** WU nodes removed only when summarized_into is set */
|
|
99
|
+
wu: { alwaysRemove: false, requiresSummarized: true },
|
|
100
|
+
/** Project nodes are never removed - architectural knowledge */
|
|
101
|
+
project: { alwaysRemove: false, requiresSummarized: false, protected: true }
|
|
102
|
+
};
|
|
103
|
+
var SENSITIVE_FLAG = "sensitive";
|
|
104
|
+
var ACTIVE_SESSION_STATUS = "active";
|
|
105
|
+
function parseTtl(ttlString) {
|
|
106
|
+
if (!ttlString || typeof ttlString !== "string") {
|
|
107
|
+
throw createError(ErrorCodes.INVALID_DURATION, "Invalid TTL format: TTL string is required");
|
|
108
|
+
}
|
|
109
|
+
const trimmed = ttlString.trim();
|
|
110
|
+
if (!trimmed) {
|
|
111
|
+
throw createError(ErrorCodes.INVALID_DURATION, "Invalid TTL format: TTL string is required");
|
|
112
|
+
}
|
|
113
|
+
const result = ms(trimmed);
|
|
114
|
+
if (result == null || result <= 0) {
|
|
115
|
+
throw createError(ErrorCodes.INVALID_DURATION, `Invalid TTL format: "${ttlString}" is not a valid duration`);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function isNodeExpired(node, ttlMs, now = Date.now()) {
|
|
120
|
+
if (!node.created_at) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const createdAt = new Date(node.created_at).getTime();
|
|
124
|
+
if (Number.isNaN(createdAt)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const age = now - createdAt;
|
|
128
|
+
return age > ttlMs;
|
|
129
|
+
}
|
|
130
|
+
function isActiveSession(node) {
|
|
131
|
+
if (node.type !== "session") {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (!node.metadata) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return node.metadata.status === ACTIVE_SESSION_STATUS;
|
|
138
|
+
}
|
|
139
|
+
function hasSensitiveFlag(node) {
|
|
140
|
+
if (!node.metadata) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
return Object.hasOwn(node.metadata, SENSITIVE_FLAG) && node.metadata[SENSITIVE_FLAG] === true;
|
|
144
|
+
}
|
|
145
|
+
function getLifecyclePolicy(lifecycle) {
|
|
146
|
+
if (!Object.hasOwn(LIFECYCLE_POLICY, lifecycle)) {
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
return LIFECYCLE_POLICY[lifecycle];
|
|
150
|
+
}
|
|
151
|
+
function shouldRemoveNode(node, options = {}) {
|
|
152
|
+
const { sessionId, ttlMs, now = Date.now() } = options;
|
|
153
|
+
if (isActiveSession(node)) {
|
|
154
|
+
return { remove: false, reason: "active-session-protected" };
|
|
155
|
+
}
|
|
156
|
+
if (hasSensitiveFlag(node)) {
|
|
157
|
+
return { remove: false, reason: "sensitive-retained" };
|
|
158
|
+
}
|
|
159
|
+
const policy = getLifecyclePolicy(node.lifecycle);
|
|
160
|
+
if (!policy) {
|
|
161
|
+
return { remove: false, reason: "unknown-lifecycle" };
|
|
162
|
+
}
|
|
163
|
+
if (policy.protected) {
|
|
164
|
+
return { remove: false, reason: "protected-lifecycle" };
|
|
165
|
+
}
|
|
166
|
+
if (ttlMs && isNodeExpired(node, ttlMs, now)) {
|
|
167
|
+
return { remove: true, reason: "ttl-expired" };
|
|
168
|
+
}
|
|
169
|
+
if (policy.alwaysRemove) {
|
|
170
|
+
return { remove: true, reason: "ephemeral-cleanup" };
|
|
171
|
+
}
|
|
172
|
+
if (node.lifecycle === "session" && sessionId && node.session_id === sessionId) {
|
|
173
|
+
return { remove: true, reason: "session-closed" };
|
|
174
|
+
}
|
|
175
|
+
if (policy.requiresSummarized && node.metadata?.summarized_into) {
|
|
176
|
+
return { remove: true, reason: "summarized-archived" };
|
|
177
|
+
}
|
|
178
|
+
return { remove: false, reason: "policy-retained" };
|
|
179
|
+
}
|
|
180
|
+
function estimateNodeBytes(node) {
|
|
181
|
+
return JSON.stringify(node).length + 1;
|
|
182
|
+
}
|
|
183
|
+
function getCompactionRatio(removedCount, totalCount) {
|
|
184
|
+
if (totalCount === 0) {
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
return removedCount / totalCount;
|
|
188
|
+
}
|
|
189
|
+
var REASON_TO_BREAKDOWN_KEY = {
|
|
190
|
+
"ephemeral-cleanup": "ephemeral",
|
|
191
|
+
"session-closed": "session",
|
|
192
|
+
"summarized-archived": "wu",
|
|
193
|
+
"sensitive-retained": "sensitive",
|
|
194
|
+
"ttl-expired": "ttlExpired",
|
|
195
|
+
"active-session-protected": "activeSessionProtected"
|
|
196
|
+
};
|
|
197
|
+
function updateBreakdown(breakdown, decision) {
|
|
198
|
+
const key = REASON_TO_BREAKDOWN_KEY[decision.reason];
|
|
199
|
+
if (key && Object.hasOwn(breakdown, key)) {
|
|
200
|
+
breakdown[key]++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function writeRetainedNodes(memoryDir, retainedNodes) {
|
|
204
|
+
const filePath = path2.join(memoryDir, MEMORY_FILE_NAME);
|
|
205
|
+
const content = retainedNodes.map((n) => JSON.stringify(n)).join("\n") + (retainedNodes.length > 0 ? "\n" : "");
|
|
206
|
+
await fs2.writeFile(filePath, content, { encoding: "utf-8" });
|
|
207
|
+
}
|
|
208
|
+
async function cleanupMemory(baseDir, options = {}) {
|
|
209
|
+
const { dryRun = false, sessionId, ttl, ttlMs: providedTtlMs, now = Date.now(), decay = false, decayThreshold = DEFAULT_DECAY_THRESHOLD, halfLifeMs = DEFAULT_HALF_LIFE_MS } = options;
|
|
210
|
+
const memoryDir = path2.join(baseDir, LUMENFLOW_MEMORY_PATHS.MEMORY_DIR);
|
|
211
|
+
let ttlMs = providedTtlMs;
|
|
212
|
+
if (ttl && !ttlMs) {
|
|
213
|
+
ttlMs = parseTtl(ttl);
|
|
214
|
+
}
|
|
215
|
+
let decayResult;
|
|
216
|
+
if (decay) {
|
|
217
|
+
decayResult = await archiveByDecay(memoryDir, {
|
|
218
|
+
threshold: decayThreshold,
|
|
219
|
+
now,
|
|
220
|
+
halfLifeMs,
|
|
221
|
+
dryRun
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const memory = await loadMemoryAll(memoryDir);
|
|
225
|
+
const removedIds = [];
|
|
226
|
+
const retainedIds = [];
|
|
227
|
+
const retainedNodes = [];
|
|
228
|
+
let bytesFreed = 0;
|
|
229
|
+
const breakdown = {
|
|
230
|
+
ephemeral: 0,
|
|
231
|
+
session: 0,
|
|
232
|
+
wu: 0,
|
|
233
|
+
sensitive: 0,
|
|
234
|
+
ttlExpired: 0,
|
|
235
|
+
activeSessionProtected: 0,
|
|
236
|
+
decayArchived: decayResult?.archivedIds.length ?? 0
|
|
237
|
+
};
|
|
238
|
+
for (const node of memory.nodes) {
|
|
239
|
+
const decision = shouldRemoveNode(node, { sessionId, ttlMs, now });
|
|
240
|
+
if (decision.remove) {
|
|
241
|
+
removedIds.push(node.id);
|
|
242
|
+
bytesFreed += estimateNodeBytes(node);
|
|
243
|
+
} else {
|
|
244
|
+
retainedIds.push(node.id);
|
|
245
|
+
retainedNodes.push(node);
|
|
246
|
+
}
|
|
247
|
+
updateBreakdown(breakdown, decision);
|
|
248
|
+
}
|
|
249
|
+
const compactionRatio = getCompactionRatio(removedIds.length, memory.nodes.length);
|
|
250
|
+
const baseResult = {
|
|
251
|
+
success: true,
|
|
252
|
+
removedIds,
|
|
253
|
+
retainedIds,
|
|
254
|
+
bytesFreed,
|
|
255
|
+
compactionRatio,
|
|
256
|
+
breakdown
|
|
257
|
+
};
|
|
258
|
+
if (ttlMs) {
|
|
259
|
+
baseResult.ttlMs = ttlMs;
|
|
260
|
+
}
|
|
261
|
+
if (decayResult) {
|
|
262
|
+
baseResult.decayResult = decayResult;
|
|
263
|
+
}
|
|
264
|
+
if (dryRun) {
|
|
265
|
+
return { ...baseResult, dryRun: true };
|
|
266
|
+
}
|
|
267
|
+
if (removedIds.length > 0) {
|
|
268
|
+
await writeRetainedNodes(memoryDir, retainedNodes);
|
|
269
|
+
}
|
|
270
|
+
return baseResult;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export {
|
|
274
|
+
archiveByDecay,
|
|
275
|
+
cleanupMemory
|
|
276
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MEMORY_PATTERNS,
|
|
3
|
+
loadMemory
|
|
4
|
+
} from "./chunk-DFR4DJBM.js";
|
|
5
|
+
import {
|
|
6
|
+
LUMENFLOW_MEMORY_PATHS
|
|
7
|
+
} from "./chunk-4N74J3UT.js";
|
|
8
|
+
import {
|
|
9
|
+
ErrorCodes,
|
|
10
|
+
createError
|
|
11
|
+
} from "./chunk-RXRKBBSM.js";
|
|
12
|
+
|
|
13
|
+
// ../memory/dist/mem-recover-core.js
|
|
14
|
+
import fs from "fs/promises";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import { parse as parseYaml } from "yaml";
|
|
17
|
+
var DEFAULT_MAX_SIZE = 8192;
|
|
18
|
+
var NODE_TYPE_CHECKPOINT = "checkpoint";
|
|
19
|
+
var CONSTRAINTS_FILENAME = "constraints.md";
|
|
20
|
+
var TIMESTAMP_UNKNOWN = "unknown";
|
|
21
|
+
var MAX_ACCEPTANCE_ITEMS = 8;
|
|
22
|
+
var MAX_CODE_PATH_ITEMS = 10;
|
|
23
|
+
var ERROR_MESSAGES = {
|
|
24
|
+
WU_ID_REQUIRED: "wuId is required",
|
|
25
|
+
WU_ID_EMPTY: "wuId cannot be empty",
|
|
26
|
+
WU_ID_INVALID: "Invalid WU ID format. Expected pattern: WU-XXX (e.g., WU-1234)"
|
|
27
|
+
};
|
|
28
|
+
var SECTION_HEADERS = {
|
|
29
|
+
RECOVERY_TITLE: "POST-COMPACTION RECOVERY",
|
|
30
|
+
LAST_CHECKPOINT: "Last Checkpoint",
|
|
31
|
+
ACCEPTANCE_CRITERIA: "Acceptance Criteria",
|
|
32
|
+
CODE_PATHS: "Code Paths",
|
|
33
|
+
FILES_CHANGED: "Files Changed",
|
|
34
|
+
CRITICAL_RULES: "Critical Rules (DO NOT FORGET)",
|
|
35
|
+
CLI_COMMANDS: "CLI Commands",
|
|
36
|
+
NEXT_ACTION: "Next Action"
|
|
37
|
+
};
|
|
38
|
+
var ESSENTIAL_CLI_COMMANDS = `| Command | Purpose |
|
|
39
|
+
|---------|---------|
|
|
40
|
+
| pnpm wu:status --id WU-XXX | Check WU status and location |
|
|
41
|
+
| pnpm wu:brief --id WU-XXX | Generate fresh agent handoff prompt |
|
|
42
|
+
| pnpm gates | Run quality gates before completion |
|
|
43
|
+
| pnpm mem:checkpoint | Save progress checkpoint |`;
|
|
44
|
+
var FALLBACK_CONSTRAINTS = `1. **Worktree Discipline**: Work only in worktrees, treat main as read-only
|
|
45
|
+
2. **WUs Are Specs**: Respect code_paths boundaries, no feature creep
|
|
46
|
+
3. **Docs-Only vs Code**: Documentation WUs use --docs-only gates
|
|
47
|
+
4. **LLM-First Inference**: Use LLMs for semantic tasks, no brittle regex
|
|
48
|
+
5. **Gates Required**: Run pnpm gates before wu:done
|
|
49
|
+
6. **Safety & Governance**: No secrets in code, stop-and-ask for sensitive ops
|
|
50
|
+
7. **Test Ratchet**: NEW test failures block, pre-existing show warning only`;
|
|
51
|
+
function validateWuId(wuId) {
|
|
52
|
+
if (wuId == null) {
|
|
53
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, ERROR_MESSAGES.WU_ID_REQUIRED);
|
|
54
|
+
}
|
|
55
|
+
if (wuId === "") {
|
|
56
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, ERROR_MESSAGES.WU_ID_EMPTY);
|
|
57
|
+
}
|
|
58
|
+
if (!MEMORY_PATTERNS.WU_ID.test(wuId)) {
|
|
59
|
+
throw createError(ErrorCodes.INVALID_WU_ID, ERROR_MESSAGES.WU_ID_INVALID);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function getLastCheckpoint(baseDir, wuId) {
|
|
63
|
+
const memoryDir = path.join(baseDir, LUMENFLOW_MEMORY_PATHS.MEMORY_DIR);
|
|
64
|
+
try {
|
|
65
|
+
const memory = await loadMemory(memoryDir);
|
|
66
|
+
const wuNodes = memory.byWu.get(wuId) ?? [];
|
|
67
|
+
const checkpoints = wuNodes.filter((node) => node.type === NODE_TYPE_CHECKPOINT).sort((a, b) => {
|
|
68
|
+
const aTime = new Date(a.created_at).getTime();
|
|
69
|
+
const bTime = new Date(b.created_at).getTime();
|
|
70
|
+
return bTime - aTime;
|
|
71
|
+
});
|
|
72
|
+
if (checkpoints.length === 0) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const latest = checkpoints[0];
|
|
76
|
+
if (!latest)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
content: latest.content,
|
|
80
|
+
timestamp: latest.created_at,
|
|
81
|
+
progress: latest.metadata?.progress,
|
|
82
|
+
nextSteps: latest.metadata?.nextSteps,
|
|
83
|
+
gitDiffStat: latest.metadata?.gitDiffStat
|
|
84
|
+
};
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function loadCompactConstraints(baseDir) {
|
|
90
|
+
const constraintsPath = path.join(baseDir, LUMENFLOW_MEMORY_PATHS.BASE, CONSTRAINTS_FILENAME);
|
|
91
|
+
try {
|
|
92
|
+
const content = await fs.readFile(constraintsPath, "utf-8");
|
|
93
|
+
const rules = [];
|
|
94
|
+
const sectionPattern = /### (\d+)\. ([^\n]+)\n\n\*\*Rule:\*\* ([^\n]+)/g;
|
|
95
|
+
let match;
|
|
96
|
+
while ((match = sectionPattern.exec(content)) !== null) {
|
|
97
|
+
const [, num, title, rule] = match;
|
|
98
|
+
rules.push(`${num}. **${title}**: ${rule}`);
|
|
99
|
+
}
|
|
100
|
+
if (rules.length > 0) {
|
|
101
|
+
return rules.join("\n");
|
|
102
|
+
}
|
|
103
|
+
return FALLBACK_CONSTRAINTS;
|
|
104
|
+
} catch {
|
|
105
|
+
return FALLBACK_CONSTRAINTS;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function loadWuMetadata(baseDir, wuId) {
|
|
109
|
+
const wuYamlPath = path.join(baseDir, "docs", "04-operations", "tasks", "wu", `${wuId}.yaml`);
|
|
110
|
+
try {
|
|
111
|
+
const content = await fs.readFile(wuYamlPath, "utf-8");
|
|
112
|
+
const doc = parseYaml(content);
|
|
113
|
+
const acceptance = Array.isArray(doc.acceptance) ? doc.acceptance.filter((s) => typeof s === "string") : [];
|
|
114
|
+
const codePaths = Array.isArray(doc.code_paths) ? doc.code_paths.filter((s) => typeof s === "string") : [];
|
|
115
|
+
if (acceptance.length === 0 && codePaths.length === 0) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return { acceptance, codePaths };
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function formatRecoveryPrompt(wuId, checkpoint, constraints, cliRef, wuMetadata, maxSize) {
|
|
124
|
+
const header = `# ${SECTION_HEADERS.RECOVERY_TITLE}
|
|
125
|
+
|
|
126
|
+
You are resuming work after context compaction. Your previous context was lost.
|
|
127
|
+
**WU:** ${wuId}
|
|
128
|
+
|
|
129
|
+
`;
|
|
130
|
+
const checkpointSection = checkpoint ? `## ${SECTION_HEADERS.LAST_CHECKPOINT}
|
|
131
|
+
- **Progress:** ${checkpoint.content}
|
|
132
|
+
- **Timestamp:** ${checkpoint.timestamp}
|
|
133
|
+
${checkpoint.nextSteps ? `- **Next Steps:** ${checkpoint.nextSteps}` : ""}
|
|
134
|
+
|
|
135
|
+
` : `## ${SECTION_HEADERS.LAST_CHECKPOINT}
|
|
136
|
+
No checkpoint found for this WU.
|
|
137
|
+
|
|
138
|
+
`;
|
|
139
|
+
const acceptanceItems = wuMetadata?.acceptance.slice(0, MAX_ACCEPTANCE_ITEMS) ?? [];
|
|
140
|
+
const acceptanceTruncated = (wuMetadata?.acceptance.length ?? 0) > MAX_ACCEPTANCE_ITEMS;
|
|
141
|
+
const acceptanceSection = acceptanceItems.length > 0 ? `## ${SECTION_HEADERS.ACCEPTANCE_CRITERIA}
|
|
142
|
+
${acceptanceItems.map((ac) => `- [ ] ${ac}`).join("\n")}${acceptanceTruncated ? `
|
|
143
|
+
- ... (${(wuMetadata?.acceptance.length ?? 0) - MAX_ACCEPTANCE_ITEMS} more \u2014 run wu:brief for full list)` : ""}
|
|
144
|
+
|
|
145
|
+
` : "";
|
|
146
|
+
const codePathItems = wuMetadata?.codePaths.slice(0, MAX_CODE_PATH_ITEMS) ?? [];
|
|
147
|
+
const codePathsTruncated = (wuMetadata?.codePaths.length ?? 0) > MAX_CODE_PATH_ITEMS;
|
|
148
|
+
const codePathsSection = codePathItems.length > 0 ? `## ${SECTION_HEADERS.CODE_PATHS}
|
|
149
|
+
${codePathItems.map((cp) => `- ${cp}`).join("\n")}${codePathsTruncated ? `
|
|
150
|
+
- ... (${(wuMetadata?.codePaths.length ?? 0) - MAX_CODE_PATH_ITEMS} more)` : ""}
|
|
151
|
+
|
|
152
|
+
` : "";
|
|
153
|
+
const gitDiffSection = checkpoint?.gitDiffStat ? `## ${SECTION_HEADERS.FILES_CHANGED}
|
|
154
|
+
\`\`\`
|
|
155
|
+
${checkpoint.gitDiffStat}
|
|
156
|
+
\`\`\`
|
|
157
|
+
|
|
158
|
+
` : "";
|
|
159
|
+
const constraintsSection = constraints ? `## ${SECTION_HEADERS.CRITICAL_RULES}
|
|
160
|
+
${constraints}
|
|
161
|
+
|
|
162
|
+
` : "";
|
|
163
|
+
const cliSection = cliRef ? `## ${SECTION_HEADERS.CLI_COMMANDS}
|
|
164
|
+
${cliRef}
|
|
165
|
+
|
|
166
|
+
` : "";
|
|
167
|
+
const footer = `## ${SECTION_HEADERS.NEXT_ACTION}
|
|
168
|
+
Continue working on the WU using the acceptance criteria and code paths above.
|
|
169
|
+
If you need full context: \`pnpm wu:brief --id ${wuId}\`
|
|
170
|
+
`;
|
|
171
|
+
const fixedContent = header + acceptanceSection + codePathsSection + gitDiffSection + constraintsSection + cliSection + footer;
|
|
172
|
+
const fixedSize = Buffer.byteLength(fixedContent, "utf-8");
|
|
173
|
+
if (fixedSize > maxSize) {
|
|
174
|
+
const minimal = `# ${SECTION_HEADERS.RECOVERY_TITLE}
|
|
175
|
+
WU: ${wuId}
|
|
176
|
+
Run: pnpm wu:brief --id ${wuId}
|
|
177
|
+
`;
|
|
178
|
+
return { context: minimal, truncated: true };
|
|
179
|
+
}
|
|
180
|
+
const remainingBudget = maxSize - fixedSize;
|
|
181
|
+
const checkpointSize = Buffer.byteLength(checkpointSection, "utf-8");
|
|
182
|
+
if (checkpointSize <= remainingBudget) {
|
|
183
|
+
const fullContext = header + checkpointSection + acceptanceSection + codePathsSection + gitDiffSection + constraintsSection + cliSection + footer;
|
|
184
|
+
return { context: fullContext, truncated: false };
|
|
185
|
+
}
|
|
186
|
+
const truncatedCheckpoint = `## ${SECTION_HEADERS.LAST_CHECKPOINT}
|
|
187
|
+
- **Progress:** (truncated - run mem:ready --wu ${wuId} for details)
|
|
188
|
+
- **Timestamp:** ${checkpoint?.timestamp ?? TIMESTAMP_UNKNOWN}
|
|
189
|
+
|
|
190
|
+
`;
|
|
191
|
+
const context = header + truncatedCheckpoint + acceptanceSection + codePathsSection + gitDiffSection + constraintsSection + cliSection + footer;
|
|
192
|
+
return { context, truncated: true };
|
|
193
|
+
}
|
|
194
|
+
async function generateRecoveryContext(options) {
|
|
195
|
+
const { wuId, baseDir = ".", maxSize = DEFAULT_MAX_SIZE, includeConstraints = true, includeCLIRef = true, includeWuMetadata = true } = options;
|
|
196
|
+
validateWuId(wuId);
|
|
197
|
+
const checkpoint = await getLastCheckpoint(baseDir, wuId);
|
|
198
|
+
const constraints = includeConstraints ? await loadCompactConstraints(baseDir) : "";
|
|
199
|
+
const cliRef = includeCLIRef ? ESSENTIAL_CLI_COMMANDS : "";
|
|
200
|
+
const wuMetadata = includeWuMetadata ? await loadWuMetadata(baseDir, wuId) : null;
|
|
201
|
+
const { context, truncated } = formatRecoveryPrompt(wuId, checkpoint, constraints, cliRef, wuMetadata, maxSize);
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
context,
|
|
205
|
+
size: Buffer.byteLength(context, "utf-8"),
|
|
206
|
+
truncated
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export {
|
|
211
|
+
generateRecoveryContext
|
|
212
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// ../memory/dist/mem-id.js
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
var MEM_ID_PREFIX = "mem-";
|
|
4
|
+
var HEX_SUFFIX_LENGTH = 4;
|
|
5
|
+
function generateMemId(content) {
|
|
6
|
+
const hash = createHash("sha256").update(content, "utf-8").digest("hex");
|
|
7
|
+
const suffix = hash.slice(0, HEX_SUFFIX_LENGTH);
|
|
8
|
+
return `${MEM_ID_PREFIX}${suffix}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
generateMemId
|
|
13
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureMemoryDir
|
|
3
|
+
} from "./chunk-ND6MY37M.js";
|
|
4
|
+
import {
|
|
5
|
+
generateMemId
|
|
6
|
+
} from "./chunk-RIAAGL2E.js";
|
|
7
|
+
import {
|
|
8
|
+
MEMORY_PATTERNS,
|
|
9
|
+
appendNode
|
|
10
|
+
} from "./chunk-DFR4DJBM.js";
|
|
11
|
+
import {
|
|
12
|
+
ErrorCodes,
|
|
13
|
+
createError
|
|
14
|
+
} from "./chunk-RXRKBBSM.js";
|
|
15
|
+
|
|
16
|
+
// ../memory/dist/mem-checkpoint-core.js
|
|
17
|
+
var ERROR_MESSAGES = {
|
|
18
|
+
NOTE_REQUIRED: "note is required",
|
|
19
|
+
NOTE_EMPTY: "note cannot be empty",
|
|
20
|
+
WU_ID_INVALID: "Invalid WU ID format. Expected pattern: WU-XXX (e.g., WU-123)"
|
|
21
|
+
};
|
|
22
|
+
var NODE_TYPE_CHECKPOINT = "checkpoint";
|
|
23
|
+
var LIFECYCLE_SESSION = "session";
|
|
24
|
+
function isValidWuId(wuId) {
|
|
25
|
+
if (!wuId)
|
|
26
|
+
return true;
|
|
27
|
+
return MEMORY_PATTERNS.WU_ID.test(wuId);
|
|
28
|
+
}
|
|
29
|
+
function generateCheckpointContent(note) {
|
|
30
|
+
return `Checkpoint: ${note}`;
|
|
31
|
+
}
|
|
32
|
+
async function createCheckpoint(baseDir, options) {
|
|
33
|
+
const { note, sessionId, wuId, progress, nextSteps, trigger, gitDiffStat } = options;
|
|
34
|
+
if (note == null) {
|
|
35
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, ERROR_MESSAGES.NOTE_REQUIRED);
|
|
36
|
+
}
|
|
37
|
+
if (note === "") {
|
|
38
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, ERROR_MESSAGES.NOTE_EMPTY);
|
|
39
|
+
}
|
|
40
|
+
if (wuId && !isValidWuId(wuId)) {
|
|
41
|
+
throw createError(ErrorCodes.INVALID_WU_ID, ERROR_MESSAGES.WU_ID_INVALID);
|
|
42
|
+
}
|
|
43
|
+
const memoryDir = await ensureMemoryDir(baseDir);
|
|
44
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
45
|
+
const content = generateCheckpointContent(note);
|
|
46
|
+
const idContent = `${content}-${timestamp}`;
|
|
47
|
+
const id = generateMemId(idContent);
|
|
48
|
+
const metadata = {};
|
|
49
|
+
if (progress) {
|
|
50
|
+
metadata.progress = progress;
|
|
51
|
+
}
|
|
52
|
+
if (nextSteps) {
|
|
53
|
+
metadata.nextSteps = nextSteps;
|
|
54
|
+
}
|
|
55
|
+
if (trigger) {
|
|
56
|
+
metadata.trigger = trigger;
|
|
57
|
+
}
|
|
58
|
+
if (gitDiffStat) {
|
|
59
|
+
metadata.gitDiffStat = gitDiffStat;
|
|
60
|
+
}
|
|
61
|
+
const checkpointNode = {
|
|
62
|
+
id,
|
|
63
|
+
type: NODE_TYPE_CHECKPOINT,
|
|
64
|
+
lifecycle: LIFECYCLE_SESSION,
|
|
65
|
+
content,
|
|
66
|
+
created_at: timestamp
|
|
67
|
+
};
|
|
68
|
+
if (wuId) {
|
|
69
|
+
checkpointNode.wu_id = wuId;
|
|
70
|
+
}
|
|
71
|
+
if (sessionId) {
|
|
72
|
+
checkpointNode.session_id = sessionId;
|
|
73
|
+
}
|
|
74
|
+
if (Object.keys(metadata).length > 0) {
|
|
75
|
+
checkpointNode.metadata = metadata;
|
|
76
|
+
}
|
|
77
|
+
await appendNode(memoryDir, checkpointNode);
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
checkpoint: checkpointNode
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
createCheckpoint
|
|
86
|
+
};
|