@peppermint-mcp/wizard 0.1.3 → 0.2.2

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/dist/cli.js CHANGED
@@ -128,20 +128,28 @@ import { promisify as promisify2 } from "util";
128
128
  var exec2 = promisify2(execFile2);
129
129
  async function detectCodex() {
130
130
  let version;
131
+ const warnings = [];
131
132
  try {
132
133
  const { stdout } = await exec2("codex", ["--version"], { timeout: 5e3 });
133
134
  version = stdout.trim().split("\n")[0];
134
135
  } catch {
135
136
  return null;
136
137
  }
138
+ let alreadyInstalled = false;
139
+ try {
140
+ const { stdout } = await exec2("codex", ["mcp", "list"], { timeout: 1e4 });
141
+ alreadyInstalled = stdout.includes("peppermint-memory") || stdout.includes("peppermint");
142
+ } catch {
143
+ warnings.push("Could not check existing MCP config");
144
+ }
137
145
  return {
138
146
  id: "codex",
139
147
  name: "Codex CLI",
140
148
  version,
141
149
  installMethod: "cli",
142
- alreadyInstalled: false,
150
+ alreadyInstalled,
143
151
  needsRestart: false,
144
- warnings: []
152
+ warnings
145
153
  };
146
154
  }
147
155
 
@@ -259,7 +267,7 @@ async function createApiKey(serverBase2, accessToken) {
259
267
  return data.raw_key;
260
268
  }
