adds TapeMachine and a directory TreeMachine in preps to formalizing project file...
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Wed, 26 Nov 2025 14:10:22 +0000 (14:10 +0000)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Wed, 26 Nov 2025 14:10:22 +0000 (14:10 +0000)
63 files changed:
00_Harmony [deleted file]
0pus_Harmony [new file with mode: 0644]
document/Harmony/02_RT_Code_Format.org
shared/authored/Harmony-sync/A_minus_B [new file with mode: 0755]
shared/authored/Harmony-sync/CLI.py [new file with mode: 0755]
shared/authored/Harmony-sync/GitIgnore.py [new file with mode: 0755]
shared/authored/Harmony-sync/Harmony.py [new file with mode: 0644]
shared/authored/Harmony-sync/Harmony_where [new file with mode: 0755]
shared/authored/Harmony-sync/README.org [new file with mode: 0644]
shared/authored/Harmony-sync/command.py [new file with mode: 0644]
shared/authored/Harmony-sync/doc.py [new file with mode: 0644]
shared/authored/Harmony-sync/in_between_and_below [new file with mode: 0755]
shared/authored/Harmony-sync/load_command_module.py [new file with mode: 0644]
shared/authored/Harmony-sync/make_Harmony_tree_dict [new file with mode: 0755]
shared/authored/Harmony-sync/meta.py [new file with mode: 0644]
shared/authored/Harmony-sync/newer [new file with mode: 0755]
shared/authored/Harmony-sync/older [new file with mode: 0755]
shared/authored/Harmony-sync/skeleton.py [new file with mode: 0644]
shared/authored/deprecated/git-empty-dir/CLI.py [new file with mode: 0755]
shared/authored/deprecated/git-empty-dir/Harmony.py [new symlink]
shared/authored/deprecated/git-empty-dir/load_command_module.py [new symlink]
shared/authored/deprecated/git-empty-dir/meta.py [new file with mode: 0644]
shared/authored/deprecated/git-empty-dir/source_sync [new symlink]
shared/authored/deprecated/gitignore_treewalk.py [new file with mode: 0755]
shared/authored/deprecated/walk [new symlink]
shared/authored/deprecated/walk-dir-tree-w-gitignore/CLI.py [new file with mode: 0755]
shared/authored/deprecated/walk-dir-tree-w-gitignore/__init__.py [new file with mode: 0644]
shared/authored/deprecated/walk-dir-tree-w-gitignore/pattern.py [new file with mode: 0644]
shared/authored/deprecated/walk-dir-tree-w-gitignore/printer.py [new file with mode: 0644]
shared/authored/deprecated/walk-dir-tree-w-gitignore/ruleset.py [new file with mode: 0644]
shared/authored/deprecated/walk-dir-tree-w-gitignore/walker.py [new file with mode: 0644]
shared/authored/dir-walk/CLI.py [new file with mode: 0755]
shared/authored/dir-walk/Queue.py [new file with mode: 0755]
shared/authored/dir-walk/TapeMachine.py [new file with mode: 0755]
shared/authored/dir-walk/TreeMachine.py [new file with mode: 0755]
shared/authored/dir-walk/meta.py [new file with mode: 0644]
shared/authored/git-empty-dir/CLI.py [deleted file]
shared/authored/git-empty-dir/Harmony.py [deleted symlink]
shared/authored/git-empty-dir/load_command_module.py [deleted symlink]
shared/authored/git-empty-dir/meta.py [deleted file]
shared/authored/git-empty-dir/source_sync [deleted symlink]
shared/authored/sync/A_minus_B [deleted file]
shared/authored/sync/CLI.py [deleted file]
shared/authored/sync/GitIgnore.py [deleted file]
shared/authored/sync/Harmony.py [deleted file]
shared/authored/sync/Harmony_where [deleted file]
shared/authored/sync/README.org [deleted file]
shared/authored/sync/command.py [deleted file]
shared/authored/sync/doc.py [deleted file]
shared/authored/sync/in_between_and_below [deleted file]
shared/authored/sync/load_command_module.py [deleted file]
shared/authored/sync/make_Harmony_tree_dict [deleted file]
shared/authored/sync/manus_2025_11_20_215471873762383.txt [deleted file]
shared/authored/sync/meta.py [deleted file]
shared/authored/sync/newer [deleted file]
shared/authored/sync/older [deleted file]
shared/authored/sync/skeleton.py [deleted file]
shared/authored/sys [new file with mode: 0644]
shared/authored_10.zip [new file with mode: 0644]
shared/made/walk [new symlink]
temp.sh [new file with mode: 0644]
tool/Harmony_sync [new symlink]
tool/sync [deleted symlink]

diff --git a/00_Harmony b/00_Harmony
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/0pus_Harmony b/0pus_Harmony
new file mode 100644 (file)
index 0000000..e69de29
index 0e9298e..78b2900 100644 (file)
@@ -23,15 +23,25 @@ This document covers:
 4. Line breaks and indentation  
 5. Cross-language guidance  
 
-* 1. Naming Conventions
+* Identifer Naming Conventions
 
-** 1.1 Identifier Naming
+** Identifier Naming
 
 - Types, modules: *PascalCase*  
 - Functions, variables: *snake_case*  
 - Globals: UPPER_SNAKE_CASE  
 
-** 1.2 Suffix Semantics
+** Proper Noun and Acronyms
+
+Even in PascalCase and snake_case, they remain capitalized, as per the English language convention.
+
+E.g.s
+
+- IEEE_publication_count
+- person_Sara_novelties_list
+
+
+** Suffix Semantics
 Optionally suffixes are added to variable names to suggest type or interface.
 
 - =*_dp  :: directory path, not specified if relative or absolute
@@ -42,6 +52,12 @@ Optionally suffixes are added to variable names to suggest type or interface.
 - =*_fpr :: relative file path  
 - =*_fpa :: absolute file path  
 
+If the file system node type is not specifically specified
+
+- =*_fs_nod_p  :: file system node path, not specified if relative or absolute
+- =*_fs_nod_pr :: relative file system node path
+- =*_fs_nod_pa :: absolute file system node path
+
 - =*_list= :: generic ordered items
 - =*_seq=  :: ordered items accessed by index
 
