@pbhamri/quartermaster-mcp 0.4.0 → 0.6.0
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.
- package/README.md +197 -31
- package/bin/server.js +234 -4
- package/package.json +2 -2
- package/resources/profiles/purview-dlm.json +45 -25
package/README.md
CHANGED
|
@@ -1,31 +1,107 @@
|
|
|
1
|
-
# quartermaster-mcp
|
|
1
|
+
# @pbhamri/quartermaster-mcp
|
|
2
2
|
|
|
3
|
-
> **MCP server that seeds any repo with the Quartermaster PM kit.**
|
|
3
|
+
> **MCP server that seeds any repo with the Quartermaster PM kit + provides read-only repo access + first-run onboarding + Teams self-agent auto-reply surface.**
|
|
4
4
|
> No folder dependency. No installer script. Callable from any Copilot / Claude / Cursor session against any cwd.
|
|
5
5
|
|
|
6
|
+
**Version:** 0.6.0 | **Transport:** stdio | **Tools:** 19 | **Profiles:** 6 (bundled) + custom
|
|
7
|
+
|
|
6
8
|
Built in response to PM Architect input (2026-05-29):
|
|
7
9
|
> *"It would be great if this can be an MCP server that seeds any repo. Then we are not tied to a folder or repo."*
|
|
8
10
|
|
|
11
|
+
---
|
|
12
|
+
|
|
9
13
|
## What it does
|
|
10
14
|
|
|
11
|
-
Exposes
|
|
15
|
+
Exposes **19 tools** over MCP stdio, organised by workflow:
|
|
16
|
+
|
|
17
|
+
### Seeding & Scaffolding (v0.1.0+)
|
|
12
18
|
|
|
13
19
|
| Tool | Purpose |
|
|
14
20
|
|---|---|
|
|
15
|
-
| `qm_list_profiles` | List the 6 Purview product profiles
|
|
16
|
-
| `qm_seed_repo` | Seed any repo
|
|
21
|
+
| `qm_list_profiles` | List the 6 Purview product profiles (DLM, Billing, CC, Records, eDiscovery, Insider Risk) |
|
|
22
|
+
| `qm_seed_repo` | Seed any repo with AGENTS.md, copilot-instructions.md, `.quartermaster/profile.json`, `/npf` + `/cxe` prompts, command-center seed. **Idempotent.** Supports `dryRun: true`. |
|
|
17
23
|
| `qm_audit_repo` | Score any repo /6 against the Quartermaster readiness checklist |
|
|
18
24
|
| `qm_install_prompts` | Install `/npf` + `/cxe` to `~/.github/prompts` |
|
|
19
25
|
| `qm_apply_profile` | Persist a profile to `~/.copilot/paved-path/profile.json` |
|
|
26
|
+
| `qm_telemetry` | Read recent paved-path telemetry events (type filter, limit) |
|
|
27
|
+
| `qm_emit_pr` | Seed + open a labeled PR via `gh` CLI in one call |
|
|
28
|
+
|
|
29
|
+
### Read-Only Repo Access (v0.3.0+)
|
|
30
|
+
|
|
31
|
+
| Tool | Purpose |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `repo_overview` | Server metadata: repo_root, framework_count, exclusion rules |
|
|
34
|
+
| `repo_list_dir` | List files/folders under repo_root (denies .git, node_modules, secrets) |
|
|
35
|
+
| `repo_read_file` | Read file content (200 KB cap; secrets/eml/browser sessions blocked) |
|
|
36
|
+
| `repo_search` | Case-insensitive regex search across .md/.js/.ts/.json/.yml/.ps1/.html/.txt |
|
|
37
|
+
| `repo_recent_sessions` | Last N entries from `knowledge/sessions.jsonl` (cross-model memory) |
|
|
38
|
+
|
|
39
|
+
### Onboarding & Customization (v0.4.0+)
|
|
40
|
+
|
|
41
|
+
| Tool | Purpose |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `qm_welcome` | First-run welcome + 3-step guide for new peers |
|
|
44
|
+
| `qm_personalize` | Interactive Q&A → writes a personalised `connect-db.json`. Never inherits package author's data. |
|
|
45
|
+
| `qm_skills_for_pm` | Curated daily-use catalog: Monday self-assess, midweek PRDs, Friday strategy, sharing |
|
|
46
|
+
| `qm_init_profile` | **Create a custom product profile from scratch** — any product, any org. Generates a reusable `.json` profile with ADO/Kusto/IcM/GitHub connections. |
|
|
47
|
+
|
|
48
|
+
### Self-Agent / Teams Auto-Reply (v0.5.0+)
|
|
20
49
|
|
|
21
|
-
|
|
50
|
+
| Tool | Purpose |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `qm_autoreply_compose` | Compose a draft Teams auto-reply using second-brain + relationship classifier + failure-mode gates |
|
|
53
|
+
| `qm_autoreply_set_presence` | Set mock presence state (Available/Away/DND/OOO) — P0 skeleton, real Graph swap is P1 |
|
|
54
|
+
| `qm_autoreply_failure_modes` | List named embarrassment-avoidance rules (severity, action, audience) |
|
|
55
|
+
| `qm_autoreply_drafts` | List recent auto-reply drafts under `~/.copilot/autoreply/drafts/` |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
### From npm (recommended for peers)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install -g @pbhamri/quartermaster-mcp
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### From source (contributor)
|
|
22
68
|
|
|
23
69
|
```powershell
|
|
24
|
-
|
|
70
|
+
git clone https://github.com/pbhamri_microsoft/Quartermaster.git
|
|
71
|
+
cd Quartermaster/source/quartermaster-mcp
|
|
25
72
|
npm install
|
|
26
73
|
```
|
|
27
74
|
|
|
28
|
-
|
|
75
|
+
### Register with your MCP client
|
|
76
|
+
|
|
77
|
+
**Copilot CLI / VS Code** — add to `~/.copilot/mcp.json` or `.vscode/mcp.json`:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"servers": {
|
|
82
|
+
"quartermaster": {
|
|
83
|
+
"type": "stdio",
|
|
84
|
+
"command": "npx",
|
|
85
|
+
"args": ["@pbhamri/quartermaster-mcp"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"quartermaster": {
|
|
97
|
+
"command": "npx",
|
|
98
|
+
"args": ["@pbhamri/quartermaster-mcp"]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**From source (dev):**
|
|
29
105
|
|
|
30
106
|
```json
|
|
31
107
|
{
|
|
@@ -33,41 +109,109 @@ Then register in `~\AppData\Roaming\Code\User\mcp.json` (or `.vscode/mcp.json`):
|
|
|
33
109
|
"quartermaster": {
|
|
34
110
|
"type": "stdio",
|
|
35
111
|
"command": "node",
|
|
36
|
-
"args": ["
|
|
112
|
+
"args": ["<path-to-clone>/source/quartermaster-mcp/bin/server.js"]
|
|
37
113
|
}
|
|
38
114
|
}
|
|
39
115
|
}
|
|
40
116
|
```
|
|
41
117
|
|
|
42
|
-
Reload
|
|
118
|
+
Reload your MCP client. Then: *"use quartermaster-mcp to seed this repo with purview-dlm"*.
|
|
119
|
+
|
|
120
|
+
---
|
|
43
121
|
|
|
44
|
-
## Usage examples
|
|
122
|
+
## Usage examples
|
|
45
123
|
|
|
46
124
|
```text
|
|
47
|
-
#
|
|
48
|
-
"
|
|
125
|
+
# Onboarding (first time)
|
|
126
|
+
"Run qm_welcome."
|
|
127
|
+
"Personalize my Quartermaster setup."
|
|
49
128
|
|
|
50
|
-
#
|
|
51
|
-
"
|
|
129
|
+
# Create YOUR product profile (not Purview? no problem)
|
|
130
|
+
"Create a new profile for my product area."
|
|
131
|
+
"I work on Microsoft Intune — set up a Quartermaster profile."
|
|
52
132
|
|
|
53
|
-
#
|
|
133
|
+
# Seeding
|
|
134
|
+
"List Quartermaster profiles."
|
|
135
|
+
"Dry-run seed C:\repos\new-repo with purview-cc."
|
|
54
136
|
"Seed this repo with purview-ediscovery."
|
|
137
|
+
"Seed this repo and open a draft PR."
|
|
55
138
|
|
|
56
|
-
#
|
|
139
|
+
# Auditing
|
|
57
140
|
"Audit C:\repos\billing-team-repo against Quartermaster readiness."
|
|
141
|
+
|
|
142
|
+
# Repo exploration
|
|
143
|
+
"Show me the repo overview."
|
|
144
|
+
"Search for 'retention policy' in this repo."
|
|
145
|
+
"Read competitive-intel-agent/agent.js."
|
|
146
|
+
|
|
147
|
+
# Self-agent
|
|
148
|
+
"Compose an auto-reply for this message from my manager."
|
|
149
|
+
"Show my failure mode rules."
|
|
150
|
+
"List recent auto-reply drafts."
|
|
151
|
+
|
|
152
|
+
# Telemetry
|
|
153
|
+
"Show my last 20 paved-path events."
|
|
58
154
|
```
|
|
59
155
|
|
|
156
|
+
---
|
|
157
|
+
|
|
60
158
|
## What gets seeded
|
|
61
159
|
|
|
62
|
-
In the target repo:
|
|
63
|
-
|
|
64
|
-
-
|
|
160
|
+
In the target repo (all idempotent — existing files are **never overwritten**):
|
|
161
|
+
|
|
162
|
+
- `AGENTS.md` — repo-specific operating manual
|
|
163
|
+
- `.github/copilot-instructions.md` — AI assistant conventions + CXE pillars
|
|
65
164
|
- `.quartermaster/profile.json` — full ADO/Kusto/IcM/directives/KPIs snapshot
|
|
66
165
|
- `.quartermaster/command-center-seed.json` — KPIs + quick-links for dashboards
|
|
67
|
-
- `.github/prompts/npf.prompt.md` — NPF self-score
|
|
68
|
-
- `.github/prompts/cxe.prompt.md` —
|
|
166
|
+
- `.github/prompts/npf.prompt.md` — NPF self-score (calibrated to IC level)
|
|
167
|
+
- `.github/prompts/cxe.prompt.md` — EVP Security CXE pillar audit
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Profiles (6 bundled)
|
|
172
|
+
|
|
173
|
+
| Profile ID | Product Area |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `purview-dlm` | Data Lifecycle Management |
|
|
176
|
+
| `purview-billing` | Purview Billing |
|
|
177
|
+
| `purview-cc` | Communication Compliance |
|
|
178
|
+
| `purview-records` | Records Management |
|
|
179
|
+
| `purview-ediscovery` | eDiscovery |
|
|
180
|
+
| `purview-insider-risk` | Insider Risk Management |
|
|
181
|
+
|
|
182
|
+
Each profile contains: ADO org/project/area-path, Kusto cluster/database/tables, IcM service, default directives, KPI targets, recommended MCP servers, and quick-links.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Architecture
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
bin/server.js ← Single-file MCP server (stdio transport)
|
|
190
|
+
resources/
|
|
191
|
+
profiles/ ← 6 product profile JSONs + _schema.json
|
|
192
|
+
prompts/ ← /npf + /cxe prompt templates
|
|
193
|
+
connect-db.template.json ← Scaffold for qm_personalize
|
|
194
|
+
mcp-servers/ ← Reference MCP configs for peers
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Security & Privacy
|
|
198
|
+
|
|
199
|
+
- **Read-only repo access** denies: `.git`, `node_modules`, `.env`, browser sessions, `*.eml`, `*token*`, `*secret*`, `*credentials*`, `*.pem`, `*.key`
|
|
200
|
+
- **No credentials in package** — author's Connect data, ADO tokens, Outlook sessions, and personal NPF history are never bundled
|
|
201
|
+
- **Telemetry is opt-out** — set `QM_METRICS_OPT_OUT=1` to disable. Events use anonymous machine hash (12-char SHA-256 of platform:arch:cpus:totalmem), never username or hostname
|
|
202
|
+
- **Self-agent** skeleton lives at `~/.copilot/self-agent/` (outside this package); tools degrade gracefully if absent
|
|
69
203
|
|
|
70
|
-
|
|
204
|
+
### Environment Variables
|
|
205
|
+
|
|
206
|
+
| Variable | Default | Purpose |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| `QM_REPO_ROOT` | `process.cwd()` | Override repo root for read-only access |
|
|
209
|
+
| `QM_CONNECT_DB` | `~/.quartermaster/connect-db.json` | Connect tracker path for `qm_personalize` |
|
|
210
|
+
| `QM_METRICS_FILE` | `~/.copilot/metrics/paved-path-events.jsonl` | Telemetry output path |
|
|
211
|
+
| `QM_METRICS_OPT_OUT` | (unset) | Set to `1` to disable telemetry |
|
|
212
|
+
| `QM_SELF_AGENT_DIR` | `~/.copilot/self-agent` | Self-agent skeleton directory |
|
|
213
|
+
|
|
214
|
+
---
|
|
71
215
|
|
|
72
216
|
## Why MCP, not a script
|
|
73
217
|
|
|
@@ -76,21 +220,43 @@ All file writes are idempotent. Existing files are **never overwritten** — the
|
|
|
76
220
|
| Run from one folder | Callable from any cwd in any agent |
|
|
77
221
|
| Windows PowerShell only | Any client speaking MCP (Copilot CLI, Claude Desktop, Cursor, VS Code) |
|
|
78
222
|
| Manual re-run per repo | Agent calls it inline mid-conversation |
|
|
79
|
-
| Updates require re-share |
|
|
223
|
+
| Updates require re-share | `npm update` and peers get the latest |
|
|
224
|
+
| No auto-reply | v0.5.0 adds second-brain-powered Teams draft surface |
|
|
225
|
+
|
|
226
|
+
---
|
|
80
227
|
|
|
81
228
|
## Source of truth
|
|
82
229
|
|
|
83
|
-
|
|
230
|
+
Profiles are bundled in `resources/profiles/`. The 6 Purview profiles are pre-built. For any other product, use `qm_init_profile` to create your own — it generates a new `.json` in the same directory with your ADO/Kusto/IcM connections.
|
|
231
|
+
|
|
232
|
+
To refresh bundled profiles from an upstream source:
|
|
84
233
|
|
|
85
234
|
```powershell
|
|
86
|
-
|
|
87
|
-
Copy-Item
|
|
235
|
+
# Only maintainers do this; peers just use qm_init_profile
|
|
236
|
+
Copy-Item <source>/profiles/*.json resources/profiles/ -Force
|
|
237
|
+
Copy-Item <source>/prompts/*.prompt.md resources/prompts/ -Force
|
|
88
238
|
```
|
|
89
239
|
|
|
90
240
|
Then bump the version in `package.json`.
|
|
91
241
|
|
|
92
|
-
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Version History
|
|
245
|
+
|
|
246
|
+
| Version | Date | What shipped |
|
|
247
|
+
|---|---|---|
|
|
248
|
+
| **0.6.0** | 2026-06-18 | `qm_init_profile` (custom profile creation for any product); stripped all personal data from bundled profiles; `customizableFields` schema |
|
|
249
|
+
| **0.5.0** | 2026-06-10 | Self-agent surface: `qm_autoreply_compose`, `set_presence`, `failure_modes`, `drafts` |
|
|
250
|
+
| **0.4.0** | 2026-06-05 | Onboarding: `qm_welcome`, `qm_personalize`, `qm_skills_for_pm` |
|
|
251
|
+
| **0.3.0** | 2026-06-01 | Read-only repo: `repo_overview`, `repo_list_dir`, `repo_read_file`, `repo_search`, `repo_recent_sessions` |
|
|
252
|
+
| **0.2.0** | 2026-05-30 | `qm_telemetry`, `qm_emit_pr` |
|
|
253
|
+
| **0.1.0** | 2026-05-29 | Core 5: `list_profiles`, `seed_repo`, `audit_repo`, `install_prompts`, `apply_profile` |
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Roadmap (post-v0.5)
|
|
93
258
|
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
259
|
+
- **v0.6** — Real Graph `/me/presence` integration (replace mock presence)
|
|
260
|
+
- **v0.7** — `qm_customer_signal` tool (ingest signals directly via MCP, feed to CI agent)
|
|
261
|
+
- **v0.8** — Multi-repo seed in one call (batch mode for team onboarding)
|
|
262
|
+
- Publish to Agency Marketplace as a registered MCP server plugin
|
package/bin/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Quartermaster MCP server — seeds any repo with the Quartermaster PM kit.
|
|
3
|
-
// Transport: stdio. Tools: qm_list_profiles, qm_seed_repo, qm_audit_repo, qm_install_prompts, qm_apply_profile.
|
|
3
|
+
// Transport: stdio. Tools: qm_list_profiles, qm_seed_repo, qm_audit_repo, qm_install_prompts, qm_apply_profile, qm_autoreply_* (v0.5.0).
|
|
4
4
|
|
|
5
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -107,8 +107,8 @@ function seedRepo({ repoPath, profileId, includeCommandCenter = true, includePro
|
|
|
107
107
|
## Profile-driven defaults
|
|
108
108
|
|
|
109
109
|
- **ADO**: \`${profile.ado.org}/${profile.ado.project}\` · area \`${profile.ado.areaPath}\`
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
${profile.kusto ? `- **Kusto**: \`${profile.kusto.cluster}\` · db \`${profile.kusto.database}\`` : "- **Kusto**: not configured"}
|
|
111
|
+
${profile.icm ? `- **IcM**: ${profile.icm.serviceName}` : "- **IcM**: not configured"}
|
|
112
112
|
|
|
113
113
|
## Directives (from profile)
|
|
114
114
|
|
|
@@ -197,7 +197,7 @@ Run \`/cxe\` to audit any PRD/work-item against the 4 pillars. Run \`/npf\` for
|
|
|
197
197
|
if (a.target.endsWith("AGENTS.md")) {
|
|
198
198
|
writeText(a.target, `# AGENTS.md — ${path.basename(repoPath)}\n\n> Seeded by quartermaster-mcp on ${new Date().toISOString().slice(0,10)}.\n> Profile: **${profile.displayName}** (${profile.id})\n\nSee \`.quartermaster/profile.json\` for ADO/Kusto/IcM/directives/KPIs.\n`);
|
|
199
199
|
} else {
|
|
200
|
-
writeText(a.target, `# Copilot Instructions — ${path.basename(repoPath)}\n\nSeeded by quartermaster-mcp · profile **${profile.displayName}**.\n\nCXE pillars (
|
|
200
|
+
writeText(a.target, `# Copilot Instructions — ${path.basename(repoPath)}\n\nSeeded by quartermaster-mcp · profile **${profile.displayName}**.\n\nCXE pillars (EVP Security): CXE-first · Value fast · Confidence · Support when it matters.\nRun /cxe and /npf prompts.\n`);
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
}
|
|
@@ -400,6 +400,106 @@ function qmPersonalize(args = {}) {
|
|
|
400
400
|
};
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
function qmInitProfile(args = {}) {
|
|
404
|
+
// Create a custom product profile from scratch — any PM, any product.
|
|
405
|
+
// Two modes: (1) no answers → returns interview questions; (2) answers → writes profile JSON.
|
|
406
|
+
if (!args.answers) {
|
|
407
|
+
return {
|
|
408
|
+
mode: "interview",
|
|
409
|
+
description: "Create a custom product profile for your area. This generates a .json file you can use with qm_seed_repo and qm_apply_profile.",
|
|
410
|
+
target_dir: path.join(RES, "profiles"),
|
|
411
|
+
questions: [
|
|
412
|
+
{ id: "id", q: "Profile ID (lowercase, hyphenated, e.g. 'purview-dlm', 'intune-endpoint', 'teams-calling')", required: true },
|
|
413
|
+
{ id: "displayName", q: "Display name (e.g. 'Microsoft Intune Endpoint Management')", required: true },
|
|
414
|
+
{ id: "tagline", q: "One-line tagline describing the product area", required: true },
|
|
415
|
+
{ id: "ado_org", q: "ADO organization (e.g. 'o365exchange', 'msazure')", required: true },
|
|
416
|
+
{ id: "ado_project", q: "ADO project (e.g. 'O365 Core')", required: true },
|
|
417
|
+
{ id: "ado_areaPath", q: "ADO area path (e.g. 'O365 Core\\\\Compliance\\\\DLM')", required: true },
|
|
418
|
+
{ id: "kusto_cluster",q: "Kusto cluster URL (or 'none')", required: false },
|
|
419
|
+
{ id: "kusto_db", q: "Kusto database name (or 'none')", required: false },
|
|
420
|
+
{ id: "icm_service", q: "IcM service name (or 'none')", required: false },
|
|
421
|
+
{ id: "github_repo", q: "Primary GitHub repo (org/repo format, or 'none')", required: false },
|
|
422
|
+
{ id: "directives", q: "Top 3-5 directives (one per line): the outcomes your manager cares about", required: false },
|
|
423
|
+
{ id: "kpis", q: "Top 3-5 KPI names (one per line): what you measure weekly", required: false },
|
|
424
|
+
],
|
|
425
|
+
instructions_for_ai_client: "Ask the user the questions above. When all answers collected, call qm_init_profile again with answers={...} keyed by question id.",
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Mode 2: write the profile.
|
|
430
|
+
const a = args.answers;
|
|
431
|
+
if (!a.id || !a.displayName) throw new Error("id and displayName are required");
|
|
432
|
+
const profileId = a.id.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
433
|
+
const profile = {
|
|
434
|
+
id: profileId,
|
|
435
|
+
displayName: a.displayName,
|
|
436
|
+
tagline: a.tagline || "",
|
|
437
|
+
lastUpdated: new Date().toISOString().split("T")[0],
|
|
438
|
+
ado: {
|
|
439
|
+
org: a.ado_org || "{{ADO_ORG}}",
|
|
440
|
+
project: a.ado_project || "{{ADO_PROJECT}}",
|
|
441
|
+
areaPath: a.ado_areaPath || "{{ADO_AREA_PATH}}",
|
|
442
|
+
iterationPath: a.ado_project || "{{ADO_PROJECT}}",
|
|
443
|
+
},
|
|
444
|
+
kusto: a.kusto_cluster && a.kusto_cluster !== "none" ? {
|
|
445
|
+
cluster: a.kusto_cluster,
|
|
446
|
+
database: a.kusto_db || "",
|
|
447
|
+
starterQueries: [],
|
|
448
|
+
} : null,
|
|
449
|
+
icm: a.icm_service && a.icm_service !== "none" ? {
|
|
450
|
+
tenant: "PROD",
|
|
451
|
+
serviceName: a.icm_service,
|
|
452
|
+
teamPublicId: "{{FILL_FROM_ICM_PORTAL}}",
|
|
453
|
+
} : null,
|
|
454
|
+
github: a.github_repo && a.github_repo !== "none" ? {
|
|
455
|
+
primaryRepo: a.github_repo,
|
|
456
|
+
fallbackRepos: [],
|
|
457
|
+
} : null,
|
|
458
|
+
personas: [],
|
|
459
|
+
defaultDirectives: (a.directives || "").split("\n").filter(Boolean).map((t, i) => ({
|
|
460
|
+
id: `D${i + 1}`, title: t.trim(), successSignal: "{{DEFINE}}", cadence: "30 days",
|
|
461
|
+
})),
|
|
462
|
+
defaultKpis: Object.fromEntries(
|
|
463
|
+
(a.kpis || "").split("\n").filter(Boolean).map((k, i) => [
|
|
464
|
+
`kpi${i + 1}`, { label: k.trim(), unit: "{{UNIT}}", target: "{{TARGET}}", owner: "self" }
|
|
465
|
+
])
|
|
466
|
+
),
|
|
467
|
+
customizableFields: {
|
|
468
|
+
_description: "Fields below are NOT bundled. Each PM fills them via qm_personalize or by editing .quartermaster/profile.json.",
|
|
469
|
+
fields: [
|
|
470
|
+
{ key: "teamContext", type: "object", description: "Engineer count + capacity allocation" },
|
|
471
|
+
{ key: "priorities", type: "object", description: "30/60/90 day committed items" },
|
|
472
|
+
{ key: "openBugs", type: "object", description: "Bug triage snapshot" },
|
|
473
|
+
{ key: "goals", type: "array", description: "CONNECT goals (via qm_personalize)" },
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
recommendedMcp: ["ado"],
|
|
477
|
+
quickLinks: [],
|
|
478
|
+
notes: [`Created via qm_init_profile on ${new Date().toISOString().split("T")[0]}. Fill in {{PLACEHOLDERS}} for your environment.`],
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// Remove null sections
|
|
482
|
+
if (!profile.kusto) delete profile.kusto;
|
|
483
|
+
if (!profile.icm) delete profile.icm;
|
|
484
|
+
if (!profile.github) delete profile.github;
|
|
485
|
+
|
|
486
|
+
const outPath = path.join(RES, "profiles", `${profileId}.json`);
|
|
487
|
+
if (exists(outPath)) throw new Error(`profile already exists: ${profileId}. Edit it manually or delete first.`);
|
|
488
|
+
writeText(outPath, JSON.stringify(profile, null, 2));
|
|
489
|
+
emit("qm.init_profile", { profileId });
|
|
490
|
+
return {
|
|
491
|
+
mode: "written",
|
|
492
|
+
path: outPath,
|
|
493
|
+
profileId,
|
|
494
|
+
summary: `Created profile '${profileId}' with ${profile.defaultDirectives.length} directives and ${Object.keys(profile.defaultKpis).length} KPIs.`,
|
|
495
|
+
next_steps: [
|
|
496
|
+
`Run \`qm_seed_repo\` with profileId '${profileId}' to scaffold a repo.`,
|
|
497
|
+
"Fill {{PLACEHOLDERS}} in the generated profile for Kusto queries, KPI targets, etc.",
|
|
498
|
+
"Run `qm_personalize` to connect your CONNECT goals.",
|
|
499
|
+
],
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
403
503
|
function qmSkillsForPm() {
|
|
404
504
|
// Curated catalog: 12 MCP tools mapped to PM daily-use cases.
|
|
405
505
|
// Optimised for product roadmap + strategy work.
|
|
@@ -557,6 +657,112 @@ function repoRecentSessions({ limit = 10 } = {}) {
|
|
|
557
657
|
return { sessions: lines.map(l => { try { return JSON.parse(l); } catch { return { raw: l }; } }) };
|
|
558
658
|
}
|
|
559
659
|
|
|
660
|
+
// ---------------- v0.5.0 self-agent (Teams auto-reply) surface ----------------
|
|
661
|
+
// The self-agent skeleton lives at ~/.copilot/self-agent/ (outside this package
|
|
662
|
+
// by design — it's a personal workspace artefact). These tools detect it and
|
|
663
|
+
// degrade gracefully if absent.
|
|
664
|
+
|
|
665
|
+
const SELF_AGENT_DIR = process.env.QM_SELF_AGENT_DIR
|
|
666
|
+
|| path.join(os.homedir(), ".copilot", "self-agent");
|
|
667
|
+
const AUTOREPLY_DIR = path.join(os.homedir(), ".copilot", "autoreply");
|
|
668
|
+
|
|
669
|
+
function selfAgentInstalled() {
|
|
670
|
+
return exists(path.join(SELF_AGENT_DIR, "reply-composer.js"))
|
|
671
|
+
&& exists(path.join(SELF_AGENT_DIR, "brain-query.js"));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function requireSelfAgent(mod) {
|
|
675
|
+
if (!selfAgentInstalled()) {
|
|
676
|
+
throw new Error(
|
|
677
|
+
`self-agent skeleton not found at ${SELF_AGENT_DIR}. Install it (see `
|
|
678
|
+
+ `https://www.npmjs.com/package/@pbhamri/quartermaster-mcp) or set QM_SELF_AGENT_DIR env var.`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
return require(path.join(SELF_AGENT_DIR, mod));
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function qmAutoreplyCompose({ message, senderName = "Test Sender", senderEmail = "test@example.com", tier } = {}) {
|
|
685
|
+
if (!message || typeof message !== "string") throw new Error("message (string) is required");
|
|
686
|
+
const { queryBrain } = requireSelfAgent("brain-query.js");
|
|
687
|
+
const { compose } = requireSelfAgent("reply-composer.js");
|
|
688
|
+
const { loadProfile } = requireSelfAgent("behavior-profile.js");
|
|
689
|
+
const { classify } = requireSelfAgent("relationship-classifier.js");
|
|
690
|
+
const profile = loadProfile();
|
|
691
|
+
const sender = { name: senderName, email: senderEmail };
|
|
692
|
+
const rel = tier || classify(sender);
|
|
693
|
+
const brain = queryBrain(message);
|
|
694
|
+
const out = compose(message, brain, { sender, relationship: rel, profile });
|
|
695
|
+
return {
|
|
696
|
+
inbound: { message, sender, relationship: rel },
|
|
697
|
+
composed: {
|
|
698
|
+
tier: out.tier,
|
|
699
|
+
action: out.action,
|
|
700
|
+
confidence_pct: out.confidencePct,
|
|
701
|
+
voice_match: out.voiceMatch,
|
|
702
|
+
stances_applied: out.appliedStances,
|
|
703
|
+
failure_mode_violations: out.failureModeViolations || [],
|
|
704
|
+
failure_mode_blocked_by: out.failureModeBlockedBy || null,
|
|
705
|
+
leverage_hint: out.leverageHint || null,
|
|
706
|
+
stale_notice: out.staleNotice || null,
|
|
707
|
+
body: out.body,
|
|
708
|
+
},
|
|
709
|
+
brain_top_source: brain.topSource || null,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function qmAutoreplySetPresence({ state } = {}) {
|
|
714
|
+
if (!state) throw new Error("state is required (Available|Away|Offline|BeRightBack|OutOfOffice|DoNotDisturb)");
|
|
715
|
+
const { setMockState, AWAY_STATES } = requireSelfAgent("presence-watcher.js");
|
|
716
|
+
setMockState(state);
|
|
717
|
+
const away = AWAY_STATES instanceof Set ? AWAY_STATES.has(state) : (AWAY_STATES || []).includes(state);
|
|
718
|
+
return { state, away, note: "mock presence (P0 skeleton; real Graph swap is P1)" };
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function qmAutoreplyFailureModes() {
|
|
722
|
+
const { loadRules } = requireSelfAgent("failure-modes.js");
|
|
723
|
+
const rules = loadRules();
|
|
724
|
+
return {
|
|
725
|
+
count: rules.length,
|
|
726
|
+
rules: rules.map(r => ({
|
|
727
|
+
id: r.id,
|
|
728
|
+
severity: r.severity || "auto-fix",
|
|
729
|
+
action: r.action || null,
|
|
730
|
+
audience: r.audience || "any",
|
|
731
|
+
description: r.description || "",
|
|
732
|
+
})),
|
|
733
|
+
note: `Edit ${path.join(SELF_AGENT_DIR, "failure-modes.json")} to add named embarrassment scenarios.`,
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function qmAutoreplyDrafts({ limit = 10 } = {}) {
|
|
738
|
+
const draftsDir = path.join(AUTOREPLY_DIR, "drafts");
|
|
739
|
+
if (!exists(draftsDir)) return { drafts: [], note: `no drafts dir at ${draftsDir}` };
|
|
740
|
+
const files = fs.readdirSync(draftsDir)
|
|
741
|
+
.filter(f => f.endsWith(".txt"))
|
|
742
|
+
.map(f => ({ file: f, mtime: fs.statSync(path.join(draftsDir, f)).mtime.toISOString() }))
|
|
743
|
+
.sort((a, b) => b.mtime.localeCompare(a.mtime))
|
|
744
|
+
.slice(0, limit);
|
|
745
|
+
const drafts = files.map(({ file, mtime }) => {
|
|
746
|
+
const full = path.join(draftsDir, file);
|
|
747
|
+
const head = fs.readFileSync(full, "utf8").split("\n").slice(0, 20);
|
|
748
|
+
const grab = (label) => {
|
|
749
|
+
const line = head.find(l => l.startsWith(`# ${label}:`));
|
|
750
|
+
return line ? line.slice(label.length + 3).trim() : null;
|
|
751
|
+
};
|
|
752
|
+
return {
|
|
753
|
+
file,
|
|
754
|
+
mtime,
|
|
755
|
+
to: grab("To"),
|
|
756
|
+
tier: grab("Tier"),
|
|
757
|
+
relationship: grab("Relationship"),
|
|
758
|
+
violations: grab("Failure-mode violations"),
|
|
759
|
+
blocked_by: grab("Failure-mode blocked-by"),
|
|
760
|
+
inbound: grab("Inbound text"),
|
|
761
|
+
};
|
|
762
|
+
});
|
|
763
|
+
return { count: drafts.length, drafts };
|
|
764
|
+
}
|
|
765
|
+
|
|
560
766
|
|
|
561
767
|
// ---------------- MCP wiring ----------------
|
|
562
768
|
const server = new Server(
|
|
@@ -597,6 +803,8 @@ const TOOLS = [
|
|
|
597
803
|
inputSchema: { type: "object", properties: { answers: { type: "object", description: "Key-value of question ids to user answers. Returned by interview mode." } } } },
|
|
598
804
|
{ name: "qm_skills_for_pm", description: "Curated catalog of the Quartermaster tools mapped to PM daily-use workflows (Monday self-assess, midweek PRDs + roadmap, Friday strategy prep, onboarding + sharing).",
|
|
599
805
|
inputSchema: { type: "object", properties: {} } },
|
|
806
|
+
{ name: "qm_init_profile", description: "Create a custom product profile from scratch for ANY product area (not just Purview). Returns an interview flow, then writes a reusable .json profile. Use this when no bundled profile matches your product.",
|
|
807
|
+
inputSchema: { type: "object", properties: { answers: { type: "object", description: "Key-value of question ids to user answers. Returned by interview mode." } } } },
|
|
600
808
|
// ---- v0.3.0 read-only repo surface ----
|
|
601
809
|
{ name: "repo_overview", description: "Returns server metadata: repo_root, framework_count (if a competitive-intel-agent/frameworks dir exists), exclusion rules. No args.",
|
|
602
810
|
inputSchema: { type: "object", properties: {} } },
|
|
@@ -608,6 +816,23 @@ const TOOLS = [
|
|
|
608
816
|
inputSchema: { type: "object", required: ["query"], properties: { query: { type: "string" }, max: { type: "integer" } } } },
|
|
609
817
|
{ name: "repo_recent_sessions", description: "Returns the last N entries from knowledge/sessions.jsonl (cross-model session memory) if present. Args: limit (default 10).",
|
|
610
818
|
inputSchema: { type: "object", properties: { limit: { type: "integer" } } } },
|
|
819
|
+
// ---- v0.5.0 self-agent (Teams auto-reply) surface ----
|
|
820
|
+
// Requires the self-agent skeleton at ~/.copilot/self-agent/ (or QM_SELF_AGENT_DIR override).
|
|
821
|
+
{ name: "qm_autoreply_compose", description: "Compose a draft Teams auto-reply for an inbound message using the user's second brain. Returns the body + confidence% + tier + named-failure-mode violations. No send.",
|
|
822
|
+
inputSchema: { type: "object", required: ["message"], properties: {
|
|
823
|
+
message: { type: "string", description: "Inbound message text" },
|
|
824
|
+
senderName: { type: "string", description: "Sender display name (optional; affects relationship classifier)" },
|
|
825
|
+
senderEmail: { type: "string", description: "Sender email (optional)" },
|
|
826
|
+
tier: { type: "string", description: "Override relationship: manager|skip|peer|report|cross-org|unknown" },
|
|
827
|
+
} } },
|
|
828
|
+
{ name: "qm_autoreply_set_presence", description: "Set the mock presence state for the self-agent (P0 skeleton; real Graph /me/presence is the P1 swap).",
|
|
829
|
+
inputSchema: { type: "object", required: ["state"], properties: {
|
|
830
|
+
state: { type: "string", enum: ["Available","Away","Offline","BeRightBack","OutOfOffice","DoNotDisturb"] },
|
|
831
|
+
} } },
|
|
832
|
+
{ name: "qm_autoreply_failure_modes", description: "List the named embarrassment-avoidance rules currently loaded by the self-agent (id, severity, action, audience, description).",
|
|
833
|
+
inputSchema: { type: "object", properties: {} } },
|
|
834
|
+
{ name: "qm_autoreply_drafts", description: "List the most recent auto-reply drafts written under ~/.copilot/autoreply/drafts (with tier, violations, inbound text).",
|
|
835
|
+
inputSchema: { type: "object", properties: { limit: { type: "integer", default: 10 } } } },
|
|
611
836
|
];
|
|
612
837
|
|
|
613
838
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
@@ -627,11 +852,16 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
627
852
|
case "qm_welcome": result = qmWelcome(); break;
|
|
628
853
|
case "qm_personalize": result = qmPersonalize(args); break;
|
|
629
854
|
case "qm_skills_for_pm": result = qmSkillsForPm(); break;
|
|
855
|
+
case "qm_init_profile": result = qmInitProfile(args); break;
|
|
630
856
|
case "repo_overview": result = repoOverview(); break;
|
|
631
857
|
case "repo_list_dir": result = repoListDir(args); break;
|
|
632
858
|
case "repo_read_file": result = repoReadFile(args); break;
|
|
633
859
|
case "repo_search": result = repoSearch(args); break;
|
|
634
860
|
case "repo_recent_sessions": result = repoRecentSessions(args); break;
|
|
861
|
+
case "qm_autoreply_compose": result = await timed("qm.autoreply_compose", {}, async () => qmAutoreplyCompose(args)); break;
|
|
862
|
+
case "qm_autoreply_set_presence": result = await timed("qm.autoreply_set_presence", { state: args.state }, async () => qmAutoreplySetPresence(args)); break;
|
|
863
|
+
case "qm_autoreply_failure_modes": result = qmAutoreplyFailureModes(); break;
|
|
864
|
+
case "qm_autoreply_drafts": result = qmAutoreplyDrafts(args); break;
|
|
635
865
|
default: throw new Error(`unknown tool: ${name}`);
|
|
636
866
|
}
|
|
637
867
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pbhamri/quartermaster-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server that seeds any repo with
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "MCP server that seeds any repo with a customizable PM kit. Any PM, any product — connect YOUR GitHub, ADO, Kusto, IcM. 19 tools: scaffolding, repo-read, onboarding, custom profile creation, self-agent auto-reply. Never ships personal data — profiles are templates you fill in.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"quartermaster-mcp": "bin/server.js"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "purview-dlm",
|
|
3
3
|
"displayName": "Purview Data Lifecycle Management (DLM)",
|
|
4
|
-
"tagline": "Retention labels, retention policies, records, disposition
|
|
4
|
+
"tagline": "Retention labels, retention policies, records, disposition -- across SharePoint, OneDrive, Exchange, Teams, Copilot.",
|
|
5
|
+
"lastUpdated": "2026-06-18",
|
|
5
6
|
"ado": {
|
|
6
7
|
"org": "o365exchange",
|
|
7
8
|
"project": "O365 Core",
|
|
@@ -12,49 +13,68 @@
|
|
|
12
13
|
"cluster": "https://cxedataplatformcluster.westus2.kusto.windows.net/",
|
|
13
14
|
"database": "PurviewTelemetry",
|
|
14
15
|
"starterQueries": [
|
|
15
|
-
{ "name": "DLM 28d retention-label apply rate",
|
|
16
|
-
{ "name": "Disposition review queue depth",
|
|
17
|
-
{ "name": "E5 vs E3 active-tenant DLM feature usage","kql": "DlmFeatureUsage | where Timestamp > ago(30d) | summarize ActiveTenants = dcount(TenantId) by Sku, FeatureName" }
|
|
16
|
+
{ "name": "DLM 28d retention-label apply rate", "kql": "RetentionLabelEvents | where Timestamp > ago(28d) | summarize Applied = count() by bin(Timestamp, 1d)" },
|
|
17
|
+
{ "name": "Disposition review queue depth", "kql": "DispositionReviewEvents | where State == 'PendingReview' | summarize Pending = count() by Workload" },
|
|
18
|
+
{ "name": "E5 vs E3 active-tenant DLM feature usage", "kql": "DlmFeatureUsage | where Timestamp > ago(30d) | summarize ActiveTenants = dcount(TenantId) by Sku, FeatureName" },
|
|
19
|
+
{ "name": "Policy sync reliability", "kql": "PolicySyncEvents | where Timestamp > ago(7d) | summarize SuccessRate = round(100.0 * countif(Status == 'Success') / count(), 2) by bin(Timestamp, 1d)" },
|
|
20
|
+
{ "name": "DLM ICM volume trend", "kql": "IcmIncidents | where ServiceName has 'Data Lifecycle' and CreateDate > ago(90d) | summarize ICMs = count() by bin(CreateDate, 7d)" }
|
|
18
21
|
]
|
|
19
22
|
},
|
|
20
23
|
"icm": {
|
|
21
24
|
"tenant": "PROD",
|
|
22
25
|
"serviceName": "Microsoft Purview / Data Lifecycle Management",
|
|
23
|
-
"teamPublicId": "
|
|
24
|
-
"routingId": "DLM-PM-
|
|
26
|
+
"teamPublicId": "{{FILL_FROM_ICM_PORTAL}}",
|
|
27
|
+
"routingId": "DLM-PM-Oncall"
|
|
25
28
|
},
|
|
26
29
|
"github": {
|
|
27
30
|
"primaryRepo": "microsoft/m365-purview-dlm",
|
|
28
31
|
"fallbackRepos": ["microsoft/m365-purview"]
|
|
29
32
|
},
|
|
30
33
|
"personas": [
|
|
31
|
-
{ "role": "Engineering Manager"
|
|
32
|
-
{ "role": "Design Partner
|
|
33
|
-
{ "role": "Compliance Marketing PMM"
|
|
34
|
-
{ "role": "Copilot Compliance PM"
|
|
34
|
+
{ "role": "Engineering Manager" },
|
|
35
|
+
{ "role": "Design Partner -- Enterprise" },
|
|
36
|
+
{ "role": "Compliance Marketing PMM" },
|
|
37
|
+
{ "role": "Copilot Compliance PM" },
|
|
38
|
+
{ "role": "FastTrack Architect" }
|
|
35
39
|
],
|
|
36
40
|
"defaultDirectives": [
|
|
37
|
-
{ "id": "D1", "title": "
|
|
38
|
-
{ "id": "D2", "title": "
|
|
39
|
-
{ "id": "D3", "title": "
|
|
40
|
-
{ "id": "D4", "title": "
|
|
41
|
-
{ "id": "D5", "title": "
|
|
41
|
+
{ "id": "D1", "title": "Ship retention insights for AI app interactions (Copilot compliance)", "successSignal": "Committed customers in preview; CXE pillar 2 (TTV) met", "cadence": "30 days" },
|
|
42
|
+
{ "id": "D2", "title": "Reduce DLM support volume via self-serve diagnostics", "successSignal": "10% IPD reduction in Sev-C ICM volume vs baseline", "cadence": "60 days" },
|
|
43
|
+
{ "id": "D3", "title": "Priority Cleanup + Hard Delete to GA (ODSP)", "successSignal": "GA milestone hit; 10+ customers in first 30d", "cadence": "30 days" },
|
|
44
|
+
{ "id": "D4", "title": "E3 -> E5 attach via DLM-originated trial starts", "successSignal": "+25% WoW trial-start -> attach-rate -> paid-conversion", "cadence": "Weekly" },
|
|
45
|
+
{ "id": "D5", "title": "Shared leverage: PM toolkit adopted by 3+ peers", "successSignal": "3 distinct users confirmed via paved-path telemetry", "cadence": "90 days" }
|
|
42
46
|
],
|
|
43
47
|
"defaultKpis": {
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
48
|
+
"copilotRetention": { "label": "Copilot retention -- preview customers", "unit": "customers", "target": "{{SET_TARGET}}", "owner": "self" },
|
|
49
|
+
"policySyncReliability":{ "label": "Policy Sync success rate", "unit": "%", "target": "99.9%", "owner": "engineering" },
|
|
50
|
+
"icmReduction": { "label": "DLM ICM volume reduction", "unit": "% vs baseline","target": "-10%", "owner": "self + eng" },
|
|
51
|
+
"e5TrialAttach": { "label": "E3 -> E5 trial-start WoW lift", "unit": "% WoW", "target": "+25%", "owner": "self" },
|
|
52
|
+
"dispositionTTM": { "label": "Disposition end-to-end time", "unit": "days", "target": "<=30d", "owner": "engineering" },
|
|
53
|
+
"peerAdoption": { "label": "PM toolkit peer adoption", "unit": "users", "target": "3+", "owner": "self" }
|
|
54
|
+
},
|
|
55
|
+
"customizableFields": {
|
|
56
|
+
"_description": "These fields are NOT bundled. Each PM fills them in via qm_personalize or by editing their local .quartermaster/profile.json after seeding.",
|
|
57
|
+
"fields": [
|
|
58
|
+
{ "key": "teamContext.engineerCount", "type": "integer", "description": "Number of engineers on your DLM sub-team" },
|
|
59
|
+
{ "key": "teamContext.capacityAllocation","type": "object", "description": "Capacity split across workstreams (keys are workstream names, values are fractions summing to 1.0)" },
|
|
60
|
+
{ "key": "priorities.thirtyDayItems", "type": "string[]","description": "Your 30-day committed items (from manager connect or sprint plan)" },
|
|
61
|
+
{ "key": "priorities.sixtyDayItems", "type": "string[]","description": "Your 60-day committed items" },
|
|
62
|
+
{ "key": "priorities.ninetyDayItems", "type": "string[]","description": "Your 90-day stretch items" },
|
|
63
|
+
{ "key": "openBugs", "type": "object", "description": "Your triaged bug snapshot (totalOpen, highlighted array)" },
|
|
64
|
+
{ "key": "goals", "type": "array", "description": "Your CONNECT goals -- generated via qm_personalize" },
|
|
65
|
+
{ "key": "quickLinks", "type": "array", "description": "Your team's SharePoint, OneNote, dashboard links" }
|
|
66
|
+
]
|
|
49
67
|
},
|
|
50
68
|
"recommendedMcp": ["kusto", "icm", "workiq", "ado", "playwright"],
|
|
51
69
|
"quickLinks": [
|
|
52
|
-
{ "label": "DLM
|
|
53
|
-
{ "label": "
|
|
54
|
-
{ "label": "
|
|
70
|
+
{ "label": "DLM ADO board", "url": "https://dev.azure.com/o365exchange/O365%20Core/_boards/board/t/DLM" },
|
|
71
|
+
{ "label": "Retention docs (M365)", "url": "https://learn.microsoft.com/purview/retention" },
|
|
72
|
+
{ "label": "DLM feature flags", "url": "https://aka.ms/purview-dlm-flags" }
|
|
55
73
|
],
|
|
56
74
|
"notes": [
|
|
57
|
-
"Fill teamPublicId from icmportal
|
|
58
|
-
"
|
|
75
|
+
"Fill teamPublicId from icmportal -> Service Tree -> 'Microsoft Purview / Data Lifecycle Management' -> 'Team' tab.",
|
|
76
|
+
"Run qm_personalize after seeding to fill in your goals, priorities, and team context.",
|
|
77
|
+
"KPI targets marked {{SET_TARGET}} must be filled by the PM during personalization.",
|
|
78
|
+
"Kusto queries are product-wide and shared across DLM PMs -- customize for your sub-area in local profile."
|
|
59
79
|
]
|
|
60
80
|
}
|