feat: update profile path resolution and enhance scripts for improved project adaptability

This commit is contained in:
2026-05-21 10:43:44 -06:00
parent f0d3cd4ce9
commit 7cbb49134a
18 changed files with 98 additions and 32 deletions

View File

@@ -41,7 +41,7 @@ bash scripts/memory/memory.sh search "PDIAP-15765"
bash scripts/memory/memory.sh create work-item pdiap-15999 "Example title"
```
This interface defaults to Markdown files under `project-knowledge/`, uses Obsidian CLI when useful and available, and falls back to direct file operations.
This interface resolves the canonical Markdown directory from `profiles/<profile>/workspace.json`, uses Obsidian CLI when useful and available, and falls back to direct file operations.
The default workspace Mattermost extractor now lives in:
@@ -79,7 +79,7 @@ Expected behavior:
- avoid interactive prompts
- return a non-zero exit code on failure
OpenCode can then use that output to refresh `ai/inbox/mattermost-latest.md` proactively. When the local Mattermost proxy mirror is running, commands should prefer `ai/inbox/mattermost-mirror/` through `scripts/mattermost-proxy/read-context.py` and use legacy sync output as fallback evidence.
OpenCode can then use that output to refresh the active profile inbox proactively. When the local Mattermost proxy mirror is running, commands should prefer `<profile inbox>/mattermost-mirror/` through `scripts/mattermost-proxy/read-context.py --profile <profile>` and use legacy sync output only as fallback evidence.
Historical Slack exports can also be imported through:

View File

@@ -34,7 +34,7 @@ The service manager unifies startup and status. It does not move capture behavio
## Local project-knowledge index
The workspace includes a dependency-free local indexer for canonical Markdown memory. The index is derived from `project-knowledge/` and written under `.aiw/indexes/<profile>/`; it is safe to delete and rebuild.
The workspace includes a dependency-free local indexer for canonical Markdown memory. The index is derived from the profile's configured `knowledge_dir` and written under the profile's configured `index_dir`; it is safe to delete and rebuild.
```bash
python3 scripts/aiw/indexer.py build --profile fidelity
@@ -62,7 +62,7 @@ Current fields:
}
```
Use `scripts/aiw/profile.py` from new scripts instead of hardcoding root-level `project-knowledge/` or `ai/inbox/` paths.
Use `scripts/aiw/profile.py` from new scripts instead of hardcoding root-level project memory or inbox paths.
## Robustness features

View File

@@ -8,6 +8,7 @@ instead of hardcoding root-level project paths.
from __future__ import annotations
import json
import argparse
from pathlib import Path
from typing import Any
@@ -70,3 +71,37 @@ def relative_to_root(path: Path, root: Path | None = None) -> str:
return str(path.relative_to(base))
except ValueError:
return str(path)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
path_parser = subparsers.add_parser("path", help="Print a resolved profile path.")
path_parser.add_argument("kind", choices=["knowledge", "inbox", "index"])
path_parser.add_argument("--profile", default="fidelity")
config_parser = subparsers.add_parser("config", help="Print resolved workspace configuration as JSON.")
config_parser.add_argument("--profile", default="fidelity")
args = parser.parse_args()
if args.command == "path":
if args.kind == "knowledge":
print(knowledge_dir(args.profile))
elif args.kind == "inbox":
print(inbox_dir(args.profile))
else:
print(index_dir(args.profile))
return
config = load_workspace_config(args.profile)
config["resolved"] = {
"knowledge_dir": str(knowledge_dir(args.profile)),
"inbox_dir": str(inbox_dir(args.profile)),
"index_dir": str(index_dir(args.profile)),
}
print(json.dumps(config, ensure_ascii=False, indent=2, sort_keys=True))
if __name__ == "__main__":
main()

View File

@@ -42,6 +42,12 @@ class ProfileTests(unittest.TestCase):
self.assertEqual(profile.inbox_dir("missing", root=root), root / "ai" / "inbox")
self.assertEqual(profile.index_dir("missing", root=root), root / ".aiw" / "indexes" / "missing")
def test_relative_to_root_handles_external_paths(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
self.assertEqual(profile.relative_to_root(root / "a" / "b", root=root), "a/b")
self.assertEqual(profile.relative_to_root(Path("/external/path"), root=root), "/external/path")
if __name__ == "__main__":
unittest.main()

View File

@@ -152,17 +152,18 @@ Keep this utility as an isolated image mailbox. If a project wants easy access,
link the project inbox to the mailbox instead of making this utility know about
the project.
Example:
Example using the active profile inbox:
```bash
mkdir -p ai/inbox
ln -s "$HOME/Pictures/Photo Inbox" ai/inbox/photos
PROFILE_INBOX="$(python3 scripts/aiw/profile.py path inbox --profile fidelity)"
mkdir -p "$PROFILE_INBOX"
ln -s "$HOME/Pictures/Photo Inbox" "$PROFILE_INBOX/photos"
```
Or point the receiver at a project-owned folder from `.env`:
```bash
PHOTO_INBOX_DIR=/absolute/path/to/project/ai/inbox/photos
PHOTO_INBOX_DIR=/absolute/path/to/profile/inbox/photos
```
The symlink approach keeps this utility reusable across projects and devices.

View File

@@ -7,7 +7,9 @@
MATTERMOST_MIRROR_HOST_ALLOW=
# Output directory for raw evidence and normalized AI-readable context.
MATTERMOST_MIRROR_DIR=ai/inbox/mattermost-mirror
# Optional. If omitted, run-mirror.sh writes to the active profile inbox:
# <inbox_dir from profiles/<profile>/workspace.json>/mattermost-mirror
# MATTERMOST_MIRROR_DIR=/absolute/path/to/mattermost-mirror
# mitmproxy listener used by launch-mattermost.sh.
MATTERMOST_MIRROR_LISTEN_HOST=127.0.0.1

View File

@@ -2,7 +2,7 @@
Local read-only Mattermost Desktop mirror for AI workspace context.
This is for **raw evidence only**. It writes under `ai/inbox/mattermost-mirror/`; durable project memory still belongs in `project-knowledge/` after normal promotion rules.
This is for **raw evidence only**. By default it writes under the active profile inbox at `<inbox_dir>/mattermost-mirror/`; durable project memory still belongs in the profile's canonical project knowledge vault after normal promotion rules.
## Why this exists
@@ -48,7 +48,7 @@ missing.
## Output layout
```text
ai/inbox/mattermost-mirror/
<profile inbox>/mattermost-mirror/
latest.jsonl # bounded AI-readable window
latest.md # bounded Markdown view
state.json # last seen by channel and user cache
@@ -138,7 +138,7 @@ Each line in the normalized JSONL contains:
## Useful environment variables
- `MATTERMOST_MIRROR_HOST_ALLOW`: exact host or parent domain to capture.
- `MATTERMOST_MIRROR_DIR`: output directory, default `ai/inbox/mattermost-mirror`.
- `MATTERMOST_MIRROR_DIR`: optional output directory. If omitted, `run-mirror.sh` uses `<inbox_dir from profiles/<profile>/workspace.json>/mattermost-mirror`.
- `MATTERMOST_MIRROR_LATEST_LIMIT`: number of messages in `latest.*`, default `200`.
- `MATTERMOST_MIRROR_CHANNEL_IDS`: optional comma-separated channel ID allowlist.
- `MATTERMOST_MIRROR_WRITE_RAW`: set to `1` to save compact raw REST/WebSocket evidence.
@@ -170,7 +170,7 @@ do not create message files. Open a channel, open a thread, scroll slightly in
history, or wait for/send a new message. Then check:
```text
ai/inbox/mattermost-mirror/latest.md
ai/inbox/mattermost-mirror/channels/<channel-name>/YYYY/MM/YYYY-MM-DD.jsonl
ai/inbox/mattermost-mirror/by-date/YYYY/MM/YYYY-MM-DD.jsonl
<profile inbox>/mattermost-mirror/latest.md
<profile inbox>/mattermost-mirror/channels/<channel-name>/YYYY/MM/YYYY-MM-DD.jsonl
<profile inbox>/mattermost-mirror/by-date/YYYY/MM/YYYY-MM-DD.jsonl
```

