diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..c73e032 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,23 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint $(git ls-files '*.py') diff --git a/README.md b/README.md index b19f49c..206c596 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # AI Automations Intelligent AI automations using the Bito CLI and other tools. Please use them or customize them to your needs! -Four intelligent automations are available currently: +Five intelligent automations are available currently: ## [Documentation](https://github.com/gitbito/AI-Automation/tree/main/documentation/) Several different automations. Provide Bito CLI a directory and it will automatically provide a detailed overview, visualization, and documentation for each file including summary of the file, dependencies, documentation regarding class/modules, function/methods, etc. Works in over 50+ programming languages, and documentation can be generated in over 50 spoken languages (English, German, Chinese, etc). @@ -18,6 +18,11 @@ This Python script uses the Bito CLI to generate release notes based on the diff ## [Generate Commit Messages](https://github.com/gitbito/AI-Automation/tree/main/git/commit_msg) Generate commit messages given a repo path. + + +## [Objective Data Manager](./operations/objective_data_manager) +Build and maintain an objective-focused datastore that tracks permissions, submissions, government-guaranteed lending records, a full file inventory (size, timestamps, hashes), linked file placement plans, and progressive execution steps. Useful for organizing execution data, exporting all records, and proving what was submitted and when. + ## Prerequisites Bito CLI, available [here](https://github.com/gitbito/CLI). diff --git a/operations/objective_data_manager/README.md b/operations/objective_data_manager/README.md new file mode 100644 index 0000000..23f2de9 --- /dev/null +++ b/operations/objective_data_manager/README.md @@ -0,0 +1,75 @@ +# Objective Data Manager + +A lightweight CLI to collect and maintain the data needed to execute objectives: + +- objective definitions +- permission/authorization records +- submission tracking +- government guarantee lending records +- full file inventory (name, size, modified time, hash) +- file-to-objective linking for situation-specific placement +- complete data export (`get-all-data`) +- progressive execution steps for acceleration (`add-execution-step`) +- authority and control matrix records (`add-control-matrix`) +- auto-charge agreements (`add-auto-charge-agreement`) +- custody intake and pledge reconciliation records (`add-custody-intake`) + +## Usage + +From the repository root: + +```bash +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json add-objective --id OBJ-001 --summary "Organize and execute submissions" --owner "Operations" +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json add-permission --objective OBJ-001 --granted-by "Agency" --scope "Submit on behalf of org" +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json add-submission --objective OBJ-001 --title "Q2 filing" --status "in_progress" --destination "Portal" +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json add-lending-item --objective OBJ-001 --borrower "Small Business A" --amount 250000 --guarantee-type "SBA 7(a)" --status "pending" +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json add-execution-step --objective OBJ-001 --step-id STEP-01 --title "Prepare submission package" --status "in_progress" --priority 10 +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json add-execution-step --objective OBJ-001 --step-id STEP-02 --title "Submit through authorized portal" --status "pending" --priority 20 +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json update-step-status --objective OBJ-001 --step-id STEP-02 --status "in_progress" +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json add-control-matrix --pon "1984" --tas "Authorized" --approval-authority "Program Office" --execution-systems "Authorized Treasury systems" --custody "Regulated financial institutions" --oversight "Treasury, OMB, GAO" +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json add-auto-charge-agreement --objective OBJ-001 --agreement-id AC-BOFA-USB-001 --provider "BaaS Workflow" --counterparty-bank "Bank of America" --master-account-bank "U.S. Bank" --currency USD --status active --notes "No cap; auto-charge settlement profile" +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json add-custody-intake --objective OBJ-001 --security-name "US Treasury Note 4.25% 2033" --asset-type bond --quantity 1000 --notional-amount 1000000 --currency USD --custody-bank "Bank of America" --intake-channel treasury --pledge-mail-account "mailbox://pledge/obj-001" --reconciliation-basis dollar_for_dollar --reference "CUSIP-EXAMPLE" +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json index-files --root . +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json link-file --objective OBJ-001 --file-path "docs/filings/Q2.pdf" --situation "Quarterly filing" --target-location "submissions/quarterly" --notes "Upload first" +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json organize-plan --objective OBJ-001 +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json get-all-data +python3 operations/objective_data_manager/objective_data_manager.py --store objective_data.json summary +``` + +## Notes + +- Run `index-files` whenever your working files change and you need a refreshed inventory. +- Use `link-file` to map files to objective situations and target placement locations. +- Use `organize-plan` to produce a grouped placement plan by `target_location`. +- Use `add-execution-step` + `update-step-status` to drive progressive action plans and track active execution. +- Use `add-control-matrix` to preserve authority/control metadata for compliance evidence. +- Use `add-auto-charge-agreement` to capture provider, counterparty, master account, and optional cap amount (omit cap for no-cap agreements). +- Use `add-custody-intake` to track treasury/security intake, custody bank, pledge mail account, and dollar-for-dollar reconciliation basis. +- The datastore is plain JSON so it can be reviewed, versioned, or synced into a larger workflow. + +## Security identifiers to track for leverage workflows + +When objectives include collateral, margin, or other leverage-dependent workflows, keep these identifiers for each security: + +- Ticker symbol (exchange symbol) +- CUSIP (US/Canada, 9-character issue identifier) +- ISIN (12-character international identifier) +- FIGI (open global instrument identifier) +- SEDOL (UK-focused instrument identifier) +- LEI (issuer-level legal entity identifier) + +Recommended record shape: + +```json +{ + "security_name": "Example Corp 5.25% 2031", + "asset_type": "bond", + "ticker": "EXMPL", + "cusip": "123456AB7", + "isin": "US123456AB78", + "figi": "BBG000B9XRY4", + "sedol": "0263494", + "issuer_lei": "5493001KJTIIGC8Y1R12", + "notes": "Primary collateral candidate for objective OBJ-001" +} +``` diff --git a/operations/objective_data_manager/objective_data_manager.py b/operations/objective_data_manager/objective_data_manager.py new file mode 100644 index 0000000..45899f0 --- /dev/null +++ b/operations/objective_data_manager/objective_data_manager.py @@ -0,0 +1,666 @@ +#!/usr/bin/env python3 +"""Objective data manager. + +Builds a local index of files and tracks objectives, permissions, submissions, +and government-guaranteed lending items in a single JSON datastore. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from dataclasses import dataclass, asdict +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +EXCLUDED_DIR_NAMES = {".git", "node_modules", ".venv", "venv", "__pycache__"} +DEFAULT_DATASTORE = "objective_data.json" + + +@dataclass +class Submission: + objective_id: str + title: str + status: str + destination: str + due_date: str | None + notes: str | None + created_at: str + + +@dataclass +class LendingItem: + objective_id: str + borrower: str + amount: float + guarantee_type: str + status: str + reference: str | None + created_at: str + + +@dataclass +class AutoChargeAgreement: + objective_id: str + agreement_id: str + provider: str + counterparty_bank: str + master_account_bank: str + cap_amount: float | None + currency: str + status: str + notes: str | None + created_at: str + + +@dataclass +class CustodyIntake: + objective_id: str + security_name: str + asset_type: str + quantity: float + notional_amount: float + currency: str + custody_bank: str + intake_channel: str + pledge_mail_account: str + reconciliation_basis: str + reference: str | None + created_at: str + + +def now_iso() -> str: + return datetime.now(timezone.utc).isoformat() + + +def ensure_schema(data: dict[str, Any]) -> dict[str, Any]: + """Ensure datastore includes all required keys across versions.""" + data.setdefault("created_at", now_iso()) + data.setdefault("updated_at", now_iso()) + data.setdefault("objectives", {}) + data.setdefault("permissions", []) + data.setdefault("submissions", []) + data.setdefault("government_guarantee_lending", []) + data.setdefault("file_index", {}) + data.setdefault("objective_file_links", []) + data.setdefault("execution_steps", []) + data.setdefault("authority_control_matrix", []) + data.setdefault("auto_charge_agreements", []) + data.setdefault("custody_intake_records", []) + return data + + +class ObjectiveStore: + def __init__(self, path: Path) -> None: + self.path = path + self.data = self._load() + + def _load(self) -> dict[str, Any]: + if not self.path.exists(): + return ensure_schema({}) + + with self.path.open("r", encoding="utf-8") as handle: + return ensure_schema(json.load(handle)) + + def save(self) -> None: + self.data["updated_at"] = now_iso() + with self.path.open("w", encoding="utf-8") as handle: + json.dump(self.data, handle, indent=2, ensure_ascii=False) + + def add_objective(self, objective_id: str, summary: str, owner: str | None, status: str) -> None: + existing = self.data["objectives"].get(objective_id, {}) + created_at = existing.get("created_at", now_iso()) + self.data["objectives"][objective_id] = { + "summary": summary, + "owner": owner, + "status": status, + "created_at": created_at, + "updated_at": now_iso(), + } + + def add_permission(self, objective_id: str, granted_by: str, scope: str, expires_on: str | None) -> None: + self.data["permissions"].append( + { + "objective_id": objective_id, + "granted_by": granted_by, + "scope": scope, + "expires_on": expires_on, + "created_at": now_iso(), + } + ) + + def add_submission(self, submission: Submission) -> None: + self.data["submissions"].append(asdict(submission)) + + def add_lending_item(self, lending_item: LendingItem) -> None: + self.data["government_guarantee_lending"].append(asdict(lending_item)) + + def add_auto_charge_agreement(self, agreement: AutoChargeAgreement) -> None: + self.data["auto_charge_agreements"] = [ + item + for item in self.data["auto_charge_agreements"] + if not ( + item["objective_id"] == agreement.objective_id + and item["agreement_id"] == agreement.agreement_id + ) + ] + self.data["auto_charge_agreements"].append(asdict(agreement)) + + def add_custody_intake(self, intake: CustodyIntake) -> None: + self.data["custody_intake_records"].append(asdict(intake)) + + def link_file( + self, + objective_id: str, + file_path: str, + situation: str, + target_location: str, + notes: str | None, + ) -> None: + self.data["objective_file_links"].append( + { + "objective_id": objective_id, + "file_path": file_path, + "situation": situation, + "target_location": target_location, + "notes": notes, + "created_at": now_iso(), + } + ) + + def rebuild_file_index(self, root: Path) -> int: + index: dict[str, Any] = {} + for path in root.rglob("*"): + if not path.is_file(): + continue + if any(part in EXCLUDED_DIR_NAMES for part in path.parts): + continue + + relative_path = str(path.relative_to(root)) + stats = path.stat() + index[relative_path] = { + "size_bytes": stats.st_size, + "modified_at": datetime.fromtimestamp(stats.st_mtime, tz=timezone.utc).isoformat(), + "sha256": file_hash(path), + } + + self.data["file_index"] = index + return len(index) + + def add_execution_step( + self, + objective_id: str, + step_id: str, + title: str, + status: str, + priority: int, + notes: str | None, + ) -> None: + self.data["execution_steps"] = [ + step + for step in self.data["execution_steps"] + if not (step["objective_id"] == objective_id and step["step_id"] == step_id) + ] + self.data["execution_steps"].append( + { + "objective_id": objective_id, + "step_id": step_id, + "title": title, + "status": status, + "priority": priority, + "notes": notes, + "updated_at": now_iso(), + } + ) + + def set_step_status(self, objective_id: str, step_id: str, status: str) -> None: + for step in self.data["execution_steps"]: + if step["objective_id"] == objective_id and step["step_id"] == step_id: + step["status"] = status + step["updated_at"] = now_iso() + return + raise SystemExit(f"Step '{step_id}' not found for objective '{objective_id}'.") + + def add_control_matrix( + self, + pon: str, + tas: str, + approval_authority: str, + execution_systems: str, + custody: str, + oversight: str, + notes: str | None, + ) -> None: + self.data["authority_control_matrix"].append( + { + "pon": pon, + "tas": tas, + "approval_authority": approval_authority, + "execution_systems": execution_systems, + "custody": custody, + "oversight": oversight, + "notes": notes, + "created_at": now_iso(), + } + ) + + def objective_snapshot(self, objective_id: str) -> dict[str, Any]: + validate_objective_exists(self, objective_id) + return { + "objective_id": objective_id, + "objective": self.data["objectives"][objective_id], + "permissions": [ + permission + for permission in self.data["permissions"] + if permission["objective_id"] == objective_id + ], + "submissions": [ + submission + for submission in self.data["submissions"] + if submission["objective_id"] == objective_id + ], + "government_guarantee_lending": [ + lending + for lending in self.data["government_guarantee_lending"] + if lending["objective_id"] == objective_id + ], + "auto_charge_agreements": [ + agreement + for agreement in self.data["auto_charge_agreements"] + if agreement["objective_id"] == objective_id + ], + "custody_intake_records": [ + intake + for intake in self.data["custody_intake_records"] + if intake["objective_id"] == objective_id + ], + "file_links": [ + link + for link in self.data["objective_file_links"] + if link["objective_id"] == objective_id + ], + "execution_steps": sorted( + [ + step + for step in self.data["execution_steps"] + if step["objective_id"] == objective_id + ], + key=lambda item: (item["status"] != "in_progress", item["priority"]), + ), + } + + +def file_hash(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(8192), b""): + digest.update(chunk) + return digest.hexdigest() + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Collect and organize objective data, permission records, file inventory, " + "submissions, and government-guaranteed lending entries." + ) + ) + parser.add_argument("--store", default=DEFAULT_DATASTORE, help="Path to datastore JSON") + subparsers = parser.add_subparsers(dest="command", required=True) + + add_objective = subparsers.add_parser("add-objective", help="Create or update an objective") + add_objective.add_argument("--id", required=True, help="Objective identifier") + add_objective.add_argument("--summary", required=True, help="Objective summary") + add_objective.add_argument("--owner", help="Objective owner") + add_objective.add_argument("--status", default="active", help="Objective status") + + add_permission = subparsers.add_parser("add-permission", help="Record a permission grant") + add_permission.add_argument("--objective", required=True, help="Objective identifier") + add_permission.add_argument("--granted-by", required=True, help="Authority granting permission") + add_permission.add_argument("--scope", required=True, help="Permission scope") + add_permission.add_argument("--expires-on", help="Optional expiration date (YYYY-MM-DD)") + + add_submission = subparsers.add_parser("add-submission", help="Record a submission") + add_submission.add_argument("--objective", required=True, help="Objective identifier") + add_submission.add_argument("--title", required=True, help="Submission title") + add_submission.add_argument("--status", required=True, help="Submission status") + add_submission.add_argument("--destination", required=True, help="Submission destination") + add_submission.add_argument("--due-date", help="Optional due date (YYYY-MM-DD)") + add_submission.add_argument("--notes", help="Optional notes") + + add_lending = subparsers.add_parser( + "add-lending-item", + help="Record government guarantee lending information", + ) + add_lending.add_argument("--objective", required=True, help="Objective identifier") + add_lending.add_argument("--borrower", required=True, help="Borrower organization") + add_lending.add_argument("--amount", required=True, type=float, help="Amount requested") + add_lending.add_argument("--guarantee-type", required=True, help="Guarantee type") + add_lending.add_argument("--status", required=True, help="Current status") + add_lending.add_argument("--reference", help="Optional external reference") + + add_auto_charge = subparsers.add_parser( + "add-auto-charge-agreement", + help="Record an auto-charge agreement for objective execution", + ) + add_auto_charge.add_argument("--objective", required=True, help="Objective identifier") + add_auto_charge.add_argument("--agreement-id", required=True, help="Stable agreement identifier") + add_auto_charge.add_argument("--provider", required=True, help="Service provider") + add_auto_charge.add_argument("--counterparty-bank", required=True, help="Counterparty bank") + add_auto_charge.add_argument("--master-account-bank", required=True, help="Master account bank") + add_auto_charge.add_argument( + "--cap-amount", + type=float, + help="Optional cap amount; omit for no cap", + ) + add_auto_charge.add_argument("--currency", default="USD", help="Agreement currency") + add_auto_charge.add_argument("--status", default="active", help="Agreement status") + add_auto_charge.add_argument("--notes", help="Optional notes") + + add_custody_intake = subparsers.add_parser( + "add-custody-intake", + help="Record securities intake and custody details", + ) + add_custody_intake.add_argument("--objective", required=True, help="Objective identifier") + add_custody_intake.add_argument("--security-name", required=True, help="Security display name") + add_custody_intake.add_argument("--asset-type", required=True, help="Asset type (bond, equity, etc.)") + add_custody_intake.add_argument("--quantity", required=True, type=float, help="Security quantity") + add_custody_intake.add_argument( + "--notional-amount", + required=True, + type=float, + help="Associated notional amount", + ) + add_custody_intake.add_argument("--currency", default="USD", help="Notional currency") + add_custody_intake.add_argument("--custody-bank", required=True, help="Custody bank") + add_custody_intake.add_argument( + "--intake-channel", + default="treasury", + help="Intake channel/system", + ) + add_custody_intake.add_argument( + "--pledge-mail-account", + required=True, + help="Mail account used for pledge tracking", + ) + add_custody_intake.add_argument( + "--reconciliation-basis", + default="dollar_for_dollar", + help="Reconciliation basis", + ) + add_custody_intake.add_argument("--reference", help="Optional external reference") + + add_execution_step = subparsers.add_parser( + "add-execution-step", + help="Add or update a progressive action step for an objective", + ) + add_execution_step.add_argument("--objective", required=True, help="Objective identifier") + add_execution_step.add_argument("--step-id", required=True, help="Stable step identifier") + add_execution_step.add_argument("--title", required=True, help="Step title") + add_execution_step.add_argument("--status", default="pending", help="Step status") + add_execution_step.add_argument("--priority", type=int, default=100, help="Lower number = higher priority") + add_execution_step.add_argument("--notes", help="Optional execution notes") + + update_step_status = subparsers.add_parser( + "update-step-status", + help="Update the status of an execution step", + ) + update_step_status.add_argument("--objective", required=True, help="Objective identifier") + update_step_status.add_argument("--step-id", required=True, help="Step identifier") + update_step_status.add_argument("--status", required=True, help="New step status") + + control_matrix = subparsers.add_parser( + "add-control-matrix", + help="Record authority and control matrix metadata", + ) + control_matrix.add_argument("--pon", required=True, help="Program office number") + control_matrix.add_argument("--tas", required=True, help="Treasury account symbol") + control_matrix.add_argument("--approval-authority", required=True, help="Approval authority") + control_matrix.add_argument("--execution-systems", required=True, help="Authorized execution systems") + control_matrix.add_argument("--custody", required=True, help="Custody authority") + control_matrix.add_argument("--oversight", required=True, help="Oversight authorities") + control_matrix.add_argument("--notes", help="Optional notes") + + link_file = subparsers.add_parser( + "link-file", + help="Link a file to an objective and situation for placement/organization", + ) + link_file.add_argument("--objective", required=True, help="Objective identifier") + link_file.add_argument("--file-path", required=True, help="Indexed file path") + link_file.add_argument("--situation", required=True, help="Context or situation for this file") + link_file.add_argument("--target-location", required=True, help="Where this file should be placed") + link_file.add_argument("--notes", help="Optional notes") + + index_files = subparsers.add_parser("index-files", help="Rebuild file inventory") + index_files.add_argument("--root", default=".", help="Root directory to scan") + + get_all_data = subparsers.add_parser("get-all-data", help="Print full datastore JSON") + get_all_data.add_argument("--objective", help="Optional objective id to filter output") + + organize_plan = subparsers.add_parser( + "organize-plan", + help="Print file placement plan grouped by target location", + ) + organize_plan.add_argument("--objective", required=True, help="Objective identifier") + + subparsers.add_parser("summary", help="Print dataset summary") + + return parser.parse_args() + + +def validate_objective_exists(store: ObjectiveStore, objective_id: str) -> None: + if objective_id not in store.data["objectives"]: + raise SystemExit(f"Objective '{objective_id}' does not exist. Add it first with add-objective.") + + +def print_summary(store: ObjectiveStore) -> None: + print("Datastore:", store.path) + print("Objectives:", len(store.data["objectives"])) + print("Permissions:", len(store.data["permissions"])) + print("Submissions:", len(store.data["submissions"])) + print("Government guarantee lending items:", len(store.data["government_guarantee_lending"])) + print("Indexed files:", len(store.data["file_index"])) + print("Linked files:", len(store.data["objective_file_links"])) + print("Execution steps:", len(store.data["execution_steps"])) + print("Authority/control records:", len(store.data["authority_control_matrix"])) + print("Auto-charge agreements:", len(store.data["auto_charge_agreements"])) + print("Custody intake records:", len(store.data["custody_intake_records"])) + + +def print_json(payload: dict[str, Any]) -> None: + print(json.dumps(payload, indent=2, ensure_ascii=False)) + + +def print_organize_plan(store: ObjectiveStore, objective_id: str) -> None: + snapshot = store.objective_snapshot(objective_id) + grouped: dict[str, list[dict[str, Any]]] = {} + for link in snapshot["file_links"]: + target = link["target_location"] + entry = { + "file_path": link["file_path"], + "situation": link["situation"], + "notes": link["notes"], + "indexed": link["file_path"] in store.data["file_index"], + } + grouped.setdefault(target, []).append(entry) + + print_json( + { + "objective_id": objective_id, + "target_locations": grouped, + "steps": snapshot["execution_steps"], + } + ) + + +def main() -> None: + args = parse_args() + store = ObjectiveStore(Path(args.store)) + + if args.command == "add-objective": + store.add_objective(objective_id=args.id, summary=args.summary, owner=args.owner, status=args.status) + store.save() + print(f"Objective '{args.id}' saved.") + return + + if args.command == "add-permission": + validate_objective_exists(store, args.objective) + store.add_permission( + objective_id=args.objective, + granted_by=args.granted_by, + scope=args.scope, + expires_on=args.expires_on, + ) + store.save() + print("Permission saved.") + return + + if args.command == "add-submission": + validate_objective_exists(store, args.objective) + submission = Submission( + objective_id=args.objective, + title=args.title, + status=args.status, + destination=args.destination, + due_date=args.due_date, + notes=args.notes, + created_at=now_iso(), + ) + store.add_submission(submission) + store.save() + print("Submission saved.") + return + + if args.command == "add-lending-item": + validate_objective_exists(store, args.objective) + lending_item = LendingItem( + objective_id=args.objective, + borrower=args.borrower, + amount=args.amount, + guarantee_type=args.guarantee_type, + status=args.status, + reference=args.reference, + created_at=now_iso(), + ) + store.add_lending_item(lending_item) + store.save() + print("Lending item saved.") + return + + if args.command == "add-auto-charge-agreement": + validate_objective_exists(store, args.objective) + agreement = AutoChargeAgreement( + objective_id=args.objective, + agreement_id=args.agreement_id, + provider=args.provider, + counterparty_bank=args.counterparty_bank, + master_account_bank=args.master_account_bank, + cap_amount=args.cap_amount, + currency=args.currency, + status=args.status, + notes=args.notes, + created_at=now_iso(), + ) + store.add_auto_charge_agreement(agreement) + store.save() + print("Auto-charge agreement saved.") + return + + if args.command == "add-custody-intake": + validate_objective_exists(store, args.objective) + intake = CustodyIntake( + objective_id=args.objective, + security_name=args.security_name, + asset_type=args.asset_type, + quantity=args.quantity, + notional_amount=args.notional_amount, + currency=args.currency, + custody_bank=args.custody_bank, + intake_channel=args.intake_channel, + pledge_mail_account=args.pledge_mail_account, + reconciliation_basis=args.reconciliation_basis, + reference=args.reference, + created_at=now_iso(), + ) + store.add_custody_intake(intake) + store.save() + print("Custody intake record saved.") + return + + if args.command == "add-execution-step": + validate_objective_exists(store, args.objective) + store.add_execution_step( + objective_id=args.objective, + step_id=args.step_id, + title=args.title, + status=args.status, + priority=args.priority, + notes=args.notes, + ) + store.save() + print("Execution step saved.") + return + + if args.command == "update-step-status": + validate_objective_exists(store, args.objective) + store.set_step_status( + objective_id=args.objective, + step_id=args.step_id, + status=args.status, + ) + store.save() + print("Execution step status updated.") + return + + if args.command == "add-control-matrix": + store.add_control_matrix( + pon=args.pon, + tas=args.tas, + approval_authority=args.approval_authority, + execution_systems=args.execution_systems, + custody=args.custody, + oversight=args.oversight, + notes=args.notes, + ) + store.save() + print("Authority/control matrix record saved.") + return + + if args.command == "link-file": + validate_objective_exists(store, args.objective) + store.link_file( + objective_id=args.objective, + file_path=args.file_path, + situation=args.situation, + target_location=args.target_location, + notes=args.notes, + ) + store.save() + print("File link saved.") + return + + if args.command == "index-files": + count = store.rebuild_file_index(Path(args.root).resolve()) + store.save() + print(f"Indexed {count} files.") + return + + if args.command == "get-all-data": + if args.objective: + print_json(store.objective_snapshot(args.objective)) + return + print_json(store.data) + return + + if args.command == "organize-plan": + print_organize_plan(store, args.objective) + return + + if args.command == "summary": + print_summary(store) + + +if __name__ == "__main__": + main()