@lumenflow/cli 4.12.4 → 4.14.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.
Files changed (38) hide show
  1. package/dist/lumenflow-disable.js +193 -0
  2. package/dist/lumenflow-disable.js.map +1 -0
  3. package/dist/lumenflow-enable.js +129 -0
  4. package/dist/lumenflow-enable.js.map +1 -0
  5. package/dist/lumenflow-file-classifier.js +159 -0
  6. package/dist/lumenflow-file-classifier.js.map +1 -0
  7. package/dist/lumenflow-uninstall.js +172 -0
  8. package/dist/lumenflow-uninstall.js.map +1 -0
  9. package/dist/public-manifest.js +21 -0
  10. package/dist/public-manifest.js.map +1 -1
  11. package/dist/wu-block.js +110 -105
  12. package/dist/wu-block.js.map +1 -1
  13. package/dist/wu-command-harness.js +58 -0
  14. package/dist/wu-command-harness.js.map +1 -0
  15. package/dist/wu-create.js +76 -61
  16. package/dist/wu-create.js.map +1 -1
  17. package/dist/wu-delete.js +103 -88
  18. package/dist/wu-delete.js.map +1 -1
  19. package/dist/wu-edit.js +92 -71
  20. package/dist/wu-edit.js.map +1 -1
  21. package/dist/wu-prep.js +49 -19
  22. package/dist/wu-prep.js.map +1 -1
  23. package/dist/wu-release.js +90 -68
  24. package/dist/wu-release.js.map +1 -1
  25. package/dist/wu-rename.js +42 -30
  26. package/dist/wu-rename.js.map +1 -1
  27. package/package.json +11 -8
  28. package/packs/agent-runtime/.turbo/turbo-build.log +1 -1
  29. package/packs/agent-runtime/package.json +1 -1
  30. package/packs/sidekick/.turbo/turbo-build.log +1 -1
  31. package/packs/sidekick/package.json +1 -1
  32. package/packs/software-delivery/.turbo/turbo-build.log +1 -1
  33. package/packs/software-delivery/package.json +1 -1
  34. package/templates/core/AGENTS.md.template +15 -0
  35. package/templates/core/LUMENFLOW.md.template +3 -0
  36. package/templates/core/ai/onboarding/quick-ref-commands.md.template +3 -0
  37. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +11 -0
  38. package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +11 -0
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 Hellmai Ltd
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ /**
5
+ * @file lumenflow-disable.ts
6
+ * Reversible opt-out: flip enforcement flags to false and regenerate hooks as
7
+ * no-ops (WU-2590).
8
+ *
9
+ * Pairs with `lumenflow:enable`. The prior enforcement state is persisted to
10
+ * `.lumenflow/state/enforcement-prior.json` so enable can restore it.
11
+ *
12
+ * Usage:
13
+ * pnpm lumenflow:disable
14
+ * pnpm lumenflow:disable --client claude-code
15
+ * pnpm lumenflow:disable --json
16
+ */
17
+ import * as fs from 'node:fs';
18
+ import * as path from 'node:path';
19
+ // eslint-disable-next-line security/detect-child-process -- legitimate CLI subprocess
20
+ import { spawnSync } from 'node:child_process';
21
+ import * as yaml from 'yaml';
22
+ import { createWUParser, LUMENFLOW_CLIENT_IDS, WORKSPACE_CONFIG_FILE_NAME, WORKSPACE_V2_KEYS, } from '@lumenflow/core';
23
+ import { runCLI } from './cli-entry-point.js';
24
+ import { integrateClaudeCode } from './commands/integrate.js';
25
+ const LOG_PREFIX = '[lumenflow:disable]';
26
+ const ENFORCEMENT_FLAGS = [
27
+ 'hooks',
28
+ 'block_outside_worktree',
29
+ 'require_wu_for_edits',
30
+ 'warn_on_stop_without_wu_done',
31
+ ];
32
+ const PRIOR_STATE_REL_PATH = path.join('.lumenflow', 'state', 'enforcement-prior.json');
33
+ export function parseDisableOptions() {
34
+ const opts = createWUParser({
35
+ name: 'lumenflow-disable',
36
+ description: 'Flip LumenFlow enforcement flags to false and regenerate hooks as no-ops. Reversible via lumenflow:enable.',
37
+ options: [
38
+ {
39
+ name: 'client',
40
+ flags: '--client <type>',
41
+ description: 'Scope to a single client (default: claude-code)',
42
+ },
43
+ {
44
+ name: 'json',
45
+ flags: '--json',
46
+ description: 'Emit machine-readable JSON output',
47
+ type: 'boolean',
48
+ },
49
+ ],
50
+ });
51
+ return {
52
+ client: opts.client ?? LUMENFLOW_CLIENT_IDS.CLAUDE_CODE,
53
+ json: opts.json ?? false,
54
+ };
55
+ }
56
+ function readWorkspaceYaml(projectDir) {
57
+ const configPath = path.join(projectDir, WORKSPACE_CONFIG_FILE_NAME);
58
+ if (!fs.existsSync(configPath))
59
+ return {};
60
+ const content = fs.readFileSync(configPath, 'utf-8');
61
+ const parsed = yaml.parse(content);
62
+ return parsed && typeof parsed === 'object' ? parsed : {};
63
+ }
64
+ /**
65
+ * Return the enforcement block for a client, or null if not configured.
66
+ */
67
+ export function readClientEnforcement(projectDir, client) {
68
+ const ws = readWorkspaceYaml(projectDir);
69
+ const sd = ws[WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY];
70
+ const agents = sd?.agents;
71
+ const clients = agents?.clients;
72
+ const entry = clients?.[client];
73
+ const enforcement = entry?.enforcement;
74
+ return enforcement ?? null;
75
+ }
76
+ /**
77
+ * Returns `true` when all enforcement flags are already false / unset.
78
+ */
79
+ export function isAlreadyDisabled(enforcement) {
80
+ if (!enforcement)
81
+ return true;
82
+ return ENFORCEMENT_FLAGS.every((flag) => enforcement[flag] !== true);
83
+ }
84
+ /**
85
+ * Persist prior enforcement state so lumenflow:enable can restore it.
86
+ * Idempotent: if a prior state file already exists, do not overwrite.
87
+ */
88
+ export function savePriorState(projectDir, client, enforcement) {
89
+ const priorPath = path.join(projectDir, PRIOR_STATE_REL_PATH);
90
+ fs.mkdirSync(path.dirname(priorPath), { recursive: true });
91
+ let existing = {};
92
+ if (fs.existsSync(priorPath)) {
93
+ try {
94
+ existing = JSON.parse(fs.readFileSync(priorPath, 'utf-8'));
95
+ }
96
+ catch {
97
+ existing = {};
98
+ }
99
+ }
100
+ if (!existing[client]) {
101
+ existing[client] = enforcement;
102
+ }
103
+ fs.writeFileSync(priorPath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
104
+ return priorPath;
105
+ }
106
+ /**
107
+ * Write the four enforcement flags to workspace.yaml via `config:set`.
108
+ * Using config:set respects constraint 9 (no raw YAML edits).
109
+ */
110
+ export function setEnforcementFlags(projectDir, client, value, run = defaultRunConfigSet) {
111
+ for (const flag of ENFORCEMENT_FLAGS) {
112
+ const key = `${WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY}.agents.clients.${client}.enforcement.${flag}`;
113
+ run(projectDir, [key, String(value)]);
114
+ }
115
+ }
116
+ function defaultRunConfigSet(projectDir, args) {
117
+ const [key, value] = args;
118
+ if (key == null || value == null) {
119
+ throw new Error('defaultRunConfigSet: expected [key, value]');
120
+ }
121
+ const binPath = path.join(projectDir, 'packages', '@lumenflow', 'cli', 'dist', 'config-set.js');
122
+ const res = spawnSync(process.execPath, [binPath, '--key', key, '--value', value], {
123
+ cwd: projectDir,
124
+ stdio: 'inherit',
125
+ });
126
+ if (res.status !== 0) {
127
+ throw new Error(`config:set failed for key ${key}`);
128
+ }
129
+ }
130
+ /**
131
+ * Core disable routine. Exposed for testing.
132
+ */
133
+ export async function disableForClient(projectDir, client, runConfigSet = defaultRunConfigSet) {
134
+ const enforcement = readClientEnforcement(projectDir, client);
135
+ const alreadyDisabled = isAlreadyDisabled(enforcement);
136
+ if (alreadyDisabled) {
137
+ return { client, alreadyDisabled, priorStateSaved: false };
138
+ }
139
+ // Save prior state (idempotent)
140
+ const priorStatePath = savePriorState(projectDir, client, enforcement);
141
+ // Flip flags to false via config:set
142
+ setEnforcementFlags(projectDir, client, false, runConfigSet);
143
+ // Regenerate hooks as no-ops. With hooks:false, integrateClaudeCode short-
144
+ // circuits and prints "Enforcement hooks not enabled, skipping" — settings
145
+ // are left with existing hook references which become no-ops because the
146
+ // scripts are preserved but flags are false. For a stricter no-op we also
147
+ // clear the settings.json hooks section.
148
+ if (client === LUMENFLOW_CLIENT_IDS.CLAUDE_CODE) {
149
+ await integrateClaudeCode(projectDir, { enforcement: { hooks: false } });
150
+ clearClaudeHooksSection(projectDir);
151
+ }
152
+ return { client, alreadyDisabled: false, priorStateSaved: true, priorStatePath };
153
+ }
154
+ /**
155
+ * Clear the LumenFlow-managed hooks section from .claude/settings.json while
156
+ * preserving all user-authored content. Keeping this surgical means `enable`
157
+ * can re-populate without clobbering.
158
+ */
159
+ export function clearClaudeHooksSection(projectDir) {
160
+ const settingsPath = path.join(projectDir, '.claude', 'settings.json');
161
+ if (!fs.existsSync(settingsPath))
162
+ return;
163
+ const raw = fs.readFileSync(settingsPath, 'utf-8');
164
+ let parsed;
165
+ try {
166
+ parsed = JSON.parse(raw);
167
+ }
168
+ catch {
169
+ return;
170
+ }
171
+ delete parsed.hooks;
172
+ fs.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + '\n', 'utf-8');
173
+ }
174
+ export async function main() {
175
+ const opts = parseDisableOptions();
176
+ const projectDir = process.cwd();
177
+ const result = await disableForClient(projectDir, opts.client);
178
+ if (opts.json) {
179
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
180
+ return;
181
+ }
182
+ if (result.alreadyDisabled) {
183
+ console.log(`${LOG_PREFIX} ${opts.client} enforcement is already disabled — nothing to do.`);
184
+ }
185
+ else {
186
+ console.log(`${LOG_PREFIX} Disabled enforcement for ${opts.client}. Prior state saved to ${result.priorStatePath}.`);
187
+ }
188
+ console.log(`${LOG_PREFIX} Restore with: pnpm lumenflow:enable --client ${opts.client}`);
189
+ }
190
+ if (import.meta.main) {
191
+ void runCLI(main);
192
+ }
193
+ //# sourceMappingURL=lumenflow-disable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lumenflow-disable.js","sourceRoot":"","sources":["../src/lumenflow-disable.ts"],"names":[],"mappings":";AACA,iCAAiC;AACjC,yCAAyC;AACzC;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,sFAAsF;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,0BAA0B,EAC1B,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAmC,MAAM,yBAAyB,CAAC;AAE/F,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAEzC,MAAM,iBAAiB,GAA4C;IACjE,OAAO;IACP,wBAAwB;IACxB,sBAAsB;IACtB,8BAA8B;CAC/B,CAAC;AAEF,MAAM,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,wBAAwB,CAAC,CAAC;AAOxF,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GAAG,cAAc,CAAC;QAC1B,IAAI,EAAE,mBAAmB;QACzB,WAAW,EACT,4GAA4G;QAC9G,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,iBAAiB;gBACxB,WAAW,EAAE,iDAAiD;aAC/D;YACD;gBACE,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,QAAQ;gBACf,WAAW,EAAE,mCAAmC;gBAChD,IAAI,EAAE,SAAS;aAChB;SACF;KACF,CAAC,CAAC;IACH,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,oBAAoB,CAAC,WAAW;QACvD,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;KACzB,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,UAAkB;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,0BAA0B,CAAC,CAAC;IACrE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,EAAE,CAAC;AACzF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkB,EAClB,MAAc;IAEd,MAAM,EAAE,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,iBAAiB,CAAwC,CAAC;IAC1F,MAAM,MAAM,GAAG,EAAE,EAAE,MAA6C,CAAC;IACjE,MAAM,OAAO,GAAG,MAAM,EAAE,OAA8C,CAAC;IACvE,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC,MAAM,CAAwC,CAAC;IACvE,MAAM,WAAW,GAAG,KAAK,EAAE,WAAqD,CAAC;IACjF,OAAO,WAAW,IAAI,IAAI,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAA8C;IAC9E,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAC9B,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;AACvE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAkB,EAClB,MAAc,EACd,WAAuC;IAEvC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC;IAC9D,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,IAAI,QAAQ,GAA+C,EAAE,CAAC;IAC9D,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAGxD,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,QAAQ,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC;IACjC,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/E,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,UAAkB,EAClB,MAAc,EACd,KAAc,EACd,MAA6C,mBAAmB;IAEhE,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,GAAG,iBAAiB,CAAC,iBAAiB,mBAAmB,MAAM,gBAAgB,IAAI,EAAE,CAAC;QAClG,GAAG,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,UAAkB,EAAE,IAAc;IAC7D,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC;IAC1B,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;IAChG,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE;QACjF,GAAG,EAAE,UAAU;QACf,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC;IACH,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AASD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAAkB,EAClB,MAAc,EACd,eAAsD,mBAAmB;IAEzE,MAAM,WAAW,GAAG,qBAAqB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC9D,MAAM,eAAe,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAEvD,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;IAC7D,CAAC;IAED,gCAAgC;IAChC,MAAM,cAAc,GAAG,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,WAAY,CAAC,CAAC;IAExE,qCAAqC;IACrC,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAE7D,2EAA2E;IAC3E,2EAA2E;IAC3E,yEAAyE;IACzE,0EAA0E;IAC1E,yCAAyC;IACzC,IAAI,MAAM,KAAK,oBAAoB,CAAC,WAAW,EAAE,CAAC;QAChD,MAAM,mBAAmB,CAAC,UAAU,EAAE,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACzE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAAkB;IACxD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IACvE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO;IACzC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACnD,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC;IACpB,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;IACnC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAE/D,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IAED,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,GAAG,UAAU,IAAI,IAAI,CAAC,MAAM,mDAAmD,CAAC,CAAC;IAC/F,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CACT,GAAG,UAAU,6BAA6B,IAAI,CAAC,MAAM,0BAA0B,MAAM,CAAC,cAAc,GAAG,CACxG,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,GAAG,UAAU,iDAAiD,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACrB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC"}
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 Hellmai Ltd
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ /**
5
+ * @file lumenflow-enable.ts
6
+ * Restore LumenFlow enforcement after `lumenflow:disable` (WU-2590).
7
+ *
8
+ * Reads `.lumenflow/state/enforcement-prior.json` written by `lumenflow:disable`
9
+ * and restores the prior flag values. If no prior state exists, defaults to all
10
+ * flags true (safe default for a fresh enable).
11
+ *
12
+ * Usage:
13
+ * pnpm lumenflow:enable
14
+ * pnpm lumenflow:enable --client claude-code
15
+ */
16
+ import * as fs from 'node:fs';
17
+ import * as path from 'node:path';
18
+ // eslint-disable-next-line security/detect-child-process -- legitimate CLI subprocess
19
+ import { spawnSync } from 'node:child_process';
20
+ import { createWUParser, LUMENFLOW_CLIENT_IDS } from '@lumenflow/core';
21
+ import { runCLI } from './cli-entry-point.js';
22
+ import { integrateClaudeCode } from './commands/integrate.js';
23
+ const LOG_PREFIX = '[lumenflow:enable]';
24
+ const PRIOR_STATE_REL_PATH = path.join('.lumenflow', 'state', 'enforcement-prior.json');
25
+ const DEFAULT_ENFORCEMENT = {
26
+ hooks: true,
27
+ block_outside_worktree: true,
28
+ require_wu_for_edits: true,
29
+ warn_on_stop_without_wu_done: true,
30
+ };
31
+ export function parseEnableOptions() {
32
+ const opts = createWUParser({
33
+ name: 'lumenflow-enable',
34
+ description: 'Re-enable LumenFlow enforcement previously disabled via lumenflow:disable.',
35
+ options: [
36
+ {
37
+ name: 'client',
38
+ flags: '--client <type>',
39
+ description: 'Scope to a single client (default: claude-code)',
40
+ },
41
+ {
42
+ name: 'json',
43
+ flags: '--json',
44
+ description: 'Emit machine-readable JSON output',
45
+ type: 'boolean',
46
+ },
47
+ ],
48
+ });
49
+ return {
50
+ client: opts.client ?? LUMENFLOW_CLIENT_IDS.CLAUDE_CODE,
51
+ json: opts.json ?? false,
52
+ };
53
+ }
54
+ export function readPriorState(projectDir, client) {
55
+ const priorPath = path.join(projectDir, PRIOR_STATE_REL_PATH);
56
+ if (!fs.existsSync(priorPath))
57
+ return null;
58
+ try {
59
+ const parsed = JSON.parse(fs.readFileSync(priorPath, 'utf-8'));
60
+ return parsed[client] ?? null;
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ export function clearPriorState(projectDir, client) {
67
+ const priorPath = path.join(projectDir, PRIOR_STATE_REL_PATH);
68
+ if (!fs.existsSync(priorPath))
69
+ return;
70
+ try {
71
+ const parsed = JSON.parse(fs.readFileSync(priorPath, 'utf-8'));
72
+ delete parsed[client];
73
+ if (Object.keys(parsed).length === 0) {
74
+ fs.unlinkSync(priorPath);
75
+ }
76
+ else {
77
+ fs.writeFileSync(priorPath, JSON.stringify(parsed, null, 2) + '\n', 'utf-8');
78
+ }
79
+ }
80
+ catch {
81
+ // ignore — not critical
82
+ }
83
+ }
84
+ export async function enableForClient(projectDir, client, runConfigSet) {
85
+ const prior = readPriorState(projectDir, client);
86
+ const enforcement = prior ?? DEFAULT_ENFORCEMENT;
87
+ const restoredFrom = prior ? 'prior-state' : 'defaults';
88
+ // Write each flag via config:set
89
+ for (const flag of Object.keys(DEFAULT_ENFORCEMENT)) {
90
+ const value = enforcement[flag] ?? DEFAULT_ENFORCEMENT[flag] ?? false;
91
+ if (runConfigSet) {
92
+ runConfigSet(projectDir, [
93
+ `software_delivery.agents.clients.${client}.enforcement.${flag}`,
94
+ String(value),
95
+ ]);
96
+ }
97
+ else {
98
+ const binPath = path.join(projectDir, 'packages', '@lumenflow', 'cli', 'dist', 'config-set.js');
99
+ const res = spawnSync(process.execPath, [
100
+ binPath,
101
+ '--key',
102
+ `software_delivery.agents.clients.${client}.enforcement.${flag}`,
103
+ '--value',
104
+ String(value),
105
+ ], { cwd: projectDir, stdio: 'inherit' });
106
+ if (res.status !== 0)
107
+ throw new Error(`config:set failed for ${flag}`);
108
+ }
109
+ }
110
+ if (client === LUMENFLOW_CLIENT_IDS.CLAUDE_CODE) {
111
+ await integrateClaudeCode(projectDir, { enforcement });
112
+ }
113
+ clearPriorState(projectDir, client);
114
+ return { client, restoredFrom, enforcement };
115
+ }
116
+ export async function main() {
117
+ const opts = parseEnableOptions();
118
+ const projectDir = process.cwd();
119
+ const result = await enableForClient(projectDir, opts.client);
120
+ if (opts.json) {
121
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
122
+ return;
123
+ }
124
+ console.log(`${LOG_PREFIX} Enabled enforcement for ${result.client} (restored from ${result.restoredFrom}).`);
125
+ }
126
+ if (import.meta.main) {
127
+ void runCLI(main);
128
+ }
129
+ //# sourceMappingURL=lumenflow-enable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lumenflow-enable.js","sourceRoot":"","sources":["../src/lumenflow-enable.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 Hellmai Ltd
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ /**
5
+ * @file lumenflow-file-classifier.ts
6
+ * Shared file manifest for LumenFlow-generated integration artifacts (WU-2590).
7
+ *
8
+ * This module is the single source of truth for "which files does LumenFlow
9
+ * generate on a user's project?" It powers both `lumenflow:disable` and
10
+ * `lumenflow:uninstall`, and it explicitly reuses the same constants that
11
+ * `commands/integrate.ts` uses (`CLAUDE_HOOKS.SCRIPTS`, `DIRECTORIES`,
12
+ * `LUMENFLOW_CLIENT_IDS`) so there is exactly one source of truth for the
13
+ * integration file set.
14
+ *
15
+ * Adding a new generated file? Add it to one of the `collect*()` functions
16
+ * below. Do NOT maintain a parallel list anywhere else.
17
+ */
18
+ import * as fs from 'node:fs';
19
+ import * as path from 'node:path';
20
+ import { CLAUDE_HOOKS, DIRECTORIES, LUMENFLOW_CLIENT_IDS } from '@lumenflow/core';
21
+ /**
22
+ * Collect Claude Code hook scripts that `integrate.ts` generates.
23
+ */
24
+ function collectClaudeHooks(projectDir) {
25
+ const hookScripts = Object.values(CLAUDE_HOOKS.SCRIPTS);
26
+ const out = [];
27
+ for (const script of hookScripts) {
28
+ const rel = path.join(DIRECTORIES.CLAUDE_HOOKS, script);
29
+ if (fs.existsSync(path.join(projectDir, rel))) {
30
+ out.push({
31
+ path: rel,
32
+ category: 'hook',
33
+ client: LUMENFLOW_CLIENT_IDS.CLAUDE_CODE,
34
+ });
35
+ }
36
+ }
37
+ return out;
38
+ }
39
+ /**
40
+ * The Claude Code settings.json may contain user-authored content alongside
41
+ * the LumenFlow-managed `hooks` section.
42
+ */
43
+ function collectClaudeSettings(projectDir) {
44
+ const rel = path.join(DIRECTORIES.CLAUDE, 'settings.json');
45
+ if (!fs.existsSync(path.join(projectDir, rel)))
46
+ return [];
47
+ return [
48
+ {
49
+ path: rel,
50
+ category: 'settings',
51
+ client: LUMENFLOW_CLIENT_IDS.CLAUDE_CODE,
52
+ surgicalStrip: true,
53
+ },
54
+ ];
55
+ }
56
+ /**
57
+ * Cursor recovery rules generated by `integrateCursor`.
58
+ */
59
+ function collectCursorRules(projectDir) {
60
+ const rel = path.join('.cursor', 'rules', 'lumenflow-recovery.md');
61
+ if (!fs.existsSync(path.join(projectDir, rel)))
62
+ return [];
63
+ return [
64
+ {
65
+ path: rel,
66
+ category: 'vendor-rule',
67
+ client: LUMENFLOW_CLIENT_IDS.CURSOR,
68
+ },
69
+ ];
70
+ }
71
+ /**
72
+ * Codex CLI integration appends a section to AGENTS.md.
73
+ */
74
+ function collectCodexAgents(projectDir) {
75
+ const rel = 'AGENTS.md';
76
+ const abs = path.join(projectDir, rel);
77
+ if (!fs.existsSync(abs))
78
+ return [];
79
+ // Only report surgical strip if the marker section exists
80
+ const content = fs.readFileSync(abs, 'utf-8');
81
+ if (!content.includes('## Context Recovery (WU-2157)'))
82
+ return [];
83
+ return [
84
+ {
85
+ path: rel,
86
+ category: 'docs',
87
+ client: LUMENFLOW_CLIENT_IDS.CODEX_CLI,
88
+ surgicalStrip: true,
89
+ },
90
+ ];
91
+ }
92
+ /**
93
+ * Core enforcement delegators generated by `syncCoreEnforcementDelegators`.
94
+ */
95
+ function collectCoreDelegators(projectDir) {
96
+ const out = [];
97
+ const candidates = [
98
+ { path: path.join('.husky', 'pre-commit'), category: 'hook' },
99
+ {
100
+ path: path.join('.github', 'workflows', 'lumenflow-ci.yml'),
101
+ category: 'ci',
102
+ },
103
+ ];
104
+ for (const c of candidates) {
105
+ if (fs.existsSync(path.join(projectDir, c.path))) {
106
+ out.push({ path: c.path, category: c.category });
107
+ }
108
+ }
109
+ return out;
110
+ }
111
+ /**
112
+ * State, stamps, and history surfaces.
113
+ */
114
+ function collectStateAndHistory(projectDir) {
115
+ const out = [];
116
+ const surfaces = [
117
+ { path: '.lumenflow/state', category: 'state' },
118
+ { path: '.lumenflow/stamps', category: 'stamp', keepWithHistory: true },
119
+ { path: '.lumenflow/constraints.md', category: 'config', keepWithConfig: true },
120
+ { path: 'docs/operations/tasks/wu', category: 'docs', keepWithHistory: true },
121
+ { path: 'workspace.yaml', category: 'config', keepWithConfig: true },
122
+ ];
123
+ for (const s of surfaces) {
124
+ if (fs.existsSync(path.join(projectDir, s.path))) {
125
+ out.push(s);
126
+ }
127
+ }
128
+ return out;
129
+ }
130
+ /**
131
+ * Return the full list of LumenFlow-generated files in a project.
132
+ *
133
+ * This is the canonical manifest. Both `lumenflow:disable` and
134
+ * `lumenflow:uninstall` consume this — do not duplicate the logic elsewhere.
135
+ */
136
+ export function getGeneratedFileManifest(projectDir, opts = {}) {
137
+ const all = [
138
+ ...collectClaudeHooks(projectDir),
139
+ ...collectClaudeSettings(projectDir),
140
+ ...collectCursorRules(projectDir),
141
+ ...collectCodexAgents(projectDir),
142
+ ...collectCoreDelegators(projectDir),
143
+ ...collectStateAndHistory(projectDir),
144
+ ];
145
+ if (!opts.client)
146
+ return all;
147
+ // Scope: include client-specific files plus non-client-scoped state/config.
148
+ return all.filter((f) => !f.client || f.client === opts.client);
149
+ }
150
+ export function filterForUninstall(files, opts = {}) {
151
+ return files.filter((f) => {
152
+ if (opts.keepHistory && f.keepWithHistory)
153
+ return false;
154
+ if (opts.keepConfig && f.keepWithConfig)
155
+ return false;
156
+ return true;
157
+ });
158
+ }
159
+ //# sourceMappingURL=lumenflow-file-classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lumenflow-file-classifier.js","sourceRoot":"","sources":["../src/lumenflow-file-classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 Hellmai Ltd
3
+ // SPDX-License-Identifier: AGPL-3.0-only
4
+ /**
5
+ * @file lumenflow-uninstall.ts
6
+ * Destructive eject — remove all LumenFlow-generated files from a project
7
+ * (WU-2590).
8
+ *
9
+ * Defaults to dry-run. `--confirm` is required to actually delete. Supports
10
+ * `--keep-history`, `--keep-config`, `--client <name>`, and `--json`.
11
+ *
12
+ * Usage:
13
+ * pnpm lumenflow:uninstall # dry-run
14
+ * pnpm lumenflow:uninstall --confirm # actually delete
15
+ * pnpm lumenflow:uninstall --keep-history --keep-config --confirm
16
+ * pnpm lumenflow:uninstall --client cursor --confirm
17
+ */
18
+ import * as fs from 'node:fs';
19
+ import * as path from 'node:path';
20
+ import { createWUParser } from '@lumenflow/core';
21
+ import { runCLI } from './cli-entry-point.js';
22
+ import { getGeneratedFileManifest, filterForUninstall, } from './lumenflow-file-classifier.js';
23
+ const LOG_PREFIX = '[lumenflow:uninstall]';
24
+ export function parseUninstallOptions() {
25
+ const opts = createWUParser({
26
+ name: 'lumenflow-uninstall',
27
+ description: 'Remove all LumenFlow-generated files from this project. Dry-run by default; pass --confirm to actually delete.',
28
+ options: [
29
+ {
30
+ name: 'confirm',
31
+ flags: '--confirm',
32
+ description: 'Actually delete files. Without this, runs as a dry-run.',
33
+ type: 'boolean',
34
+ },
35
+ {
36
+ name: 'keepHistory',
37
+ flags: '--keep-history',
38
+ description: 'Preserve docs/operations/tasks/wu/ and .lumenflow/stamps/',
39
+ type: 'boolean',
40
+ },
41
+ {
42
+ name: 'keepConfig',
43
+ flags: '--keep-config',
44
+ description: 'Preserve workspace.yaml and .lumenflow/constraints.md',
45
+ type: 'boolean',
46
+ },
47
+ {
48
+ name: 'client',
49
+ flags: '--client <type>',
50
+ description: 'Scope deletion to a single client (default: all)',
51
+ },
52
+ {
53
+ name: 'json',
54
+ flags: '--json',
55
+ description: 'Emit machine-readable JSON output',
56
+ type: 'boolean',
57
+ },
58
+ ],
59
+ });
60
+ return {
61
+ confirm: opts.confirm ?? false,
62
+ keepHistory: opts.keepHistory ?? false,
63
+ keepConfig: opts.keepConfig ?? false,
64
+ client: opts.client,
65
+ json: opts.json ?? false,
66
+ };
67
+ }
68
+ export function buildUninstallPlan(projectDir, opts) {
69
+ const all = getGeneratedFileManifest(projectDir, { client: opts.client });
70
+ const filtered = filterForUninstall(all, {
71
+ keepHistory: opts.keepHistory,
72
+ keepConfig: opts.keepConfig,
73
+ });
74
+ const toStrip = filtered.filter((f) => f.surgicalStrip);
75
+ const toDelete = filtered.filter((f) => !f.surgicalStrip);
76
+ return { dryRun: !opts.confirm, toDelete, toStrip };
77
+ }
78
+ /**
79
+ * Surgically strip the LumenFlow-managed hooks section from .claude/settings.json
80
+ * while preserving user content.
81
+ */
82
+ export function stripClaudeSettings(abs) {
83
+ if (!fs.existsSync(abs))
84
+ return false;
85
+ try {
86
+ const raw = fs.readFileSync(abs, 'utf-8');
87
+ const parsed = JSON.parse(raw);
88
+ delete parsed.hooks;
89
+ fs.writeFileSync(abs, JSON.stringify(parsed, null, 2) + '\n', 'utf-8');
90
+ return true;
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ }
96
+ /**
97
+ * Strip the WU-2157 recovery section appended to AGENTS.md by integrateCodexCli.
98
+ */
99
+ export function stripAgentsRecovery(abs) {
100
+ if (!fs.existsSync(abs))
101
+ return false;
102
+ const content = fs.readFileSync(abs, 'utf-8');
103
+ const marker = '\n---\n\n## Context Recovery (WU-2157)';
104
+ const idx = content.indexOf(marker);
105
+ if (idx < 0)
106
+ return false;
107
+ fs.writeFileSync(abs, content.slice(0, idx).trimEnd() + '\n', 'utf-8');
108
+ return true;
109
+ }
110
+ export function executePlan(projectDir, plan) {
111
+ for (const file of plan.toDelete) {
112
+ const abs = path.join(projectDir, file.path);
113
+ if (!fs.existsSync(abs))
114
+ continue;
115
+ const stat = fs.statSync(abs);
116
+ if (stat.isDirectory()) {
117
+ fs.rmSync(abs, { recursive: true, force: true });
118
+ }
119
+ else {
120
+ fs.unlinkSync(abs);
121
+ }
122
+ }
123
+ for (const file of plan.toStrip) {
124
+ const abs = path.join(projectDir, file.path);
125
+ if (file.path.endsWith('settings.json')) {
126
+ stripClaudeSettings(abs);
127
+ }
128
+ else if (file.path === 'AGENTS.md') {
129
+ stripAgentsRecovery(abs);
130
+ }
131
+ }
132
+ }
133
+ function formatPlan(plan) {
134
+ const lines = [];
135
+ lines.push(plan.dryRun ? `${LOG_PREFIX} DRY-RUN — no files modified.` : `${LOG_PREFIX} Removing files:`);
136
+ if (plan.toDelete.length === 0 && plan.toStrip.length === 0) {
137
+ lines.push(`${LOG_PREFIX} No LumenFlow-generated files found.`);
138
+ return lines.join('\n');
139
+ }
140
+ for (const f of plan.toDelete) {
141
+ lines.push(` delete ${f.path} (${f.category}${f.client ? ` · ${f.client}` : ''})`);
142
+ }
143
+ for (const f of plan.toStrip) {
144
+ lines.push(` strip ${f.path} (${f.category}${f.client ? ` · ${f.client}` : ''})`);
145
+ }
146
+ if (plan.dryRun) {
147
+ lines.push('');
148
+ lines.push(`${LOG_PREFIX} Re-run with --confirm to apply.`);
149
+ }
150
+ else {
151
+ lines.push('');
152
+ lines.push(`${LOG_PREFIX} Restore via: git checkout -- <files> (or: git revert <commit> if already committed)`);
153
+ }
154
+ return lines.join('\n');
155
+ }
156
+ export async function main() {
157
+ const opts = parseUninstallOptions();
158
+ const projectDir = process.cwd();
159
+ const plan = buildUninstallPlan(projectDir, opts);
160
+ if (!plan.dryRun) {
161
+ executePlan(projectDir, plan);
162
+ }
163
+ if (opts.json) {
164
+ process.stdout.write(JSON.stringify(plan, null, 2) + '\n');
165
+ return;
166
+ }
167
+ console.log(formatPlan(plan));
168
+ }
169
+ if (import.meta.main) {
170
+ void runCLI(main);
171
+ }
172
+ //# sourceMappingURL=lumenflow-uninstall.js.map