View File

@@ -6,7 +6,7 @@ This addon is intentionally narrow:
- redact secrets
- normalize posts into date-rotated JSONL files for AI context
The output under ai/inbox/ is raw evidence, not canonical project memory.
The output under the profile inbox is raw evidence, not canonical project memory.
"""
from __future__ import annotations
@@ -23,7 +23,7 @@ from urllib.parse import urlparse
from mitmproxy import http
DEFAULT_OUT_DIR = "ai/inbox/mattermost-mirror"
DEFAULT_OUT_DIR = "mattermost-mirror"
POST_ID_RE = re.compile(r"^[a-z0-9]{26}$")
SAFE_NAME_RE = re.compile(r"[^a-zA-Z0-9._-]+")

View File

@@ -13,17 +13,29 @@ import argparse
import json
import os
import shlex
import sys
from datetime import date, datetime, timedelta
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
MIRROR_DIR = ROOT / "ai" / "inbox" / "mattermost-mirror"
LEGACY_LATEST = ROOT / "ai" / "inbox" / "mattermost-latest.md"
sys.path.insert(0, str(ROOT / "scripts" / "aiw"))
import profile as aiw_profile # noqa: E402
DEFAULT_PROFILE = os.getenv("AIW_PROJECT_PROFILE", "fidelity")
MIRROR_DIR = aiw_profile.inbox_dir(DEFAULT_PROFILE, root=ROOT) / "mattermost-mirror"
LEGACY_LATEST = aiw_profile.inbox_dir(DEFAULT_PROFILE, root=ROOT) / "mattermost-latest.md"
LEGACY_GENERATED = ROOT / "scripts" / "mattermost" / "generated" / "mattermost_context.jsonl"
LOCAL_ENV = Path(__file__).resolve().parent / ".env"
def configure_profile_paths(profile: str) -> None:
global MIRROR_DIR, LEGACY_LATEST
inbox = aiw_profile.inbox_dir(profile, root=ROOT)
MIRROR_DIR = inbox / "mattermost-mirror"
LEGACY_LATEST = inbox / "mattermost-latest.md"
def load_local_env(path: Path = LOCAL_ENV) -> None:
"""Load simple KEY=VALUE pairs from the connector-local .env.
@@ -201,6 +213,7 @@ def main() -> None:
load_local_env()
parser = argparse.ArgumentParser()
parser.add_argument("--profile", default=DEFAULT_PROFILE)
parser.add_argument("--mode", choices=["latest", "previous-workday", "standup", "focused"], default="latest")
parser.add_argument("--today", default="")
parser.add_argument(
@@ -210,6 +223,7 @@ def main() -> None:
)
parser.add_argument("--limit", type=int, default=80, help="Max records per section; use 0 for no limit.")
args = parser.parse_args()
configure_profile_paths(args.profile)
channels = parse_channels(args.channels or None)
limit = args.limit if args.limit > 0 else None

