Skip to content

Commit a0d9a65

Browse files
feat: Agency plugin manifests + PostToolUse hook (Phase 1)
Phase 1 cross-engine: Agency manifests (agency.json, .mcp.json, hooks/), classification-only PostToolUse hook via lib/heuristics.mjs, session summary taskDescription fallback fix. 184 tests. 3-model council approved. Docs: README runtime compat table, cross-engine-spec Phase 2 invariants, AGENTS.md architecture updates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent abc9b04 commit a0d9a65

15 files changed

Lines changed: 344 additions & 12 deletions

File tree

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"$schema": "https://json.schemastore.org/claude-code-plugin.json",
33
"name": "copilot-brag-sheet",
4-
"version": "1.0.3",
54
"description": "Auto-track AI coding sessions into a structured work impact log. Local-first, MCP-conformant. Cross-engine: Claude Code, Copilot CLI, Codex CLI, Cursor, Agency.",
65
"author": {
76
"name": "Microsoft",
@@ -17,6 +16,7 @@
1716
"performance-review",
1817
"local-first"
1918
],
19+
"hooks": "hooks/hooks.json",
2020
"skills": [
2121
"./skills/brag-sheet/SKILL.md"
2222
],

.github/workflows/release.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,15 @@ jobs:
6060
required=(
6161
extension.mjs
6262
mcp-server.mjs
63+
agency.json
64+
.mcp.json
6365
plugin.json
6466
package.json
6567
bin/install.mjs
6668
bin/setup.mjs
6769
bin/mcp-server.mjs
70+
hooks/hooks.json
71+
hooks/post-tool-use.mjs
6872
lib/config.mjs
6973
lib/git-backup.mjs
7074
lib/heuristics.mjs

.mcp.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"mcpServers": {
3+
"brag-sheet": {
4+
"type": "stdio",
5+
"command": "node",
6+
"args": ["${CLAUDE_PLUGIN_ROOT}/bin/mcp-server.mjs"],
7+
"description": "Save, review, and export brag-sheet entries via MCP."
8+
}
9+
}
10+
}