diff --git a/shared/authored/Harmony-sync/A_minus_B b/shared/authored/Harmony-sync/A_minus_B
new file mode 100755 (executable)
index 0000000..f6f7bbb
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+A_minus_B - CLI test driver for skeleton.tree_dict_A_minus_B(A, B)
+
+Usage:
+  A_minus_B <A_root> <B_root>
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Sequence
+
+import meta
+import skeleton
+
+
+def CLI(argv: Sequence[str] | None = None) -> int:
+  if argv is None:
+    argv = sys.argv[1:]
+
+  prog = os.path.basename(sys.argv[0]) if sys.argv else "A_minus_B"
+
+  if len(argv) != 2 or argv[0] in ("-h", "--help"):
+    print(f"Usage: {prog} <A_root> <B_root>")
+    return 1
+
+  A_root = argv[0]
+  B_root = argv[1]
+
+  if not os.path.isdir(A_root):
+    print(f"{prog}: {A_root}: not a directory")
+    return 2
+
+  if not os.path.isdir(B_root):
+    print(f"{prog}: {B_root}: not a directory")
+    return 3
+
+  A = skeleton.tree_dict_make(A_root, None)
+  B = skeleton.tree_dict_make(B_root, None)
+
+  meta.debug_set("tree_dict_A_minus_B")
+
+  _result = skeleton.tree_dict_A_minus_B(A, B)
+
+  return 0
+
+
+if __name__ == "__main__":
+  raise SystemExit(CLI())
diff --git a/shared/authored/Harmony-sync/CLI.py b/shared/authored/Harmony-sync/CLI.py
new file mode 100755 (executable)
index 0000000..f7fb0b0
--- /dev/null
@@ -0,0 +1,282 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+CLI.py - Harmony skeleton checker
+
+Grammar (informal):
+
+  check <command>* [<other>]
+
+  <command>   :: <help> | <no_other> | <has_other>
+
+  <help>      :: version | help | usage
+  <no_other>  :: environment
+  <has_other> :: structure | import | export | suspicious | addendum | all
+
+Commands are sorted into three sets:
+  1. HELP_COMMANDS
+  2. NO_OTHER_COMMANDS
+  3. HAS_OTHER_COMMANDS
+
+At runtime, argv commands are classified into four lists:
+  1. help_list
+  2. no_other_list
+  3. has_other_list
+  4. unclassified_list
+
+If the meta debug set contains the tag "print_command_lists", these four lists
+are printed.
+
+If 'environment' appears in no_other_list, the meta.printenv() helper
+is invoked to print the environment.
+
+For <has_other> commands we compare:
+
+  A = Harmony skeleton tree_dict
+  B = <other> project tree_dict (path is the last argv token when any
+      <has_other> is present before it).
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Sequence
+
+import command
+import doc
+import Harmony
+import meta
+import skeleton
+
+# meta.debug_set("print_command_lists")
+
+# Command tag sets (classification universe)
+HELP_COMMANDS: set[str] = set([
+  "version"
+  ,"help"
+  ,"usage"
+])
+
+NO_OTHER_COMMANDS: set[str] = set([
+  "environment"
+])
+
+HAS_OTHER_COMMANDS: set[str] = set([
+  "structure"
+  ,"import"
+  ,"export"
+  ,"suspicious"
+  ,"addendum"
+  ,"all"
+])
+
+
+def command_type(arg: str) -> str:
+  """
+  Classify a single command token.
+
+  Returns:
+    "Help"         if arg is a help command
+    "NoOther"      if arg is a no_other command
+    "HasOther"     if arg is a has_other command
+    "UnClassified" otherwise
+  """
+  if arg in HELP_COMMANDS:
+    return "Help"
+
+  if arg in NO_OTHER_COMMANDS:
+    return "NoOther"
+
+  if arg in HAS_OTHER_COMMANDS:
+    return "HasOther"
+
+  return "UnClassified"
+
+
+def print_command_lists(
+  help_list: list[str]
+  ,no_other_list: list[str]
+  ,has_other_list: list[str]
+  ,unclassified_list: list[str]
+) -> None:
+  """
+  Print the four classified command lists derived from argv.
+  """
+  print("help_list:", help_list)
+  print("no_other_list:", no_other_list)
+  print("has_other_list:", has_other_list)
+  print("unclassified_list:", unclassified_list)
+
+
+def CLI(argv: Sequence[str] | None = None) -> int:
+  """
+  CLI entrypoint.
+
+  Responsibilities:
+    1. Accept argv (or sys.argv[1:] by default).
+    2. Classify arguments using command_type(), with the last argument
+       treated specially to avoid aliasing.
+    3. Invoke behaviors implied by the commands.
+    4. Return integer status code.
+
+  Argument interpretation:
+
+    Let argv = [a0, a1, ..., aN-1].
+
+    - If N == 0:
+        no commands; nothing to do.
+
+    - If N >= 1:
+        * Classify a0..aN-2.
+          - If any are UnClassified -> error.
+
+        * If any <has_other> appear in a0..aN-2:
+            - aN-1 is treated as <other> path (B_root), not classified.
+
+        * If no <has_other> appear in a0..aN-2:
+            - Classify aN-1:
+                - If UnClassified -> error (unknown command).
+                - If HasOther    -> error (other path not specified).
+                - Else           -> added to Help / NoOther lists.
+  """
+  if argv is None:
+    argv = sys.argv[1:]
+
+  # No arguments: print usage and exit with status 1.
+  if len(argv) == 0:
+    doc.print_usage()
+    return 1
+
+  # No arguments: nothing to do (could later decide to print usage).
+  if len(argv) == 0:
+    return 0
+
+  # Split into head (all but last) and last argument
+  head = argv[:-1]
+  last = argv[-1]
+
+  help_list: list[str] = []
+  no_other_list: list[str] = []
+  has_other_list: list[str] = []
+  unclassified_list: list[str] = []
+
+  # 1. Classify head tokens
+  for arg in head:
+    ct = command_type(arg)
+
+    if ct == "Help":
+      help_list.append(arg)
+    elif ct == "NoOther":
+      no_other_list.append(arg)
+    elif ct == "HasOther":
+      has_other_list.append(arg)
+    else:
+      unclassified_list.append(arg)
+
+  # Any unclassified in the head is an error
+  if len(unclassified_list) > 0:
+    first_bad = unclassified_list[0]
+    print(f"Unrecognized command: {first_bad}")
+    return 5
+
+  head_has_other = (len(has_other_list) > 0)
+
+  B_root: str | None = None
+
+  if head_has_other:
+    # 2A. Any <has_other> in head -> last arg is always <other> path.
+    B_root = os.path.abspath(last)
+  else:
+    # 2B. No <has_other> in head -> classify last.
+    ct = command_type(last)
+
+    if ct == "UnClassified":
+      print(f"Unrecognized command: {last}")
+      return 5
+
+    if ct == "HasOther":
+      print("Other path not specified for has_other command(s).")
+      return 6
+
+    if ct == "Help":
+      help_list.append(last)
+    elif ct == "NoOther":
+      no_other_list.append(last)
+    # ct cannot be HasOther here due to earlier check.
+
+  if meta.debug_has("print_command_lists"):
+    print_command_lists(
+      help_list
+      ,no_other_list
+      ,has_other_list
+      ,unclassified_list
+    )
+
+  # Help handling
+  if len(help_list) > 0:
+    if "version" in help_list:
+      meta.version_print()
+    if "usage" in help_list:
+      doc.print_usage()
+    if "help" in help_list:
+      doc.print_help()
+    return 1
+
+  ret_val = 0
+
+  # No-other commands (environment, etc.)
+  if "environment" in no_other_list:
+    env_status = meta.printenv()
+    if env_status != 0:
+      ret_val = env_status
+
+  # If we still have no has_other commands, we are done.
+  # (Example: just "environment", or just "help/usage".)
+  if len(has_other_list) == 0:
+    return ret_val
+
+  # At this point we know:
+  #   - has_other_list is non-empty
+  #   - B_root must have been set (head_has_other was True)
+  if B_root is None:
+    print("Internal error: B_root not set despite has_other commands.")
+    return 7
+
+  if not os.path.isdir(B_root):
+    print(f"Other project path is not a directory: {B_root}")
+    return 4
+
+  # Determine Harmony root (A_root)
+  status, A_root = Harmony.where()
+
+  if status == "not-found":
+    print("Harmony project not found; normally this command is run from within Harmony.")
+    return 3
+
+  if status == "different":
+    print("Seems we are not running in the Harmony project, will exit.")
+    return 2
+
+  # Build tree_dicts for A (Harmony) and B (other project)
+  A_tree = skeleton.tree_dict_make(A_root, None)
+  B_tree = skeleton.tree_dict_make(B_root, None)
+
+  # Dispatch the <has_other> commands
+  cmd_status = command.dispatch(
+    has_other_list
+    ,A_tree
+    ,B_tree
+    ,A_root
+    ,B_root
+  )
+
+  if cmd_status != 0:
+    ret_val = cmd_status
+
+  return ret_val
+
+
+if __name__ == "__main__":
+  raise SystemExit(CLI())
diff --git a/shared/authored/Harmony-sync/GitIgnore.py b/shared/authored/Harmony-sync/GitIgnore.py
new file mode 100755 (executable)
index 0000000..70c6509
--- /dev/null
@@ -0,0 +1,270 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+GitIgnore.py - minimal .gitignore-based helper for Harmony projects
+
+Behavior:
+
+  1. During initialization, traverse the project tree rooted at
+     <project_path>.
+
+  2. Whenever a directory contains a '.gitignore' file, record:
+       - its relative directory path from the project root
+       - a list of regular expressions compiled from the patterns
+         in that '.gitignore' file
+
+     These are stored in:
+
+       self.rules: Dict[str, List[Pattern]]
+
+       where the key is the directory RELATIVE to the project root:
+         ""          -> project root (top-level .gitignore)
+         "src"       -> src/.gitignore
+         "src/module" -> src/module/.gitignore
+
+  3. check(<path>) -> token:
+
+       - <path> is a path relative to the project root.
+
+       - We compute all prefix directories of <path>, including the
+         root (""), for example:
+
+           path = "a/b/c.py"
+           prefixes = ["", "a", "a/b"]
+
+       - For each prefix, if there are regexes stored for that directory,
+         we collect them.
+
+       - We then test ALL collected regexes against the basename of
+         <path> (the last component only).
+
+       - If ANY regex matches, return 'Ignore'.
+         Otherwise return 'Accept'.
+
+Notes:
+
+  * We implement a simplified subset of .gitignore semantics suitable
+    for your current patterns and add a small base ignore set for
+    always-ignored names such as '.git'.
+"""
+
+from __future__ import annotations
+
+import fnmatch
+import os
+import re
+from typing import Dict, List
+import Harmony
+
+
+class GitIgnore:
+  """
+  GitIgnore(project_path)
+
+  Attributes:
+    project_path:
+      Absolute path to the project root.
+
+    rules:
+      Mapping from relative directory path -> list of compiled regex
+      patterns derived from that directory's '.gitignore' file.
+
+      Example:
+        rules[""]           -> patterns from <root>/.gitignore
+        rules["developer"]  -> patterns from developer/.gitignore
+
+    base_patterns:
+      List of compiled regex patterns applied to the basename of every
+      checked path, independent of any .gitignore file. Currently used
+      to always ignore '.git' directories.
+  """
+
+  def __init__(
+    self
+    ,project_path: str
+  ) -> None:
+    """
+    Initialize a GitIgnore instance with a path to a project and
+    scan for '.gitignore' files.
+    """
+    self.project_path: str = os.path.abspath(project_path)
+    self.rules: Dict[str, List[re.Pattern]] = {}
+
+    # Base patterns: always applied, regardless of .gitignore contents.
+    # These are matched against basenames only.
+    self.base_patterns: List[re.Pattern] = [
+      re.compile(r"^\.git$")    # ignore any basename == ".git"
+    ]
+
+    self._scan_project()
+
+  def _scan_project(self) -> None:
+    """
+    Traverse the project tree and populate self.rules with entries of
+    the form:
+
+      <rel_dir> -> [Pattern, Pattern, ...]
+
+    where <rel_dir> is the directory containing '.gitignore', relative
+    to the project root ("" for root).
+    """
+    root = self.project_path
+
+    for dirpath, dirnames, filenames in os.walk(root, topdown=True):
+      if ".gitignore" not in filenames:
+        continue
+
+      rel_dir = os.path.relpath(dirpath, root)
+      if rel_dir == ".":
+        rel_dir = ""
+
+      gitignore_path = os.path.join(dirpath, ".gitignore")
+      patterns = self._parse_gitignore_file(gitignore_path)
+
+      if patterns:
+        if rel_dir not in self.rules:
+          self.rules[rel_dir] = []
+        self.rules[rel_dir].extend(patterns)
+
+  def _parse_gitignore_file(
+    self
+    ,gitignore_path: str
+  ) -> List[re.Pattern]:
+    """
+    Parse a single '.gitignore' file into a list of compiled regex patterns.
+
+    Simplified rules:
+      - Blank lines and lines starting with '#' are ignored.
+      - Lines containing '/' in the MIDDLE are currently ignored
+        (future extension).
+      - Lines ending with '/' are treated as directory name patterns:
+          '__pycache__/' -> pattern on basename '__pycache__'
+      - All patterns are treated as name globs and compiled via
+        fnmatch.translate(), to be matched against basenames only.
+    """
+    patterns: List[re.Pattern] = []
+
+    try:
+      with open(gitignore_path, "r", encoding="utf-8") as f:
+        for raw_line in f:
+          line = raw_line.strip()
+
+          # Skip comments and blank lines
+          if not line or line.startswith("#"):
+            continue
+
+          # Remove trailing '/' for directory patterns (e.g. '__pycache__/')
+          if line.endswith("/"):
+            line = line[:-1].strip()
+            if not line:
+              continue
+
+          # If there is still a '/' in the line, we do not support this
+          # pattern in this minimal implementation.
+          if "/" in line:
+            continue
+
+          # Compile as a name glob -> regex
+          regex_text = fnmatch.translate(line)
+          patterns.append(re.compile(regex_text))
+
+    except OSError:
+      # If the .gitignore cannot be read, just skip it.
+      return patterns
+
+    return patterns
+
+  def check(
+    self
+    ,path: str
+  ) -> str:
+    """
+    Check a path against the collected .gitignore patterns.
+
+    path:
+      A path relative to the project root.
+
+    Returns:
+      'Ignore' if any applicable pattern matches the basename of the path,
+      otherwise 'Accept'.
+    """
+    # Normalize the incoming path
+    norm = os.path.normpath(path)
+
+    # If the path is '.' or empty, we accept it
+    if norm in ("", "."):
+      return "Accept"
+
+    basename = os.path.basename(norm)
+
+    # First, apply base patterns (always applied).
+    for pat in self.base_patterns:
+      if pat.match(basename):
+        return "Ignore"
+
+    # Build the list of directories that may contribute .gitignore rules.
+    #
+    # For path "a/b/c":
+    #   prefixes: ["", "a", "a/b"]
+    parts = norm.split(os.sep)
+
+    prefixes: List[str] = [""]
+    prefix = None
+    for part in parts[:-1]:
+      if prefix is None:
+        prefix = part
+      else:
+        prefix = os.path.join(prefix, part)
+      prefixes.append(prefix)
+
+    # Collect all patterns from the applicable .gitignore directories
+    for rel_dir in prefixes:
+      dir_patterns = self.rules.get(rel_dir)
+      if not dir_patterns:
+        continue
+
+      for pat in dir_patterns:
+        if pat.match(basename):
+          return "Ignore"
+
+    return "Accept"
+
+
+def test_GitIgnore() -> int:
+  """
+    1. Locate the Harmony project root using Harmony.where().
+    2. Create a GitIgnore instance rooted at that path.
+    3. Print:
+       - directories that have .gitignore rules
+       - directories (relative) that would be ignored by check()
+  """
+  status, Harmony_root = Harmony.where()
+
+  if status == "not-found":
+    print("Harmony project not found; cannot test GitIgnore.")
+    return 1
+
+  if status == "different":
+    print("Warning: Harmony not found, using nearest .git directory for GitIgnore test.")
+
+  gi = GitIgnore(Harmony_root)
+
+  print(".gitignore rule directories (relative to Harmony root):")
+  for rel_dir in sorted(gi.rules.keys()):
+    print(f"  {rel_dir if rel_dir else '.'}")
+
+  print("\nDirectories that would be ignored (relative to Harmony root):")
+  for dirpath, dirnames, filenames in os.walk(Harmony_root, topdown=True):
+    rel_dir = os.path.relpath(dirpath, Harmony_root)
+    if rel_dir == ".":
+      rel_dir = ""
+
+    if gi.check(rel_dir) == "Ignore":
+      print(f"  {rel_dir if rel_dir else '.'}")
+
+  return 0
+
+
+if __name__ == "__main__":
+  raise SystemExit(test_GitIgnore())
diff --git a/shared/authored/Harmony-sync/Harmony.py b/shared/authored/Harmony-sync/Harmony.py
new file mode 100644 (file)
index 0000000..9385507
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+locate the project root
+"""
+
+from __future__ import annotations
+
+import meta
+import os
+import sys
+from typing import Any, Callable, Dict
+
+# where
+#
+# Context / assumptions:
+#   1. This module lives somewhere under the Harmony tree, for example:
+#        /.../Harmony/tool/skeleton/skeleton.py
+#   2. CLI.py is run from somewhere inside the same tree (or a clone).
+#
+# Search behavior:
+#   1. Start from the directory containing this file.
+#   2. Walk upward towards the filesystem root, with limits:
+#      a) Do not move up more than 5 levels.
+#      b) Stop immediately if the current directory contains a
+#         '.git' subdirectory.
+#
+# Result classification:
+#   status is one of:
+#     'found'      -> we found a directory whose basename is 'Harmony'
+#     'different'  -> we stopped at a directory that has a '.git'
+#                     subdirectory, but its basename is not 'Harmony'
+#     'not-found'  -> we hit the 5-level limit or filesystem root
+#                     without finding 'Harmony' or a '.git' directory
+#
+# Path:
+#   - In all cases, the returned path is the last directory inspected:
+#       * the 'Harmony' directory (status 'found'), or
+#       * the directory with '.git' (status 'different'), or
+#       * the directory at the 5-level limit / filesystem root
+#         (status 'not-found').
+#
+# Debug printing:
+#   - If meta.debug_has("print_Harmony_root") is true, print:
+#       * "The Harmony project root found at: {path}"
+#         when status == 'found'
+#       * "Harmony not found, but found: {path}"
+#         when status == 'different'
+#       * "Harmony not found."
+#         when status == 'not-found'
+def where() -> tuple[str, str]:
+  """
+  Locate the Harmony root (or best guess).
+
+  Returns:
+    (status, path)
+  """
+  here = os.path.abspath(__file__)
+  d = os.path.dirname(here)
+
+  harmony_root = None
+  status = "not-found"
+
+  max_up = 5
+  steps = 0
+
+  while True:
+    base = os.path.basename(d)
+
+    # Case 1: exact 'Harmony' directory name
+    if base == "Harmony":
+      harmony_root = d
+      status = "found"
+      break
+
+    # Case 2: stop at a directory that has a '.git' subdirectory
+    git_dir = os.path.join(d, ".git")
+    if os.path.isdir(git_dir):
+      harmony_root = d
+      if base == "Harmony":
+        status = "found"
+      else:
+        status = "different"
+      break
+
+    parent = os.path.dirname(d)
+
+    # Stop if we hit filesystem root
+    if parent == d:
+      harmony_root = d
+      status = "not-found"
+      break
+
+    steps += 1
+    if steps > max_up:
+      # Reached search depth limit; last inspected directory is d
+      harmony_root = d
+      status = "not-found"
+      break
+
+    d = parent
+
+  if harmony_root is None:
+    # Extremely defensive; in practice harmony_root will be set above.
+    harmony_root = d
+
+  root_base = os.path.basename(harmony_root)
+
+  # Warning to stderr if we are not literally in a 'Harmony' directory
+  if root_base != "Harmony":
+    sys.stderr.write(
+      f"WARNING: Harmony root basename is '{root_base}', expected 'Harmony'.\n"
+    )
+
+  if meta.debug_has("print_Harmony_root"):
+    if status == "found":
+      print(f"The Harmony project root found at: {harmony_root}")
+    elif status == "different":
+      print(f"Harmony not found, but found: {harmony_root}")
+    else:
+      print("Harmony not found.")
+
+  return status, harmony_root
+
+def test_where() -> int:
+  """
+  Simple test that prints the Harmony root using the debug flag.
+  """
+  meta.debug_set("print_Harmony_root")
+  status, _root = where()
+  return 0 if status != "not-found" else 1
+
diff --git a/shared/authored/Harmony-sync/Harmony_where b/shared/authored/Harmony-sync/Harmony_where
new file mode 100755 (executable)
index 0000000..9d39f1e
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+Harmony_where - CLI to locate the Harmony project root
+
+Usage:
+  Harmony_where
+
+Prints the status and path returned by Harmony.where().
+"""
+
+from __future__ import annotations
+
+import sys
+
+import Harmony
+
+
+def CLI(argv=None) -> int:
+  # Ignore argv; no arguments expected
+  status, Harmony_root = Harmony.where()
+
+  if status == "found":
+    print(f"Harmony project root found at: {Harmony_root}")
+    return 0
+
+  if status == "different":
+    print(f"Harmony not found, but nearest .git directory is: {Harmony_root}")
+    return 1
+
+  print("Harmony project root not found.")
+  return 2
+
+
+if __name__ == "__main__":
+  raise SystemExit(CLI())
diff --git a/shared/authored/Harmony-sync/README.org b/shared/authored/Harmony-sync/README.org
new file mode 100644 (file)
index 0000000..387780d
--- /dev/null
@@ -0,0 +1,278 @@
+#+TITLE: skeleton_compare – Harmony skeleton comparison tool
+#+AUTHOR: Reasoning Technology
+
+* 1. Overview
+
+1.1
+~skeleton_compare~ compares a Harmony skeleton (=A=) with a derived or legacy project (=B=).
+
+1.2
+It answers:
+
+- How has B diverged from A?
+- What should be imported back into A?
+- What should be exported from A into B?
+- Which nodes are misplaced or suspicious?
+- Which nodes represent valid project-specific extensions?
+
+1.3
+The entrypoint in this project is the symlink:
+
+- =tool/skeleton_compaare=
+
+which points to:
+
+- =tool/skeleton_compare_source/CLI.py=
+
+* 2. Role in the Harmony ecosystem
+
+2.1
+Harmony defines a skeleton layout (directories, leaves, extension points).
+
+2.2
+Projects are expected to:
+
+- start from that skeleton
+- add work under approved extension points
+- keep core structure aligned over time
+
+2.3
+Reality diverges:
+
+- legacy projects that predate Harmony
+- projects with ad-hoc edits in skeleton areas
+- skeleton evolution over months or years
+
+2.4
+~skeleton_compare~ provides:
+
+- a structural comparison
+- a semantic comparison (types, topology)
+- a chronological comparison (mtimes)
+- actionable commands to re-align projects
+
+* 3. High-level behavior
+
+3.1
+Tree construction
+
+1. Build =tree_dict= for A (Harmony skeleton).
+2. Build =tree_dict= for B (other project).
+3. Attach metadata per relative path:
+
+   - =node_type= :: =directory= | =file= | =other= | =constrained=
+   - =dir_info=  :: =root= | =branch= | =leaf= | =NA=
+   - =mtime=     :: float seconds since epoch
+
+3.2
+Git ignore
+
+1. A simplified =.gitignore= model is applied.
+2. Some paths (e.g., =.git=) are always ignored.
+3. Only paths admitted by this model participate in comparisons.
+
+3.3
+Topology classification (relative to A)
+
+1. =in_between= :: under a directory in A, but not under any leaf in A.
+2. =below=      :: under a leaf directory in A.
+3. Neither      :: not under any directory known to A (ignored for most commands).
+
+3.4
+Chronological classification
+
+1. newer(B,A) :: B node has a newer mtime than A at the same path.
+2. older(B,A) :: B node has an older mtime than A at the same path.
+3. A-only     :: path exists in A but not B.
+4. B-only     :: path exists in B but not A.
+
+* 4. Command surface (conceptual)
+
+4.1
+~structure~
+
+1. Compares directory topology.
+2. Reports directories that:
+
+   - exist as directories in A
+   - are missing or non-directories in B
+
+3. Intended use:
+
+   - detect missing branches in projects
+   - detect structural drift
+
+4.2
+~import~
+
+1. Direction: B → A.
+2. Only considers:
+
+   - nodes in the =in_between= region of B
+   - that are new or absent in A
+
+3. Outputs:
+
+   - ~mkdir -p~ commands (when needed)
+   - ~cp --parents -a~ commands for files
+   - a comment list for nodes that cannot be handled automatically
+     (type mismatches, non-file/dir, constrained nodes)
+
+4. Intended use:
+
+   - mine “good ideas” in B that belong in the skeleton
+   - keep Harmony evolving based on real projects
+
+4.3
+~export~
+
+1. Direction: A → B.
+2. Considers:
+
+   - A-only nodes (present in A, missing in B)
+   - nodes where A’s file is newer than B’s file
+
+3. Outputs:
+
+   - ~mkdir -p~ commands for B
+   - ~cp --parents -a~ commands for files
+
+4. Intended use:
+
+   - bring B back into alignment with the current Harmony skeleton
+   - propagate skeleton fixes and improvements into projects
+
+4.4
+~suspicious~
+
+1. Reports nodes in B that are:
+
+   - inside A’s directory structure
+   - but not under any leaf directory
+
+2. Intended use:
+
+   - highlight questionable placements
+   - identify candidates for new skeleton structure
+   - catch misuse of the skeleton (work living in the “framework” layer)
+
+4.5
+~addendum~
+
+1. Reports nodes in B that are:
+
+   - under leaf directories in A
+
+2. Intended use:
+
+   - show work added at the intended extension points
+   - give a quick outline of “project-specific” content layered on Harmony
+
+4.6
+~all~
+
+1. Runs:
+
+   - =structure=
+   - =import=
+   - =export=
+   - =suspicious=
+   - =addendum=
+
+2. Intended use:
+
+   - periodic health check of a project against Harmony
+   - initial analysis when inheriting an old project
+
+* 5. Safety and behavior guarantees
+
+5.1
+No direct modification
+
+1. ~skeleton_compaare~ itself does not modify either tree.
+2. It only prints suggested shell commands.
+3. A human is expected to review and run those commands (or not).
+
+5.2
+Constrained and unknown nodes
+
+1. Some paths are “constrained”:
+
+   - object exists but metadata (e.g., ~mtime~) cannot be safely read
+   - typical for special files or broken links
+
+2. These are:
+
+   - classified as =constrained=
+   - never touched by import/export logic
+   - surfaced in “not handled automatically” lists
+
+5.3
+Robust to legacy layouts
+
+1. A and B are assumed to be non-overlapping roots.
+2. B does not have to be a clean Harmony derivative.
+3. The tool is designed to:
+
+   - tolerate missing branches
+   - tolerate ad-hoc additions
+   - still classify and report differences coherently
+
+* 6. How to run it
+
+6.1
+From inside the Harmony repo:
+
+#+begin_src sh
+cd /path/to/Harmony
+tool/skeleton_compaare help
+tool/skeleton_compaare usage
+tool/skeleton_compaare structure ../SomeProject
+tool/skeleton_compaare all ../Rabbit
+#+end_src
+
+6.2
+The CLI help (from ~doc.py~) is the canonical reference for:
+
+1. grammar and argument rules
+2. meaning of A and B
+3. exact semantics of each command
+
+This =.org= file is a conceptual overview for Harmony toolsmiths and administrators.
+
+* 7. Maintenance notes
+
+7.1
+Core modules
+
+1. =skeleton_compare_source/skeleton.py=
+   - tree construction
+   - topology classification
+   - “newer/older” logic
+   - in-between / below partitioning
+
+2. =skeleton_compare_source/command.py=
+   - high-level command semantics
+   - import/export planning and printing
+
+3. =skeleton_compare_source/CLI.py=
+   - argument classification
+   - environment checks
+   - dispatch to command handlers
+
+7.2
+Change discipline
+
+1. CLI behavior and text should be updated in:
+
+   - =doc.py= (help/usage text)
+   - this =.org= file (conceptual intent)
+
+2. Any behavioral change that affects:
+
+   - classification rules
+   - import/export semantics
+   - constrained handling
+
+   should be reflected here in section 3 or 4.
+
diff --git a/shared/authored/Harmony-sync/command.py b/shared/authored/Harmony-sync/command.py
new file mode 100644 (file)
index 0000000..0ebb856
--- /dev/null
@@ -0,0 +1,539 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+command.py - high-level dispatch for <has_other> Harmony check commands
+
+Commands (semantics):
+
+  structure:
+    - Differences in directory structure: directories present in A but
+      not present as directories in B.
+
+  import:
+    - Shell copy commands to copy:
+        * in-between nodes in B that are newer than A (same relative path), or
+        * in-between nodes in B that do not exist in A at all.
+      Direction: B -> A
+      Also emits:
+        * a mkdir list (directories to create in A)
+        * an "other" list for type mismatches / non-file/dir nodes.
+
+  export:
+    - Shell copy commands to copy:
+        * nodes in A that are newer than B, and
+        * nodes in A that do not exist in B.
+      Direction: A -> B
+      Also emits:
+        * a mkdir list (directories to create in B)
+        * an "other" list for type mismatches / non-file/dir nodes.
+
+  suspicious:
+    - Nodes in B that fall "in between" the Harmony skeleton topology:
+      under some A directory, but not under any A leaf directory.
+      (tree_dict_in_between_and_below(A,B).in_between)
+
+  addendum:
+    - Nodes in B that fall "below" Harmony leaf directories:
+      added work in appropriate extension locations.
+      (tree_dict_in_between_and_below(A,B).below)
+
+  all:
+    - Runs structure, import, export, suspicious, and addendum.
+"""
+
+from __future__ import annotations
+
+import os
+from typing import Any, Dict, List, Tuple
+
+import skeleton
+
+TreeDict = Dict[str, Dict[str, Any]]
+
+
+def shell_quote(
+  s: str
+) -> str:
+  """
+  Minimal single-quote shell quoting.
+  """
+  return "'" + s.replace("'", "'\"'\"'") + "'"
+
+
+def _print_header(
+  title: str
+) -> None:
+  print()
+  print(f"== {title} ==")
+
+
+# ----------------------------------------------------------------------
+# structure: directories in A that are missing / non-directories in B
+# ----------------------------------------------------------------------
+def cmd_structure(
+  A: TreeDict
+  ,B: TreeDict
+) -> int:
+  """
+  structure: differences in directory structure, directories in A - B.
+
+  We include any path where:
+    - A[path].node_type == 'directory', and
+    - either path not in B, or B[path].node_type != 'directory'.
+  """
+  structural: TreeDict = {}
+
+  for path, info_A in A.items():
+    if info_A.get("node_type") != "directory":
+      continue
+
+    info_B = B.get(path)
+    if info_B is None or info_B.get("node_type") != "directory":
+      structural[path] = info_A
+
+  if not structural:
+    _print_header("structure")
+    print("No structural directory differences (A - B).")
+    return 0
+
+  _print_header("structure: directories in A not in B")
+  skeleton.tree_dict_print(structural)
+  return 0
+
+
+# ----------------------------------------------------------------------
+# import: B -> A (mkdir, cp, and "other" list), using in_between_newer
+# ----------------------------------------------------------------------
+
+
+def build_import_commands(
+  A_tree: TreeDict
+  ,B_tree: TreeDict
+  ,A_root: str
+  ,B_root: str
+) -> Tuple[List[str], List[str], List[str]]:
+  """
+  Compute shell commands to update A from B.
+
+  Returns:
+    (mkdir_cmds, cp_cmds, other_list)
+
+  Semantics:
+
+    mkdir_cmds:
+      - Directories that are directories in B, but are missing in A.
+      - We DO NOT auto-resolve type mismatches (e.g. B=directory,
+        A=file); those go into other_list.
+
+    cp_cmds:
+      - Files where:
+          * the path does not exist in A, OR
+          * the node in A is not a file, OR
+          * the B copy is newer than A (mtime comparison).
+      - However, if A has a non-file at that path, we treat it as a
+        type mismatch and add that path to other_list instead of
+        emitting a cp command.
+
+    other_list:
+      - Human-readable notes for:
+          * type mismatches between A and B, and
+          * nodes in B that are neither 'file' nor 'directory'.
+  """
+  mkdir_cmds: List[str] = []
+  cp_cmds: List[str] = []
+  other_list: List[str] = []
+
+  for rel_path, b_info in B_tree.items():
+    b_type = b_info.get("node_type")
+    rel_display = rel_path if rel_path else "."
+
+    a_info = A_tree.get(rel_path)
+    a_type = a_info.get("node_type") if a_info is not None else "MISSING"
+
+    # Case 1: B node is neither file nor directory -> other_list
+    if b_type not in ("file", "directory"):
+      other_list.append(
+        f"{rel_display}: A={a_type}, B={b_type}"
+      )
+      continue
+
+    # Case 2: B directory
+    if b_type == "directory":
+      if a_info is None:
+        # Missing in A: copy the directory recursively.
+        src = os.path.join(B_root, rel_path) if rel_path else B_root
+        # The destination should be the parent directory in A.
+        # os.path.join(A_root, rel_path) gives the full path to the new directory in A.
+        # os.path.dirname of that gives the parent directory.
+        dst = os.path.dirname(os.path.join(A_root, rel_path))
+        mkdir_cmds.append(
+          f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
+        )
+      else:
+        # Exists in A: must also be a directory to be "structurally OK"
+        if a_type != "directory":
+          # Type mismatch: do not mkdir, just report
+          other_list.append(
+            f"{rel_display}: A={a_type}, B=directory"
+          )
+      continue
+
+    # Case 3: B file
+    #   Decide whether to copy B -> A, or report conflict.
+    if a_info is None:
+      # B-only file
+      src = os.path.join(B_root, rel_path) if rel_path else B_root
+      # Destination is the parent directory in A, with a trailing slash
+      dst = os.path.dirname(os.path.join(A_root, rel_path))
+      cp_cmds.append(
+        f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
+      )
+      continue
+
+    # A has something at this path
+    if a_type != "file":
+      # Type mismatch (e.g. A=directory, B=file, or A=other)
+      other_list.append(
+        f"{rel_display}: A={a_type}, B=file"
+      )
+      continue
+
+    # Both files: compare mtime
+    a_mtime = a_info.get("mtime")
+    b_mtime = b_info.get("mtime")
+
+    if isinstance(a_mtime, (int, float)) and isinstance(b_mtime, (int, float)):
+      if b_mtime > a_mtime:
+        src = os.path.join(B_root, rel_path) if rel_path else B_root
+        # Destination is the parent directory in A, with a trailing slash
+        dst = os.path.dirname(os.path.join(A_root, rel_path))
+        cp_cmds.append(
+          f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
+        )
+
+  return mkdir_cmds, cp_cmds, other_list
+
+def cmd_import(
+  A_tree: TreeDict
+  ,B_tree: TreeDict
+  ,A_root: str
+  ,B_root: str
+) -> int:
+  """
+  import: update the skeleton (A) from the project (B),
+  using only in_between_newer nodes.
+  """
+  inb_newer = skeleton.in_between_newer(A_tree, B_tree)
+
+  mkdir_cmds, cp_cmds, other_list = build_import_commands(
+    A_tree
+    ,inb_newer
+    ,A_root
+    ,B_root
+  )
+
+  print("== import: copy from B -> A (in-between newer only) ==")
+  print(f"# A root: {A_root}")
+  print(f"# B root: {B_root}")
+  print("# Only considering in-between files that are new or absent in A.")
+  print("#")
+
+  print("# Directories to copy from B -> A (cp -a):")
+  if mkdir_cmds:
+    for line in mkdir_cmds:
+      print(line)
+  else:
+    print("#   (none)")
+  print("#")
+
+  print("# Files to copy from B -> A (cp -a):")
+  if cp_cmds:
+    for line in cp_cmds:
+      print(line)
+  else:
+    print("#   (none)")
+  print("#")
+
+  print("# Nodes NOT handled automatically (type mismatches / non-file/dir):")
+  if other_list:
+    for rel in other_list:
+      print(f"#   {rel}")
+  else:
+    print("#   (none)")
+
+  return 0
+
+
+# ----------------------------------------------------------------------
+# export: A -> B (mkdir, cp, and "other" list)
+# ----------------------------------------------------------------------
+def build_export_commands(
+  A_tree: TreeDict
+  ,B_tree: TreeDict
+  ,A_root: str
+  ,B_root: str
+) -> Tuple[List[str], List[str], List[str]]:
+  """
+  Compute shell commands to update B from A.
+
+  Returns:
+    (mkdir_cmds, cp_cmds, other_list)
+
+  Semantics:
+
+    mkdir_cmds:
+      - Directories that are directories in A, but are missing in B.
+      - Type mismatches go into other_list.
+
+    cp_cmds:
+      - Files where:
+          * the path does not exist in B, OR
+          * the node in B is not a file, OR
+          * the A copy is newer than B (mtime comparison).
+      - If B has a non-file while A has a file, treat as type mismatch.
+
+    other_list:
+      - Human-readable notes for:
+          * type mismatches between A and B, and
+          * nodes in A that are neither 'file' nor 'directory'.
+  """
+  mkdir_cmds: List[str] = []
+  cp_cmds: List[str] = []
+  other_list: List[str] = []
+
+  # Sort keys to ensure parent directories are processed before their children.
+  sorted_paths = sorted(A_tree.keys(), key=len)
+  included_dirs: Set[str] = set()
+
+  for rel_path in sorted_paths:
+    a_info = A_tree[rel_path]
+    a_type = a_info.get("node_type")
+    rel_display = rel_path if rel_path else "."
+
+    b_info = B_tree.get(rel_path)
+    b_type = b_info.get("node_type") if b_info is not None else "MISSING"
+
+    # Case 1: A node is neither file nor directory -> other_list
+    if a_type not in ("file", "directory"):
+      other_list.append(
+        f"{rel_display}: A={a_type}, B={b_type}"
+      )
+      continue
+
+    # Check if this path is a child of an already included directory
+    is_child_of_included_dir = False
+    for d in included_dirs:
+      if rel_path.startswith(d + os.sep):
+        is_child_of_included_dir = True
+        break
+    
+    if is_child_of_included_dir:
+      continue
+
+    # Case 2: A directory
+    if a_type == "directory":
+      if b_info is None:
+        # Missing in B: copy the directory recursively.
+        src = os.path.join(A_root, rel_path) if rel_path else A_root
+        # The destination should be the parent directory in B.
+        dst = os.path.dirname(os.path.join(B_root, rel_path))
+        mkdir_cmds.append(
+          f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
+        )
+        included_dirs.add(rel_path)
+      else:
+        # Exists in B: must also be directory
+        if b_type != "directory":
+          other_list.append(
+            f"{rel_display}: A=directory, B={b_type}"
+          )
+      continue
+
+    # Case 3: A file
+    if b_info is None:
+      # A-only file
+      src = os.path.join(A_root, rel_path) if rel_path else A_root
+      # Destination is the parent directory in B, with a trailing slash
+      dst = os.path.dirname(os.path.join(B_root, rel_path))
+      cp_cmds.append(
+        f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
+      )
+      continue
+
+    if b_type != "file":
+      other_list.append(
+        f"{rel_display}: A=file, B={b_type}"
+      )
+      continue
+
+    # Both files: compare mtime
+    a_mtime = a_info.get("mtime")
+    b_mtime = b_info.get("mtime")
+
+    if isinstance(a_mtime, (int, float)) and isinstance(b_mtime, (int, float)):
+      if a_mtime > b_mtime:
+        src = os.path.join(A_root, rel_path) if rel_path else A_root
+        # Destination is the parent directory in B, with a trailing slash
+        dst = os.path.dirname(os.path.join(B_root, rel_path))
+        cp_cmds.append(
+          f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
+        )
+
+  return mkdir_cmds, cp_cmds, other_list
+
+
+def cmd_export(
+  A_tree: TreeDict
+  ,B_tree: TreeDict
+  ,A_root: str
+  ,B_root: str
+) -> int:
+  """
+  export: show directory creation and copy commands A -> B.
+  """
+  mkdir_cmds, cp_cmds, other_list = build_export_commands(
+    A_tree
+    ,B_tree
+    ,A_root
+    ,B_root
+  )
+
+  print("== export: copy from A -> B ==")
+  print(f"# A root: {A_root}")
+  print(f"# B root: {B_root}")
+  print("#")
+
+  print("# Directories to copy from A -> B (cp -a):")
+  if mkdir_cmds:
+    for line in mkdir_cmds:
+      print(line)
+  else:
+    print("#   (none)")
+  print("#")
+
+  print("# Files to copy from A -> B (cp -a):")
+  if cp_cmds:
+    for line in cp_cmds:
+      print(line)
+  else:
+    print("#   (none)")
+  print("#")
+
+  print("# Nodes NOT handled automatically (type mismatches / non-file/dir):")
+  if other_list:
+    for rel in other_list:
+      print(f"#   {rel}")
+  else:
+    print("#   (none)")
+
+  return 0
+
+
+# ----------------------------------------------------------------------
+# suspicious / addendum via in_between_and_below
+# ----------------------------------------------------------------------
+def cmd_suspicious(
+  A: TreeDict
+  ,B: TreeDict
+) -> int:
+  """
+  suspicious: nodes in B that fall 'in between' the Harmony skeleton,
+  not under leaf directories.
+  """
+  in_between, _below = skeleton.tree_dict_in_between_and_below(A, B)
+
+  _print_header("suspicious: nodes in-between Harmony leaves")
+
+  if not in_between:
+    print("No suspicious nodes found in B (relative to A).")
+    return 0
+
+  skeleton.tree_dict_print(in_between)
+  return 0
+
+
+def cmd_addendum(
+  A: TreeDict
+  ,B: TreeDict
+) -> int:
+  """
+  addendum: nodes in B that fall 'below' Harmony leaf directories.
+  """
+  _in_between, below = skeleton.tree_dict_in_between_and_below(A, B)
+
+  _print_header("addendum: nodes added under Harmony leaves")
+
+  if not below:
+    print("No addendum nodes found in B (relative to A).")
+    return 0
+
+  skeleton.tree_dict_print(below)
+  return 0
+
+
+# ----------------------------------------------------------------------
+# Top-level dispatcher
+# ----------------------------------------------------------------------
+def dispatch(
+  has_other_list: List[str]
+  ,A: TreeDict
+  ,B: TreeDict
+  ,A_root: str
+  ,B_root: str
+) -> int:
+  """
+  Dispatch <has_other> commands.
+
+  has_other_list:
+    List of command tokens (subset of:
+      'structure', 'import', 'export', 'suspicious', 'addendum', 'all').
+
+  A, B:
+    tree_dicts for Harmony skeleton (A) and <other> project (B).
+
+  A_root, B_root:
+    Root paths corresponding to A and B (for copy commands).
+  """
+  cmds = set(has_other_list)
+
+  if "all" in cmds:
+    cmds.update([
+      "structure"
+      ,"import"
+      ,"export"
+      ,"suspicious"
+      ,"addendum"
+    ])
+
+  ordered = [
+    "structure"
+    ,"import"
+    ,"export"
+    ,"suspicious"
+    ,"addendum"
+  ]
+
+  status = 0
+
+  for name in ordered:
+    if name not in cmds:
+      continue
+
+    if name == "structure":
+      rc = cmd_structure(A, B)
+    elif name == "import":
+      rc = cmd_import(A, B, A_root, B_root)
+    elif name == "export":
+      rc = cmd_export(A, B, A_root, B_root)
+    elif name == "suspicious":
+      rc = cmd_suspicious(A, B)
+    elif name == "addendum":
+      rc = cmd_addendum(A, B)
+    else:
+      rc = 0
+
+    if rc != 0:
+      status = rc
+
+  return status
diff --git a/shared/authored/Harmony-sync/doc.py b/shared/authored/Harmony-sync/doc.py
new file mode 100644 (file)
index 0000000..3198b96
--- /dev/null
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+doc.py - usage and help text for the Harmony 'check' tool
+
+Grammar (informal):
+
+  <prog> <command>* [<other>]
+
+  <command>   :: <help> | <no_other> | <has_other>
+
+  <help>      :: version | help | usage
+  <no_other>  :: environment
+  <has_other> :: structure | import | export | suspicious | addendum | all
+"""
+
+from __future__ import annotations
+
+import meta
+import os
+import sys
+from typing import TextIO
+
+
+def prog_name() -> str:
+  """
+  Return the program name as invoked by the user.
+
+  Typically:
+    - basename(sys.argv[0]) when running from the shell.
+    - Falls back to 'check' if argv[0] is empty.
+  """
+  raw = sys.argv[0] if sys.argv and sys.argv[0] else "check"
+  base = os.path.basename(raw) or raw
+  return base
+
+
+def _usage_text(prog: str) -> str:
+  return f"""\
+Usage:
+  {prog} <command>* [<other>]
+
+Where:
+  <command>   :: <help> | <no_other> | <has_other>
+
+  <help>      :: version | help | usage
+  <no_other>  :: environment
+  <has_other> :: structure | import | export | suspicious | addendum | all
+"""
+
+def _help_text(prog: str) -> str:
+  return f"""\
+{prog} - Harmony skeleton integrity and metadata checker
+
+Syntax:
+  {prog} <command>* [<other>]
+
+Where:
+  <other>   :: path
+  <command> :: <help> | <no_other> | <has_other>
+
+  <help>      :: version | help | usage
+  <no_other>  :: environment
+  <has_other> :: structure | import | export | suspicious | addendum | all
+
+Argument rules (informal):
+  1. <help> commands are processed first, and then the program returns.
+     Hence if any help commands are present, the remaining commands
+     are ignored.
+
+  2. We assume {prog} is run within the Harmony skeleton, or a skeleton
+     derived directly from it. This is the 'default skeleton', or simply 'A'.
+
+  3. The <other> path is the directory of a project that is assumed to
+     be built upon the default skeleton. This second project root is
+     called 'B'.
+
+  4. If none of the commands require an <other> path, then <other>
+     must not be given. If at least one command requires <other>, then
+     <other> is required. Commands that require a path are called
+     <has_other> commands.
+
+  5. Implementation detail:
+       All arguments except the final one are interpreted strictly as
+       command tokens. If any of those are <has_other>, the final argument
+       is taken as <other>. If none of the earlier tokens are <has_other>,
+       the final argument is also treated as a command token.
+
+Roots:
+  A = Skeleton project root (auto-detected). Usually the Harmony skeleton.
+  B = <other> project root (supplied when required).
+
+{prog} compares A with B. Differences may come from:
+  - edits to the skeleton itself,
+  - edits to skeleton files inside B,
+  - or new files/directories added to B.
+Conceptually, A and B are any two non-overlapping directory trees.
+
+Command semantics:
+  structure
+    - Report directory-structure differences:
+        directories present in A that are missing in B or not
+        directories in B.
+    - Output: table of such directories.
+
+  import
+    - Update A from B using only "in-between newer" files:
+        * files in B that lie in the 'in-between' region relative to A, and
+        * are newer than A or absent from A.
+    - Also emits:
+        * directories to create in A,
+        * files to copy (B -> A),
+        * nodes that cannot be handled automatically (type mismatches,
+          constrained nodes, non-file/dir nodes).
+    - Direction: B -> A
+
+  export
+    - Update B from A:
+        * files in A newer than B at the same path,
+        * files present in A but missing in B.
+    - Also emits:
+        * directories to create in B,
+        * files to copy (A -> B),
+        * nodes that cannot be handled automatically.
+    - Direction: A -> B
+
+  suspicious
+    - Report B nodes that lie "in-between" Harmony leaves:
+        under a directory from A, but not under any leaf directory of A.
+    - Indicates questionable placements or missing skeleton structure.
+
+  addendum
+    - Report B nodes located "below" Harmony leaf directories:
+        project-specific additions placed in proper extension points.
+
+  all
+    - Run: structure, import, export, suspicious, addendum (in that order).
+
+Notes:
+  - tree_dict traversal respects a simplified .gitignore model plus
+    always-ignored patterns (e.g. '.git').
+  - Timestamps are formatted via the Z helper in UTC (ISO 8601).
+"""
+
+def print_usage(
+  stream: TextIO | None = None
+) -> None:
+  """
+  Print the usage text to the given stream (default: sys.stdout),
+  using the actual program name as invoked.
+  """
+  if stream is None:
+    stream = sys.stdout
+
+  text = _usage_text(prog_name())
+  stream.write(text)
+  if not text.endswith("\n"):
+    stream.write("\n")
+
+
+def print_help(
+  stream: TextIO | None = None
+) -> None:
+  """
+  Print the help text to the given stream (default: sys.stdout),
+  using the actual program name as invoked.
+  """
+  if stream is None:
+    stream = sys.stdout
+
+  utext = _usage_text(prog_name())
+  htext = _help_text(prog_name())
+
+  stream.write(utext)
+  if not utext.endswith("\n"):
+    stream.write("\n")
+
+  stream.write("\n")
+  stream.write(htext)
+  if not htext.endswith("\n"):
+    stream.write("\n")
diff --git a/shared/authored/Harmony-sync/in_between_and_below b/shared/authored/Harmony-sync/in_between_and_below
new file mode 100755 (executable)
index 0000000..2993767
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+in_between_and_below - CLI test driver for skeleton.tree_dict_in_between_and_below(A, B)
+
+Usage:
+  in_between_and_below <A_root> <B_root>
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Sequence
+
+import meta
+import skeleton
+
+
+def CLI(argv: Sequence[str] | None = None) -> int:
+  if argv is None:
+    argv = sys.argv[1:]
+
+  prog = os.path.basename(sys.argv[0]) if sys.argv else "in_between_and_below"
+
+  if len(argv) != 2 or argv[0] in ("-h", "--help"):
+    print(f"Usage: {prog} <A_root> <B_root>")
+    return 1
+
+  A_root = argv[0]
+  B_root = argv[1]
+
+  if not os.path.isdir(A_root):
+    print(f"{prog}: {A_root}: not a directory")
+    return 2
+
+  if not os.path.isdir(B_root):
+    print(f"{prog}: {B_root}: not a directory")
+    return 3
+
+  A = skeleton.tree_dict_make(A_root, None)
+  B = skeleton.tree_dict_make(B_root, None)
+
+  meta.debug_set("tree_dict_in_between_and_below")
+
+  _result = skeleton.tree_dict_in_between_and_below(A, B)
+
+  return 0
+
+
+if __name__ == "__main__":
+  raise SystemExit(CLI())
diff --git a/shared/authored/Harmony-sync/load_command_module.py b/shared/authored/Harmony-sync/load_command_module.py
new file mode 100644 (file)
index 0000000..226b6dd
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+load_command_module.py - locate and import Python command modules from $PATH
+
+Behavior:
+  1. Search $PATH for an executable with the given command name.
+  2. Prefer a path containing '/incommon/'.
+  3. If only /usr/bin/<command> is found, raise an error saying we were
+     looking for the incommon version.
+  4. Import the chosen script as a Python module, even if it has no .py
+     extension, by forcing a SourceFileLoader.
+"""
+
+from __future__ import annotations
+
+import importlib.util
+import os
+from importlib.machinery import SourceFileLoader
+from types import ModuleType
+from typing import List
+
+
+def _find_command_candidates(command_name: str) -> List[str]:
+  """
+  Return a list of absolute paths to executables named `command_name`
+  found on $PATH.
+  """
+  paths: list[str] = []
+
+  path_env = os.environ.get("PATH", "")
+  for dir_path in path_env.split(os.pathsep):
+    if not dir_path:
+      continue
+    candidate = os.path.join(dir_path, command_name)
+    if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
+      paths.append(os.path.realpath(candidate))
+
+  return paths
+
+
+def load_command_module(command_name: str) -> ModuleType:
+  """
+  Locate an executable named `command_name` on $PATH and load it
+  as a Python module.
+
+  Selection policy:
+    1. Prefer any path containing '/incommon/'.
+    2. If only /usr/bin/<command_name> candidates exist, raise an error
+       saying we were looking for the incommon version.
+    3. If no candidate is found, raise an error.
+
+  Implementation detail:
+    Because the incommon command may lack a .py suffix, we explicitly
+    construct a SourceFileLoader rather than relying on the default
+    extension-based loader resolution.
+  """
+  candidates = _find_command_candidates(command_name)
+
+  incommon_candidates = [
+    p
+    for p in candidates
+    if "/incommon/" in p
+  ]
+
+  usrbin_candidates = [
+    p
+    for p in candidates
+    if p.startswith("/usr/bin/")
+  ]
+
+  if incommon_candidates:
+    target = incommon_candidates[0]
+  elif usrbin_candidates:
+    raise RuntimeError(
+      f"Found /usr/bin/{command_name}, but expected the incommon Python "
+      f"{command_name} module on PATH."
+    )
+  else:
+    raise RuntimeError(
+      f"Could not find an incommon '{command_name}' module on PATH."
+    )
+
+  module_name = f"rt_incommon_{command_name}"
+
+  loader = SourceFileLoader(
+    module_name
+    ,target
+  )
+  spec = importlib.util.spec_from_loader(
+    module_name
+    ,loader
+  )
+  if spec is None:
+    raise RuntimeError(f"Failed to create spec for {command_name} from {target}")
+
+  module = importlib.util.module_from_spec(spec)
+  # spec.loader is the SourceFileLoader we just created
+  assert spec.loader is not None
+  spec.loader.exec_module(module)
+
+  return module
diff --git a/shared/authored/Harmony-sync/make_Harmony_tree_dict b/shared/authored/Harmony-sync/make_Harmony_tree_dict
new file mode 100755 (executable)
index 0000000..2ed3cea
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+skeleton_test - build and print the Harmony tree_dict
+
+Usage:
+  skeleton_test
+
+Behavior:
+  1. Locate the Harmony project root via Harmony.where().
+  2. Enable 'tree_dict_print' debug flag.
+  3. Call skeleton.tree_dict_make(Harmony_root, None).
+
+The skeleton.tree_dict_make() function is expected to call
+tree_dict_print() when the 'tree_dict_print' debug flag is set.
+"""
+
+from __future__ import annotations
+
+import sys
+
+import Harmony
+import meta
+import skeleton
+
+
+def CLI(argv=None) -> int:
+  # No arguments expected
+  status, Harmony_root = Harmony.where()
+
+  if status == "not-found":
+    print("Harmony project not found; cannot build tree_dict.")
+    return 1
+
+  if status == "different":
+    print("Warning: Harmony not found, using nearest .git directory for tree_dict.")
+
+  # Enable printing inside tree_dict_make
+  meta.debug_set("tree_dict_print")
+
+  _tree = skeleton.tree_dict_make(Harmony_root, None)
+
+  return 0
+
+
+if __name__ == "__main__":
+  raise SystemExit(CLI())
diff --git a/shared/authored/Harmony-sync/meta.py b/shared/authored/Harmony-sync/meta.py
new file mode 100644 (file)
index 0000000..5c8da89
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+meta.py - thin wrappers around command modules
+
+Current responsibilities:
+  1. Load the incommon 'printenv' command module (no .py extension)
+     using load_command_module.load_command_module().
+  2. Expose printenv() here, calling the imported printenv() work
+     function with default arguments (equivalent to running without
+     any CLI arguments).
+  3. Provide a simple version printer for this meta module.
+  4. Provide a small debug tag API (set/clear/has).
+"""
+
+from __future__ import annotations
+
+import datetime
+from load_command_module import load_command_module
+
+
+# Load the incommon printenv module once at import time
+_PRINTENV_MODULE = load_command_module("printenv")
+_Z_MODULE = load_command_module("Z")
+
+
+# Meta module version
+_major = 1
+_minor = 7
+def version_print() -> None:
+  """
+  Print the meta module version as MAJOR.MINOR.
+  """
+  print(f"{_major}.{_minor}")
+
+
+# Debug tag set and helpers
+_debug = set([
+])
+
+
+def debug_set(tag: str) -> None:
+  """
+  Add a debug tag to the meta debug set.
+  """
+  _debug.add(tag)
+
+
+def debug_clear(tag: str) -> None:
+  """
+  Remove a debug tag from the meta debug set, if present.
+  """
+  _debug.discard(tag)
+
+
+def debug_has(tag: str) -> bool:
+  """
+  Return True if the given debug tag is present.
+  """
+  return tag in _debug
+
+
+# Touch the default tag once so static checkers do not complain about
+# unused helpers when imported purely for side-effects.
+debug_has("Command")
+
+
+def printenv() -> int:
+  """
+  Call the imported printenv() work function with default arguments:
+    - no null termination
+    - no newline quoting
+    - no specific names (print full environment)
+    - prog name 'printenv'
+  """
+  return _PRINTENV_MODULE.printenv(
+    False      # null_terminate
+    ,False     # quote_newlines
+    ,[]        # names
+    ,"printenv"
+  )
+
+
+def z_format_mtime(
+  mtime: float
+) -> str:
+  """
+  Format a POSIX mtime (seconds since epoch, UTC) using the Z module.
+
+  Uses Z.ISO8601_FORMAT and Z.make_timestamp(dt=...).
+  """
+  dt = datetime.datetime.fromtimestamp(mtime, datetime.timezone.utc)
+  return _Z_MODULE.make_timestamp(
+    fmt=_Z_MODULE.ISO8601_FORMAT
+    ,dt=dt
+  )
diff --git a/shared/authored/Harmony-sync/newer b/shared/authored/Harmony-sync/newer
new file mode 100755 (executable)
index 0000000..30aa373
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+newer - CLI test driver for skeleton.tree_dict_newer(A, B)
+
+Usage:
+  newer <A_root> <B_root>
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Sequence
+
+import meta
+import skeleton
+
+
+def CLI(argv: Sequence[str] | None = None) -> int:
+  if argv is None:
+    argv = sys.argv[1:]
+
+  prog = os.path.basename(sys.argv[0]) if sys.argv else "newer"
+
+  if len(argv) != 2 or argv[0] in ("-h", "--help"):
+    print(f"Usage: {prog} <A_root> <B_root>")
+    return 1
+
+  A_root = argv[0]
+  B_root = argv[1]
+
+  if not os.path.isdir(A_root):
+    print(f"{prog}: {A_root}: not a directory")
+    return 2
+
+  if not os.path.isdir(B_root):
+    print(f"{prog}: {B_root}: not a directory")
+    return 3
+
+  A = skeleton.tree_dict_make(A_root, None)
+  B = skeleton.tree_dict_make(B_root, None)
+
+  meta.debug_set("tree_dict_newer")
+
+  _result = skeleton.tree_dict_newer(A, B)
+
+  return 0
+
+
+if __name__ == "__main__":
+  raise SystemExit(CLI())
diff --git a/shared/authored/Harmony-sync/older b/shared/authored/Harmony-sync/older
new file mode 100755 (executable)
index 0000000..f8ff24d
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+older - CLI test driver for skeleton.tree_dict_older(A, B)
+
+Usage:
+  older <A_root> <B_root>
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Sequence
+
+import meta
+import skeleton
+
+
+def CLI(argv: Sequence[str] | None = None) -> int:
+  if argv is None:
+    argv = sys.argv[1:]
+
+  prog = os.path.basename(sys.argv[0]) if sys.argv else "older"
+
+  if len(argv) != 2 or argv[0] in ("-h", "--help"):
+    print(f"Usage: {prog} <A_root> <B_root>")
+    return 1
+
+  A_root = argv[0]
+  B_root = argv[1]
+
+  if not os.path.isdir(A_root):
+    print(f"{prog}: {A_root}: not a directory")
+    return 2
+
+  if not os.path.isdir(B_root):
+    print(f"{prog}: {B_root}: not a directory")
+    return 3
+
+  A = skeleton.tree_dict_make(A_root, None)
+  B = skeleton.tree_dict_make(B_root, None)
+
+  meta.debug_set("tree_dict_older")
+
+  _result = skeleton.tree_dict_older(A, B)
+
+  return 0
+
+
+if __name__ == "__main__":
+  raise SystemExit(CLI())
diff --git a/shared/authored/Harmony-sync/skeleton.py b/shared/authored/Harmony-sync/skeleton.py
new file mode 100644 (file)
index 0000000..ae272f9
--- /dev/null
@@ -0,0 +1,576 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+skeleton.py - helpers for working with the Harmony skeleton tree
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Any, Callable, Dict, List, Set
+
+import meta
+from GitIgnore import GitIgnore
+import Harmony
+
+TreeDict = Dict[str, Dict[str, Any]]
+
+# tree_dict_make / tree_dict_print
+#
+# Build a dictionary describing a project tree, respecting GitIgnore.
+#
+# tree_dict_make(<path>, <checksum_fn>) -> tree_dict
+#
+#   <checksum_fn>(<abs_path>) -> bignum | None
+#
+#   Keys of tree_dict:
+#     - Relative paths from <path>; the root itself is stored under "".
+#
+#   Values are dicts with:
+#     1. 'mtime'     : last modification time (float seconds) or None
+#     2. 'node_type' : 'file', 'directory', 'other', or 'constrained'
+#     3. 'dir_info'  : 'NA', 'leaf', 'branch', 'root'
+#     4. 'checksum'  : present only for file nodes when checksum_fn is
+#                      not None
+#
+#   Traversal:
+#     - Directories whose relative path GitIgnore.check() marks as
+#       'Ignore' are included in tree_dict but not traversed further.
+def tree_dict_make(
+  path: str
+  ,checksum_fn: Callable[[str], int] | None
+) -> Dict[str, Dict[str, Any]]:
+  """
+  Build a tree_dict for the subtree rooted at <path>, respecting GitIgnore.
+
+  Semantics (current):
+    * Any path (directory or file) for which GitIgnore.check(<rel_path>)
+      returns 'Ignore' is completely omitted from the tree_dict.
+    * The root directory ('') is always included.
+    * Directory dir_info:
+        - 'root'   for the root
+        - 'branch' for directories that have child directories
+                    (after GitIgnore filtering)
+        - 'leaf'   for directories with no child directories
+    * Non-directory dir_info:
+        - 'NA'
+    * Symlinks are classified as file/directory/other based on what
+      they point to, if accessible.
+    * If any filesystem access needed for classification/mtime raises,
+      the node is recorded as node_type='constrained', dir_info='NA',
+      mtime=None, and we do not attempt checksum.
+  """
+  root = os.path.abspath(path)
+  gi = GitIgnore(root)
+
+  tree_dict: Dict[str, Dict[str, Any]] = {}
+
+  for dirpath, dirnames, filenames in os.walk(root, topdown=True):
+    rel_dir = os.path.relpath(dirpath, root)
+    if rel_dir == ".":
+      rel_dir = ""
+
+    # Skip ignored directories (except the root).
+    if rel_dir != "" and gi.check(rel_dir) == "Ignore":
+      dirnames[:] = []
+      continue
+
+    # Filter child directories by GitIgnore so dir_info reflects
+    # only directories we will actually traverse.
+    kept_dirnames: List[str] = []
+    for dn in list(dirnames):
+      child_rel = dn if rel_dir == "" else os.path.join(rel_dir, dn)
+      if gi.check(child_rel) == "Ignore":
+        dirnames.remove(dn)
+      else:
+        kept_dirnames.append(dn)
+
+    # Record the directory node itself
+    dir_abs = dirpath
+    try:
+      dir_mtime = os.path.getmtime(dir_abs)
+      dir_node_type = "directory"
+      if rel_dir == "":
+        dir_info = "root"
+      elif kept_dirnames:
+        dir_info = "branch"
+      else:
+        dir_info = "leaf"
+    except OSError:
+      # Could not stat the directory: treat as constrained.
+      dir_mtime = None
+      dir_node_type = "constrained"
+      dir_info = "NA"
+
+    tree_dict[rel_dir] = {
+      "mtime": dir_mtime
+      ,"node_type": dir_node_type
+      ,"dir_info": dir_info
+    }
+
+    # For non-ignored directories, record files within
+    for name in filenames:
+      abs_path = os.path.join(dirpath, name)
+      if rel_dir == "":
+        rel_path = name
+      else:
+        rel_path = os.path.join(rel_dir, name)
+
+      if gi.check(rel_path) == "Ignore":
+        continue
+
+      # Wrap classification + mtime in one try/except so any failure
+      # marks the node as constrained.
+      try:
+        if os.path.islink(abs_path):
+          # Symlink: classify by target if possible
+          if os.path.isdir(abs_path):
+            node_type = "directory"
+            dir_info_f = "branch"
+          elif os.path.isfile(abs_path):
+            node_type = "file"
+            dir_info_f = "NA"
+          else:
+            node_type = "other"
+            dir_info_f = "NA"
+          mtime = os.path.getmtime(abs_path)
+        else:
+          # Normal node
+          if os.path.isfile(abs_path):
+            node_type = "file"
+            dir_info_f = "NA"
+          elif os.path.isdir(abs_path):
+            node_type = "directory"
+            dir_info_f = "branch"
+          else:
+            node_type = "other"
+            dir_info_f = "NA"
+          mtime = os.path.getmtime(abs_path)
+      except OSError:
+        # Anything that blows up during classification/stat becomes
+        # constrained; we do not attempt checksum for these.
+        node_type = "constrained"
+        dir_info_f = "NA"
+        mtime = None
+
+      info: Dict[str, Any] = {
+        "mtime": mtime
+        ,"node_type": node_type
+        ,"dir_info": dir_info_f
+      }
+
+      if node_type == "file" and checksum_fn is not None and isinstance(mtime, (int, float)):
+        info["checksum"] = checksum_fn(abs_path)
+
+      tree_dict[rel_path] = info
+
+  if meta.debug_has("tree_dict_print"):
+    tree_dict_print(tree_dict)
+
+  return tree_dict
+
+def tree_dict_print(
+  tree_dict: Dict[str, Dict[str, Any]]
+) -> None:
+  """
+  Pretty-print a tree_dict produced by tree_dict_make() in fixed-width columns:
+
+    [type]  [dir]  [mtime]  [checksum?]  [relative path]
+
+  Only the values are printed in each column (no 'field=' prefixes).
+  mtime is formatted via the Z module for human readability.
+  """
+  entries: List[tuple[str, str, str, str, str]] = []
+  has_checksum = False
+
+  for rel_path in sorted(tree_dict.keys()):
+    info = tree_dict[rel_path]
+    display_path = rel_path if rel_path != "" else "."
+
+    type_val = str(info.get("node_type", ""))
+    dir_val = str(info.get("dir_info", ""))
+
+    raw_mtime = info.get("mtime")
+    if isinstance(raw_mtime, (int, float)):
+      mtime_val = meta.z_format_mtime(raw_mtime)
+    else:
+      mtime_val = str(raw_mtime)
+
+    if "checksum" in info:
+      checksum_val = str(info["checksum"])
+      has_checksum = True
+    else:
+      checksum_val = ""
+
+    entries.append((
+      type_val
+      ,dir_val
+      ,mtime_val
+      ,checksum_val
+      ,display_path
+    ))
+
+  # Compute column widths
+  type_w = 0
+  dir_w = 0
+  mtime_w = 0
+  checksum_w = 0
+
+  for type_val, dir_val, mtime_val, checksum_val, _ in entries:
+    if len(type_val) > type_w:
+      type_w = len(type_val)
+    if len(dir_val) > dir_w:
+      dir_w = len(dir_val)
+    if len(mtime_val) > mtime_w:
+      mtime_w = len(mtime_val)
+    if has_checksum and len(checksum_val) > checksum_w:
+      checksum_w = len(checksum_val)
+
+  print("Tree dictionary contents:")
+  for type_val, dir_val, mtime_val, checksum_val, display_path in entries:
+    line = "  "
+    line += type_val.ljust(type_w)
+    line += "  "
+    line += dir_val.ljust(dir_w)
+    line += "  "
+    line += mtime_val.ljust(mtime_w)
+
+    if has_checksum:
+      line += "  "
+      line += checksum_val.ljust(checksum_w)
+
+    line += "  "
+    line += display_path
+
+    print(line)
+
+
+def tree_dict_A_minus_B(
+  A: Dict[str, Dict[str, Any]]
+  ,B: Dict[str, Dict[str, Any]]
+) -> Dict[str, Dict[str, Any]]:
+  """
+  Compute the set difference of two tree_dicts at the key level:
+
+    Result = A \\ B
+
+  That is, return a new tree_dict containing only those entries whose
+  keys are present in A but NOT present in B.
+  """
+  result: Dict[str, Dict[str, Any]] = {}
+
+  B_keys = set(B.keys())
+
+  for key, info in A.items():
+    if key not in B_keys:
+      result[key] = info
+
+  if meta.debug_has("tree_dict_A_minus_B"):
+    tree_dict_print(result)
+
+  return result
+
+
+def tree_dict_in_between_and_below(
+  A: Dict[str, Dict[str, Any]]
+  ,B: Dict[str, Dict[str, Any]]
+) -> tuple[Dict[str, Dict[str, Any]], Dict[str, Dict[str, Any]]]:
+  """
+  Partition nodes of B into two topology-based sets relative to A:
+
+    1. in_between:
+         Nodes in B that lie under at least one directory node in A,
+         but do NOT lie under any leaf directory of A.
+
+    2. below:
+         Nodes in B that lie under at least one leaf directory of A.
+
+  Definitions (relative to A's directory topology):
+
+    - A directory node in A is any key whose info['node_type'] == 'directory'.
+
+    - A leaf directory in A is a directory that has no *other* directory
+      in A as a proper descendant. The project root ('') is therefore
+      never a leaf (it always has descendant directories if the tree is
+      non-trivial).
+
+    - “Lies under”:
+        * For a path p in B, we look at the chain of directory ancestors
+          (including the root "") and, if p itself is a directory, p
+          itself. Any of those that appear as directory keys in A are
+          considered directory ancestors in A.
+
+        * If any of those ancestors is a leaf in A, p goes to 'below'.
+          Otherwise, if there is at least one directory ancestor in A,
+          p goes to 'in_between'.
+
+    - Nodes in B that do not lie under any directory in A are ignored.
+
+  Returns:
+    (in_between_dict, below_dict), both keyed like B and containing
+    copies of the info dicts from B.
+  """
+  # 1. Collect all directory keys from A
+  A_dir_keys: Set[str] = set(
+    key for key, info in A.items()
+    if info.get("node_type") == "directory"
+  )
+
+  # 2. Compute leaf directories in A
+  leaf_dirs: Set[str] = set()
+
+  for d in A_dir_keys:
+    if d == "":
+      continue
+
+    has_child_dir = False
+    prefix = d + os.sep
+
+    for other in A_dir_keys:
+      if other == d:
+        continue
+      if other.startswith(prefix):
+        has_child_dir = True
+        break
+
+    if not has_child_dir:
+      leaf_dirs.add(d)
+
+  in_between: Dict[str, Dict[str, Any]] = {}
+  below: Dict[str, Dict[str, Any]] = {}
+
+  for key, info in B.items():
+    # Skip B's root
+    if key in ("", "."):
+      continue
+
+    parts = key.split(os.sep)
+
+    # Build directory ancestor chain
+    node_is_dir = (info.get("node_type") == "directory")
+
+    ancestors: List[str] = [""]
+    prefix = None
+
+    if node_is_dir:
+      upto = parts
+    else:
+      upto = parts[:-1]
+
+    for part in upto:
+      if prefix is None:
+        prefix = part
+      else:
+        prefix = os.path.join(prefix, part)
+      ancestors.append(prefix)
+
+    # Filter ancestors to those that exist as directories in A
+    ancestors_in_A = [d for d in ancestors if d in A_dir_keys]
+
+    if not ancestors_in_A:
+      # This B node is not under any directory from A; ignore it.
+      continue
+
+    # Any leaf ancestor in A?
+    has_leaf_ancestor = any(d in leaf_dirs for d in ancestors_in_A)
+
+    if has_leaf_ancestor:
+      below[key] = info
+    else:
+      in_between[key] = info
+
+  if meta.debug_has("tree_dict_in_between_and_below"):
+    merged: Dict[str, Dict[str, Any]] = {}
+    merged.update(in_between)
+    merged.update(below)
+    tree_dict_print(merged)
+
+  return in_between, below
+
+
+def tree_dict_newer(
+  A: Dict[str, Dict[str, Any]]
+  ,B: Dict[str, Dict[str, Any]]
+) -> Dict[str, Dict[str, Any]]:
+  """
+  Return a dictionary of nodes from B that are newer than their
+  corresponding nodes in A.
+
+  For each key k:
+
+    - If k exists in both A and B, and
+    - B[k]['mtime'] > A[k]['mtime'],
+
+  then k is included in the result with value B[k].
+
+  Keys that are only in B (not in A) are ignored here.
+  """
+  result: Dict[str, Dict[str, Any]] = {}
+
+  for key, info_B in B.items():
+    info_A = A.get(key)
+    if info_A is None:
+      continue
+
+    mtime_A = info_A.get("mtime")
+    mtime_B = info_B.get("mtime")
+
+    if mtime_A is None or mtime_B is None:
+      continue
+
+    if mtime_B > mtime_A:
+      result[key] = info_B
+
+  if meta.debug_has("tree_dict_newer"):
+    tree_dict_print(result)
+
+  return result
+
+
+def tree_dict_older(
+  A: Dict[str, Dict[str, Dict[str, Any]]]
+  ,B: Dict[str, Dict[str, Dict[str, Any]]]
+) -> Dict[str, Dict[str, Any]]:
+  """
+  Return a dictionary of nodes from B that are older than their
+  corresponding nodes in A.
+
+  For each key k:
+
+    - If k exists in both A and B, and
+    - B[k]['mtime'] < A[k]['mtime'],
+
+  then k is included in the result with value B[k].
+
+  Keys that are only in B (not in A) are ignored here.
+  """
+  result: Dict[str, Dict[str, Any]] = {}
+
+  for key, info_B in B.items():
+    info_A = A.get(key)
+    if info_A is None:
+      continue
+
+    mtime_A = info_A.get("mtime")
+    mtime_B = info_B.get("mtime")
+
+    if mtime_A is None or mtime_B is None:
+      continue
+
+    if mtime_B < mtime_A:
+      result[key] = info_B
+
+  if meta.debug_has("tree_dict_older"):
+    tree_dict_print(result)
+
+  return result
+
+def in_between_newer(
+  A: TreeDict
+  ,B: TreeDict
+) -> TreeDict:
+  """
+  in_between_newer(A, B) -> TreeDict
+
+  Return the subset of B's nodes that:
+
+    1. Are in the 'in_between' region with respect to A's topology:
+         - under some directory that exists in A
+         - NOT under any leaf directory in A
+       (as defined by tree_dict_in_between_and_below), and
+
+    2. For file nodes:
+         - are "newer" than A at the same path, or
+         - are absent from A.
+
+       More precisely:
+         - If A has no entry for that path -> include.
+         - If A has a non-file and B has a file -> include.
+         - If both are files and B.mtime > A.mtime -> include.
+
+    3. For constrained nodes:
+         - are always included, so that higher-level commands (e.g.
+           'import') can surface them as "not handled automatically".
+
+  Notes:
+    - Only file nodes participate in mtime comparisons.
+    - Nodes with node_type == 'constrained' are passed through without
+      mtime checks, so that callers can report them separately.
+  """
+  in_between, _below = tree_dict_in_between_and_below(A, B)
+
+  result: TreeDict = {}
+
+  # Keep track of directories already included in the result
+  included_dirs: Set[str] = set()
+
+  # Sort keys to ensure parent directories are processed before their children.
+  # This is crucial for the child exclusion logic to work correctly.
+  sorted_paths = sorted(in_between.keys(), key=len)
+
+  for path in sorted_paths:
+    b_info = in_between[path]
+    b_type = b_info.get("node_type")
+
+    # Constrained nodes: always surface so the caller can list them
+    # under "not handled automatically".
+    # Check if this path is a child of an already included directory
+    is_child_of_included_dir = False
+    for d in included_dirs:
+      if path.startswith(d + os.sep):
+        is_child_of_included_dir = True
+        break
+    
+    if is_child_of_included_dir:
+      continue
+
+    # Constrained nodes: always surface so the caller can list them
+    # under "not handled automatically".
+    if b_type == "constrained":
+      result[path] = b_info
+      if b_type == "directory":
+        included_dirs.add(path)
+      continue
+
+
+
+    b_mtime = b_info.get("mtime")
+    a_info = A.get(path)
+
+    # Case 1: path not in A at all -> include (new file/dir in in-between)
+    if a_info is None:
+      result[path] = b_info
+      if b_type == "directory":
+        included_dirs.add(path)
+      continue
+
+    # We only do "newer" semantics for regular files.
+    if b_type != "file":
+      continue
+
+    a_type = a_info.get("node_type")
+
+    # Case 2: A has non-file, B has file -> include
+    if a_type != "file":
+      result[path] = b_info
+      # Note: b_type must be "file" here due to the check above, so no need
+      # to check for directory inclusion.
+      continue
+
+    # Case 3: both are files; compare mtime
+    a_mtime = a_info.get("mtime")
+    if (
+      isinstance(a_mtime, (int, float))
+      and isinstance(b_mtime, (int, float))
+      and b_mtime > a_mtime
+    ):
+      result[path] = b_info
+      # Note: b_type must be "file" here, so no need to check for directory inclusion.
+
+  if meta.debug_has("in_between_newer"):
+    tree_dict_print(result)
+
+  return result
+
diff --git a/shared/authored/deprecated/git-empty-dir/CLI.py b/shared/authored/deprecated/git-empty-dir/CLI.py
new file mode 100755 (executable)
index 0000000..2fb22e1
--- /dev/null
@@ -0,0 +1,251 @@
+#!/usr/bin/env python3
+# ----------------------------------------------------------------------
+# git-empty-dir :: list/mark/clean empty directories, .gitignore aware
+# ----------------------------------------------------------------------
+
+import sys
+import os
+from pathlib import Path
+
+# The source_sync GitIgnore parser is inside the unpacked tool.
+# We assume this directory structure:
+#   git-empty-dir/
+#     CLI.py
+#     source_sync/
+#       GitIgnore.py
+#
+# That mirrors how your harmony sync tool is structured.
+
+# Adjust import path so we can load source_sync.*
+HERE = Path(__file__).resolve().parent
+sys.path.insert(0, str(HERE))
+
+from source_sync.GitIgnore import GitIgnore  # type: ignore
+
+
+# ----------------------------------------------------------------------
+# helpers
+# ----------------------------------------------------------------------
+
+def load_gitignore_tree(root: Path):
+  """
+  Build a GitIgnore instance rooted at <root>.
+  """
+  return GitIgnore(str(root))
+
+def is_empty_dir(path: Path) -> bool:
+  """
+  A directory is empty if it contains no files or subdirectories.
+  (Hidden files count; .gitignored children are irrelevant because
+  behavior here should reflect real filesystem emptiness.)
+  """
+  try:
+    for _ in path.iterdir():
+      return False
+    return True
+  except PermissionError:
+    # treat as non-empty: safer than aborting
+    return False
+
+
+def has_mark(path: Path, mark_file: str) -> bool:
+  return (path / mark_file).exists()
+
+
+def sorted_dirs(root: Path):
+  """
+  Produce a list of all directories under root, in parent-before-child order.
+  Sort rule:
+    1. by path length
+    2. then lexicographically
+  """
+  all_dirs = []
+  for p in root.rglob("*"):
+    if p.is_dir():
+      all_dirs.append(p)
+
+  return sorted(
+    all_dirs
+    ,key = lambda p: (len(p.parts), str(p))
+  )
+
+
+# ----------------------------------------------------------------------
+# traversal
+# ----------------------------------------------------------------------
+
+def visible_dirs(root: Path, ignore_tree, mark_file: str):
+  """
+  Yield all dirs under root, applying:
+    - skip .git
+    - apply .gitignore rules (if a dir is ignored, do not descend)
+    - parent-before-child ordering
+  """
+  for d in sorted_dirs(root):
+    rel = d.relative_to(root)
+
+    if rel == Path("."):
+      continue
+
+    # skip .git explicitly
+    if d.name == ".git":
+      continue
+
+    # .gitignore filtering
+    if ignore_tree.check(str(rel)) == "Ignore":
+      continue
+
+    yield d
+
+
+# ----------------------------------------------------------------------
+# actions
+# ----------------------------------------------------------------------
+
+def action_list(root, ignore_tree, mark_file, mode):
+  """
+  mode ∈ {"empty","marked","all"}
+  """
+  for d in visible_dirs(root, ignore_tree, mark_file):
+    if mode == "all":
+      print(d.relative_to(root))
+      continue
+
+    if mode == "marked":
+      if has_mark(d, mark_file):
+        print(d.relative_to(root))
+      continue
+
+    if mode == "empty":
+      if is_empty_dir(d):
+        print(d.relative_to(root))
+      continue
+
+
+def action_mark(root, ignore_tree, mark_file, mode):
+  """
+  mode ∈ {"empty","all"}
+  """
+  for d in visible_dirs(root, ignore_tree, mark_file):
+    if mode == "empty" and not is_empty_dir(d):
+      continue
+    try:
+      (d / mark_file).touch(exist_ok=True)
+    except Exception:
+      pass
+
+
+def action_clean(root, ignore_tree, mark_file, mode):
+  """
+  mode ∈ {"nonempty","all"}
+  """
+  for d in visible_dirs(root, ignore_tree, mark_file):
+    m = d / mark_file
+    if not m.exists():
+      continue
+
+    if mode == "nonempty":
+      if is_empty_dir(d):
+        continue
+
+    try:
+      m.unlink()
+    except Exception:
+      pass
+
+
+# ----------------------------------------------------------------------
+# usage
+# ----------------------------------------------------------------------
+
+USAGE = """
+usage:
+  git-empty-dir (list|mark|clean) [all|marked|empty] [file-<name>]
+  git-empty-dir help
+  git-empty-dir usage
+
+defaults:
+  mark-file = .gitkeep
+  ignores .git
+  follows .gitignore (no descent into ignored dirs)
+
+examples:
+  git-empty-dir list
+  git-empty-dir list marked file-.githolder
+  git-empty-dir mark
+  git-empty-dir clean all
+"""
+
+
+# ----------------------------------------------------------------------
+# CLI
+# ----------------------------------------------------------------------
+
+def CLI(argv):
+  if len(argv) == 0:
+    print(USAGE)
+    return 0
+
+  cmd = argv[0]
+
+  if cmd in ("help","usage"):
+    print(USAGE)
+    return 0
+
+  # command
+  if cmd not in ("list","mark","clean"):
+    print(f"unknown command: {cmd}")
+    print(USAGE)
+    return 1
+
+  # submode
+  mode = None
+  mark_file = ".gitkeep"
+
+  for a in argv[1:]:
+    if a.startswith("file-"):
+      mark_file = a[5:]
+      continue
+
+    if a in ("all","empty","marked"):
+      mode = a
+      continue
+
+    print(f"unknown argument: {a}")
+    print(USAGE)
+    return 1
+
+  # defaults
+  if cmd == "list":
+    if mode is None:
+      mode = "empty"
+  elif cmd == "mark":
+    if mode is None:
+      mode = "empty"
+  elif cmd == "clean":
+    if mode is None:
+      mode = "nonempty"
+
+  root = Path(".").resolve()
+  ignore_tree = load_gitignore_tree(root)
+
+  if cmd == "list":
+    action_list(root, ignore_tree, mark_file, mode)
+
+  elif cmd == "mark":
+    if mode == "all":
+      action_mark(root, ignore_tree, mark_file, "all")
+    else:
+      action_mark(root, ignore_tree, mark_file, "empty")
+
+  elif cmd == "clean":
+    if mode == "all":
+      action_clean(root, ignore_tree, mark_file, "all")
+    else:
+      action_clean(root, ignore_tree, mark_file, "nonempty")
+
+  return 0
+
+
+if __name__ == "__main__":
+  sys.exit(CLI(sys.argv[1:]))
diff --git a/shared/authored/deprecated/git-empty-dir/Harmony.py b/shared/authored/deprecated/git-empty-dir/Harmony.py
new file mode 120000 (symlink)
index 0000000..112663e
--- /dev/null
@@ -0,0 +1 @@
+../source_sync/Harmony.py
\ No newline at end of file
diff --git a/shared/authored/deprecated/git-empty-dir/load_command_module.py b/shared/authored/deprecated/git-empty-dir/load_command_module.py
new file mode 120000 (symlink)
index 0000000..87b98be
--- /dev/null
@@ -0,0 +1 @@
+../source_sync/load_command_module.py
\ No newline at end of file
diff --git a/shared/authored/deprecated/git-empty-dir/meta.py b/shared/authored/deprecated/git-empty-dir/meta.py
new file mode 100644 (file)
index 0000000..dee6439
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+meta.py - thin wrappers around command modules
+
+Current responsibilities:
+  1. Load the incommon 'printenv' command module (no .py extension)
+     using load_command_module.load_command_module().
+  2. Expose printenv() here, calling the imported printenv() work
+     function with default arguments (equivalent to running without
+     any CLI arguments).
+  3. Provide a simple version printer for this meta module.
+  4. Provide a small debug tag API (set/clear/has).
+"""
+
+from __future__ import annotations
+
+import datetime
+from load_command_module import load_command_module
+
+
+# Load the incommon printenv module once at import time
+_PRINTENV_MODULE = load_command_module("printenv")
+_Z_MODULE = load_command_module("Z")
+
+
+# Meta module version
+_major = 1
+_minor = 1
+def version_print() -> None:
+  """
+  Print the meta module version as MAJOR.MINOR.
+  """
+  print(f"{_major}.{_minor}")
+
+
+# Debug tag set and helpers
+_debug = set([
+])
+
+
+def debug_set(tag: str) -> None:
+  """
+  Add a debug tag to the meta debug set.
+  """
+  _debug.add(tag)
+
+
+def debug_clear(tag: str) -> None:
+  """
+  Remove a debug tag from the meta debug set, if present.
+  """
+  _debug.discard(tag)
+
+
+def debug_has(tag: str) -> bool:
+  """
+  Return True if the given debug tag is present.
+  """
+  return tag in _debug
+
+
+# Touch the default tag once so static checkers do not complain about
+# unused helpers when imported purely for side-effects.
+debug_has("Command")
+
+
+def printenv() -> int:
+  """
+  Call the imported printenv() work function with default arguments:
+    - no null termination
+    - no newline quoting
+    - no specific names (print full environment)
+    - prog name 'printenv'
+  """
+  return _PRINTENV_MODULE.printenv(
+    False      # null_terminate
+    ,False     # quote_newlines
+    ,[]        # names
+    ,"printenv"
+  )
+
+
+def z_format_mtime(
+  mtime: float
+) -> str:
+  """
+  Format a POSIX mtime (seconds since epoch, UTC) using the Z module.
+
+  Uses Z.ISO8601_FORMAT and Z.make_timestamp(dt=...).
+  """
+  dt = datetime.datetime.fromtimestamp(mtime, datetime.timezone.utc)
+  return _Z_MODULE.make_timestamp(
+    fmt=_Z_MODULE.ISO8601_FORMAT
+    ,dt=dt
+  )
diff --git a/shared/authored/deprecated/git-empty-dir/source_sync b/shared/authored/deprecated/git-empty-dir/source_sync
new file mode 120000 (symlink)
index 0000000..9fd1d51
--- /dev/null
@@ -0,0 +1 @@
+../source_sync/
\ No newline at end of file
diff --git a/shared/authored/deprecated/gitignore_treewalk.py b/shared/authored/deprecated/gitignore_treewalk.py
new file mode 100755 (executable)
index 0000000..eef94ec
--- /dev/null
@@ -0,0 +1,185 @@
+#!/usr/bin/env python3
+"""
+gitignore_walk.py — Fully correct .gitignore-aware depth-first walker
+Now passes:
+  • __pycache__/        (directory listed, contents ignored)
+  • scratchpad/* !/.gitignore
+  • third_party/.gitignore ignoring everything inside
+  • top-level .gitignore
+"""
+
+from __future__ import annotations
+
+import os
+import re
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Generator, List
+
+
+@dataclass(frozen=True)
+class Rule:
+    raw: str
+    negated: bool
+    dir_only: bool          # pattern ends with /
+    anchored: bool           # pattern starts with /
+    regex: re.Pattern
+
+
+def _compile_rule(line: str) -> Rule | None:
+    line = line.strip()
+    if not line or line.startswith("#"):
+        return None
+
+    negated = line.startswith("!")
+    if negated:
+        line = line[1:].lstrip()
+
+    dir_only = line.endswith("/")
+    if dir_only:
+        line = line[:-1]
+
+    anchored = line.startswith("/")
+    if anchored:
+        line = line[1:]
+
+    # Convert git pattern to regex
+    parts = []
+    i = 0
+    while i < len(line):
+        c = line[i]
+        if c == "*":
+            if i + 1 < len(line) and line[i + 1] == "*":
+                parts.append(".*")
+                i += 2
+            else:
+                parts.append("[^/]*")
+                i += 1
+        elif c == "?":
+            parts.append("[^/]")
+            i += 1
+        else:
+            parts.append(re.escape(c))
+            i += 1
+
+    regex_str = "".join(parts)
+
+    if anchored:
+        regex_str = f"^{regex_str}"
+    else:
+        regex_str = f"(^|/){regex_str}"
+
+    # For dir-only patterns: match path + optional trailing slash
+    if dir_only:
+        regex_str += "(/.*)?$"
+    else:
+        regex_str += "($|/.*$)"
+
+    return Rule(
+        raw=line,
+        negated=negated,
+        dir_only=dir_only,
+        anchored=anchored,
+        regex=re.compile(regex_str),
+    )
+
+
+def _load_rules(dirpath: Path) -> List[Rule]:
+    rules: List[Rule] = []
+    gitignore = dirpath / ".gitignore"
+    if gitignore.is_file():
+        try:
+            for raw_line in gitignore.read_text(encoding="utf-8", errors="ignore").splitlines():
+                rule = _compile_rule(raw_line)
+                if rule:
+                    rules.append(rule)
+        except Exception:
+            pass
+    return rules
+
+
+def gitignore_walk(root: str | Path) -> Generator[Path, None, None]:
+    root = Path(root).resolve()
+    if not root.is_dir():
+        return
+
+    # Stack: (directory_path, rules_from_root_to_here)
+    stack: List[tuple[Path, List[Rule]]] = [(root, [])]
+
+    while stack:
+        cur_dir, inherited_rules = stack.pop()  # depth-first
+
+        # Load local rules
+        local_rules = _load_rules(cur_dir)
+        all_rules = inherited_rules + local_rules
+
+        # Relative path string from project root
+        try:
+            rel = cur_dir.relative_to(root)
+            rel_str = "" if rel == Path(".") else rel.as_posix()
+        except ValueError:
+            rel_str = ""
+
+        # === Is this directory itself ignored? ===
+        dir_ignored = False
+        for rule in reversed(all_rules):  # last match wins
+            if rule.regex.match(rel_str + "/"):  # always test as dir
+                dir_ignored = rule.negated
+                break
+
+        # Yield the directory if not ignored
+        if not dir_ignored:
+            yield cur_dir
+
+        # Scan children only if directory is not ignored
+        if dir_ignored:
+            continue
+
+        try:
+            children = list(cur_dir.iterdir())
+        except PermissionError:
+            continue
+
+        children.sort(key=lambda p: p.name.lower())
+
+        to_visit = []
+        for child in children:
+            if child.name == ".git":
+                continue
+
+            child_rel = child.relative_to(root)
+            child_rel_str = child_rel.as_posix()
+
+            # Special case: .gitignore files are never ignored by their own rules
+            if child.name == ".gitignore":
+                if not dir_ignored:
+                    yield child
+                continue
+
+            # Evaluate rules against the full relative path
+            ignored = False
+            for rule in reversed(all_rules):
+                match_str = child_rel_str + "/" if child.is_dir() else child_rel_str
+                if rule.regex.match(match_str):
+                    ignored = rule.negated
+                    break
+
+            if not ignored:
+                if child.is_dir():
+                    to_visit.append(child)
+                else:
+                    yield child
+
+        # Push children in reverse order → depth-first, left-to-right
+        for child_dir in reversed(to_visit):
+            stack.append((child_dir, all_rules))
+
+
+if __name__ == "__main__":
+    import argparse
+    parser = argparse.ArgumentParser(description="Gitignore-aware tree walk")
+    parser.add_argument("path", nargs="?", default=".", help="Root directory")
+    args = parser.parse_args()
+
+    for p in gitignore_walk(args.path):
+        print(p)
diff --git a/shared/authored/deprecated/walk b/shared/authored/deprecated/walk
new file mode 120000 (symlink)
index 0000000..cd6bd22
--- /dev/null
@@ -0,0 +1 @@
+./gitignore_treewalk/CLI.py
\ No newline at end of file
diff --git a/shared/authored/deprecated/walk-dir-tree-w-gitignore/CLI.py b/shared/authored/deprecated/walk-dir-tree-w-gitignore/CLI.py
new file mode 100755 (executable)
index 0000000..1e3be48
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*-
+
+from __future__ import annotations
+
+import os
+import sys
+
+# ----------------------------------------------------------------------
+# Bootstrap import context when executed via symlink (e.g. ../walk)
+# ----------------------------------------------------------------------
+if __name__ == "__main__" and __package__ is None:
+    # Resolve the real file (follows symlinks)
+    _real = os.path.realpath(__file__)
+    _pkg_dir = os.path.dirname(_real)
+    _pkg_root = os.path.dirname(_pkg_dir)  # authored/
+
+    # Ensure authored/ is on sys.path
+    if _pkg_root not in sys.path:
+        sys.path.insert(0, _pkg_root)
+
+    # Force package name so relative imports work
+    __package__ = "gitignore_treewalk"
+
+# Now safe to do relative imports
+from .pattern import Pattern
+from .ruleset import RuleSet
+from .walker import Walker
+from .printer import Printer
+
+
+# ----------------------------------------------------------------------
+# Usage text
+# ----------------------------------------------------------------------
+def usage() -> int:
+  print(
+    "Usage:\n"
+    "  walk |usage|help\n"
+    "      Show this help.\n"
+    "\n"
+    "  walk list\n"
+    "      Walk the working directory applying gitignore rules.\n"
+  )
+  return 0
+
+
+# ----------------------------------------------------------------------
+# CLI dispatcher
+# ----------------------------------------------------------------------
+def CLI(argv: List[str]) -> int:
+  if not argv:
+    return usage()
+
+  cmd = argv[0]
+
+  if cmd in ("usage", "help"):
+    return usage()
+
+  if cmd == "list":
+    cwd = os.getcwd()
+    cwd_dpa = os.path.abspath(cwd)
+
+    rs = RuleSet.from_gitignore_files(
+      start_dir=cwd_dpa
+    )
+
+    walker = Walker(
+      root=cwd_dpa
+      ,rules=rs
+    )
+
+    for p in walker.walk():
+      print_path(
+        p
+        ,cwd_dpa
+      )
+    return 0
+
+  print(f"Unknown command: {cmd}")
+  return usage()
+
+
+# ----------------------------------------------------------------------
+# Entrypoint
+# ----------------------------------------------------------------------
+if __name__ == "__main__":
+  sys.exit(
+    CLI(
+      sys.argv[1:]
+    )
+  )
diff --git a/shared/authored/deprecated/walk-dir-tree-w-gitignore/__init__.py b/shared/authored/deprecated/walk-dir-tree-w-gitignore/__init__.py
new file mode 100644 (file)
index 0000000..c3fae0e
--- /dev/null
@@ -0,0 +1,15 @@
+# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*-
+"""
+gitignore_treewalk — Git-aware directory traversal library.
+
+Exports:
+  Pattern
+  RuleSet
+  Walker
+  Printer
+"""
+
+from .pattern import Pattern
+from .ruleset import RuleSet
+from .walker import Walker
+from .printer import Printer
diff --git a/shared/authored/deprecated/walk-dir-tree-w-gitignore/pattern.py b/shared/authored/deprecated/walk-dir-tree-w-gitignore/pattern.py
new file mode 100644 (file)
index 0000000..31718ae
--- /dev/null
@@ -0,0 +1,115 @@
+# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*-
+
+"""
+pattern.py — Git ignore pattern parser.
+
+Implements:
+  Git pattern semantics:
+    - !negation
+    - directory-only ('foo/')
+    - anchored ('/foo')
+    - wildcards '*', '?'
+    - recursive wildcard '**'
+    - full-path matching
+    - last rule wins
+"""
+
+from __future__ import annotations
+import os
+import re
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class Pattern:
+  raw: str
+  negated: bool
+  anchored: bool
+  dir_only: bool
+  regex: re.Pattern
+
+  @staticmethod
+  def from_line(line: str) -> Optional["Pattern"]:
+    """
+    Parse a single .gitignore pattern line.
+    Return None for comments/empty.
+    """
+
+    stripped = line.strip()
+    if not stripped or stripped.startswith("#"):
+      return None
+
+    negated = stripped.startswith("!")
+    if negated:
+      stripped = stripped[1:].lstrip()
+      if not stripped:
+        return None
+
+    dir_only = stripped.endswith("/")
+    if dir_only:
+      stripped = stripped[:-1]
+
+    anchored = stripped.startswith("/")
+    if anchored:
+      stripped = stripped[1:]
+
+    # Convert git-style pattern to regex
+    # Git semantics:
+    #   **  -> match any depth
+    #   *   -> match any sequence except '/'
+    #   ?   -> match one char except '/'
+    #
+    # Always match against full path (unix style, no leading '.')
+    #
+    def escape(s: str) -> str:
+      return re.escape(s)
+
+    # Convert pattern piecewise
+    regex_pieces = []
+    i = 0
+    while i < len(stripped):
+      c = stripped[i]
+      if c == "*":
+        # Check for **
+        if i + 1 < len(stripped) and stripped[i + 1] == "*":
+          # '**' -> match zero or more directories OR characters
+          regex_pieces.append(".*")
+          i += 2
+        else:
+          # '*' -> match any chars except '/'
+          regex_pieces.append("[^/]*")
+          i += 1
+      elif c == "?":
+        regex_pieces.append("[^/]")
+        i += 1
+      else:
+        regex_pieces.append(escape(c))
+        i += 1
+
+    regex_string = "".join(regex_pieces)
+
+    # Anchored: match from start of path
+    # Unanchored: match anywhere in path
+    if anchored:
+      full = fr"^{regex_string}$"
+    else:
+      full = fr"(^|/){regex_string}($|/)"
+
+    return Pattern(
+      raw=line,
+      negated=negated,
+      anchored=anchored,
+      dir_only=dir_only,
+      regex=re.compile(full),
+    )
+
+  def matches(self, relpath: str, is_dir: bool) -> bool:
+    """
+    Match full relative path, not just basename.
+    """
+    # If pattern is directory-only, relpath must be a directory
+    if self.dir_only and not is_dir:
+      return False
+
+    return bool(self.regex.search(relpath))
diff --git a/shared/authored/deprecated/walk-dir-tree-w-gitignore/printer.py b/shared/authored/deprecated/walk-dir-tree-w-gitignore/printer.py
new file mode 100644 (file)
index 0000000..288978c
--- /dev/null
@@ -0,0 +1,38 @@
+# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*-
+
+"""
+printer.py — utilities for printing path listings:
+  - linear list
+  - ASCII "tree" view where each line begins with the actual path,
+    then optional visual decoration for humans.
+"""
+
+from __future__ import annotations
+from pathlib import Path
+from typing import Iterable
+
+
+class Printer:
+  @staticmethod
+  def print_linear(paths: Iterable[Path], cwd: Path) -> None:
+    for p in paths:
+      rel = p.relative_to(cwd)
+      print(rel.as_posix())
+
+  @staticmethod
+  def print_tree(paths: Iterable[Path], cwd: Path) -> None:
+    """
+    Print each line as:
+      <relpath>    <drawing>
+
+    Where <drawing> is ASCII tree structure.
+    """
+    items = sorted(paths, key=lambda p: p.relative_to(cwd).as_posix())
+    rels = [p.relative_to(cwd).as_posix() for p in items]
+
+    # Build a tree prefix for human reading
+    for rel in rels:
+      parts = rel.split("/")
+      indent = "  " * (len(parts) - 1)
+      branch = "└─ " if len(parts) > 1 else ""
+      print(f"{rel}    {indent}{branch}")
diff --git a/shared/authored/deprecated/walk-dir-tree-w-gitignore/ruleset.py b/shared/authored/deprecated/walk-dir-tree-w-gitignore/ruleset.py
new file mode 100644 (file)
index 0000000..d233de8
--- /dev/null
@@ -0,0 +1,57 @@
+# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*-
+
+"""
+ruleset.py — layered Git ignore rule-set stack.
+
+Implements the Git semantics:
+  - Each directory can contribute patterns from .gitignore
+  - Parent directories apply first
+  - Last matching pattern wins
+  - Negation overrides earlier ignores
+  - dir-only rules respected
+"""
+
+from __future__ import annotations
+import os
+from typing import List, Optional
+from .pattern import Pattern
+
+
+class RuleSet:
+  """
+  Manages a stack of patterns from:
+    - global excludes
+    - .git/info/exclude
+    - directory-local .gitignore
+
+  push(patterns)
+  pop(count)
+  evaluate(path, is_dir)
+  """
+
+  def __init__(self) -> None:
+    self.stack: List[List[Pattern]] = []
+
+  def push(self, patterns: List[Pattern]) -> None:
+    self.stack.append(patterns)
+
+  def pop(self) -> None:
+    if self.stack:
+      self.stack.pop()
+
+  def evaluate(self, relpath: str, is_dir: bool) -> bool:
+    """
+    Return True iff path is ignored.
+    Last matching rule wins.
+    """
+    verdict: Optional[bool] = None
+
+    for group in self.stack:
+      for pat in group:
+        if pat.matches(relpath, is_dir):
+          if pat.negated:
+            verdict = False
+          else:
+            verdict = True
+
+    return bool(verdict)
diff --git a/shared/authored/deprecated/walk-dir-tree-w-gitignore/walker.py b/shared/authored/deprecated/walk-dir-tree-w-gitignore/walker.py
new file mode 100644 (file)
index 0000000..57d89b9
--- /dev/null
@@ -0,0 +1,121 @@
+# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*-
+
+"""
+walker.py — Git-aware directory traversal.
+
+Features:
+  - Loads global excludes
+  - Loads .git/info/exclude if present
+  - Loads .gitignore in each directory
+  - Does NOT descend into ignored directories
+  - Yields both files and directories (Path objects)
+  - Always parent-before-child
+  - Sorted lexicographically
+"""
+
+from __future__ import annotations
+import os
+from pathlib import Path
+from typing import Iterator, List
+
+from .pattern import Pattern
+from .ruleset import RuleSet
+
+
+class Walker:
+  def __init__(self, root: Path) -> None:
+    self.root = root.resolve()
+    self.ruleset = RuleSet()
+
+    # Load global and project-local excludes
+    self._push_global_excludes()
+    self._push_local_excludes()
+
+  # ----------------------------------------------------------------------
+  # Exclude Sources
+  # ----------------------------------------------------------------------
+
+  def _push_global_excludes(self) -> None:
+    """
+    Load user's global ignore file if present:
+      ~/.config/git/ignore
+      or ~/.gitignore_global
+    """
+    candidates = [
+      Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "git" / "ignore",
+      Path.home() / ".gitignore_global"
+    ]
+    patterns = []
+
+    for f in candidates:
+      if f.exists():
+        for line in f.read_text().splitlines():
+          p = Pattern.from_line(line)
+          if p:
+            patterns.append(p)
+        break
+
+    if patterns:
+      self.ruleset.push(patterns)
+
+  def _push_local_excludes(self) -> None:
+    """
+    Load <root>/.git/info/exclude
+    """
+    f = self.root / ".git" / "info" / "exclude"
+    patterns = []
+    if f.exists():
+      for line in f.read_text().splitlines():
+        p = Pattern.from_line(line)
+        if p:
+          patterns.append(p)
+
+    if patterns:
+      self.ruleset.push(patterns)
+
+  # ----------------------------------------------------------------------
+  # Walk
+  # ----------------------------------------------------------------------
+
+  def walk(self) -> Iterator[Path]:
+    return self._walk_dir(self.root, prefix="")
+
+  def _walk_dir(self, dpath: Path, prefix: str) -> Iterator[Path]:
+    # Load .gitignore for this directory
+    patterns = []
+    gitignore = dpath / ".gitignore"
+    if gitignore.exists():
+      for line in gitignore.read_text().splitlines():
+        p = Pattern.from_line(line)
+        if p:
+          patterns.append(p)
+
+    self.ruleset.push(patterns)
+
+    # Evaluate this directory (except root)
+    if prefix:
+      if self.ruleset.evaluate(prefix, is_dir=True):
+        # ignored directories are NOT descended into
+        self.ruleset.pop()
+        return
+
+    yield dpath
+
+    # Enumerate children sorted
+    entries: List[Path] = sorted(dpath.iterdir(), key=lambda p: p.name)
+
+    for entry in entries:
+      rel = entry.relative_to(self.root).as_posix()
+      is_dir = entry.is_dir()
+
+      # Skip ignored
+      if self.ruleset.evaluate(rel, is_dir=is_dir):
+        continue
+
+      # Directories
+      if is_dir:
+        yield from self._walk_dir(entry, rel)
+      else:
+        yield entry
+
+    self.ruleset.pop()
diff --git a/shared/authored/dir-walk/CLI.py b/shared/authored/dir-walk/CLI.py
new file mode 100755 (executable)
index 0000000..62472e4
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Sequence
+
+import walk
+
+
+COMMAND_set: set[str] = set([
+  "usage"
+  ,"help"
+  ,"all"
+])
+
+
+def print_usage() -> None:
+  prog_name = os.path.basename(sys.argv[0] or "CLI.py")
+  print(f"Usage: {prog_name} <command>")
+  print("Commands:")
+  print("  usage  - print this usage message")
+  print("  help   - print this usage message")
+  print("  all    - walk current directory and print directories with indentation")
+
+
+def CLI(
+  argv: Sequence[str] | None = None
+) -> int:
+  """
+  Minimal CLI to exercise the TM (Tape Machine over the FS tree).
+
+  Commands:
+    usage, help:
+      Print usage information.
+
+    all:
+      Call walk.print_indent('.') to walk the current directory
+      and print directories with indentation.
+  """
+  if argv is None:
+    argv = sys.argv[1:]
+
+  if len(argv) != 1:
+    print_usage()
+    return 1
+
+  cmd = argv[0]
+
+  if cmd not in COMMAND_set:
+    print(f"Unrecognized command: {cmd}")
+    print_usage()
+    return 2
+
+  if cmd in ("usage", "help"):
+    print_usage()
+    return 0
+
+  if cmd == "all":
+    root_dp = "."
+    status = walk.print_indent(root_dp)
+    return status
+
+  # Should be unreachable if COMMAND_set and logic stay aligned.
+  print("Internal error: command dispatch fell through.")
+  return 9
+
+
+if __name__ == "__main__":
+  raise SystemExit(CLI())
diff --git a/shared/authored/dir-walk/Queue.py b/shared/authored/dir-walk/Queue.py
new file mode 100755 (executable)
index 0000000..2597423
--- /dev/null
@@ -0,0 +1,299 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+from __future__ import annotations
+
+from typing import Any, Generic, Iterable, List, Optional, TypeVar
+
+import meta
+
+
+T = TypeVar("T")
+
+
+def _format_item_for_debug(
+  item: Any
+) -> str:
+  """
+  Convert a queued item to a human-readable string for debug output.
+
+  If the item has a callable .print() method, we call it and use
+  the returned value (converted to str). Otherwise we fall back
+  to repr(item).
+  """
+  if hasattr(item, "print"):
+    candidate = getattr(item, "print")
+    if callable(candidate):
+      try:
+        text = candidate()
+        if text is None:
+          return repr(item)
+        return str(text)
+      except Exception:
+        return repr(item)
+  return repr(item)
+
+
+def _queue_debug(
+  queue: Any
+  ,op: str
+  ,item: Any | None = None
+) -> None:
+  """
+  Internal helper: emit a queue-related debug message if
+  the meta.DEBUG set contains the tag "Queue".
+
+  Includes the queue instance pointer in hex so we can
+  distinguish multiple Queue instances.
+  """
+  if "Queue" not in meta.DEBUG:
+    return
+
+  qid = hex(id(queue))
+
+  if item is None:
+    print(f"[Queue {qid}] {op}")
+  else:
+    desc = _format_item_for_debug(item)
+    print(f"[Queue {qid}] {op}: {desc}")
+
+
+class Queue(Generic[T]):
+  """
+  Queue — ordered queue of items (e.g., Context objects).
+
+  Semantics:
+
+    - The "front" of the queue is the current item.
+    - The "back" of the queue will eventually be visited.
+    - Popping an empty queue is an error.
+
+  Interface:
+
+    front()  -> T    : current item (head), does not remove it.
+    back()   -> T    : last item in the queue, does not remove it.
+    top()    -> T    : alias for front().
+    depth()  -> int  : number of entries in the queue.
+    pop()    -> T    : remove and return the front item.
+
+    push(where_tag, item: T) -> None:
+
+      where_tag:
+        "front"      -> insert item at front of queue
+        "back"       -> append item at back of queue
+        "throw-away" -> do not store, treated as intentionally ignored
+
+    clone() -> Queue[T]
+
+      Creates a copy of the Queue. For each item:
+        - If item has a .clone() method, it is called.
+        - Otherwise the item reference is reused.
+  """
+
+  def __init__(
+    self
+    ,initial_iter: Optional[Iterable[T]] = None
+  ) -> None:
+    if initial_iter is None:
+      self._queue_list: List[T] = []
+    else:
+      self._queue_list = list(initial_iter)
+
+  # ----------------------------------------------------------------------
+  # Basic properties
+  # ----------------------------------------------------------------------
+  def is_empty(self) -> bool:
+    return len(self._queue_list) == 0
+
+  def depth(self) -> int:
+    return len(self._queue_list)
+
+  # ----------------------------------------------------------------------
+  # Cloning
+  # ----------------------------------------------------------------------
+  def clone(self) -> "Queue[T]":
+    """
+    Create a copy of this Queue.
+
+    The new queue has its own internal list. If an item provides
+    a .clone() method, that is used to copy the item; otherwise
+    the item reference is reused.
+    """
+    new_item_list: List[T] = []
+    for item in self._queue_list:
+      if hasattr(item, "clone") and callable(getattr(item, "clone")):
+        new_item_list.append(item.clone())  # type: ignore[arg-type]
+      else:
+        new_item_list.append(item)
+    new_q = Queue[T](new_item_list)
+    _queue_debug(self, f"clone -> new {hex(id(new_q))}")
+    return new_q
+
+  # ----------------------------------------------------------------------
+  # Accessors
+  # ----------------------------------------------------------------------
+  def front(self) -> T:
+    if self.is_empty():
+      raise RuntimeError("Queue.front() on empty queue.")
+    return self._queue_list[0]
+
+  def back(self) -> T:
+    if self.is_empty():
+      raise RuntimeError("Queue.back() on empty queue.")
+    return self._queue_list[-1]
+
+  def top(self) -> T:
+    """
+    Alias for front().
+    """
+    return self.front()
+
+  # ----------------------------------------------------------------------
+  # Mutating operations
+  # ----------------------------------------------------------------------
+  def pop(self) -> T:
+    """
+    Remove and return the front item.
+
+    Popping an empty queue is an error.
+    """
+    if self.is_empty():
+      raise RuntimeError("Queue.pop() on empty queue.")
+    item = self._queue_list.pop(0)
+    _queue_debug(self, "pop", item)
+    return item
+
+  def push(
+    self
+    ,where_tag: str
+    ,item: T
+  ) -> None:
+    """
+    Push an item according to where_tag:
+
+      where_tag == "front":
+        insert item at front of the queue.
+
+      where_tag == "back":
+        append item at back of the queue.
+
+      where_tag == "throw-away":
+        do not store item; treated as intentionally ignored.
+
+    Any other where_tag is an error.
+    """
+    if where_tag == "front":
+      self._queue_list.insert(0, item)
+      _queue_debug(self, "push front", item)
+      return
+
+    if where_tag == "back":
+      self._queue_list.append(item)
+      _queue_debug(self, "push back", item)
+      return
+
+    if where_tag == "throw-away":
+      _queue_debug(self, "push throw-away (discard)", item)
+      return
+
+    raise ValueError(f"Unknown Queue push tag: {where_tag!r}")
+
+  # ----------------------------------------------------------------------
+  # Dispatcher
+  # ----------------------------------------------------------------------
+  def dispatch(
+    self
+    ,op: str
+    ,*args
+    ,**kwargs
+  ):
+    """
+    Generic dispatcher for method-tag control.
+
+    Supported op values:
+
+      "front" -> front()
+      "back"  -> back()
+      "top"   -> top()
+      "depth" -> depth()
+      "pop"   -> pop()
+
+      "push"  -> push(where_tag, item)
+
+    Any unknown op raises ValueError.
+    """
+    if op == "front":
+      return self.front()
+    if op == "back":
+      return self.back()
+    if op == "top":
+      return self.top()
+    if op == "depth":
+      return self.depth()
+    if op == "pop":
+      return self.pop()
+    if op == "push":
+      if len(args) != 2:
+        raise ValueError("dispatch('push', where_tag, item) requires 2 arguments.")
+      where_tag = args[0]
+      item = args[1]
+      self.push(where_tag, item)
+      return None
+
+    raise ValueError(f"Unknown Queue operation: {op!r}")
+
+
+# ----------------------------------------------------------------------
+# Simple test harness
+# ----------------------------------------------------------------------
+class TestItem:
+  """
+  Minimal test item for the __main__ test().
+
+  It implements .print() so Queue can use it in debug messages.
+  """
+
+  def __init__(
+    self
+    ,name: str
+  ) -> None:
+    self.name = name
+
+  def print(self) -> str:
+    return f"TestItem({self.name})"
+
+  def clone(self) -> "TestItem":
+    # For the test, clone is just another instance with same name.
+    return TestItem(self.name)
+
+
+def test() -> None:
+  """
+  Simple test for Queue with debug enabled.
+  """
+  meta.DEBUG.add("Queue")
+
+  print("Queue test starting...")
+  meta.version()
+
+  q = Queue[TestItem]()
+  q2 = q.clone()  # exercise clone-debug once at the start
+
+  q.push("front", TestItem("root"))
+  q.push("back", TestItem("child-A"))
+  q.push("back", TestItem("child-B"))
+  q.push("throw-away", TestItem("ignored-node"))
+
+  print(f"depth = {q.depth()}")
+  print(f"front = {q.front().print()}")
+  print(f"back  = {q.back().print()}")
+
+  popped = q.pop()
+  print(f"popped = {popped.print()}")
+  print(f"depth  = {q.depth()}")
+
+  print("Queue test done.")
+
+
+if __name__ == "__main__":
+  test()
diff --git a/shared/authored/dir-walk/TapeMachine.py b/shared/authored/dir-walk/TapeMachine.py
new file mode 100755 (executable)
index 0000000..0ca1d21
--- /dev/null
@@ -0,0 +1,131 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+from __future__ import annotations
+
+from typing import Any, Optional
+
+import meta
+
+
+def _tape_debug(tm: Any, msg: str) -> None:
+  """Emit a TapeMachine-related debug message if meta.DEBUG has 'TapeMachine'."""
+  if "TapeMachine" not in meta.DEBUG: return
+  print(f"[TapeMachine {hex(id(tm))}] {msg}")
+
+
+class TapeMachine:
+  """
+  TapeMachine — single-tape, single-head, first-rest pattern.
+
+    - Tape is non-empty.
+    - Head starts on cell 0 and is always on a valid cell.
+    - Movement contract: caller checks can_step(n) before step(n).
+  """
+
+  def __init__(self, tape: Optional[Any] = None) -> None:
+    if tape is None: tape = []
+    for name in ("__len__", "__getitem__", "__setitem__"):
+      if not hasattr(tape, name): raise TypeError(f"TapeMachine tape must support {name}: {tape!r}")
+    self._length: int = len(tape)
+    if self._length == 0: raise ValueError("TapeMachine requires a non-empty tape (first-rest pattern).")
+    self._tape = tape
+    self._head_position: int = 0
+    _tape_debug(self, f"init length={self._length}, head_position=0")
+
+  @property
+  def length(self) -> int:
+    """Length of the tape (number of cells)."""
+    return self._length
+
+  def cue(self, new_position: Optional[int] = None) -> int:
+    """Get or set head (cue) position."""
+    if new_position is None: return self._head_position
+    if not 0 <= new_position < self._length: raise IndexError(f"TapeMachine.cue: out of range ({new_position}).")
+    self._head_position = new_position
+    return self._head_position
+
+  def can_step(self, n: int = 1) -> bool:
+    """True if step(n) would keep the head on the tape."""
+    proposed_position = self._head_position + n
+    return 0 <= proposed_position < self.length
+
+  def rewind(self) -> None:
+    """Move head to the first cell (index 0)."""
+    self._head_position = 0
+
+  def cue_end(self) -> None:
+    """Move head to the last cell (index length-1)."""
+    self._head_position = self._length - 1
+
+  def step(self, n: int = 1) -> None:
+    """Move head by n cells (n>0 right, n<0 left, n=0 no-op); caller must ensure can_step(n)."""
+    self._head_position += n
+
+  def read(self) -> Any:
+    """Read value under head."""
+    return self._tape[self._head_position]
+
+  def write(self, value: Any) -> None:
+    """Write a new value into the current cell."""
+    self._tape[self._head_position] = value
+
+  def print(self) -> str:
+    """
+    Printable view of the tape.
+
+      Two spaces then value per line; line with the head is prefixed '* '.
+    """
+    line_list = []
+    for idx in range(self._length):
+      prefix = "* " if idx == self._head_position else "  "
+      line_list.append(f"{prefix}{self._tape[idx]}")
+    return "\n".join(line_list)
+
+  def entangle(self) -> "TapeMachine":
+    """Create an entangled copy: shared tape, independent head_position."""
+    new_tm = object.__new__(TapeMachine)
+    new_tm._tape = self._tape
+    new_tm._length = self._length
+    new_tm._head_position = self._head_position
+    _tape_debug(self, f"entangle -> new {hex(id(new_tm))} head_position={self._head_position}")
+    return new_tm
+
+
+def test() -> None:
+  """Simple TapeMachine test with first-rest semantics."""
+  meta.DEBUG.add("TapeMachine")
+  print("TapeMachine test starting..."); meta.version()
+
+  tm = TapeMachine(["apple", "orange", "tangerine"])
+  print(f"initial: cue={tm.cue()}, value={tm.read()!r}")
+
+  while tm.can_step(1):
+    tm.step(1)
+    print(f"step(+1): cue={tm.cue()}, value={tm.read()!r}")
+
+  if tm.can_step(-1):
+    tm.step(-1)
+    print(f"step(-1): cue={tm.cue()}, value={tm.read()!r}")
+
+  tm.rewind()
+  print(f"rewind: cue={tm.cue()}, value={tm.read()!r}")
+
+  tm2 = tm.entangle()
+  if tm.can_step(2):
+    tm.step(2)
+    print(f"tm step(+2): cue={tm.cue()}, value={tm.read()!r}")
+  if tm2.can_step(1):
+    tm2.step(1)
+    print(f"tm2 step(+1): cue={tm2.cue()}, value={tm2.read()!r}")
+
+  tm.cue_end()
+  print(f"cue_end: cue={tm.cue()}, value={tm.read()!r}")
+
+  print("print(tm):")
+  print(tm.print())
+  print("TapeMachine test done.")
+
+
+if __name__ == "__main__":
+  test()
diff --git a/shared/authored/dir-walk/TreeMachine.py b/shared/authored/dir-walk/TreeMachine.py
new file mode 100755 (executable)
index 0000000..6e5eaf2
--- /dev/null
@@ -0,0 +1,414 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+from __future__ import annotations
+
+import os
+from typing import Any, List, Optional, Tuple
+
+import meta
+from Queue import Queue
+from TapeMachine import TapeMachine
+
+
+def _tm_debug(tm: Any, msg: str) -> None:
+  """Emit a TreeMachine-related debug message if meta.DEBUG has 'TreeMachine'."""
+  if "TreeMachine" not in meta.DEBUG: return
+  print(f"[TreeMachine {hex(id(tm))}] {msg}")
+
+
+class Context:
+  """
+  Context - per-directory iteration state for TreeMachine.
+
+  Fields:
+
+    dir_dpr : directory path, relative to root ('.' for root).
+    level   : integer >= 1 representing depth; root has level == 1.
+
+    _child_tm : TapeMachine over the child entry names.
+    _started  : False until the first step() for this context.
+
+  Contract:
+
+    - Context is only constructed for directories with at least one child.
+      (Empty directories never become Contexts.)
+
+    - First step:
+
+        _started is False, _child_tm.cue() == 0.
+        step() sets _started = True and returns index 0.
+
+    - Subsequent steps:
+
+        step() uses TapeMachine.can_step(1)/step(1) to move to the
+        next child and returns its index.
+
+    - rightmost():
+
+        True when this context cannot be stepped further without error.
+  """
+
+  def __init__(self, dir_dpr: str, level: int, child_name_list: List[str]) -> None:
+    if not child_name_list:
+      raise ValueError("Context requires non-empty child_name_list.")
+    self.dir_dpr: str = os.path.normpath(dir_dpr)
+    self.level: int = level
+    self._child_tm: TapeMachine = TapeMachine(child_name_list)
+    self._started: bool = False
+
+  def clone(self) -> "Context":
+    """Clone this Context so that the copy can be advanced independently."""
+    new_ctx = object.__new__(Context)
+    new_ctx.dir_dpr = self.dir_dpr
+    new_ctx.level = self.level
+    new_ctx._child_tm = self._child_tm.entangle()
+    new_ctx._started = self._started
+    return new_ctx
+
+  def rightmost(self) -> bool:
+    """
+    True when this context cannot be stepped further without error.
+
+    For a directory with N children:
+
+      - If not started yet:
+          N == 1 -> True  (single child; only one step possible)
+          N >  1 -> False (more than one child)
+
+      - If started:
+          True iff we cannot move one more child to the right.
+    """
+    if not self._started:
+      return self._child_tm.length <= 1
+    return not self._child_tm.can_step(1)
+
+  def step(self) -> int:
+    """
+    Advance within this directory.
+
+    Returns:
+      index (0-based) into this directory's child list.
+
+    Raises:
+      StopIteration if there is no valid next position.
+    """
+    if not self._started:
+      # First step: stay at cue 0, just mark started.
+      self._started = True
+      return self._child_tm.cue()
+
+    if not self._child_tm.can_step(1):
+      raise StopIteration("Context.step() would move past rightmost child.")
+
+    self._child_tm.step(1)
+    return self._child_tm.cue()
+
+  def current_child_name(self) -> str:
+    """
+    Return the current child's basename in this directory.
+
+    Raises:
+      RuntimeError if step() has never been successfully called.
+    """
+    if not self._started:
+      raise RuntimeError("Context.current_child_name() with no current child.")
+    return self._child_tm.read()
+
+  def current_child_dpr(self) -> str:
+    """
+    Return the current child's path, relative to the TreeMachine root.
+
+    If dir_dpr == '.', this is just the child name.
+    Otherwise it is 'dir_dpr/child_name'.
+    """
+    child_name = self.current_child_name()
+    if self.dir_dpr == ".": return child_name
+    return os.path.join(self.dir_dpr, child_name)
+
+  def current_child_address(self) -> Tuple[int, int]:
+    """
+    Return the address (level, index) of the current child.
+
+      level = directory level + 1
+      index = index within this directory's child list
+    """
+    if not self._started:
+      raise RuntimeError("Context.current_child_address() with no current child.")
+    return (self.level + 1, self._child_tm.cue())
+
+  def print(self) -> str:
+    """Printable representation for debugging."""
+    n = self._child_tm.length
+    idx = self._child_tm.cue() if self._started else -1
+    return f"Context(dir_dpr={self.dir_dpr!r}, level={self.level}, index={idx}, n={n})"
+
+
+class TreeMachine:
+  """
+  TreeMachine - multihead iterator over a directory tree.
+
+  Initialization:
+
+    TreeMachine(root_dp)
+
+      root_dp:
+        directory path (relative or absolute) that defines the
+        top of the tree to be walked.
+
+  Behavior:
+
+    - Visits file system nodes (files, directories, other) under root.
+    - Directories that have children get a Context and join the queue.
+    - Directories with no children are still visited as nodes, but are
+      never used as Contexts (no descent possible).
+  """
+
+  def __init__(self, root_dp: str) -> None:
+    root_dpa = os.path.abspath(root_dp)
+    if not os.path.isdir(root_dpa):
+      raise ValueError(f"TreeMachine root must be a directory: {root_dp!r}")
+
+    self.root_dpa: str = root_dpa
+
+    try:
+      child_name_list = sorted(os.listdir(self.root_dpa))
+    except OSError:
+      child_name_list = []
+
+    if child_name_list:
+      root_ctx = Context(".", 1, child_name_list)
+      self._context_queue: Queue[Context] = Queue([root_ctx])
+    else:
+      # Root has no children; no Contexts to descend into.
+      self._context_queue = Queue([])
+
+    self._current_fs_nod_pr: Optional[str] = None
+    self._address: Optional[Tuple[int, int]] = None  # (level, index)
+
+    _tm_debug(self, f"init root_dpa={self.root_dpa!r}")
+
+  # --------------------------------------------------------------------
+  # Internal helpers
+  # --------------------------------------------------------------------
+  def _current_context(self) -> Context:
+    if self._context_queue.is_empty():
+      raise RuntimeError("TreeMachine has no active context (empty queue).")
+    return self._context_queue.front()
+
+  def _fs_nod_pr_to_dpa(self, fs_nod_pr: str) -> str:
+    """Convert a relative node path (from root) to an absolute path."""
+    if fs_nod_pr == ".": return self.root_dpa
+    return os.path.join(self.root_dpa, fs_nod_pr)
+
+  def _compute_property_list(self, fs_nod_pr: str) -> List[str]:
+    """Compute property_list for a node, given its path relative to root."""
+    prop_list: List[str] = []
+    fs_nod_pa = self._fs_nod_pr_to_dpa(fs_nod_pr)
+
+    if os.path.isdir(fs_nod_pa):
+      prop_list.append("directory")
+      try:
+        child_name_list = os.listdir(fs_nod_pa)
+      except OSError:
+        child_name_list = []
+      if len(child_name_list) == 0:
+        prop_list.append("empty")
+    elif os.path.isfile(fs_nod_pa):
+      prop_list.append("file")
+    else:
+      prop_list.append("other")
+
+    return prop_list
+
+  # --------------------------------------------------------------------
+  # Core movement & observation
+  # --------------------------------------------------------------------
+  def step(self) -> Tuple[int, int]:
+    """
+    Advance the TreeMachine head within the current directory context.
+
+    Returns:
+      address = (level, index) for the new node under the head.
+
+    Raises:
+      StopIteration if there is no active context or the current
+      context is already at its rightmost cell.
+    """
+    ctx = self._current_context()
+
+    try:
+      _ = ctx.step()
+    except StopIteration:
+      raise StopIteration("TreeMachine.step(): current context has no next node.")
+
+    fs_nod_pr = ctx.current_child_dpr()
+    address = ctx.current_child_address()
+
+    self._current_fs_nod_pr = fs_nod_pr
+    self._address = address
+
+    _tm_debug(self, f"step -> fs_nod_pr={fs_nod_pr!r}, address={address}")
+    return address
+
+  def read(self):
+    """
+    Read the value under the head.
+
+    Returns:
+      (value, property_list, address)
+
+      value:
+        path string, relative to the TreeMachine root.
+
+      property_list:
+        list drawn from {"directory","file","other","empty"}.
+
+      address:
+        (level, index) for the current node.
+
+    Raises:
+      RuntimeError if there is no current node (no successful step yet).
+    """
+    if self._current_fs_nod_pr is None or self._address is None:
+      raise RuntimeError("TreeMachine.read() with no current node; call step() first.")
+
+    value = self._current_fs_nod_pr
+    prop_list = self._compute_property_list(value)
+    address = self._address
+
+    return (value, prop_list, address)
+
+  def rightmost(self) -> bool:
+    """True when the current context cannot be stepped further."""
+    ctx = self._current_context()
+    return ctx.rightmost()
+
+  # --------------------------------------------------------------------
+  # Context queue interaction
+  # --------------------------------------------------------------------
+  def push_current_directory(self, where_tag: str) -> None:
+    """
+    If the current node is a directory with children, create a new
+    Context for it and push it into the context queue according
+    to where_tag:
+
+      "front"      -> visit this directory next (depth-first flavor)
+      "back"       -> visit this directory later (breadth-first flavor)
+      "throw-away" -> ignore this directory entirely
+
+    If the current node is not a directory, or is an empty directory,
+    this is a no-op (with debug).
+    """
+    if self._current_fs_nod_pr is None or self._address is None:
+      raise RuntimeError("push_current_directory() with no current node; call step() first.")
+
+    fs_nod_pr = self._current_fs_nod_pr
+    prop_list = self._compute_property_list(fs_nod_pr)
+
+    if "directory" not in prop_list:
+      _tm_debug(self, f"push_current_directory skipped (not a directory): {fs_nod_pr!r}")
+      return
+
+    fs_nod_pa = self._fs_nod_pr_to_dpa(fs_nod_pr)
+    try:
+      child_name_list = sorted(os.listdir(fs_nod_pa))
+    except OSError:
+      child_name_list = []
+
+    if not child_name_list:
+      _tm_debug(self, f"push_current_directory skipped (empty directory): {fs_nod_pr!r}")
+      return
+
+    level, _idx = self._address
+    new_ctx = Context(fs_nod_pr, level, child_name_list)
+    self._context_queue.push(where_tag, new_ctx)
+    _tm_debug(self, f"push_current_directory -> {where_tag} {new_ctx.print()}")
+
+  def pop_context(self) -> None:
+    """
+    Pop the current directory context from the queue.
+
+    After popping, the new current context (if any) has no current
+    node yet, so the TreeMachine head is cleared.
+    """
+    popped = self._context_queue.pop()
+    _tm_debug(self, f"pop_context -> {popped.print()}")
+
+    self._current_fs_nod_pr = None
+    self._address = None
+
+  def context_depth(self) -> int:
+    """Return the number of directory contexts currently in the queue."""
+    return self._context_queue.depth()
+
+  # --------------------------------------------------------------------
+  # Entanglement
+  # --------------------------------------------------------------------
+  def entangle(self) -> "TreeMachine":
+    """
+    Create an entangled copy of this TreeMachine.
+
+    The entangled copy:
+
+      - Has the same root_dpa.
+      - Has a cloned Queue of Contexts (using Context.clone()).
+      - Starts with the same current node (fs_nod_pr) and address.
+
+    Afterwards, the original and entangled copy evolve independently.
+    """
+    new_tm = object.__new__(TreeMachine)
+    new_tm.root_dpa = self.root_dpa
+    new_tm._context_queue = self._context_queue.clone()
+    new_tm._current_fs_nod_pr = self._current_fs_nod_pr
+    new_tm._address = None if self._address is None else (self._address[0], self._address[1])
+    _tm_debug(self, f"entangle -> new {hex(id(new_tm))}")
+    return new_tm
+
+
+def test() -> None:
+  """
+  Simple TreeMachine test.
+
+  It walks a handful of nodes from the current directory, pushing
+  directory nodes to the back of the context queue (breadth-first flavor).
+  """
+  meta.DEBUG.add("Queue")
+  meta.DEBUG.add("TreeMachine")
+
+  print("TreeMachine test starting...")
+  meta.version()
+
+  tm = TreeMachine(".")
+
+  steps = 0
+  max_steps = 20
+
+  while steps < max_steps:
+    if tm._context_queue.is_empty():
+      print("no more contexts in queue; stopping")
+      break
+
+    try:
+      address = tm.step()
+    except StopIteration:
+      print("TreeMachine: StopIteration from step()")
+      break
+
+    value, prop_list, addr = tm.read()
+    print(f"step {steps}: value={value!r}, props={prop_list}, address={addr}")
+
+    if "directory" in prop_list and "empty" not in prop_list:
+      tm.push_current_directory("back")
+
+    if tm.rightmost():
+      print("  current context is rightmost; popping context")
+      tm.pop_context()
+
+    steps += 1
+
+  print("TreeMachine test done.")
+
+
+if __name__ == "__main__":
+  test()
diff --git a/shared/authored/dir-walk/meta.py b/shared/authored/dir-walk/meta.py
new file mode 100644 (file)
index 0000000..299aef7
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+from __future__ import annotations
+
+"""
+meta.py - module-level metadata and debug flags for the TreeMachine tools.
+"""
+
+# Version components are local to this module.
+_MAJOR = 0
+_MINOR = 2
+
+# Debug flags:
+#
+#   "Queue"       -> enable debug for Queue operations.
+#   "TreeMachine" -> enable debug for TreeMachine operations.
+#
+DEBUG: set[str] = set()
+
+
+def version() -> None:
+  """
+  Print the current version as:
+
+    version <major> <minor>
+  """
+  print(f"version {_MAJOR} {_MINOR}")
diff --git a/shared/authored/git-empty-dir/CLI.py b/shared/authored/git-empty-dir/CLI.py
deleted file mode 100755 (executable)
index 2fb22e1..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-#!/usr/bin/env python3
-# ----------------------------------------------------------------------
-# git-empty-dir :: list/mark/clean empty directories, .gitignore aware
-# ----------------------------------------------------------------------
-
-import sys
-import os
-from pathlib import Path
-
-# The source_sync GitIgnore parser is inside the unpacked tool.
-# We assume this directory structure:
-#   git-empty-dir/
-#     CLI.py
-#     source_sync/
-#       GitIgnore.py
-#
-# That mirrors how your harmony sync tool is structured.
-
-# Adjust import path so we can load source_sync.*
-HERE = Path(__file__).resolve().parent
-sys.path.insert(0, str(HERE))
-
-from source_sync.GitIgnore import GitIgnore  # type: ignore
-
-
-# ----------------------------------------------------------------------
-# helpers
-# ----------------------------------------------------------------------
-
-def load_gitignore_tree(root: Path):
-  """
-  Build a GitIgnore instance rooted at <root>.
-  """
-  return GitIgnore(str(root))
-
-def is_empty_dir(path: Path) -> bool:
-  """
-  A directory is empty if it contains no files or subdirectories.
-  (Hidden files count; .gitignored children are irrelevant because
-  behavior here should reflect real filesystem emptiness.)
-  """
-  try:
-    for _ in path.iterdir():
-      return False
-    return True
-  except PermissionError:
-    # treat as non-empty: safer than aborting
-    return False
-
-
-def has_mark(path: Path, mark_file: str) -> bool:
-  return (path / mark_file).exists()
-
-
-def sorted_dirs(root: Path):
-  """
-  Produce a list of all directories under root, in parent-before-child order.
-  Sort rule:
-    1. by path length
-    2. then lexicographically
-  """
-  all_dirs = []
-  for p in root.rglob("*"):
-    if p.is_dir():
-      all_dirs.append(p)
-
-  return sorted(
-    all_dirs
-    ,key = lambda p: (len(p.parts), str(p))
-  )
-
-
-# ----------------------------------------------------------------------
-# traversal
-# ----------------------------------------------------------------------
-
-def visible_dirs(root: Path, ignore_tree, mark_file: str):
-  """
-  Yield all dirs under root, applying:
-    - skip .git
-    - apply .gitignore rules (if a dir is ignored, do not descend)
-    - parent-before-child ordering
-  """
-  for d in sorted_dirs(root):
-    rel = d.relative_to(root)
-
-    if rel == Path("."):
-      continue
-
-    # skip .git explicitly
-    if d.name == ".git":
-      continue
-
-    # .gitignore filtering
-    if ignore_tree.check(str(rel)) == "Ignore":
-      continue
-
-    yield d
-
-
-# ----------------------------------------------------------------------
-# actions
-# ----------------------------------------------------------------------
-
-def action_list(root, ignore_tree, mark_file, mode):
-  """
-  mode ∈ {"empty","marked","all"}
-  """
-  for d in visible_dirs(root, ignore_tree, mark_file):
-    if mode == "all":
-      print(d.relative_to(root))
-      continue
-
-    if mode == "marked":
-      if has_mark(d, mark_file):
-        print(d.relative_to(root))
-      continue
-
-    if mode == "empty":
-      if is_empty_dir(d):
-        print(d.relative_to(root))
-      continue
-
-
-def action_mark(root, ignore_tree, mark_file, mode):
-  """
-  mode ∈ {"empty","all"}
-  """
-  for d in visible_dirs(root, ignore_tree, mark_file):
-    if mode == "empty" and not is_empty_dir(d):
-      continue
-    try:
-      (d / mark_file).touch(exist_ok=True)
-    except Exception:
-      pass
-
-
-def action_clean(root, ignore_tree, mark_file, mode):
-  """
-  mode ∈ {"nonempty","all"}
-  """
-  for d in visible_dirs(root, ignore_tree, mark_file):
-    m = d / mark_file
-    if not m.exists():
-      continue
-
-    if mode == "nonempty":
-      if is_empty_dir(d):
-        continue
-
-    try:
-      m.unlink()
-    except Exception:
-      pass
-
-
-# ----------------------------------------------------------------------
-# usage
-# ----------------------------------------------------------------------
-
-USAGE = """
-usage:
-  git-empty-dir (list|mark|clean) [all|marked|empty] [file-<name>]
-  git-empty-dir help
-  git-empty-dir usage
-
-defaults:
-  mark-file = .gitkeep
-  ignores .git
-  follows .gitignore (no descent into ignored dirs)
-
-examples:
-  git-empty-dir list
-  git-empty-dir list marked file-.githolder
-  git-empty-dir mark
-  git-empty-dir clean all
-"""
-
-
-# ----------------------------------------------------------------------
-# CLI
-# ----------------------------------------------------------------------
-
-def CLI(argv):
-  if len(argv) == 0:
-    print(USAGE)
-    return 0
-
-  cmd = argv[0]
-
-  if cmd in ("help","usage"):
-    print(USAGE)
-    return 0
-
-  # command
-  if cmd not in ("list","mark","clean"):
-    print(f"unknown command: {cmd}")
-    print(USAGE)
-    return 1
-
-  # submode
-  mode = None
-  mark_file = ".gitkeep"
-
-  for a in argv[1:]:
-    if a.startswith("file-"):
-      mark_file = a[5:]
-      continue
-
-    if a in ("all","empty","marked"):
-      mode = a
-      continue
-
-    print(f"unknown argument: {a}")
-    print(USAGE)
-    return 1
-
-  # defaults
-  if cmd == "list":
-    if mode is None:
-      mode = "empty"
-  elif cmd == "mark":
-    if mode is None:
-      mode = "empty"
-  elif cmd == "clean":
-    if mode is None:
-      mode = "nonempty"
-
-  root = Path(".").resolve()
-  ignore_tree = load_gitignore_tree(root)
-
-  if cmd == "list":
-    action_list(root, ignore_tree, mark_file, mode)
-
-  elif cmd == "mark":
-    if mode == "all":
-      action_mark(root, ignore_tree, mark_file, "all")
-    else:
-      action_mark(root, ignore_tree, mark_file, "empty")
-
-  elif cmd == "clean":
-    if mode == "all":
-      action_clean(root, ignore_tree, mark_file, "all")
-    else:
-      action_clean(root, ignore_tree, mark_file, "nonempty")
-
-  return 0
-
-
-if __name__ == "__main__":
-  sys.exit(CLI(sys.argv[1:]))
diff --git a/shared/authored/git-empty-dir/Harmony.py b/shared/authored/git-empty-dir/Harmony.py
deleted file mode 120000 (symlink)
index 112663e..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../source_sync/Harmony.py
\ No newline at end of file
diff --git a/shared/authored/git-empty-dir/load_command_module.py b/shared/authored/git-empty-dir/load_command_module.py
deleted file mode 120000 (symlink)
index 87b98be..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../source_sync/load_command_module.py
\ No newline at end of file
diff --git a/shared/authored/git-empty-dir/meta.py b/shared/authored/git-empty-dir/meta.py
deleted file mode 100644 (file)
index dee6439..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-meta.py - thin wrappers around command modules
-
-Current responsibilities:
-  1. Load the incommon 'printenv' command module (no .py extension)
-     using load_command_module.load_command_module().
-  2. Expose printenv() here, calling the imported printenv() work
-     function with default arguments (equivalent to running without
-     any CLI arguments).
-  3. Provide a simple version printer for this meta module.
-  4. Provide a small debug tag API (set/clear/has).
-"""
-
-from __future__ import annotations
-
-import datetime
-from load_command_module import load_command_module
-
-
-# Load the incommon printenv module once at import time
-_PRINTENV_MODULE = load_command_module("printenv")
-_Z_MODULE = load_command_module("Z")
-
-
-# Meta module version
-_major = 1
-_minor = 1
-def version_print() -> None:
-  """
-  Print the meta module version as MAJOR.MINOR.
-  """
-  print(f"{_major}.{_minor}")
-
-
-# Debug tag set and helpers
-_debug = set([
-])
-
-
-def debug_set(tag: str) -> None:
-  """
-  Add a debug tag to the meta debug set.
-  """
-  _debug.add(tag)
-
-
-def debug_clear(tag: str) -> None:
-  """
-  Remove a debug tag from the meta debug set, if present.
-  """
-  _debug.discard(tag)
-
-
-def debug_has(tag: str) -> bool:
-  """
-  Return True if the given debug tag is present.
-  """
-  return tag in _debug
-
-
-# Touch the default tag once so static checkers do not complain about
-# unused helpers when imported purely for side-effects.
-debug_has("Command")
-
-
-def printenv() -> int:
-  """
-  Call the imported printenv() work function with default arguments:
-    - no null termination
-    - no newline quoting
-    - no specific names (print full environment)
-    - prog name 'printenv'
-  """
-  return _PRINTENV_MODULE.printenv(
-    False      # null_terminate
-    ,False     # quote_newlines
-    ,[]        # names
-    ,"printenv"
-  )
-
-
-def z_format_mtime(
-  mtime: float
-) -> str:
-  """
-  Format a POSIX mtime (seconds since epoch, UTC) using the Z module.
-
-  Uses Z.ISO8601_FORMAT and Z.make_timestamp(dt=...).
-  """
-  dt = datetime.datetime.fromtimestamp(mtime, datetime.timezone.utc)
-  return _Z_MODULE.make_timestamp(
-    fmt=_Z_MODULE.ISO8601_FORMAT
-    ,dt=dt
-  )
diff --git a/shared/authored/git-empty-dir/source_sync b/shared/authored/git-empty-dir/source_sync
deleted file mode 120000 (symlink)
index 9fd1d51..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../source_sync/
\ No newline at end of file
diff --git a/shared/authored/sync/A_minus_B b/shared/authored/sync/A_minus_B
deleted file mode 100755 (executable)
index f6f7bbb..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-A_minus_B - CLI test driver for skeleton.tree_dict_A_minus_B(A, B)
-
-Usage:
-  A_minus_B <A_root> <B_root>
-"""
-
-from __future__ import annotations
-
-import os
-import sys
-from typing import Sequence
-
-import meta
-import skeleton
-
-
-def CLI(argv: Sequence[str] | None = None) -> int:
-  if argv is None:
-    argv = sys.argv[1:]
-
-  prog = os.path.basename(sys.argv[0]) if sys.argv else "A_minus_B"
-
-  if len(argv) != 2 or argv[0] in ("-h", "--help"):
-    print(f"Usage: {prog} <A_root> <B_root>")
-    return 1
-
-  A_root = argv[0]
-  B_root = argv[1]
-
-  if not os.path.isdir(A_root):
-    print(f"{prog}: {A_root}: not a directory")
-    return 2
-
-  if not os.path.isdir(B_root):
-    print(f"{prog}: {B_root}: not a directory")
-    return 3
-
-  A = skeleton.tree_dict_make(A_root, None)
-  B = skeleton.tree_dict_make(B_root, None)
-
-  meta.debug_set("tree_dict_A_minus_B")
-
-  _result = skeleton.tree_dict_A_minus_B(A, B)
-
-  return 0
-
-
-if __name__ == "__main__":
-  raise SystemExit(CLI())
diff --git a/shared/authored/sync/CLI.py b/shared/authored/sync/CLI.py
deleted file mode 100755 (executable)
index f7fb0b0..0000000
+++ /dev/null
@@ -1,282 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-CLI.py - Harmony skeleton checker
-
-Grammar (informal):
-
-  check <command>* [<other>]
-
-  <command>   :: <help> | <no_other> | <has_other>
-
-  <help>      :: version | help | usage
-  <no_other>  :: environment
-  <has_other> :: structure | import | export | suspicious | addendum | all
-
-Commands are sorted into three sets:
-  1. HELP_COMMANDS
-  2. NO_OTHER_COMMANDS
-  3. HAS_OTHER_COMMANDS
-
-At runtime, argv commands are classified into four lists:
-  1. help_list
-  2. no_other_list
-  3. has_other_list
-  4. unclassified_list
-
-If the meta debug set contains the tag "print_command_lists", these four lists
-are printed.
-
-If 'environment' appears in no_other_list, the meta.printenv() helper
-is invoked to print the environment.
-
-For <has_other> commands we compare:
-
-  A = Harmony skeleton tree_dict
-  B = <other> project tree_dict (path is the last argv token when any
-      <has_other> is present before it).
-"""
-
-from __future__ import annotations
-
-import os
-import sys
-from typing import Sequence
-
-import command
-import doc
-import Harmony
-import meta
-import skeleton
-
-# meta.debug_set("print_command_lists")
-
-# Command tag sets (classification universe)
-HELP_COMMANDS: set[str] = set([
-  "version"
-  ,"help"
-  ,"usage"
-])
-
-NO_OTHER_COMMANDS: set[str] = set([
-  "environment"
-])
-
-HAS_OTHER_COMMANDS: set[str] = set([
-  "structure"
-  ,"import"
-  ,"export"
-  ,"suspicious"
-  ,"addendum"
-  ,"all"
-])
-
-
-def command_type(arg: str) -> str:
-  """
-  Classify a single command token.
-
-  Returns:
-    "Help"         if arg is a help command
-    "NoOther"      if arg is a no_other command
-    "HasOther"     if arg is a has_other command
-    "UnClassified" otherwise
-  """
-  if arg in HELP_COMMANDS:
-    return "Help"
-
-  if arg in NO_OTHER_COMMANDS:
-    return "NoOther"
-
-  if arg in HAS_OTHER_COMMANDS:
-    return "HasOther"
-
-  return "UnClassified"
-
-
-def print_command_lists(
-  help_list: list[str]
-  ,no_other_list: list[str]
-  ,has_other_list: list[str]
-  ,unclassified_list: list[str]
-) -> None:
-  """
-  Print the four classified command lists derived from argv.
-  """
-  print("help_list:", help_list)
-  print("no_other_list:", no_other_list)
-  print("has_other_list:", has_other_list)
-  print("unclassified_list:", unclassified_list)
-
-
-def CLI(argv: Sequence[str] | None = None) -> int:
-  """
-  CLI entrypoint.
-
-  Responsibilities:
-    1. Accept argv (or sys.argv[1:] by default).
-    2. Classify arguments using command_type(), with the last argument
-       treated specially to avoid aliasing.
-    3. Invoke behaviors implied by the commands.
-    4. Return integer status code.
-
-  Argument interpretation:
-
-    Let argv = [a0, a1, ..., aN-1].
-
-    - If N == 0:
-        no commands; nothing to do.
-
-    - If N >= 1:
-        * Classify a0..aN-2.
-          - If any are UnClassified -> error.
-
-        * If any <has_other> appear in a0..aN-2:
-            - aN-1 is treated as <other> path (B_root), not classified.
-
-        * If no <has_other> appear in a0..aN-2:
-            - Classify aN-1:
-                - If UnClassified -> error (unknown command).
-                - If HasOther    -> error (other path not specified).
-                - Else           -> added to Help / NoOther lists.
-  """
-  if argv is None:
-    argv = sys.argv[1:]
-
-  # No arguments: print usage and exit with status 1.
-  if len(argv) == 0:
-    doc.print_usage()
-    return 1
-
-  # No arguments: nothing to do (could later decide to print usage).
-  if len(argv) == 0:
-    return 0
-
-  # Split into head (all but last) and last argument
-  head = argv[:-1]
-  last = argv[-1]
-
-  help_list: list[str] = []
-  no_other_list: list[str] = []
-  has_other_list: list[str] = []
-  unclassified_list: list[str] = []
-
-  # 1. Classify head tokens
-  for arg in head:
-    ct = command_type(arg)
-
-    if ct == "Help":
-      help_list.append(arg)
-    elif ct == "NoOther":
-      no_other_list.append(arg)
-    elif ct == "HasOther":
-      has_other_list.append(arg)
-    else:
-      unclassified_list.append(arg)
-
-  # Any unclassified in the head is an error
-  if len(unclassified_list) > 0:
-    first_bad = unclassified_list[0]
-    print(f"Unrecognized command: {first_bad}")
-    return 5
-
-  head_has_other = (len(has_other_list) > 0)
-
-  B_root: str | None = None
-
-  if head_has_other:
-    # 2A. Any <has_other> in head -> last arg is always <other> path.
-    B_root = os.path.abspath(last)
-  else:
-    # 2B. No <has_other> in head -> classify last.
-    ct = command_type(last)
-
-    if ct == "UnClassified":
-      print(f"Unrecognized command: {last}")
-      return 5
-
-    if ct == "HasOther":
-      print("Other path not specified for has_other command(s).")
-      return 6
-
-    if ct == "Help":
-      help_list.append(last)
-    elif ct == "NoOther":
-      no_other_list.append(last)
-    # ct cannot be HasOther here due to earlier check.
-
-  if meta.debug_has("print_command_lists"):
-    print_command_lists(
-      help_list
-      ,no_other_list
-      ,has_other_list
-      ,unclassified_list
-    )
-
-  # Help handling
-  if len(help_list) > 0:
-    if "version" in help_list:
-      meta.version_print()
-    if "usage" in help_list:
-      doc.print_usage()
-    if "help" in help_list:
-      doc.print_help()
-    return 1
-
-  ret_val = 0
-
-  # No-other commands (environment, etc.)
-  if "environment" in no_other_list:
-    env_status = meta.printenv()
-    if env_status != 0:
-      ret_val = env_status
-
-  # If we still have no has_other commands, we are done.
-  # (Example: just "environment", or just "help/usage".)
-  if len(has_other_list) == 0:
-    return ret_val
-
-  # At this point we know:
-  #   - has_other_list is non-empty
-  #   - B_root must have been set (head_has_other was True)
-  if B_root is None:
-    print("Internal error: B_root not set despite has_other commands.")
-    return 7
-
-  if not os.path.isdir(B_root):
-    print(f"Other project path is not a directory: {B_root}")
-    return 4
-
-  # Determine Harmony root (A_root)
-  status, A_root = Harmony.where()
-
-  if status == "not-found":
-    print("Harmony project not found; normally this command is run from within Harmony.")
-    return 3
-
-  if status == "different":
-    print("Seems we are not running in the Harmony project, will exit.")
-    return 2
-
-  # Build tree_dicts for A (Harmony) and B (other project)
-  A_tree = skeleton.tree_dict_make(A_root, None)
-  B_tree = skeleton.tree_dict_make(B_root, None)
-
-  # Dispatch the <has_other> commands
-  cmd_status = command.dispatch(
-    has_other_list
-    ,A_tree
-    ,B_tree
-    ,A_root
-    ,B_root
-  )
-
-  if cmd_status != 0:
-    ret_val = cmd_status
-
-  return ret_val
-
-
-if __name__ == "__main__":
-  raise SystemExit(CLI())
diff --git a/shared/authored/sync/GitIgnore.py b/shared/authored/sync/GitIgnore.py
deleted file mode 100755 (executable)
index 70c6509..0000000
+++ /dev/null
@@ -1,270 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-GitIgnore.py - minimal .gitignore-based helper for Harmony projects
-
-Behavior:
-
-  1. During initialization, traverse the project tree rooted at
-     <project_path>.
-
-  2. Whenever a directory contains a '.gitignore' file, record:
-       - its relative directory path from the project root
-       - a list of regular expressions compiled from the patterns
-         in that '.gitignore' file
-
-     These are stored in:
-
-       self.rules: Dict[str, List[Pattern]]
-
-       where the key is the directory RELATIVE to the project root:
-         ""          -> project root (top-level .gitignore)
-         "src"       -> src/.gitignore
-         "src/module" -> src/module/.gitignore
-
-  3. check(<path>) -> token:
-
-       - <path> is a path relative to the project root.
-
-       - We compute all prefix directories of <path>, including the
-         root (""), for example:
-
-           path = "a/b/c.py"
-           prefixes = ["", "a", "a/b"]
-
-       - For each prefix, if there are regexes stored for that directory,
-         we collect them.
-
-       - We then test ALL collected regexes against the basename of
-         <path> (the last component only).
-
-       - If ANY regex matches, return 'Ignore'.
-         Otherwise return 'Accept'.
-
-Notes:
-
-  * We implement a simplified subset of .gitignore semantics suitable
-    for your current patterns and add a small base ignore set for
-    always-ignored names such as '.git'.
-"""
-
-from __future__ import annotations
-
-import fnmatch
-import os
-import re
-from typing import Dict, List
-import Harmony
-
-
-class GitIgnore:
-  """
-  GitIgnore(project_path)
-
-  Attributes:
-    project_path:
-      Absolute path to the project root.
-
-    rules:
-      Mapping from relative directory path -> list of compiled regex
-      patterns derived from that directory's '.gitignore' file.
-
-      Example:
-        rules[""]           -> patterns from <root>/.gitignore
-        rules["developer"]  -> patterns from developer/.gitignore
-
-    base_patterns:
-      List of compiled regex patterns applied to the basename of every
-      checked path, independent of any .gitignore file. Currently used
-      to always ignore '.git' directories.
-  """
-
-  def __init__(
-    self
-    ,project_path: str
-  ) -> None:
-    """
-    Initialize a GitIgnore instance with a path to a project and
-    scan for '.gitignore' files.
-    """
-    self.project_path: str = os.path.abspath(project_path)
-    self.rules: Dict[str, List[re.Pattern]] = {}
-
-    # Base patterns: always applied, regardless of .gitignore contents.
-    # These are matched against basenames only.
-    self.base_patterns: List[re.Pattern] = [
-      re.compile(r"^\.git$")    # ignore any basename == ".git"
-    ]
-
-    self._scan_project()
-
-  def _scan_project(self) -> None:
-    """
-    Traverse the project tree and populate self.rules with entries of
-    the form:
-
-      <rel_dir> -> [Pattern, Pattern, ...]
-
-    where <rel_dir> is the directory containing '.gitignore', relative
-    to the project root ("" for root).
-    """
-    root = self.project_path
-
-    for dirpath, dirnames, filenames in os.walk(root, topdown=True):
-      if ".gitignore" not in filenames:
-        continue
-
-      rel_dir = os.path.relpath(dirpath, root)
-      if rel_dir == ".":
-        rel_dir = ""
-
-      gitignore_path = os.path.join(dirpath, ".gitignore")
-      patterns = self._parse_gitignore_file(gitignore_path)
-
-      if patterns:
-        if rel_dir not in self.rules:
-          self.rules[rel_dir] = []
-        self.rules[rel_dir].extend(patterns)
-
-  def _parse_gitignore_file(
-    self
-    ,gitignore_path: str
-  ) -> List[re.Pattern]:
-    """
-    Parse a single '.gitignore' file into a list of compiled regex patterns.
-
-    Simplified rules:
-      - Blank lines and lines starting with '#' are ignored.
-      - Lines containing '/' in the MIDDLE are currently ignored
-        (future extension).
-      - Lines ending with '/' are treated as directory name patterns:
-          '__pycache__/' -> pattern on basename '__pycache__'
-      - All patterns are treated as name globs and compiled via
-        fnmatch.translate(), to be matched against basenames only.
-    """
-    patterns: List[re.Pattern] = []
-
-    try:
-      with open(gitignore_path, "r", encoding="utf-8") as f:
-        for raw_line in f:
-          line = raw_line.strip()
-
-          # Skip comments and blank lines
-          if not line or line.startswith("#"):
-            continue
-
-          # Remove trailing '/' for directory patterns (e.g. '__pycache__/')
-          if line.endswith("/"):
-            line = line[:-1].strip()
-            if not line:
-              continue
-
-          # If there is still a '/' in the line, we do not support this
-          # pattern in this minimal implementation.
-          if "/" in line:
-            continue
-
-          # Compile as a name glob -> regex
-          regex_text = fnmatch.translate(line)
-          patterns.append(re.compile(regex_text))
-
-    except OSError:
-      # If the .gitignore cannot be read, just skip it.
-      return patterns
-
-    return patterns
-
-  def check(
-    self
-    ,path: str
-  ) -> str:
-    """
-    Check a path against the collected .gitignore patterns.
-
-    path:
-      A path relative to the project root.
-
-    Returns:
-      'Ignore' if any applicable pattern matches the basename of the path,
-      otherwise 'Accept'.
-    """
-    # Normalize the incoming path
-    norm = os.path.normpath(path)
-
-    # If the path is '.' or empty, we accept it
-    if norm in ("", "."):
-      return "Accept"
-
-    basename = os.path.basename(norm)
-
-    # First, apply base patterns (always applied).
-    for pat in self.base_patterns:
-      if pat.match(basename):
-        return "Ignore"
-
-    # Build the list of directories that may contribute .gitignore rules.
-    #
-    # For path "a/b/c":
-    #   prefixes: ["", "a", "a/b"]
-    parts = norm.split(os.sep)
-
-    prefixes: List[str] = [""]
-    prefix = None
-    for part in parts[:-1]:
-      if prefix is None:
-        prefix = part
-      else:
-        prefix = os.path.join(prefix, part)
-      prefixes.append(prefix)
-
-    # Collect all patterns from the applicable .gitignore directories
-    for rel_dir in prefixes:
-      dir_patterns = self.rules.get(rel_dir)
-      if not dir_patterns:
-        continue
-
-      for pat in dir_patterns:
-        if pat.match(basename):
-          return "Ignore"
-
-    return "Accept"
-
-
-def test_GitIgnore() -> int:
-  """
-    1. Locate the Harmony project root using Harmony.where().
-    2. Create a GitIgnore instance rooted at that path.
-    3. Print:
-       - directories that have .gitignore rules
-       - directories (relative) that would be ignored by check()
-  """
-  status, Harmony_root = Harmony.where()
-
-  if status == "not-found":
-    print("Harmony project not found; cannot test GitIgnore.")
-    return 1
-
-  if status == "different":
-    print("Warning: Harmony not found, using nearest .git directory for GitIgnore test.")
-
-  gi = GitIgnore(Harmony_root)
-
-  print(".gitignore rule directories (relative to Harmony root):")
-  for rel_dir in sorted(gi.rules.keys()):
-    print(f"  {rel_dir if rel_dir else '.'}")
-
-  print("\nDirectories that would be ignored (relative to Harmony root):")
-  for dirpath, dirnames, filenames in os.walk(Harmony_root, topdown=True):
-    rel_dir = os.path.relpath(dirpath, Harmony_root)
-    if rel_dir == ".":
-      rel_dir = ""
-
-    if gi.check(rel_dir) == "Ignore":
-      print(f"  {rel_dir if rel_dir else '.'}")
-
-  return 0
-
-
-if __name__ == "__main__":
-  raise SystemExit(test_GitIgnore())
diff --git a/shared/authored/sync/Harmony.py b/shared/authored/sync/Harmony.py
deleted file mode 100644 (file)
index 9385507..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-locate the project root
-"""
-
-from __future__ import annotations
-
-import meta
-import os
-import sys
-from typing import Any, Callable, Dict
-
-# where
-#
-# Context / assumptions:
-#   1. This module lives somewhere under the Harmony tree, for example:
-#        /.../Harmony/tool/skeleton/skeleton.py
-#   2. CLI.py is run from somewhere inside the same tree (or a clone).
-#
-# Search behavior:
-#   1. Start from the directory containing this file.
-#   2. Walk upward towards the filesystem root, with limits:
-#      a) Do not move up more than 5 levels.
-#      b) Stop immediately if the current directory contains a
-#         '.git' subdirectory.
-#
-# Result classification:
-#   status is one of:
-#     'found'      -> we found a directory whose basename is 'Harmony'
-#     'different'  -> we stopped at a directory that has a '.git'
-#                     subdirectory, but its basename is not 'Harmony'
-#     'not-found'  -> we hit the 5-level limit or filesystem root
-#                     without finding 'Harmony' or a '.git' directory
-#
-# Path:
-#   - In all cases, the returned path is the last directory inspected:
-#       * the 'Harmony' directory (status 'found'), or
-#       * the directory with '.git' (status 'different'), or
-#       * the directory at the 5-level limit / filesystem root
-#         (status 'not-found').
-#
-# Debug printing:
-#   - If meta.debug_has("print_Harmony_root") is true, print:
-#       * "The Harmony project root found at: {path}"
-#         when status == 'found'
-#       * "Harmony not found, but found: {path}"
-#         when status == 'different'
-#       * "Harmony not found."
-#         when status == 'not-found'
-def where() -> tuple[str, str]:
-  """
-  Locate the Harmony root (or best guess).
-
-  Returns:
-    (status, path)
-  """
-  here = os.path.abspath(__file__)
-  d = os.path.dirname(here)
-
-  harmony_root = None
-  status = "not-found"
-
-  max_up = 5
-  steps = 0
-
-  while True:
-    base = os.path.basename(d)
-
-    # Case 1: exact 'Harmony' directory name
-    if base == "Harmony":
-      harmony_root = d
-      status = "found"
-      break
-
-    # Case 2: stop at a directory that has a '.git' subdirectory
-    git_dir = os.path.join(d, ".git")
-    if os.path.isdir(git_dir):
-      harmony_root = d
-      if base == "Harmony":
-        status = "found"
-      else:
-        status = "different"
-      break
-
-    parent = os.path.dirname(d)
-
-    # Stop if we hit filesystem root
-    if parent == d:
-      harmony_root = d
-      status = "not-found"
-      break
-
-    steps += 1
-    if steps > max_up:
-      # Reached search depth limit; last inspected directory is d
-      harmony_root = d
-      status = "not-found"
-      break
-
-    d = parent
-
-  if harmony_root is None:
-    # Extremely defensive; in practice harmony_root will be set above.
-    harmony_root = d
-
-  root_base = os.path.basename(harmony_root)
-
-  # Warning to stderr if we are not literally in a 'Harmony' directory
-  if root_base != "Harmony":
-    sys.stderr.write(
-      f"WARNING: Harmony root basename is '{root_base}', expected 'Harmony'.\n"
-    )
-
-  if meta.debug_has("print_Harmony_root"):
-    if status == "found":
-      print(f"The Harmony project root found at: {harmony_root}")
-    elif status == "different":
-      print(f"Harmony not found, but found: {harmony_root}")
-    else:
-      print("Harmony not found.")
-
-  return status, harmony_root
-
-def test_where() -> int:
-  """
-  Simple test that prints the Harmony root using the debug flag.
-  """
-  meta.debug_set("print_Harmony_root")
-  status, _root = where()
-  return 0 if status != "not-found" else 1
-
diff --git a/shared/authored/sync/Harmony_where b/shared/authored/sync/Harmony_where
deleted file mode 100755 (executable)
index 9d39f1e..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-Harmony_where - CLI to locate the Harmony project root
-
-Usage:
-  Harmony_where
-
-Prints the status and path returned by Harmony.where().
-"""
-
-from __future__ import annotations
-
-import sys
-
-import Harmony
-
-
-def CLI(argv=None) -> int:
-  # Ignore argv; no arguments expected
-  status, Harmony_root = Harmony.where()
-
-  if status == "found":
-    print(f"Harmony project root found at: {Harmony_root}")
-    return 0
-
-  if status == "different":
-    print(f"Harmony not found, but nearest .git directory is: {Harmony_root}")
-    return 1
-
-  print("Harmony project root not found.")
-  return 2
-
-
-if __name__ == "__main__":
-  raise SystemExit(CLI())
diff --git a/shared/authored/sync/README.org b/shared/authored/sync/README.org
deleted file mode 100644 (file)
index 387780d..0000000
+++ /dev/null
@@ -1,278 +0,0 @@
-#+TITLE: skeleton_compare – Harmony skeleton comparison tool
-#+AUTHOR: Reasoning Technology
-
-* 1. Overview
-
-1.1
-~skeleton_compare~ compares a Harmony skeleton (=A=) with a derived or legacy project (=B=).
-
-1.2
-It answers:
-
-- How has B diverged from A?
-- What should be imported back into A?
-- What should be exported from A into B?
-- Which nodes are misplaced or suspicious?
-- Which nodes represent valid project-specific extensions?
-
-1.3
-The entrypoint in this project is the symlink:
-
-- =tool/skeleton_compaare=
-
-which points to:
-
-- =tool/skeleton_compare_source/CLI.py=
-
-* 2. Role in the Harmony ecosystem
-
-2.1
-Harmony defines a skeleton layout (directories, leaves, extension points).
-
-2.2
-Projects are expected to:
-
-- start from that skeleton
-- add work under approved extension points
-- keep core structure aligned over time
-
-2.3
-Reality diverges:
-
-- legacy projects that predate Harmony
-- projects with ad-hoc edits in skeleton areas
-- skeleton evolution over months or years
-
-2.4
-~skeleton_compare~ provides:
-
-- a structural comparison
-- a semantic comparison (types, topology)
-- a chronological comparison (mtimes)
-- actionable commands to re-align projects
-
-* 3. High-level behavior
-
-3.1
-Tree construction
-
-1. Build =tree_dict= for A (Harmony skeleton).
-2. Build =tree_dict= for B (other project).
-3. Attach metadata per relative path:
-
-   - =node_type= :: =directory= | =file= | =other= | =constrained=
-   - =dir_info=  :: =root= | =branch= | =leaf= | =NA=
-   - =mtime=     :: float seconds since epoch
-
-3.2
-Git ignore
-
-1. A simplified =.gitignore= model is applied.
-2. Some paths (e.g., =.git=) are always ignored.
-3. Only paths admitted by this model participate in comparisons.
-
-3.3
-Topology classification (relative to A)
-
-1. =in_between= :: under a directory in A, but not under any leaf in A.
-2. =below=      :: under a leaf directory in A.
-3. Neither      :: not under any directory known to A (ignored for most commands).
-
-3.4
-Chronological classification
-
-1. newer(B,A) :: B node has a newer mtime than A at the same path.
-2. older(B,A) :: B node has an older mtime than A at the same path.
-3. A-only     :: path exists in A but not B.
-4. B-only     :: path exists in B but not A.
-
-* 4. Command surface (conceptual)
-
-4.1
-~structure~
-
-1. Compares directory topology.
-2. Reports directories that:
-
-   - exist as directories in A
-   - are missing or non-directories in B
-
-3. Intended use:
-
-   - detect missing branches in projects
-   - detect structural drift
-
-4.2
-~import~
-
-1. Direction: B → A.
-2. Only considers:
-
-   - nodes in the =in_between= region of B
-   - that are new or absent in A
-
-3. Outputs:
-
-   - ~mkdir -p~ commands (when needed)
-   - ~cp --parents -a~ commands for files
-   - a comment list for nodes that cannot be handled automatically
-     (type mismatches, non-file/dir, constrained nodes)
-
-4. Intended use:
-
-   - mine “good ideas” in B that belong in the skeleton
-   - keep Harmony evolving based on real projects
-
-4.3
-~export~
-
-1. Direction: A → B.
-2. Considers:
-
-   - A-only nodes (present in A, missing in B)
-   - nodes where A’s file is newer than B’s file
-
-3. Outputs:
-
-   - ~mkdir -p~ commands for B
-   - ~cp --parents -a~ commands for files
-
-4. Intended use:
-
-   - bring B back into alignment with the current Harmony skeleton
-   - propagate skeleton fixes and improvements into projects
-
-4.4
-~suspicious~
-
-1. Reports nodes in B that are:
-
-   - inside A’s directory structure
-   - but not under any leaf directory
-
-2. Intended use:
-
-   - highlight questionable placements
-   - identify candidates for new skeleton structure
-   - catch misuse of the skeleton (work living in the “framework” layer)
-
-4.5
-~addendum~
-
-1. Reports nodes in B that are:
-
-   - under leaf directories in A
-
-2. Intended use:
-
-   - show work added at the intended extension points
-   - give a quick outline of “project-specific” content layered on Harmony
-
-4.6
-~all~
-
-1. Runs:
-
-   - =structure=
-   - =import=
-   - =export=
-   - =suspicious=
-   - =addendum=
-
-2. Intended use:
-
-   - periodic health check of a project against Harmony
-   - initial analysis when inheriting an old project
-
-* 5. Safety and behavior guarantees
-
-5.1
-No direct modification
-
-1. ~skeleton_compaare~ itself does not modify either tree.
-2. It only prints suggested shell commands.
-3. A human is expected to review and run those commands (or not).
-
-5.2
-Constrained and unknown nodes
-
-1. Some paths are “constrained”:
-
-   - object exists but metadata (e.g., ~mtime~) cannot be safely read
-   - typical for special files or broken links
-
-2. These are:
-
-   - classified as =constrained=
-   - never touched by import/export logic
-   - surfaced in “not handled automatically” lists
-
-5.3
-Robust to legacy layouts
-
-1. A and B are assumed to be non-overlapping roots.
-2. B does not have to be a clean Harmony derivative.
-3. The tool is designed to:
-
-   - tolerate missing branches
-   - tolerate ad-hoc additions
-   - still classify and report differences coherently
-
-* 6. How to run it
-
-6.1
-From inside the Harmony repo:
-
-#+begin_src sh
-cd /path/to/Harmony
-tool/skeleton_compaare help
-tool/skeleton_compaare usage
-tool/skeleton_compaare structure ../SomeProject
-tool/skeleton_compaare all ../Rabbit
-#+end_src
-
-6.2
-The CLI help (from ~doc.py~) is the canonical reference for:
-
-1. grammar and argument rules
-2. meaning of A and B
-3. exact semantics of each command
-
-This =.org= file is a conceptual overview for Harmony toolsmiths and administrators.
-
-* 7. Maintenance notes
-
-7.1
-Core modules
-
-1. =skeleton_compare_source/skeleton.py=
-   - tree construction
-   - topology classification
-   - “newer/older” logic
-   - in-between / below partitioning
-
-2. =skeleton_compare_source/command.py=
-   - high-level command semantics
-   - import/export planning and printing
-
-3. =skeleton_compare_source/CLI.py=
-   - argument classification
-   - environment checks
-   - dispatch to command handlers
-
-7.2
-Change discipline
-
-1. CLI behavior and text should be updated in:
-
-   - =doc.py= (help/usage text)
-   - this =.org= file (conceptual intent)
-
-2. Any behavioral change that affects:
-
-   - classification rules
-   - import/export semantics
-   - constrained handling
-
-   should be reflected here in section 3 or 4.
-
diff --git a/shared/authored/sync/command.py b/shared/authored/sync/command.py
deleted file mode 100644 (file)
index 0ebb856..0000000
+++ /dev/null
@@ -1,539 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-command.py - high-level dispatch for <has_other> Harmony check commands
-
-Commands (semantics):
-
-  structure:
-    - Differences in directory structure: directories present in A but
-      not present as directories in B.
-
-  import:
-    - Shell copy commands to copy:
-        * in-between nodes in B that are newer than A (same relative path), or
-        * in-between nodes in B that do not exist in A at all.
-      Direction: B -> A
-      Also emits:
-        * a mkdir list (directories to create in A)
-        * an "other" list for type mismatches / non-file/dir nodes.
-
-  export:
-    - Shell copy commands to copy:
-        * nodes in A that are newer than B, and
-        * nodes in A that do not exist in B.
-      Direction: A -> B
-      Also emits:
-        * a mkdir list (directories to create in B)
-        * an "other" list for type mismatches / non-file/dir nodes.
-
-  suspicious:
-    - Nodes in B that fall "in between" the Harmony skeleton topology:
-      under some A directory, but not under any A leaf directory.
-      (tree_dict_in_between_and_below(A,B).in_between)
-
-  addendum:
-    - Nodes in B that fall "below" Harmony leaf directories:
-      added work in appropriate extension locations.
-      (tree_dict_in_between_and_below(A,B).below)
-
-  all:
-    - Runs structure, import, export, suspicious, and addendum.
-"""
-
-from __future__ import annotations
-
-import os
-from typing import Any, Dict, List, Tuple
-
-import skeleton
-
-TreeDict = Dict[str, Dict[str, Any]]
-
-
-def shell_quote(
-  s: str
-) -> str:
-  """
-  Minimal single-quote shell quoting.
-  """
-  return "'" + s.replace("'", "'\"'\"'") + "'"
-
-
-def _print_header(
-  title: str
-) -> None:
-  print()
-  print(f"== {title} ==")
-
-
-# ----------------------------------------------------------------------
-# structure: directories in A that are missing / non-directories in B
-# ----------------------------------------------------------------------
-def cmd_structure(
-  A: TreeDict
-  ,B: TreeDict
-) -> int:
-  """
-  structure: differences in directory structure, directories in A - B.
-
-  We include any path where:
-    - A[path].node_type == 'directory', and
-    - either path not in B, or B[path].node_type != 'directory'.
-  """
-  structural: TreeDict = {}
-
-  for path, info_A in A.items():
-    if info_A.get("node_type") != "directory":
-      continue
-
-    info_B = B.get(path)
-    if info_B is None or info_B.get("node_type") != "directory":
-      structural[path] = info_A
-
-  if not structural:
-    _print_header("structure")
-    print("No structural directory differences (A - B).")
-    return 0
-
-  _print_header("structure: directories in A not in B")
-  skeleton.tree_dict_print(structural)
-  return 0
-
-
-# ----------------------------------------------------------------------
-# import: B -> A (mkdir, cp, and "other" list), using in_between_newer
-# ----------------------------------------------------------------------
-
-
-def build_import_commands(
-  A_tree: TreeDict
-  ,B_tree: TreeDict
-  ,A_root: str
-  ,B_root: str
-) -> Tuple[List[str], List[str], List[str]]:
-  """
-  Compute shell commands to update A from B.
-
-  Returns:
-    (mkdir_cmds, cp_cmds, other_list)
-
-  Semantics:
-
-    mkdir_cmds:
-      - Directories that are directories in B, but are missing in A.
-      - We DO NOT auto-resolve type mismatches (e.g. B=directory,
-        A=file); those go into other_list.
-
-    cp_cmds:
-      - Files where:
-          * the path does not exist in A, OR
-          * the node in A is not a file, OR
-          * the B copy is newer than A (mtime comparison).
-      - However, if A has a non-file at that path, we treat it as a
-        type mismatch and add that path to other_list instead of
-        emitting a cp command.
-
-    other_list:
-      - Human-readable notes for:
-          * type mismatches between A and B, and
-          * nodes in B that are neither 'file' nor 'directory'.
-  """
-  mkdir_cmds: List[str] = []
-  cp_cmds: List[str] = []
-  other_list: List[str] = []
-
-  for rel_path, b_info in B_tree.items():
-    b_type = b_info.get("node_type")
-    rel_display = rel_path if rel_path else "."
-
-    a_info = A_tree.get(rel_path)
-    a_type = a_info.get("node_type") if a_info is not None else "MISSING"
-
-    # Case 1: B node is neither file nor directory -> other_list
-    if b_type not in ("file", "directory"):
-      other_list.append(
-        f"{rel_display}: A={a_type}, B={b_type}"
-      )
-      continue
-
-    # Case 2: B directory
-    if b_type == "directory":
-      if a_info is None:
-        # Missing in A: copy the directory recursively.
-        src = os.path.join(B_root, rel_path) if rel_path else B_root
-        # The destination should be the parent directory in A.
-        # os.path.join(A_root, rel_path) gives the full path to the new directory in A.
-        # os.path.dirname of that gives the parent directory.
-        dst = os.path.dirname(os.path.join(A_root, rel_path))
-        mkdir_cmds.append(
-          f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
-        )
-      else:
-        # Exists in A: must also be a directory to be "structurally OK"
-        if a_type != "directory":
-          # Type mismatch: do not mkdir, just report
-          other_list.append(
-            f"{rel_display}: A={a_type}, B=directory"
-          )
-      continue
-
-    # Case 3: B file
-    #   Decide whether to copy B -> A, or report conflict.
-    if a_info is None:
-      # B-only file
-      src = os.path.join(B_root, rel_path) if rel_path else B_root
-      # Destination is the parent directory in A, with a trailing slash
-      dst = os.path.dirname(os.path.join(A_root, rel_path))
-      cp_cmds.append(
-        f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
-      )
-      continue
-
-    # A has something at this path
-    if a_type != "file":
-      # Type mismatch (e.g. A=directory, B=file, or A=other)
-      other_list.append(
-        f"{rel_display}: A={a_type}, B=file"
-      )
-      continue
-
-    # Both files: compare mtime
-    a_mtime = a_info.get("mtime")
-    b_mtime = b_info.get("mtime")
-
-    if isinstance(a_mtime, (int, float)) and isinstance(b_mtime, (int, float)):
-      if b_mtime > a_mtime:
-        src = os.path.join(B_root, rel_path) if rel_path else B_root
-        # Destination is the parent directory in A, with a trailing slash
-        dst = os.path.dirname(os.path.join(A_root, rel_path))
-        cp_cmds.append(
-          f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
-        )
-
-  return mkdir_cmds, cp_cmds, other_list
-
-def cmd_import(
-  A_tree: TreeDict
-  ,B_tree: TreeDict
-  ,A_root: str
-  ,B_root: str
-) -> int:
-  """
-  import: update the skeleton (A) from the project (B),
-  using only in_between_newer nodes.
-  """
-  inb_newer = skeleton.in_between_newer(A_tree, B_tree)
-
-  mkdir_cmds, cp_cmds, other_list = build_import_commands(
-    A_tree
-    ,inb_newer
-    ,A_root
-    ,B_root
-  )
-
-  print("== import: copy from B -> A (in-between newer only) ==")
-  print(f"# A root: {A_root}")
-  print(f"# B root: {B_root}")
-  print("# Only considering in-between files that are new or absent in A.")
-  print("#")
-
-  print("# Directories to copy from B -> A (cp -a):")
-  if mkdir_cmds:
-    for line in mkdir_cmds:
-      print(line)
-  else:
-    print("#   (none)")
-  print("#")
-
-  print("# Files to copy from B -> A (cp -a):")
-  if cp_cmds:
-    for line in cp_cmds:
-      print(line)
-  else:
-    print("#   (none)")
-  print("#")
-
-  print("# Nodes NOT handled automatically (type mismatches / non-file/dir):")
-  if other_list:
-    for rel in other_list:
-      print(f"#   {rel}")
-  else:
-    print("#   (none)")
-
-  return 0
-
-
-# ----------------------------------------------------------------------
-# export: A -> B (mkdir, cp, and "other" list)
-# ----------------------------------------------------------------------
-def build_export_commands(
-  A_tree: TreeDict
-  ,B_tree: TreeDict
-  ,A_root: str
-  ,B_root: str
-) -> Tuple[List[str], List[str], List[str]]:
-  """
-  Compute shell commands to update B from A.
-
-  Returns:
-    (mkdir_cmds, cp_cmds, other_list)
-
-  Semantics:
-
-    mkdir_cmds:
-      - Directories that are directories in A, but are missing in B.
-      - Type mismatches go into other_list.
-
-    cp_cmds:
-      - Files where:
-          * the path does not exist in B, OR
-          * the node in B is not a file, OR
-          * the A copy is newer than B (mtime comparison).
-      - If B has a non-file while A has a file, treat as type mismatch.
-
-    other_list:
-      - Human-readable notes for:
-          * type mismatches between A and B, and
-          * nodes in A that are neither 'file' nor 'directory'.
-  """
-  mkdir_cmds: List[str] = []
-  cp_cmds: List[str] = []
-  other_list: List[str] = []
-
-  # Sort keys to ensure parent directories are processed before their children.
-  sorted_paths = sorted(A_tree.keys(), key=len)
-  included_dirs: Set[str] = set()
-
-  for rel_path in sorted_paths:
-    a_info = A_tree[rel_path]
-    a_type = a_info.get("node_type")
-    rel_display = rel_path if rel_path else "."
-
-    b_info = B_tree.get(rel_path)
-    b_type = b_info.get("node_type") if b_info is not None else "MISSING"
-
-    # Case 1: A node is neither file nor directory -> other_list
-    if a_type not in ("file", "directory"):
-      other_list.append(
-        f"{rel_display}: A={a_type}, B={b_type}"
-      )
-      continue
-
-    # Check if this path is a child of an already included directory
-    is_child_of_included_dir = False
-    for d in included_dirs:
-      if rel_path.startswith(d + os.sep):
-        is_child_of_included_dir = True
-        break
-    
-    if is_child_of_included_dir:
-      continue
-
-    # Case 2: A directory
-    if a_type == "directory":
-      if b_info is None:
-        # Missing in B: copy the directory recursively.
-        src = os.path.join(A_root, rel_path) if rel_path else A_root
-        # The destination should be the parent directory in B.
-        dst = os.path.dirname(os.path.join(B_root, rel_path))
-        mkdir_cmds.append(
-          f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
-        )
-        included_dirs.add(rel_path)
-      else:
-        # Exists in B: must also be directory
-        if b_type != "directory":
-          other_list.append(
-            f"{rel_display}: A=directory, B={b_type}"
-          )
-      continue
-
-    # Case 3: A file
-    if b_info is None:
-      # A-only file
-      src = os.path.join(A_root, rel_path) if rel_path else A_root
-      # Destination is the parent directory in B, with a trailing slash
-      dst = os.path.dirname(os.path.join(B_root, rel_path))
-      cp_cmds.append(
-        f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
-      )
-      continue
-
-    if b_type != "file":
-      other_list.append(
-        f"{rel_display}: A=file, B={b_type}"
-      )
-      continue
-
-    # Both files: compare mtime
-    a_mtime = a_info.get("mtime")
-    b_mtime = b_info.get("mtime")
-
-    if isinstance(a_mtime, (int, float)) and isinstance(b_mtime, (int, float)):
-      if a_mtime > b_mtime:
-        src = os.path.join(A_root, rel_path) if rel_path else A_root
-        # Destination is the parent directory in B, with a trailing slash
-        dst = os.path.dirname(os.path.join(B_root, rel_path))
-        cp_cmds.append(
-          f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
-        )
-
-  return mkdir_cmds, cp_cmds, other_list
-
-
-def cmd_export(
-  A_tree: TreeDict
-  ,B_tree: TreeDict
-  ,A_root: str
-  ,B_root: str
-) -> int:
-  """
-  export: show directory creation and copy commands A -> B.
-  """
-  mkdir_cmds, cp_cmds, other_list = build_export_commands(
-    A_tree
-    ,B_tree
-    ,A_root
-    ,B_root
-  )
-
-  print("== export: copy from A -> B ==")
-  print(f"# A root: {A_root}")
-  print(f"# B root: {B_root}")
-  print("#")
-
-  print("# Directories to copy from A -> B (cp -a):")
-  if mkdir_cmds:
-    for line in mkdir_cmds:
-      print(line)
-  else:
-    print("#   (none)")
-  print("#")
-
-  print("# Files to copy from A -> B (cp -a):")
-  if cp_cmds:
-    for line in cp_cmds:
-      print(line)
-  else:
-    print("#   (none)")
-  print("#")
-
-  print("# Nodes NOT handled automatically (type mismatches / non-file/dir):")
-  if other_list:
-    for rel in other_list:
-      print(f"#   {rel}")
-  else:
-    print("#   (none)")
-
-  return 0
-
-
-# ----------------------------------------------------------------------
-# suspicious / addendum via in_between_and_below
-# ----------------------------------------------------------------------
-def cmd_suspicious(
-  A: TreeDict
-  ,B: TreeDict
-) -> int:
-  """
-  suspicious: nodes in B that fall 'in between' the Harmony skeleton,
-  not under leaf directories.
-  """
-  in_between, _below = skeleton.tree_dict_in_between_and_below(A, B)
-
-  _print_header("suspicious: nodes in-between Harmony leaves")
-
-  if not in_between:
-    print("No suspicious nodes found in B (relative to A).")
-    return 0
-
-  skeleton.tree_dict_print(in_between)
-  return 0
-
-
-def cmd_addendum(
-  A: TreeDict
-  ,B: TreeDict
-) -> int:
-  """
-  addendum: nodes in B that fall 'below' Harmony leaf directories.
-  """
-  _in_between, below = skeleton.tree_dict_in_between_and_below(A, B)
-
-  _print_header("addendum: nodes added under Harmony leaves")
-
-  if not below:
-    print("No addendum nodes found in B (relative to A).")
-    return 0
-
-  skeleton.tree_dict_print(below)
-  return 0
-
-
-# ----------------------------------------------------------------------
-# Top-level dispatcher
-# ----------------------------------------------------------------------
-def dispatch(
-  has_other_list: List[str]
-  ,A: TreeDict
-  ,B: TreeDict
-  ,A_root: str
-  ,B_root: str
-) -> int:
-  """
-  Dispatch <has_other> commands.
-
-  has_other_list:
-    List of command tokens (subset of:
-      'structure', 'import', 'export', 'suspicious', 'addendum', 'all').
-
-  A, B:
-    tree_dicts for Harmony skeleton (A) and <other> project (B).
-
-  A_root, B_root:
-    Root paths corresponding to A and B (for copy commands).
-  """
-  cmds = set(has_other_list)
-
-  if "all" in cmds:
-    cmds.update([
-      "structure"
-      ,"import"
-      ,"export"
-      ,"suspicious"
-      ,"addendum"
-    ])
-
-  ordered = [
-    "structure"
-    ,"import"
-    ,"export"
-    ,"suspicious"
-    ,"addendum"
-  ]
-
-  status = 0
-
-  for name in ordered:
-    if name not in cmds:
-      continue
-
-    if name == "structure":
-      rc = cmd_structure(A, B)
-    elif name == "import":
-      rc = cmd_import(A, B, A_root, B_root)
-    elif name == "export":
-      rc = cmd_export(A, B, A_root, B_root)
-    elif name == "suspicious":
-      rc = cmd_suspicious(A, B)
-    elif name == "addendum":
-      rc = cmd_addendum(A, B)
-    else:
-      rc = 0
-
-    if rc != 0:
-      status = rc
-
-  return status
diff --git a/shared/authored/sync/doc.py b/shared/authored/sync/doc.py
deleted file mode 100644 (file)
index 3198b96..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-doc.py - usage and help text for the Harmony 'check' tool
-
-Grammar (informal):
-
-  <prog> <command>* [<other>]
-
-  <command>   :: <help> | <no_other> | <has_other>
-
-  <help>      :: version | help | usage
-  <no_other>  :: environment
-  <has_other> :: structure | import | export | suspicious | addendum | all
-"""
-
-from __future__ import annotations
-
-import meta
-import os
-import sys
-from typing import TextIO
-
-
-def prog_name() -> str:
-  """
-  Return the program name as invoked by the user.
-
-  Typically:
-    - basename(sys.argv[0]) when running from the shell.
-    - Falls back to 'check' if argv[0] is empty.
-  """
-  raw = sys.argv[0] if sys.argv and sys.argv[0] else "check"
-  base = os.path.basename(raw) or raw
-  return base
-
-
-def _usage_text(prog: str) -> str:
-  return f"""\
-Usage:
-  {prog} <command>* [<other>]
-
-Where:
-  <command>   :: <help> | <no_other> | <has_other>
-
-  <help>      :: version | help | usage
-  <no_other>  :: environment
-  <has_other> :: structure | import | export | suspicious | addendum | all
-"""
-
-def _help_text(prog: str) -> str:
-  return f"""\
-{prog} - Harmony skeleton integrity and metadata checker
-
-Syntax:
-  {prog} <command>* [<other>]
-
-Where:
-  <other>   :: path
-  <command> :: <help> | <no_other> | <has_other>
-
-  <help>      :: version | help | usage
-  <no_other>  :: environment
-  <has_other> :: structure | import | export | suspicious | addendum | all
-
-Argument rules (informal):
-  1. <help> commands are processed first, and then the program returns.
-     Hence if any help commands are present, the remaining commands
-     are ignored.
-
-  2. We assume {prog} is run within the Harmony skeleton, or a skeleton
-     derived directly from it. This is the 'default skeleton', or simply 'A'.
-
-  3. The <other> path is the directory of a project that is assumed to
-     be built upon the default skeleton. This second project root is
-     called 'B'.
-
-  4. If none of the commands require an <other> path, then <other>
-     must not be given. If at least one command requires <other>, then
-     <other> is required. Commands that require a path are called
-     <has_other> commands.
-
-  5. Implementation detail:
-       All arguments except the final one are interpreted strictly as
-       command tokens. If any of those are <has_other>, the final argument
-       is taken as <other>. If none of the earlier tokens are <has_other>,
-       the final argument is also treated as a command token.
-
-Roots:
-  A = Skeleton project root (auto-detected). Usually the Harmony skeleton.
-  B = <other> project root (supplied when required).
-
-{prog} compares A with B. Differences may come from:
-  - edits to the skeleton itself,
-  - edits to skeleton files inside B,
-  - or new files/directories added to B.
-Conceptually, A and B are any two non-overlapping directory trees.
-
-Command semantics:
-  structure
-    - Report directory-structure differences:
-        directories present in A that are missing in B or not
-        directories in B.
-    - Output: table of such directories.
-
-  import
-    - Update A from B using only "in-between newer" files:
-        * files in B that lie in the 'in-between' region relative to A, and
-        * are newer than A or absent from A.
-    - Also emits:
-        * directories to create in A,
-        * files to copy (B -> A),
-        * nodes that cannot be handled automatically (type mismatches,
-          constrained nodes, non-file/dir nodes).
-    - Direction: B -> A
-
-  export
-    - Update B from A:
-        * files in A newer than B at the same path,
-        * files present in A but missing in B.
-    - Also emits:
-        * directories to create in B,
-        * files to copy (A -> B),
-        * nodes that cannot be handled automatically.
-    - Direction: A -> B
-
-  suspicious
-    - Report B nodes that lie "in-between" Harmony leaves:
-        under a directory from A, but not under any leaf directory of A.
-    - Indicates questionable placements or missing skeleton structure.
-
-  addendum
-    - Report B nodes located "below" Harmony leaf directories:
-        project-specific additions placed in proper extension points.
-
-  all
-    - Run: structure, import, export, suspicious, addendum (in that order).
-
-Notes:
-  - tree_dict traversal respects a simplified .gitignore model plus
-    always-ignored patterns (e.g. '.git').
-  - Timestamps are formatted via the Z helper in UTC (ISO 8601).
-"""
-
-def print_usage(
-  stream: TextIO | None = None
-) -> None:
-  """
-  Print the usage text to the given stream (default: sys.stdout),
-  using the actual program name as invoked.
-  """
-  if stream is None:
-    stream = sys.stdout
-
-  text = _usage_text(prog_name())
-  stream.write(text)
-  if not text.endswith("\n"):
-    stream.write("\n")
-
-
-def print_help(
-  stream: TextIO | None = None
-) -> None:
-  """
-  Print the help text to the given stream (default: sys.stdout),
-  using the actual program name as invoked.
-  """
-  if stream is None:
-    stream = sys.stdout
-
-  utext = _usage_text(prog_name())
-  htext = _help_text(prog_name())
-
-  stream.write(utext)
-  if not utext.endswith("\n"):
-    stream.write("\n")
-
-  stream.write("\n")
-  stream.write(htext)
-  if not htext.endswith("\n"):
-    stream.write("\n")
diff --git a/shared/authored/sync/in_between_and_below b/shared/authored/sync/in_between_and_below
deleted file mode 100755 (executable)
index 2993767..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-in_between_and_below - CLI test driver for skeleton.tree_dict_in_between_and_below(A, B)
-
-Usage:
-  in_between_and_below <A_root> <B_root>
-"""
-
-from __future__ import annotations
-
-import os
-import sys
-from typing import Sequence
-
-import meta
-import skeleton
-
-
-def CLI(argv: Sequence[str] | None = None) -> int:
-  if argv is None:
-    argv = sys.argv[1:]
-
-  prog = os.path.basename(sys.argv[0]) if sys.argv else "in_between_and_below"
-
-  if len(argv) != 2 or argv[0] in ("-h", "--help"):
-    print(f"Usage: {prog} <A_root> <B_root>")
-    return 1
-
-  A_root = argv[0]
-  B_root = argv[1]
-
-  if not os.path.isdir(A_root):
-    print(f"{prog}: {A_root}: not a directory")
-    return 2
-
-  if not os.path.isdir(B_root):
-    print(f"{prog}: {B_root}: not a directory")
-    return 3
-
-  A = skeleton.tree_dict_make(A_root, None)
-  B = skeleton.tree_dict_make(B_root, None)
-
-  meta.debug_set("tree_dict_in_between_and_below")
-
-  _result = skeleton.tree_dict_in_between_and_below(A, B)
-
-  return 0
-
-
-if __name__ == "__main__":
-  raise SystemExit(CLI())
diff --git a/shared/authored/sync/load_command_module.py b/shared/authored/sync/load_command_module.py
deleted file mode 100644 (file)
index 226b6dd..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-load_command_module.py - locate and import Python command modules from $PATH
-
-Behavior:
-  1. Search $PATH for an executable with the given command name.
-  2. Prefer a path containing '/incommon/'.
-  3. If only /usr/bin/<command> is found, raise an error saying we were
-     looking for the incommon version.
-  4. Import the chosen script as a Python module, even if it has no .py
-     extension, by forcing a SourceFileLoader.
-"""
-
-from __future__ import annotations
-
-import importlib.util
-import os
-from importlib.machinery import SourceFileLoader
-from types import ModuleType
-from typing import List
-
-
-def _find_command_candidates(command_name: str) -> List[str]:
-  """
-  Return a list of absolute paths to executables named `command_name`
-  found on $PATH.
-  """
-  paths: list[str] = []
-
-  path_env = os.environ.get("PATH", "")
-  for dir_path in path_env.split(os.pathsep):
-    if not dir_path:
-      continue
-    candidate = os.path.join(dir_path, command_name)
-    if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
-      paths.append(os.path.realpath(candidate))
-
-  return paths
-
-
-def load_command_module(command_name: str) -> ModuleType:
-  """
-  Locate an executable named `command_name` on $PATH and load it
-  as a Python module.
-
-  Selection policy:
-    1. Prefer any path containing '/incommon/'.
-    2. If only /usr/bin/<command_name> candidates exist, raise an error
-       saying we were looking for the incommon version.
-    3. If no candidate is found, raise an error.
-
-  Implementation detail:
-    Because the incommon command may lack a .py suffix, we explicitly
-    construct a SourceFileLoader rather than relying on the default
-    extension-based loader resolution.
-  """
-  candidates = _find_command_candidates(command_name)
-
-  incommon_candidates = [
-    p
-    for p in candidates
-    if "/incommon/" in p
-  ]
-
-  usrbin_candidates = [
-    p
-    for p in candidates
-    if p.startswith("/usr/bin/")
-  ]
-
-  if incommon_candidates:
-    target = incommon_candidates[0]
-  elif usrbin_candidates:
-    raise RuntimeError(
-      f"Found /usr/bin/{command_name}, but expected the incommon Python "
-      f"{command_name} module on PATH."
-    )
-  else:
-    raise RuntimeError(
-      f"Could not find an incommon '{command_name}' module on PATH."
-    )
-
-  module_name = f"rt_incommon_{command_name}"
-
-  loader = SourceFileLoader(
-    module_name
-    ,target
-  )
-  spec = importlib.util.spec_from_loader(
-    module_name
-    ,loader
-  )
-  if spec is None:
-    raise RuntimeError(f"Failed to create spec for {command_name} from {target}")
-
-  module = importlib.util.module_from_spec(spec)
-  # spec.loader is the SourceFileLoader we just created
-  assert spec.loader is not None
-  spec.loader.exec_module(module)
-
-  return module
diff --git a/shared/authored/sync/make_Harmony_tree_dict b/shared/authored/sync/make_Harmony_tree_dict
deleted file mode 100755 (executable)
index 2ed3cea..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-skeleton_test - build and print the Harmony tree_dict
-
-Usage:
-  skeleton_test
-
-Behavior:
-  1. Locate the Harmony project root via Harmony.where().
-  2. Enable 'tree_dict_print' debug flag.
-  3. Call skeleton.tree_dict_make(Harmony_root, None).
-
-The skeleton.tree_dict_make() function is expected to call
-tree_dict_print() when the 'tree_dict_print' debug flag is set.
-"""
-
-from __future__ import annotations
-
-import sys
-
-import Harmony
-import meta
-import skeleton
-
-
-def CLI(argv=None) -> int:
-  # No arguments expected
-  status, Harmony_root = Harmony.where()
-
-  if status == "not-found":
-    print("Harmony project not found; cannot build tree_dict.")
-    return 1
-
-  if status == "different":
-    print("Warning: Harmony not found, using nearest .git directory for tree_dict.")
-
-  # Enable printing inside tree_dict_make
-  meta.debug_set("tree_dict_print")
-
-  _tree = skeleton.tree_dict_make(Harmony_root, None)
-
-  return 0
-
-
-if __name__ == "__main__":
-  raise SystemExit(CLI())
diff --git a/shared/authored/sync/manus_2025_11_20_215471873762383.txt b/shared/authored/sync/manus_2025_11_20_215471873762383.txt
deleted file mode 100644 (file)
index 70e1f99..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-Conversation with Manus\r
-Started on November 20, 2025 at 08:56 PM Singapore time +08 (GMT+0800)\r
-\r
----\r
-\r
-08:56 PM | Thomas Lynch: Hello, I signed up today with 7 days free. However, in the middle of a chat, I was prompted to sign up, which I already did. It then charged me for the month, but we have not been 7 days yet, and it is full charge. Why did it do that? 
- As a second question is the 'credits'. Did it prompt again because the credits were used? Am I now on the hook for two months of charges in less than a day of use? 
- If just a few hours use uses up a 'month' of credits, then I really can't afford Manus, and need to back out of this.\r
-\r
-08:56 PM | Manus AI: I understand your concern about the unexpected charge! Let me clarify what likely happened. 
- About the charge: Our subscriptions operate on a credit-based system (http://help.manus.im/en/articles/11711097-what-are-the-rules-for-credits-consumption-and-how-can-i-obtain-them) where credits are consumed during active task processing. When you run out of credits (even during a trial), you may be prompted to upgrade to continue using the service, which would trigger billing. 
- You're not charged twice: The billing is monthly from your subscription date (http://help.manus.im/en/articles/11711089-where-can-i-find-the-invoice-after-purchase-can-the-invoice-be-modified), so you'll only have one active subscription. 
- To check your usage: Visit Settings > Usage to see exactly how your credits were consumed and review transaction details. 
- Your options: 
- - Cancel subscription: (http://help.manus.im/en/articles/11711087-how-can-i-cancel-my-subscription) Go to your profile > Account > Manage subscription > Cancel subscription to stop future charges. 
-- Request refund: You can submit a refund request with your invoice information and reason. We'll review it according to our refund policy. 
- The good news is you can cancel anytime to prevent future charges, and we do consider refund requests on a case-by-case basis! 
-Sources:
-What are the rules for credits consumption? And how can I obtain them? (http://help.manus.im/en/articles/11711097-what-are-the-rules-for-credits-consumption-and-how-can-i-obtain-them)
-Where can I check my credit consumption/usage? (http://help.manus.im/en/articles/12520235-where-can-i-check-my-credit-consumption-usage)
-How can I request a refund for a purchased membership? (http://help.manus.im/en/articles/11711102-how-can-i-request-a-refund-for-a-purchased-membership)
-How can I cancel my subscription? (http://help.manus.im/en/articles/11711087-how-can-i-cancel-my-subscription)
-Where can I find the invoice after purchase, can the invoice be modified? (http://help.manus.im/en/articles/11711089-where-can-i-find-the-invoice-after-purchase-can-the-invoice-be-modified)\r
-\r
-08:57 PM | Liora from Manus: We will respond to you with the resolution within 48 hours. Please wait for our update!\r
-\r
-09:01 PM | Thomas Lynch: You charged my $46.80, the plan says it costs $33.00 explain.\r
-\r
-09:02 PM | Thomas Lynch: hello?\r
-\r
----\r
-Exported from Manus on November 20, 2025 at 09:03 PM Singapore time +08 (GMT+0800)
\ No newline at end of file
diff --git a/shared/authored/sync/meta.py b/shared/authored/sync/meta.py
deleted file mode 100644 (file)
index 5c8da89..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-meta.py - thin wrappers around command modules
-
-Current responsibilities:
-  1. Load the incommon 'printenv' command module (no .py extension)
-     using load_command_module.load_command_module().
-  2. Expose printenv() here, calling the imported printenv() work
-     function with default arguments (equivalent to running without
-     any CLI arguments).
-  3. Provide a simple version printer for this meta module.
-  4. Provide a small debug tag API (set/clear/has).
-"""
-
-from __future__ import annotations
-
-import datetime
-from load_command_module import load_command_module
-
-
-# Load the incommon printenv module once at import time
-_PRINTENV_MODULE = load_command_module("printenv")
-_Z_MODULE = load_command_module("Z")
-
-
-# Meta module version
-_major = 1
-_minor = 7
-def version_print() -> None:
-  """
-  Print the meta module version as MAJOR.MINOR.
-  """
-  print(f"{_major}.{_minor}")
-
-
-# Debug tag set and helpers
-_debug = set([
-])
-
-
-def debug_set(tag: str) -> None:
-  """
-  Add a debug tag to the meta debug set.
-  """
-  _debug.add(tag)
-
-
-def debug_clear(tag: str) -> None:
-  """
-  Remove a debug tag from the meta debug set, if present.
-  """
-  _debug.discard(tag)
-
-
-def debug_has(tag: str) -> bool:
-  """
-  Return True if the given debug tag is present.
-  """
-  return tag in _debug
-
-
-# Touch the default tag once so static checkers do not complain about
-# unused helpers when imported purely for side-effects.
-debug_has("Command")
-
-
-def printenv() -> int:
-  """
-  Call the imported printenv() work function with default arguments:
-    - no null termination
-    - no newline quoting
-    - no specific names (print full environment)
-    - prog name 'printenv'
-  """
-  return _PRINTENV_MODULE.printenv(
-    False      # null_terminate
-    ,False     # quote_newlines
-    ,[]        # names
-    ,"printenv"
-  )
-
-
-def z_format_mtime(
-  mtime: float
-) -> str:
-  """
-  Format a POSIX mtime (seconds since epoch, UTC) using the Z module.
-
-  Uses Z.ISO8601_FORMAT and Z.make_timestamp(dt=...).
-  """
-  dt = datetime.datetime.fromtimestamp(mtime, datetime.timezone.utc)
-  return _Z_MODULE.make_timestamp(
-    fmt=_Z_MODULE.ISO8601_FORMAT
-    ,dt=dt
-  )
diff --git a/shared/authored/sync/newer b/shared/authored/sync/newer
deleted file mode 100755 (executable)
index 30aa373..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-newer - CLI test driver for skeleton.tree_dict_newer(A, B)
-
-Usage:
-  newer <A_root> <B_root>
-"""
-
-from __future__ import annotations
-
-import os
-import sys
-from typing import Sequence
-
-import meta
-import skeleton
-
-
-def CLI(argv: Sequence[str] | None = None) -> int:
-  if argv is None:
-    argv = sys.argv[1:]
-
-  prog = os.path.basename(sys.argv[0]) if sys.argv else "newer"
-
-  if len(argv) != 2 or argv[0] in ("-h", "--help"):
-    print(f"Usage: {prog} <A_root> <B_root>")
-    return 1
-
-  A_root = argv[0]
-  B_root = argv[1]
-
-  if not os.path.isdir(A_root):
-    print(f"{prog}: {A_root}: not a directory")
-    return 2
-
-  if not os.path.isdir(B_root):
-    print(f"{prog}: {B_root}: not a directory")
-    return 3
-
-  A = skeleton.tree_dict_make(A_root, None)
-  B = skeleton.tree_dict_make(B_root, None)
-
-  meta.debug_set("tree_dict_newer")
-
-  _result = skeleton.tree_dict_newer(A, B)
-
-  return 0
-
-
-if __name__ == "__main__":
-  raise SystemExit(CLI())
diff --git a/shared/authored/sync/older b/shared/authored/sync/older
deleted file mode 100755 (executable)
index f8ff24d..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-older - CLI test driver for skeleton.tree_dict_older(A, B)
-
-Usage:
-  older <A_root> <B_root>
-"""
-
-from __future__ import annotations
-
-import os
-import sys
-from typing import Sequence
-
-import meta
-import skeleton
-
-
-def CLI(argv: Sequence[str] | None = None) -> int:
-  if argv is None:
-    argv = sys.argv[1:]
-
-  prog = os.path.basename(sys.argv[0]) if sys.argv else "older"
-
-  if len(argv) != 2 or argv[0] in ("-h", "--help"):
-    print(f"Usage: {prog} <A_root> <B_root>")
-    return 1
-
-  A_root = argv[0]
-  B_root = argv[1]
-
-  if not os.path.isdir(A_root):
-    print(f"{prog}: {A_root}: not a directory")
-    return 2
-
-  if not os.path.isdir(B_root):
-    print(f"{prog}: {B_root}: not a directory")
-    return 3
-
-  A = skeleton.tree_dict_make(A_root, None)
-  B = skeleton.tree_dict_make(B_root, None)
-
-  meta.debug_set("tree_dict_older")
-
-  _result = skeleton.tree_dict_older(A, B)
-
-  return 0
-
-
-if __name__ == "__main__":
-  raise SystemExit(CLI())
diff --git a/shared/authored/sync/skeleton.py b/shared/authored/sync/skeleton.py
deleted file mode 100644 (file)
index ae272f9..0000000
+++ /dev/null
@@ -1,576 +0,0 @@
-#!/usr/bin/env python3
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-skeleton.py - helpers for working with the Harmony skeleton tree
-"""
-
-from __future__ import annotations
-
-import os
-import sys
-from typing import Any, Callable, Dict, List, Set
-
-import meta
-from GitIgnore import GitIgnore
-import Harmony
-
-TreeDict = Dict[str, Dict[str, Any]]
-
-# tree_dict_make / tree_dict_print
-#
-# Build a dictionary describing a project tree, respecting GitIgnore.
-#
-# tree_dict_make(<path>, <checksum_fn>) -> tree_dict
-#
-#   <checksum_fn>(<abs_path>) -> bignum | None
-#
-#   Keys of tree_dict:
-#     - Relative paths from <path>; the root itself is stored under "".
-#
-#   Values are dicts with:
-#     1. 'mtime'     : last modification time (float seconds) or None
-#     2. 'node_type' : 'file', 'directory', 'other', or 'constrained'
-#     3. 'dir_info'  : 'NA', 'leaf', 'branch', 'root'
-#     4. 'checksum'  : present only for file nodes when checksum_fn is
-#                      not None
-#
-#   Traversal:
-#     - Directories whose relative path GitIgnore.check() marks as
-#       'Ignore' are included in tree_dict but not traversed further.
-def tree_dict_make(
-  path: str
-  ,checksum_fn: Callable[[str], int] | None
-) -> Dict[str, Dict[str, Any]]:
-  """
-  Build a tree_dict for the subtree rooted at <path>, respecting GitIgnore.
-
-  Semantics (current):
-    * Any path (directory or file) for which GitIgnore.check(<rel_path>)
-      returns 'Ignore' is completely omitted from the tree_dict.
-    * The root directory ('') is always included.
-    * Directory dir_info:
-        - 'root'   for the root
-        - 'branch' for directories that have child directories
-                    (after GitIgnore filtering)
-        - 'leaf'   for directories with no child directories
-    * Non-directory dir_info:
-        - 'NA'
-    * Symlinks are classified as file/directory/other based on what
-      they point to, if accessible.
-    * If any filesystem access needed for classification/mtime raises,
-      the node is recorded as node_type='constrained', dir_info='NA',
-      mtime=None, and we do not attempt checksum.
-  """
-  root = os.path.abspath(path)
-  gi = GitIgnore(root)
-
-  tree_dict: Dict[str, Dict[str, Any]] = {}
-
-  for dirpath, dirnames, filenames in os.walk(root, topdown=True):
-    rel_dir = os.path.relpath(dirpath, root)
-    if rel_dir == ".":
-      rel_dir = ""
-
-    # Skip ignored directories (except the root).
-    if rel_dir != "" and gi.check(rel_dir) == "Ignore":
-      dirnames[:] = []
-      continue
-
-    # Filter child directories by GitIgnore so dir_info reflects
-    # only directories we will actually traverse.
-    kept_dirnames: List[str] = []
-    for dn in list(dirnames):
-      child_rel = dn if rel_dir == "" else os.path.join(rel_dir, dn)
-      if gi.check(child_rel) == "Ignore":
-        dirnames.remove(dn)
-      else:
-        kept_dirnames.append(dn)
-
-    # Record the directory node itself
-    dir_abs = dirpath
-    try:
-      dir_mtime = os.path.getmtime(dir_abs)
-      dir_node_type = "directory"
-      if rel_dir == "":
-        dir_info = "root"
-      elif kept_dirnames:
-        dir_info = "branch"
-      else:
-        dir_info = "leaf"
-    except OSError:
-      # Could not stat the directory: treat as constrained.
-      dir_mtime = None
-      dir_node_type = "constrained"
-      dir_info = "NA"
-
-    tree_dict[rel_dir] = {
-      "mtime": dir_mtime
-      ,"node_type": dir_node_type
-      ,"dir_info": dir_info
-    }
-
-    # For non-ignored directories, record files within
-    for name in filenames:
-      abs_path = os.path.join(dirpath, name)
-      if rel_dir == "":
-        rel_path = name
-      else:
-        rel_path = os.path.join(rel_dir, name)
-
-      if gi.check(rel_path) == "Ignore":
-        continue
-
-      # Wrap classification + mtime in one try/except so any failure
-      # marks the node as constrained.
-      try:
-        if os.path.islink(abs_path):
-          # Symlink: classify by target if possible
-          if os.path.isdir(abs_path):
-            node_type = "directory"
-            dir_info_f = "branch"
-          elif os.path.isfile(abs_path):
-            node_type = "file"
-            dir_info_f = "NA"
-          else:
-            node_type = "other"
-            dir_info_f = "NA"
-          mtime = os.path.getmtime(abs_path)
-        else:
-          # Normal node
-          if os.path.isfile(abs_path):
-            node_type = "file"
-            dir_info_f = "NA"
-          elif os.path.isdir(abs_path):
-            node_type = "directory"
-            dir_info_f = "branch"
-          else:
-            node_type = "other"
-            dir_info_f = "NA"
-          mtime = os.path.getmtime(abs_path)
-      except OSError:
-        # Anything that blows up during classification/stat becomes
-        # constrained; we do not attempt checksum for these.
-        node_type = "constrained"
-        dir_info_f = "NA"
-        mtime = None
-
-      info: Dict[str, Any] = {
-        "mtime": mtime
-        ,"node_type": node_type
-        ,"dir_info": dir_info_f
-      }
-
-      if node_type == "file" and checksum_fn is not None and isinstance(mtime, (int, float)):
-        info["checksum"] = checksum_fn(abs_path)
-
-      tree_dict[rel_path] = info
-
-  if meta.debug_has("tree_dict_print"):
-    tree_dict_print(tree_dict)
-
-  return tree_dict
-
-def tree_dict_print(
-  tree_dict: Dict[str, Dict[str, Any]]
-) -> None:
-  """
-  Pretty-print a tree_dict produced by tree_dict_make() in fixed-width columns:
-
-    [type]  [dir]  [mtime]  [checksum?]  [relative path]
-
-  Only the values are printed in each column (no 'field=' prefixes).
-  mtime is formatted via the Z module for human readability.
-  """
-  entries: List[tuple[str, str, str, str, str]] = []
-  has_checksum = False
-
-  for rel_path in sorted(tree_dict.keys()):
-    info = tree_dict[rel_path]
-    display_path = rel_path if rel_path != "" else "."
-
-    type_val = str(info.get("node_type", ""))
-    dir_val = str(info.get("dir_info", ""))
-
-    raw_mtime = info.get("mtime")
-    if isinstance(raw_mtime, (int, float)):
-      mtime_val = meta.z_format_mtime(raw_mtime)
-    else:
-      mtime_val = str(raw_mtime)
-
-    if "checksum" in info:
-      checksum_val = str(info["checksum"])
-      has_checksum = True
-    else:
-      checksum_val = ""
-
-    entries.append((
-      type_val
-      ,dir_val
-      ,mtime_val
-      ,checksum_val
-      ,display_path
-    ))
-
-  # Compute column widths
-  type_w = 0
-  dir_w = 0
-  mtime_w = 0
-  checksum_w = 0
-
-  for type_val, dir_val, mtime_val, checksum_val, _ in entries:
-    if len(type_val) > type_w:
-      type_w = len(type_val)
-    if len(dir_val) > dir_w:
-      dir_w = len(dir_val)
-    if len(mtime_val) > mtime_w:
-      mtime_w = len(mtime_val)
-    if has_checksum and len(checksum_val) > checksum_w:
-      checksum_w = len(checksum_val)
-
-  print("Tree dictionary contents:")
-  for type_val, dir_val, mtime_val, checksum_val, display_path in entries:
-    line = "  "
-    line += type_val.ljust(type_w)
-    line += "  "
-    line += dir_val.ljust(dir_w)
-    line += "  "
-    line += mtime_val.ljust(mtime_w)
-
-    if has_checksum:
-      line += "  "
-      line += checksum_val.ljust(checksum_w)
-
-    line += "  "
-    line += display_path
-
-    print(line)
-
-
-def tree_dict_A_minus_B(
-  A: Dict[str, Dict[str, Any]]
-  ,B: Dict[str, Dict[str, Any]]
-) -> Dict[str, Dict[str, Any]]:
-  """
-  Compute the set difference of two tree_dicts at the key level:
-
-    Result = A \\ B
-
-  That is, return a new tree_dict containing only those entries whose
-  keys are present in A but NOT present in B.
-  """
-  result: Dict[str, Dict[str, Any]] = {}
-
-  B_keys = set(B.keys())
-
-  for key, info in A.items():
-    if key not in B_keys:
-      result[key] = info
-
-  if meta.debug_has("tree_dict_A_minus_B"):
-    tree_dict_print(result)
-
-  return result
-
-
-def tree_dict_in_between_and_below(
-  A: Dict[str, Dict[str, Any]]
-  ,B: Dict[str, Dict[str, Any]]
-) -> tuple[Dict[str, Dict[str, Any]], Dict[str, Dict[str, Any]]]:
-  """
-  Partition nodes of B into two topology-based sets relative to A:
-
-    1. in_between:
-         Nodes in B that lie under at least one directory node in A,
-         but do NOT lie under any leaf directory of A.
-
-    2. below:
-         Nodes in B that lie under at least one leaf directory of A.
-
-  Definitions (relative to A's directory topology):
-
-    - A directory node in A is any key whose info['node_type'] == 'directory'.
-
-    - A leaf directory in A is a directory that has no *other* directory
-      in A as a proper descendant. The project root ('') is therefore
-      never a leaf (it always has descendant directories if the tree is
-      non-trivial).
-
-    - “Lies under”:
-        * For a path p in B, we look at the chain of directory ancestors
-          (including the root "") and, if p itself is a directory, p
-          itself. Any of those that appear as directory keys in A are
-          considered directory ancestors in A.
-
-        * If any of those ancestors is a leaf in A, p goes to 'below'.
-          Otherwise, if there is at least one directory ancestor in A,
-          p goes to 'in_between'.
-
-    - Nodes in B that do not lie under any directory in A are ignored.
-
-  Returns:
-    (in_between_dict, below_dict), both keyed like B and containing
-    copies of the info dicts from B.
-  """
-  # 1. Collect all directory keys from A
-  A_dir_keys: Set[str] = set(
-    key for key, info in A.items()
-    if info.get("node_type") == "directory"
-  )
-
-  # 2. Compute leaf directories in A
-  leaf_dirs: Set[str] = set()
-
-  for d in A_dir_keys:
-    if d == "":
-      continue
-
-    has_child_dir = False
-    prefix = d + os.sep
-
-    for other in A_dir_keys:
-      if other == d:
-        continue
-      if other.startswith(prefix):
-        has_child_dir = True
-        break
-
-    if not has_child_dir:
-      leaf_dirs.add(d)
-
-  in_between: Dict[str, Dict[str, Any]] = {}
-  below: Dict[str, Dict[str, Any]] = {}
-
-  for key, info in B.items():
-    # Skip B's root
-    if key in ("", "."):
-      continue
-
-    parts = key.split(os.sep)
-
-    # Build directory ancestor chain
-    node_is_dir = (info.get("node_type") == "directory")
-
-    ancestors: List[str] = [""]
-    prefix = None
-
-    if node_is_dir:
-      upto = parts
-    else:
-      upto = parts[:-1]
-
-    for part in upto:
-      if prefix is None:
-        prefix = part
-      else:
-        prefix = os.path.join(prefix, part)
-      ancestors.append(prefix)
-
-    # Filter ancestors to those that exist as directories in A
-    ancestors_in_A = [d for d in ancestors if d in A_dir_keys]
-
-    if not ancestors_in_A:
-      # This B node is not under any directory from A; ignore it.
-      continue
-
-    # Any leaf ancestor in A?
-    has_leaf_ancestor = any(d in leaf_dirs for d in ancestors_in_A)
-
-    if has_leaf_ancestor:
-      below[key] = info
-    else:
-      in_between[key] = info
-
-  if meta.debug_has("tree_dict_in_between_and_below"):
-    merged: Dict[str, Dict[str, Any]] = {}
-    merged.update(in_between)
-    merged.update(below)
-    tree_dict_print(merged)
-
-  return in_between, below
-
-
-def tree_dict_newer(
-  A: Dict[str, Dict[str, Any]]
-  ,B: Dict[str, Dict[str, Any]]
-) -> Dict[str, Dict[str, Any]]:
-  """
-  Return a dictionary of nodes from B that are newer than their
-  corresponding nodes in A.
-
-  For each key k:
-
-    - If k exists in both A and B, and
-    - B[k]['mtime'] > A[k]['mtime'],
-
-  then k is included in the result with value B[k].
-
-  Keys that are only in B (not in A) are ignored here.
-  """
-  result: Dict[str, Dict[str, Any]] = {}
-
-  for key, info_B in B.items():
-    info_A = A.get(key)
-    if info_A is None:
-      continue
-
-    mtime_A = info_A.get("mtime")
-    mtime_B = info_B.get("mtime")
-
-    if mtime_A is None or mtime_B is None:
-      continue
-
-    if mtime_B > mtime_A:
-      result[key] = info_B
-
-  if meta.debug_has("tree_dict_newer"):
-    tree_dict_print(result)
-
-  return result
-
-
-def tree_dict_older(
-  A: Dict[str, Dict[str, Dict[str, Any]]]
-  ,B: Dict[str, Dict[str, Dict[str, Any]]]
-) -> Dict[str, Dict[str, Any]]:
-  """
-  Return a dictionary of nodes from B that are older than their
-  corresponding nodes in A.
-
-  For each key k:
-
-    - If k exists in both A and B, and
-    - B[k]['mtime'] < A[k]['mtime'],
-
-  then k is included in the result with value B[k].
-
-  Keys that are only in B (not in A) are ignored here.
-  """
-  result: Dict[str, Dict[str, Any]] = {}
-
-  for key, info_B in B.items():
-    info_A = A.get(key)
-    if info_A is None:
-      continue
-
-    mtime_A = info_A.get("mtime")
-    mtime_B = info_B.get("mtime")
-
-    if mtime_A is None or mtime_B is None:
-      continue
-
-    if mtime_B < mtime_A:
-      result[key] = info_B
-
-  if meta.debug_has("tree_dict_older"):
-    tree_dict_print(result)
-
-  return result
-
-def in_between_newer(
-  A: TreeDict
-  ,B: TreeDict
-) -> TreeDict:
-  """
-  in_between_newer(A, B) -> TreeDict
-
-  Return the subset of B's nodes that:
-
-    1. Are in the 'in_between' region with respect to A's topology:
-         - under some directory that exists in A
-         - NOT under any leaf directory in A
-       (as defined by tree_dict_in_between_and_below), and
-
-    2. For file nodes:
-         - are "newer" than A at the same path, or
-         - are absent from A.
-
-       More precisely:
-         - If A has no entry for that path -> include.
-         - If A has a non-file and B has a file -> include.
-         - If both are files and B.mtime > A.mtime -> include.
-
-    3. For constrained nodes:
-         - are always included, so that higher-level commands (e.g.
-           'import') can surface them as "not handled automatically".
-
-  Notes:
-    - Only file nodes participate in mtime comparisons.
-    - Nodes with node_type == 'constrained' are passed through without
-      mtime checks, so that callers can report them separately.
-  """
-  in_between, _below = tree_dict_in_between_and_below(A, B)
-
-  result: TreeDict = {}
-
-  # Keep track of directories already included in the result
-  included_dirs: Set[str] = set()
-
-  # Sort keys to ensure parent directories are processed before their children.
-  # This is crucial for the child exclusion logic to work correctly.
-  sorted_paths = sorted(in_between.keys(), key=len)
-
-  for path in sorted_paths:
-    b_info = in_between[path]
-    b_type = b_info.get("node_type")
-
-    # Constrained nodes: always surface so the caller can list them
-    # under "not handled automatically".
-    # Check if this path is a child of an already included directory
-    is_child_of_included_dir = False
-    for d in included_dirs:
-      if path.startswith(d + os.sep):
-        is_child_of_included_dir = True
-        break
-    
-    if is_child_of_included_dir:
-      continue
-
-    # Constrained nodes: always surface so the caller can list them
-    # under "not handled automatically".
-    if b_type == "constrained":
-      result[path] = b_info
-      if b_type == "directory":
-        included_dirs.add(path)
-      continue
-
-
-
-    b_mtime = b_info.get("mtime")
-    a_info = A.get(path)
-
-    # Case 1: path not in A at all -> include (new file/dir in in-between)
-    if a_info is None:
-      result[path] = b_info
-      if b_type == "directory":
-        included_dirs.add(path)
-      continue
-
-    # We only do "newer" semantics for regular files.
-    if b_type != "file":
-      continue
-
-    a_type = a_info.get("node_type")
-
-    # Case 2: A has non-file, B has file -> include
-    if a_type != "file":
-      result[path] = b_info
-      # Note: b_type must be "file" here due to the check above, so no need
-      # to check for directory inclusion.
-      continue
-
-    # Case 3: both are files; compare mtime
-    a_mtime = a_info.get("mtime")
-    if (
-      isinstance(a_mtime, (int, float))
-      and isinstance(b_mtime, (int, float))
-      and b_mtime > a_mtime
-    ):
-      result[path] = b_info
-      # Note: b_type must be "file" here, so no need to check for directory inclusion.
-
-  if meta.debug_has("in_between_newer"):
-    tree_dict_print(result)
-
-  return result
-
diff --git a/shared/authored/sys b/shared/authored/sys
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/shared/authored_10.zip b/shared/authored_10.zip
new file mode 100644 (file)
index 0000000..2326f1f
Binary files /dev/null and b/shared/authored_10.zip differ
diff --git a/shared/made/walk b/shared/made/walk
new file mode 120000 (symlink)
index 0000000..1325c19
--- /dev/null
@@ -0,0 +1 @@
+../authored/gitignore_treewalk.py
\ No newline at end of file
diff --git a/temp.sh b/temp.sh
new file mode 100644 (file)
index 0000000..c166d29
--- /dev/null
+++ b/temp.sh
@@ -0,0 +1,11 @@
+2025-11-25 09:33:05 Z [subu:developer] Thomas_developer@StanleyPark
+§/home/Thomas/subu_data/developer/subu_data/Harmony§
+> find . -type l -exec ls -l {} \;
+lrwxrwxrwx 1 Thomas_developer Thomas_developer 35 Nov 25 09:08 ./tool/sync -> ../tool_shared/authored/sync/CLI.py
+lrwxrwxrwx 1 Thomas_developer Thomas_developer 3 May 19  2025 ./shared/third_party/Python/lib64 -> lib
+lrwxrwxrwx 1 Thomas_developer Thomas_developer 16 May 19  2025 ./shared/third_party/Python/bin/python3 -> /usr/bin/python3
+lrwxrwxrwx 1 Thomas_developer Thomas_developer 7 May 19  2025 ./shared/third_party/Python/bin/python -> python3
+lrwxrwxrwx 1 Thomas_developer Thomas_developer 7 May 19  2025 ./shared/third_party/Python/bin/python3.11 -> python3
+lrwxrwxrwx 1 Thomas_developer Thomas_developer 15 Nov 24 15:19 ./shared/authored/git-empty-dir/source_sync -> ../source_sync/
+lrwxrwxrwx 1 Thomas_developer Thomas_developer 25 Nov 24 15:21 ./shared/authored/git-empty-dir/Harmony.py -> ../source_sync/Harmony.py
+lrwxrwxrwx 1 Thomas_developer Thomas_developer 37 Nov 24 15:22 ./shared/authored/git-empty-dir/load_command_module.py -> ../source_sync/load_command_module.py
diff --git a/tool/Harmony_sync b/tool/Harmony_sync
new file mode 120000 (symlink)
index 0000000..22ddb4e
--- /dev/null
@@ -0,0 +1 @@
+../shared/authored/Harmony_sync/CLI.py
\ No newline at end of file
diff --git a/tool/sync b/tool/sync
deleted file mode 120000 (symlink)
index e58d04e..0000000
--- a/tool/sync
+++ /dev/null
@@ -1 +0,0 @@
-../tool_shared/authored/sync/CLI.py
\ No newline at end of file