diff --git a/.gitignore b/.gitignore index 343010f5..605784a3 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,10 @@ whiteboard.md README_ORIGINAL_DISABLED.md readme_history/ src/aipass/trigger/trigger_data.lock + +# Private integrations — driver layer (@api) and wrapper layer (all branches) +# Per DPLAN-0133. Contents are gitignored; only the scaffold README.md is tracked. +# Drop project-specific code into src/aipass/{branch}/apps/integrations/{project}/ +# It stays local. Never appears in git. +src/aipass/*/apps/integrations/** +!src/aipass/*/apps/integrations/README.md diff --git a/STATUS.md b/STATUS.md index 7392081d..727bf1ae 100644 --- a/STATUS.md +++ b/STATUS.md @@ -300,7 +300,7 @@ ### Friction notes -- **seedgo __init__.py false positive**: `imports: Failed` and `naming: invalid characters` fire on Python reserved filename. Need special-case in seedgo's naming standard and imports standard. Encountered in another project; likely also in AIPass repo. Open a seedgo standard PR when there's bandwidth. +(none active — seedgo __init__.py false positive FIXED by @seedgo in PR #279, 2026-04-14) ### S92 (2026-04-14 ~12:45 PT) — PRE-COMPACT, system prompt reformat work in flight diff --git a/src/aipass/ai_mail/apps/integrations/README.md b/src/aipass/ai_mail/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/ai_mail/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale. diff --git a/src/aipass/api/apps/integrations/README.md b/src/aipass/api/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/api/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale. diff --git a/src/aipass/cli/apps/integrations/README.md b/src/aipass/cli/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/cli/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale. diff --git a/src/aipass/drone/apps/integrations/README.md b/src/aipass/drone/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/drone/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale. diff --git a/src/aipass/drone/apps/plugins/devpulse_ops/merge_plugin.py b/src/aipass/drone/apps/plugins/devpulse_ops/merge_plugin.py index 8ac6ce68..02a47920 100644 --- a/src/aipass/drone/apps/plugins/devpulse_ops/merge_plugin.py +++ b/src/aipass/drone/apps/plugins/devpulse_ops/merge_plugin.py @@ -65,16 +65,39 @@ def merge_pr(pr_number: str, caller: str) -> dict: logger.error(result["message"]) return result - # Step 2: Sync local main + # Step 2: Sync local main — stash any unstaged changes first so + # git pull --rebase doesn't abort on a dirty working tree. + stash = subprocess.run( + ["git", "stash"], + capture_output=True, text=True, cwd=str(repo_root), + ) + stashed = "No local changes to save" not in stash.stdout + pull = subprocess.run( ["git", "pull", "--rebase"], capture_output=True, text=True, cwd=str(repo_root), ) if pull.returncode != 0: + if stashed: + subprocess.run( + ["git", "stash", "pop"], + capture_output=True, text=True, cwd=str(repo_root), + ) result["message"] = f"Pull after merge failed: {pull.stderr.strip()}" logger.error(result["message"]) return result + if stashed: + pop = subprocess.run( + ["git", "stash", "pop"], + capture_output=True, text=True, cwd=str(repo_root), + ) + if pop.returncode != 0: + logger.warning( + "merge_pr: stash pop after pull failed (manual restore may be needed): %s", + pop.stderr.strip(), + ) + # Step 3: Get the merge commit hash rev = subprocess.run( ["git", "rev-parse", "HEAD"], diff --git a/src/aipass/flow/apps/integrations/README.md b/src/aipass/flow/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/flow/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale. diff --git a/src/aipass/memory/apps/integrations/README.md b/src/aipass/memory/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/memory/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale. diff --git a/src/aipass/prax/apps/integrations/README.md b/src/aipass/prax/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/prax/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale. diff --git a/src/aipass/seedgo/apps/integrations/README.md b/src/aipass/seedgo/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/seedgo/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale. diff --git a/src/aipass/spawn/apps/integrations/README.md b/src/aipass/spawn/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/spawn/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale. diff --git a/src/aipass/trigger/apps/integrations/README.md b/src/aipass/trigger/apps/integrations/README.md new file mode 100644 index 00000000..0fab10ce --- /dev/null +++ b/src/aipass/trigger/apps/integrations/README.md @@ -0,0 +1,64 @@ +# apps/integrations/ + +Private integration space for this branch. + +**This folder is gitignored.** Only this README is tracked. Everything else you drop in here stays local and never appears in git, PRs, or the public repo. Safe by construction, not by discipline. + +## What goes here + +**Branch-specific wrappers** that consume external systems via the @api driver layer. Each wrapper handles how THIS branch uses an external system in its own domain. + +``` +apps/integrations/ +└── {project}/ + ├── wrapper.py # How this branch uses the driver + ├── config.json # Optional — local config + └── tests/ # Private tests colocated +``` + +Wrappers should call into `@api`'s generic contracts (e.g. `api.memory_backend.query(...)`), never reference the private project by name in any tracked code. The private project name lives in the @api driver, not here. + +## What does NOT go here + +- **Driver code** — that belongs in `@api/apps/integrations/{project}/driver.py` (the connection layer). +- **Public business logic** — use `apps/modules/` or `apps/handlers/` for that. +- **Drone plugins** — use `apps/plugins/` for those. +- **Secrets** — they live in `~/.secrets/aipass/`, never in the repo. + +## Architecture + +The full design is in DPLAN-0133 (private integrations architecture). Three layers: + +1. **@api driver layer** (`@api/apps/integrations/{project}/`) — owns the physical connection, auth, transport. Knows the private project name. +2. **Per-branch wrapper layer** (`{this_folder}/{project}/`) — owns how this branch consumes the driver's output in its domain. Calls generic contracts, never names private projects. +3. **Public drone commands** (`drone @api integrations list`, `drone @api integrations call `) — advertise the extension points without naming specifics. Fork-safe. + +## Usage + +```python +# Your public code (committed, in apps/modules/ or apps/handlers/) +from aipass.api import memory_backend + +results = memory_backend.query("when did we ship watchdog?") +# memory_backend is a generic contract. In your local setup it routes to whatever +# driver you registered in @api/apps/integrations/. In a fresh clone with nothing +# registered, it returns NotConfigured gracefully. +``` + +```python +# Your private wrapper (in this folder, gitignored) +# apps/integrations/someproject/wrapper.py + +from aipass.api import memory_backend + +def domain_specific_query(context): + """Branch-specific query pattern for domain needs.""" + hint = build_query_from_context(context) + return memory_backend.query(hint, top_k=5, filter={"kind": "decision"}) +``` + +The wrapper stays here, the call into the contract stays here, no private name leaks into tracked code. + +--- + +See DPLAN-0133 for the full design rationale.