@pbhamri/quartermaster-mcp 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pbhamri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # quartermaster-mcp
2
+
3
+ > **MCP server that seeds any repo with the Quartermaster PM kit.**
4
+ > No folder dependency. No installer script. Callable from any Copilot / Claude / Cursor session against any cwd.
5
+
6
+ Built in response to PM Architect input (2026-05-29):
7
+ > *"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
+
9
+ ## What it does
10
+
11
+ Exposes 5 tools over MCP stdio:
12
+
13
+ | Tool | Purpose |
14
+ |---|---|
15
+ | `qm_list_profiles` | List the 6 Purview product profiles bundled in the package |
16
+ | `qm_seed_repo` | Seed any repo path with AGENTS.md, copilot-instructions.md, `.quartermaster/profile.json`, `/npf` + `/cxe` prompts, command-center seed. **Idempotent.** Supports `dryRun: true`. |
17
+ | `qm_audit_repo` | Score any repo /6 against the Quartermaster readiness checklist |
18
+ | `qm_install_prompts` | Install `/npf` + `/cxe` to `~/.github/prompts` |
19
+ | `qm_apply_profile` | Persist a profile to `~/.copilot/paved-path/profile.json` |
20
+
21
+ ## Install (local, from source)
22
+
23
+ ```powershell
24
+ cd C:\Users\pbhamri\source\quartermaster-mcp
25
+ npm install
26
+ ```
27
+
28
+ Then register in `~\AppData\Roaming\Code\User\mcp.json` (or `.vscode/mcp.json`):
29
+
30
+ ```json
31
+ {
32
+ "servers": {
33
+ "quartermaster": {
34
+ "type": "stdio",
35
+ "command": "node",
36
+ "args": ["C:\\Users\\pbhamri\\source\\quartermaster-mcp\\bin\\server.js"]
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ Reload VS Code. Then in any chat: *"use quartermaster-mcp to seed this repo with purview-dlm"*.
43
+
44
+ ## Usage examples (natural language → MCP)
45
+
46
+ ```text
47
+ # List what's available
48
+ "List Quartermaster profiles."
49
+
50
+ # Dry-run before writing
51
+ "Use quartermaster-mcp to dry-run seed C:\repos\new-repo with purview-cc."
52
+
53
+ # Real seed
54
+ "Seed this repo with purview-ediscovery."
55
+
56
+ # Audit any path
57
+ "Audit C:\repos\billing-team-repo against Quartermaster readiness."
58
+ ```
59
+
60
+ ## What gets seeded
61
+
62
+ In the target repo:
63
+ - `AGENTS.md` (only if missing)
64
+ - `.github/copilot-instructions.md` (only if missing)
65
+ - `.quartermaster/profile.json` — full ADO/Kusto/IcM/directives/KPIs snapshot
66
+ - `.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` — Hayete CXE pillar audit
69
+
70
+ All file writes are idempotent. Existing files are **never overwritten** — they're reported as `skip (exists)`.
71
+
72
+ ## Why MCP, not a script
73
+
74
+ | Old (install-wizard.ps1) | New (quartermaster-mcp) |
75
+ |---|---|
76
+ | Run from one folder | Callable from any cwd in any agent |
77
+ | Windows PowerShell only | Any client speaking MCP (Copilot CLI, Claude Desktop, Cursor, VS Code) |
78
+ | Manual re-run per repo | Agent calls it inline mid-conversation |
79
+ | Updates require re-share | Single source of truth; bump version, peers pick up via mcp.json |
80
+
81
+ ## Source of truth
82
+
83
+ Resources are bundled in `resources/` (profiles, prompts, mcp-server registry). To refresh from the live share-kit:
84
+
85
+ ```powershell
86
+ Copy-Item C:\Users\pbhamri\share-kit\profiles\*.json resources\profiles\ -Force
87
+ Copy-Item C:\Users\pbhamri\.github\prompts\*.prompt.md resources\prompts\ -Force
88
+ ```
89
+
90
+ Then bump the version in `package.json`.
91
+
92
+ ## Roadmap (post-v0.1)
93
+
94
+ - `qm_emit_pr` — open a PR against a seeded repo with the Quartermaster scaffolding
95
+ - `qm_telemetry` — emit `paved-path-events.jsonl` entries per seed/audit so adoption is measurable
96
+ - Publish to npm as `@pbhamri/quartermaster-mcp` for one-line `npx` install
package/bin/server.js ADDED
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env node
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.
4
+
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import {
8
+ CallToolRequestSchema,
9
+ ListToolsRequestSchema,
10
+ ListResourcesRequestSchema,
11
+ ReadResourceRequestSchema,
12
+ } from "@modelcontextprotocol/sdk/types.js";
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import os from "node:os";
16
+ import { execFileSync } from "node:child_process";
17
+ import { createRequire } from "node:module";
18
+ import { fileURLToPath } from "node:url";
19
+ const require = createRequire(import.meta.url);
20
+
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ const RES = path.join(__dirname, "..", "resources");
23
+ const PKG = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
24
+
25
+ // ---- Telemetry (§9 paved-path contract: type, latency_ms, snake_case) ----
26
+ const METRICS_FILE = process.env.QM_METRICS_FILE
27
+ || path.join(os.homedir(), ".copilot", "metrics", "paved-path-events.jsonl");
28
+
29
+ function machineId() {
30
+ try {
31
+ const crypto = require("node:crypto");
32
+ const seed = `${os.platform()}:${os.arch()}:${os.cpus().length}:${os.totalmem()}`;
33
+ return crypto.createHash("sha256").update(seed).digest("hex").slice(0, 12);
34
+ } catch { return "unknown"; }
35
+ }
36
+
37
+ function emit(type, data = {}) {
38
+ if (process.env.QM_METRICS_OPT_OUT === "1") return;
39
+ try {
40
+ fs.mkdirSync(path.dirname(METRICS_FILE), { recursive: true });
41
+ const evt = {
42
+ ts: new Date().toISOString(),
43
+ type,
44
+ source: "quartermaster-mcp",
45
+ version: PKG.version,
46
+ machine: machineId(),
47
+ ...data,
48
+ };
49
+ fs.appendFileSync(METRICS_FILE, JSON.stringify(evt) + "\n", "utf8");
50
+ } catch (e) { process.stderr.write(`telemetry: ${e.message}\n`); }
51
+ }
52
+
53
+ async function timed(type, baseData, fn) {
54
+ const t0 = Date.now();
55
+ try {
56
+ const result = await fn();
57
+ emit(type, { ...baseData, success: true, latency_ms: Date.now() - t0 });
58
+ return result;
59
+ } catch (e) {
60
+ emit(type, { ...baseData, success: false, latency_ms: Date.now() - t0, error: e.message });
61
+ throw e;
62
+ }
63
+ }
64
+
65
+ const readJson = (p) => JSON.parse(fs.readFileSync(p, "utf8"));
66
+ const writeText = (p, t) => { fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, t, "utf8"); };
67
+ const exists = (p) => fs.existsSync(p);
68
+
69
+ function listProfiles() {
70
+ return fs.readdirSync(path.join(RES, "profiles"))
71
+ .filter(f => f.endsWith(".json") && !f.startsWith("_"))
72
+ .map(f => {
73
+ const j = readJson(path.join(RES, "profiles", f));
74
+ return { id: j.id, displayName: j.displayName, file: f, directives: (j.defaultDirectives||[]).length, kpis: Object.keys(j.defaultKpis||{}).length };
75
+ });
76
+ }
77
+
78
+ function seedRepo({ repoPath, profileId, includeCommandCenter = true, includePrompts = true, dryRun = false }) {
79
+ if (!repoPath || !exists(repoPath)) throw new Error(`repoPath does not exist: ${repoPath}`);
80
+ const profileFile = path.join(RES, "profiles", `${profileId}.json`);
81
+ if (!exists(profileFile)) throw new Error(`profile not found: ${profileId}. Use qm_list_profiles.`);
82
+ const profile = readJson(profileFile);
83
+
84
+ const actions = [];
85
+ const plan = (target, kind, bytes) => actions.push({ target, kind, bytes });
86
+
87
+ // 1. .quartermaster/profile.json (in target repo, not ~/.copilot — keeps it self-contained)
88
+ const profileTarget = path.join(repoPath, ".quartermaster", "profile.json");
89
+ const profileContent = JSON.stringify({
90
+ seededAt: new Date().toISOString(),
91
+ seededBy: "quartermaster-mcp@0.1.0",
92
+ profile: { id: profile.id, displayName: profile.displayName },
93
+ ado: profile.ado, kusto: profile.kusto, icm: profile.icm,
94
+ directives: profile.defaultDirectives, kpis: profile.defaultKpis,
95
+ recommendedMcp: profile.recommendedMcp, quickLinks: profile.quickLinks,
96
+ }, null, 2);
97
+ plan(profileTarget, "create", profileContent.length);
98
+
99
+ // 2. AGENTS.md stub (only if not present)
100
+ const agentsPath = path.join(repoPath, "AGENTS.md");
101
+ if (!exists(agentsPath)) {
102
+ const agents = `# AGENTS.md — ${path.basename(repoPath)}
103
+
104
+ > Seeded by quartermaster-mcp on ${new Date().toISOString().slice(0,10)}.
105
+ > Profile: **${profile.displayName}** (${profile.id})
106
+
107
+ ## Profile-driven defaults
108
+
109
+ - **ADO**: \`${profile.ado.org}/${profile.ado.project}\` · area \`${profile.ado.areaPath}\`
110
+ - **Kusto**: \`${profile.kusto.cluster}\` · db \`${profile.kusto.database}\`
111
+ - **IcM**: ${profile.icm.serviceName}
112
+
113
+ ## Directives (from profile)
114
+
115
+ ${profile.defaultDirectives.map((d,i) => `${i+1}. **${d.id}** — ${d.title}`).join("\n")}
116
+
117
+ ## Verification
118
+
119
+ Run \`pwsh scripts/verify.ps1\` (if present) before opening PRs.
120
+
121
+ ## Source
122
+ This file was generated from the Quartermaster MCP server. To regenerate or
123
+ re-seed, ask any agent: *"use quartermaster-mcp to re-seed this repo with profile ${profile.id}"*.
124
+ `;
125
+ plan(agentsPath, "create", agents.length);
126
+ } else {
127
+ plan(agentsPath, "skip (exists)", 0);
128
+ }
129
+
130
+ // 3. .github/copilot-instructions.md stub (only if not present)
131
+ const ciPath = path.join(repoPath, ".github", "copilot-instructions.md");
132
+ if (!exists(ciPath)) {
133
+ const ci = `# Copilot Instructions — ${path.basename(repoPath)}
134
+
135
+ Seeded by quartermaster-mcp · profile **${profile.displayName}**.
136
+
137
+ ## Operating model
138
+ - Score every artefact against the **TRANSFORMATIVE** PM behaviors (AI-driven innovation, outcome-centric, shared leverage, decision velocity).
139
+ - Close every response with a 2-line Transformative prompt-sharpening tip.
140
+
141
+ ## CXE pillars (Hayete, 2026-05-29)
142
+ 1. CXE = core priority
143
+ 2. Customers get value fast (TTV)
144
+ 3. Operate with confidence
145
+ 4. Support when it matters
146
+
147
+ Run \`/cxe\` to audit any PRD/work-item against the 4 pillars. Run \`/npf\` for the maturity self-score.
148
+ `;
149
+ plan(ciPath, "create", ci.length);
150
+ } else {
151
+ plan(ciPath, "skip (exists)", 0);
152
+ }
153
+
154
+ // 4. .github/prompts/{npf,cxe}.prompt.md
155
+ if (includePrompts) {
156
+ for (const name of ["npf.prompt.md", "cxe.prompt.md"]) {
157
+ const src = path.join(RES, "prompts", name);
158
+ const dst = path.join(repoPath, ".github", "prompts", name);
159
+ if (exists(src) && !exists(dst)) plan(dst, "create", fs.statSync(src).size);
160
+ else if (exists(dst)) plan(dst, "skip (exists)", 0);
161
+ }
162
+ }
163
+
164
+ // 5. Command Center starter
165
+ if (includeCommandCenter) {
166
+ const ccPath = path.join(repoPath, ".quartermaster", "command-center-seed.json");
167
+ const cc = JSON.stringify({
168
+ generatedAt: new Date().toISOString(),
169
+ product: profile.displayName,
170
+ directives: profile.defaultDirectives,
171
+ kpis: profile.defaultKpis,
172
+ quickLinks: profile.quickLinks,
173
+ }, null, 2);
174
+ plan(ccPath, "create", cc.length);
175
+ }
176
+
177
+ // Execute unless dryRun
178
+ if (!dryRun) {
179
+ for (const a of actions) {
180
+ if (a.kind === "create") {
181
+ const isProfile = a.target.endsWith("profile.json") && a.target.includes(".quartermaster");
182
+ const isSeed = a.target.endsWith("command-center-seed.json");
183
+ const isPrompt = a.target.includes(path.join(".github","prompts"));
184
+ if (isProfile) writeText(a.target, profileContent);
185
+ else if (isSeed) writeText(a.target, JSON.stringify({
186
+ generatedAt: new Date().toISOString(), product: profile.displayName,
187
+ directives: profile.defaultDirectives, kpis: profile.defaultKpis, quickLinks: profile.quickLinks,
188
+ }, null, 2));
189
+ else if (isPrompt) {
190
+ const name = path.basename(a.target);
191
+ fs.mkdirSync(path.dirname(a.target), { recursive: true });
192
+ fs.copyFileSync(path.join(RES, "prompts", name), a.target);
193
+ }
194
+ else if (a.target.endsWith("AGENTS.md") || a.target.endsWith("copilot-instructions.md")) {
195
+ // Re-derive content (kept in plan() above is local-scope variables)
196
+ // To keep this simple, regenerate inline here
197
+ if (a.target.endsWith("AGENTS.md")) {
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
+ } else {
200
+ writeText(a.target, `# Copilot Instructions — ${path.basename(repoPath)}\n\nSeeded by quartermaster-mcp · profile **${profile.displayName}**.\n\nCXE pillars (Hayete): CXE-first · Value fast · Confidence · Support when it matters.\nRun /cxe and /npf prompts.\n`);
201
+ }
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ return {
208
+ repoPath, profile: { id: profile.id, displayName: profile.displayName },
209
+ dryRun, actions,
210
+ summary: `${actions.filter(a=>a.kind==='create').length} files ${dryRun ? "would be created" : "created"}, ${actions.filter(a=>a.kind.startsWith('skip')).length} skipped`,
211
+ };
212
+ }
213
+
214
+ function auditRepo({ repoPath }) {
215
+ if (!repoPath || !exists(repoPath)) throw new Error(`repoPath does not exist: ${repoPath}`);
216
+ const checks = [
217
+ { id: "agents-md", label: "AGENTS.md present", pass: exists(path.join(repoPath, "AGENTS.md")) },
218
+ { id: "copilot-instr", label: ".github/copilot-instructions.md", pass: exists(path.join(repoPath, ".github", "copilot-instructions.md")) },
219
+ { id: "qm-profile", label: ".quartermaster/profile.json", pass: exists(path.join(repoPath, ".quartermaster", "profile.json")) },
220
+ { id: "npf-prompt", label: ".github/prompts/npf.prompt.md", pass: exists(path.join(repoPath, ".github", "prompts", "npf.prompt.md")) },
221
+ { id: "cxe-prompt", label: ".github/prompts/cxe.prompt.md", pass: exists(path.join(repoPath, ".github", "prompts", "cxe.prompt.md")) },
222
+ { id: "verify-ps1", label: "scripts/verify.ps1", pass: exists(path.join(repoPath, "scripts", "verify.ps1")) },
223
+ ];
224
+ const score = checks.filter(c => c.pass).length;
225
+ return { repoPath, score, max: checks.length, band: score>=5?"Strong":score>=3?"Acceptable":"Weak", checks };
226
+ }
227
+
228
+ function installPrompts({ targetDir = path.join(os.homedir(), ".github", "prompts") } = {}) {
229
+ fs.mkdirSync(targetDir, { recursive: true });
230
+ const installed = [];
231
+ for (const f of fs.readdirSync(path.join(RES, "prompts"))) {
232
+ const src = path.join(RES, "prompts", f);
233
+ const dst = path.join(targetDir, f);
234
+ fs.copyFileSync(src, dst);
235
+ installed.push(dst);
236
+ }
237
+ return { targetDir, installed };
238
+ }
239
+
240
+ function applyProfile({ profileId, targetPath = path.join(os.homedir(), ".copilot", "paved-path", "profile.json") }) {
241
+ const src = path.join(RES, "profiles", `${profileId}.json`);
242
+ if (!exists(src)) throw new Error(`profile not found: ${profileId}`);
243
+ const profile = readJson(src);
244
+ const persisted = {
245
+ installedAt: new Date().toISOString(),
246
+ wizardVersion: "mcp-0.1.0",
247
+ profile: { id: profile.id, displayName: profile.displayName },
248
+ ado: profile.ado, kusto: profile.kusto, icm: profile.icm,
249
+ };
250
+ writeText(targetPath, JSON.stringify(persisted, null, 2));
251
+ return { targetPath, profile: persisted.profile };
252
+ }
253
+
254
+ function readTelemetry({ limit = 50, typeFilter = null } = {}) {
255
+ if (!exists(METRICS_FILE)) return { file: METRICS_FILE, total: 0, events: [], summary: {} };
256
+ const lines = fs.readFileSync(METRICS_FILE, "utf8").split("\n").filter(Boolean);
257
+ const all = [];
258
+ for (const l of lines) { try { all.push(JSON.parse(l)); } catch {} }
259
+ const filtered = typeFilter ? all.filter(e => e.type === typeFilter) : all;
260
+ const summary = {};
261
+ for (const e of all) summary[e.type] = (summary[e.type] || 0) + 1;
262
+ return { file: METRICS_FILE, total: all.length, returned: Math.min(limit, filtered.length), events: filtered.slice(-limit), summary };
263
+ }
264
+
265
+ function emitPr({ repoPath, profileId, branch = null, title = null, body = null, base = "main", draft = false }) {
266
+ if (!repoPath || !exists(repoPath)) throw new Error(`repoPath does not exist: ${repoPath}`);
267
+ if (!exists(path.join(repoPath, ".git"))) throw new Error(`not a git repo: ${repoPath}`);
268
+ // Prerequisite: gh CLI on PATH
269
+ try { execFileSync("gh", ["--version"], { stdio: "pipe" }); }
270
+ catch { throw new Error("gh CLI not found on PATH. Install: https://cli.github.com/"); }
271
+
272
+ const stamp = new Date().toISOString().slice(0,10);
273
+ branch = branch || `quartermaster/seed-${profileId}-${stamp}`;
274
+ title = title || `Seed Quartermaster kit (${profileId})`;
275
+
276
+ const run = (args, opts = {}) => execFileSync("git", args, { cwd: repoPath, stdio: "pipe", ...opts }).toString().trim();
277
+ const runGh = (args, opts = {}) => execFileSync("gh", args, { cwd: repoPath, stdio: "pipe", ...opts }).toString().trim();
278
+
279
+ // Ensure clean for the files we'll create — we seed first, then commit
280
+ const seedResult = seedRepo({ repoPath, profileId, dryRun: false });
281
+ const created = seedResult.actions.filter(a => a.kind === "create").map(a => a.target);
282
+ if (created.length === 0) {
283
+ return { repoPath, branch, created: 0, message: "nothing to seed (all files already exist); no PR opened" };
284
+ }
285
+
286
+ // Branch + commit + push + PR
287
+ const baseBranch = (() => { try { return run(["rev-parse","--abbrev-ref","HEAD"]); } catch { return base; } })();
288
+ try { run(["checkout","-b", branch]); } catch { run(["checkout", branch]); }
289
+ for (const f of created) { try { run(["add", path.relative(repoPath, f)]); } catch {} }
290
+ const commitMsg = `Seed Quartermaster kit (${profileId})\n\n${created.length} files added by quartermaster-mcp@${PKG.version}.\n\n<!-- BEGIN pr-telemetry -->\nassistance: agentic-cli\ntype: infra\nagent-tool: copilot-cli\nagent-model: claude-opus-4.7\nwork-item: n/a\n<!-- END pr-telemetry -->\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>`;
291
+ run(["commit","-m", commitMsg]);
292
+ run(["push","-u","origin", branch]);
293
+
294
+ body = body || `Seeded by \`quartermaster-mcp@${PKG.version}\` with profile **${profileId}**.\n\nFiles added:\n${created.map(f => `- \`${path.relative(repoPath, f)}\``).join("\n")}\n\n<!-- BEGIN pr-telemetry -->\nassistance: agentic-cli\ntype: infra\nagent-tool: copilot-cli\nagent-model: claude-opus-4.7\nwork-item: n/a\n<!-- END pr-telemetry -->`;
295
+
296
+ const ghArgs = ["pr","create","--base", base, "--head", branch, "--title", title, "--body", body];
297
+ if (draft) ghArgs.push("--draft");
298
+ const prUrl = runGh(ghArgs);
299
+ return { repoPath, branch, baseBranch, base, prUrl, filesAdded: created.length };
300
+ }
301
+
302
+
303
+ // ---------------- MCP wiring ----------------
304
+ const server = new Server(
305
+ { name: "quartermaster-mcp", version: "0.1.0" },
306
+ { capabilities: { tools: {}, resources: {} } }
307
+ );
308
+
309
+ const TOOLS = [
310
+ { name: "qm_list_profiles", description: "List all available Purview product profiles (DLM, Billing, CC, Records, eDiscovery, Insider Risk).",
311
+ inputSchema: { type: "object", properties: {} } },
312
+ { name: "qm_seed_repo", description: "Seed a target repo with the Quartermaster kit (AGENTS.md, copilot-instructions.md, .quartermaster/profile.json, /npf + /cxe prompts, command-center seed). Idempotent — skips existing files.",
313
+ inputSchema: { type: "object", required: ["repoPath","profileId"], properties: {
314
+ repoPath: { type:"string", description:"Absolute path to target repo root" },
315
+ profileId: { type:"string", description:"e.g. purview-dlm, purview-billing, purview-cc, purview-records, purview-ediscovery, purview-insider-risk" },
316
+ includeCommandCenter: { type:"boolean", default:true },
317
+ includePrompts: { type:"boolean", default:true },
318
+ dryRun: { type:"boolean", default:false, description:"Plan only; write nothing." },
319
+ } } },
320
+ { name: "qm_audit_repo", description: "Score a repo /6 against Quartermaster readiness (AGENTS.md, copilot-instructions, profile, /npf, /cxe, verify.ps1).",
321
+ inputSchema: { type:"object", required:["repoPath"], properties: { repoPath: { type:"string" } } } },
322
+ { name: "qm_install_prompts", description: "Install /npf and /cxe prompts to ~/.github/prompts (or override targetDir).",
323
+ inputSchema: { type:"object", properties: { targetDir: { type:"string" } } } },
324
+ { name: "qm_apply_profile", description: "Persist a Purview product profile to ~/.copilot/paved-path/profile.json (or override targetPath).",
325
+ inputSchema: { type:"object", required:["profileId"], properties: { profileId: { type:"string" }, targetPath: { type:"string" } } } },
326
+ { name: "qm_telemetry", description: "Read recent paved-path telemetry events emitted by this MCP server (and any other paved-path source writing to the same JSONL).",
327
+ inputSchema: { type:"object", properties: { limit: { type:"integer", default:50 }, typeFilter: { type:"string", description:"e.g. qm.seed_repo, qm.audit_repo" } } } },
328
+ { name: "qm_emit_pr", description: "Seed a repo with a profile AND open a labeled PR via gh CLI. Requires gh on PATH and a git repo at repoPath.",
329
+ inputSchema: { type:"object", required:["repoPath","profileId"], properties: {
330
+ repoPath: { type:"string" }, profileId: { type:"string" },
331
+ branch: { type:"string", description:"defaults to quartermaster/seed-<profile>-<date>" },
332
+ title: { type:"string" }, body: { type:"string" },
333
+ base: { type:"string", default:"main" }, draft: { type:"boolean", default:false }
334
+ } } },
335
+ ];
336
+
337
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
338
+
339
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
340
+ const { name, arguments: args = {} } = req.params;
341
+ try {
342
+ let result;
343
+ switch (name) {
344
+ case "qm_list_profiles": result = await timed("qm.list_profiles", {}, async () => listProfiles()); break;
345
+ case "qm_seed_repo": result = await timed("qm.seed_repo", { profileId: args.profileId, dryRun: !!args.dryRun }, async () => seedRepo(args)); break;
346
+ case "qm_audit_repo": result = await timed("qm.audit_repo", {}, async () => auditRepo(args)); break;
347
+ case "qm_install_prompts": result = await timed("qm.install_prompts", {}, async () => installPrompts(args)); break;
348
+ case "qm_apply_profile": result = await timed("qm.apply_profile", { profileId: args.profileId }, async () => applyProfile(args)); break;
349
+ case "qm_telemetry": result = readTelemetry(args); break;
350
+ case "qm_emit_pr": result = await timed("qm.emit_pr", { profileId: args.profileId }, async () => emitPr(args)); break;
351
+ default: throw new Error(`unknown tool: ${name}`);
352
+ }
353
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
354
+ } catch (e) {
355
+ return { content: [{ type: "text", text: `ERROR: ${e.message}` }], isError: true };
356
+ }
357
+ });
358
+
359
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
360
+ resources: listProfiles().map(p => ({
361
+ uri: `quartermaster://profile/${p.id}`,
362
+ name: `Profile · ${p.displayName}`,
363
+ mimeType: "application/json",
364
+ })),
365
+ }));
366
+
367
+ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
368
+ const m = /^quartermaster:\/\/profile\/(.+)$/.exec(req.params.uri);
369
+ if (!m) throw new Error(`bad uri: ${req.params.uri}`);
370
+ const p = path.join(RES, "profiles", `${m[1]}.json`);
371
+ return { contents: [{ uri: req.params.uri, mimeType: "application/json", text: fs.readFileSync(p, "utf8") }] };
372
+ });
373
+
374
+ await server.connect(new StdioServerTransport());
375
+ process.stderr.write(`quartermaster-mcp v${PKG.version} ready (stdio)\n`);
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@pbhamri/quartermaster-mcp",
3
+ "version": "0.2.0",
4
+ "description": "MCP server that seeds any repo with the Quartermaster PM kit (profiles, prompts, AGENTS.md, command center). No folder dependency — callable from any Copilot/Claude/Cursor session.",
5
+ "type": "module",
6
+ "bin": {
7
+ "quartermaster-mcp": "bin/server.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "resources/",
12
+ "LICENSE",
13
+ "README.md"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/pbhamri_microsoft/Quartermaster.git",
21
+ "directory": "source/quartermaster-mcp"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.29.0"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "keywords": ["mcp", "pm", "purview", "quartermaster", "copilot"],
30
+ "author": "pbhamri",
31
+ "license": "MIT"
32
+ }
@@ -0,0 +1,41 @@
1
+ # MCP server registry
2
+
3
+ Each `*.json` file is a paste-ready Model Context Protocol server config. The wizard installer (`install/install-wizard.ps1`) reads these, substitutes profile-specific tokens (`{{KUSTO_CLUSTER}}`, `{{ADO_ORG}}`, etc.), and merges the result into:
4
+
5
+ - **VS Code**: `~/AppData/Roaming/Code/User/mcp.json` (workspace-agnostic) and/or `.vscode/mcp.json` (per-project)
6
+ - **Copilot CLI**: `~/.copilot/mcp.json`
7
+
8
+ ## Available servers
9
+
10
+ | ID | Server | Public? | Notes |
11
+ |---|---|---|---|
12
+ | `kusto` | Azure Kusto (Data Explorer) | yes (`uvx azure-kusto-mcp`) | Profile-driven default cluster |
13
+ | `icm` | IcM (Incident Manager) — Microsoft internal | no | Install from internal feed; wizard drops a TODO file if package not found |
14
+ | `playwright` | Playwright browser automation | yes (`@playwright/mcp`) | Used by `emailer.js` and `pm-share-kit` |
15
+ | `workiq` | WorkIQ — M365 productivity data | yes (`@microsoft/workiq`) | Pulls meeting/email/Teams context |
16
+ | `ado` | Azure DevOps (work items + repos) | yes (`@azure-devops/mcp`) | Pre-fills org/project from profile |
17
+
18
+ ## Token substitution
19
+
20
+ The wizard replaces these tokens at install time using the selected profile:
21
+
22
+ | Token | Source field in profile JSON |
23
+ |---|---|
24
+ | `{{KUSTO_CLUSTER}}` | `kusto.cluster` |
25
+ | `{{KUSTO_DATABASE}}` | `kusto.database` |
26
+ | `{{ICM_SERVICE_NAME}}` | `icm.serviceName` |
27
+ | `{{ICM_TEAM_PUBLIC_ID}}`| `icm.teamPublicId` |
28
+ | `{{ADO_ORG}}` | `ado.org` |
29
+ | `{{ADO_PROJECT}}` | `ado.project` |
30
+
31
+ ## Adding a new MCP server
32
+
33
+ 1. Drop a new `<id>.json` here matching the existing schema (`id`, `displayName`, `description`, `requires`, `config`, `validation`).
34
+ 2. Add the new `<id>` to `share-kit/profiles/_schema.json` under `recommendedMcp.items.enum`.
35
+ 3. Reference the new `<id>` in any product profile's `recommendedMcp` array that should pre-select it.
36
+
37
+ That's it — the wizard auto-discovers `*.json` files in this folder. No code change to the installer required.
38
+
39
+ ## §7.5 safety rule
40
+
41
+ The wizard **snapshots** the current VS Code MCP config to `~/.copilot/session-state/mcp-snapshot-<timestamp>.json` before any edit, and prompts you to **close VS Code first**. If MCP setup spans more than 5 turns, the wizard stops and asks you to verify the snapshot before continuing — VS Code silently overwrites these files on save/reload.
@@ -0,0 +1,20 @@
1
+ {
2
+ "id": "ado",
3
+ "displayName": "Azure DevOps (work items + repos)",
4
+ "description": "Read/write ADO work items, query backlogs, link PRs to work items — across the org/project from the active profile.",
5
+ "requires": ["Azure CLI signed in: az login", "az devops extension: az extension add --name azure-devops"],
6
+ "config": {
7
+ "type": "stdio",
8
+ "command": "npx",
9
+ "args": ["-y", "@azure-devops/mcp@latest"],
10
+ "env": {
11
+ "ADO_DEFAULT_ORG": "{{ADO_ORG}}",
12
+ "ADO_DEFAULT_PROJECT": "{{ADO_PROJECT}}"
13
+ }
14
+ },
15
+ "validation": "After install: in Copilot CLI, ask 'use ado-mcp to list my active work items'.",
16
+ "notes": [
17
+ "Wizard pre-fills ADO_DEFAULT_ORG and ADO_DEFAULT_PROJECT from the selected profile (e.g., o365exchange / O365 Core for DLM).",
18
+ "az rest against dev.azure.com requires --resource 499b84ac-1321-427f-aa17-267ca6975798 — the MCP server handles this internally."
19
+ ]
20
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "id": "icm",
3
+ "displayName": "IcM (Incident Manager) — Microsoft internal",
4
+ "description": "Query IcM incidents and create new ones from Copilot. Requires the Microsoft-internal IcM MCP server (no public package — install from internal feed).",
5
+ "requires": [
6
+ "Microsoft corp network or AzureVPN",
7
+ "Internal IcM MCP server: ask in #icm-mcp-users Teams channel or see https://aka.ms/icm-mcp for install steps",
8
+ "IcM bearer token via 'icm-cli login' (or use device-code flow on first run)"
9
+ ],
10
+ "config": {
11
+ "type": "stdio",
12
+ "command": "npx",
13
+ "args": ["-y", "@microsoft-internal/icm-mcp@latest"],
14
+ "env": {
15
+ "ICM_TENANT": "PROD",
16
+ "ICM_DEFAULT_SERVICE": "{{ICM_SERVICE_NAME}}",
17
+ "ICM_TEAM_PUBLIC_ID": "{{ICM_TEAM_PUBLIC_ID}}"
18
+ }
19
+ },
20
+ "validation": "After install: in Copilot CLI, ask 'use icm-mcp to show my active incidents'. On first run, complete the device-code auth in the browser.",
21
+ "fallbackIfPackageMissing": {
22
+ "note": "If @microsoft-internal/icm-mcp is not yet published to your npm scope, the wizard installs a stub that lists the manual steps in ~/.copilot/icm-mcp-todo.md. The Copilot CLI will still work — just no IcM tools until you complete the manual install.",
23
+ "manualSteps": [
24
+ "1. Visit https://aka.ms/icm-mcp for the latest install instructions",
25
+ "2. Authenticate: icm-cli login (uses device code)",
26
+ "3. Re-run the wizard with -SkipMcpInstall to refresh the config"
27
+ ]
28
+ }
29
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "kusto",
3
+ "displayName": "Azure Kusto (Data Explorer)",
4
+ "description": "Query Kusto clusters from Copilot. Profile-driven default cluster; peer can swap KUSTO_SERVICE_URI to their own.",
5
+ "requires": ["uv (https://docs.astral.sh/uv/) or uvx on PATH", "Azure CLI signed in: az login"],
6
+ "config": {
7
+ "type": "stdio",
8
+ "command": "uvx",
9
+ "args": ["azure-kusto-mcp"],
10
+ "env": {
11
+ "KUSTO_SERVICE_URI": "{{KUSTO_CLUSTER}}",
12
+ "PATH": "C:\\Program Files\\Microsoft SDKs\\Azure\\CLI2\\wbin;${env:PATH}"
13
+ }
14
+ },
15
+ "validation": "After install: in Copilot CLI, ask 'use kusto-mcp to list databases on the default cluster'."
16
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "playwright",
3
+ "displayName": "Playwright (browser automation)",
4
+ "description": "Drive a real browser from Copilot — for Outlook web sends, Teams DMs, dashboard screenshots, IcM portal scraping.",
5
+ "requires": ["Node 20+", "npx on PATH"],
6
+ "config": {
7
+ "command": "npx",
8
+ "args": ["-y", "@playwright/mcp@latest"],
9
+ "tools": ["*"],
10
+ "type": "stdio"
11
+ },
12
+ "validation": "After install: in Copilot CLI, ask 'use playwright-mcp to open bing.com and screenshot the page'."
13
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "workiq",
3
+ "displayName": "WorkIQ — Microsoft 365 productivity data",
4
+ "description": "Pull meeting transcripts, emails, Teams messages, and calendar events from M365 directly into Copilot context.",
5
+ "requires": ["Node 20+", "Microsoft 365 account signed into M365 apps (Teams desktop)"],
6
+ "config": {
7
+ "type": "stdio",
8
+ "command": "npx",
9
+ "args": ["-y", "@microsoft/workiq", "mcp"]
10
+ },
11
+ "validation": "After install: in Copilot CLI, ask 'use workiq to show my last 3 meetings'. First run will pop a Microsoft consent dialog — approve."
12
+ }
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft-07/schema#",
3
+ "title": "Purview PM Profile",
4
+ "type": "object",
5
+ "required": ["id", "displayName", "ado", "kusto", "icm", "personas", "defaultDirectives", "defaultKpis", "recommendedMcp"],
6
+ "properties": {
7
+ "id": { "type": "string", "description": "Slug — also the filename" },
8
+ "displayName": { "type": "string" },
9
+ "tagline": { "type": "string", "description": "1-line product summary for the dashboard header" },
10
+ "ado": {
11
+ "type": "object",
12
+ "required": ["org", "project"],
13
+ "properties": {
14
+ "org": { "type": "string", "examples": ["o365exchange", "msazure"] },
15
+ "project": { "type": "string", "examples": ["M365 Security", "O365 Core"] },
16
+ "areaPath": { "type": "string" },
17
+ "iterationPath":{ "type": "string" }
18
+ }
19
+ },
20
+ "kusto": {
21
+ "type": "object",
22
+ "required": ["cluster", "database"],
23
+ "properties": {
24
+ "cluster": { "type": "string", "format": "uri" },
25
+ "database": { "type": "string" },
26
+ "starterQueries": {
27
+ "type": "array",
28
+ "items": { "type": "object", "required": ["name", "kql"], "properties": { "name": {"type":"string"}, "kql": {"type":"string"} } }
29
+ }
30
+ }
31
+ },
32
+ "icm": {
33
+ "type": "object",
34
+ "properties": {
35
+ "tenant": { "type": "string", "default": "PROD" },
36
+ "serviceName": { "type": "string" },
37
+ "teamPublicId": { "type": "string", "description": "GUID of the on-call team" },
38
+ "routingId": { "type": "string" }
39
+ }
40
+ },
41
+ "github": {
42
+ "type": "object",
43
+ "properties": {
44
+ "primaryRepo": { "type": "string", "description": "owner/repo for PR + ADO linking" },
45
+ "fallbackRepos": { "type": "array", "items": { "type": "string" } }
46
+ }
47
+ },
48
+ "personas": {
49
+ "type": "array",
50
+ "description": "Key stakeholder roles to pre-seed in people/",
51
+ "items": { "type": "object", "required": ["role"], "properties": { "role": {"type":"string"}, "exampleName": {"type":"string"}, "exampleEmail": {"type":"string"} } }
52
+ },
53
+ "defaultDirectives": {
54
+ "type": "array",
55
+ "description": "Pre-seeded directives for connect-db.json — peer can edit",
56
+ "items": { "type": "object", "required": ["id", "title"], "properties": { "id": {"type":"string"}, "title": {"type":"string"}, "successSignal": {"type":"string"}, "cadence": {"type":"string"} } }
57
+ },
58
+ "defaultKpis": {
59
+ "type": "object",
60
+ "description": "Pre-seeded KPIs for the dashboard — peer fills in real numbers",
61
+ "additionalProperties": {
62
+ "type": "object",
63
+ "properties": { "label": {"type":"string"}, "unit": {"type":"string"}, "target": {"type":"string"}, "owner": {"type":"string"} }
64
+ }
65
+ },
66
+ "recommendedMcp": {
67
+ "type": "array",
68
+ "description": "MCP servers preselected for this product",
69
+ "items": { "type": "string", "enum": ["kusto", "icm", "playwright", "workiq", "ado"] }
70
+ },
71
+ "quickLinks": {
72
+ "type": "array",
73
+ "items": { "type": "object", "required": ["label", "url"], "properties": { "label": {"type":"string"}, "url": {"type":"string"} } }
74
+ },
75
+ "notes": { "type": "array", "items": { "type": "string" } }
76
+ }
77
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "id": "purview-billing",
3
+ "displayName": "Purview Billing & Metering",
4
+ "tagline": "Consumption metering, SKU mapping, invoice telemetry, cost-attribution across Purview SKUs.",
5
+ "ado": {
6
+ "org": "o365exchange",
7
+ "project": "O365 Core",
8
+ "areaPath": "O365 Core\\Compliance\\Billing"
9
+ },
10
+ "kusto": {
11
+ "cluster": "https://cxedataplatformcluster.westus2.kusto.windows.net/",
12
+ "database": "PurviewBilling",
13
+ "starterQueries": [
14
+ { "name": "Daily metering rows per tenant (28d)", "kql": "MeteringEvents | where Timestamp > ago(28d) | summarize Rows = count() by bin(Timestamp,1d), TenantId" },
15
+ { "name": "SKU mix MoM", "kql": "BillingSnapshots | where Timestamp > ago(60d) | summarize ARR = sum(MonthlyARR) by SkuName, bin(Timestamp,30d)" },
16
+ { "name": "Top 10 cost drivers this month", "kql": "CostAttribution | where Month == startofmonth(now()) | top 10 by AttributedCostUSD desc" }
17
+ ]
18
+ },
19
+ "icm": {
20
+ "tenant": "PROD",
21
+ "serviceName": "Microsoft Purview / Billing & Metering",
22
+ "teamPublicId": "TBD-fill-from-IcM-portal",
23
+ "routingId": "Purview-Billing-Oncall"
24
+ },
25
+ "github": {
26
+ "primaryRepo": "microsoft/m365-purview-billing",
27
+ "fallbackRepos": ["microsoft/m365-purview"]
28
+ },
29
+ "personas": [
30
+ { "role": "Finance Partner" },
31
+ { "role": "Engineering Manager — Metering" },
32
+ { "role": "Pricing PM" },
33
+ { "role": "CSS — Billing escalations" }
34
+ ],
35
+ "defaultDirectives": [
36
+ { "id": "D1", "title": "Cost-attribution accuracy: close 95% reconciliation gap", "successSignal": "Variance vs invoice < 1% for 4 consecutive weeks", "cadence": "Weekly" },
37
+ { "id": "D2", "title": "SKU-mapping coverage: 100% Purview SKUs mapped to features", "successSignal": "Map file in repo, signed off by Finance", "cadence": "30 days" },
38
+ { "id": "D3", "title": "Invoice anomaly alerts: ship 2 AI-driven alert types", "successSignal": "Alert fires + cost saved per fire logged", "cadence": "Monthly" },
39
+ { "id": "D4", "title": "Self-serve cost dashboard for FieldCxO", "successSignal": "10 internal users / week", "cadence": "60 days" }
40
+ ],
41
+ "defaultKpis": {
42
+ "reconciliationGap": { "label": "Reconciliation gap (invoice vs metering)", "unit": "%", "target": "< 1%", "owner": "self" },
43
+ "skuCoverage": { "label": "SKUs mapped to features", "unit": "% of SKUs","target": "100%", "owner": "self" },
44
+ "anomalyAlertsShipped":{"label": "AI anomaly-alert types live", "unit": "/2", "target": "2 by Q+1", "owner": "self" },
45
+ "fieldDashboardUsers":{ "label": "Field-CxO dashboard weekly users", "unit": "users/wk","target": "10", "owner": "self" }
46
+ },
47
+ "recommendedMcp": ["kusto", "icm", "workiq", "ado"],
48
+ "quickLinks": [
49
+ { "label": "Purview Billing wiki", "url": "https://aka.ms/purview-billing-wiki" },
50
+ { "label": "Commerce Operations", "url": "https://aka.ms/commerce-ops" }
51
+ ],
52
+ "notes": [
53
+ "Reconciliation gap must be measured against the same Commerce invoice cut — pull both from the same Kusto snapshot timestamp."
54
+ ]
55
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "id": "purview-cc",
3
+ "displayName": "Purview Communication Compliance (CC)",
4
+ "tagline": "Detect, triage, and remediate inappropriate or risky communications across Teams, Exchange, Copilot, and third-party connectors.",
5
+ "ado": {
6
+ "org": "o365exchange",
7
+ "project": "M365 Security",
8
+ "areaPath": "M365 Security\\Compliance\\CommunicationCompliance"
9
+ },
10
+ "kusto": {
11
+ "cluster": "https://cxedataplatformcluster.westus2.kusto.windows.net/",
12
+ "database": "PurviewCC",
13
+ "starterQueries": [
14
+ { "name": "Alert volume by policy (28d)", "kql": "CCPolicyAlerts | where Timestamp > ago(28d) | summarize Alerts = count() by PolicyName, bin(Timestamp,1d)" },
15
+ { "name": "Investigator MTTR by tenant", "kql": "CCInvestigations | where ClosedAt > ago(30d) | summarize MTTRhrs = avg(datetime_diff('hour', ClosedAt, OpenedAt)) by TenantId" },
16
+ { "name": "False-positive rate by classifier", "kql": "CCClassifierFeedback | where Timestamp > ago(28d) | summarize FpRate = countif(Label == 'FP') * 1.0 / count() by ClassifierName" }
17
+ ]
18
+ },
19
+ "icm": {
20
+ "tenant": "PROD",
21
+ "serviceName": "Microsoft Purview / Communication Compliance",
22
+ "teamPublicId": "TBD-fill-from-IcM-portal"
23
+ },
24
+ "github": {
25
+ "primaryRepo": "microsoft/m365-purview-cc"
26
+ },
27
+ "personas": [
28
+ { "role": "Engineering Manager" },
29
+ { "role": "Classifier ML Lead" },
30
+ { "role": "CELA / Privacy partner" },
31
+ { "role": "Regulator-facing Field Specialist" }
32
+ ],
33
+ "defaultDirectives": [
34
+ { "id": "D1", "title": "Classifier false-positive rate: drive below 8%", "successSignal": "4 consecutive weeks of < 8% FP", "cadence": "Weekly" },
35
+ { "id": "D2", "title": "Copilot interactions in scope: ship coverage GA", "successSignal": "GA blog + 5 named customers live", "cadence": "Quarterly" },
36
+ { "id": "D3", "title": "Investigator MTTR: cut by 30% via AI-summarized threads", "successSignal": "Median MTTR < 4h", "cadence": "Monthly" },
37
+ { "id": "D4", "title": "Regulator-ready audit pack: one-click export", "successSignal": "Top-5 regulated customers using", "cadence": "60 days" }
38
+ ],
39
+ "defaultKpis": {
40
+ "fpRate": { "label": "Classifier false-positive rate", "unit": "%", "target": "< 8%", "owner": "self" },
41
+ "copilotCoverageGa":{ "label": "Copilot-in-scope coverage", "unit": "GA y/n", "target": "GA", "owner": "self" },
42
+ "investigatorMttr": { "label": "Investigator MTTR (median)", "unit": "hours", "target": "< 4h", "owner": "self" },
43
+ "auditPackUsers": { "label": "Regulator audit-pack adopters", "unit": "tenants", "target": "5", "owner": "self" }
44
+ },
45
+ "recommendedMcp": ["kusto", "icm", "workiq", "ado"],
46
+ "quickLinks": [
47
+ { "label": "CC PM workspace", "url": "https://aka.ms/purview-cc-pm" },
48
+ { "label": "CC docs", "url": "https://learn.microsoft.com/purview/communication-compliance" }
49
+ ],
50
+ "notes": [
51
+ "FP rate measurement requires investigator feedback signal — ensure UI-instrumentation is shipping events to PurviewCC.CCClassifierFeedback before measuring."
52
+ ]
53
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "id": "purview-dlm",
3
+ "displayName": "Purview Data Lifecycle Management (DLM)",
4
+ "tagline": "Retention labels, retention policies, records, disposition — across SharePoint, OneDrive, Exchange, Teams.",
5
+ "ado": {
6
+ "org": "o365exchange",
7
+ "project": "O365 Core",
8
+ "areaPath": "O365 Core\\Compliance\\DLM",
9
+ "iterationPath": "O365 Core"
10
+ },
11
+ "kusto": {
12
+ "cluster": "https://cxedataplatformcluster.westus2.kusto.windows.net/",
13
+ "database": "PurviewTelemetry",
14
+ "starterQueries": [
15
+ { "name": "DLM 28d retention-label apply rate", "kql": "RetentionLabelEvents | where Timestamp > ago(28d) | summarize Applied = count() by bin(Timestamp, 1d)" },
16
+ { "name": "Disposition review queue depth", "kql": "DispositionReviewEvents | where State == 'PendingReview' | summarize Pending = count() by Workload" },
17
+ { "name": "E5 vs E3 active-tenant DLM feature usage","kql": "DlmFeatureUsage | where Timestamp > ago(30d) | summarize ActiveTenants = dcount(TenantId) by Sku, FeatureName" }
18
+ ]
19
+ },
20
+ "icm": {
21
+ "tenant": "PROD",
22
+ "serviceName": "Microsoft Purview / Data Lifecycle Management",
23
+ "teamPublicId": "TBD-fill-from-IcM-portal",
24
+ "routingId": "DLM-PM-Onсall"
25
+ },
26
+ "github": {
27
+ "primaryRepo": "microsoft/m365-purview-dlm",
28
+ "fallbackRepos": ["microsoft/m365-purview"]
29
+ },
30
+ "personas": [
31
+ { "role": "Engineering Manager", "exampleName": "TBD" },
32
+ { "role": "Design Partner — Enterprise", "exampleName": "TBD" },
33
+ { "role": "Compliance Marketing PMM", "exampleName": "TBD" },
34
+ { "role": "Copilot Compliance PM", "exampleName": "TBD" }
35
+ ],
36
+ "defaultDirectives": [
37
+ { "id": "D1", "title": "Ramp-up: convert insights into 2-3 backlog wins with named customer evidence", "successSignal": "3 ADO items filed with customer quote + tenant ID", "cadence": "30 days" },
38
+ { "id": "D2", "title": "E3 → E5 adoption: 25% lift target", "successSignal": "30-day baseline read, then weekly trend", "cadence": "Weekly Mon" },
39
+ { "id": "D3", "title": "AI in DLM: ship 1-2 surfaces with value metrics", "successSignal": "Minutes saved + queue collapse + false-positive reduction", "cadence": "Monthly readout" },
40
+ { "id": "D4", "title": "Safe AI battlecard: co-own with Copilot + Compliance marketing", "successSignal": "Battlecard published + sales-enabled", "cadence": "60 days" },
41
+ { "id": "D5", "title": "Cross-functional 1:1s evolve from info-gathering to joint problem-solving", "successSignal": "Each 1:1 brings a specific hypothesis", "cadence": "Weekly" }
42
+ ],
43
+ "defaultKpis": {
44
+ "e5BaselineLift": { "label": "E3 → E5 lift", "unit": "% MoM", "target": "+25% in 90d", "owner": "self" },
45
+ "aiSurfacesShipped": { "label": "AI surfaces shipped", "unit": "/2 chosen", "target": "2 by 2026-06-13", "owner": "self" },
46
+ "backlogWinsFiled": { "label": "ADO backlog wins from ramp-up", "unit": "/3", "target": "3 by 2026-06-10", "owner": "self" },
47
+ "dispositionTimeBefore":{ "label": "Disposition time — baseline", "unit": "minutes", "target": "capture by 2026-06-05", "owner": "self" },
48
+ "dispositionTimeAfter": { "label": "Disposition time — with AI", "unit": "minutes", "target": "-50% vs baseline", "owner": "self" }
49
+ },
50
+ "recommendedMcp": ["kusto", "icm", "workiq", "ado", "playwright"],
51
+ "quickLinks": [
52
+ { "label": "DLM PM workspace", "url": "https://aka.ms/purview-dlm-pm" },
53
+ { "label": "DLM ADO board", "url": "https://dev.azure.com/o365exchange/O365%20Core/_boards/board/t/DLM" },
54
+ { "label": "Retention docs (M365)", "url": "https://learn.microsoft.com/purview/retention" }
55
+ ],
56
+ "notes": [
57
+ "Fill teamPublicId from icmportal → Service Tree → 'Microsoft Purview / Data Lifecycle Management' → 'Team' tab.",
58
+ "Disposition KPI baseline must be captured BEFORE AI-in-DLM ships — otherwise no proof of value metric."
59
+ ]
60
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "id": "purview-ediscovery",
3
+ "displayName": "Purview eDiscovery (Premium)",
4
+ "tagline": "Identify, collect, review, and produce ESI for legal matters. Custodian holds, search, review-set, predictive coding.",
5
+ "ado": {
6
+ "org": "o365exchange",
7
+ "project": "M365 Security",
8
+ "areaPath": "M365 Security\\Compliance\\eDiscovery"
9
+ },
10
+ "kusto": {
11
+ "cluster": "https://cxedataplatformcluster.westus2.kusto.windows.net/",
12
+ "database": "PurviewEDiscovery",
13
+ "starterQueries": [
14
+ { "name": "Active matters by tenant (28d)", "kql": "Matters | where Status == 'Active' and Timestamp > ago(28d) | summarize Active = count() by TenantId" },
15
+ { "name": "Search latency P95 (review-set build)","kql": "SearchOps | where Op == 'BuildReviewSet' and Timestamp > ago(7d) | summarize P95s = percentile(LatencySec, 95) by bin(Timestamp,1h)" },
16
+ { "name": "Predictive-coding accuracy by matter", "kql": "PredictiveCodingResults | where MatterCreatedAt > ago(30d) | summarize avg(F1Score) by MatterId" }
17
+ ]
18
+ },
19
+ "icm": {
20
+ "tenant": "PROD",
21
+ "serviceName": "Microsoft Purview / eDiscovery",
22
+ "teamPublicId": "TBD-fill-from-IcM-portal"
23
+ },
24
+ "github": {
25
+ "primaryRepo": "microsoft/m365-purview-ediscovery"
26
+ },
27
+ "personas": [
28
+ { "role": "Outside counsel / litigation lead" },
29
+ { "role": "In-house Legal Ops" },
30
+ { "role": "Engineering Manager" },
31
+ { "role": "Premier support — Legal escalations" }
32
+ ],
33
+ "defaultDirectives": [
34
+ { "id": "D1", "title": "Review-set build P95 latency under 2 minutes", "successSignal": "P95 < 120s for 30 consecutive days", "cadence": "Weekly" },
35
+ { "id": "D2", "title": "Predictive coding F1 > 0.85 on 5 customer matters", "successSignal": "5 matters validated by counsel", "cadence": "Quarterly" },
36
+ { "id": "D3", "title": "AI deposition-prep assistant: ship preview", "successSignal": "10 firms in preview + NPS > 40", "cadence": "60 days" },
37
+ { "id": "D4", "title": "Custodian hold notice automation", "successSignal": "Median notice cycle < 1 hour", "cadence": "Quarterly" }
38
+ ],
39
+ "defaultKpis": {
40
+ "reviewSetP95": { "label": "Review-set build P95", "unit": "seconds", "target": "< 120", "owner": "self" },
41
+ "predictiveCodingF1":{ "label": "Predictive coding F1", "unit": "score", "target": "> 0.85","owner": "self" },
42
+ "depositionPreviewFirms":{"label": "Deposition-prep preview firms","unit": "firms","target":"10","owner":"self" },
43
+ "holdNoticeCycle": { "label": "Custodian hold notice cycle","unit": "hours", "target": "< 1", "owner": "self" }
44
+ },
45
+ "recommendedMcp": ["kusto", "icm", "workiq", "ado", "playwright"],
46
+ "quickLinks": [
47
+ { "label": "eDiscovery PM workspace", "url": "https://aka.ms/purview-ediscovery-pm" },
48
+ { "label": "Legal Hold docs", "url": "https://learn.microsoft.com/purview/ediscovery-holds" }
49
+ ],
50
+ "notes": [
51
+ "Predictive-coding accuracy must be validated by outside counsel before being used as a marketed metric — never publish self-reported F1."
52
+ ]
53
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "id": "purview-insider-risk",
3
+ "displayName": "Purview Insider Risk Management (IRM)",
4
+ "tagline": "ML-driven detection of risky insider activity — data theft, departures, IP exfil — with privacy-by-design.",
5
+ "ado": {
6
+ "org": "o365exchange",
7
+ "project": "M365 Security",
8
+ "areaPath": "M365 Security\\Compliance\\InsiderRisk"
9
+ },
10
+ "kusto": {
11
+ "cluster": "https://cxedataplatformcluster.westus2.kusto.windows.net/",
12
+ "database": "PurviewIRM",
13
+ "starterQueries": [
14
+ { "name": "Active risk-policy alerts (28d)", "kql": "IRMAlerts | where Timestamp > ago(28d) | summarize Alerts = count() by PolicyName, bin(Timestamp,1d)" },
15
+ { "name": "Analyst MTTR by alert severity", "kql": "IRMInvestigations | where ClosedAt > ago(30d) | summarize MTTRhrs = avg(datetime_diff('hour', ClosedAt, OpenedAt)) by Severity" },
16
+ { "name": "Departure-risk model precision", "kql": "DepartureModelFeedback | where Timestamp > ago(28d) | summarize Precision = countif(Label == 'TruePositive') * 1.0 / count() by ModelVersion" }
17
+ ]
18
+ },
19
+ "icm": {
20
+ "tenant": "PROD",
21
+ "serviceName": "Microsoft Purview / Insider Risk Management",
22
+ "teamPublicId": "TBD-fill-from-IcM-portal"
23
+ },
24
+ "github": {
25
+ "primaryRepo": "microsoft/m365-purview-irm"
26
+ },
27
+ "personas": [
28
+ { "role": "CISO / Insider Risk lead (customer)" },
29
+ { "role": "Privacy Officer / Works Council partner" },
30
+ { "role": "ML Engineering Manager" },
31
+ { "role": "CELA — Insider Risk" }
32
+ ],
33
+ "defaultDirectives": [
34
+ { "id": "D1", "title": "Departure-risk model precision > 75%", "successSignal": "4 consecutive weeks > 75%", "cadence": "Weekly" },
35
+ { "id": "D2", "title": "Privacy-by-design: pseudonymization GA by EU region", "successSignal": "GA + 3 EU customers live", "cadence": "Quarterly" },
36
+ { "id": "D3", "title": "Copilot risk-summary: cut analyst triage time by 40%", "successSignal": "Median triage < 6 min", "cadence": "60 days" },
37
+ { "id": "D4", "title": "Adaptive Protection: 5 customers in production", "successSignal": "5 named tenants live", "cadence": "Quarterly" }
38
+ ],
39
+ "defaultKpis": {
40
+ "departureModelPrecision":{ "label": "Departure-risk model precision","unit": "%", "target": "> 75%", "owner": "self" },
41
+ "pseudonymizationGa": { "label": "Pseudonymization GA (EU)", "unit": "GA y/n", "target": "GA", "owner": "self" },
42
+ "analystTriageTime": { "label": "Analyst triage time (median)", "unit": "minutes", "target": "< 6", "owner": "self" },
43
+ "adaptiveProtectionAdopters":{"label":"Adaptive Protection adopters", "unit": "tenants", "target": "5", "owner": "self" }
44
+ },
45
+ "recommendedMcp": ["kusto", "icm", "workiq", "ado"],
46
+ "quickLinks": [
47
+ { "label": "IRM PM workspace", "url": "https://aka.ms/purview-irm-pm" },
48
+ { "label": "IRM privacy guidance","url": "https://aka.ms/irm-privacy" }
49
+ ],
50
+ "notes": [
51
+ "Any model-precision number shared externally MUST be reviewed by CELA + Privacy — IRM operates under stricter regulatory scrutiny (BetrVG in DE, equivalents in JP/FR)."
52
+ ]
53
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "id": "purview-records",
3
+ "displayName": "Purview Records Management",
4
+ "tagline": "Regulatory recordkeeping — declare, manage, dispose. SEC 17a-4, FINRA, GDPR, data-residency.",
5
+ "ado": {
6
+ "org": "o365exchange",
7
+ "project": "O365 Core",
8
+ "areaPath": "O365 Core\\Compliance\\RecordsManagement"
9
+ },
10
+ "kusto": {
11
+ "cluster": "https://cxedataplatformcluster.westus2.kusto.windows.net/",
12
+ "database": "PurviewRecords",
13
+ "starterQueries": [
14
+ { "name": "Records declared / day (28d)", "kql": "RecordEvents | where EventType == 'Declare' and Timestamp > ago(28d) | summarize Declared = count() by bin(Timestamp,1d)" },
15
+ { "name": "Disposition aging (P95)", "kql": "DispositionQueue | summarize P95days = percentile(AgeDays, 95) by Workload" },
16
+ { "name": "Hold conflicts (open)", "kql": "HoldConflicts | where Status == 'Open' | summarize Open = count() by HoldType" }
17
+ ]
18
+ },
19
+ "icm": {
20
+ "tenant": "PROD",
21
+ "serviceName": "Microsoft Purview / Records Management",
22
+ "teamPublicId": "TBD-fill-from-IcM-portal"
23
+ },
24
+ "github": {
25
+ "primaryRepo": "microsoft/m365-purview-records"
26
+ },
27
+ "personas": [
28
+ { "role": "Records Compliance Officer (customer)" },
29
+ { "role": "CELA — Records & Retention" },
30
+ { "role": "Engineering Manager" },
31
+ { "role": "FINRA / SEC field specialist" }
32
+ ],
33
+ "defaultDirectives": [
34
+ { "id": "D1", "title": "SEC 17a-4 (f) compliance audit ready", "successSignal": "Third-party assessment passes", "cadence": "Quarterly" },
35
+ { "id": "D2", "title": "Disposition P95 aging under 14 days", "successSignal": "P95 < 14d for 4 consecutive weeks", "cadence": "Weekly" },
36
+ { "id": "D3", "title": "AI auto-classify regulated records (pilot)", "successSignal": "10 customers + > 80% accuracy", "cadence": "60 days" },
37
+ { "id": "D4", "title": "Hold-conflict UX overhaul", "successSignal": "Conflicts cleared 50% faster", "cadence": "Quarterly" }
38
+ ],
39
+ "defaultKpis": {
40
+ "secAuditStatus": { "label": "SEC 17a-4 (f) audit status", "unit": "pass/fail", "target": "pass", "owner": "CELA" },
41
+ "dispositionP95": { "label": "Disposition P95 aging", "unit": "days", "target": "< 14", "owner": "self" },
42
+ "aiClassifyAccuracy":{ "label": "AI classifier accuracy", "unit": "%", "target": "> 80%", "owner": "self" },
43
+ "holdConflictMttr": { "label": "Hold-conflict MTTR", "unit": "days", "target": "-50%", "owner": "self" }
44
+ },
45
+ "recommendedMcp": ["kusto", "icm", "workiq", "ado"],
46
+ "quickLinks": [
47
+ { "label": "Records PM workspace", "url": "https://aka.ms/purview-records-pm" },
48
+ { "label": "SEC 17a-4 guidance", "url": "https://aka.ms/sec17a4" }
49
+ ],
50
+ "notes": [
51
+ "Regulator-facing KPIs must be reviewed by CELA before being externalized — keep target language conservative."
52
+ ]
53
+ }
@@ -0,0 +1,56 @@
1
+ ---
2
+ description: One-word trigger — audit any PRD, work-item, or shipped surface against Hayete's 4 CXE pillars (CXE-first / Value-fast / Confidence / Support-when-it-matters).
3
+ ---
4
+ # /cxe — Hayete CXE pillar audit
5
+
6
+ When the user types `/cxe` (alone, or `/cxe <id-or-path>`), run a 4-pillar audit.
7
+
8
+ ## Inputs
9
+ - If no arg: audit the most recent PRD in `competitive-intel-agent/prd-store.json` and the top 3 active directives from `~/Documents/Connect/db/connect-db.json`.
10
+ - If `<id>`: audit that PRD id.
11
+ - If `<path>`: audit the file at that path (PRD .md / .docx / .html or ADO work-item .json export).
12
+
13
+ ## Pillars (verbatim from Hayete, 2026-05-29)
14
+
15
+ | # | Pillar | The lens |
16
+ |---|---|---|
17
+ | **P1** | CXE = core priority | Is the customer experience the lead, not a follow-up? |
18
+ | **P2** | Value fast (TTV) | Median time from install/enable → first measurable customer value? |
19
+ | **P3** | Operate with confidence | Can the customer predict what the product will do next? Reversible? Auditable? |
20
+ | **P4** | Support when it matters | Right help at the right place at the right moment — in-product, contextual, not a doc link. |
21
+
22
+ ## Scoring rubric
23
+
24
+ Each pillar: 0 / 1 / 2.
25
+ - **0** = not addressed (or worse, contradicted)
26
+ - **1** = acknowledged but no measurable target / no shipping plan
27
+ - **2** = explicit, measurable target with owner + by-when
28
+
29
+ Total /8. Bands: 7-8 *Strong* · 5-6 *Acceptable* · 3-4 *Weak* · 0-2 *Reject*.
30
+
31
+ ## Hard rule
32
+
33
+ Any artefact scoring 0 on P1 (CXE not the lead) cannot proceed regardless of total — flag for rewrite.
34
+
35
+ ## Output format
36
+
37
+ ```
38
+ CXE Audit · <artefact-name> · <total>/8 · <band>
39
+
40
+ Pillar Score Evidence / Gap
41
+ P1 CXE = core priority x/2 <one-line proof or gap>
42
+ P2 Value fast (TTV) x/2 <TTV measured or "no baseline">
43
+ P3 Operate with confidence x/2 <reversibility/auditability proof or gap>
44
+ P4 Support when it matters x/2 <in-product help mechanism or gap>
45
+
46
+ Hard-rule status: <pass | REJECT — P1=0>
47
+ Biggest gap: <one sentence>
48
+ Next move: <one specific edit + owner + by-when>
49
+ ```
50
+
51
+ Then close with the standard 3-block (Transformative tip / Hayete AMA / NPF Reach Expansion) — the **Hayete AMA line must now reference whichever pillar the audit found weakest**, so the rubric drives the close, not generic alignment.
52
+
53
+ ## Notes
54
+ - This is candid, not flattering. A 3/8 is a real signal that the artefact needs a CXE rewrite, not a re-edit.
55
+ - TTV (P2) cannot be scored 2 without a *measured* baseline number — "TTV is fast" doesn't count.
56
+ - Support (P4) must be **in-product**. A docs link is P4=1, not P4=2.
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: One-word trigger — assess current NPF (Net Productivity Factor) performance from connect-db.json + recent CI reports, score against L3 Transformative band, and recommend the single highest-leverage next move.
3
+ ---
4
+ # /npf — Net Productivity Factor assessment
5
+
6
+ When the user types `/npf` (or just "npf"), run this assessment.
7
+
8
+ ## Steps
9
+
10
+ 1. Read `C:\Users\pbhamri\Documents\Connect\db\connect-db.json` → extract `npfHistory[]`, `kpis`, `goals`, `directives`, `todos`.
11
+ 2. Compute:
12
+ - **Current NPF**: latest `npfHistory[].index` + band
13
+ - **Week-over-week delta**: latest − snapshot from 7 days ago
14
+ - **Trend**: rising / flat / falling
15
+ - **L3 band proof**: count of evidence in last 7 days (skills shipped, §7 rules, frameworks added, paved-path artefacts, ADO/PR telemetry-tagged work)
16
+ 3. Score against the four TRANSFORMATIVE behaviors (each 0–25, total /100):
17
+ - **Leverage** — shared assets built (skills, agents, paved paths, templates)
18
+ - **Reach** — peers / workloads who can reuse this week's output
19
+ - **Cycle-time collapse** — weeks→days or days→minutes proof
20
+ - **Outcome focus** — customer/business metric movement vs activity counts
21
+ 4. Identify the **single biggest NPF leak** (KPI stuck at 0 / blocker on a directive / overdue todo / no real NPF capture).
22
+ 5. Recommend **one move** that would lift NPF by ≥5 points before the next refresh.
23
+
24
+ ## Output format
25
+
26
+ ```
27
+ NPF: <score>/100 · <band> · <trend arrow> <delta> wk/wk
28
+
29
+ Behavior Score Evidence
30
+ Leverage xx/25 <1-line proof>
31
+ Reach xx/25 <1-line proof>
32
+ Cycle-time xx/25 <1-line proof>
33
+ Outcome focus xx/25 <1-line proof>
34
+
35
+ Biggest leak: <one sentence>
36
+ Next move (≥+5): <one specific action with owner + by-when>
37
+ ```
38
+
39
+ Then close with the standard 3-block (Transformative tip / Hayete AMA / NPF Reach Expansion).
40
+
41
+ ## Notes
42
+
43
+ - The carried-forward NPF (note contains "auto-carried") is a placeholder, not a real score — call it out and *overwrite* it as part of this assessment by editing `connect-db.json` directly with your real computed score.
44
+ - Never score above 80 without at least one shipped artefact a *peer PM has actually consumed* (proof of reach, not just availability).
45
+ - This is a self-assessment for the user's growth — be candid, not flattering.