View File

@@ -11,7 +11,9 @@ if [ -f "$SCRIPT_DIR/.env" ]; then
set +a
fi
export MATTERMOST_MIRROR_DIR="${MATTERMOST_MIRROR_DIR:-$WORKSPACE_ROOT/ai/inbox/mattermost-mirror}"
PROFILE="${AIW_PROJECT_PROFILE:-fidelity}"
PROFILE_INBOX_DIR="$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path inbox --profile "$PROFILE")"
export MATTERMOST_MIRROR_DIR="${MATTERMOST_MIRROR_DIR:-$PROFILE_INBOX_DIR/mattermost-mirror}"
export MATTERMOST_MIRROR_LISTEN_HOST="${MATTERMOST_MIRROR_LISTEN_HOST:-127.0.0.1}"
export MATTERMOST_MIRROR_LISTEN_PORT="${MATTERMOST_MIRROR_LISTEN_PORT:-8080}"

View File

@@ -3,7 +3,7 @@
This directory contains the workspace-local Mattermost extractor used by OpenCode to refresh communication context.
The preferred live Mattermost evidence source is now the proxy mirror under
`ai/inbox/mattermost-mirror/` when it is running. This legacy extractor remains
the active profile inbox, `<profile inbox>/mattermost-mirror/`, when it is running. This legacy extractor remains
the fallback and explicit refresh path for commands that need a fresh pull from
the Mattermost API.
@@ -72,7 +72,7 @@ Generic workspace variables are preferred for reusable projects:
- `AIW_MATTERMOST_SYNC_CMD`
- `AIW_MATTERMOST_SYNC_INTERVAL_MINUTES`
The older `FIDELITY_*` variables remain supported for this project profile.
Profile-specific compatibility variables may exist for older project setups, but new reusable workflows should prefer the generic `AIW_*` variables.
Previous workday mode for standups:

View File

