moving to authored/loadable ontology
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 21 Nov 2025 10:03:38 +0000 (10:03 +0000)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 21 Nov 2025 10:03:38 +0000 (10:03 +0000)
36 files changed:
0pus_Harmony_skeleton [deleted file]
developer/authored/.gitkeep [new file with mode: 0644]
developer/authored/hello.cli.c [new file with mode: 0644]
developer/cc/.githolder [deleted file]
developer/cc/hello.cli.c [deleted file]
developer/document/.githolder [deleted file]
developer/document/.gitkeep [new file with mode: 0644]
developer/document/ontology.org [new file with mode: 0644]
developer/document/python packages [deleted file]
developer/experiment/.githolder [deleted file]
developer/experiment/.gitkeep [new file with mode: 0644]
developer/python3/decompose.py [deleted file]
developer/python3/iroot.py [deleted file]
developer/python3/test_env.py [deleted file]
developer/python3/test_isqrt.py [deleted file]
developer/python3/test_reduce.py [deleted file]
developer/tool/.githolder [deleted file]
developer/tool/.gitkeep [new file with mode: 0644]
document/.githolder [deleted file]
document/.gitkeep [new file with mode: 0644]
release/authored/.gitkeep [new file with mode: 0644]
release/documnt/.gitkeep [new file with mode: 0644]
release/loadable/.gitkeep [new file with mode: 0644]
release/local_build/.gitignore [new file with mode: 0644]
release/python3/.githolder [deleted file]
release/shell/.githolder [deleted file]
tester/.githolder [deleted file]
tester/.gitkeep [new file with mode: 0644]
tool/source_skeleton_compare/command.py
tool/source_skeleton_compare/manus_2025_11_20_215471873762383.txt [new file with mode: 0644]
tool_shared/bespoke/.githolder [deleted file]
tool_shared/bespoke/.gitkeep [new file with mode: 0644]
tool_shared/document/.githolder [deleted file]
tool_shared/document/.gitkeep [new file with mode: 0644]
tool_shared/patch/.githolder [deleted file]
tool_shared/patch/.gitkeep [new file with mode: 0644]

