Skip to content

Comments

Add module removal report and remove-module command#171

Merged
yogthos merged 2 commits intokit-clj:masterfrom
kovan:module-removal-report
Feb 10, 2026
Merged

Add module removal report and remove-module command#171
yogthos merged 2 commits intokit-clj:masterfrom
kovan:module-removal-report

Conversation

@kovan
Copy link
Contributor

@kovan kovan commented Feb 8, 2026

Summary

Implements the first phase of module removal (closes #110) following maintainer guidance from the issue discussion:

  • Enhanced install-log.edn: install-module now persists a detailed manifest per module — assets with SHA-256 checksums, injection descriptions, feature flags, and timestamps
  • removal-report / print-removal-report: Generates human-readable removal instructions. For files created by the module, compares current SHA-256 against the stored hash to determine if auto-deletion is safe. For code injections, prints what to manually remove
  • remove-module: Auto-deletes unchanged files, prints manual steps for injections, checks reverse dependencies (with :force? and :dry? options)
  • Backward compatible: Old install-log.edn entries (bare :success/:failed keywords) continue to work; removal reports degrade gracefully without SHA comparison

Example usage

;; See what removal would involve
(kit/print-removal-report :kit/html)

;; Dry run (no changes)
(kit/remove-module :kit/html {:dry? true})

;; Actually remove
(kit/remove-module :kit/html)

;; Force remove even if other modules depend on it
(kit/remove-module :kit/html {:force? true})

Files changed

File Change
modules_log.clj SHA-256 hashing, manifest building, backward-compat normalization, record-installation, module-manifest, untrack-module
api.clj removal-report, print-removal-report, remove-module; refactored install-module to persist manifests
dependencies.clj immediate-dependents for reverse dependency checking
generator.clj slurp-bytes helper
project.clj, generator_test.clj Backward-compat fix for test module-installed? helpers
removal_test.clj New — 9 test cases

Test plan

  • All 18 existing + new tests pass (79 assertions, 0 failures, 0 errors)
  • install-module creates detailed manifest in install-log.edn
  • Backward compat with old bare-keyword install-log entries
  • Removal report correctly classifies unchanged vs modified files
  • remove-module auto-deletes unchanged files and cleans install log
  • Dependency check blocks removal when dependents exist
  • :force? overrides dependency check
  • :dry? prints report without making changes
  • Removing nonexistent module prints appropriate error

🤖 Generated with Claude Code

Implements the first phase of module removal (issue kit-clj#110) following
maintainer guidance: persist installation manifests, generate human-readable
removal reports, and auto-delete unchanged files while printing manual
instructions for code injections.

Key changes:
- Enhanced install-log.edn to store detailed manifests (assets with SHA-256,
  injection descriptions, feature flags, timestamps)
- Added removal-report, print-removal-report, and remove-module to public API
- Added dependency checking to prevent removing modules that others depend on
- Backward compatible with existing install-log.edn entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yogthos
Copy link
Collaborator

yogthos commented Feb 10, 2026

There are a few minor issues that would be good to address here.

There's a breaking change in installed-modules return type modules_log.clj. The existing installed-modules returns (keys ...) which is a seq of module keys. The PR changes it to return a map of {key -> entry}. However, installed-modules is referred into api.clj and used in the current code. While the PR removes its usage from list-installed-modules, it's still a public function and the return type change is a breaking API change. That's not a deal breaker, but if we could avoid breaking changes that would be best.

The PR keeps installed-modules in the :refer list of api.clj (line 10-11 in the new code) but no longer calls it anywhere in api.clj. This is a dead import.

There's a duplicate slurp-bytes definition in generator.clj and modules_log.clj. Both do the same thing: (Files/readAllBytes (.toPath (jio/file path))). The one in generator.clj also appears unused.

The PR marks track-installation as deprecated in the docstring and stops using it in api.clj, replacing it with build-installation-manifest + record-installation. However, it's still referred in the old code and it remains a macro that could be called by external consumers. Not necessarily wrong, but worth noting for eventual cleanup.

I notice that build-installation-manifest is called after generator/generate writes assets. It then reads the files back from disk to compute SHA-256 hashes. This means the hash reflects what's on disk, which is correct for integrity checking. However, there's a subtle TOCTOU issue here if generate writes a file and then something modifies it before build-installation-manifest runs, the hash won't match the original generated content. In practice this is very unlikely but worth documenting.

The remove-module calls (jio/delete-file (jio/file f)) for each safe file. If a file is classified as :safe in the report but gets deleted between the report and the removal, jio/delete-file will throw a FileNotFoundException. Using (jio/delete-file f true) (the 2-arity "silently" variant) would be safer.

The removal-report swallows dependency resolution exceptions with (catch Exception _ #{}). Silently catching all exceptions during dependency resolution could hide real errors (e.g., corrupted module configs). I'd log a warning here.

The PR adds opts to the destructuring: {:keys [ctx pending-modules installed-modules opts]}. However, installation-plan (line 94-102) already returns :opts in its result map, so this is correct. The issue is that the outer opts parameter shadows the destructured opts, which was intentional (to get the flat version), but could be confusing.

Overall this is solid work with good test coverage and backward compatibility handling. The main concerns are the duplicate slurp-bytes, the dead import, and the potential for delete-file to throw on a missing file.

Fix issues raised in PR kit-clj#171 review: remove duplicate slurp-bytes from
generator.clj, remove dead installed-modules import from api.clj, use
silent delete-file variant, log warning on dependency resolution errors,
avoid opts shadowing, add installed-modules-log as separate function,
and track missing files in removal report.

Add 16 new tests covering sha256, installed-modules return type,
module-manifest retrieval, describe-injection-for-removal branches,
immediate-dependents, and missing file detection in removal reports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kovan
Copy link
Contributor Author

kovan commented Feb 10, 2026

Thanks for the thorough review! All actionable items have been addressed in 4152a22:

Fixed:

  • Duplicate slurp-bytes — Removed from generator.clj; only exists in modules_log.clj now.
  • Dead import — Removed installed-modules from the :refer list in api.clj.
  • delete-file throwing — Now uses (jio/delete-file f true) (silent variant).
  • installed-modules return type — Kept the original return type (seq of keys). Added a separate installed-modules-log function for the map variant.
  • Exception swallowingremoval-report now prints "WARNING: error resolving dependents:" instead of silently returning #{}.
  • opts shadowing — Destructured as plan-opts :opts to avoid confusion.
  • TOCTOU — Documented in the build-installation-manifest docstring.
  • Missing files bug — Discovered that files deleted between install and report were silently dropped. Added :missing-files to the removal report and display in print-removal-report.

New tests (16 total):

  • sha256 — known-digest and string/bytes equivalence
  • installed-modules — regression test asserting seq-of-keys return type
  • module-manifest — new format, old format, and not-installed cases
  • describe-injection-for-removal — all 7 branches (:edn :merge, :edn :append, :clj :append-requires, :clj :append-build-task, :clj :append-build-task-call, :html :append, and default fallback)
  • immediate-dependents — direct tests for finds-dependent, empty-when-no-dependents, and excludes-self
  • removal-report-missing-files — verifies deleted files appear in :missing-files

All 35 tests pass (113 assertions, 0 failures).

@yogthos yogthos merged commit 8b8cc1a into kit-clj:master Feb 10, 2026
1 check passed
@yogthos
Copy link
Collaborator

yogthos commented Feb 10, 2026

Thanks for the clean up. I'll try push out a new release in the near future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add possibility to remove modules

2 participants