diff -pruN 0.5-3/.gitattributes 0.6-2/.gitattributes
--- 0.5-3/.gitattributes	1970-01-01 00:00:00.000000000 +0000
+++ 0.6-2/.gitattributes	2024-08-30 20:46:27.000000000 +0000
@@ -0,0 +1,2 @@
+makem.sh linguist-vendored
+Makefile linguist-vendored
\ No newline at end of file
diff -pruN 0.5-3/README.org 0.6-2/README.org
--- 0.5-3/README.org	2020-04-09 14:36:55.000000000 +0000
+++ 0.6-2/README.org	2024-08-30 20:46:27.000000000 +0000
@@ -1,9 +1,5 @@
 #+PROPERTY: LOGGING nil
 
-#+BEGIN_HTML
-<a href=https://alphapapa.github.io/dont-tread-on-emacs/><img src="dont-tread-on-emacs-150.png" align="right"></a>
-#+END_HTML
-
 * org-make-toc
 :PROPERTIES:
 :TOC:      ignore
@@ -112,15 +108,30 @@ Or, you may activate it in all Org buffe
   (add-hook 'org-mode-hook #'org-make-toc-mode)
 #+END_SRC
 
-** Known Issues
+** Making links work in both Emacs/Org and on GitHub
 
-Unfortunately, due to the way GitHub renders Org documents and links, it's not possible to make links which work in both Org itself and the GitHub-rendered HTML.  See [[https://github.com/wallyqs/org-ruby/issues/11][org-ruby issue #11]].  =toc-org= provides a workaround using =org-link-translation-function= to change how Org handles =#heading=-style links, but, of course, that breaks compatibility with such links that aren't intended to be rendered by GitHub, so it must be manually enabled in each document as appropriate.
+Because of the way GitHub renders Org documents and links, it's not possible to make links which work in both Org itself and the GitHub-rendered HTML unless headings have ~CUSTOM_ID~ properties.  If the option ~org-make-toc-insert-custom-ids~ is enabled, this package will automatically add them as needed.
 
 * Changelog
 :PROPERTIES:
 :TOC:      :depth 0
 :END:
 
