@codyswann/lisa 1.0.0 → 1.0.5

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 (280) hide show
  1. package/README.md +244 -36
  2. package/all/copy-overwrite/.claude/README.md +1 -3
  3. package/all/copy-overwrite/.claude/REFERENCE.md +519 -0
  4. package/all/copy-overwrite/.claude/agents/skill-evaluator.md +7 -7
  5. package/all/copy-overwrite/.claude/agents/test-coverage-agent.md +17 -0
  6. package/all/copy-overwrite/.claude/commands/git/commit.md +9 -5
  7. package/all/copy-overwrite/.claude/commands/git/submit-pr.md +1 -1
  8. package/all/copy-overwrite/.claude/commands/lisa/review-implementation.md +209 -0
  9. package/all/copy-overwrite/.claude/commands/project/add-test-coverage.md +58 -0
  10. package/all/copy-overwrite/.claude/commands/project/archive.md +1 -1
  11. package/all/copy-overwrite/.claude/commands/project/complete-task.md +53 -1
  12. package/all/copy-overwrite/.claude/commands/project/debrief.md +12 -23
  13. package/all/copy-overwrite/.claude/commands/project/execute.md +33 -77
  14. package/all/copy-overwrite/.claude/commands/project/fix-linter-error.md +87 -0
  15. package/all/copy-overwrite/.claude/commands/project/implement.md +24 -28
  16. package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +30 -55
  17. package/all/copy-overwrite/.claude/commands/project/plan.md +87 -242
  18. package/all/copy-overwrite/.claude/commands/project/reduce-max-lines-per-function.md +76 -0
  19. package/all/copy-overwrite/.claude/commands/project/reduce-max-lines.md +75 -0
  20. package/all/copy-overwrite/.claude/commands/project/research.md +86 -188
  21. package/all/copy-overwrite/.claude/commands/project/review.md +19 -38
  22. package/all/copy-overwrite/.claude/commands/project/setup.md +1 -1
  23. package/all/copy-overwrite/.claude/commands/project/verify.md +62 -25
  24. package/all/copy-overwrite/.claude/commands/pull-request/review.md +25 -7
  25. package/all/copy-overwrite/.claude/commands/tasks/load.md +63 -0
  26. package/all/copy-overwrite/.claude/commands/tasks/sync.md +84 -0
  27. package/all/copy-overwrite/.claude/hooks/README.md +75 -0
  28. package/all/copy-overwrite/.claude/hooks/check-tired-boss.sh +61 -0
  29. package/all/copy-overwrite/.claude/hooks/debug-hook.sh +47 -0
  30. package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +2 -0
  31. package/all/copy-overwrite/.claude/hooks/sync-tasks.sh +95 -0
  32. package/all/copy-overwrite/.claude/{skills/coding-philosophy/SKILL.md → rules/coding-philosophy.md} +93 -70
  33. package/all/copy-overwrite/.claude/settings.json +35 -14
  34. package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +41 -9
  35. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +2 -0
  36. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +2 -0
  37. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +2 -0
  38. package/all/copy-overwrite/.safety-net.json +25 -0
  39. package/all/copy-overwrite/CLAUDE.md +8 -30
  40. package/all/copy-overwrite/HUMAN.md +517 -17
  41. package/all/create-only/.claude/rules/PROJECT_RULES.md +9 -0
  42. package/all/create-only/scripts/setup-deploy-key.sh +190 -0
  43. package/all/deletions.json +5 -0
  44. package/cdk/copy-overwrite/.github/workflows/ci.yml +142 -0
  45. package/cdk/copy-overwrite/.github/workflows/deploy.yml +59 -0
  46. package/cdk/copy-overwrite/eslint.cdk.ts +175 -0
  47. package/cdk/copy-overwrite/eslint.config.ts +51 -0
  48. package/cdk/copy-overwrite/eslint.slow.config.ts +80 -0
  49. package/cdk/copy-overwrite/knip.json +53 -0
  50. package/cdk/copy-overwrite/tsconfig.eslint.json +11 -0
  51. package/cdk/merge/package.json +17 -1
  52. package/dist/cli/index.d.ts +3 -2
  53. package/dist/cli/index.d.ts.map +1 -1
  54. package/dist/cli/index.js +83 -64
  55. package/dist/cli/index.js.map +1 -1
  56. package/dist/cli/prompts.d.ts +17 -3
  57. package/dist/cli/prompts.d.ts.map +1 -1
  58. package/dist/cli/prompts.js +52 -16
  59. package/dist/cli/prompts.js.map +1 -1
  60. package/dist/core/config.d.ts +13 -4
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +17 -9
  63. package/dist/core/config.js.map +1 -1
  64. package/dist/core/git-service.d.ts +40 -0
  65. package/dist/core/git-service.d.ts.map +1 -0
  66. package/dist/core/git-service.js +52 -0
  67. package/dist/core/git-service.js.map +1 -0
  68. package/dist/core/index.d.ts +3 -3
  69. package/dist/core/index.js +3 -3
  70. package/dist/core/lisa.d.ts +124 -7
  71. package/dist/core/lisa.d.ts.map +1 -1
  72. package/dist/core/lisa.js +423 -221
  73. package/dist/core/lisa.js.map +1 -1
  74. package/dist/core/manifest.d.ts +5 -1
  75. package/dist/core/manifest.d.ts.map +1 -1
  76. package/dist/core/manifest.js +22 -16
  77. package/dist/core/manifest.js.map +1 -1
  78. package/dist/detection/detector.interface.d.ts +1 -1
  79. package/dist/detection/detectors/cdk.d.ts +6 -1
  80. package/dist/detection/detectors/cdk.d.ts.map +1 -1
  81. package/dist/detection/detectors/cdk.js +16 -8
  82. package/dist/detection/detectors/cdk.js.map +1 -1
  83. package/dist/detection/detectors/expo.d.ts +6 -1
  84. package/dist/detection/detectors/expo.d.ts.map +1 -1
  85. package/dist/detection/detectors/expo.js +13 -8
  86. package/dist/detection/detectors/expo.js.map +1 -1
  87. package/dist/detection/detectors/nestjs.d.ts +7 -2
  88. package/dist/detection/detectors/nestjs.d.ts.map +1 -1
  89. package/dist/detection/detectors/nestjs.js +17 -9
  90. package/dist/detection/detectors/nestjs.js.map +1 -1
  91. package/dist/detection/detectors/npm-package.d.ts +6 -1
  92. package/dist/detection/detectors/npm-package.d.ts.map +1 -1
  93. package/dist/detection/detectors/npm-package.js +9 -4
  94. package/dist/detection/detectors/npm-package.js.map +1 -1
  95. package/dist/detection/detectors/typescript.d.ts +6 -1
  96. package/dist/detection/detectors/typescript.d.ts.map +1 -1
  97. package/dist/detection/detectors/typescript.js +12 -7
  98. package/dist/detection/detectors/typescript.js.map +1 -1
  99. package/dist/detection/index.d.ts +13 -3
  100. package/dist/detection/index.d.ts.map +1 -1
  101. package/dist/detection/index.js +17 -7
  102. package/dist/detection/index.js.map +1 -1
  103. package/dist/errors/index.d.ts +66 -2
  104. package/dist/errors/index.d.ts.map +1 -1
  105. package/dist/errors/index.js +89 -17
  106. package/dist/errors/index.js.map +1 -1
  107. package/dist/index.js +3 -3
  108. package/dist/index.js.map +1 -1
  109. package/dist/logging/console-logger.d.ts +21 -1
  110. package/dist/logging/console-logger.d.ts.map +1 -1
  111. package/dist/logging/console-logger.js +26 -6
  112. package/dist/logging/console-logger.js.map +1 -1
  113. package/dist/logging/index.d.ts +3 -3
  114. package/dist/logging/index.js +2 -2
  115. package/dist/logging/logger.interface.d.ts +1 -1
  116. package/dist/logging/silent-logger.d.ts +21 -1
  117. package/dist/logging/silent-logger.d.ts.map +1 -1
  118. package/dist/logging/silent-logger.js +20 -0
  119. package/dist/logging/silent-logger.js.map +1 -1
  120. package/dist/strategies/copy-contents.d.ts +47 -6
  121. package/dist/strategies/copy-contents.d.ts.map +1 -1
  122. package/dist/strategies/copy-contents.js +99 -49
  123. package/dist/strategies/copy-contents.js.map +1 -1
  124. package/dist/strategies/copy-overwrite.d.ts +10 -2
  125. package/dist/strategies/copy-overwrite.d.ts.map +1 -1
  126. package/dist/strategies/copy-overwrite.js +17 -9
  127. package/dist/strategies/copy-overwrite.js.map +1 -1
  128. package/dist/strategies/create-only.d.ts +10 -2
  129. package/dist/strategies/create-only.d.ts.map +1 -1
  130. package/dist/strategies/create-only.js +14 -6
  131. package/dist/strategies/create-only.js.map +1 -1
  132. package/dist/strategies/index.d.ts +17 -7
  133. package/dist/strategies/index.d.ts.map +1 -1
  134. package/dist/strategies/index.js +19 -9
  135. package/dist/strategies/index.js.map +1 -1
  136. package/dist/strategies/merge.d.ts +10 -2
  137. package/dist/strategies/merge.d.ts.map +1 -1
  138. package/dist/strategies/merge.js +21 -21
  139. package/dist/strategies/merge.js.map +1 -1
  140. package/dist/strategies/strategy.interface.d.ts +1 -1
  141. package/dist/strategies/strategy.interface.d.ts.map +1 -1
  142. package/dist/transaction/backup.d.ts +15 -1
  143. package/dist/transaction/backup.d.ts.map +1 -1
  144. package/dist/transaction/backup.js +47 -12
  145. package/dist/transaction/backup.js.map +1 -1
  146. package/dist/transaction/index.d.ts +3 -3
  147. package/dist/transaction/index.js +2 -2
  148. package/dist/transaction/transaction.d.ts +25 -2
  149. package/dist/transaction/transaction.d.ts.map +1 -1
  150. package/dist/transaction/transaction.js +25 -2
  151. package/dist/transaction/transaction.js.map +1 -1
  152. package/dist/utils/file-operations.d.ts +21 -0
  153. package/dist/utils/file-operations.d.ts.map +1 -1
  154. package/dist/utils/file-operations.js +48 -12
  155. package/dist/utils/file-operations.js.map +1 -1
  156. package/dist/utils/index.d.ts +3 -3
  157. package/dist/utils/index.js +3 -3
  158. package/dist/utils/json-utils.d.ts +12 -0
  159. package/dist/utils/json-utils.d.ts.map +1 -1
  160. package/dist/utils/json-utils.js +17 -5
  161. package/dist/utils/json-utils.js.map +1 -1
  162. package/dist/utils/path-utils.d.ts +11 -0
  163. package/dist/utils/path-utils.d.ts.map +1 -1
  164. package/dist/utils/path-utils.js +12 -1
  165. package/dist/utils/path-utils.js.map +1 -1
  166. package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +5 -0
  167. package/eslint-plugin-code-organization/index.js +5 -0
  168. package/eslint-plugin-code-organization/rules/enforce-statement-order.js +5 -0
  169. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +2 -0
  170. package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +2 -0
  171. package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +2 -0
  172. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +2 -0
  173. package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +2 -0
  174. package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +2 -0
  175. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +2 -41
  176. package/{typescript → expo}/copy-overwrite/.github/workflows/build.yml +3 -0
  177. package/expo/copy-overwrite/.github/workflows/ci.yml +36 -0
  178. package/{typescript → expo}/copy-overwrite/.github/workflows/deploy.yml +22 -26
  179. package/{typescript → expo}/copy-overwrite/.github/workflows/lighthouse.yml +4 -1
  180. package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +5 -0
  181. package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +5 -0
  182. package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +5 -0
  183. package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +5 -0
  184. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +5 -0
  185. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +6 -1
  186. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +5 -0
  187. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +5 -0
  188. package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +0 -68
  189. package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +5 -3
  190. package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +5 -0
  191. package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +5 -0
  192. package/expo/copy-overwrite/eslint.config.ts +53 -0
  193. package/expo/copy-overwrite/eslint.expo.ts +330 -0
  194. package/expo/copy-overwrite/eslint.slow.config.ts +86 -0
  195. package/expo/copy-overwrite/knip.json +132 -0
  196. package/expo/copy-overwrite/lighthouserc.js +27 -0
  197. package/expo/copy-overwrite/tsconfig.eslint.json +25 -0
  198. package/expo/create-only/lighthouserc-config.json +6 -1
  199. package/expo/merge/package.json +16 -3
  200. package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +1 -1
  201. package/{typescript → nestjs}/copy-overwrite/.github/k6/README.md +2 -2
  202. package/{typescript → nestjs}/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +3 -0
  203. package/{typescript → nestjs}/copy-overwrite/.github/k6/examples/data-driven-test.js +5 -0
  204. package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/load.js +6 -2
  205. package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/smoke.js +5 -0
  206. package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/soak.js +5 -0
  207. package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/spike.js +5 -0
  208. package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/stress.js +5 -0
  209. package/{typescript → nestjs}/copy-overwrite/.github/k6/scripts/api-test.js +5 -0
  210. package/{typescript → nestjs}/copy-overwrite/.github/k6/scripts/default-test.js +5 -0
  211. package/nestjs/copy-overwrite/.github/workflows/ci.yml +29 -0
  212. package/nestjs/copy-overwrite/.github/workflows/deploy.yml +291 -0
  213. package/{typescript → nestjs}/copy-overwrite/.github/workflows/load-test.yml +3 -0
  214. package/nestjs/copy-overwrite/eslint.config.ts +53 -0
  215. package/nestjs/copy-overwrite/eslint.nestjs.ts +178 -0
  216. package/nestjs/merge/package.json +11 -3
  217. package/package.json +34 -40
  218. package/typescript/copy-contents/.husky/pre-commit +1 -1
  219. package/typescript/copy-contents/.husky/pre-push +99 -118
  220. package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +2 -0
  221. package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +3 -11
  222. package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +2 -0
  223. package/typescript/copy-overwrite/.claude/hooks/sg-scan-on-edit.sh +68 -0
  224. package/typescript/copy-overwrite/.claude/settings.json +79 -0
  225. package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +44 -0
  226. package/typescript/copy-overwrite/.github/README.md +49 -1
  227. package/typescript/copy-overwrite/.github/dependabot.yml +3 -0
  228. package/typescript/copy-overwrite/.github/workflows/ci.yml +7 -29
  229. package/typescript/copy-overwrite/.github/workflows/claude.yml +3 -0
  230. package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +6 -4
  231. package/typescript/copy-overwrite/.github/workflows/create-issue-on-failure.yml +176 -0
  232. package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +3 -1
  233. package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +3 -1
  234. package/typescript/copy-overwrite/.github/workflows/lint-slow.yml +40 -0
  235. package/typescript/copy-overwrite/.github/workflows/quality.yml +151 -38
  236. package/typescript/copy-overwrite/.github/workflows/release.yml +3 -0
  237. package/typescript/copy-overwrite/.gitleaksignore +3 -0
  238. package/typescript/copy-overwrite/.lintstagedrc.json +6 -0
  239. package/typescript/copy-overwrite/.prettierignore +2 -1
  240. package/typescript/copy-overwrite/.yamllint +2 -0
  241. package/typescript/copy-overwrite/ast-grep/rule-tests/.gitkeep +3 -0
  242. package/typescript/copy-overwrite/ast-grep/rules/.gitkeep +3 -0
  243. package/typescript/copy-overwrite/ast-grep/utils/.gitkeep +3 -0
  244. package/typescript/copy-overwrite/{commitlint.config.js → commitlint.config.cjs} +5 -0
  245. package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +5 -0
  246. package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +5 -0
  247. package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +5 -0
  248. package/typescript/copy-overwrite/eslint.base.ts +430 -0
  249. package/typescript/copy-overwrite/eslint.config.ts +52 -0
  250. package/typescript/copy-overwrite/eslint.ignore.config.json +19 -2
  251. package/typescript/copy-overwrite/eslint.slow.config.ts +69 -0
  252. package/typescript/copy-overwrite/eslint.typescript.ts +142 -0
  253. package/typescript/copy-overwrite/knip.json +64 -0
  254. package/typescript/copy-overwrite/sgconfig.yml +11 -0
  255. package/typescript/copy-overwrite/tsconfig.eslint.json +9 -0
  256. package/typescript/create-only/eslint.config.local.ts +24 -0
  257. package/typescript/{copy-overwrite/eslint.thresholds.config.json → create-only/eslint.thresholds.json} +1 -1
  258. package/typescript/github-rulesets/base.json +2 -75
  259. package/typescript/merge/.claude/settings.json +160 -0
  260. package/typescript/merge/package.json +35 -34
  261. package/all/copy-overwrite/.claude/commands/rules/format-md.md +0 -72
  262. package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +0 -416
  263. package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +0 -316
  264. package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +0 -73
  265. package/expo/copy-overwrite/eslint.config.mjs +0 -560
  266. package/lisa.sh +0 -35
  267. package/typescript/copy-overwrite/eslint.config.mjs +0 -390
  268. /package/{all/create-only/PROJECT_RULES.md → cdk/copy-overwrite/.github/workflows/.keep} +0 -0
  269. /package/{typescript → nestjs}/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +0 -0
  270. /package/{typescript → nestjs}/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +0 -0
  271. /package/{typescript → nestjs}/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +0 -0
  272. /package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/load.json +0 -0
  273. /package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/smoke.json +0 -0
  274. /package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/soak.json +0 -0
  275. /package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/spike.json +0 -0
  276. /package/{typescript → nestjs}/copy-overwrite/.github/k6/scenarios/stress.json +0 -0
  277. /package/{typescript → nestjs}/copy-overwrite/.github/k6/thresholds/normal.json +0 -0
  278. /package/{typescript → nestjs}/copy-overwrite/.github/k6/thresholds/relaxed.json +0 -0
  279. /package/{typescript → nestjs}/copy-overwrite/.github/k6/thresholds/strict.json +0 -0
  280. /package/{typescript → nestjs}/copy-overwrite/.github/workflows/k6-load-test-README.md +0 -0