@@ -45,7 +45,7 @@ python3 scripts/mcp/aiw-context-mcp/server.py --transport stdio
- `memory_hybrid_search`
- `photos_latest`
All tools are read-only. Mattermost tools read `ai/inbox/mattermost-mirror/`; photo tools list local Photo Inbox files without embedding image data; project tools read canonical Markdown under `project-knowledge/`.
All tools are read-only. Mattermost tools read the active profile's `mattermost-mirror/` inbox; photo tools list local Photo Inbox files without embedding image data; project tools read canonical Markdown from the profile's configured `knowledge_dir`.
`memory_hybrid_search` reads the derived local index built by:
@@ -53,7 +53,7 @@ All tools are read-only. Mattermost tools read `ai/inbox/mattermost-mirror/`; ph
python3 scripts/aiw/indexer.py build --profile fidelity
```
If the index is missing, it falls back to bounded live Markdown search over `project-knowledge/`. The index is not canonical memory; `project-knowledge/` remains the source of truth.
If the index is missing, it falls back to bounded live Markdown search over the profile's configured knowledge directory. The index is not canonical memory; Markdown remains the source of truth.
Mattermost latest/date/standup tools filter to the active profile's context channels by default. For Fidelity, that list lives in `profiles/fidelity/context-sources.json`. Pass explicit `channels` to override the profile list, or `include_all_channels: true` when broad unfiltered mirror evidence is intentionally needed.

View File

@@ -2,12 +2,13 @@
This directory exposes a project-agnostic interface for canonical project knowledge.
The current implementation uses Markdown files under `project-knowledge/` and can optionally delegate to the Obsidian CLI when it is available. The agent should depend on this memory interface, not on Obsidian-specific behavior, so the backing tool can be replaced later.
The current implementation resolves the canonical Markdown directory from `profiles/<profile>/workspace.json` and can optionally delegate to the Obsidian CLI when it is available. The agent should depend on this memory interface, not on Obsidian-specific behavior, so the backing tool can be replaced later.
## Backend Model
- `AIW_PROJECT_KNOWLEDGE_DIR` points to the canonical Markdown project knowledge directory.
- `AIW_MEMORY_VAULT_DIR` and `AIW_OBSIDIAN_VAULT_DIR` are transition aliases.
- `AIW_PROJECT_PROFILE` selects the profile, defaulting to `fidelity` in this repository.
- `profiles/<profile>/workspace.json` defines the canonical Markdown project knowledge directory.
- `AIW_PROJECT_KNOWLEDGE_DIR` can override the resolved directory for local experiments.
- `AIW_MEMORY_BACKEND` defaults to `auto`.
- `auto` uses Obsidian CLI when it is useful and available, then falls back to direct Markdown operations.
- `files` forces direct Markdown operations.

View File

@@ -4,7 +4,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PROJECT_KNOWLEDGE_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}"
PROFILE="${AIW_PROJECT_PROFILE:-fidelity}"
PROJECT_KNOWLEDGE_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}"
VAULT_DIR="$PROJECT_KNOWLEDGE_DIR"
BACKEND="${AIW_MEMORY_BACKEND:-auto}"
OBSIDIAN_CLI="$WORKSPACE_ROOT/scripts/obsidian/cli.sh"

View File

@@ -4,7 +4,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}"
PROFILE="${AIW_PROJECT_PROFILE:-fidelity}"
VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}"
if ! command -v obsidian >/dev/null 2>&1; then
echo "obsidian CLI is not installed or not in PATH" >&2

View File

@@ -4,7 +4,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}"
PROFILE="${AIW_PROJECT_PROFILE:-fidelity}"
VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}"
VAULT_NAME="${AIW_OBSIDIAN_VAULT_NAME:-$(basename "$VAULT_DIR")}"
URI="$("$SCRIPT_DIR/uri.sh" daily "vault=$VAULT_NAME")"

View File

@@ -4,7 +4,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}"
PROFILE="${AIW_PROJECT_PROFILE:-fidelity}"
VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}"
VAULT_NAME="${AIW_OBSIDIAN_VAULT_NAME:-$(basename "$VAULT_DIR")}"
if [[ $# -lt 1 ]]; then

View File

@@ -4,7 +4,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}"
PROFILE="${AIW_PROJECT_PROFILE:-fidelity}"
VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}"
VAULT_NAME="${AIW_OBSIDIAN_VAULT_NAME:-$(basename "$VAULT_DIR")}"
if [[ $# -lt 1 ]]; then