AGENTS.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,27 @@ Copilot CLI host ──spawns──▶ extension.mjs (joinSession)
5959
6060
6161
optional: git commit/push to private repo
62+
63+
Agency host ──spawns──▶ hooks/post-tool-use.mjs (subprocess per event)
64+
65+
├── reads JSON payload from stdin
66+
├── classifies via lib/heuristics.mjs
67+
└── writes JSON response to stdout
68+
(Phase 1: classification only, no persistence)
69+
70+
MCP host ──stdio──▶ mcp-server.mjs (@modelcontextprotocol/sdk)
71+
72+
├── tools: save_to_brag_sheet / review_brag_sheet / generate_work_log
73+
└── delegates to lib/operations.mjs
6274
```
6375

6476
**Entry points** (start here):
6577

6678
| File | Role |
6779
|---|---|
6880
| [`extension.mjs`](extension.mjs) | The only file that imports `@github/copilot-sdk/extension`. Wires hooks and tools to `lib/*`. **All Copilot-specific glue lives here and nowhere else.** |
81+
| [`mcp-server.mjs`](mcp-server.mjs) | MCP stdio server exposing all three tools. Uses `@modelcontextprotocol/sdk` + `zod`. Works with any MCP-compatible host. |
82+
| [`hooks/post-tool-use.mjs`](hooks/post-tool-use.mjs) | Agency PostToolUse hook. Classifies tool calls via `lib/heuristics.mjs`, returns classification to host via stdout. **Phase 1: classification only, no persistence.** |
6983
| [`bin/install.mjs`](bin/install.mjs) | `npm i -g copilot-brag-sheet && copilot-brag-sheet` — copies package files into `~/.copilot/extensions/` and runs setup. |
7084
| [`bin/setup.mjs`](bin/setup.mjs) | Interactive wizard: presets, git backup, output path. Non-TTY exits cleanly (CI-safe). |
7185
| [`install.sh`](install.sh) / [`install.ps1`](install.ps1) | Curl-pipe-bash installers. Cross-platform CI-tested on PS 5.1 and pwsh 7. |
@@ -202,7 +216,7 @@ real bug reports). Don't change them without an issue and discussion first.
202216

203217
## 5. Testing strategy
204218

205-
Current state: **177 tests, all green, run cross-platform in CI.** Counts
219+
Current state: **184 tests, all green, run cross-platform in CI.** Counts
206220
per file (verify with `Select-String -Pattern '^\s*it\('`):
207221

208222
| File | Tests | Covers |
@@ -212,14 +226,15 @@ per file (verify with `Select-String -Pattern '^\s*it\('`):
212226
| `test/git-backup.test.mjs` | 19 | `ensureGitRepo`, `addRemote`, `backupToGit` with a mocked `git` runner. |
213227
| `test/mcp-server.test.mjs` | 18 | MCP server tool handlers via buildServer(), Zod validation, pagination, structured output. |
214228
| `test/operations.test.mjs` | 16 | Shared saveBragEntry, reviewBragEntries, generateWorkLog with real disk I/O. |
215-
| `test/render.test.mjs` | 14 | Markdown rendering, week boundaries (UTC), category grouping, escaping. |
229+
| `test/render.test.mjs` | 15 | Markdown rendering, week boundaries (UTC), category grouping, escaping, taskDescription fallback. |
216230
| `test/storage.test.mjs` | 12 | Atomic JSON/text writes, shard layout, filter semantics, update flow. |
217231
| `test/config.test.mjs` | 9 | Default merge, microsoft preset, category resolution. |
218232
| `test/records.test.mjs` | 8 | Record factories, sanitization, file-path dedup. |
219233
| `test/paths.test.mjs` | 7 | Per-platform data dir resolution, env-var overrides. |
220234
| `test/lock.test.mjs` | 7 | Lock acquisition, stale-PID cleanup, contention. |
235+
| `test/hooks.test.mjs` | 6 | Agency PostToolUse hook subprocess tests: classification, malformed input, stdout purity, camelCase compat. |
221236
| `test/pack-smoke.test.mjs` | 1 | Tarball validation, install simulation. |
222-
| **Total** | **177** | |
237+
| **Total** | **184** | |
223238

224239
**What's covered:**
225240

@@ -347,8 +362,8 @@ Until then, the `_npmUser` is whoever holds the `NPM_TOKEN`, not
347362
| **npm** | ✅ live (`copilot-brag-sheet`) | `npm i -g copilot-brag-sheet && copilot-brag-sheet` runs `bin/install.mjs`. |
348363
| **`install.sh` / `install.ps1`** | ✅ live | One-line curl-pipe-bash from `raw.githubusercontent.com/...`. CI-tested on PS 5.1 + pwsh 7 + bash. |
349364
| **awesome-copilot skill** | ✅ live | [`SKILL.md`](skills/brag-sheet/SKILL.md) is mirrored in [github/awesome-copilot](https://github.com/github/awesome-copilot) (PR #1428). The skill is the prompt; this repo is the prompt **plus** the deterministic capture. |
350-
| **Claude Code plugin** | 🟡 planned | Tracked in [`docs/cross-engine-spec.md`](docs/cross-engine-spec.md). Will reuse `lib/*` unchanged via a thin Claude adapter. |
351-
| **MCP server** | 🟡 planned | `mcp-server.mjs` will expose the same three tools to any MCP-compatible host (Cursor, VS Code, Codex, Copilot CLI). |
365+
| **Claude Code plugin** | ✅ partial (tools: v1.1; auto-tracking: Phase 2) | Tracked in [`docs/cross-engine-spec.md`](docs/cross-engine-spec.md). MCP tools work fully; auto-tracking hooks are classification-only (persistence in Phase 2). |
366+
| **MCP server** | ✅ live | `mcp-server.mjs` exposes all three tools to any MCP-compatible host (Cursor, VS Code, Codex, Copilot CLI) via `copilot-brag-sheet-mcp` bin. |
352367
| **`copilot plugin install`** | 🚫 blocked upstream | This is a `joinSession()` extension, not an MCP plugin — needs [github/copilot-cli#3023](https://github.com/github/copilot-cli/issues/3023). Don't try to register it under `~/.copilot/plugins/`. |
353368

354369
Don't add new distribution channels (e.g. Homebrew, scoop, winget)
@@ -371,6 +386,11 @@ proving distribution conversion before fanning out further.
371386
| `lib/records.mjs` | Record factories, `sanitize`, `addFileToRecord`. | Changing the record schema (and read §10 first). |
372387
| `lib/render.mjs` | Records → Markdown. Reserved markers live here. | Changing the work-log Markdown layout. |
373388
| `lib/git-backup.mjs` | Optional git init/commit/push of data dir. | Adding remote types, fixing git edge cases. |
389+
| `mcp-server.mjs` | MCP stdio server (3 tools). Only file that imports `@modelcontextprotocol/sdk` + `zod`. | Adding an MCP tool or changing protocol behavior. |
390+
| `agency.json` | Agency governance manifest (layer 4, developer-tools). **No `version` field**`package.json` is source of truth. | Changing Agency category, engines, or governance metadata. |
391+
| `.mcp.json` | Standalone MCP server config for Agency hosts. | Changing MCP server args or transport. |
392+
| `hooks/hooks.json` | Agency hook declarations (PostToolUse). | Adding a hook event (e.g. SessionStart, SessionEnd for Phase 2). |
393+
| `hooks/post-tool-use.mjs` | Agency PostToolUse hook. Classifies tool calls via `lib/heuristics.mjs`. **Phase 1: classification only.** | Changing classification behavior or adding persistence (Phase 2). |
374394
| `bin/install.mjs` | Post-`npm-i` wizard launcher. Copies pkg → `~/.copilot/extensions/`. | Changing the install layout or required-files list. |
375395
| `bin/setup.mjs` | Interactive setup wizard. Non-TTY-safe. | Adding a config field, preset, or onboarding step. |
376396
| `install.sh` / `install.ps1` | Curl-pipe-bash installers. | Cross-platform install behavior. PR must include CI smoke updates. |

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- **Agency plugin manifests**`agency.json` (governance manifest), `.mcp.json` (standalone MCP config), and `hooks/hooks.json` (PostToolUse hook declaration) enable installation via `agency plugin install`. The PostToolUse hook classifies tool calls using `lib/heuristics.mjs` and returns classification data to the host. **Phase 1: classification only; session persistence is deferred to Phase 2.**
12+
- **`hooks/post-tool-use.mjs`** — Agency PostToolUse hook script. Reads JSON from stdin, classifies file edits / PR creation / git actions, writes JSON response to stdout. Stateless subprocess — each invocation is independent.
13+
914
### Changed
1015

1116
- **Extracted `lib/heuristics.mjs`** — tool classification sets, extraction helpers (`extractPrInfo`, `detectShellGitAction`), brag keyword detection (`isBragRequest`), and composite `classifyToolUse()` are now importable from any entry point. `extension.mjs` imports from this module instead of defining them inline.
@@ -17,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
1722
### Fixed
1823

1924
- **git config fallback null guard**`extension.mjs` now applies the same `?? { enabled: false, push: false }` guard as `mcp-server.mjs`, preventing a potential NPE when `config.git` is undefined.
25+
- **Empty session summaries in work-log**`formatSessionRow` now falls back to `taskDescription` (captured from the user's first prompt) when `summary` is absent. Previously sessions without a host-provided `finalMessage` rendered as blank rows in the session activity log.
2026

2127
## [1.1.0] — 2026-05-11
2228

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ Plus three tools the agent can call on your behalf:
5252
| `review_brag_sheet` | Review recent entries for performance discussions |
5353
| `generate_work_log` | Render all records into a Markdown file |
5454

55+
### Runtime compatibility
56+
57+
| Runtime | Manual tools | Auto-tracking | Status |
58+
|---------|-------------|---------------|--------|
59+
| **Copilot CLI** | ✅ Full | ✅ Full | Production |
60+
| **MCP hosts** (Claude, Cursor, VS Code) | ✅ Full | ⚠️ Phase 2 | Beta |
61+
| **Agency** | ✅ Full | ⚠️ Phase 2 | Beta |
62+
63+
**MCP / Agency users:** The three tools (`save_to_brag_sheet`, `review_brag_sheet`, `generate_work_log`) work fully. Automatic session tracking (files edited, PRs created, git actions) requires the Copilot CLI `joinSession()` runtime and is not yet available in other hosts. For now, say `"brag — <accomplishment>"` to capture work manually.
64+
65+
**Copilot CLI users:** Full automatic tracking works today — no action needed.
66+
5567
### When the agent will use this
5668

5769
The agent picks up these tools when you say (anything close to) one of:

agency.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"engines": {
3+
"agency": ">=1.0.0"
4+
},
5+
"category": "developer-tools",
6+
"description": "Auto-track AI coding sessions into a structured work impact log. Local-first, zero telemetry.",
7+
"governance": {
8+
"layer": 4,
9+
"certification": "draft",
10+
"organization": "Microsoft",
11+
"teamScope": ["microsoft/copilot-brag-sheet-maintainers"]
12+
}
13+
}

docs/cross-engine-spec.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
# Cross-Engine Support: MCP Server + Hooks
22

33
> **Issue:** microsoft/copilot-brag-sheet#22
4-
> **Status:** Research complete, spec draft, pending full review
4+
> **Status:** Phase 1 shipped (tools + classification hooks); Phase 2 pending (persistence)
55
> **Priority:** Post-FHL (after May 1)
66
> **Scope:** Full (tools + hooks — full extension parity)
77
> **Distribution:** Agency first (internal MSFT), then public Claude Code plugin
88
9+
## Phase Status
10+
11+
| Component | Status | Notes |
12+
|-----------|--------|-------|
13+
| MCP server (`mcp-server.mjs`) | ✅ Shipped v1.1.0 | 3 tools over stdio, `@modelcontextprotocol/sdk` + `zod` |
14+
| Agency manifests (`agency.json`, `.mcp.json`) | ✅ Shipped | Layer 4, developer-tools category |
15+
| PostToolUse hook (classification) | ✅ Shipped | Classifies tool calls, returns `{ continue, classification }` to host |
16+
| PostToolUse hook (persistence) | ⚠️ Phase 2 | Classification data is returned to host but not accumulated on disk |
17+
| SessionStart / SessionEnd hooks | ⚠️ Phase 2 | Needed for file-based session record lifecycle |
18+
19+
## Phase 2 Design Invariants
20+
21+
These invariants are locked now to ensure Phase 2 is a non-breaking addition:
22+
23+
1. **Hooks are stateless subprocesses.** All cross-invocation state goes through `lib/storage.mjs` (atomic writes + `withFileLock`).
24+
2. **Stdout is the hook response channel.** Same rule as `extension.mjs` §10.5. Debug → stderr, gated on `BRAG_SHEET_DEBUG=1`.
25+
3. **Hook failures must not break the host session.** Always emit valid JSON, never throw.
26+
4. **`classification` field is provisional** until Phase 2 stabilizes the persistence story. Consumers depend on it at their own risk.
27+
5. **Session-key strategy is TBD.** Will be resolved when SessionStart hook is added — likely minted by SessionStart and exported via env var or sidecar file for PostToolUse to read.
28+
6. **Phase 2 additions are non-breaking:** new hooks (`SessionStart`, `SessionEnd`) and disk I/O inside `PostToolUse`. The stdout response shape grows, never shrinks.
29+
930
---
1031

1132
## Problem

docs/testing-strategy.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
## Current state — 2026-05
99

10-
**177 tests, 100% pass rate, 30 suites, run cross-platform in CI.**
10+
**184 tests, 100% pass rate, 31 suites, run cross-platform in CI.**
1111
CI matrix: `{ubuntu-latest, macos-latest, windows-latest} × {Node 18, 20, 22}` =
1212
**9 combinations** running on every PR and `main` push, plus three
1313
**install-smoke** jobs that exercise the curl-pipe-bash installers
@@ -16,7 +16,7 @@ CI matrix: `{ubuntu-latest, macos-latest, windows-latest} × {Node 18, 20, 22}`
1616
Run the suite locally:
1717

1818
```bash
19-
npm test # all 177
19+
npm test # all 184
2020
node --test test/storage.test.mjs # one file
2121
node --test --test-name-pattern="atomic" # one pattern
2222
```
@@ -46,15 +46,16 @@ No test framework dependency — we use `node:test` and
4646
| [`lib/lock.mjs`](../lib/lock.mjs) | `test/lock.test.mjs` | 7 | Successful acquire/release, contention (`EEXIST` retries), stale-PID detection (`process.kill(pid, 0)`), lock-file content readback, timeout. | Multi-process contention (we mock PIDs). Filesystem-level locking on network shares (SMB/NFS). |
4747
| [`lib/storage.mjs`](../lib/storage.mjs) | `test/storage.test.mjs` | 12 | Atomic JSON write (tmp file cleanup on failure), atomic text write (round-trip, overwrite), shard layout (`YYYY/MM/<ts>_<id>.json`), shard-bound filtering on `since`/`until`, type/category/repo/tags filters, `updateRecord` merge semantics, `logError` never throws. | Disk-full scenarios. `fsync` failures (we trust the OS). Records with `Date.parse`-invalid timestamps in old data. |
4848
| [`lib/records.mjs`](../lib/records.mjs) | `test/records.test.mjs` | 8 | `createSessionRecord` + `createEntryRecord` shape, `sanitize()` (newlines, markers, headings, pipes, length cap), `addFileToRecord` dedup + `.copilot/session-state` filtering + repo-relative path normalization. | Case-insensitive dedup on Windows/macOS. Path traversal (`../`) attempts. |
49-
| [`lib/render.mjs`](../lib/render.mjs) | `test/render.test.mjs` | 14 | `weekOf` UTC consistency including year boundaries, `renderMarkdown` empty/single/multi-week/multi-category cases, session-log opt-in, escaping pipes in tables, ordering newest-first, "Other" bucket for uncategorized, `renderReviewSummary` window filtering. | Internationalized week boundaries (we hardcode UTC). Locale-specific month names. |
49+
| [`lib/render.mjs`](../lib/render.mjs) | `test/render.test.mjs` | 15 | `weekOf` UTC consistency including year boundaries, `renderMarkdown` empty/single/multi-week/multi-category cases, session-log opt-in, escaping pipes in tables, ordering newest-first, "Other" bucket for uncategorized, `taskDescription` fallback for sessions without summary, `renderReviewSummary` window filtering. | Internationalized week boundaries (we hardcode UTC). Locale-specific month names. |
5050
| [`lib/git-backup.mjs`](../lib/git-backup.mjs) | `test/git-backup.test.mjs` | 19 | `ensureGitRepo` init + idempotent reuse, `addRemote`, `hasRemote`, `backupToGit` happy path / no-changes / commit-fail / push-fail, error logging via the injectable runner pattern (`createGitRunner`). | Real `git` binary execution (we mock). Auth failures on push. Detached HEAD states. Repos with submodules. |
5151
| [`extension.mjs`](../extension.mjs) | `test/extension.test.mjs` | 32 | Session lifecycle (active/finalized/orphaned/emergency-saved), file tracking (edit/create classification, dedup, `.copilot/session-state` skip, repo-relative normalization), significant-action accumulation, `save_to_brag_sheet` flow, category validation, summary sanitization, repo/branch auto-detection, `review_brag_sheet` rendering, `generate_work_log` write, brag/PR/git smoke tests (importing from `lib/heuristics.mjs`). | Hooks firing inside the SDK runtime (see "What is NOT covered" below). |
5252
| [`mcp-server.mjs`](../mcp-server.mjs) | `test/mcp-server.test.mjs` | 18 | MCP server tool handlers via `buildServer()`, Zod input validation, pagination, structured output, response_format switching. | Real MCP transport. |
53+
| [`hooks/post-tool-use.mjs`](../hooks/post-tool-use.mjs) | `test/hooks.test.mjs` | 6 | Subprocess stdin/stdout classification (file edit, PR creation, unrecognized tool), malformed-input graceful fallback, stdout purity (no console.log pollution), camelCase payload compat. | Hook execution inside a real Agency host. Phase 2 persistence paths. |
5354
| [`bin/setup.mjs`](../bin/setup.mjs) || 0 || Interactive prompts. Non-TTY exit (covered indirectly by CI matrix). |
5455
| [`bin/install.mjs`](../bin/install.mjs) | install-smoke (CI) | 0 unit | Tarball → `~/.copilot/extensions/...` layout. Re-run idempotency. | Failure path when `COPILOT_HOME` exists but isn't writable. |
5556
| [`install.sh`](../install.sh) / [`install.ps1`](../install.ps1) | install-smoke (CI) || End-to-end install on Linux/macOS, Windows PS 5.1, Windows pwsh 7+ (matches real-world Windows 10/11 default shell). | Air-gapped installs. Behind-corporate-proxy installs. |
5657

57-
**Total: 177 unit tests + 4 install-smoke jobs (3 OS × shells).**
58+
**Total: 184 unit tests + 4 install-smoke jobs (3 OS × shells).**
5859

5960
---
6061

hooks/hooks.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"hooks": {
3+
"PostToolUse": [
4+
{
5+
"type": "command",
6+
"command": "node ${PLUGIN_ROOT}/hooks/post-tool-use.mjs",
7+
"timeout": 5,
8+
"description": "Classify tool calls and track files, PRs, and git actions to the brag sheet"
9+
}
10+
]
11+
}
12+
}

0 commit comments

Comments
 (0)