@mriekki/lean 0.1.0 → 0.2.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 (41) hide show
  1. package/README.md +71 -67
  2. package/assets/components.json +33 -33
  3. package/assets/core/distribution.json +52 -51
  4. package/assets/deployment/distribution.json +34 -34
  5. package/assets/generated/ASSEMBLY.json +38 -33
  6. package/assets/generated/core/core/skills/core-sync/SKILL.md +78 -120
  7. package/assets/generated/core/core/skills/deploy/SKILL.md +74 -91
  8. package/assets/generated/core/core/skills/lean-update/SKILL.md +189 -0
  9. package/assets/generated/core/core/skills/upstream-fix/SKILL.md +91 -76
  10. package/assets/generated/deployment/distribution/lean-poc-deploy.py +8 -8
  11. package/assets/generated/deployment/src/lean_poc_deployment/__init__.py +3 -3
  12. package/assets/generated/deployment/src/lean_poc_deployment/__main__.py +3 -3
  13. package/assets/generated/deployment/src/lean_poc_deployment/app_cli.py +140 -140
  14. package/assets/generated/deployment/src/lean_poc_deployment/app_runtime.py +455 -455
  15. package/assets/generated/deployment/src/lean_poc_deployment/app_runtime_remote.py +415 -415
  16. package/assets/generated/deployment/src/lean_poc_deployment/box_platform.py +208 -208
  17. package/assets/generated/deployment/src/lean_poc_deployment/box_platform_assets.py +100 -100
  18. package/assets/generated/deployment/src/lean_poc_deployment/box_platform_remote.py +271 -271
  19. package/assets/generated/deployment/src/lean_poc_deployment/cli.py +437 -437
  20. package/assets/generated/deployment/src/lean_poc_deployment/doctor.py +121 -121
  21. package/assets/generated/deployment/src/lean_poc_deployment/entrypoint.py +25 -25
  22. package/assets/generated/deployment/src/lean_poc_deployment/errors.py +5 -5
  23. package/assets/generated/deployment/src/lean_poc_deployment/keys.py +171 -171
  24. package/assets/generated/deployment/src/lean_poc_deployment/profiles.py +204 -204
  25. package/assets/generated/deployment/src/lean_poc_deployment/route_registry.py +367 -367
  26. package/assets/generated/deployment/src/lean_poc_deployment/route_registry_remote.py +443 -443
  27. package/assets/generated/deployment/src/lean_poc_deployment/security.py +290 -290
  28. package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter.py +334 -334
  29. package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter_remote.py +339 -339
  30. package/assets/generated/deployment/src/lean_poc_deployment/sqlite_cli.py +85 -85
  31. package/assets/generated/deployment/src/lean_poc_deployment/sqlite_import_recovery.py +437 -437
  32. package/assets/generated/deployment/src/lean_poc_deployment/ssh.py +118 -118
  33. package/assets/generated/deployment/src/lean_poc_deployment/teardown.py +286 -286
  34. package/assets/generated/deployment/src/lean_poc_deployment/teardown_cli.py +120 -120
  35. package/assets/generated/deployment/src/lean_poc_deployment/teardown_remote.py +100 -100
  36. package/assets/generated/deployment/src/lean_poc_deployment/trust.py +278 -278
  37. package/bin/lean.mjs +9 -9
  38. package/package.json +33 -33
  39. package/src/cli.mjs +131 -131
  40. package/src/engine.mjs +243 -243
  41. package/src/package-data.mjs +18 -18
