@lumenflow/cli 3.21.0 → 3.22.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/invariants-runner-W5RGHCSU.js +27 -0
- package/dist/lane-lock-6J36HD5O.js +35 -0
- 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/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/wu-done-pr-WLFFFEPJ.js +25 -0
- package/dist/wu-done-validation-3J5E36FE.js +30 -0
- package/dist/wu-duplicate-id-detector-5S7JHELK.js +232 -0
- package/dist/wu-spawn-strategy-resolver.js +18 -5
- package/dist/wu-spawn-strategy-resolver.js.map +1 -1
- package/package.json +8 -8
- package/packs/agent-runtime/.turbo/turbo-build.log +1 -1
- package/packs/agent-runtime/.turbo/turbo-typecheck.log +4 -0
- package/packs/agent-runtime/package.json +1 -1
- package/packs/sidekick/.turbo/turbo-build.log +1 -1
- package/packs/sidekick/.turbo/turbo-test.log +12 -0
- 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
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateEnforceWorktreeScript,
|
|
3
|
+
generateEnforcementHooks,
|
|
4
|
+
generateRequireWuScript,
|
|
5
|
+
generateWarnIncompleteScript
|
|
6
|
+
} from "./chunk-EA3IVO64.js";
|
|
7
|
+
import {
|
|
8
|
+
loadTemplate
|
|
9
|
+
} from "./chunk-HEVS5YLD.js";
|
|
10
|
+
import {
|
|
11
|
+
generateScriptsFromManifest
|
|
12
|
+
} from "./chunk-KY4PGL5V.js";
|
|
13
|
+
import {
|
|
14
|
+
WU_OPTIONS,
|
|
15
|
+
createWUParser,
|
|
16
|
+
runCLI
|
|
17
|
+
} from "./chunk-2GXVIN57.js";
|
|
18
|
+
import {
|
|
19
|
+
CLAUDE_HOOKS,
|
|
20
|
+
DIRECTORIES
|
|
21
|
+
} from "./chunk-DWMLTXKQ.js";
|
|
22
|
+
import {
|
|
23
|
+
LUMENFLOW_CLIENT_IDS,
|
|
24
|
+
WORKSPACE_CONFIG_FILE_NAME,
|
|
25
|
+
WORKSPACE_V2_KEYS
|
|
26
|
+
} from "./chunk-V6OJGLBA.js";
|
|
27
|
+
|
|
28
|
+
// src/commands/integrate.ts
|
|
29
|
+
import * as fs from "fs";
|
|
30
|
+
import * as path from "path";
|
|
31
|
+
import * as yaml from "yaml";
|
|
32
|
+
var SUPPORTED_CLIENTS = [
|
|
33
|
+
LUMENFLOW_CLIENT_IDS.CLAUDE_CODE,
|
|
34
|
+
LUMENFLOW_CLIENT_IDS.CURSOR,
|
|
35
|
+
LUMENFLOW_CLIENT_IDS.CODEX_CLI
|
|
36
|
+
];
|
|
37
|
+
var INTEGRATE_OPTIONS = {
|
|
38
|
+
client: {
|
|
39
|
+
name: "client",
|
|
40
|
+
flags: "--client <type>",
|
|
41
|
+
description: `Client type to integrate (${SUPPORTED_CLIENTS.join(", ")})`
|
|
42
|
+
},
|
|
43
|
+
sync: {
|
|
44
|
+
name: "sync",
|
|
45
|
+
flags: "--sync",
|
|
46
|
+
description: "Re-scaffold vendor-agnostic pre-commit/CI delegators and sync package.json command aliases"
|
|
47
|
+
},
|
|
48
|
+
force: WU_OPTIONS.force
|
|
49
|
+
};
|
|
50
|
+
function parseIntegrateOptions() {
|
|
51
|
+
const opts = createWUParser({
|
|
52
|
+
name: "lumenflow-integrate",
|
|
53
|
+
description: "Integrate LumenFlow enforcement with AI client tools",
|
|
54
|
+
options: Object.values(INTEGRATE_OPTIONS)
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
client: opts.client ?? LUMENFLOW_CLIENT_IDS.CLAUDE_CODE,
|
|
58
|
+
sync: opts.sync ?? false,
|
|
59
|
+
force: opts.force ?? false
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
var PRE_COMMIT_TEMPLATE_PATH = "core/.husky/pre-commit.template";
|
|
63
|
+
var CI_TEMPLATE_PATH = "core/.github/workflows/lumenflow-ci.yml.template";
|
|
64
|
+
var PROJECT_PRE_COMMIT_PATH = ".husky/pre-commit";
|
|
65
|
+
var PROJECT_CI_PATH = ".github/workflows/lumenflow-ci.yml";
|
|
66
|
+
function syncCoreEnforcementDelegators(projectDir) {
|
|
67
|
+
const created = [];
|
|
68
|
+
const preCommitContent = loadTemplate(PRE_COMMIT_TEMPLATE_PATH);
|
|
69
|
+
const preCommitPath = path.join(projectDir, PROJECT_PRE_COMMIT_PATH);
|
|
70
|
+
fs.mkdirSync(path.dirname(preCommitPath), { recursive: true });
|
|
71
|
+
fs.writeFileSync(preCommitPath, preCommitContent, { mode: 493 });
|
|
72
|
+
created.push(PROJECT_PRE_COMMIT_PATH);
|
|
73
|
+
const ciContent = loadTemplate(CI_TEMPLATE_PATH);
|
|
74
|
+
const ciPath = path.join(projectDir, PROJECT_CI_PATH);
|
|
75
|
+
fs.mkdirSync(path.dirname(ciPath), { recursive: true });
|
|
76
|
+
fs.writeFileSync(ciPath, ciContent, "utf-8");
|
|
77
|
+
created.push(PROJECT_CI_PATH);
|
|
78
|
+
return created;
|
|
79
|
+
}
|
|
80
|
+
function syncPackageJsonScripts(projectDir) {
|
|
81
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
82
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
83
|
+
return { added: [], modified: false };
|
|
84
|
+
}
|
|
85
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
86
|
+
if (!packageJson.scripts || typeof packageJson.scripts !== "object") {
|
|
87
|
+
packageJson.scripts = {};
|
|
88
|
+
}
|
|
89
|
+
const scripts = packageJson.scripts;
|
|
90
|
+
const manifestScripts = generateScriptsFromManifest();
|
|
91
|
+
const added = [];
|
|
92
|
+
for (const [name, command] of Object.entries(manifestScripts)) {
|
|
93
|
+
if (!(name in scripts)) {
|
|
94
|
+
scripts[name] = command;
|
|
95
|
+
added.push(name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const modified = added.length > 0;
|
|
99
|
+
if (modified) {
|
|
100
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
|
|
101
|
+
}
|
|
102
|
+
return { added, modified };
|
|
103
|
+
}
|
|
104
|
+
function readClaudeSettings(projectDir) {
|
|
105
|
+
const settingsPath = path.join(projectDir, DIRECTORIES.CLAUDE, "settings.json");
|
|
106
|
+
if (!fs.existsSync(settingsPath)) {
|
|
107
|
+
return {
|
|
108
|
+
$schema: "https://json.schemastore.org/claude-code-settings.json",
|
|
109
|
+
permissions: {
|
|
110
|
+
allow: ["Bash", "Read", "Write", "Edit", "WebFetch", "WebSearch", "Skill"]
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const content = fs.readFileSync(settingsPath, "utf-8");
|
|
116
|
+
return JSON.parse(content);
|
|
117
|
+
} catch {
|
|
118
|
+
return {
|
|
119
|
+
$schema: "https://json.schemastore.org/claude-code-settings.json"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function mergeHooksIntoSettings(existing, generated) {
|
|
124
|
+
const result = { ...existing };
|
|
125
|
+
if (!result.hooks) {
|
|
126
|
+
result.hooks = {};
|
|
127
|
+
}
|
|
128
|
+
if (generated.preToolUse) {
|
|
129
|
+
if (!result.hooks.PreToolUse) {
|
|
130
|
+
result.hooks.PreToolUse = [];
|
|
131
|
+
}
|
|
132
|
+
for (const newHook of generated.preToolUse) {
|
|
133
|
+
const existingIndex = result.hooks.PreToolUse.findIndex((h) => h.matcher === newHook.matcher);
|
|
134
|
+
if (existingIndex >= 0) {
|
|
135
|
+
const existing2 = result.hooks.PreToolUse[existingIndex];
|
|
136
|
+
for (const hook of newHook.hooks) {
|
|
137
|
+
const isDuplicate = existing2.hooks.some((h) => h.command === hook.command);
|
|
138
|
+
if (!isDuplicate) {
|
|
139
|
+
existing2.hooks.push(hook);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
result.hooks.PreToolUse.push(newHook);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (generated.stop) {
|
|
148
|
+
if (!result.hooks.Stop) {
|
|
149
|
+
result.hooks.Stop = [];
|
|
150
|
+
}
|
|
151
|
+
for (const newHook of generated.stop) {
|
|
152
|
+
const existingIndex = result.hooks.Stop.findIndex((h) => h.matcher === newHook.matcher);
|
|
153
|
+
if (existingIndex >= 0) {
|
|
154
|
+
const existing2 = result.hooks.Stop[existingIndex];
|
|
155
|
+
for (const hook of newHook.hooks) {
|
|
156
|
+
const isDuplicate = existing2.hooks.some((h) => h.command === hook.command);
|
|
157
|
+
if (!isDuplicate) {
|
|
158
|
+
existing2.hooks.push(hook);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
result.hooks.Stop.push(newHook);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
async function integrateClaudeCode(projectDir, config) {
|
|
169
|
+
const enforcement = config.enforcement;
|
|
170
|
+
const created = [];
|
|
171
|
+
if (!enforcement?.hooks) {
|
|
172
|
+
console.log("[integrate] Enforcement hooks not enabled, skipping");
|
|
173
|
+
return created;
|
|
174
|
+
}
|
|
175
|
+
const hooksDir = path.join(projectDir, DIRECTORIES.CLAUDE_HOOKS);
|
|
176
|
+
if (!fs.existsSync(hooksDir)) {
|
|
177
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
178
|
+
console.log("[integrate] Created hooks directory");
|
|
179
|
+
}
|
|
180
|
+
const generatedHooks = generateEnforcementHooks({
|
|
181
|
+
block_outside_worktree: enforcement.block_outside_worktree ?? false,
|
|
182
|
+
require_wu_for_edits: enforcement.require_wu_for_edits ?? false,
|
|
183
|
+
warn_on_stop_without_wu_done: enforcement.warn_on_stop_without_wu_done ?? false
|
|
184
|
+
});
|
|
185
|
+
if (enforcement.block_outside_worktree) {
|
|
186
|
+
const scriptPath = path.join(hooksDir, CLAUDE_HOOKS.SCRIPTS.ENFORCE_WORKTREE);
|
|
187
|
+
fs.writeFileSync(scriptPath, generateEnforceWorktreeScript({ projectRoot: projectDir }), {
|
|
188
|
+
mode: 493
|
|
189
|
+
});
|
|
190
|
+
console.log(`[integrate] Generated ${CLAUDE_HOOKS.SCRIPTS.ENFORCE_WORKTREE}`);
|
|
191
|
+
created.push(path.join(DIRECTORIES.CLAUDE_HOOKS, CLAUDE_HOOKS.SCRIPTS.ENFORCE_WORKTREE));
|
|
192
|
+
}
|
|
193
|
+
if (enforcement.require_wu_for_edits) {
|
|
194
|
+
const scriptPath = path.join(hooksDir, CLAUDE_HOOKS.SCRIPTS.REQUIRE_WU);
|
|
195
|
+
fs.writeFileSync(scriptPath, generateRequireWuScript(), { mode: 493 });
|
|
196
|
+
console.log(`[integrate] Generated ${CLAUDE_HOOKS.SCRIPTS.REQUIRE_WU}`);
|
|
197
|
+
created.push(path.join(DIRECTORIES.CLAUDE_HOOKS, CLAUDE_HOOKS.SCRIPTS.REQUIRE_WU));
|
|
198
|
+
}
|
|
199
|
+
if (enforcement.warn_on_stop_without_wu_done) {
|
|
200
|
+
const scriptPath = path.join(hooksDir, CLAUDE_HOOKS.SCRIPTS.WARN_INCOMPLETE);
|
|
201
|
+
fs.writeFileSync(scriptPath, generateWarnIncompleteScript(), { mode: 493 });
|
|
202
|
+
console.log(`[integrate] Generated ${CLAUDE_HOOKS.SCRIPTS.WARN_INCOMPLETE}`);
|
|
203
|
+
created.push(path.join(DIRECTORIES.CLAUDE_HOOKS, CLAUDE_HOOKS.SCRIPTS.WARN_INCOMPLETE));
|
|
204
|
+
}
|
|
205
|
+
const existingSettings = readClaudeSettings(projectDir);
|
|
206
|
+
const updatedSettings = mergeHooksIntoSettings(existingSettings, generatedHooks);
|
|
207
|
+
const settingsPath = path.join(projectDir, DIRECTORIES.CLAUDE, "settings.json");
|
|
208
|
+
fs.writeFileSync(settingsPath, JSON.stringify(updatedSettings, null, 2) + "\n", "utf-8");
|
|
209
|
+
console.log("[integrate] Updated settings.json");
|
|
210
|
+
return created;
|
|
211
|
+
}
|
|
212
|
+
function readWorkspaceSoftwareDeliveryConfig(projectDir) {
|
|
213
|
+
const configPath = path.join(projectDir, WORKSPACE_CONFIG_FILE_NAME);
|
|
214
|
+
if (!fs.existsSync(configPath)) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
219
|
+
const workspaceDoc = yaml.parse(content);
|
|
220
|
+
if (!workspaceDoc || typeof workspaceDoc !== "object") {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const workspace = workspaceDoc;
|
|
224
|
+
const softwareDelivery = workspace[WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY];
|
|
225
|
+
if (!softwareDelivery || typeof softwareDelivery !== "object") {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return softwareDelivery;
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function readEnforcementConfig(projectDir) {
|
|
234
|
+
const softwareDelivery = readWorkspaceSoftwareDeliveryConfig(projectDir);
|
|
235
|
+
return softwareDelivery?.agents?.clients?.[LUMENFLOW_CLIENT_IDS.CLAUDE_CODE]?.enforcement ?? null;
|
|
236
|
+
}
|
|
237
|
+
var CURSOR_RECOVERY_RULES = `# LumenFlow Context Recovery
|
|
238
|
+
|
|
239
|
+
## On Session Start
|
|
240
|
+
|
|
241
|
+
When starting a new session or resuming work, always check for pending recovery context:
|
|
242
|
+
|
|
243
|
+
\`\`\`bash
|
|
244
|
+
# Check for recovery files
|
|
245
|
+
ls .lumenflow/state/recovery-pending-*.md 2>/dev/null
|
|
246
|
+
\`\`\`
|
|
247
|
+
|
|
248
|
+
If recovery files exist:
|
|
249
|
+
|
|
250
|
+
1. Read the recovery file contents \u2014 they contain your last checkpoint, acceptance criteria, and code paths
|
|
251
|
+
2. Run \`pnpm mem:recover --wu WU-XXX\` (replace WU-XXX with the WU ID from the filename) for the latest context
|
|
252
|
+
3. Continue working based on the recovery context
|
|
253
|
+
|
|
254
|
+
## Context Loss Prevention
|
|
255
|
+
|
|
256
|
+
Before any long operation that might lose context:
|
|
257
|
+
|
|
258
|
+
\`\`\`bash
|
|
259
|
+
pnpm mem:checkpoint "description of current progress" --wu WU-XXX
|
|
260
|
+
\`\`\`
|
|
261
|
+
|
|
262
|
+
## Recovery Command Reference
|
|
263
|
+
|
|
264
|
+
| Command | Purpose |
|
|
265
|
+
| ------------------------------------------- | ---------------------------------- |
|
|
266
|
+
| \`pnpm mem:recover --wu WU-XXX\` | Generate recovery context for a WU |
|
|
267
|
+
| \`pnpm wu:brief --id WU-XXX --client cursor\` | Generate full handoff prompt |
|
|
268
|
+
| \`pnpm wu:status --id WU-XXX\` | Check WU status and location |
|
|
269
|
+
| \`pnpm mem:checkpoint\` | Save progress checkpoint |
|
|
270
|
+
`;
|
|
271
|
+
var AGENTS_RECOVERY_SECTION = `
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Context Recovery (WU-2157)
|
|
275
|
+
|
|
276
|
+
If you are resuming work or have lost context, check for recovery files:
|
|
277
|
+
|
|
278
|
+
\`\`\`bash
|
|
279
|
+
# Check for pending recovery
|
|
280
|
+
ls .lumenflow/state/recovery-pending-*.md 2>/dev/null
|
|
281
|
+
|
|
282
|
+
# Generate fresh recovery context
|
|
283
|
+
pnpm mem:recover --wu WU-XXX
|
|
284
|
+
|
|
285
|
+
# Or generate a full handoff prompt
|
|
286
|
+
pnpm wu:brief --id WU-XXX --client codex-cli
|
|
287
|
+
\`\`\`
|
|
288
|
+
|
|
289
|
+
Recovery files contain your last checkpoint, acceptance criteria, code paths, and changed files.
|
|
290
|
+
Always save checkpoints before long operations: \`pnpm mem:checkpoint "progress note" --wu WU-XXX\`
|
|
291
|
+
`;
|
|
292
|
+
function integrateCursor(projectDir) {
|
|
293
|
+
const created = [];
|
|
294
|
+
const rulesDir = path.join(projectDir, ".cursor", "rules");
|
|
295
|
+
if (!fs.existsSync(rulesDir)) {
|
|
296
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
297
|
+
console.log("[integrate] Created .cursor/rules/ directory");
|
|
298
|
+
}
|
|
299
|
+
const recoveryRulesPath = path.join(rulesDir, "lumenflow-recovery.md");
|
|
300
|
+
fs.writeFileSync(recoveryRulesPath, CURSOR_RECOVERY_RULES, "utf-8");
|
|
301
|
+
console.log("[integrate] Generated .cursor/rules/lumenflow-recovery.md");
|
|
302
|
+
created.push(".cursor/rules/lumenflow-recovery.md");
|
|
303
|
+
return created;
|
|
304
|
+
}
|
|
305
|
+
function integrateCodexCli(projectDir) {
|
|
306
|
+
const created = [];
|
|
307
|
+
const agentsPath = path.join(projectDir, "AGENTS.md");
|
|
308
|
+
if (!fs.existsSync(agentsPath)) {
|
|
309
|
+
console.log("[integrate] AGENTS.md not found, skipping Codex integration");
|
|
310
|
+
return created;
|
|
311
|
+
}
|
|
312
|
+
const content = fs.readFileSync(agentsPath, "utf-8");
|
|
313
|
+
if (content.includes("## Context Recovery (WU-2157)")) {
|
|
314
|
+
console.log("[integrate] AGENTS.md already contains recovery section, skipping");
|
|
315
|
+
return created;
|
|
316
|
+
}
|
|
317
|
+
fs.writeFileSync(agentsPath, content + AGENTS_RECOVERY_SECTION, "utf-8");
|
|
318
|
+
console.log("[integrate] Appended recovery section to AGENTS.md");
|
|
319
|
+
created.push("AGENTS.md");
|
|
320
|
+
return created;
|
|
321
|
+
}
|
|
322
|
+
async function main() {
|
|
323
|
+
const opts = parseIntegrateOptions();
|
|
324
|
+
const projectDir = process.cwd();
|
|
325
|
+
if (opts.sync) {
|
|
326
|
+
const synced = syncCoreEnforcementDelegators(projectDir);
|
|
327
|
+
for (const file of synced) {
|
|
328
|
+
console.log(`[integrate] Synced ${file}`);
|
|
329
|
+
}
|
|
330
|
+
const scriptSync = syncPackageJsonScripts(projectDir);
|
|
331
|
+
if (scriptSync.modified) {
|
|
332
|
+
console.log(
|
|
333
|
+
`[integrate] Added ${scriptSync.added.length} command alias(es): ${scriptSync.added.join(", ")}`
|
|
334
|
+
);
|
|
335
|
+
} else {
|
|
336
|
+
console.log("[integrate] package.json command aliases already up to date");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
switch (opts.client) {
|
|
340
|
+
case LUMENFLOW_CLIENT_IDS.CLAUDE_CODE: {
|
|
341
|
+
const enforcement = readEnforcementConfig(projectDir);
|
|
342
|
+
if (!enforcement) {
|
|
343
|
+
console.log(
|
|
344
|
+
`[integrate] No enforcement config found in ${WORKSPACE_CONFIG_FILE_NAME} ${WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY}.agents.clients.${LUMENFLOW_CLIENT_IDS.CLAUDE_CODE}`
|
|
345
|
+
);
|
|
346
|
+
console.log("[integrate] Add this to your workspace config to enable enforcement hooks:");
|
|
347
|
+
console.log(`
|
|
348
|
+
${WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY}:
|
|
349
|
+
agents:
|
|
350
|
+
clients:
|
|
351
|
+
${LUMENFLOW_CLIENT_IDS.CLAUDE_CODE}:
|
|
352
|
+
enforcement:
|
|
353
|
+
hooks: true
|
|
354
|
+
block_outside_worktree: true
|
|
355
|
+
require_wu_for_edits: true
|
|
356
|
+
warn_on_stop_without_wu_done: true
|
|
357
|
+
`);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
await integrateClaudeCode(projectDir, { enforcement });
|
|
361
|
+
console.log("[integrate] Claude Code integration complete");
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
case LUMENFLOW_CLIENT_IDS.CURSOR: {
|
|
365
|
+
integrateCursor(projectDir);
|
|
366
|
+
console.log("[integrate] Cursor integration complete");
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case LUMENFLOW_CLIENT_IDS.CODEX_CLI: {
|
|
370
|
+
integrateCodexCli(projectDir);
|
|
371
|
+
console.log("[integrate] Codex CLI integration complete");
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
default:
|
|
375
|
+
console.error(`[integrate] Unsupported client: ${opts.client}`);
|
|
376
|
+
console.error(`[integrate] Supported clients: ${SUPPORTED_CLIENTS.join(", ")}`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (import.meta.main) {
|
|
381
|
+
void runCLI(main);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export {
|
|
385
|
+
parseIntegrateOptions,
|
|
386
|
+
syncCoreEnforcementDelegators,
|
|
387
|
+
syncPackageJsonScripts,
|
|
388
|
+
integrateClaudeCode,
|
|
389
|
+
integrateCursor,
|
|
390
|
+
integrateCodexCli,
|
|
391
|
+
main
|
|
392
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SIGNAL_FILE_NAME,
|
|
3
|
+
SIGNAL_RECEIPTS_FILE_NAME
|
|
4
|
+
} from "./chunk-OMJD6A3S.js";
|
|
5
|
+
import {
|
|
6
|
+
LUMENFLOW_MEMORY_PATHS
|
|
7
|
+
} from "./chunk-4N74J3UT.js";
|
|
8
|
+
import {
|
|
9
|
+
MS_PER_DAY
|
|
10
|
+
} from "./chunk-V6OJGLBA.js";
|
|
11
|
+
import {
|
|
12
|
+
ErrorCodes,
|
|
13
|
+
createError
|
|
14
|
+
} from "./chunk-RXRKBBSM.js";
|
|
15
|
+
|
|
16
|
+
// ../memory/dist/signal-cleanup-core.js
|
|
17
|
+
import fs from "fs/promises";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import { createRequire } from "module";
|
|
20
|
+
var require2 = createRequire(import.meta.url);
|
|
21
|
+
var ms = require2("ms");
|
|
22
|
+
var DEFAULT_SIGNAL_CLEANUP_CONFIG = {
|
|
23
|
+
ttl: 7 * MS_PER_DAY,
|
|
24
|
+
unreadTtl: 30 * MS_PER_DAY,
|
|
25
|
+
maxEntries: 500
|
|
26
|
+
};
|
|
27
|
+
function parseSignalTtl(ttlString) {
|
|
28
|
+
if (!ttlString || typeof ttlString !== "string") {
|
|
29
|
+
throw createError(ErrorCodes.INVALID_DURATION, "Invalid TTL format: TTL string is required");
|
|
30
|
+
}
|
|
31
|
+
const trimmed = ttlString.trim();
|
|
32
|
+
if (!trimmed) {
|
|
33
|
+
throw createError(ErrorCodes.INVALID_DURATION, "Invalid TTL format: TTL string is required");
|
|
34
|
+
}
|
|
35
|
+
const result = ms(trimmed);
|
|
36
|
+
if (result == null || result <= 0) {
|
|
37
|
+
throw createError(ErrorCodes.INVALID_DURATION, `Invalid TTL format: "${ttlString}" is not a valid duration`);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function isSignalExpired(signal, ttlMs, now) {
|
|
42
|
+
if (!signal.created_at) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const createdAt = new Date(signal.created_at).getTime();
|
|
46
|
+
if (Number.isNaN(createdAt)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const age = now - createdAt;
|
|
50
|
+
return age > ttlMs;
|
|
51
|
+
}
|
|
52
|
+
function shouldRemoveSignal(signal, config, context) {
|
|
53
|
+
const { now, activeWuIds } = context;
|
|
54
|
+
if (signal.wu_id && activeWuIds.has(signal.wu_id)) {
|
|
55
|
+
return { remove: false, reason: "active-wu-protected" };
|
|
56
|
+
}
|
|
57
|
+
if (signal.read) {
|
|
58
|
+
if (isSignalExpired(signal, config.ttl, now)) {
|
|
59
|
+
return { remove: true, reason: "ttl-expired" };
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
if (isSignalExpired(signal, config.unreadTtl, now)) {
|
|
63
|
+
return { remove: true, reason: "unread-ttl-expired" };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { remove: false, reason: "within-ttl" };
|
|
67
|
+
}
|
|
68
|
+
function estimateSignalBytes(signal) {
|
|
69
|
+
return JSON.stringify(signal).length + 1;
|
|
70
|
+
}
|
|
71
|
+
function getCompactionRatio(removedCount, totalCount) {
|
|
72
|
+
if (totalCount === 0) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
return removedCount / totalCount;
|
|
76
|
+
}
|
|
77
|
+
function getSignalsPath(baseDir) {
|
|
78
|
+
return path.join(baseDir, LUMENFLOW_MEMORY_PATHS.MEMORY_DIR, SIGNAL_FILE_NAME);
|
|
79
|
+
}
|
|
80
|
+
function getReceiptsPath(baseDir) {
|
|
81
|
+
return path.join(baseDir, LUMENFLOW_MEMORY_PATHS.MEMORY_DIR, SIGNAL_RECEIPTS_FILE_NAME);
|
|
82
|
+
}
|
|
83
|
+
async function loadAllReceipts(baseDir) {
|
|
84
|
+
const receiptsPath = getReceiptsPath(baseDir);
|
|
85
|
+
try {
|
|
86
|
+
const content = await fs.readFile(receiptsPath, { encoding: "utf-8" });
|
|
87
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
88
|
+
return lines.map((line) => JSON.parse(line));
|
|
89
|
+
} catch (err) {
|
|
90
|
+
const error = err;
|
|
91
|
+
if (error.code === "ENOENT") {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function writeReceipts(baseDir, receipts) {
|
|
98
|
+
const receiptsPath = getReceiptsPath(baseDir);
|
|
99
|
+
const content = receipts.map((r) => JSON.stringify(r)).join("\n") + (receipts.length > 0 ? "\n" : "");
|
|
100
|
+
await fs.writeFile(receiptsPath, content, { encoding: "utf-8" });
|
|
101
|
+
}
|
|
102
|
+
async function loadAllSignals(baseDir) {
|
|
103
|
+
const signalsPath = getSignalsPath(baseDir);
|
|
104
|
+
try {
|
|
105
|
+
const content = await fs.readFile(signalsPath, { encoding: "utf-8" });
|
|
106
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
107
|
+
return lines.map((line) => JSON.parse(line));
|
|
108
|
+
} catch (err) {
|
|
109
|
+
const error = err;
|
|
110
|
+
if (error.code === "ENOENT") {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function writeSignals(baseDir, signals) {
|
|
117
|
+
const signalsPath = getSignalsPath(baseDir);
|
|
118
|
+
const content = signals.map((s) => JSON.stringify(s)).join("\n") + (signals.length > 0 ? "\n" : "");
|
|
119
|
+
await fs.writeFile(signalsPath, content, { encoding: "utf-8" });
|
|
120
|
+
}
|
|
121
|
+
async function defaultGetActiveWuIds() {
|
|
122
|
+
return /* @__PURE__ */ new Set();
|
|
123
|
+
}
|
|
124
|
+
function buildCleanupConfig(options) {
|
|
125
|
+
const { ttl, ttlMs: providedTtlMs, unreadTtl, unreadTtlMs: providedUnreadTtlMs, maxEntries } = options;
|
|
126
|
+
let ttlMs = providedTtlMs ?? DEFAULT_SIGNAL_CLEANUP_CONFIG.ttl;
|
|
127
|
+
if (ttl && !providedTtlMs) {
|
|
128
|
+
ttlMs = parseSignalTtl(ttl);
|
|
129
|
+
}
|
|
130
|
+
let unreadTtlMs = providedUnreadTtlMs ?? DEFAULT_SIGNAL_CLEANUP_CONFIG.unreadTtl;
|
|
131
|
+
if (unreadTtl && !providedUnreadTtlMs) {
|
|
132
|
+
unreadTtlMs = parseSignalTtl(unreadTtl);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
ttl: ttlMs,
|
|
136
|
+
unreadTtl: unreadTtlMs,
|
|
137
|
+
maxEntries: maxEntries ?? DEFAULT_SIGNAL_CLEANUP_CONFIG.maxEntries
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function processSignalsForTtl(signals, config, context) {
|
|
141
|
+
const state = {
|
|
142
|
+
removedIds: [],
|
|
143
|
+
retainedIds: [],
|
|
144
|
+
retainedSignals: [],
|
|
145
|
+
bytesFreed: 0,
|
|
146
|
+
breakdown: {
|
|
147
|
+
ttlExpired: 0,
|
|
148
|
+
unreadTtlExpired: 0,
|
|
149
|
+
countLimitExceeded: 0,
|
|
150
|
+
activeWuProtected: 0
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
for (const signal of signals) {
|
|
154
|
+
const decision = shouldRemoveSignal(signal, config, context);
|
|
155
|
+
processSignalDecision(signal, decision, state);
|
|
156
|
+
}
|
|
157
|
+
return state;
|
|
158
|
+
}
|
|
159
|
+
function processSignalDecision(signal, decision, state) {
|
|
160
|
+
if (decision.remove) {
|
|
161
|
+
state.removedIds.push(signal.id);
|
|
162
|
+
state.bytesFreed += estimateSignalBytes(signal);
|
|
163
|
+
updateBreakdownForRemoval(decision.reason, state.breakdown);
|
|
164
|
+
} else {
|
|
165
|
+
state.retainedIds.push(signal.id);
|
|
166
|
+
state.retainedSignals.push(signal);
|
|
167
|
+
if (decision.reason === "active-wu-protected") {
|
|
168
|
+
state.breakdown.activeWuProtected++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function updateBreakdownForRemoval(reason, breakdown) {
|
|
173
|
+
if (reason === "ttl-expired") {
|
|
174
|
+
breakdown.ttlExpired++;
|
|
175
|
+
} else if (reason === "unread-ttl-expired") {
|
|
176
|
+
breakdown.unreadTtlExpired++;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function applyCountPruning(state, maxEntries) {
|
|
180
|
+
if (state.retainedSignals.length <= maxEntries) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
state.retainedSignals.sort((a, b) => {
|
|
184
|
+
const aTime = new Date(a.created_at).getTime();
|
|
185
|
+
const bTime = new Date(b.created_at).getTime();
|
|
186
|
+
return aTime - bTime;
|
|
187
|
+
});
|
|
188
|
+
const toRemove = state.retainedSignals.length - maxEntries;
|
|
189
|
+
for (let i = 0; i < toRemove; i++) {
|
|
190
|
+
const signal = state.retainedSignals[i];
|
|
191
|
+
if (!signal)
|
|
192
|
+
continue;
|
|
193
|
+
const idIndex = state.retainedIds.indexOf(signal.id);
|
|
194
|
+
if (idIndex !== -1) {
|
|
195
|
+
state.retainedIds.splice(idIndex, 1);
|
|
196
|
+
}
|
|
197
|
+
state.removedIds.push(signal.id);
|
|
198
|
+
state.bytesFreed += estimateSignalBytes(signal);
|
|
199
|
+
state.breakdown.countLimitExceeded++;
|
|
200
|
+
}
|
|
201
|
+
state.retainedSignals.splice(0, toRemove);
|
|
202
|
+
}
|
|
203
|
+
async function cleanupSignals(baseDir, options = {}) {
|
|
204
|
+
const { dryRun = false, now = Date.now(), getActiveWuIds = defaultGetActiveWuIds } = options;
|
|
205
|
+
const config = buildCleanupConfig(options);
|
|
206
|
+
const signals = await loadAllSignals(baseDir);
|
|
207
|
+
const activeWuIds = await getActiveWuIds();
|
|
208
|
+
const receipts = await loadAllReceipts(baseDir);
|
|
209
|
+
const receiptSignalIds = new Set(receipts.map((r) => r.signal_id));
|
|
210
|
+
for (const signal of signals) {
|
|
211
|
+
if (!signal.read && receiptSignalIds.has(signal.id)) {
|
|
212
|
+
signal.read = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const state = processSignalsForTtl(signals, config, { now, activeWuIds });
|
|
216
|
+
applyCountPruning(state, config.maxEntries);
|
|
217
|
+
const compactionRatio = getCompactionRatio(state.removedIds.length, signals.length);
|
|
218
|
+
const baseResult = {
|
|
219
|
+
success: true,
|
|
220
|
+
removedIds: state.removedIds,
|
|
221
|
+
retainedIds: state.retainedIds,
|
|
222
|
+
bytesFreed: state.bytesFreed,
|
|
223
|
+
compactionRatio,
|
|
224
|
+
breakdown: state.breakdown
|
|
225
|
+
};
|
|
226
|
+
if (dryRun) {
|
|
227
|
+
return { ...baseResult, dryRun: true };
|
|
228
|
+
}
|
|
229
|
+
if (state.removedIds.length > 0) {
|
|
230
|
+
await writeSignals(baseDir, state.retainedSignals);
|
|
231
|
+
const retainedIdSet = new Set(state.retainedIds);
|
|
232
|
+
const retainedReceipts = receipts.filter((r) => retainedIdSet.has(r.signal_id));
|
|
233
|
+
await writeReceipts(baseDir, retainedReceipts);
|
|
234
|
+
}
|
|
235
|
+
return baseResult;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export {
|
|
239
|
+
cleanupSignals
|
|
240
|
+
};
|