261
269
  function waitForCallback(port, expectedState) {
262
- return new Promise((resolve, reject) => {
270
+ return new Promise((resolve2, reject) => {
263
271
  const timeout = setTimeout(() => {
264
272
  server.close();
265
273
  reject(new Error("Authentication timed out (5 minutes). Please try again."));
@@ -301,15 +309,15 @@ function waitForCallback(port, expectedState) {
301
309
  );
302
310
  clearTimeout(timeout);
303
311
  server.close();
304
- resolve({ code });
312
+ resolve2({ code });
305
313
  });
306
314
  server.listen(port, "127.0.0.1");
307
315
  });
308
316
  }
309
317
  async function authenticateWithBrowser(serverBase2) {
310
318
  const tempServer = createServer();
311
- await new Promise((resolve) => {
312
- tempServer.listen(0, "127.0.0.1", () => resolve());
319
+ await new Promise((resolve2) => {
320
+ tempServer.listen(0, "127.0.0.1", () => resolve2());
313
321
  });
314
322
  const port = tempServer.address().port;
315
323
  tempServer.close();
@@ -722,6 +730,129 @@ function checkHostConfig(hostId, configPath) {
722
730
  }
723
731
  }
724
732
 
733
+ // src/skills/index.ts
734
+ import {
735
+ copyFileSync as copyFileSync2,
736
+ existsSync as existsSync7,
737
+ mkdirSync as mkdirSync4,
738
+ readdirSync,
739
+ rmSync,
740
+ statSync
741
+ } from "fs";
742
+ import { homedir as homedir7 } from "os";
743
+ import { dirname as dirname3, join as join7, resolve } from "path";
744
+ import { fileURLToPath } from "url";
745
+ var __dirname = dirname3(fileURLToPath(import.meta.url));
746
+ var LEGACY_SKILLS = [
747
+ "peppermint-recall",
748
+ "peppermint-capture",
749
+ "peppermint-ask-twin"
750
+ ];
751
+ function getSkillsBundlePath() {
752
+ return resolve(__dirname, "..", "skills-bundle", "peppermint");
753
+ }
754
+ function getTargetPath() {
755
+ return join7(homedir7(), ".claude", "skills", "peppermint");
756
+ }
757
+ function copyDirRecursive(src, dest) {
758
+ mkdirSync4(dest, { recursive: true });
759
+ for (const entry of readdirSync(src)) {
760
+ const srcPath = join7(src, entry);
761
+ const destPath = join7(dest, entry);
762
+ if (statSync(srcPath).isDirectory()) {
763
+ copyDirRecursive(srcPath, destPath);
764
+ } else {
765
+ copyFileSync2(srcPath, destPath);
766
+ }
767
+ }
768
+ }
769
+ function removeLegacySkills(dryRun) {
770
+ const removed = [];
771
+ const skillsDir = join7(homedir7(), ".claude", "skills");
772
+ for (const name of LEGACY_SKILLS) {
773
+ const path = join7(skillsDir, name);
774
+ if (existsSync7(path)) {
775
+ if (!dryRun) {
776
+ rmSync(path, { recursive: true, force: true });
777
+ }
778
+ removed.push(name);
779
+ }
780
+ }
781
+ return removed;
782
+ }
783
+ function installSkills(dryRun) {
784
+ const bundlePath = getSkillsBundlePath();
785
+ const targetPath = getTargetPath();
786
+ const alreadyExists = existsSync7(join7(targetPath, "SKILL.md"));
787
+ if (!existsSync7(bundlePath)) {
788
+ return { installed: false, updated: false, targetPath, error: "Skills bundle not found in package" };
789
+ }
790
+ if (dryRun) {
791
+ return { installed: true, updated: alreadyExists, targetPath };
792
+ }
793
+ try {
794
+ if (existsSync7(targetPath)) {
795
+ rmSync(targetPath, { recursive: true, force: true });
796
+ }
797
+ copyDirRecursive(bundlePath, targetPath);
798
+ return { installed: true, updated: alreadyExists, targetPath };
799
+ } catch (err) {
800
+ const message = err instanceof Error ? err.message : "Failed to install skills";
801
+ return { installed: false, updated: false, targetPath, error: message };
802
+ }
803
+ }
804
+
805
+ // src/skills/permissions.ts
806
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
807
+ import { homedir as homedir8 } from "os";
808
+ import { dirname as dirname4, join as join8 } from "path";
809
+ import * as jsonc5 from "jsonc-parser";
810
+ var REQUIRED_PERMISSIONS = [
811
+ "mcp__peppermint-memory__search",
812
+ "mcp__peppermint-memory__get",
813
+ "mcp__peppermint-memory__discover_tools",
814
+ "mcp__peppermint-memory__ask_twin",
815
+ "mcp__peppermint-memory__query_integration",
816
+ "mcp__peppermint-memory__create_memory",
817
+ "mcp__peppermint-memory__create_fact",
818
+ "mcp__peppermint-memory__update_memory"
819
+ ];
820
+ function getSettingsPath() {
821
+ return join8(homedir8(), ".claude", "settings.json");
822
+ }
823
+ function installPermissions(dryRun) {
824
+ const settingsPath = getSettingsPath();
825
+ try {
826
+ let content = "";
827
+ if (existsSync8(settingsPath)) {
828
+ content = readFileSync7(settingsPath, "utf-8");
829
+ }
830
+ const parsed = content ? jsonc5.parse(content) : {};
831
+ const existingAllow = parsed?.permissions?.allow || [];
832
+ const toAdd = REQUIRED_PERMISSIONS.filter((p2) => !existingAllow.includes(p2));
833
+ if (toAdd.length === 0) {
834
+ return { added: [] };
835
+ }
836
+ if (dryRun) {
837
+ return { added: toAdd };
838
+ }
839
+ const newAllow = [...existingAllow, ...toAdd];
840
+ const edits = jsonc5.modify(content, ["permissions", "allow"], newAllow, {
841
+ formattingOptions: { tabSize: 2, insertSpaces: true }
842
+ });
843
+ const updated = jsonc5.applyEdits(content, edits);
844
+ const dir = dirname4(settingsPath);
845
+ if (!existsSync8(dir)) {
846
+ mkdirSync5(dir, { recursive: true });
847
+ }
848
+ writeFileSync4(settingsPath, updated, "utf-8");
849
+ return { added: toAdd };
850
+ } catch (err) {
851
+ const message = err instanceof Error ? err.message : "Failed to update permissions";
852
+ return { added: [], error: message };
853
+ }
854
+ }
855
+
725
856
  // src/cli.ts
726
857
  var DEFAULT_SERVER = "https://api.peppermint.com/mcp/";
727
858
  function serverBase(serverUrl) {
@@ -832,6 +963,28 @@ Check your internet connection and try again.`
832
963
  p.log.info(` ${icon} ${host.name} ${pc.dim(result.message)}`);
833
964
  results.push({ host, result });
834
965
  }
966
+ const hasClaudeHost = hosts.some(
967
+ (h) => h.id === "claude-code" || h.id === "claude-desktop"
968
+ );
969
+ if (hasClaudeHost) {
970
+ const removed = removeLegacySkills(options.dryRun);
971
+ if (removed.length > 0) {
972
+ p.log.info(` ${pc.green("\u2713")} Removed legacy skills: ${pc.dim(removed.join(", "))}`);
973
+ }
974
+ const skillResult = installSkills(options.dryRun);
975
+ if (skillResult.installed) {
976
+ const verb = skillResult.updated ? "Updated" : "Installed";
977
+ p.log.info(` ${pc.green("\u2713")} ${verb} Peppermint skill ${pc.dim(skillResult.targetPath)}`);
978
+ } else if (skillResult.error) {
979
+ p.log.info(` ${pc.red("\u2717")} Skill install failed: ${pc.dim(skillResult.error)}`);
980
+ }
981
+ const permsResult = installPermissions(options.dryRun);
982
+ if (permsResult.error) {
983
+ p.log.info(` ${pc.red("\u2717")} Permissions: ${pc.dim(permsResult.error)}`);
984
+ } else if (permsResult.added.length > 0) {
985
+ p.log.info(` ${pc.green("\u2713")} Added ${permsResult.added.length} tool permissions to Claude Code settings`);
986
+ }
987
+ }
835
988
  if (options.verify && !options.dryRun) {
836
989
  p.log.step("Verifying...");
837
990
  for (const { host } of results) {
@@ -843,9 +996,7 @@ Check your internet connection and try again.`
843
996
  const needRestart = results.filter((r) => r.result.needsRestart);
844
997
  const failed = results.filter((r) => !r.result.success);
845
998
  p.outro(
846
- failed.length > 0 ? pc.red(`${failed.length} host(s) failed. Check the output above.`) : needRestart.length > 0 ? pc.green("Done!") + pc.dim(
847
- ` Restart ${needRestart.map((r) => r.host.name).join(", ")} to finish.`
848
- ) : pc.green("Done! Peppermint MCP is ready.")
999
+ failed.length > 0 ? pc.red(`${failed.length} host(s) failed. Check the output above.`) : hasClaudeHost ? pc.green("Done!") + (needRestart.length > 0 ? pc.dim(` Restart ${needRestart.map((r) => r.host.name).join(", ")} to finish.`) : "") + "\n\n " + pc.cyan("Peppermint skill installed. Start a new Claude Code session and type") + "\n " + pc.cyan("@pep or /peppermint to begin onboarding.") : needRestart.length > 0 ? pc.green("Done!") + pc.dim(` Restart ${needRestart.map((r) => r.host.name).join(", ")} to finish.`) : pc.green("Done! Peppermint MCP is ready.")
849
1000
  );
850
1001
  if (failed.length > 0) process.exit(2);
851
1002
  }
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@peppermint-mcp/wizard",
3
- "version": "0.1.3",
3
+ "version": "0.2.2",
4
4
  "description": "One-command installer for Peppermint MCP across AI coding hosts",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "peppermint-mcp-wizard": "dist/cli.js"
8
8
  },
9
9
  "files": [
10
- "dist/"
10
+ "dist/",
11
+ "skills-bundle/"
11
12
  ],
12
13
  "scripts": {
13
14
  "build": "tsup",
@@ -0,0 +1,205 @@
1
+ ---
2
+ name: peppermint
3
+ description: Peppermint memory companion — onboarding, @pep queries, passive capture.
4
+ when_to_trigger: |
5
+ - User types `@pep <...>` or `/peppermint <...>`
6
+ - User states a durable preference, rule, or identity claim
7
+ - User asks about prior work, past decisions, or historical context
8
+ - User references a teammate's work or asks what someone else is doing
9
+ allowed-tools:
10
+ - mcp__peppermint-memory__search
11
+ - mcp__peppermint-memory__get
12
+ - mcp__peppermint-memory__discover_tools
13
+ - mcp__peppermint-memory__create_memory
14
+ - mcp__peppermint-memory__create_fact
15
+ - mcp__peppermint-memory__update_memory
16
+ - mcp__peppermint-memory__ask_twin
17
+ - mcp__peppermint-memory__query_integration
18
+ - Read
19
+ - Write
20
+ - Edit
21
+ ---
22
+
23
+ # Peppermint companion skill
24
+
25
+ You are an assistant with access to Peppermint — a persistent memory system that captures what the user sees, hears, reads, and works on across their devices. Peppermint stores memories, facts, daily summaries, and integration data (Linear, Asana, Slack, Google Workspace, GitHub).
26
+
27
+ This skill handles three modes: **onboarding** (first run), **@pep queries** (daily use), and **passive capture** (background).
28
+
29
+ ## 0. Host detection
30
+
31
+ Detect the host environment:
32
+ - If the user has `~/.claude/CLAUDE.md` or you're running inside Claude Code → **claude-code**
33
+ - If you detect Codex CLI markers → **codex**
34
+ - If you detect Gemini markers → **gemini**
35
+ - Otherwise → **unknown**
36
+
37
+ For v1, only **claude-code** gets the full onboarding flow. All hosts get `@pep` queries and passive capture.
38
+
39
+ ---
40
+
41
+ ## 1. Onboarding (claude-code only)
42
+
43
+ ### 1.1 Detection — should onboarding run?
44
+
45
+ Check two signals:
46
+ 1. **Server signal:** `search(query="onboarded claude-code", scope="facts", limit=1)` — look for a `skill_state` fact with subject `claude-code`
47
+ 2. **File signal:** Check if `~/.claude/CLAUDE.md` contains `<!-- peppermint:onboarded`
48
+
49
+ | Server fact | File exists | Action |
50
+ |---|---|---|
51
+ | Missing | Missing | Run full onboarding |
52
+ | Present | Present | Skip — print "Peppermint is connected. Use @pep to query your memory." |
53
+ | One missing | One present | Warn about inconsistent state, run onboarding |
54
+
55
+ ### 1.2 Onboarding flow
56
+
57
+ 1. **Greet:** "Setting up Peppermint memory sync for Claude Code."
58
+
59
+ 2. **Parse host memory files:**
60
+ - Read `~/.claude/CLAUDE.md` (global)
61
+ - Glob `~/.claude/projects/*/memory/*.md` (project memories)
62
+ - Extract structured claims using the parse-claims sub-prompt (see `prompts/parse-claims.md`)
63
+ - Each claim becomes: `{content, inferred_family, confidence}`
64
+
65
+ 3. **Diff against Peppermint graph:**
66
+ - For each batch of 10 claims, call `search(query=<claim>, scope="facts", limit=3)` in parallel
67
+ - Categorize each claim using the diff-categorize sub-prompt (see `prompts/diff-categorize.md`)
68
+ - Categories: ADD (new to Peppermint), UPDATE (refines existing), ADD-to-host (exists in Peppermint but not host), CONFLICT (contradicts), SKIP (trivial/duplicate)
69
+
70
+ 4. **Present summary with top-3 examples per category:**
71
+ ```
72
+ Found 23 claims in your Claude Code memory.
73
+
74
+ New to Peppermint (8):
75
+ - "Prefers mental-model-first explanations"
76
+ - "Uses uv for Python package management"
77
+ - "Working on Peppermint MCP server architecture"
78
+ ... and 5 more
79
+
80
+ Already in Peppermint (12): synced
81
+ Conflicts (1): "Role: CTO" vs Peppermint fact "Role: Co-founder & CTO"
82
+ To add to Claude Code (2): ...
83
+ ```
84
+
85
+ 5. **Confirm:** Ask "Want me to sync these? I'll save the new claims to Peppermint and update your identity core." Accept freeform confirmation.
86
+
87
+ 6. **Commit:**
88
+ - Save ADD claims as facts: `create_fact(content=..., fact_family=..., fact_subject=...)`
89
+ - Save UPDATE claims as facts (upsert behavior)
90
+ - Resolve CONFLICTs per user instruction
91
+ - Append the identity core to `~/.claude/CLAUDE.md` inside a marker fence (see identity-core template). If a previous `<!-- peppermint:onboarded` block exists, replace it in-place. This ensures the identity is always in Claude Code's context window.
92
+ - Save onboarding marker: `create_fact(content="onboarded v=1 date=<today>", fact_family="skill_state", fact_subject="claude-code")`
93
+
94
+ 7. **Post-onboarding training block:**
95
+ ```
96
+ ## How to use Peppermint from here on
97
+
98
+ **@pep <question>** — Pull memory into a decision in flight. This is the daily driver.
99
+ Examples: "@pep what was decided about the auth middleware?", "@pep what's PEP-371 status?"
100
+
101
+ **@pep ask <teammate> <question>** — Ask a teammate's digital twin.
102
+ Their twin answers from their memories. They're notified when their twin is queried.
103
+ Example: "@pep ask Sabrina what's the status of the PII layer fix?"
104
+
105
+ **@pep refresh** — Re-sync your identity core when things drift.
106
+
107
+ Peppermint also runs passively — it captures durable preferences and decisions
108
+ you state during normal work, without you having to invoke it.
109
+ ```
110
+
111
+ 8. **5-section reinforcement summary:** Generate using `prompts/reinforcement.md` and display as a sanity check. Ask "Does this look right?"
112
+
113
+ ---
114
+
115
+ ## 2. @pep handler
116
+
117
+ Users invoke this via `@pep <question>`, `/peppermint <question>`, or natural language that references prior work/decisions.
118
+
119
+ ### 2.1 Self-query (default): route through ask_twin
120
+
121
+ For any `@pep <question>` that isn't a sub-command:
122
+
123
+ 1. Call `ask_twin(person="me", question="<the question>", context="<last 3-5 conversation turns if available>")`
124
+ 2. This routes through the **exact same pipeline** as the Slack twin:
125
+ - 7 parallel backend queries (memories, facts, summaries, integrations, routines)
126
+ - 8-turn agentic Gemini tool loop with 11 tools
127
+ - Privacy domain filtering
128
+ - All attribution/commitment/identity rules
129
+ 3. Strip the `[<name>'s Twin]` header from the response (self-queries don't need it)
130
+ 4. Present the response directly to the user
131
+
132
+ **Context passing:** When the user has been in a multi-turn conversation before invoking `@pep`, include the last 3-5 turns as the `context` parameter. This helps the twin understand references like "tell me more about that" or "what about the other option?"
133
+
134
+ ### 2.2 Sub-commands
135
+
136
+ **`@pep ask <person> <question>`**
137
+ 1. Call `ask_twin(person="<person>", question="<question>", context="<conversation context>")`
138
+ 2. Present with `[<person>'s Twin]` header
139
+ 3. **Pass through near-verbatim** — the twin's response already follows attribution/commitment rules. Do NOT:
140
+ - Add commitments ("they'll get back to you")
141
+ - Re-attribute ("the team confirmed" when the twin said "Sabrina confirmed")
142
+ - Re-synthesize or editorialize
143
+ 4. On error (twin declined, user not found, no context), show the specific error message from the tool
144
+
145
+ **`@pep refresh`**
146
+ 1. Re-run the onboarding flow (§1.2) without the detection step
147
+ 2. Replace the `<!-- peppermint:onboarded ... -->` block in `~/.claude/CLAUDE.md` with the new identity core
148
+ 3. Update the `skill_state` fact with new date
149
+
150
+ **`@pep status`**
151
+ 1. Call `get(kind="stats")` for memory stats
152
+ 2. Call `search(query="connected", scope="facts", limit=5)` for integration status
153
+ 3. Report: memory count, date range, connected integrations, last sync time
154
+
155
+ ---
156
+
157
+ ## 3. Passive capture
158
+
159
+ During normal conversation (not `@pep` invocations), watch for signals that the user stated something durable. Capture it silently.
160
+
161
+ ### 3.1 Four-signal detection
162
+
163
+ A statement is worth capturing when ALL of these are true:
164
+ 1. **Durable:** Would still be true next week (not "I'm debugging this right now")
165
+ 2. **First-person:** The user is stating their own preference/decision/rule (not quoting someone)
166
+ 3. **New or contradictory:** Not already captured in Peppermint (quick `search` check)
167
+ 4. **Non-trivial:** Would change how an agent assists them ("I prefer TypeScript" yes; "I like coffee" no)
168
+
169
+ ### 3.2 Don't capture list
170
+
171
+ Never capture:
172
+ - One-off debugging statements ("this variable is null")
173
+ - Emotional reactions ("this is frustrating")
174
+ - Questions the user is asking (they're seeking info, not stating facts)
175
+ - Code snippets or terminal output
176
+ - Anything about family, hobbies, entertainment, health, or personal life
177
+
178
+ ### 3.3 Capture action
179
+
180
+ When a capturable signal is detected:
181
+ 1. Activate the capture pack: `discover_tools(activate=["capture"])`
182
+ 2. Determine if it's a new fact or memory:
183
+ - **Facts** (durable identity/preference/role): `create_fact(content=..., fact_family=..., fact_subject=...)`
184
+ - **Memories** (decisions, commitments, project context): `create_memory(content=..., tags=[...], importance=0.7)`
185
+ 3. If contradicting an existing fact: note inline — "Updated your preference from X to Y in Peppermint."
186
+ 4. If new: capture silently (no notification to user)
187
+
188
+ ### 3.4 Budget
189
+
190
+ - Max 1 capture per conversation turn
191
+ - Max 15 captures per session
192
+ - If budget exhausted, stop watching (don't queue)
193
+
194
+ ---
195
+
196
+ ## 4. Error handling
197
+
198
+ | Error | Action |
199
+ |---|---|
200
+ | MCP server unreachable | Print "Peppermint is offline — memory features unavailable this session." Disable capture for the session. |
201
+ | `ask_twin` returns decline | Print "[person]'s twin doesn't have enough context to answer that right now." |
202
+ | `ask_twin` user not found | Print "No one matching '[person]' found in your organization." |
203
+ | `ask_twin` no org | Print "You need to be part of an organization to query teammates' twins." |
204
+ | `search` returns empty | Answer from conversation context alone, note "No relevant memories found in Peppermint." |
205
+ | Onboarding — < 10 facts | Run onboarding but note: "Your Peppermint memory is still building up. The more you use your devices with Peppermint running, the richer your memory gets." |
@@ -0,0 +1,55 @@
1
+ # Identity core template
2
+
3
+ This template is used by the Peppermint skill to generate the identity core block
4
+ appended to `~/.claude/CLAUDE.md`. The skill fills in each section from the user's
5
+ Peppermint fact graph, wraps it in marker fences, and appends it to CLAUDE.md
6
+ (or replaces an existing block on refresh).
7
+
8
+ ## Output format
9
+
10
+ ```markdown
11
+ <!-- peppermint:onboarded v=1 date=YYYY-MM-DD -->
12
+ ## Peppermint identity core
13
+
14
+ ### Who you are at work
15
+ - Role: {current_role fact}
16
+ - Company: {current_employer fact}
17
+ - Department: {department fact}
18
+ - Expertise: {expertise facts, comma-separated}
19
+
20
+ ### How you work
21
+ - {preference facts — tools, languages, workflows}
22
+ - {routine facts — daily patterns, work hours}
23
+
24
+ ### Who you work with
25
+ - {team_membership facts — direct collaborators, max 5}
26
+ - {reporting_structure facts}
27
+
28
+ ### What you're working on
29
+ - {current_work facts, max 5 most recent}
30
+ - {project_assignments facts}
31
+ - {current_blocker facts if any}
32
+
33
+ ### Rules and preferences
34
+ - {preference facts that are rules — "always do X", "never do Y"}
35
+ - {identity facts that constrain behavior}
36
+ <!-- peppermint:end -->
37
+ ```
38
+
39
+ ## Token budget
40
+
41
+ Target: 500-800 tokens. Hard cap: 800 tokens.
42
+
43
+ ### Priority cuts (when over budget)
44
+
45
+ 1. Cut oldest "What you're working on" items (keep max 5)
46
+ 2. Cut collaborators beyond top 5
47
+ 3. Cut lowest-frequency rules/preferences
48
+ 4. Cut department/reporting_structure if space is tight
49
+
50
+ ### Work-relevance test
51
+
52
+ For each fact, ask: "Would knowing this change the agent's advice on a coding task?"
53
+
54
+ **Include:** role, tools, languages, frameworks, team, active projects, coding preferences, communication style
55
+ **Exclude:** family, hobbies, entertainment, health, food preferences, personal life
@@ -0,0 +1,41 @@
1
+ # Diff and categorize claims against Peppermint graph
2
+
3
+ Given a set of claims extracted from host memory files and the Peppermint search results for each, categorize what action to take.
4
+
5
+ ## Input
6
+
7
+ For each claim, you receive:
8
+ - The claim: `{content, inferred_family, confidence}`
9
+ - Search results from Peppermint: 0-3 matching facts with content and similarity
10
+
11
+ ## Categories
12
+
13
+ - **ADD** — Claim is new. No matching fact in Peppermint. Save it.
14
+ - **UPDATE** — Claim refines or updates an existing Peppermint fact (same topic, newer/more specific info). Save as update.
15
+ - **ADD-to-host** — Peppermint has this fact but the host memory file doesn't. Note for the user (don't auto-write to host files).
16
+ - **CONFLICT** — Claim contradicts a Peppermint fact (e.g., different role, different preference). Flag for user resolution.
17
+ - **SKIP** — Claim is a near-duplicate of an existing fact, or too low-confidence to act on.
18
+
19
+ ## Rules
20
+
21
+ 1. **Semantic matching, not string matching.** "Uses TypeScript" and "Prefers TypeScript for new projects" are the same fact (SKIP or UPDATE depending on specificity).
22
+
23
+ 2. **Recency wins for current_work, current_blocker, project_assignments.** If the claim is more recent than the Peppermint fact, categorize as UPDATE.
24
+
25
+ 3. **CONFLICTs require genuine contradiction.** "CTO" vs "Co-founder & CTO" is not a conflict — it's an UPDATE (more specific). "CTO" vs "VP Engineering" IS a conflict.
26
+
27
+ 4. **Low-confidence claims:** If `confidence=low` and there's no matching Peppermint fact, categorize as SKIP (don't pollute the graph with weak signals).
28
+
29
+ 5. **ADD-to-host is informational only.** The skill doesn't auto-write to CLAUDE.md or project memory files. It just tells the user "Peppermint knows X that your Claude Code memory doesn't."
30
+
31
+ ## Output
32
+
33
+ For each claim, output:
34
+ ```json
35
+ {
36
+ "claim": "...",
37
+ "category": "ADD | UPDATE | ADD-to-host | CONFLICT | SKIP",
38
+ "reason": "one-line explanation",
39
+ "matching_fact_id": "uuid or null"
40
+ }
41
+ ```
@@ -0,0 +1,59 @@
1
+ # Parse claims from host memory files
2
+
3
+ Given the content of a user's memory file (CLAUDE.md, project memory, etc.), extract structured claims about the user.
4
+
5
+ ## Input
6
+
7
+ The raw text content of one or more memory files.
8
+
9
+ ## Output
10
+
11
+ A JSON array of claims:
12
+ ```json
13
+ [
14
+ {
15
+ "content": "Prefers TypeScript over JavaScript for new projects",
16
+ "inferred_family": "preference",
17
+ "confidence": "high"
18
+ },
19
+ {
20
+ "content": "Currently working on Peppermint MCP server architecture",
21
+ "inferred_family": "current_work",
22
+ "confidence": "medium"
23
+ }
24
+ ]
25
+ ```
26
+
27
+ ## Rules
28
+
29
+ 1. **Extract only durable claims** — skip ephemeral task notes, debugging context, and conversation-specific instructions.
30
+
31
+ 2. **Map to fact families:**
32
+ - `identity` — who the user is (name, background, education)
33
+ - `preference` — how they like things done (tools, patterns, styles, rules)
34
+ - `routine` — recurring patterns (daily standup time, review process)
35
+ - `current_work` — what they're actively building/fixing
36
+ - `current_employer` — company name
37
+ - `current_role` — job title
38
+ - `current_blocker` — things blocking progress
39
+ - `team_membership` — direct collaborators
40
+ - `reporting_structure` — who reports to whom
41
+ - `department` — organizational unit
42
+ - `expertise` — skills and domain knowledge
43
+ - `project_assignments` — projects they're on
44
+
45
+ 3. **Confidence levels:**
46
+ - `high` — explicitly stated ("I prefer X", "Always use Y")
47
+ - `medium` — strongly implied by context (listed in preferences section)
48
+ - `low` — weakly implied (mentioned once in passing)
49
+
50
+ 4. **Skip these:**
51
+ - Code snippets and command examples (these are documentation, not claims)
52
+ - Tool configuration details (file paths, API endpoints)
53
+ - Instructions to the AI that aren't about the user ("don't use emojis" is a preference; "respond in JSON" is a formatting instruction)
54
+
55
+ 5. **Normalize:** Write claims as standalone sentences that make sense without the source file context.
56
+
57
+ 6. **Deduplicate:** If the same fact appears multiple times across files, emit it once with the highest confidence.
58
+
59
+ 7. **Work-relevance filter:** Would knowing this change the agent's advice on a work task? If no, skip it. Family, hobbies, entertainment, health, and personal life are always excluded.
@@ -0,0 +1,50 @@
1
+ # Generate 5-section reinforcement summary
2
+
3
+ After onboarding, generate a human-readable summary of what Peppermint knows about the user. This serves as a sanity check — the user reviews it and corrects anything wrong.
4
+
5
+ ## Input
6
+
7
+ The user's Peppermint facts, organized by family. Fetched via:
8
+ - `search(query="identity role employer", scope="facts", limit=20)`
9
+ - `search(query="preference routine workflow", scope="facts", limit=20)`
10
+ - `search(query="team collaborator manager", scope="facts", limit=20)`
11
+ - `search(query="current work project blocker", scope="facts", limit=20)`
12
+
13
+ ## Output format
14
+
15
+ ```
16
+ Here's what Peppermint knows about you:
17
+
18
+ **Who you are at work**
19
+ [Role, company, department, expertise — 2-4 bullets]
20
+
21
+ **How you work**
22
+ [Tools, languages, workflows, patterns — 3-5 bullets]
23
+
24
+ **Who you work with**
25
+ [Key collaborators, team, reporting — 2-4 bullets]
26
+
27
+ **What you're working on**
28
+ [Active projects, current focus — 2-5 bullets]
29
+
30
+ **Rules and preferences**
31
+ [Coding style, communication preferences, hard rules — 2-5 bullets]
32
+
33
+ Does this look right? Tell me if anything is wrong or missing.
34
+ ```
35
+
36
+ ## Rules
37
+
38
+ 1. **Natural language, not raw facts.** Don't say "fact_family: preference, content: uses uv". Say "You use uv for Python package management."
39
+
40
+ 2. **Group related facts.** If there are 3 facts about Python tooling, combine them into one bullet.
41
+
42
+ 3. **Prioritize by work relevance.** Lead each section with the most impactful facts.
43
+
44
+ 4. **Cap each section at 5 bullets.** If more facts exist, pick the highest-value ones.
45
+
46
+ 5. **Work-relevance filter.** Exclude family, hobbies, entertainment, health. If a fact slipped through, drop it silently.
47
+
48
+ 6. **Acknowledge gaps.** If a section is empty (e.g., no team_membership facts), say "Not enough data yet — this section will fill in as Peppermint learns more."
49
+
50
+ 7. **Tone:** Direct, professional, second-person ("You work at...", "Your team includes..."). Not formal, not casual.