sys-buddy 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. sys_buddy-1.2.0/.dockerignore +37 -0
  2. sys_buddy-1.2.0/.env.example +12 -0
  3. sys_buddy-1.2.0/.github/CODEOWNERS +6 -0
  4. sys_buddy-1.2.0/.github/pull_request_template.md +11 -0
  5. sys_buddy-1.2.0/.github/workflows/ci.yml +46 -0
  6. sys_buddy-1.2.0/.github/workflows/pr-title.yml +38 -0
  7. sys_buddy-1.2.0/.github/workflows/release-please.yml +79 -0
  8. sys_buddy-1.2.0/.gitignore +39 -0
  9. sys_buddy-1.2.0/.mcp.json +8 -0
  10. sys_buddy-1.2.0/.release-please-manifest.json +3 -0
  11. sys_buddy-1.2.0/CHANGELOG.md +166 -0
  12. sys_buddy-1.2.0/CLAUDE.md +37 -0
  13. sys_buddy-1.2.0/CONTRIBUTING.md +220 -0
  14. sys_buddy-1.2.0/DECISIONS.md +122 -0
  15. sys_buddy-1.2.0/DOGFOOD.md +68 -0
  16. sys_buddy-1.2.0/Dockerfile +38 -0
  17. sys_buddy-1.2.0/KICKOFF.md +69 -0
  18. sys_buddy-1.2.0/LICENSE +21 -0
  19. sys_buddy-1.2.0/Makefile +67 -0
  20. sys_buddy-1.2.0/OSS.md +67 -0
  21. sys_buddy-1.2.0/PKG-INFO +250 -0
  22. sys_buddy-1.2.0/README.md +240 -0
  23. sys_buddy-1.2.0/SPEC.md +539 -0
  24. sys_buddy-1.2.0/design/NOTE.md +24 -0
  25. sys_buddy-1.2.0/design/README.md +22 -0
  26. sys_buddy-1.2.0/design/project/.thumbnail +0 -0
  27. sys_buddy-1.2.0/design/project/Sys-Buddy Dashboard.dc.html +839 -0
  28. sys_buddy-1.2.0/design/project/support.js +1768 -0
  29. sys_buddy-1.2.0/docker-compose.yml +24 -0
  30. sys_buddy-1.2.0/features/v1.1.0/README.md +264 -0
  31. sys_buddy-1.2.0/features/v1.1.0/screens/01-task-list-rollup-light.png +0 -0
  32. sys_buddy-1.2.0/features/v1.1.0/screens/02-todo-task-panel-light.png +0 -0
  33. sys_buddy-1.2.0/features/v1.1.0/screens/03-todo-task-list-light.png +0 -0
  34. sys_buddy-1.2.0/features/v1.1.0/screens/04-todo-detail-host-drop-light.png +0 -0
  35. sys_buddy-1.2.0/features/v1.1.0/screens/05-todo-task-dark.png +0 -0
  36. sys_buddy-1.2.0/features/v1.1.0/screens/06-todo-contract-mini-stepper-dark.png +0 -0
  37. sys_buddy-1.2.0/features/v1.1.0/screens/07-listening-presence-dark.png +0 -0
  38. sys_buddy-1.2.0/features/v1.1.0/screens/08-no-todo-task-unchanged-light.png +0 -0
  39. sys_buddy-1.2.0/features/v1.1.0/seed_demo.py +147 -0
  40. sys_buddy-1.2.0/pyproject.toml +31 -0
  41. sys_buddy-1.2.0/reference/AGENT_BUS_GUIDE.md +221 -0
  42. sys_buddy-1.2.0/reference/NOTE.md +34 -0
  43. sys_buddy-1.2.0/reference/SETUP.md +58 -0
  44. sys_buddy-1.2.0/reference/agent_bus.py +136 -0
  45. sys_buddy-1.2.0/release-please-config.json +22 -0
  46. sys_buddy-1.2.0/releases/v1.0.0.md +38 -0
  47. sys_buddy-1.2.0/releases/v1.0.1.md +23 -0
  48. sys_buddy-1.2.0/releases/v1.1.0.md +145 -0
  49. sys_buddy-1.2.0/releases/v1.1.1.md +40 -0
  50. sys_buddy-1.2.0/sessions/2026-07-16-sys-buddy-build.md +49 -0
  51. sys_buddy-1.2.0/sessions/2026-07-17-sys-buddy-build.md +50 -0
  52. sys_buddy-1.2.0/sessions/2026-07-18-gui-mvp.md +43 -0
  53. sys_buddy-1.2.0/sessions/2026-07-18-security-hardening.md +56 -0
  54. sys_buddy-1.2.0/sessions/2026-07-19-collab-and-role-redesign.md +59 -0
  55. sys_buddy-1.2.0/sessions/2026-07-19-role-prompt-redesign.md +83 -0
  56. sys_buddy-1.2.0/sessions/2026-07-20-browser-buddy-join-page.md +89 -0
  57. sys_buddy-1.2.0/sessions/2026-07-20-preflight-stepper-negotiations.md +104 -0
  58. sys_buddy-1.2.0/sessions/2026-07-20-repair-safe-mcp-and-loader-page.md +82 -0
  59. sys_buddy-1.2.0/sessions/2026-07-23-get-contract-shows-proposal.md +79 -0
  60. sys_buddy-1.2.0/sessions/role-prompt-redesign-checklist.md +94 -0
  61. sys_buddy-1.2.0/src/sys_buddy/__init__.py +22 -0
  62. sys_buddy-1.2.0/src/sys_buddy/admin.py +358 -0
  63. sys_buddy-1.2.0/src/sys_buddy/api.py +844 -0
  64. sys_buddy-1.2.0/src/sys_buddy/audit.py +34 -0
  65. sys_buddy-1.2.0/src/sys_buddy/cli.py +419 -0
  66. sys_buddy-1.2.0/src/sys_buddy/config.py +68 -0
  67. sys_buddy-1.2.0/src/sys_buddy/contracts.py +200 -0
  68. sys_buddy-1.2.0/src/sys_buddy/db.py +310 -0
  69. sys_buddy-1.2.0/src/sys_buddy/gui.py +270 -0
  70. sys_buddy-1.2.0/src/sys_buddy/gui_app.html +1389 -0
  71. sys_buddy-1.2.0/src/sys_buddy/http_middleware.py +94 -0
  72. sys_buddy-1.2.0/src/sys_buddy/identity.py +115 -0
  73. sys_buddy-1.2.0/src/sys_buddy/join.html +456 -0
  74. sys_buddy-1.2.0/src/sys_buddy/middleware.py +137 -0
  75. sys_buddy-1.2.0/src/sys_buddy/onboarding.py +509 -0
  76. sys_buddy-1.2.0/src/sys_buddy/pairing.py +293 -0
  77. sys_buddy-1.2.0/src/sys_buddy/readiness.py +302 -0
  78. sys_buddy-1.2.0/src/sys_buddy/rules.py +107 -0
  79. sys_buddy-1.2.0/src/sys_buddy/server.py +61 -0
  80. sys_buddy-1.2.0/src/sys_buddy/service.py +467 -0
  81. sys_buddy-1.2.0/src/sys_buddy/slack.py +55 -0
  82. sys_buddy-1.2.0/src/sys_buddy/state.py +1364 -0
  83. sys_buddy-1.2.0/src/sys_buddy/todos.py +730 -0
  84. sys_buddy-1.2.0/src/sys_buddy/tools.py +722 -0
  85. sys_buddy-1.2.0/src/sys_buddy/ui.html +1346 -0
  86. sys_buddy-1.2.0/src/sys_buddy/updates.py +110 -0
  87. sys_buddy-1.2.0/tests/conftest.py +56 -0
  88. sys_buddy-1.2.0/tests/test_admin.py +64 -0
  89. sys_buddy-1.2.0/tests/test_api.py +524 -0
  90. sys_buddy-1.2.0/tests/test_cli.py +64 -0
  91. sys_buddy-1.2.0/tests/test_contracts.py +206 -0
  92. sys_buddy-1.2.0/tests/test_debug.py +88 -0
  93. sys_buddy-1.2.0/tests/test_directed.py +67 -0
  94. sys_buddy-1.2.0/tests/test_identity.py +95 -0
  95. sys_buddy-1.2.0/tests/test_messaging.py +233 -0
  96. sys_buddy-1.2.0/tests/test_onboarding.py +547 -0
  97. sys_buddy-1.2.0/tests/test_pairing.py +336 -0
  98. sys_buddy-1.2.0/tests/test_presence.py +253 -0
  99. sys_buddy-1.2.0/tests/test_readiness.py +182 -0
  100. sys_buddy-1.2.0/tests/test_security_hardening.py +540 -0
  101. sys_buddy-1.2.0/tests/test_server.py +47 -0
  102. sys_buddy-1.2.0/tests/test_slack.py +69 -0
  103. sys_buddy-1.2.0/tests/test_state.py +686 -0
  104. sys_buddy-1.2.0/tests/test_todos_api.py +442 -0
  105. sys_buddy-1.2.0/tests/test_todos_cli.py +259 -0
  106. sys_buddy-1.2.0/tests/test_todos_state.py +569 -0
  107. sys_buddy-1.2.0/tests/test_todos_tools.py +194 -0
  108. sys_buddy-1.2.0/tests/test_updates.py +101 -0
  109. sys_buddy-1.2.0/tests/test_version.py +33 -0
  110. sys_buddy-1.2.0/uv.lock +1732 -0
  111. sys_buddy-1.2.0/v2.md +526 -0