package/src/cli.mjs CHANGED
@@ -1,131 +1,131 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { execFileSync } from 'node:child_process';
4
- import readline from 'node:readline/promises';
5
- import { stdin as input, stdout as output } from 'node:process';
6
- import { applyProfile, checkProfile, loadLeanBaseline, planProfile } from './engine.mjs';
7
- import { loadPackageData } from './package-data.mjs';
8
-
9
- function parseArgs(argv) {
10
- const options = { acceptedRetired: new Map() };
11
- const positional = [];
12
- for (let i = 0; i < argv.length; i += 1) {
13
- const arg = argv[i];
14
- if (arg === '--apply' || arg === '--yes') options.apply = true;
15
- else if (arg === '--root') options.root = argv[++i];
16
- else if (arg === '--profile') options.profile = argv[++i];
17
- else if (arg === '--consumer') options.consumer = argv[++i];
18
- else if (arg === '--consumer-baseline') options.consumerBaseline = argv[++i];
19
- else if (arg === '--accept-retired') {
20
- const value = argv[++i] ?? '';
21
- const index = value.indexOf(':');
22
- if (index <= 0) throw new Error('--accept-retired must use component:path');
23
- const component = value.slice(0, index);
24
- const retiredPath = value.slice(index + 1);
25
- if (!options.acceptedRetired.has(component)) options.acceptedRetired.set(component, new Set());
26
- options.acceptedRetired.get(component).add(retiredPath);
27
- } else if (arg.startsWith('-')) throw new Error(`Unknown option: ${arg}`);
28
- else positional.push(arg);
29
- }
30
- return { command: positional[0] ?? 'help', options };
31
- }
32
-
33
- function inferConsumer(root) {
34
- try {
35
- const origin = execFileSync('git', ['-C', root, 'config', '--get', 'remote.origin.url'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
36
- if (origin) {
37
- const normalized = origin.replace(/\\/g, '/').replace(/\.git$/, '');
38
- const match = normalized.match(/[:/]([^/:]+\/[^/]+)$/);
39
- if (match) return match[1].split('/').pop();
40
- }
41
- } catch {}
42
- return path.basename(root);
43
- }
44
-
45
- function printPlan(plan) {
46
- console.log(`Lean profile: ${plan.profileName}`);
47
- for (const component of plan.plans) {
48
- console.log(`\n${component.componentId.toUpperCase()} ${component.component.commit}`);
49
- for (const row of component.rows) console.log(`${row.state.padEnd(20)} ${row.path}`);
50
- for (const retired of component.retired) console.log(`${retired.state.padEnd(20)} ${retired.path}`);
51
- }
52
- console.log(`\nUnsafe drift/conflicts: ${plan.unsafe.length}`);
53
- for (const item of plan.unsafe) console.log(` - ${item.component}: ${item.state}: ${item.path}`);
54
- }
55
-
56
- async function shouldApply(options) {
57
- if (options.apply) return true;
58
- if (!input.isTTY || !output.isTTY) return false;
59
- const rl = readline.createInterface({ input, output });
60
- try {
61
- const answer = (await rl.question('Apply this safe Lean asset plan? [y/N] ')).trim().toLowerCase();
62
- return answer === 'y' || answer === 'yes';
63
- } finally {
64
- rl.close();
65
- }
66
- }
67
-
68
- function usage() {
69
- console.log(`@mriekki/lean\n\nCommands:\n install Preview/install bundled Lean assets\n update Preview/update to this package's bundled Lean assets\n status Show installed umbrella/component provenance\n check Verify managed files and baselines exactly match this package\n version Show package version\n\nCommon options:\n --root PATH\n --profile greenfield-poc\n --apply | --yes\n --consumer NAME\n --consumer-baseline LABEL\n --accept-retired core:path\n`);
70
- }
71
-
72
- export async function run(argv = process.argv.slice(2), packageData = loadPackageData()) {
73
- const { command, options } = parseArgs(argv);
74
- if (command === 'help' || command === '--help') { usage(); return 0; }
75
- if (command === 'version') { console.log(`${packageData.pkg.name} ${packageData.pkg.version}`); return 0; }
76
- const root = path.resolve(options.root ?? process.cwd());
77
- if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) throw new Error(`Repository root not found: ${root}`);
78
- const installed = loadLeanBaseline(root);
79
- const profile = options.profile ?? installed.profile ?? packageData.assets.defaultProfile;
80
-
81
- if (command === 'status') {
82
- if (!Object.keys(installed).length) {
83
- console.log(`Lean: not installed\nAvailable package: ${packageData.pkg.name}@${packageData.pkg.version}\nProfile: ${profile}`);
84
- return 0;
85
- }
86
- console.log(`Lean package: ${installed.package}@${installed.version}`);
87
- console.log(`Available package: ${packageData.pkg.name}@${packageData.pkg.version}`);
88
- console.log(`Profile: ${installed.profile}`);
89
- for (const [name, component] of Object.entries(installed.components ?? {})) {
90
- console.log(`${name}: ${component.version ? component.version + ' @ ' : ''}${component.commit}`);
91
- }
92
- return 0;
93
- }
94
-
95
- if (command === 'check') {
96
- const result = checkProfile(packageData, profile, root);
97
- console.log(`Lean package baseline current: ${result.leanCurrent ? 'yes' : 'no'}`);
98
- for (const component of result.plan.plans) {
99
- const clean = component.metadataCurrent && component.rows.every((row) => row.state === 'SAME') && component.retired.length === 0;
100
- console.log(`${component.componentId}: ${clean ? 'PASS' : 'STALE OR DRIFTED'}`);
101
- }
102
- console.log(`Lean check: ${result.clean ? 'PASS' : 'STALE OR DRIFTED'}`);
103
- return result.clean ? 0 : 1;
104
- }
105
-
106
- if (!['install', 'update'].includes(command)) throw new Error(`Unknown command: ${command}`);
107
- if (command === 'install' && Object.keys(installed).length) throw new Error('LEAN_BASELINE.json already exists; use update instead of install');
108
- if (command === 'update' && !Object.keys(installed).length) throw new Error('LEAN_BASELINE.json is missing; use install first');
109
- if (installed.profile && installed.profile !== profile) throw new Error(`Installed Lean profile is ${installed.profile}; refusing implicit profile change to ${profile}`);
110
-
111
- const plan = planProfile(packageData, profile, root, options.acceptedRetired);
112
- printPlan(plan);
113
- if (plan.unsafe.length) {
114
- console.log('\nRefusing apply. Resolve drift/conflicts and preview again. No managed files were changed.');
115
- return 2;
116
- }
117
- const apply = await shouldApply(options);
118
- if (!apply) {
119
- console.log('\nPreview only. Re-run with --apply, or run interactively and approve the prompt.');
120
- return 0;
121
- }
122
- const baselineConsumers = [...new Set(plan.plans.map((item) => item.baseline.consumer).filter(Boolean))];
123
- const baselineLabels = [...new Set(plan.plans.map((item) => item.baseline.consumerBaseline).filter(Boolean))];
124
- if (baselineConsumers.length > 1) throw new Error(`Component baselines disagree on consumer identity: ${baselineConsumers.join(', ')}`);
125
- if (baselineLabels.length > 1) throw new Error(`Component baselines disagree on consumer baseline: ${baselineLabels.join(', ')}`);
126
- const consumer = options.consumer ?? installed.consumer ?? baselineConsumers[0] ?? inferConsumer(root);
127
- const consumerBaseline = options.consumerBaseline ?? installed.consumerBaseline ?? baselineLabels[0] ?? `${packageData.pkg.name}@${packageData.pkg.version}`;
128
- applyProfile(packageData, plan, root, consumer, consumerBaseline);
129
- console.log(`\nLean ${command}: PASS (${packageData.pkg.name}@${packageData.pkg.version})`);
130
- return 0;
131
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+ import readline from 'node:readline/promises';
5
+ import { stdin as input, stdout as output } from 'node:process';
6
+ import { applyProfile, checkProfile, loadLeanBaseline, planProfile } from './engine.mjs';
7
+ import { loadPackageData } from './package-data.mjs';
8
+
9
+ function parseArgs(argv) {
10
+ const options = { acceptedRetired: new Map() };
11
+ const positional = [];
12
+ for (let i = 0; i < argv.length; i += 1) {
13
+ const arg = argv[i];
14
+ if (arg === '--apply' || arg === '--yes') options.apply = true;
15
+ else if (arg === '--root') options.root = argv[++i];
16
+ else if (arg === '--profile') options.profile = argv[++i];
17
+ else if (arg === '--consumer') options.consumer = argv[++i];
18
+ else if (arg === '--consumer-baseline') options.consumerBaseline = argv[++i];
19
+ else if (arg === '--accept-retired') {
20
+ const value = argv[++i] ?? '';
21
+ const index = value.indexOf(':');
22
+ if (index <= 0) throw new Error('--accept-retired must use component:path');
23
+ const component = value.slice(0, index);
24
+ const retiredPath = value.slice(index + 1);
25
+ if (!options.acceptedRetired.has(component)) options.acceptedRetired.set(component, new Set());
26
+ options.acceptedRetired.get(component).add(retiredPath);
27
+ } else if (arg.startsWith('-')) throw new Error(`Unknown option: ${arg}`);
28
+ else positional.push(arg);
29
+ }
30
+ return { command: positional[0] ?? 'help', options };
31
+ }
32
+
33
+ function inferConsumer(root) {
34
+ try {
35
+ const origin = execFileSync('git', ['-C', root, 'config', '--get', 'remote.origin.url'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
36
+ if (origin) {
37
+ const normalized = origin.replace(/\\/g, '/').replace(/\.git$/, '');
38
+ const match = normalized.match(/[:/]([^/:]+\/[^/]+)$/);
39
+ if (match) return match[1].split('/').pop();
40
+ }
41
+ } catch {}
42
+ return path.basename(root);
43
+ }
44
+
45
+ function printPlan(plan) {
46
+ console.log(`Lean profile: ${plan.profileName}`);
47
+ for (const component of plan.plans) {
48
+ console.log(`\n${component.componentId.toUpperCase()} ${component.component.commit}`);
49
+ for (const row of component.rows) console.log(`${row.state.padEnd(20)} ${row.path}`);
50
+ for (const retired of component.retired) console.log(`${retired.state.padEnd(20)} ${retired.path}`);
51
+ }
52
+ console.log(`\nUnsafe drift/conflicts: ${plan.unsafe.length}`);
53
+ for (const item of plan.unsafe) console.log(` - ${item.component}: ${item.state}: ${item.path}`);
54
+ }
55
+
56
+ async function shouldApply(options) {
57
+ if (options.apply) return true;
58
+ if (!input.isTTY || !output.isTTY) return false;
59
+ const rl = readline.createInterface({ input, output });
60
+ try {
61
+ const answer = (await rl.question('Apply this safe Lean asset plan? [y/N] ')).trim().toLowerCase();
62
+ return answer === 'y' || answer === 'yes';
63
+ } finally {
64
+ rl.close();
65
+ }
66
+ }
67
+
68
+ function usage() {
69
+ console.log(`@mriekki/lean\n\nCommands:\n install Preview/install bundled Lean assets\n update Preview/update to this package's bundled Lean assets\n status Show installed umbrella/component provenance\n check Verify managed files and baselines exactly match this package\n version Show package version\n\nCommon options:\n --root PATH\n --profile greenfield-poc\n --apply | --yes\n --consumer NAME\n --consumer-baseline LABEL\n --accept-retired core:path\n`);
70
+ }
71
+
72
+ export async function run(argv = process.argv.slice(2), packageData = loadPackageData()) {
73
+ const { command, options } = parseArgs(argv);
74
+ if (command === 'help' || command === '--help') { usage(); return 0; }
75
+ if (command === 'version') { console.log(`${packageData.pkg.name} ${packageData.pkg.version}`); return 0; }
76
+ const root = path.resolve(options.root ?? process.cwd());
77
+ if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) throw new Error(`Repository root not found: ${root}`);
78
+ const installed = loadLeanBaseline(root);
79
+ const profile = options.profile ?? installed.profile ?? packageData.assets.defaultProfile;
80
+
81
+ if (command === 'status') {
82
+ if (!Object.keys(installed).length) {
83
+ console.log(`Lean: not installed\nAvailable package: ${packageData.pkg.name}@${packageData.pkg.version}\nProfile: ${profile}`);
84
+ return 0;
85
+ }
86
+ console.log(`Lean package: ${installed.package}@${installed.version}`);
87
+ console.log(`Available package: ${packageData.pkg.name}@${packageData.pkg.version}`);
88
+ console.log(`Profile: ${installed.profile}`);
89
+ for (const [name, component] of Object.entries(installed.components ?? {})) {
90
+ console.log(`${name}: ${component.version ? component.version + ' @ ' : ''}${component.commit}`);
91
+ }
92
+ return 0;
93
+ }
94
+
95
+ if (command === 'check') {
96
+ const result = checkProfile(packageData, profile, root);
97
+ console.log(`Lean package baseline current: ${result.leanCurrent ? 'yes' : 'no'}`);
98
+ for (const component of result.plan.plans) {
99
+ const clean = component.metadataCurrent && component.rows.every((row) => row.state === 'SAME') && component.retired.length === 0;
100
+ console.log(`${component.componentId}: ${clean ? 'PASS' : 'STALE OR DRIFTED'}`);
101
+ }
102
+ console.log(`Lean check: ${result.clean ? 'PASS' : 'STALE OR DRIFTED'}`);
103
+ return result.clean ? 0 : 1;
104
+ }
105
+
106
+ if (!['install', 'update'].includes(command)) throw new Error(`Unknown command: ${command}`);
107
+ if (command === 'install' && Object.keys(installed).length) throw new Error('LEAN_BASELINE.json already exists; use update instead of install');
108
+ if (command === 'update' && !Object.keys(installed).length) throw new Error('LEAN_BASELINE.json is missing; use install first');
109
+ if (installed.profile && installed.profile !== profile) throw new Error(`Installed Lean profile is ${installed.profile}; refusing implicit profile change to ${profile}`);
110
+
111
+ const plan = planProfile(packageData, profile, root, options.acceptedRetired);
112
+ printPlan(plan);
113
+ if (plan.unsafe.length) {
114
+ console.log('\nRefusing apply. Resolve drift/conflicts and preview again. No managed files were changed.');
115
+ return 2;
116
+ }
117
+ const apply = await shouldApply(options);
118
+ if (!apply) {
119
+ console.log('\nPreview only. Re-run with --apply, or run interactively and approve the prompt.');
120
+ return 0;
121
+ }
122
+ const baselineConsumers = [...new Set(plan.plans.map((item) => item.baseline.consumer).filter(Boolean))];
123
+ const baselineLabels = [...new Set(plan.plans.map((item) => item.baseline.consumerBaseline).filter(Boolean))];
124
+ if (baselineConsumers.length > 1) throw new Error(`Component baselines disagree on consumer identity: ${baselineConsumers.join(', ')}`);
125
+ if (baselineLabels.length > 1) throw new Error(`Component baselines disagree on consumer baseline: ${baselineLabels.join(', ')}`);
126
+ const consumer = options.consumer ?? installed.consumer ?? baselineConsumers[0] ?? inferConsumer(root);
127
+ const consumerBaseline = options.consumerBaseline ?? installed.consumerBaseline ?? baselineLabels[0] ?? `${packageData.pkg.name}@${packageData.pkg.version}`;
128
+ applyProfile(packageData, plan, root, consumer, consumerBaseline);
129
+ console.log(`\nLean ${command}: PASS (${packageData.pkg.name}@${packageData.pkg.version})`);
130
+ return 0;
131
+ }