--- /dev/null
+#include <stdio.h>
+int main(void){ puts("hello from Rabbit CLI"); return 0; }
+++ /dev/null
-#include <stdio.h>
-int main(void){ puts("hello from Rabbit CLI"); return 0; }
--- /dev/null
+#+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.
+++ /dev/null
-
-pip install isqrt
+++ /dev/null
-#!/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()
+++ /dev/null
-#!/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()
+++ /dev/null
-#!/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()
+++ /dev/null
-#!/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()
+++ /dev/null
-
-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()
--- /dev/null
+
+*
+!/.gitignore
\ No newline at end of file
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 "."
)
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":
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
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
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)
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)
--- /dev/null
+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