@@ -0,0 +1,37 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ __pycache__
5
+ *.pyc
6
+ .pytest_cache
7
+ *.egg-info
8
+ .DS_Store
9
+
10
+ # runtime db (mounted via volume, not baked into the image)
11
+ *.db
12
+ *.db-wal
13
+ *.db-shm
14
+
15
+ # dev/editor
16
+ .vscode
17
+ .idea
18
+ .claude
19
+
20
+ # docs & non-runtime project material
21
+ sessions
22
+ design
23
+ reference
24
+ CONTRIBUTING.md
25
+ DOGFOOD.md
26
+ DECISIONS.md
27
+ KICKOFF.md
28
+ SPEC.md
29
+ v2.md
30
+
31
+ # already produced by CI, not needed to build the image
32
+ tests
33
+ docker-compose.yml
34
+ Dockerfile
35
+ .dockerignore
36
+ .env
37
+ .env.example
@@ -0,0 +1,12 @@
1
+ # Copy to .env and fill in for `docker compose up`. All are optional for local testing.
2
+
3
+ # Public https origin (e.g. your tunnel) used to build pairing/invite links.
4
+ # Required for a real "serve" deployment — remote mode enforces auth over it.
5
+ SYS_BUDDY_PUBLIC_URL=
6
+
7
+ # Agent-token lifetime in seconds. Defaults to no expiry, or 24h once
8
+ # SYS_BUDDY_PUBLIC_URL is set.
9
+ SYS_BUDDY_TOKEN_TTL=
10
+
11
+ # Optional Slack webhook for broker notifications.
12
+ SLACK_WEBHOOK_URL=
@@ -0,0 +1,6 @@
1
+ # Code owners for sys-buddy.
2
+ # Every pull request touching these paths requires review from an owner listed
3
+ # here before it can be merged. The catch-all below makes @tooney92 the required
4
+ # reviewer for ALL changes — combined with branch protection on `main`, nothing
5
+ # reaches main without the owner's approval.
6
+ * @tooney92
@@ -0,0 +1,11 @@
1
+ <!-- Thanks for contributing to sys-buddy! Fill this in so the owner can review quickly. -->
2
+
3
+ ## What & why
4
+ <!-- What does this PR change, and what problem does it solve? -->
5
+
6
+ ## How it was tested
7
+ <!-- e.g. `uv run pytest -q` output; any manual/Playwright checks against the local broker -->
8
+ - [ ] `uv run pytest -q` passes locally
9
+
10
+ ## Notes for the reviewer
11
+ <!-- Anything the maintainer should look at closely, trade-offs, follow-ups. -->
@@ -0,0 +1,46 @@
1
+ name: CI
2
+
3
+ # Runs on every PR and on pushes to main. Making these checks *required* in branch
4
+ # protection is what turns them from advisory into a merge gate — see the PR that adds
5
+ # this file for the one-time settings step.
6
+ on:
7
+ pull_request:
8
+ push:
9
+ branches: [main]
10
+
11
+ jobs:
12
+ test:
13
+ name: pytest
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v5
18
+ with:
19
+ python-version: "3.11"
20
+ - name: Install dependencies
21
+ run: uv sync --frozen
22
+ - name: Run the suite
23
+ run: uv run pytest -q
24
+
25
+ version-guard:
26
+ name: version not hand-edited
27
+ # Only on PRs, and skip release-please's own release PR — that one legitimately
28
+ # bumps the version. Everyone else must leave the version to the release pipeline.
29
+ if: github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release-please')
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ with:
34
+ fetch-depth: 0
35
+ - name: Fail if the version was bumped by hand
36
+ run: |
37
+ base="origin/${{ github.base_ref }}"
38
+ if git diff "$base"...HEAD -- pyproject.toml | grep -qE '^\+[[:space:]]*version[[:space:]]*='; then
39
+ echo "::error::Don't bump the version in a PR — releases are automated (see CONTRIBUTING.md §8). Remove the pyproject.toml version change; the release bot sets it."
40
+ exit 1
41
+ fi
42
+ if git diff "$base"...HEAD -- src/sys_buddy/__init__.py | grep -qE '^\+.*__version__[[:space:]]*='; then
43
+ echo "::error::Don't set __version__ by hand — it derives from the package metadata."
44
+ exit 1
45
+ fi
46
+ echo "OK — no manual version edit."
@@ -0,0 +1,38 @@
1
+ name: PR Title
2
+
3
+ # We squash-merge, so the PR TITLE becomes the commit on main — and release-please
4
+ # reads those commits to compute the version and changelog. This check refuses a PR
5
+ # whose title isn't a valid Conventional Commit, so the release machinery never sees
6
+ # a malformed message. See CONTRIBUTING.md §8 for the types.
7
+ on:
8
+ pull_request:
9
+ types: [opened, edited, synchronize, reopened]
10
+
11
+ permissions:
12
+ pull-requests: read
13
+
14
+ jobs:
15
+ lint-title:
16
+ name: conventional PR title
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: amannn/action-semantic-pull-request@v5
20
+ env:
21
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22
+ with:
23
+ # Must match the type→bump table in CONTRIBUTING.md §8.
24
+ types: |
25
+ feat
26
+ fix
27
+ docs
28
+ chore
29
+ refactor
30
+ test
31
+ ci
32
+ # Subject after the "type:" — just require it to be non-empty and not
33
+ # Sentence-cased-into-a-Title, keeping titles terse and imperative.
34
+ requireScope: false
35
+ subjectPattern: ^(?![A-Z]).+$
36
+ subjectPatternError: |
37
+ The subject after "type:" should start lower-case and be a short
38
+ imperative summary, e.g. "feat: add docker packaging".
@@ -0,0 +1,79 @@
1
+ name: Release
2
+
3
+ # One workflow, three jobs. release-please keeps a Release PR open, auto-versioned from
4
+ # Conventional-Commit history; merging that PR cuts a GitHub release + tag. The publish
5
+ # jobs run in the SAME workflow, gated on `release_created`, rather than in a separate
6
+ # workflow triggered by the release event — a release created with the built-in
7
+ # GITHUB_TOKEN does not trigger other workflows, so chaining here is what makes publish
8
+ # actually fire.
9
+ on:
10
+ push:
11
+ branches: [main]
12
+
13
+ permissions:
14
+ contents: write
15
+ pull-requests: write
16
+
17
+ jobs:
18
+ release-please:
19
+ name: release PR / tag
20
+ runs-on: ubuntu-latest
21
+ outputs:
22
+ release_created: ${{ steps.release.outputs.release_created }}
23
+ tag_name: ${{ steps.release.outputs.tag_name }}
24
+ version: ${{ steps.release.outputs.version }}
25
+ steps:
26
+ - uses: googleapis/release-please-action@v4
27
+ id: release
28
+ with:
29
+ token: ${{ secrets.GITHUB_TOKEN }}
30
+ config-file: release-please-config.json
31
+ manifest-file: .release-please-manifest.json
32
+
33
+ publish-pypi:
34
+ name: publish to PyPI
35
+ needs: release-please
36
+ if: ${{ needs.release-please.outputs.release_created }}
37
+ runs-on: ubuntu-latest
38
+ permissions:
39
+ contents: read
40
+ id-token: write # OIDC for PyPI Trusted Publishing — no stored token/secret
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ with:
44
+ ref: ${{ needs.release-please.outputs.tag_name }}
45
+ - uses: astral-sh/setup-uv@v5
46
+ with:
47
+ python-version: "3.11"
48
+ - name: Build sdist + wheel
49
+ run: uv build
50
+ - name: Publish to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
52
+ # No password: — Trusted Publishing authenticates via the OIDC token above.
53
+
54
+ publish-docker:
55
+ name: publish image to ghcr.io
56
+ needs: release-please
57
+ if: ${{ needs.release-please.outputs.release_created }}
58
+ runs-on: ubuntu-latest
59
+ permissions:
60
+ contents: read
61
+ packages: write # push to ghcr.io — uses the built-in token, no account needed
62
+ steps:
63
+ - uses: actions/checkout@v4
64
+ with:
65
+ ref: ${{ needs.release-please.outputs.tag_name }}
66
+ - name: Log in to ghcr.io
67
+ uses: docker/login-action@v3
68
+ with:
69
+ registry: ghcr.io
70
+ username: ${{ github.actor }}
71
+ password: ${{ secrets.GITHUB_TOKEN }}
72
+ - name: Build and push
73
+ uses: docker/build-push-action@v6
74
+ with:
75
+ context: .
76
+ push: true
77
+ tags: |
78
+ ghcr.io/${{ github.repository }}:${{ needs.release-please.outputs.version }}
79
+ ghcr.io/${{ github.repository }}:latest
@@ -0,0 +1,39 @@
1
+ # OS
2
+ .DS_Store
3
+ Thumbs.db
4
+
5
+ # Editor
6
+ .vscode/
7
+ .idea/
8
+ *.swp
9
+
10
+ # Logs
11
+ *.log
12
+
13
+ # Environment
14
+ .env
15
+ .env.local
16
+ .env.*.local
17
+
18
+ # Dependencies / build (add stack-specific entries as the project grows)
19
+ node_modules/
20
+ dist/
21
+ build/
22
+
23
+ # Claude Code — local per-user settings (approvals, allowlist). .mcp.json IS tracked.
24
+ .claude/settings.local.json
25
+
26
+ # Python
27
+ .venv/
28
+ __pycache__/
29
+ *.py[cod]
30
+ *.egg-info/
31
+ .pytest_cache/
32
+
33
+ # sys-buddy runtime database
34
+ *.db
35
+ *.db-wal
36
+ *.db-shm
37
+
38
+ # Playwright MCP scratch output (screenshots/snapshots from browser-driven checks)
39
+ .playwright-mcp/
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "playwright": {
4
+ "command": "npx",
5
+ "args": ["-y", "@playwright/mcp@0.0.77"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.2.0"
3
+ }
@@ -0,0 +1,166 @@
1
+ # Changelog
2
+
3
+ All notable changes to **sys-buddy** are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/), and the project uses
5
+ [Semantic Versioning](https://semver.org/) — `MAJOR.MINOR.PATCH`:
6
+
7
+ - **MAJOR** — incompatible changes to the tool/wire contract or agent-visible behavior.
8
+ - **MINOR** — new, backwards-compatible capability.
9
+ - **PATCH** — backwards-compatible fixes.
10
+
11
+ Each release is also git-tagged `vX.Y.Z` and has a fuller note in `releases/vX.Y.Z.md`.
12
+
13
+ ## [1.2.0](https://github.com/tooney92/sys-buddy/compare/v1.1.1...v1.2.0) (2026-07-25)
14
+
15
+
16
+ ### Added
17
+
18
+ * version awareness — /api/version, single-sourced version, GUI banner ([2506b8a](https://github.com/tooney92/sys-buddy/commit/2506b8a26a107061585600ac7a116d0ffd87f3b7))
19
+ * version awareness (/api/version + GUI update banner) ([4e23c5b](https://github.com/tooney92/sys-buddy/commit/4e23c5b1eaa5b94cd5bab07414719e22368ffec9))
20
+
21
+ ## [Unreleased]
22
+
23
+ ### Added
24
+ - **Version awareness in the desktop app.** A new unauthenticated `GET /api/version`
25
+ reports the version the broker process is actually *running*. The desktop app compares
26
+ three numbers — installed (on disk), running (from the endpoint), and, if the user opts
27
+ in, the latest published GitHub release — and shows a banner when they disagree: a
28
+ "restart needed" warning when the broker is serving older code than what's installed
29
+ (the drift that hid a migration in 1.1.0/1.1.1), or an "update available" note with the
30
+ release notes inline when GitHub has something newer. The GitHub check is strictly
31
+ opt-in — the app makes no external request unless the toggle is on.
32
+ - **Version is single-sourced.** `sys_buddy.__version__` now derives from the installed
33
+ package metadata rather than a second hand-edited literal, so `pyproject.toml` is the
34
+ one place to bump it; a test fails the build if the two ever drift.
35
+ - **Docker packaging.** A multi-stage root `Dockerfile` (built with `uv`) and
36
+ `docker-compose.yml` run the broker as a single container, non-root, with a persisted
37
+ `sys-buddy-data` volume for the SQLite DB. Defaults to `serve` (auth-enforced) — never
38
+ `local`, which is unauthenticated and must not be reachable off-box. A `Makefile`
39
+ wraps the common flows and `.env.example` documents the tunnel setup for remote use.
40
+
41
+ ### Backlog
42
+ - Tracked in `v2.md` — stronger auth (mTLS / OAuth 2.1); image/screenshot attachments;
43
+ non-HTTP / `interface_type` contracts; auto-revoke on completion + reopenable tasks +
44
+ token timer; multi-process presence & wait-cap accounting.
45
+
46
+ ## [1.1.1] — 2026-07-24
47
+
48
+ ### Fixed
49
+ - **Broker crashed on startup against a database created before 1.1.0.** The index on
50
+ `contracts(todo_id)` was declared in the schema block, which runs *before* the
51
+ migration that adds the column. On a fresh database the table is created with the
52
+ column so it worked; on an existing (pre-todos) database the `CREATE TABLE IF NOT
53
+ EXISTS` was a no-op and the index creation raised `no such column: todo_id`, aborting
54
+ boot. The index is now created after the migrations, once the column is guaranteed to
55
+ exist. Regression test added exercising an existing pre-todos database — the whole
56
+ test suite used fresh databases, which is why this shipped in 1.1.0.
57
+
58
+ ## [1.1.0] — 2026-07-24
59
+
60
+ Todos: a task can carry several deliverables instead of exactly one. Additive — a task
61
+ with no todos behaves and renders exactly as it did in 1.0.1.
62
+
63
+ ### Added
64
+ - **Todos — several deliverables under one task.** Each todo has its own contract chain
65
+ and its own `proposed → locked → built → verified` march. Agent-proposed and
66
+ peer-accepted, with no human approval gate (the same authority `propose_contract`
67
+ already had). Proposing IS the creator's consent, so you cannot propose work that
68
+ binds only other people; accepting agrees on WHAT, while the contract on that todo is
69
+ a separate, later agreement about HOW. Declines are recorded as a list beside the
70
+ acceptances (with a reason) rather than a status a state machine would have to unwind;
71
+ `repropose_todo` issues a new version and resets every acceptance, so nobody is held
72
+ to a scope they did not read. Dropping is mutual — every named party consents — with
73
+ a host override via `sys-buddy todo drop`.
74
+ - **Seats are not participants.** A todo reuses the task's existing seats and names
75
+ which of them it binds. A seat that is not a party can read the todo, but is not bound
76
+ by it, is not in its contract's quorum, and does not block it.
77
+ - **Todos on the dashboard.** The stepper TRUNCATES to three nodes (`open`,
78
+ `pre-flight assessments`, `todos`) when a task has todos, the last carrying a progress
79
+ bar and the `⚠ N awaiting acceptance` count; the five later phases move INTO each todo
80
+ as a mini-stepper. The right column becomes the todo LIST, pending first with a `⚠`;
81
+ selecting a todo swaps the panel to that todo's own contract card. ONE message thread
82
+ for the task, with a `⟨todo⟩` chip marking which deliverable a message belongs to. The
83
+ task-list row gains the same rollup (`2/6 verified ⚠1`).
84
+ - **Always-listening presence.** An agent parked in `wait_for_message` shows a live
85
+ pulsing dot and a `listening — 42m` streak on the dashboard. Stored as an EXPIRY, not
86
+ a boolean, so a broker that dies with agents parked cannot leave rows claiming to be
87
+ listening forever.
88
+ - **`releases/` + `features/` write-ups.** `features/v1.1.0/` carries the release notes,
89
+ eight dashboard screenshots (light and dark), and `seed_demo.py`, which reproduces
90
+ every screen locally in three commands.
91
+
92
+ ### Changed
93
+ - **A task's state is now DERIVED from its todos, not set by an agent.** Agents no
94
+ longer call `_transition` on a task that has todos. This is agent-visible behaviour,
95
+ and it is the reason todos was worth the cost: a rollup cannot disagree with its parts
96
+ the way an agent-set task state can drift from them. `verified` requires ALL live
97
+ todos; `stuck` is never derived, because one stuck deliverable must not freeze the
98
+ other five.
99
+ - **Contract lock is pushed, not polled.** When a contract locks, the broker notifies
100
+ the roles that already signed instead of leaving them to poll.
101
+ - **`staging_url` is collected at host setup**, with validation strictness keyed to the
102
+ task's connectivity and `localhost` permitted for same-machine tasks.
103
+ - `messages.todo_id` is stored at post time; the dashboard's `⟨todo⟩` chip reads the
104
+ column rather than scraping "todo #N" out of prose, with the regex kept only as a
105
+ fallback for rows written before the column existed.
106
+ - Decision D11 recorded: the dashboard never issues commands (read-only; it surfaces
107
+ state and tells the human what to type). The host's `[Drop todo]` affordance therefore
108
+ prints the CLI line to type instead of mutating anything.
109
+ - `CONTRIBUTING.md` expanded into a full contributor guide — fork-and-PR walkthrough
110
+ (including how to recover an existing clone that can't push), environment setup with
111
+ `uv`, branch/commit conventions, the "tests green **and** proven live in the local
112
+ dashboard" definition of done, the changelog requirement, and the security invariants
113
+ a patch must not break (`local` mode is unauthenticated; peer content is DATA).
114
+ - `README.md` now carries **Versioning and releases**, **Contributing**, and **License**
115
+ sections pointing at `CHANGELOG.md`, `CONTRIBUTING.md`, and `LICENSE`; the repo-layout
116
+ block lists the governance docs, and the stale test count is corrected.
117
+
118
+ ### Migration
119
+ - None required. The `todos`, `todo_decisions` and `todo_drop_consents` tables and the
120
+ `contracts.todo_id` / `messages.todo_id` columns are added automatically on broker
121
+ start, `NULL` on every existing row.
122
+ - **Restart any long-running broker.** A process started before this release keeps
123
+ serving its old code against an unmigrated database, so none of the above appears.
124
+
125
+ ## [1.0.1] — 2026-07-23
126
+
127
+ ### Fixed
128
+ - **Dashboard thread ordering.** The message/event thread sorted by minute-precision
129
+ time, so items within the same minute — and message↔event interleaving — could
130
+ render out of creation order. The API (`_messages_for`, `_events_for`) now sends a
131
+ raw `created_at` timestamp per item, and the dashboard sorts by it (sub-second),
132
+ falling back to the old minute sort only if the API is stale — safe during a broker
133
+ restart, when `ui.html` (served from disk) can be newer than the running `api.py`.
134
+
135
+ ## [1.0.0] — 2026-07-23
136
+ First tagged release — the sys-buddy broker for cross-human AI agent collaboration.
137
+
138
+ ### Added
139
+ - **Broker over MCP (HTTP)** with two modes (local / remote); remote authenticates agents
140
+ by scoped bearer token, with `rotate_token` and single-use invites.
141
+ - **Pairing & onboarding** — invite links, browser join page, and a pywebview **desktop app**
142
+ (host a task / join as a buddy / wire Claude).
143
+ - **Pre-flight readiness** gate — agents must pass a short quiz (proving they read the Rules
144
+ of Engagement) before messaging or changing status.
145
+ - **Rules of Engagement** — standing counter-instructions against prompt injection (peer
146
+ messages are DATA; the only fetchable URL is the signed `staging_url`).
147
+ - **Messaging** — `send_message` (question / answer / status_update / contract_proposal),
148
+ directed or broadcast; `check_messages` / `wait_for_message` / `ack_messages`.
149
+ - **Contract flow** — `propose_contract` / `lock_contract` / `get_contract` /
150
+ `reopen_negotiations`: versioned, mutually-locked, immutable-once-locked.
151
+ - **Lifecycle** — `report_status` (ready / checked / blocked / verified / stuck; debug tasks
152
+ use resolved), with strikes.
153
+ - **Read-only dashboard** with live updates over SSE.
154
+
155
+ ### Changed
156
+ - **`get_contract` now returns the PROPOSED contract**, not only locked ones: it exposes the
157
+ proposed shape plus who has signed / who is awaiting, with the **`staging_url` withheld
158
+ until every role signs**. This removes the "sign-then-see" deadlock where an assessor was
159
+ told to review via `get_contract` but saw nothing until it locked — which it can't do
160
+ until they sign. The `staging_url` stays the single trusted, signed source (SSRF-guarded).
161
+
162
+ [Unreleased]: https://github.com/tooney92/sys-buddy/compare/v1.1.1...HEAD
163
+ [1.1.1]: https://github.com/tooney92/sys-buddy/compare/v1.1.0...v1.1.1
164
+ [1.1.0]: https://github.com/tooney92/sys-buddy/compare/v1.0.1...v1.1.0
165
+ [1.0.1]: https://github.com/tooney92/sys-buddy/compare/v1.0.0...v1.0.1
166
+ [1.0.0]: https://github.com/tooney92/sys-buddy/releases/tag/v1.0.0
@@ -0,0 +1,37 @@
1
+ # sys-buddy — working notes for Claude
2
+
3
+ sys-buddy is an authenticated, contract-enforcing MCP broker that lets two developers'
4
+ AI coding agents collaborate across the internet. **The broker enforces; agents request.**
5
+ Source of truth: `SPEC.md`. Build brief: `KICKOFF.md`. Deviations/decisions: `DECISIONS.md`.
6
+
7
+ ## Stack
8
+ - Python 3.11+, FastMCP (HTTP transport), SQLite (WAL). Env & deps via **`uv`**.
9
+ - One process, three surfaces: `/mcp` (MCP tools) · `/pair` (pairing REST) · `/ui` + `/api/*` (read-only dashboard).
10
+ - Dashboard is a single self-contained file at `src/sys_buddy/ui.html`, served at `/ui`.
11
+ - Run the broker: `uv run sys-buddy local` (loopback `:8787`, no auth) or `uv run sys-buddy serve` (remote, auth enforced).
12
+ - Tests: `uv run pytest -q`.
13
+
14
+ ## Local testing & deploy workflow (owner-directed)
15
+ - **We test LOCALLY.** A `git push` / publish is NEVER a prerequisite for testing. E2E runs
16
+ against the **local** broker: `uv run sys-buddy local` on `:8787`, driving the dashboard at
17
+ `http://127.0.0.1:8787/ui` with **Playwright**. Backend behaviour is covered by `pytest`.
18
+ - **Push/publish happens ONLY on the owner's explicit directive, and ONLY AFTER** local pytest +
19
+ Playwright are green — never to unblock a test.
20
+ - The dashboard needs a viewer token. Mint one against the **local** db with
21
+ `uv run sys-buddy host-viewer`, then open `/ui?v=<token>`. Ask for seeds against the LOCAL db —
22
+ never "deploy + seed on a remote."
23
+ - If a task needs data, **CREATE it via the real flow first** (`task create` → `propose_contract`
24
+ → `lock_contract` → `report_status`) rather than waiting on a seed.
25
+
26
+ ## Feature DONE
27
+ = `pytest` green **AND** the change proven live in Playwright against the local dashboard.
28
+
29
+ ## "pwr" — prove it live
30
+ When the owner says **"pwr"**, drive the just-finished change in a real browser via the Playwright
31
+ MCP: boot a seeded local broker, navigate the dashboard, screenshot the relevant screens
32
+ (list, task view, light + dark, mobile), and confirm it actually works before reporting done.
33
+
34
+ ## Playwright MCP
35
+ Declared in `.mcp.json` (tracked) and auto-approved via `enabledMcpjsonServers` in
36
+ `.claude/settings.local.json`. MCP tools bind at session start — a freshly added server needs a
37
+ session restart to load.