diff --git a/0pus_Harmony_skeleton b/0pus_Harmony_skeleton
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/developer/authored/.gitkeep b/developer/authored/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/developer/authored/hello.cli.c b/developer/authored/hello.cli.c
new file mode 100644 (file)
index 0000000..a626cac
--- /dev/null
@@ -0,0 +1,2 @@
+#include <stdio.h>
+int main(void){ puts("hello from Rabbit CLI"); return 0; }
diff --git a/developer/cc/.githolder b/developer/cc/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/developer/cc/hello.cli.c b/developer/cc/hello.cli.c
deleted file mode 100644 (file)
index a626cac..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-#include <stdio.h>
-int main(void){ puts("hello from Rabbit CLI"); return 0; }
diff --git a/developer/document/.githolder b/developer/document/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/developer/document/.gitkeep b/developer/document/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/developer/document/ontology.org b/developer/document/ontology.org
new file mode 100644 (file)
index 0000000..f1aae70
--- /dev/null
@@ -0,0 +1,69 @@
+#+TITLE: Project Ontology: Authored vs. Loadable
+#+AUTHOR: Harmony Developer
+#+DATE: 2025-11-21
+#+OPTIONS: toc:t num:nil
+
+* The Core Philosophy
+This project moves away from "techno-idiosyncratic" directory names (like ~source~, ~bin~, or ~src~) in favor of names that describe the *fundamental invariants* of the files they contain.
+
+We distinguish files based on two properties:
+1.  **Provenance**: Who created this file?
+2.  **Capability**: What can the system do with this file?
+
+**The Golden Rule:**
+- **God, Artists, and SQL** (and Developers) *create* things.
+- **Factories** (Build Systems) *make* things.
+
+* Directory Structure Overview
+
+#+BEGIN_SRC text
+developer/
+├── authored/              # (The Truth) Human-written code.
+├── loadable/              # (The Entry Points) Script links & architecture-independent runners.
+├── scratchpad/
+│   └── loadable/          # (The Heavy Machinery) Compiled binaries. Ignored by Git.
+└── tool/                  # (The Factory) Build scripts and orchestrators.
+#+END_SRC
+
+* Detailed Definitions
+
+** ~developer/authored/~
+This directory contains the *primary truth* of the project.
+- **Invariant:** Every file here is written by a human author (the Artist).
+- **Rule:** Build scripts generally have *read-only* access to this directory. They should never modify files here.
+- **Contents:** C source files (~*.c~), Python modules (~*.py~), Header files (~*.h~).
+
+** ~developer/loadable/~
+This directory contains the *entry points* for the system.
+- **Invariant:** Files here possess the property of being "loadable" (runnable) by the user or the OS.
+- **Contents:**
+  - Symlinks to interpreted code (e.g., links to ~../authored/CLI.py~).
+  - Shell wrappers.
+  - Architecture-independent runners.
+
+** ~developer/scratchpad/loadable/~
+This directory contains the *machine-generated* executables.
+- **Invariant:** These files are derived/synthesized by the factory (Make).
+- **Rule:** This directory is **ignored by Git**. It prevents binary blobs and architecture-specific files from polluting the repository.
+- **Contents:** Compiled C binaries, object files, and temporary build artifacts.
+
+* Workflow Examples
+
+** Scenario A: Working with C (Compiled)
+1.  **Author:** You write ~my_program.c~ in ~developer/authored/~.
+2.  **Build:** You run ~tool/make~.
+3.  **Result:** The compiler reads ~authored/my_program.c~ and synthesizes a binary at ~developer/scratchpad/loadable/my_program~.
+   - *Note:* This binary is transient. If you delete ~scratchpad/~, it can be rebuilt.
+
+** Scenario B: Working with Python (Interpreted)
+1.  **Author:** You write ~my_script.CLI.py~ in ~developer/authored/~.
+2.  **Setup:** You (or a setup script) create a semantic link.
+3.  **Result:** A link exists at ~developer/loadable/my_script~ pointing to the authored file.
+   - *Note:* This makes the script "loadable" from the system path without moving the source of truth.
+
+* The Release Process
+When the project is ready for distribution, the release script collects items from both sources:
+- It takes the *logic* from ~authored~.
+- It takes the *binaries* from ~scratchpad/loadable~.
+- It places them into the final ~$REPO_HOME/release/~ sub directories for the end-user.
+It is common that the developer customizes the release script.  
diff --git a/developer/document/python packages b/developer/document/python packages
deleted file mode 100644 (file)
index 83a0dd0..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-
-pip install isqrt
diff --git a/developer/experiment/.githolder b/developer/experiment/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/developer/experiment/.gitkeep b/developer/experiment/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/developer/python3/decompose.py b/developer/python3/decompose.py
deleted file mode 100644 (file)
index f979189..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-import random
-from isqrt import isqrt
-
-def decompose(n0_h ,r1_p ,acc_car):
-
-  if r0_h < 0 : print "r0_h is negative"
-  if r1_p < 0 : print "r1_p is negative"
-  if acc_car != 0 and acc_car != 1 : print "acc_car is not 0 or 1"
-
-  k_n0_h = n0_h.bit_length()
-  k_r1_p = r1_p.bit_length()
-
-  if acc_car :
-    k_n1_p = k_
-
-  return z1 ,n1_p
-
-
-def main():
-  return 1
-
-if __name__ == "__main__":
-  main()
diff --git a/developer/python3/iroot.py b/developer/python3/iroot.py
deleted file mode 100644 (file)
index a6ae3e8..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-import random
-from isqrt import isqrt
-
-def iroot(z):
-  c = isqrt(z)
-  r = z - n0_h ** 2
-  return c ,r
-
-def main():
-  return 1
-
-if __name__ == "__main__":
-  main()
diff --git a/developer/python3/test_env.py b/developer/python3/test_env.py
deleted file mode 100644 (file)
index d47e8a1..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env python3
-
-import os
-import sys
-
-def print_env_var(name):
-    value = os.getenv(name)
-    print(f"{name:<16}: {value if value else '<not set>'}")
-
-def main():
-    print("=== Python Environment Test ===")
-    print(f"Python executable : {sys.executable}")
-    print(f"Python version    : {sys.version}")
-    print()
-
-    print("=== Harmony Environment Variables ===")
-    for var in ["ROLE", "REPO_HOME", "PYTHON_HOME", "VIRTUAL_ENV", "ENV"]:
-        print_env_var(var)
-
-    print()
-    print("=== Current Working Directory ===")
-    print(os.getcwd())
-
-if __name__ == "__main__":
-    main()
diff --git a/developer/python3/test_isqrt.py b/developer/python3/test_isqrt.py
deleted file mode 100644 (file)
index adac4ad..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-import random
-from isqrt import isqrt
-
-def test_isqrt(num_tests, max_bits):
-  failed = 0
-  passed = 0
-
-  for i in range(1, num_tests + 1):
-    bits = random.randint(1, max_bits)
-    n = random.getrandbits(bits)
-
-    root = isqrt(n)
-    lower = root * root
-    upper = (root + 1) * (root + 1)
-
-    if lower <= n < upper:
-      passed += 1
-    else:
-      print(f"\n❌ Test failed:")
-      print(f"bits   = {bits}")
-      print(f"n      = {hex(n)}")
-      print(f"root   = {hex(root)}")
-      if lower > n:
-        print("Reason = root is too large")
-      elif upper <= n:
-        print("Reason = root + 1 is too small")
-      failed += 1
-
-    if i % 1000 == 0:
-      print(".", end="", flush=True)
-
-  if num_tests >= 10:
-    print()
-
-  print(f"✅ Passed: {passed}")
-  print(f"❌ Failed: {failed}")
-
-def print_root(n):
-  print_bin(isqrt(n))
-
-def reduce(n):
-  n  
-
-def main():
-  if len(sys.argv) != 3:
-    print(f"Usage: {sys.argv[0]} <n_tests> <max_bits>")
-    sys.exit(1)
-
-  try:
-    n = int(sys.argv[1], 0)
-    max = int(sys.argv[2], 0)
-  except ValueError:
-    print("Error: argument must be a valid integer.")
-    sys.exit(1)
-
-  if n < 0:
-    print("Error: number of tests must be non-negative.")
-    sys.exit(1)
-
-  if max < 4:
-    print("Error: max_bits must be at least 4.")
-    sys.exit(1)
-
-  test_isqrt(n, max)
-
-if __name__ == "__main__":
-  main()
diff --git a/developer/python3/test_reduce.py b/developer/python3/test_reduce.py
deleted file mode 100644 (file)
index 02cb53e..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-
-import sys
-import random
-from iroot import iroot
-
-def test_reduce(num_tests, max_bits):
-  failed = 0
-  passed = 0
-
-  hex_width = (max_bits + 3) // 4       # ceil(max_bits / 4) for hex digit count
-  hex_widtho2 = (max_bits + 7) // 8     # ceil(max_bits / 8) for reduced-width fields
-
-  field = f"#0{hex_width + 2}x"
-  fieldo2 = f"#0{hex_widtho2 + 2}x"
-
-  header = (
-    f"{'z':>{hex_width + 2}}  "
-    f"{'n0h':>{hex_widtho2 + 2}}  "
-    f"{'r_h':>{hex_widtho2 + 2}}  "
-    f"{'r_hp':>{hex_widtho2 + 2}}"
-  )
-  print(header)
-  print("-" * len(header))
-
-  for i in range(1, num_tests + 1):
-    bits = random.randint(1, max_bits)
-    z = random.getrandbits(bits)
-
-    n0h, r_h, r_hp = iroot(z)
-
-    # Format fields, fallback to full-width hex if overflow detected
-    z_str     = format(z, field)     if z.bit_length() <= max_bits else f"{z:#x}"
-    n0h_str   = format(n0h, fieldo2) if n0h.bit_length() <= max_bits else f"{n0h:#x}"
-    r_h_str  = format(r_h, fieldo2) if r_h.bit_length() <= max_bits else f"{r_h:#x}"
-    r_hp_str = format(r_hp, fieldo2) if r_hp.bit_length() <= max_bits else f"{r_hp:#x}"
-
-    print(f"{z_str}  {n0h_str}  {r_h_str}  {r_hp_str}")
-
-    if i % 1000 == 0:
-      print(".", end="", flush=True)
-
-  if num_tests >= 1000:
-    print()
-
-
-
-def main():
-  if len(sys.argv) != 3:
-    print(f"Usage: {sys.argv[0]} <n_tests> <max_bits>")
-    sys.exit(1)
-
-  try:
-    n = int(sys.argv[1], 0)
-    max = int(sys.argv[2], 0)
-  except ValueError:
-    print("Error: argument must be a valid integer.")
-    sys.exit(1)
-
-  if n < 0:
-    print("Error: number of tests must be non-negative.")
-    sys.exit(1)
-
-  if max < 4:
-    print("Error: max_bits must be at least 4.")
-    sys.exit(1)
-
-  test_reduce(n, max)
-
-if __name__ == "__main__":
-  main()
diff --git a/developer/tool/.githolder b/developer/tool/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/developer/tool/.gitkeep b/developer/tool/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/document/.githolder b/document/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/document/.gitkeep b/document/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/release/authored/.gitkeep b/release/authored/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/release/documnt/.gitkeep b/release/documnt/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/release/loadable/.gitkeep b/release/loadable/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/release/local_build/.gitignore b/release/local_build/.gitignore
new file mode 100644 (file)
index 0000000..bea49c8
--- /dev/null
@@ -0,0 +1,3 @@
+
+*
+!/.gitignore
\ No newline at end of file
diff --git a/release/python3/.githolder b/release/python3/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/release/shell/.githolder b/release/shell/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tester/.githolder b/tester/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tester/.gitkeep b/tester/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
index 969c70a..0ebb856 100644 (file)
@@ -302,7 +302,12 @@ def build_export_commands(
   cp_cmds: List[str] = []
   other_list: List[str] = []
 
-  for rel_path, a_info in A_tree.items():
+  # 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 "."
 
@@ -316,12 +321,27 @@ def build_export_commands(
       )
       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: safe to mkdir -p
-        target_dir = os.path.join(B_root, rel_path) if rel_path else B_root
-        mkdir_cmds.append(f"mkdir -p {shell_quote(target_dir)}")
+        # 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":
@@ -334,9 +354,10 @@ def build_export_commands(
     if b_info is None:
       # A-only file
       src = os.path.join(A_root, rel_path) if rel_path else A_root
-      dst = B_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 --parents -a {shell_quote(src)} {shell_quote(dst)}/"
+        f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
       )
       continue
 
@@ -353,9 +374,10 @@ def build_export_commands(
     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
-        dst = B_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 --parents -a {shell_quote(src)} {shell_quote(dst)}/"
+          f"cp -a {shell_quote(src)} {shell_quote(dst + os.sep)}"
         )
 
   return mkdir_cmds, cp_cmds, other_list
@@ -382,7 +404,7 @@ def cmd_export(
   print(f"# B root: {B_root}")
   print("#")
 
-  print("# Directories to create in B (mkdir -p):")
+  print("# Directories to copy from A -> B (cp -a):")
   if mkdir_cmds:
     for line in mkdir_cmds:
       print(line)
@@ -390,7 +412,7 @@ def cmd_export(
     print("#   (none)")
   print("#")
 
-  print("# Files to copy from A -> B (cp --parents -a):")
+  print("# Files to copy from A -> B (cp -a):")
   if cp_cmds:
     for line in cp_cmds:
       print(line)
diff --git a/tool/source_skeleton_compare/manus_2025_11_20_215471873762383.txt b/tool/source_skeleton_compare/manus_2025_11_20_215471873762383.txt
new file mode 100644 (file)
index 0000000..70e1f99
--- /dev/null
@@ -0,0 +1,33 @@
+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/tool_shared/bespoke/.githolder b/tool_shared/bespoke/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tool_shared/bespoke/.gitkeep b/tool_shared/bespoke/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tool_shared/document/.githolder b/tool_shared/document/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tool_shared/document/.gitkeep b/tool_shared/document/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tool_shared/patch/.githolder b/tool_shared/patch/.githolder
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tool_shared/patch/.gitkeep b/tool_shared/patch/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29