@lumenflow/cli 3.18.0 → 3.18.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.
Files changed (117) hide show
  1. package/README.md +44 -43
  2. package/dist/chunk-2D2VOCA4.js +37 -0
  3. package/dist/chunk-2D5KFYGX.js +284 -0
  4. package/dist/chunk-2GXVIN57.js +14072 -0
  5. package/dist/chunk-2MQ7HZWZ.js +26 -0
  6. package/dist/chunk-2UFQ3A3C.js +643 -0
  7. package/dist/chunk-3RG5ZIWI.js +10 -0
  8. package/dist/chunk-4N74J3UT.js +15 -0
  9. package/dist/chunk-5GTOXFYR.js +392 -0
  10. package/dist/chunk-5VY6MQMC.js +240 -0
  11. package/dist/chunk-67XVPMRY.js +1297 -0
  12. package/dist/chunk-6HO4GWJE.js +164 -0
  13. package/dist/chunk-6W5XHWYV.js +1890 -0
  14. package/dist/chunk-6X4EMYJQ.js +64 -0
  15. package/dist/chunk-6XYXI2NQ.js +772 -0
  16. package/dist/chunk-7ANSOV6Q.js +285 -0
  17. package/dist/chunk-A624LFLB.js +1380 -0
  18. package/dist/chunk-ADN5NHG4.js +126 -0
  19. package/dist/chunk-B7YJYJKG.js +33 -0
  20. package/dist/chunk-CCLHCPKG.js +210 -0
  21. package/dist/chunk-CK36VROC.js +1584 -0
  22. package/dist/chunk-D3UOFRSB.js +81 -0
  23. package/dist/chunk-DFR4DJBM.js +230 -0
  24. package/dist/chunk-DSYBDHYH.js +79 -0
  25. package/dist/chunk-DWMLTXKQ.js +1176 -0
  26. package/dist/chunk-E3REJTAJ.js +28 -0
  27. package/dist/chunk-EA3IVO64.js +633 -0
  28. package/dist/chunk-EK2AKZKD.js +55 -0
  29. package/dist/chunk-ELD7JTTT.js +343 -0
  30. package/dist/chunk-EX6TT2XI.js +195 -0
  31. package/dist/chunk-EXINSFZE.js +82 -0
  32. package/dist/chunk-EZ6ZBYBM.js +510 -0
  33. package/dist/chunk-FBKAPTJ2.js +16 -0
  34. package/dist/chunk-FVLV5RYH.js +1118 -0
  35. package/dist/chunk-GDNSBQVK.js +2485 -0
  36. package/dist/chunk-GPQHMBNN.js +278 -0
  37. package/dist/chunk-GTFJB67L.js +68 -0
  38. package/dist/chunk-HANJXVKW.js +1127 -0
  39. package/dist/chunk-HEVS5YLD.js +269 -0
  40. package/dist/chunk-HMEVZKPQ.js +9 -0
  41. package/dist/chunk-HRGSYNLM.js +3511 -0
  42. package/dist/chunk-ISZR5N4K.js +60 -0
  43. package/dist/chunk-J6SUPR2C.js +226 -0
  44. package/dist/chunk-JERYVEIZ.js +244 -0
  45. package/dist/chunk-JHHWGL2N.js +87 -0
  46. package/dist/chunk-JONWQUB5.js +775 -0
  47. package/dist/chunk-K2DIWWDM.js +1766 -0
  48. package/dist/chunk-KY4PGL5V.js +969 -0
  49. package/dist/chunk-L737LQ4C.js +1285 -0
  50. package/dist/chunk-LFTWYIB2.js +497 -0
  51. package/dist/chunk-LV47RFNJ.js +41 -0
  52. package/dist/chunk-MKSAITI7.js +15 -0
  53. package/dist/chunk-MZ7RKIX4.js +212 -0
  54. package/dist/chunk-NAP6CFSO.js +84 -0
  55. package/dist/chunk-ND6MY37M.js +16 -0
  56. package/dist/chunk-NMG736UR.js +683 -0
  57. package/dist/chunk-NRAXROED.js +32 -0
  58. package/dist/chunk-NRIZR3A7.js +690 -0
  59. package/dist/chunk-NX43BG3M.js +233 -0
  60. package/dist/chunk-O645XLSI.js +297 -0
  61. package/dist/chunk-OMJD6A3S.js +235 -0
  62. package/dist/chunk-QB6SJD4T.js +430 -0
  63. package/dist/chunk-QFSTL4J3.js +276 -0
  64. package/dist/chunk-QLGDFMFX.js +212 -0
  65. package/dist/chunk-RIAAGL2E.js +13 -0
  66. package/dist/chunk-RWO5XMZ6.js +86 -0
  67. package/dist/chunk-RXRKBBSM.js +149 -0
  68. package/dist/chunk-RZOZMML6.js +363 -0
  69. package/dist/chunk-U7I7FS7T.js +113 -0
  70. package/dist/chunk-UI42RODY.js +717 -0
  71. package/dist/chunk-UTVMVSCO.js +519 -0
  72. package/dist/chunk-V6OJGLBA.js +1746 -0
  73. package/dist/chunk-W2JHVH7D.js +152 -0
  74. package/dist/chunk-WD3Y7VQN.js +280 -0
  75. package/dist/chunk-WOCTQ5MS.js +303 -0
  76. package/dist/chunk-WZR3ZUNN.js +696 -0
  77. package/dist/chunk-XGI665H7.js +150 -0
  78. package/dist/chunk-XKY65P2T.js +304 -0
  79. package/dist/chunk-Y4CQZY65.js +57 -0
  80. package/dist/chunk-YFEXKLVE.js +194 -0
  81. package/dist/chunk-YHO3HS5X.js +287 -0
  82. package/dist/chunk-YLS7AZSX.js +738 -0
  83. package/dist/chunk-ZE473AO6.js +49 -0
  84. package/dist/chunk-ZF747T3O.js +644 -0
  85. package/dist/chunk-ZHCZHZH3.js +43 -0
  86. package/dist/chunk-ZZNZX2XY.js +87 -0
  87. package/dist/config-set.js +10 -1
  88. package/dist/config-set.js.map +1 -1
  89. package/dist/constants-7QAP3VQ4.js +23 -0
  90. package/dist/dist-IY3UUMWK.js +33 -0
  91. package/dist/gate-co-change.js +5 -2
  92. package/dist/gate-co-change.js.map +1 -1
  93. package/dist/initiative-edit.js +8 -3
  94. package/dist/initiative-edit.js.map +1 -1
  95. package/dist/invariants-runner-W5RGHCSU.js +27 -0
  96. package/dist/lane-lock-6J36HD5O.js +35 -0
  97. package/dist/lumenflow-upgrade.js +49 -0
  98. package/dist/lumenflow-upgrade.js.map +1 -1
  99. package/dist/mem-checkpoint-core-EANG2GVN.js +14 -0
  100. package/dist/mem-signal-core-2LZ2WYHW.js +19 -0
  101. package/dist/memory-store-OLB5FO7K.js +18 -0
  102. package/dist/service-6BYCOCO5.js +13 -0
  103. package/dist/spawn-policy-resolver-NTSZYQ6R.js +17 -0
  104. package/dist/spawn-task-builder-R4E2BHSW.js +22 -0
  105. package/dist/wu-done-pr-WLFFFEPJ.js +25 -0
  106. package/dist/wu-done-validation-3J5E36FE.js +30 -0
  107. package/dist/wu-duplicate-id-detector-5S7JHELK.js +232 -0
  108. package/package.json +8 -8
  109. package/packs/agent-runtime/.turbo/turbo-build.log +1 -1
  110. package/packs/agent-runtime/package.json +1 -1
  111. package/packs/sidekick/.turbo/turbo-build.log +1 -1
  112. package/packs/sidekick/.turbo/turbo-test.log +12 -0
  113. package/packs/sidekick/.turbo/turbo-typecheck.log +4 -0
  114. package/packs/sidekick/package.json +1 -1
  115. package/packs/software-delivery/.turbo/turbo-build.log +1 -1
  116. package/packs/software-delivery/.turbo/turbo-typecheck.log +4 -0
  117. 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
+ };