+** 0.6
+
+*Compatibility*
++ Org 9.3 or later is now required.
+
+*Additions*
+
++ Option ~org-make-toc-insert-custom-ids~ automatically adds ~CUSTOM_ID~ properties to headings so links can work on both GitHub-rendered Org files and in Emacs.  (Thanks to [[https://github.com/noctuid][Fox Kiester]].)
+
+*Fixes*
+
++ Tolerate whitespace before drawer opening/closing lines.  ([[https://github.com/alphapapa/org-make-toc/pull/15][#15]], [[https://github.com/alphapapa/org-make-toc/issues/17][#17]].  Thanks to [[https://github.com/progfolio][Nicholas Vollmer]].)
++ Link-type function called with position as argument.  (Fixes occasional bugs with heading IDs.)
++ Mode ~org-make-toc-mode~ now adds to the ~before-save-hook~ in the local buffer rather than globally.  ([[https://github.com/alphapapa/org-make-toc/pull/24][#24]].  Thanks to [[https://github.com/akirak][Akira Komamura]].)
+
 ** 0.5
 
 This version is a major rewrite that requires reconfiguring existing TOCs.  Please see the usage instructions anew.  Users who don't want to convert to 0.5-style TOCs may continue using version 0.4.
diff -pruN 0.5-3/debian/changelog 0.6-2/debian/changelog
--- 0.5-3/debian/changelog	2024-07-25 14:08:12.000000000 +0000
+++ 0.6-2/debian/changelog	2025-08-17 21:30:31.000000000 +0000
@@ -1,3 +1,15 @@
+org-make-toc (0.6-2) unstable; urgency=medium
+
+  * Source-only upload.
+
+ -- Sean Whitton <spwhitton@spwhitton.name>  Sun, 17 Aug 2025 22:30:31 +0100
+
+org-make-toc (0.6-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Sean Whitton <spwhitton@spwhitton.name>  Sun, 17 Aug 2025 18:56:57 +0100
+
 org-make-toc (0.5-3) unstable; urgency=medium
 
   * Team upload.
Binary files 0.5-3/dont-tread-on-emacs-150.png and 0.6-2/dont-tread-on-emacs-150.png differ
diff -pruN 0.5-3/example.org 0.6-2/example.org
--- 0.5-3/example.org	2020-04-09 14:36:55.000000000 +0000
+++ 0.6-2/example.org	2024-08-30 20:46:27.000000000 +0000
@@ -232,7 +232,7 @@ This section is *not* included in the Ch
 
 *** Section 5c
 :PROPERTIES:
-:TOC:      :include descendants :depth 5
+:TOC:      :include descendants :depth 3
 :END:
 :CONTENTS:
 - [[#section-5c1][Section 5c1]]
diff -pruN 0.5-3/makem.sh 0.6-2/makem.sh
--- 0.5-3/makem.sh	2020-04-09 14:36:55.000000000 +0000
+++ 0.6-2/makem.sh	2024-08-30 20:46:27.000000000 +0000
@@ -2,11 +2,12 @@
 
 # * makem.sh --- Script to aid building and testing Emacs Lisp packages
 
-# https://github.com/alphapapa/makem.sh
+# URL: https://github.com/alphapapa/makem.sh
+# Version: 0.7-pre
 
 # * Commentary:
 
-# makem.sh is a script helps to build, lint, and test Emacs Lisp
+# makem.sh is a script that helps to build, lint, and test Emacs Lisp
 # packages.  It aims to make linting and testing as simple as possible
 # without requiring per-package configuration.
 
@@ -78,7 +79,8 @@ Rules:
 Options:
   -d, --debug    Print debug info.
   -h, --help     I need somebody!
-  -v, --verbose  Increase verbosity, up to -vv.
+  -v, --verbose  Increase verbosity, up to -vvv.
+  --no-color     Disable color output.
 
   --debug-load-path  Print load-path from inside Emacs.
 
@@ -87,8 +89,9 @@ Options:
   -e, --exclude FILE  Exclude FILE from linting and testing.
   -f, --file FILE     Check FILE in addition to discovered files.
 
-  --no-color        Disable color output.
-  -C, --no-compile  Don't compile files automatically.
+  -c, --compile-batch  Batch-compile files (instead of separately; quicker, but
+                                            may hide problems).
+  -C, --no-compile     Don't compile files automatically.
 
 Sandbox options:
   -s[DIR], --sandbox[=DIR]  Run Emacs with an empty config in a sandbox DIR.
@@ -109,6 +112,12 @@ Source files are automatically discovere
 specified with options.  Package dependencies are discovered from
 "Package-Requires" headers in source files, from -pkg.el files, and
 from a Cask file.
+
+Checkdoc's spell checker may not recognize some words, causing the
+`lint-checkdoc' rule to fail.  Custom words can be added in file-local
+or directory-local variables using the variable
+`ispell-buffer-session-localwords', which should be set to a list of
+strings.
 EOF
 }
 
@@ -133,6 +142,27 @@ EOF
     echo $file
 }
 
+function elisp-elint-file {
+    local file=$(mktemp)
+    cat >$file <<EOF
+(require 'cl-lib)
+(require 'elint)
+(defun makem-elint-file (file)
+  (let ((errors 0))
+    (cl-letf (((symbol-function 'orig-message) (symbol-function 'message))
+              ((symbol-function 'message) (symbol-function 'ignore))
+              ((symbol-function 'elint-output)
+               (lambda (string)
+                 (cl-incf errors)
+                 (orig-message "%s" string))))
+      (elint-file file)
+      ;; NOTE: \`errors' is not actually the number of errors, because
+      ;; it's incremented for non-error header strings as well.
+      (kill-emacs errors))))
+EOF
+    echo "$file"
+}
+
 function elisp-checkdoc-file {
     # Since checkdoc doesn't have a batch function that exits non-zero
     # when errors are found, we make one.
@@ -151,7 +181,9 @@ function elisp-checkdoc-file {
                                ": " text)))
               (message msg)
               (setq makem-checkdoc-errors-p t)
-              (list text start end unfixable)))))
+              ;; Return nil because we *are* generating a buffered list of errors.
+              nil))))
+    (put 'ispell-buffer-session-localwords 'safe-local-variable #'list-of-strings-p)
     (mapcar #'checkdoc-file files)
     (when makem-checkdoc-errors-p
       (kill-emacs 1))))
@@ -162,6 +194,51 @@ EOF
     echo $file
 }
 
+function elisp-byte-compile-file {
+    # This seems to be the only way to make byte-compilation signal
+    # errors for warnings AND display all warnings rather than only
+    # the first one.
+    local file=$(mktemp)
+    # TODO: Add file to $paths_temp in other elisp- functions.
+    paths_temp+=("$file")
+
+    cat >"$file" <<EOF
+(defun makem-batch-byte-compile (&rest args)
+  ""
+  (let ((num-errors 0)
+        (num-warnings 0))
+    ;; NOTE: Only accepts files as args, not directories.
+    (dolist (file command-line-args-left)
+      (pcase-let ((\`(,errors ,warnings) (makem-byte-compile-file file)))
+        (cl-incf num-errors errors)
+        (cl-incf num-warnings warnings)))
+    (zerop num-errors)))
+
+(defun makem-byte-compile-file (filename &optional load)
+  "Call \`byte-compile-warn', returning the number of errors and the number of warnings."
+  (let ((num-warnings 0)
+        (num-errors 0))
+    (cl-letf (((symbol-function 'byte-compile-warn)
+               (lambda (format &rest args)
+                 ;; Copied from \`byte-compile-warn'.
+                 (cl-incf num-warnings)
+                 (setq format (apply #'format-message format args))
+                 (byte-compile-log-warning format t :warning)))
+              ((symbol-function 'byte-compile-report-error)
+               (lambda (error-info &optional fill &rest args)
+                 (cl-incf num-errors)
+                 ;; Copied from \`byte-compile-report-error'.
+                 (setq byte-compiler-error-flag t)
+                 (byte-compile-log-warning
+                  (if (stringp error-info) error-info
+                    (error-message-string error-info))
+                  fill :error))))
+      (byte-compile-file filename load))
+    (list num-errors num-warnings)))
+EOF
+    echo "$file"
+}
+
 function elisp-check-declare-file {
     # Since check-declare doesn't have a batch function that exits
     # non-zero when errors are found, we make one.
@@ -197,20 +274,23 @@ Exits non-zero if mis-indented lines are
   (let ((errors-p))
     (cl-labels ((lint-file (file)
                            (find-file file)
-                           (let ((tick (buffer-modified-tick)))
-                             (let ((inhibit-message t))
-                               (indent-region (point-min) (point-max)))
-                             (when (/= tick (buffer-modified-tick))
-                               ;; Indentation changed: warn for each line.
-                               (dolist (line (undo-lines buffer-undo-list))
-                                 (message "%s:%s: Indentation mismatch" (buffer-name) line))
-                               (setf errors-p t))))
+                           (let ((inhibit-message t))
+                             (indent-region (point-min) (point-max)))
+                           (when buffer-undo-list
+                             ;; Indentation changed: warn for each line.
+                             (dolist (line (undo-lines buffer-undo-list))
+                               (message "%s:%s: Indentation mismatch" (buffer-name) line))
+                             (setf errors-p t)))
+                (undo-pos (entry)
+                           (cl-typecase (car entry)
+                             (number (car entry))
+                             (string (abs (cdr entry)))))
                 (undo-lines (undo-list)
                             ;; Return list of lines changed in UNDO-LIST.
                             (nreverse (cl-loop for elt in undo-list
-                                               when (and (consp elt)
-                                                         (numberp (car elt)))
-                                               collect (line-number-at-pos (car elt))))))
+                                               for pos = (undo-pos elt)
+                                               when pos
+                                               collect (line-number-at-pos pos)))))
       (mapc #'lint-file (mapcar #'expand-file-name command-line-args-left))
       (when errors-p
         (kill-emacs 1)))))
@@ -229,7 +309,6 @@ function elisp-package-initialize-file {
                              (cons "melpa-stable" "https://stable.melpa.org/packages/")))
 $elisp_org_package_archive
 (package-initialize)
-(setq load-prefer-newer t)
 EOF
     echo $file
 }
@@ -242,6 +321,7 @@ function run_emacs {
     local emacs_command=(
         "${emacs_command[@]}"
         -Q
+        --eval "(setq load-prefer-newer t)"
         "${args_debug[@]}"
         "${args_sandbox[@]}"
         -l $package_initialize_file
@@ -283,13 +363,59 @@ function batch-byte-compile {
     [[ $compile_error_on_warn ]] && local error_on_warn=(--eval "(setq byte-compile-error-on-warn t)")
 
     run_emacs \
+        --load "$(elisp-byte-compile-file)" \
         "${error_on_warn[@]}" \
-        --funcall batch-byte-compile \
+        --eval "(unless (makem-batch-byte-compile) (kill-emacs 1))" \
         "$@"
 }
 
+function byte-compile-file {
+    debug "byte-compile: ERROR-ON-WARN:$compile_error_on_warn"
+    local file="$1"
+
+    [[ $compile_error_on_warn ]] && local error_on_warn=(--eval "(setq byte-compile-error-on-warn t)")
+
+    # FIXME: Why is the line starting with "&& verbose 3" not indented properly?  Emacs insists on indenting it back a level.
+    run_emacs \
+        --load "$(elisp-byte-compile-file)" \
+        "${error_on_warn[@]}" \
+        --eval "(pcase-let ((\`(,num-errors ,num-warnings) (makem-byte-compile-file \"$file\"))) (when (or (and byte-compile-error-on-warn (not (zerop num-warnings))) (not (zerop num-errors))) (kill-emacs 1)))" \
+        && verbose 3 "Compiling $file finished without errors." \
+            || { verbose 3 "Compiling file failed: $file"; return 1; }
+}
+
 # ** Files
 
+function submodules {
+    # Echo a list of submodules's paths relative to the repo root.
+    # TODO: Parse with bash regexp instead of cut.
+    git submodule status | awk '{print $2}'
+}
+
+function project-root {
+    # Echo the root of the project (or superproject, if running from
+    # within a submodule).
+    root_dir=$(git rev-parse --show-superproject-working-tree)
+    [[ $root_dir ]] || root_dir=$(git rev-parse --show-toplevel)
+    [[ $root_dir ]] || error "Can't find repo root."
+
+    echo "$root_dir"
+}
+
+function files-project {
+    # Return a list of files in project; or with $1, files in it
+    # matching that pattern.  Excludes submodules.
+    [[ $1 ]] && pattern="/$1" || pattern="."
+
+    local excludes
+    for submodule in $(submodules)
+    do
+        excludes+=(":!:$submodule")
+    done
+
+    git ls-files -- "$pattern" "${excludes[@]}"
+}
+
 function dirs-project {
     # Echo list of directories to be used in load path.
     files-project-feature | dirnames
@@ -298,7 +424,7 @@ function dirs-project {
 
 function files-project-elisp {
     # Echo list of Elisp files in project.
-    git ls-files 2>/dev/null \
+    files-project 2>/dev/null \
         | egrep "\.el$" \
         | filter-files-exclude-default \
         | filter-files-exclude-args
@@ -307,13 +433,13 @@ function files-project-elisp {
 function files-project-feature {
     # Echo list of Elisp files that are not tests and provide a feature.
     files-project-elisp \
-        | egrep -v "$test_files_regexp" \
+        | grep -E -v "$test_files_regexp" \
         | filter-files-feature
 }
 
 function files-project-test {
     # Echo list of Elisp test files.
-    files-project-elisp | egrep "$test_files_regexp"
+    files-project-elisp | grep -E "$test_files_regexp"
 }
 
 function dirnames {
@@ -326,7 +452,7 @@ function dirnames {
 
 function filter-files-exclude-default {
     # Filter out paths (STDIN) which should be excluded by default.
-    egrep -v "(/\.cask/|-autoloads.el|.dir-locals)"
+    grep -E -v "(/\.cask/|-autoloads.el|.dir-locals)"
 }
 
 function filter-files-exclude-args {
@@ -352,7 +478,7 @@ function filter-files-feature {
     # Read paths on STDIN and echo ones that (provide 'a-feature).
     while read path
     do
-        egrep "^\\(provide '" "$path" &>/dev/null \
+        grep -E "^\\(provide '" "$path" &>/dev/null \
             && echo "$path"
     done
 }
@@ -361,7 +487,8 @@ function args-load-files {
     # For file in $@, echo "--load $file".
     for file in "$@"
     do
-        printf -- '--load %q ' "$file"
+        sans_extension=${file%%.el}
+        printf -- '--load %q ' "$sans_extension"
     done
 }
 
@@ -398,9 +525,8 @@ function ert-tests-p {
 }
 
 function package-main-file {
-    # Echo the package's main file.  Helpful for setting package-lint-main-file.
-
-    file_pkg=$(git ls-files ./*-pkg.el 2>/dev/null)
+    # Echo the package's main file.
+    file_pkg=$(files-project "*-pkg.el" 2>/dev/null)
 
     if [[ $file_pkg ]]
     then
@@ -421,24 +547,25 @@ function package-main-file {
 function dependencies {
     # Echo list of package dependencies.
 
-    # Search package headers.
-    egrep -i '^;; Package-Requires: ' $(files-project-feature) $(files-project-test) \
-        | egrep -o '\([^([:space:]][^)]*\)' \
-        | egrep -o '^[^[:space:])]+' \
+    # Search package headers.  Use -a so grep won't think that an Elisp file containing
+    # control characters (rare, but sometimes necessary) is binary and refuse to search it.
+    grep -E -a -i '^;; Package-Requires: ' $(files-project-feature) $(files-project-test) \
+        | grep -E -o '\([^([:space:]][^)]*\)' \
+        | grep -E -o '^[^[:space:])]+' \
         | sed -r 's/\(//g' \
-        | egrep -v '^emacs$'  # Ignore Emacs version requirement.
+        | grep -E -v '^emacs$'  # Ignore Emacs version requirement.
 
     # Search Cask file.
     if [[ -r Cask ]]
     then
-        egrep '\(depends-on "[^"]+"' Cask \
+        grep -E '\(depends-on "[^"]+"' Cask \
             | sed -r -e 's/\(depends-on "([^"]+)".*/\1/g'
     fi
 
     # Search -pkg.el file.
-    if [[ $(git ls-files ./*-pkg.el 2>/dev/null) ]]
+    if [[ $(files-project "*-pkg.el" 2>/dev/null) ]]
     then
-        sed -nr 's/.*\(([-[:alnum:]]+)[[:blank:]]+"[.[:digit:]]+"\).*/\1/p' $(git ls-files ./*-pkg.el 2>/dev/null)
+        sed -nr 's/.*\(([-[:alnum:]]+)[[:blank:]]+"[.[:digit:]]+"\).*/\1/p' $(files-project- -- -pkg.el 2>/dev/null)
     fi
 }
 
@@ -480,6 +607,8 @@ function sandbox {
     args_sandbox=(
         --title "makem.sh: $(basename $(pwd)) (sandbox: $sandbox_dir)"
         --eval "(setq user-emacs-directory (file-truename \"$sandbox_dir\"))"
+        --load package
+        --eval "(setq package-user-dir (expand-file-name \"elpa\" user-emacs-directory))"
         --eval "(setq user-init-file (file-truename \"$init_file\"))"
     )
 
@@ -642,7 +771,8 @@ function verbose {
     if [[ $verbose -ge $1 ]]
     then
         [[ $1 -eq 1 ]] && local color_name=blue
-        [[ $1 -ge 2 ]] && local color_name=cyan
+        [[ $1 -eq 2 ]] && local color_name=cyan
+        [[ $1 -ge 3 ]] && local color_name=white
 
         shift
         log_color $color_name "$@" >&2
@@ -682,16 +812,54 @@ function all {
     tests
 }
 
-function compile {
+function compile-batch {
     [[ $compile ]] || return 0
     unset compile  # Only compile once.
 
     verbose 1 "Compiling..."
+    verbose 2 "Batch-compiling files..."
     debug "Byte-compile files: ${files_project_byte_compile[@]}"
 
-    batch-byte-compile "${files_project_byte_compile[@]}" \
-        && success "Compiling finished without errors." \
-            || error "Compilation failed."
+    batch-byte-compile "${files_project_byte_compile[@]}"
+}
+
+function compile-each {
+    [[ $compile ]] || return 0
+    unset compile  # Only compile once.
+
+    verbose 1 "Compiling..."
+    debug "Byte-compile files: ${files_project_byte_compile[@]}"
+
+    local compile_errors
+    for file in "${files_project_byte_compile[@]}"
+    do
+        verbose 2 "Compiling file: $file..."
+        byte-compile-file "$file" \
+            || compile_errors=t
+    done
+
+    [[ ! $compile_errors ]]
+}
+
+function compile {
+    if [[ $compile = batch ]]
+    then
+        compile-batch "$@"
+    else
+        compile-each "$@"
+    fi
+    local status=$?
+
+    if [[ $compile_error_on_warn ]]
+    then
+        # Linting: just return status code, because lint rule will print messages.
+        [[ $status = 0 ]]
+    else
+        # Not linting: print messages here.
+        [[ $status = 0 ]] \
+            && success "Compiling finished without errors." \
+                || error "Compiling failed."
+    fi
 }
 
 function batch {
@@ -706,12 +874,15 @@ function batch {
 
 function interactive {
     # Run Emacs interactively.  Most useful with --sandbox and --install-deps.
+    local load_file_args=$(args-load-files "${files_project_feature[@]}" "${files_project_test[@]}")
     verbose 1 "Running Emacs interactively..."
-    verbose 2 "Loading files:" "${files_project_feature[@]}" "${files_project_test[@]}"
+    verbose 2 "Loading files: ${load_file_args//--load /}"
+
+    [[ $compile ]] && compile
 
     unset arg_batch
     run_emacs \
-        $(args-load-files "${files_project_feature[@]}" "${files_project_test[@]}") \
+        $load_file_args \
         --eval "(load user-init-file)" \
         "${args_batch_interactive[@]}"
     arg_batch="--batch"
@@ -723,6 +894,9 @@ function lint {
     lint-checkdoc
     lint-compile
     lint-declare
+    # NOTE: Elint doesn't seem very useful at the moment.  See comment
+    # in lint-elint function.
+    # lint-elint
     lint-indent
     lint-package
     lint-regexps
@@ -745,7 +919,7 @@ function lint-compile {
     verbose 1 "Linting compilation..."
 
     compile_error_on_warn=true
-    batch-byte-compile "${files_project_byte_compile[@]}" \
+    compile "${files_project_byte_compile[@]}" \
         && success "Linting compilation finished without errors." \
             || error "Linting compilation failed."
     unset compile_error_on_warn
@@ -779,6 +953,28 @@ function lint-elsa {
             || error "Linting with Elsa failed."
 }
 
+function lint-elint {
+    # NOTE: Elint gives a lot of spurious warnings, apparently because it doesn't load files
+    # that are `require'd, so its output isn't very useful.  But in case it's improved in
+    # the future, and since this wrapper code already works, we might as well leave it in.
+    verbose 1 "Linting with Elint..."
+
+    local errors=0
+    for file in "${files_project_feature[@]}"
+    do
+        verbose 2 "Linting with Elint: $file..."
+        run_emacs \
+            --load "$(elisp-elint-file)" \
+            --eval "(makem-elint-file \"$file\")" \
+            && verbose 3 "Linting with Elint found no errors." \
+                || { error "Linting with Elint failed: $file"; ((errors++)) ; }
+    done
+
+    [[ $errors = 0 ]] \
+        && success "Linting with Elint finished without errors." \
+            || error "Linting with Elint failed."
+}
+
 function lint-indent {
     verbose 1 "Linting indentation..."
 
@@ -850,7 +1046,8 @@ function test-buttercup {
 
     run_emacs \
         $(args-load-files "${files_project_test[@]}") \
-        -f buttercup-run \
+        --load "$buttercup_file" \
+        --eval "(progn (setq backtrace-on-error-noninteractive nil) (buttercup-run))" \
         && success "Buttercup tests finished without errors." \
             || error "Buttercup tests failed."
 }
@@ -878,6 +1075,7 @@ errors=0
 verbose=0
 compile=true
 arg_batch="--batch"
+compile=each
 
 # MAYBE: Disable color if not outputting to a terminal.  (OTOH, the
 # colorized output is helpful in CI logs, and I don't know if,
@@ -936,8 +1134,8 @@ elisp_org_package_archive="(add-to-list
 # * Args
 
 args=$(getopt -n "$0" \
-              -o dhe:E:i:s::vf:CO \
-              -l exclude:,emacs:,install-deps,install-linters,debug,debug-load-path,help,install:,verbose,file:,no-color,no-compile,no-org-repo,sandbox:: \
+              -o dhce:E:i:s::vf:CO \
+              -l compile-batch,exclude:,emacs:,install-deps,install-linters,debug,debug-load-path,help,install:,verbose,file:,no-color,no-compile,no-org-repo,sandbox:: \
               -- "$@") \
     || { usage; exit 1; }
 eval set -- "$args"
@@ -964,6 +1162,10 @@ do
             usage
             exit
             ;;
+        -c|--compile-batch)
+            debug "Compiling files in batch mode"
+            compile=batch
+            ;;
         -E|--emacs)
             shift
             emacs_command=($1)
@@ -1028,6 +1230,9 @@ paths_temp+=("$package_initialize_file")
 
 trap cleanup EXIT INT TERM
 
+# Change to project root directory first.
+cd "$(project-root)"
+
 # Discover project files.
 files_project_feature=($(files-project-feature))
 files_project_test=($(files-project-test))
diff -pruN 0.5-3/notes.org 0.6-2/notes.org
--- 0.5-3/notes.org	2020-04-09 14:36:55.000000000 +0000
+++ 0.6-2/notes.org	2024-08-30 20:46:27.000000000 +0000
@@ -5,9 +5,9 @@
 
 * Plans
 
-** UNDERWAY 0.5
+** DONE 0.5
 
-*** TODO Release 0.5
+*** DONE Release 0.5
 
 +  [X] Check comment TODOs (using =magit-todos=).
 +  [X] Check issues.
@@ -17,13 +17,13 @@
 +  [X] Update version numbers in file headers.
      -  [X] org-make-toc.el
 +  [X] Update changelog in =README.org=.
-+  [ ] Tag and sign new version (using Magit's =t r=).
-+  [ ] Push =master=.
-+  [ ] Push tags.
-+  [ ] Post-release changes:
-     -  [ ] Bump version numbers to n+1-pre:
-          +  [ ] org-make-toc.el
-          +  [ ] README.org
++  [X] Tag and sign new version (using Magit's =t r=).
++  [X] Push =master=.
++  [X] Push tags.
++  [X] Post-release changes:
+     -  [X] Bump version numbers to n+1-pre:
+          +  [X] org-make-toc.el
+          +  [X] README.org
 
 * Notes
 
diff -pruN 0.5-3/org-make-toc.el 0.6-2/org-make-toc.el
--- 0.5-3/org-make-toc.el	2020-04-09 14:36:55.000000000 +0000
+++ 0.6-2/org-make-toc.el	2024-08-30 20:46:27.000000000 +0000
@@ -4,8 +4,8 @@
 
 ;; Author: Adam Porter <adam@alphapapa.net>
 ;; URL: http://github.com/alphapapa/org-make-toc
-;; Version: 0.5
-;; Package-Requires: ((emacs "26.1") (dash "2.12") (s "1.10.0") (org "9.0"))
+;; Version: 0.6
+;; Package-Requires: ((emacs "26.1") (dash "2.12") (s "1.10.0") (org "9.3") (compat "29.1"))
 ;; Keywords: Org, convenience
 
 ;;; Commentary:
@@ -131,6 +131,7 @@
 ;;; Code:
 
 (require 'cl-lib)
+(require 'compat)
 (require 'org)
 (require 'rx)
 (require 'seq)
@@ -160,34 +161,55 @@ with the destination of the published fi
                  (const :tag "Org-compatible" org-make-toc--link-entry-org)
                  (function :tag "Custom function")))
 
+(defcustom org-make-toc-insert-custom-ids nil
+  "Add \"CUSTOM_ID\" properties to headings when using GitHub-compatible links.
+When non-nil and using the default `org-make-toc-link-type-fn' to
+generate GitHub-compatible links, automatically insert a
+\"CUSTOM_ID\" property for each entry.  This will allow links to
+also work in `org-mode' in Emacs."
+  :type 'boolean)
+
 (defcustom org-make-toc-exclude-tags '("noexport")
   "Entries with any of these tags are excluded from TOCs."
   :type '(repeat string))
 
+(defconst org-make-toc-contents-drawer-start-regexp
+  (rx bol (0+ blank) ":CONTENTS:" (0+ blank) eol)
+  "Regular expression for the beginning of a :CONTENTS: drawer.")
+
+(defvar-local org-make-toc-disambiguations nil
+  "Used to disambiguate custom IDs.")
+(defvar-local org-make-toc-ids nil
+  "Maps custom IDs to buffer positions.")
+
 ;;;; Commands
 
 ;;;###autoload
 (defun org-make-toc ()
   "Make or update table of contents in current buffer."
   (interactive)
-  (save-excursion
-    (goto-char (point-min))
-    (cl-loop with made-toc
-             for pos = (org-make-toc--next-toc-position)
-             while pos
-             do (progn
-                  (goto-char pos)
-                  (when (org-make-toc--update-toc-at-point)
-                    (setq made-toc t)))
-             finally do (unless made-toc
-                          (message "org-make-toc: No TOC node found.")))))
+  (let ((org-make-toc-disambiguations (make-hash-table :test #'equal))
+        (org-make-toc-ids (make-hash-table :test #'equal)))
+    (save-excursion
+      (goto-char (point-min))
+      (cl-loop with made-toc
+               for pos = (org-make-toc--next-toc-position)
+               while pos
+               do (progn
+                    (goto-char pos)
+                    (when (org-make-toc--update-toc-at-point)
+                      (setq made-toc t)))
+               finally do (unless made-toc
+                            (message "org-make-toc: No TOC node found."))))))
 
 ;;;###autoload
 (defun org-make-toc-at-point ()
   "Make or update table of contents at current entry."
   (interactive)
-  (unless (org-make-toc--update-toc-at-point)
-    (user-error "No TOC node found")))
+  (let ((org-make-toc-disambiguations (make-hash-table :test #'equal))
+        (org-make-toc-ids (make-hash-table :test #'equal)))
+    (unless (org-make-toc--update-toc-at-point)
+      (user-error "No TOC node found"))))
 
 ;;;###autoload
 (defun org-make-toc-insert ()
@@ -207,29 +229,29 @@ with the destination of the published fi
 (defun org-make-toc--complete-toc-properties ()
   "Return TOC properties string read with completion."
   (cl-labels ((property (property)
-                        (--> (org-entry-get (point) "TOC")
-                             (concat "(" it ")") (read it)
-                             (plist-get it property)
-                             (if it
-                                 (prin1-to-string it)
-                               "")))
+                (--> (org-entry-get (point) "TOC")
+                     (concat "(" it ")") (read it)
+                     (plist-get it property)
+                     (if it
+                         (prin1-to-string it)
+                       "")))
               (read-number (prompt &optional initial-input)
-                           ;; The default `read-number' only accepts a number, and
-                           ;; we need to allow the user to input nothing.  But
-                           ;; using `read-string' with `string-to-number' returns
-                           ;; 0 for the empty string, so we use this instead.
-                           (let ((input (read-string prompt initial-input)))
-                             (pcase input
-                               ((rx bos (1+ digit) eos)
-                                (string-to-number input))
-                               ((rx bos (0+ blank) eos) "")
-                               (_ (read-number prompt initial-input)))))
+                ;; The default `read-number' only accepts a number, and
+                ;; we need to allow the user to input nothing.  But
+                ;; using `read-string' with `string-to-number' returns
+                ;; 0 for the empty string, so we use this instead.
+                (let ((input (read-string prompt initial-input)))
+                  (pcase input
+                    ((rx bos (1+ digit) eos)
+                     (string-to-number input))
+                    ((rx bos (0+ blank) eos) "")
+                    (_ (read-number prompt initial-input)))))
               (completing-read-description
-               (prompt collection &optional predicate require-match
-                       initial-input hist def inherit-input-method)
-               (let ((choice (completing-read prompt collection predicate require-match
-                                              initial-input hist def inherit-input-method)))
-                 (alist-get choice collection nil nil #'equal)))
+                (prompt collection &optional predicate require-match
+                        initial-input hist def inherit-input-method)
+                (let ((choice (completing-read prompt collection predicate require-match
+                                               initial-input hist def inherit-input-method)))
+                  (alist-get choice collection nil nil #'equal)))
               ;; TODO: Version of `completing-read-multiple' that works like that.  Sigh.
               )
     (let ((props
@@ -263,7 +285,7 @@ with the destination of the published fi
 (defun org-make-toc--next-toc-position ()
   "Return position of next TOC, or nil."
   (save-excursion
-    (when (and (re-search-forward (rx bol ":CONTENTS:" (0+ blank) eol) nil t)
+    (when (and (re-search-forward org-make-toc-contents-drawer-start-regexp nil t)
                (save-excursion
                  (beginning-of-line)
                  (looking-at-p org-drawer-regexp)))
@@ -278,67 +300,67 @@ with the destination of the published fi
 (defun org-make-toc--toc-at-point ()
   "Return TOC tree for entry at point."
   (cl-labels ((descendants (&key depth force)
-                           (when (and (or (null depth) (> depth 0))
-                                      (children-p))
-                             (save-excursion
-                               (save-restriction
-                                 (org-narrow-to-subtree)
-                                 (outline-next-heading)
-                                 (cl-loop collect (cons (entry :force force)
-                                                        (unless (entry-match :ignore 'descendants)
-                                                          (descendants :depth (or (unless (or (arg-has force 'depth)
-                                                                                              (entry-match :local 'depth))
-                                                                                    (entry-property :depth))
-                                                                                  (when depth
-                                                                                    (1- depth)))
-                                                                       :force force)))
-                                          while (next-sibling))))))
+                (when (and (or (null depth) (> depth 0))
+                           (children-p))
+                  (save-excursion
+                    (save-restriction
+                      (org-narrow-to-subtree)
+                      (outline-next-heading)
+                      (cl-loop collect (cons (entry :force force)
+                                             (unless (entry-match :ignore 'descendants)
+                                               (descendants :depth (or (unless (or (arg-has force 'depth)
+                                                                                   (entry-match :local 'depth))
+                                                                         (entry-property :depth))
+                                                                       (when depth
+                                                                         (1- depth)))
+                                                            :force force)))
+                               while (next-sibling))))))
               (siblings (&key depth force)
-                        (save-excursion
-                          (save-restriction
-                            (when (org-up-heading-safe)
-                              (org-narrow-to-subtree)
-                              (outline-next-heading)
-                              (outline-next-heading))
-                            (cl-loop collect (cons (entry :force force)
-                                                   (unless (entry-match :ignore 'descendants)
-                                                     (descendants :depth (or (unless (or (arg-has force 'depth)
-                                                                                         (entry-match :local 'depth))
-                                                                               (entry-property :depth))
-                                                                             (when depth
-                                                                               (1- depth)))
-                                                                  :force force)))
-                                     while (next-sibling)))))
+                (save-excursion
+                  (save-restriction
+                    (when (org-up-heading-safe)
+                      (org-narrow-to-subtree)
+                      (outline-next-heading)
+                      (outline-next-heading))
+                    (cl-loop collect (cons (entry :force force)
+                                           (unless (entry-match :ignore 'descendants)
+                                             (descendants :depth (or (unless (or (arg-has force 'depth)
+                                                                                 (entry-match :local 'depth))
+                                                                       (entry-property :depth))
+                                                                     (when depth
+                                                                       (1- depth)))
+                                                          :force force)))
+                             while (next-sibling)))))
               (children-p ()
-                          (let ((level (org-current-level)))
-                            (save-excursion
-                              (when (outline-next-heading)
-                                (> (org-current-level) level)))))
+                (let ((level (org-current-level)))
+                  (save-excursion
+                    (when (outline-next-heading)
+                      (> (org-current-level) level)))))
               (next-sibling ()
-                            (let ((pos (point)))
-                              (org-forward-heading-same-level 1 'invisible-ok)
-                              (/= pos (point))))
+                (let ((pos (point)))
+                  (org-forward-heading-same-level 1 'invisible-ok)
+                  (/= pos (point))))
               (arg-has (var val)
-                       (or (equal var val)
-                           (and (listp var)
-                                (member val var))))
+                (or (equal var val)
+                    (and (listp var)
+                         (member val var))))
               (entry (&key force)
-                     (unless (or (and (not (arg-has force 'ignore))
-                                      (entry-match :ignore 'this))
-                                 ;; TODO: Add configurable predicate list to exclude entries.
-                                 (seq-intersection org-make-toc-exclude-tags (org-get-tags))
-                                 ;; NOTE: The "COMMENT" keyword is not returned as the to-do keyword
-                                 ;; by `org-heading-components', so it can't be tested as a keyword.
-                                 (string-match-p (rx bos "COMMENT" (or blank eos))
-                                                 (nth 4 (org-heading-components))))
-                       (funcall org-make-toc-link-type-fn)))
+                (unless (or (and (not (arg-has force 'ignore))
+                                 (entry-match :ignore 'this))
+                            ;; TODO: Add configurable predicate list to exclude entries.
+                            (seq-intersection org-make-toc-exclude-tags (org-get-tags))
+                            ;; NOTE: The "COMMENT" keyword is not returned as the to-do keyword
+                            ;; by `org-heading-components', so it can't be tested as a keyword.
+                            (string-match-p (rx bos "COMMENT" (or blank eos))
+                                            (nth 4 (org-heading-components))))
+                  (funcall org-make-toc-link-type-fn (point))))
               (entry-match (property value)
-                           (when-let* ((found-value (entry-property property)))
-                             (or (equal value found-value)
-                                 (and (listp found-value) (member value found-value)))))
+                (when-let* ((found-value (entry-property property)))
+                  (or (equal value found-value)
+                      (and (listp found-value) (member value found-value)))))
               (entry-property (property)
-                              (plist-get (read (concat "(" (org-entry-get (point) "TOC") ")"))
-                                         property)))
+                (plist-get (read (concat "(" (org-entry-get (point) "TOC") ")"))
+                           property)))
     (save-excursion
       (save-restriction
         (-let* (((&plist :include :depth :force force)
@@ -361,21 +383,36 @@ with the destination of the published fi
 (defun org-make-toc--tree-to-list (tree)
   "Return list string for TOC TREE."
   (cl-labels ((tree (tree depth)
-                    (when (> (length tree) 0)
-                      (when-let* ((entries (->> (append (when (car tree)
-                                                          (list (concat (s-repeat depth "  ")
-                                                                        "- " (car tree))))
-                                                        (--map (tree it (1+ depth))
-                                                               (cdr tree)))
-                                                -non-nil -flatten)))
-                        (s-join "\n" entries)))))
+                (when (> (length tree) 0)
+                  (when-let* ((entries (->> (append (when (car tree)
+                                                      (list (concat (s-repeat depth "  ")
+                                                                    "- " (car tree))))
+                                                    (--map (tree it (1+ depth))
+                                                           (cdr tree)))
+                                            -non-nil -flatten)))
+                    (s-join "\n" entries)))))
     (->> tree
          (--map (tree it 0))
          -flatten (s-join "\n"))))
 
-(defun org-make-toc--link-entry-github ()
-  "Return text for ENTRY converted to GitHub style link."
-  (-when-let* ((title (nth 4 (org-heading-components)))
+(defun org-make-toc--disambiguate (string)
+  "Return STRING having been disambiguated.
+Uses hash table `org-make-toc-disambiguations'."
+  (if (not (gethash string org-make-toc-disambiguations))
+      (progn
+        (setf (gethash string org-make-toc-disambiguations) t)
+        string)
+    (cl-loop for i from 0 to 1000
+             do (when (= 1000 i)
+                  (error "Tried to disambiguate %s 1000 times" string))
+             for new-string = (format "%s-%s" string i)
+             if (not (gethash new-string org-make-toc-disambiguations))
+             do (puthash new-string t org-make-toc-disambiguations)
+             and return new-string)))
+
+(defun org-make-toc--link-entry-github (pos)
+  "Return text for entry at POS converted to GitHub style link."
+  (-when-let* ((title (org-link-display-format (org-entry-get pos "ITEM")))
                (target (--> title
                             org-link-display-format
                             (downcase it)
@@ -384,17 +421,22 @@ with the destination of the published fi
                (filename (if org-make-toc-filename-prefix
                              (file-name-nondirectory (buffer-file-name))
                            "")))
-    (org-make-link-string (concat filename "#" target)
+    (when org-make-toc-insert-custom-ids
+      (setf target (or (gethash pos org-make-toc-ids)
+                       (setf (gethash pos org-make-toc-ids)
+                             (org-make-toc--disambiguate target))))
+      (org-set-property "CUSTOM_ID" target))
+    (org-link-make-string (concat filename "#" target)
                           (org-make-toc--visible-text title))))
 
-(defun org-make-toc--link-entry-org ()
-  "Return text for ENTRY converted to regular Org link."
+(defun org-make-toc--link-entry-org (pos)
+  "Return text for entry at POS converted to regular Org link."
   ;; FIXME: There must be a built-in function to do this, although it might be in `org-export'.
-  (-when-let* ((title (nth 4 (org-heading-components)))
+  (-when-let* ((title (org-link-display-format (org-entry-get pos "ITEM")))
                (filename (if org-make-toc-filename-prefix
                              (concat "file:" (file-name-nondirectory (buffer-file-name)) "::")
                            "")))
-    (org-make-link-string (concat filename title)
+    (org-link-make-string (concat filename title)
                           (org-make-toc--visible-text title))))
 
 (defun org-make-toc--replace-entry-contents (contents)
@@ -404,20 +446,20 @@ Replaces contents of :CONTENTS: drawer."
     (org-back-to-heading 'invisible-ok)
     (let* ((end (org-entry-end-position))
            contents-beg contents-end)
-      (when (and (re-search-forward (rx bol ":CONTENTS:" (0+ blank) eol) end t)
+      (when (and (re-search-forward org-make-toc-contents-drawer-start-regexp end t)
                  (save-excursion
                    (beginning-of-line)
                    (looking-at-p org-drawer-regexp)))
         ;; Set the end first, then search back and skip any ":TOC:" property line in the drawer.
         (setf contents-end (save-excursion
-                             (when (re-search-forward (rx bol ":END:" (0+ blank) eol) end)
+                             (when (re-search-forward (rx bol (0+ blank) ":END:" (0+ blank) eol) end)
                                (match-beginning 0)))
               contents-beg (progn
                              (when (save-excursion
                                      (forward-line 1)
                                      (looking-at-p (rx bol ":TOC:" (0+ blank) (group (1+ nonl)))))
                                (forward-line 1))
-                             (point-at-eol))
+                             (pos-eol))
               contents (concat "\n" (string-trim contents) "\n")
               (buffer-substring contents-beg contents-end) contents)))))
 
@@ -446,17 +488,17 @@ created."
           (cl-flet ((visible-p () (not (get-char-property (point) 'invisible)))
                     (invisible-p () (get-char-property (point) 'invisible))
                     (forward-until (until)
-                                   (cl-loop until (or (eobp) (funcall until))
-                                            for pos = (next-single-property-change (point) 'invisible nil (point-max))
-                                            while pos
-                                            do (goto-char pos))
-                                   (point))
+                      (cl-loop until (or (eobp) (funcall until))
+                               for pos = (next-single-property-change (point) 'invisible nil (point-max))
+                               while pos
+                               do (goto-char pos))
+                      (point))
                     (backward-until (until)
-                                    (cl-loop until (or (eobp) (funcall until))
-                                             for pos = (previous-single-property-change (point) 'invisible nil (point-max))
-                                             while pos
-                                             do (goto-char pos))
-                                    (point)))
+                      (cl-loop until (or (eobp) (funcall until))
+                               for pos = (previous-single-property-change (point) 'invisible nil (point-max))
+                               while pos
+                               do (goto-char pos))
+                      (point)))
             (goto-char (point-min))
             (unless (visible-p)
               (forward-until #'visible-p))
@@ -469,13 +511,14 @@ created."
 
 ;;;###autoload
 (define-minor-mode org-make-toc-mode
-  "Add the `org-make-toc' command to the `before-save-hook' in the current Org buffer.
+  "Add `org-make-toc' to the `before-save-hook' in the current Org buffer.
 With prefix argument ARG, turn on if positive, otherwise off."
   :init-value nil
   (unless (derived-mode-p 'org-mode)
     (user-error "Not an Org buffer"))
-  (funcall (if org-make-toc-mode #'add-hook #'remove-hook)
-           'before-save-hook #'org-make-toc)
+  (if org-make-toc-mode
+      (add-hook 'before-save-hook #'org-make-toc nil t)
+    (remove-hook 'before-save-hook #'org-make-toc t))
   (message (format "org-make-toc-mode %s."
                    (if org-make-toc-mode
                        "enabled"
