Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 71 additions & 28 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions .claude/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Local settings that shouldn't be committed
settings.local.json
# Local settings that shouldn't be committed
settings.local.json
48 changes: 48 additions & 0 deletions .config/mise/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,24 @@ bun = "1.3.8"
# JSON Schema CLI (https://github.com/sourcemeta/jsonschema)
jsonschema = "latest"

# ===== Initialization Tasks =====
[tasks.init]
description = "Initialize development environment. Use --ci for CI environments."
run = """
#!/usr/bin/env bash
set -e

if [[ "$1" == "--ci" ]]; then
# CI mode: shallow clone submodules for faster checkout
echo "Initializing in CI mode (shallow submodules)..."
git submodule update --init --recursive --depth 1
else
# Development mode: full clone
echo "Initializing development environment..."
git submodule update --init --recursive
fi
"""

# ===== Formatting Tasks =====
[tasks.fmt]
description = "Format all code"
Expand Down Expand Up @@ -130,3 +148,33 @@ description = "Run TypeScript type checking on website"
run = "npm run typecheck"
dir = "website"
depends = ["website:install"]

# ===== Submodule Tasks =====
[tasks."submodules:init"]
description = "Initialize and fetch all submodules (first-time or after clone without --recurse-submodules)"
run = "git submodule update --init --recursive"

[tasks."submodules:update"]
description = "Update each submodule to the commit recorded in the superproject"
run = "git submodule update --recursive"

[tasks."submodules:status"]
description = "Show submodule status (commit, branch, dirty state)"
run = "git submodule status"

[tasks."submodules:pull"]
description = "Pull latest in each submodule (superproject ref unchanged until you commit)"
run = "git submodule foreach 'git pull origin main'"

[tasks."submodules:add"]
description = "Add a new submodule under ecosystem/. Usage: mise run submodules:add -- <name> [url]"
run = "python .config/mise/tasks/submodules/add.py"

# ===== CI Tasks =====
[tasks."ci:prepare-cli-workspace"]
description = "Prepare Cargo workspace for CLI-only build (excludes morphir-live)"
run = "python .config/mise/tasks/ci/prepare_cli_workspace.py"

[tasks."ci:validate-docs"]
description = "Validate documentation files"
run = "python .config/mise/tasks/ci/validate_docs.py"
81 changes: 81 additions & 0 deletions .config/mise/tasks/ci/prepare_cli_workspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""
Prepare Cargo workspace for morphir CLI build.

This script modifies Cargo.toml to exclude morphir-live (which has conflicting
dependencies) and regenerates the lockfile for CLI-only builds.

Usage:
python prepare_cli_workspace.py
"""

import re
import subprocess
import sys
from pathlib import Path


def main() -> int:
"""Prepare workspace for CLI-only build."""
repo_root = Path(__file__).resolve().parents[4]
cargo_toml = repo_root / "Cargo.toml"
cargo_lock = repo_root / "Cargo.lock"

print("Preparing workspace for morphir CLI build...")

# Read current Cargo.toml
content = cargo_toml.read_text()

# Update members to only include morphir crate
content = re.sub(
r'members\s*=\s*\["crates/\*"\]',
'members = ["crates/morphir"]',
content,
)

# Update default-members to only include morphir
content = re.sub(
r'default-members\s*=\s*\["crates/morphir-live",\s*"crates/morphir"\]',
'default-members = ["crates/morphir"]',
content,
)

# Write modified Cargo.toml
cargo_toml.write_text(content)
print(" Updated Cargo.toml to exclude morphir-live")

# Remove existing lockfile
if cargo_lock.exists():
cargo_lock.unlink()
print(" Removed existing Cargo.lock")

# Generate fresh lockfile
print(" Generating fresh lockfile...")
result = subprocess.run(
["cargo", "generate-lockfile"],
cwd=repo_root,
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"Error generating lockfile: {result.stderr}", file=sys.stderr)
return 1

# Update extism to latest compatible version
print(" Updating extism to latest compatible version...")
result = subprocess.run(
["cargo", "update", "-p", "extism"],
cwd=repo_root,
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"Warning: Could not update extism: {result.stderr}", file=sys.stderr)
# Don't fail - extism update is optional

print("Workspace prepared for CLI build!")
return 0


if __name__ == "__main__":
sys.exit(main())
66 changes: 66 additions & 0 deletions .config/mise/tasks/ci/validate_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""
Validate documentation files.

This script performs quick validation checks on markdown and documentation
files without making slow HTTP requests to verify external links.

Usage:
python validate_docs.py
"""