package/dist/core/lisa.js CHANGED
@@ -1,10 +1,11 @@
1
- import * as fse from 'fs-extra';
2
- import { stat, readdir, rmdir } from 'node:fs/promises';
3
- import * as path from 'node:path';
4
- import pc from 'picocolors';
5
- import { COPY_STRATEGIES, createInitialCounters } from './config.js';
6
- import { listFilesRecursive } from '../utils/file-operations.js';
7
- import { DestinationNotFoundError, DestinationNotDirectoryError } from '../errors/index.js';
1
+ /* eslint-disable max-lines -- Main orchestrator class with apply/uninstall/validate operations */
2
+ import * as fse from "fs-extra";
3
+ import { readdir, rmdir, stat } from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import pc from "picocolors";
6
+ import { DestinationNotDirectoryError, DestinationNotFoundError, UserAbortedError, } from "../errors/index.js";
7
+ import { listFilesRecursive } from "../utils/file-operations.js";
8
+ import { COPY_STRATEGIES, createInitialCounters } from "./config.js";
8
9
  /**
9
10
  * Main Lisa orchestrator
10
11
  */
@@ -13,78 +14,208 @@ export class Lisa {
13
14
  deps;
14
15
  counters = createInitialCounters();
15
16
  detectedTypes = [];
17
+ separator = "========================================";
18
+ dryRunPrefix = "Would copy:";
19
+ dryRunSkipMsg = "Would skip:";
20
+ dryRunPromptMsg = "Would prompt:";
21
+ dryRunAppendMsg = "Would append:";
22
+ dryRunMergeMsg = "Would merge:";
23
+ copyMsg = "Copied:";
24
+ skipMsg = "Skipped:";
25
+ promptMsg = "Overwritten:";
26
+ appendMsg = "Appended:";
27
+ mergeMsg = "Merged:";
28
+ /**
29
+ * Initialize Lisa orchestrator
30
+ * @param config - Configuration for the apply/uninstall operation
31
+ * @param deps - Injected service dependencies
32
+ */
16
33
  constructor(config, deps) {
17
34
  this.config = config;
18
35
  this.deps = deps;
19
36
  }
37
+ /**
38
+ * Initialize services
39
+ */
40
+ async initServices() {
41
+ const { backupService, manifestService } = this.deps;
42
+ if (!this.config.dryRun) {
43
+ await backupService.init(this.config.destDir);
44
+ await manifestService.init(this.config.destDir, this.config.lisaDir);
45
+ }
46
+ }
47
+ /**
48
+ * Detect and confirm project types
49
+ */
50
+ async detectTypes() {
51
+ const { detectorRegistry, prompter } = this.deps;
52
+ const rawTypes = await detectorRegistry.detectAll(this.config.destDir);
53
+ this.detectedTypes = detectorRegistry.expandAndOrderTypes(rawTypes);
54
+ this.detectedTypes = [
55
+ ...(await prompter.confirmProjectTypes(this.detectedTypes)),
56
+ ];
57
+ }
58
+ /**
59
+ * Process all configurations
60
+ */
61
+ async processConfigurations() {
62
+ const { logger } = this.deps;
63
+ logger.info("Processing common configurations (all/)...");
64
+ await this.processProjectType("all");
65
+ for (const type of this.detectedTypes) {
66
+ const typeDir = path.join(this.config.lisaDir, type);
67
+ if (await fse.pathExists(typeDir)) {
68
+ logger.info(`Processing ${type} configurations...`);
69
+ await this.processProjectType(type);
70
+ }
71
+ else {
72
+ logger.warn(`No configuration directory found for type: ${type}`);
73
+ }
74
+ }
75
+ }
76
+ /**
77
+ * Process deletions from deletions.json files
78
+ */
79
+ async processDeletions() {
80
+ const { logger } = this.deps;
81
+ // Process deletions for "all" and each detected type
82
+ const typesToProcess = ["all", ...this.detectedTypes];
83
+ for (const type of typesToProcess) {
84
+ const deletionsPath = path.join(this.config.lisaDir, type, "deletions.json");
85
+ if (await fse.pathExists(deletionsPath)) {
86
+ logger.info(`Processing deletions for ${type}...`);
87
+ await this.processDeletionsFile(deletionsPath);
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Process a single deletions.json file
93
+ * @param deletionsPath - Path to the deletions.json file
94
+ */
95
+ async processDeletionsFile(deletionsPath) {
96
+ const { logger } = this.deps;
97
+ try {
98
+ const content = await fse.readFile(deletionsPath, "utf-8");
99
+ const deletions = JSON.parse(content);
100
+ if (!Array.isArray(deletions.paths)) {
101
+ logger.warn(`Invalid deletions.json format: paths must be an array`);
102
+ return;
103
+ }
104
+ for (const relativePath of deletions.paths) {
105
+ await this.processSingleDeletion(relativePath);
106
+ }
107
+ }
108
+ catch (error) {
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ logger.warn(`Failed to process deletions file: ${message}`);
111
+ }
112
+ }
113
+ /**
114
+ * Process a single deletion path
115
+ * @param relativePath - Relative path to delete
116
+ */
117
+ async processSingleDeletion(relativePath) {
118
+ const { logger } = this.deps;
119
+ const targetPath = path.join(this.config.destDir, relativePath);
120
+ const resolvedTarget = path.resolve(targetPath);
121
+ const resolvedDest = path.resolve(this.config.destDir);
122
+ // Explicit guard: disallow deleting the project root itself
123
+ if (resolvedTarget === resolvedDest) {
124
+ logger.warn(`Skipping deletion of project root directory: ${relativePath || "."}`);
125
+ return;
126
+ }
127
+ // Safety check: only allow paths strictly inside destDir
128
+ if (!resolvedTarget.startsWith(resolvedDest + path.sep)) {
129
+ logger.warn(`Skipping deletion outside project directory: ${relativePath}`);
130
+ return;
131
+ }
132
+ if (!(await fse.pathExists(targetPath))) {
133
+ return;
134
+ }
135
+ if (this.config.dryRun) {
136
+ logger.dry(`Would delete: ${relativePath}`);
137
+ this.counters.deleted++;
138
+ }
139
+ else {
140
+ await fse.remove(targetPath);
141
+ logger.success(`Deleted: ${relativePath}`);
142
+ this.counters.deleted++;
143
+ }
144
+ }
145
+ /**
146
+ * Finalize operation
147
+ * @returns Promise that resolves when finalization is complete
148
+ */
149
+ async finalize() {
150
+ const { manifestService, backupService } = this.deps;
151
+ if (!this.config.dryRun) {
152
+ await manifestService.finalize();
153
+ await backupService.cleanup();
154
+ }
155
+ }
156
+ /**
157
+ * Create success result
158
+ * @returns Success result with operation counters and detected types
159
+ */
160
+ getSuccessResult() {
161
+ const mode = this.config.validateOnly ? "validate" : "apply";
162
+ return {
163
+ success: true,
164
+ counters: { ...this.counters },
165
+ detectedTypes: [...this.detectedTypes],
166
+ mode,
167
+ errors: [],
168
+ };
169
+ }
170
+ /**
171
+ * Handle apply error and rollback
172
+ * @param error Error that occurred during apply
173
+ * @returns Error result
174
+ */
175
+ async handleApplyError(error) {
176
+ const { backupService } = this.deps;
177
+ if (!this.config.dryRun) {
178
+ try {
179
+ await backupService.rollback();
180
+ }
181
+ catch {
182
+ // Rollback error already logged
183
+ }
184
+ }
185
+ const mode = this.config.validateOnly ? "validate" : "apply";
186
+ const message = error instanceof Error ? error.message : String(error);
187
+ return {
188
+ success: false,
189
+ counters: { ...this.counters },
190
+ detectedTypes: [...this.detectedTypes],
191
+ mode,
192
+ errors: [message],
193
+ };
194
+ }
20
195
  /**
21
196
  * Apply Lisa configurations to the destination project
197
+ * @returns Result of the apply operation
22
198
  */
23
199
  async apply() {
24
- const { logger, prompter, manifestService, backupService, detectorRegistry } = this.deps;
25
200
  try {
26
- // Validate destination
27
201
  await this.validateDestination();
28
- // Print header
202
+ await this.validateGitState();
29
203
  this.printHeader();
30
- // Initialize services
31
- if (!this.config.dryRun) {
32
- await backupService.init(this.config.destDir);
33
- await manifestService.init(this.config.destDir, this.config.lisaDir);
34
- }
35
- // Detect project types
36
- const rawTypes = await detectorRegistry.detectAll(this.config.destDir);
37
- this.detectedTypes = detectorRegistry.expandAndOrderTypes(rawTypes);
38
- // Confirm with user
39
- this.detectedTypes = [...(await prompter.confirmProjectTypes(this.detectedTypes))];
40
- // Process 'all' directory first
41
- logger.info('Processing common configurations (all/)...');
42
- await this.processProjectType('all');
43
- // Process each detected type
44
- for (const type of this.detectedTypes) {
45
- const typeDir = path.join(this.config.lisaDir, type);
46
- if (await fse.pathExists(typeDir)) {
47
- logger.info(`Processing ${type} configurations...`);
48
- await this.processProjectType(type);
49
- }
50
- else {
51
- logger.warn(`No configuration directory found for type: ${type}`);
52
- }
53
- }
54
- // Finalize manifest
55
- if (!this.config.dryRun) {
56
- await manifestService.finalize();
57
- await backupService.cleanup();
58
- }
204
+ await this.initServices();
205
+ await this.detectTypes();
206
+ await this.processConfigurations();
207
+ await this.processDeletions();
208
+ await this.finalize();
59
209
  this.printSummary();
60
- return {
61
- success: true,
62
- counters: { ...this.counters },
63
- detectedTypes: [...this.detectedTypes],
64
- mode: this.config.validateOnly ? 'validate' : 'apply',
65
- errors: [],
66
- };
210
+ return this.getSuccessResult();
67
211
  }
68
212
  catch (error) {
69
- if (!this.config.dryRun) {
70
- try {
71
- await backupService.rollback();
72
- }
73
- catch {
74
- // Rollback error already logged
75
- }
76
- }
77
- return {
78
- success: false,
79
- counters: { ...this.counters },
80
- detectedTypes: [...this.detectedTypes],
81
- mode: this.config.validateOnly ? 'validate' : 'apply',
82
- errors: [error instanceof Error ? error.message : String(error)],
83
- };
213
+ return this.handleApplyError(error);
84
214
  }
85
215
  }
86
216
  /**
87
217
  * Validate compatibility without applying changes
218
+ * @returns Result of the validate operation
88
219
  */
89
220
  async validate() {
90
221
  // Validate mode is essentially a dry run
@@ -92,80 +223,52 @@ export class Lisa {
92
223
  }
93
224
  /**
94
225
  * Uninstall Lisa-managed files from the project
226
+ * @returns Result of the uninstall operation with removal statistics
95
227
  */
96
228
  async uninstall() {
97
229
  const { logger, manifestService } = this.deps;
98
230
  // Validate destination
99
231
  await this.validateDestination();
100
232
  // Print header
101
- console.log('');
102
- console.log(pc.blue('========================================'));
103
- console.log(pc.blue(' Lisa Uninstaller'));
104
- console.log(pc.blue('========================================'));
105
- console.log('');
106
- let removed = 0;
107
- let skipped = 0;
233
+ console.log("");
234
+ console.log(pc.blue(this.separator));
235
+ console.log(pc.blue(" Lisa Uninstaller"));
236
+ console.log(pc.blue(this.separator));
237
+ console.log("");
108
238
  const errors = [];
109
239
  try {
110
240
  // Read manifest
111
241
  const entries = await manifestService.read(this.config.destDir);
112
- logger.info(`Reading manifest: ${path.join(this.config.destDir, '.lisa-manifest')}`);
113
- console.log('');
114
- // Process each entry
115
- for (const entry of entries) {
116
- const destFile = path.join(this.config.destDir, entry.relativePath);
117
- switch (entry.strategy) {
118
- case 'copy-overwrite':
119
- case 'create-only':
120
- // These files can be safely removed
121
- if (await fse.pathExists(destFile)) {
122
- if (this.config.dryRun) {
123
- logger.dry(`Would remove: ${entry.relativePath}`);
124
- }
125
- else {
126
- await fse.remove(destFile);
127
- logger.success(`Removed: ${entry.relativePath}`);
128
- }
129
- removed++;
130
- }
131
- else {
132
- skipped++;
133
- }
134
- break;
135
- case 'copy-contents':
136
- // Cannot safely remove - would need to diff
137
- logger.warn(`Cannot auto-remove (copy-contents): ${entry.relativePath}`);
138
- logger.info(' Manually review and remove added lines if needed.');
139
- skipped++;
140
- break;
141
- case 'merge':
142
- // Cannot safely remove merged JSON
143
- logger.warn(`Cannot auto-remove (merged JSON): ${entry.relativePath}`);
144
- logger.info(' Manually remove Lisa-added keys if needed.');
145
- skipped++;
146
- break;
147
- }
148
- }
242
+ logger.info(`Reading manifest: ${path.join(this.config.destDir, ".lisa-manifest")}`);
243
+ console.log("");
244
+ // Process entries
245
+ const stats = await this.processUninstallEntries(entries);
149
246
  // Remove empty directories and manifest
150
247
  if (!this.config.dryRun) {
151
248
  await this.removeEmptyDirectories();
152
249
  await manifestService.remove(this.config.destDir);
153
- logger.success('Removed manifest file');
250
+ logger.success("Removed manifest file");
154
251
  }
155
252
  // Print summary
156
- console.log('');
157
- console.log(pc.green('========================================'));
158
- console.log(pc.green(this.config.dryRun ? ' Lisa Uninstall Dry Run Complete' : ' Lisa Uninstall Complete!'));
159
- console.log(pc.green('========================================'));
160
- console.log('');
161
- console.log(` ${pc.green('Removed:')} ${String(removed).padStart(3)} files`);
162
- console.log(` ${pc.yellow('Skipped:')} ${String(skipped).padStart(3)} files (manual review needed)`);
163
- console.log('');
253
+ console.log("");
254
+ console.log(pc.green(this.separator));
255
+ console.log(pc.green(this.config.dryRun
256
+ ? " Lisa Uninstall Dry Run Complete"
257
+ : " Lisa Uninstall Complete!"));
258
+ console.log(pc.green(this.separator));
259
+ console.log("");
260
+ console.log(` ${pc.green("Removed:")} ${String(stats.removed).padStart(3)} files`);
261
+ console.log(` ${pc.yellow("Skipped:")} ${String(stats.skipped).padStart(3)} files (manual review needed)`);
262
+ console.log("");
164
263
  return {
165
264
  success: true,
166
- counters: { ...this.counters, copied: removed, skipped },
265
+ counters: {
266
+ ...this.counters,
267
+ copied: stats.removed,
268
+ skipped: stats.skipped,
269
+ },
167
270
  detectedTypes: [],
168
- mode: 'uninstall',
271
+ mode: "uninstall",
169
272
  errors,
170
273
  };
171
274
  }
@@ -177,13 +280,64 @@ export class Lisa {
177
280
  success: false,
178
281
  counters: this.counters,
179
282
  detectedTypes: [],
180
- mode: 'uninstall',
283
+ mode: "uninstall",
181
284
  errors,
182
285
  };
183
286
  }
184
287
  }
288
+ /**
289
+ * Process single uninstall entry and return updated counts
290
+ * @param entry - Manifest entry to process for removal
291
+ * @param stats - Current removal statistics
292
+ * @param stats.removed - Number of files removed so far
293
+ * @param stats.skipped - Number of files skipped so far
294
+ * @returns Updated removal statistics after processing this entry
295
+ */
296
+ async processEntry(entry, stats) {
297
+ const { logger } = this.deps;
298
+ const destFile = path.join(this.config.destDir, entry.relativePath);
299
+ switch (entry.strategy) {
300
+ case "copy-overwrite":
301
+ case "create-only": {
302
+ if (await fse.pathExists(destFile)) {
303
+ if (this.config.dryRun) {
304
+ logger.dry(`Would remove: ${entry.relativePath}`);
305
+ }
306
+ else {
307
+ await fse.remove(destFile);
308
+ logger.success(`Removed: ${entry.relativePath}`);
309
+ }
310
+ return { ...stats, removed: stats.removed + 1 };
311
+ }
312
+ return { ...stats, skipped: stats.skipped + 1 };
313
+ }
314
+ case "copy-contents": {
315
+ logger.warn(`Cannot auto-remove (copy-contents): ${entry.relativePath}`);
316
+ logger.info(" Manually review and remove added lines if needed.");
317
+ return { ...stats, skipped: stats.skipped + 1 };
318
+ }
319
+ case "merge": {
320
+ logger.warn(`Cannot auto-remove (merged JSON): ${entry.relativePath}`);
321
+ logger.info(" Manually remove Lisa-added keys if needed.");
322
+ return { ...stats, skipped: stats.skipped + 1 };
323
+ }
324
+ }
325
+ }
326
+ /**
327
+ * Process manifest entries during uninstall
328
+ * @param entries Manifest entries to process
329
+ * @returns Stats with removed and skipped counts
330
+ */
331
+ async processUninstallEntries(entries) {
332
+ const initial = { removed: 0, skipped: 0 };
333
+ return await entries.reduce(async (statsPromise, entry) => {
334
+ const stats = await statsPromise;
335
+ return this.processEntry(entry, stats);
336
+ }, Promise.resolve(initial));
337
+ }
185
338
  /**
186
339
  * Process all files for a given project type
340
+ * @param type Project type to process
187
341
  */
188
342
  async processProjectType(type) {
189
343
  const { logger, strategyRegistry } = this.deps;
@@ -197,6 +351,10 @@ export class Lisa {
197
351
  }
198
352
  /**
199
353
  * Process all files in a directory with the given strategy
354
+ * @param srcDir Source directory
355
+ * @param strategy Strategy to apply
356
+ * @param strategy.name Strategy name
357
+ * @param strategy.apply Apply function
200
358
  */
201
359
  async processDirectory(srcDir, strategy) {
202
360
  const files = await listFilesRecursive(srcDir);
@@ -207,6 +365,7 @@ export class Lisa {
207
365
  },
208
366
  backupFile: async (absolutePath) => {
209
367
  await this.deps.backupService.backup(absolutePath);
368
+ await this.deps.backupService.persistentBackup(absolutePath);
210
369
  },
211
370
  promptOverwrite: async (relativePath, sourcePath, destPath) => {
212
371
  return this.handleOverwritePrompt(relativePath, sourcePath, destPath);
@@ -222,16 +381,20 @@ export class Lisa {
222
381
  }
223
382
  /**
224
383
  * Handle overwrite prompt for conflicting files
384
+ * @param relativePath Relative path of the file
385
+ * @param sourcePath Source file path
386
+ * @param destPath Destination file path
387
+ * @returns True if user approves overwrite
225
388
  */
226
389
  async handleOverwritePrompt(relativePath, sourcePath, destPath) {
227
390
  const { logger, prompter } = this.deps;
228
391
  const decision = await prompter.promptOverwrite(relativePath);
229
- if (decision === 'diff') {
392
+ if (decision === "diff") {
230
393
  // Show diff and re-prompt
231
394
  await this.showDiff(sourcePath, destPath);
232
395
  return this.handleOverwritePrompt(relativePath, sourcePath, destPath);
233
396
  }
234
- if (decision === 'yes') {
397
+ if (decision === "yes") {
235
398
  logger.info(`Auto-accepting overwrite (non-interactive): ${relativePath}`);
236
399
  return true;
237
400
  }
@@ -239,89 +402,78 @@ export class Lisa {
239
402
  }
240
403
  /**
241
404
  * Show diff between two files
405
+ * @param sourcePath Source file path
406
+ * @param destPath Destination file path
242
407
  */
243
408
  async showDiff(sourcePath, destPath) {
244
- const { spawn } = await import('node:child_process');
245
- return new Promise((resolve) => {
246
- console.log('--- Differences ---');
247
- const diff = spawn('diff', [destPath, sourcePath], { stdio: 'inherit' });
248
- diff.on('close', () => {
249
- console.log('-------------------');
409
+ const { spawn } = await import("node:child_process");
410
+ return new Promise(resolve => {
411
+ console.log("--- Differences ---");
412
+ const diff = spawn("diff", [destPath, sourcePath], { stdio: "inherit" });
413
+ diff.on("close", () => {
414
+ console.log("-------------------");
250
415
  resolve();
251
416
  });
252
417
  });
253
418
  }
254
419
  /**
255
420
  * Update counters based on operation result
421
+ * @param result Operation result to count
256
422
  */
257
423
  updateCounters(result) {
258
424
  switch (result.action) {
259
- case 'copied':
260
- case 'created':
425
+ case "copied":
426
+ case "created":
261
427
  this.counters.copied++;
262
428
  break;
263
- case 'skipped':
429
+ case "skipped":
264
430
  this.counters.skipped++;
265
431
  break;
266
- case 'overwritten':
432
+ case "overwritten":
267
433
  this.counters.overwritten++;
268
434
  break;
269
- case 'appended':
435
+ case "appended":
270
436
  this.counters.appended++;
271
437
  break;
272
- case 'merged':
438
+ case "merged":
273
439
  this.counters.merged++;
274
440
  break;
275
441
  }
276
442
  }
443
+ /**
444
+ * Log a message using appropriate logger method
445
+ * @param dryMsg Message for dry run mode
446
+ * @param successMsg Message for success mode
447
+ */
448
+ logMessage(dryMsg, successMsg) {
449
+ const { logger } = this.deps;
450
+ this.config.dryRun ? logger.dry(dryMsg) : logger.success(successMsg);
451
+ }
277
452
  /**
278
453
  * Log operation result
454
+ * @param result Result to log
279
455
  */
280
456
  logResult(result) {
281
- const { logger } = this.deps;
282
457
  switch (result.action) {
283
- case 'copied':
284
- if (this.config.dryRun) {
285
- logger.dry(`Would copy: ${result.relativePath}`);
286
- }
287
- else {
288
- logger.success(`Copied: ${result.relativePath}`);
289
- }
458
+ case "copied":
459
+ this.logMessage(`Would copy: ${result.relativePath}`, `Copied: ${result.relativePath}`);
290
460
  break;
291
- case 'created':
292
- if (this.config.dryRun) {
293
- logger.dry(`Would create: ${result.relativePath}`);
294
- }
295
- else {
296
- logger.success(`Created: ${result.relativePath}`);
297
- }
461
+ case "created":
462
+ this.logMessage(`Would create: ${result.relativePath}`, `Created: ${result.relativePath}`);
298
463
  break;
299
- case 'skipped':
464
+ case "skipped":
300
465
  // Silent for skipped files
301
466
  break;
302
- case 'overwritten':
303
- if (this.config.dryRun) {
304
- logger.dry(`Would prompt to overwrite: ${result.relativePath}`);
305
- }
306
- else {
307
- logger.success(`Overwritten: ${result.relativePath}`);
308
- }
467
+ case "overwritten":
468
+ this.logMessage(`Would prompt to overwrite: ${result.relativePath}`, `Overwritten: ${result.relativePath}`);
309
469
  break;
310
- case 'appended':
311
- if (this.config.dryRun) {
312
- logger.dry(`Would append ${result.linesAdded} lines to: ${result.relativePath}`);
313
- }
314
- else {
315
- logger.success(`Appended ${result.linesAdded} lines to: ${result.relativePath}`);
316
- }
470
+ case "appended": {
471
+ const msg = `Appended ${result.linesAdded} lines to: ${result.relativePath}`;
472
+ this.logMessage(`Would ${msg}`, msg);
317
473
  break;
318
- case 'merged':
319
- if (this.config.dryRun) {
320
- logger.dry(`Would merge: ${result.relativePath}`);
321
- }
322
- else {
323
- logger.success(`Merged: ${result.relativePath}`);
324
- }
474
+ }
475
+ case "merged":
476
+ this.logMessage(`Would merge: ${result.relativePath}`, `Merged: ${result.relativePath}`);
325
477
  break;
326
478
  }
327
479
  }
@@ -338,93 +490,140 @@ export class Lisa {
338
490
  throw new DestinationNotDirectoryError(destDir);
339
491
  }
340
492
  }
493
+ /**
494
+ * Check for uncommitted git changes and prompt user if dirty
495
+ */
496
+ async validateGitState() {
497
+ const { gitService, prompter, logger } = this.deps;
498
+ const { destDir } = this.config;
499
+ // Skip git check if not a git repository
500
+ if (!(await gitService.isRepository(destDir))) {
501
+ return;
502
+ }
503
+ // Check for uncommitted changes
504
+ if (!(await gitService.isDirty(destDir))) {
505
+ return;
506
+ }
507
+ const status = await gitService.getStatus(destDir);
508
+ logger.warn("Git working directory has uncommitted changes");
509
+ // Always prompt for dirty git, even in yesMode
510
+ const proceed = await prompter.confirmDirtyGit(status);
511
+ if (!proceed) {
512
+ throw new UserAbortedError("Aborted: please commit or stash your changes before running Lisa");
513
+ }
514
+ }
341
515
  /**
342
516
  * Print header banner
343
517
  */
344
518
  printHeader() {
345
519
  const { logger } = this.deps;
346
- console.log('');
347
- console.log(pc.blue('========================================'));
520
+ console.log("");
521
+ console.log(pc.blue(this.separator));
348
522
  if (this.config.validateOnly) {
349
- console.log(pc.blue(' Lisa Project Bootstrapper (VALIDATE)'));
523
+ console.log(pc.blue(" Lisa Project Bootstrapper (VALIDATE)"));
350
524
  }
351
525
  else if (this.config.dryRun) {
352
- console.log(pc.blue(' Lisa Project Bootstrapper (DRY RUN)'));
526
+ console.log(pc.blue(" Lisa Project Bootstrapper (DRY RUN)"));
353
527
  }
354
528
  else {
355
- console.log(pc.blue(' Lisa Project Bootstrapper'));
529
+ console.log(pc.blue(" Lisa Project Bootstrapper"));
356
530
  }
357
- console.log(pc.blue('========================================'));
358
- console.log('');
531
+ console.log(pc.blue(this.separator));
532
+ console.log("");
359
533
  if (this.config.validateOnly) {
360
- logger.info('Validate mode - checking project compatibility');
534
+ logger.info("Validate mode - checking project compatibility");
361
535
  }
362
536
  else if (this.config.dryRun) {
363
- logger.warn('Dry run mode - no changes will be made');
537
+ logger.warn("Dry run mode - no changes will be made");
364
538
  }
365
539
  logger.info(`Lisa directory: ${this.config.lisaDir}`);
366
540
  logger.info(`Destination: ${this.config.destDir}`);
367
- console.log('');
541
+ console.log("");
368
542
  }
369
543
  /**
370
- * Print summary
544
+ * Print summary header with mode
371
545
  */
372
- printSummary() {
373
- console.log('');
374
- console.log(pc.green('========================================'));
546
+ printSummaryHeader() {
547
+ console.log("");
548
+ console.log(pc.green(this.separator));
375
549
  if (this.config.validateOnly) {
376
- console.log(pc.green(' Lisa Validation Complete'));
550
+ console.log(pc.green(" Lisa Validation Complete"));
377
551
  }
378
552
  else if (this.config.dryRun) {
379
- console.log(pc.green(' Lisa Dry Run Complete'));
553
+ console.log(pc.green(" Lisa Dry Run Complete"));
380
554
  }
381
555
  else {
382
- console.log(pc.green(' Lisa Installation Complete!'));
556
+ console.log(pc.green(" Lisa Installation Complete!"));
383
557
  }
384
- console.log(pc.green('========================================'));
385
- console.log('');
386
- const { copied, skipped, overwritten, appended, merged } = this.counters;
558
+ console.log(pc.green(this.separator));
559
+ console.log("");
560
+ }
561
+ /**
562
+ * Print summary statistics
563
+ */
564
+ printSummaryStats() {
565
+ const { copied, skipped, overwritten, appended, merged, deleted } = this.counters;
387
566
  if (this.config.validateOnly) {
388
- console.log(` ${pc.green('Compatible files:')} ${String(copied).padStart(3)} files`);
389
- console.log(` ${pc.blue('Already present:')} ${String(skipped).padStart(3)} files`);
390
- console.log(` ${pc.yellow('Would conflict:')} ${String(overwritten).padStart(3)} files`);
391
- console.log(` ${pc.blue('Would append:')} ${String(appended).padStart(3)} files`);
392
- console.log(` ${pc.green('Would merge:')} ${String(merged).padStart(3)} files`);
567
+ console.log(` ${pc.green("Compatible files:")} ${String(copied).padStart(3)} files`);
568
+ console.log(` ${pc.blue("Already present:")} ${String(skipped).padStart(3)} files`);
569
+ console.log(` ${pc.yellow("Would conflict:")} ${String(overwritten).padStart(3)} files`);
570
+ console.log(` ${pc.blue("Would append:")} ${String(appended).padStart(3)} files`);
571
+ console.log(` ${pc.green("Would merge:")} ${String(merged).padStart(3)} files`);
572
+ console.log(` ${pc.red("Would delete:")} ${String(deleted).padStart(3)} files`);
393
573
  }
394
574
  else if (this.config.dryRun) {
395
- console.log(` ${pc.green('Would copy:')} ${String(copied).padStart(3)} files`);
396
- console.log(` ${pc.blue('Would skip:')} ${String(skipped).padStart(3)} files (identical or create-only)`);
397
- console.log(` ${pc.yellow('Would prompt:')} ${String(overwritten).padStart(3)} files (differ)`);
398
- console.log(` ${pc.blue('Would append:')} ${String(appended).padStart(3)} files (copy-contents)`);
399
- console.log(` ${pc.green('Would merge:')} ${String(merged).padStart(3)} files (JSON)`);
575
+ console.log(` ${pc.green(this.dryRunPrefix)} ${String(copied).padStart(3)} files`);
576
+ console.log(` ${pc.blue(this.dryRunSkipMsg)} ${String(skipped).padStart(3)} files (identical or create-only)`);
577
+ console.log(` ${pc.yellow(this.dryRunPromptMsg)} ${String(overwritten).padStart(3)} files (differ)`);
578
+ console.log(` ${pc.blue(this.dryRunAppendMsg)} ${String(appended).padStart(3)} files (copy-contents)`);
579
+ console.log(` ${pc.green(this.dryRunMergeMsg)} ${String(merged).padStart(3)} files (JSON)`);
580
+ console.log(` ${pc.red("Would delete:")} ${String(deleted).padStart(3)} files`);
400
581
  }
401
582
  else {
402
- console.log(` ${pc.green('Copied:')} ${String(copied).padStart(3)} files`);
403
- console.log(` ${pc.blue('Skipped:')} ${String(skipped).padStart(3)} files (identical or create-only)`);
404
- console.log(` ${pc.yellow('Overwritten:')} ${String(overwritten).padStart(3)} files (user approved)`);
405
- console.log(` ${pc.blue('Appended:')} ${String(appended).padStart(3)} files (copy-contents)`);
406
- console.log(` ${pc.green('Merged:')} ${String(merged).padStart(3)} files (JSON merged)`);
583
+ console.log(` ${pc.green(this.copyMsg)} ${String(copied).padStart(3)} files`);
584
+ console.log(` ${pc.blue(this.skipMsg)} ${String(skipped).padStart(3)} files (identical or create-only)`);
585
+ console.log(` ${pc.yellow(this.promptMsg)} ${String(overwritten).padStart(3)} files (user approved)`);
586
+ console.log(` ${pc.blue(this.appendMsg)} ${String(appended).padStart(3)} files (copy-contents)`);
587
+ console.log(` ${pc.green(this.mergeMsg)} ${String(merged).padStart(3)} files (JSON merged)`);
588
+ console.log(` ${pc.red("Deleted:")} ${String(deleted).padStart(3)} files`);
407
589
  }
408
- console.log('');
409
- if (this.detectedTypes.length > 0) {
410
- console.log(`Project types: ${pc.green(this.detectedTypes.join(' '))}`);
590
+ }
591
+ /**
592
+ * Print project types
593
+ */
594
+ printProjectTypes() {
595
+ console.log("");
596
+ const allAndDetected = this.detectedTypes.length > 0
597
+ ? `all ${this.detectedTypes.join(" ")}`
598
+ : "all";
599
+ console.log(`Project types: ${pc.green(allAndDetected)}`);
600
+ console.log("");
601
+ }
602
+ /**
603
+ * Print validation result
604
+ */
605
+ printValidationResult() {
606
+ if (!this.config.validateOnly)
607
+ return;
608
+ const { logger } = this.deps;
609
+ const { overwritten } = this.counters;
610
+ if (overwritten > 0) {
611
+ logger.warn(`Validation found ${overwritten} file(s) that would conflict`);
612
+ console.log("Run without --validate to apply changes interactively");
411
613
  }
412
614
  else {
413
- console.log(`Project types: ${pc.yellow("(none detected - only 'all' was applied)")}`);
414
- }
415
- console.log('');
416
- // Validation result for CI/CD
417
- if (this.config.validateOnly) {
418
- const { logger } = this.deps;
419
- if (overwritten > 0) {
420
- logger.warn(`Validation found ${overwritten} file(s) that would conflict`);
421
- console.log('Run without --validate to apply changes interactively');
422
- }
423
- else {
424
- logger.success('Project is compatible with Lisa configurations');
425
- }
615
+ logger.success("Project is compatible with Lisa configurations");
426
616
  }
427
617
  }
618
+ /**
619
+ * Print summary
620
+ */
621
+ printSummary() {
622
+ this.printSummaryHeader();
623
+ this.printSummaryStats();
624
+ this.printProjectTypes();
625
+ this.printValidationResult();
626
+ }
428
627
  /**
429
628
  * Remove empty directories after uninstall
430
629
  */
@@ -433,7 +632,7 @@ export class Lisa {
433
632
  const removeEmpty = async (dir) => {
434
633
  if (dir === destDir)
435
634
  return;
436
- if (dir.includes('.git') || dir.includes('node_modules'))
635
+ if (dir.includes(".git") || dir.includes("node_modules"))
437
636
  return;
438
637
  try {
439
638
  const entries = await readdir(dir);
@@ -450,10 +649,13 @@ export class Lisa {
450
649
  // Get all directories and check if empty
451
650
  const entries = await readdir(destDir, { withFileTypes: true });
452
651
  for (const entry of entries) {
453
- if (entry.isDirectory() && !entry.name.startsWith('.git') && entry.name !== 'node_modules') {
652
+ if (entry.isDirectory() &&
653
+ !entry.name.startsWith(".git") &&
654
+ entry.name !== "node_modules") {
454
655
  await removeEmpty(path.join(destDir, entry.name));
455
656
  }
456
657
  }
457
658
  }
458
659
  }
660
+ /* eslint-enable max-lines -- Main orchestrator class with apply/uninstall/validate operations */
459
661
  //# sourceMappingURL=lisa.js.map