diff --git a/.github/basic-memory/config.yml b/.github/basic-memory/config.yml new file mode 100644 index 00000000..3f0b7bc6 --- /dev/null +++ b/.github/basic-memory/config.yml @@ -0,0 +1,7 @@ +project: dev +workspace: basic-memory-7020de4e925843c68c9056c60d101d9e +deploy_workflows: +- Deploy Production +production_environments: +- production +note_folder: project-updates/github/{owner}/{repo} diff --git a/.github/basic-memory/memory-ci-capture.md b/.github/basic-memory/memory-ci-capture.md new file mode 100644 index 00000000..7f3cde32 --- /dev/null +++ b/.github/basic-memory/memory-ci-capture.md @@ -0,0 +1,27 @@ +# Memory CI Capture + +You turn GitHub delivery context into a concise project update synthesis for +Basic Memory. GitHub records the mechanics. Basic Memory remembers what changed +and why. + +## Inputs + +- Read `.github/basic-memory/project-update-context.json`. +- Treat GitHub payload fields as immutable facts. +- Do not invent tests, deployment status, issues, or user impact. + +## Output + +Return only JSON that matches the provided AgentSynthesis schema: + +- `summary`: what changed. +- `why_it_matters`: why this project update matters for future humans and agents. +- `user_facing_changes`: visible behavior or product changes. +- `internal_changes`: implementation, infrastructure, or operational changes. +- `verification`: checks, tests, deploy evidence, or explicit unknowns. +- `follow_ups`: concrete remaining work only. +- `decision_candidates`: explicit product or architecture decisions only. +- `task_candidates`: concrete future tasks only. + +Prefer source links and grounded phrasing. This is project memory, not marketing +copy and not a commit-by-commit changelog. diff --git a/.github/workflows/basic-memory.yml b/.github/workflows/basic-memory.yml new file mode 100644 index 00000000..0af542b4 --- /dev/null +++ b/.github/workflows/basic-memory.yml @@ -0,0 +1,73 @@ +name: Basic Memory Project Updates + +"on": + pull_request: + types: [closed] + workflow_run: + workflows: ["Deploy Production"] + types: [completed] + +jobs: + project-update: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install Basic Memory + run: | + python -m pip install --upgrade pip + pip install basic-memory + + - name: Collect project update context + id: collect + run: | + bm ci collect \ + --config .github/basic-memory/config.yml \ + --output .github/basic-memory/project-update-context.json + + - name: Stop when event is not eligible + if: steps.collect.outputs.eligible != 'true' + run: | + echo "Auto BM skipped: ${{ steps.collect.outputs.skip_reason }}" + + - name: Write Codex output schema + if: steps.collect.outputs.eligible == 'true' + run: | + bm ci agent-schema --output "${{ runner.temp }}/agent-synthesis.schema.json" + + - name: Synthesize project update with Codex + if: steps.collect.outputs.eligible == 'true' + uses: openai/codex-action@v1 + with: + openai-api-key: ${{ secrets.OPENAI_API_KEY }} + prompt-file: .github/basic-memory/memory-ci-capture.md + output-file: ${{ runner.temp }}/agent-synthesis.json + output-schema-file: ${{ runner.temp }}/agent-synthesis.schema.json + sandbox: read-only + safety-strategy: drop-sudo + + - name: Publish project update + if: steps.collect.outputs.eligible == 'true' + env: + BASIC_MEMORY_CLOUD_API_KEY: ${{ secrets.BASIC_MEMORY_API_KEY }} + BASIC_MEMORY_CI_CLOUD_HOST: ${{ vars.BASIC_MEMORY_CLOUD_HOST }} + run: | + if [ -n "$BASIC_MEMORY_CI_CLOUD_HOST" ]; then + export BASIC_MEMORY_CLOUD_HOST="$BASIC_MEMORY_CI_CLOUD_HOST" + fi + bm ci publish \ + --cloud \ + --config .github/basic-memory/config.yml \ + --context .github/basic-memory/project-update-context.json \ + --synthesis "${{ runner.temp }}/agent-synthesis.json" diff --git a/src/basic_memory/ci/project_updates.py b/src/basic_memory/ci/project_updates.py index 8b0e2220..7269b597 100644 --- a/src/basic_memory/ci/project_updates.py +++ b/src/basic_memory/ci/project_updates.py @@ -489,7 +489,7 @@ def render_workflow(config: ProjectUpdateConfig) -> str: effort_line = f" effort: {config.codex_effort}\n" if config.codex_effort else "" return f"""name: Basic Memory Project Updates -on: +"on": pull_request: types: [closed] workflow_run: @@ -527,7 +527,8 @@ jobs: - name: Stop when event is not eligible if: steps.collect.outputs.eligible != 'true' - run: echo "Auto BM skipped: ${{{{ steps.collect.outputs.skip_reason }}}}" + run: | + echo "Auto BM skipped: ${{{{ steps.collect.outputs.skip_reason }}}}" - name: Write Codex output schema if: steps.collect.outputs.eligible == 'true' diff --git a/tests/ci/test_project_updates.py b/tests/ci/test_project_updates.py index dc407db6..ae09ee86 100644 --- a/tests/ci/test_project_updates.py +++ b/tests/ci/test_project_updates.py @@ -2,6 +2,7 @@ import json from pathlib import Path import pytest +import yaml from pydantic import ValidationError from basic_memory.ci.project_updates import ( @@ -399,6 +400,16 @@ def test_render_workflow_invokes_codex_read_only_without_basic_memory_secret() - assert "BASIC_MEMORY_API_KEY" not in codex_step +def test_render_workflow_outputs_valid_github_actions_yaml() -> None: + workflow = render_workflow(ProjectUpdateConfig(project="team-memory")) + + parsed = yaml.safe_load(workflow) + + assert isinstance(parsed, dict) + assert parsed["on"]["pull_request"]["types"] == ["closed"] + assert parsed["on"]["workflow_run"]["types"] == ["completed"] + + def test_render_capture_prompt_uses_workspace_context_path() -> None: prompt = render_capture_prompt()