import sys
from pathlib import Path


def main() -> int:
"""Validate documentation files."""
repo_root = Path(__file__).resolve().parents[4]

print("Validating documentation files...")

# Collect markdown files
md_files = list(repo_root.glob("**/*.md"))

# Exclude common non-doc directories
excluded_dirs = {".git", "node_modules", "target", ".mise", "dist", "build"}
md_files = [
f
for f in md_files
if not any(excluded in f.parts for excluded in excluded_dirs)
]

if not md_files:
print(" No markdown files found")
return 0

print(f" Found {len(md_files)} markdown files")

# Quick validation - just verify files are readable
errors = []
for md_file in md_files[:50]: # Check first 50 files to keep it fast
try:
content = md_file.read_text(encoding="utf-8")
# Basic check: file is not empty or just whitespace
if not content.strip():
errors.append(f" Empty file: {md_file.relative_to(repo_root)}")
except UnicodeDecodeError:
errors.append(f" Encoding error: {md_file.relative_to(repo_root)}")
except Exception as e:
errors.append(f" Error reading {md_file.relative_to(repo_root)}: {e}")

if errors:
print("\nValidation issues:")
for error in errors:
print(error)
# Don't fail on empty files, just warn
print("\nValidation completed with warnings")
else:
print(" All checked files are valid")

print("Documentation validation passed!")
return 0


if __name__ == "__main__":
sys.exit(main())
76 changes: 76 additions & 0 deletions .config/mise/tasks/submodules/add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""
Add a new git submodule under ecosystem/.

Usage: mise run submodules:add -- <name> [url]
name: Submodule directory name (e.g. morphir-go, morphir-elm). Required.
url: Clone URL. If omitted, defaults to https://github.com/finos/<name>.git

Example:
mise run submodules:add -- morphir-go
mise run submodules:add -- morphir-elm https://github.com/finos/morphir-elm.git
"""

from __future__ import annotations

import os
import re
import subprocess
import sys


def main() -> int:
args = [a for a in sys.argv[1:] if a != "--"]
if not args:
print("Usage: mise run submodules:add -- <name> [url]", file=sys.stderr)
print(" name: submodule directory under ecosystem/ (e.g. morphir-go)", file=sys.stderr)
print(" url: optional; defaults to https://github.com/finos/<name>.git", file=sys.stderr)
return 1

name = args[0].strip()
if not name:
print("Error: name cannot be empty", file=sys.stderr)
return 1

# Basic sanity: name should look like a repo name (no path separators, no scheme)
if re.search(r"[/\\:]", name) or name.startswith("."):
print("Error: name should be a simple directory name (e.g. morphir-go)", file=sys.stderr)
return 1

url = args[1].strip() if len(args) > 1 else f"https://github.com/finos/{name}.git"

repo_root = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if repo_root.returncode != 0:
print("Error: must run from inside a git repository", file=sys.stderr)
return 1

root = repo_root.stdout.strip()
ecosystem_dir = os.path.join(root, "ecosystem")
path = os.path.join(ecosystem_dir, name)

if not os.path.isdir(ecosystem_dir):
print(f"Error: ecosystem directory not found: {ecosystem_dir}", file=sys.stderr)
return 1

if os.path.exists(path):
print(f"Error: path already exists: {path}", file=sys.stderr)
return 1

cmd = ["git", "submodule", "add", url, f"ecosystem/{name}"]
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, cwd=root)
if result.returncode != 0:
return result.returncode

print("")
print("Next: add the new submodule to ecosystem/README.md and ecosystem/AGENTS.md.")
return 0


if __name__ == "__main__":
sys.exit(main())
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Normalize line endings to LF for cross-platform consistency (avoids CRLF/LF noise on Windows)
* text=auto eol=lf

*.lockb binary diff=lockb

# Use bd merge for beads JSONL files
Expand Down
Loading