@forwardimpact/basecamp 0.1.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.
Files changed (36) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +229 -0
  3. package/build.js +124 -0
  4. package/config/scheduler.json +28 -0
  5. package/package.json +37 -0
  6. package/scheduler.js +552 -0
  7. package/scripts/build-pkg.sh +117 -0
  8. package/scripts/compile.sh +26 -0
  9. package/scripts/install.sh +108 -0
  10. package/scripts/pkg-resources/conclusion.html +62 -0
  11. package/scripts/pkg-resources/welcome.html +64 -0
  12. package/scripts/postinstall +46 -0
  13. package/scripts/uninstall.sh +56 -0
  14. package/template/.claude/settings.json +40 -0
  15. package/template/.claude/skills/create-presentations/SKILL.md +75 -0
  16. package/template/.claude/skills/create-presentations/references/slide.css +35 -0
  17. package/template/.claude/skills/create-presentations/scripts/convert-to-pdf.js +32 -0
  18. package/template/.claude/skills/doc-collab/SKILL.md +112 -0
  19. package/template/.claude/skills/draft-emails/SKILL.md +191 -0
  20. package/template/.claude/skills/draft-emails/scripts/scan-emails.sh +33 -0
  21. package/template/.claude/skills/extract-entities/SKILL.md +466 -0
  22. package/template/.claude/skills/extract-entities/references/TEMPLATES.md +131 -0
  23. package/template/.claude/skills/extract-entities/scripts/state.py +100 -0
  24. package/template/.claude/skills/meeting-prep/SKILL.md +135 -0
  25. package/template/.claude/skills/organize-files/SKILL.md +146 -0
  26. package/template/.claude/skills/organize-files/scripts/organize-by-type.sh +42 -0
  27. package/template/.claude/skills/organize-files/scripts/summarize.sh +21 -0
  28. package/template/.claude/skills/sync-apple-calendar/SKILL.md +101 -0
  29. package/template/.claude/skills/sync-apple-calendar/references/SCHEMA.md +80 -0
  30. package/template/.claude/skills/sync-apple-calendar/scripts/sync.py +233 -0
  31. package/template/.claude/skills/sync-apple-mail/SKILL.md +131 -0
  32. package/template/.claude/skills/sync-apple-mail/references/SCHEMA.md +88 -0
  33. package/template/.claude/skills/sync-apple-mail/scripts/parse-emlx.py +104 -0
  34. package/template/.claude/skills/sync-apple-mail/scripts/sync.py +348 -0
  35. package/template/CLAUDE.md +152 -0
  36. package/template/USER.md +5 -0
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: draft-emails
3
+ description: Draft email responses using the knowledge base and calendar for full context on every person and conversation. Use when the user asks to draft, reply to, or respond to an email. Looks up people and organizations in the knowledge base before drafting.
4
+ ---
5
+
6
+ # Draft Emails
7
+
8
+ Help the user draft email responses. Uses the knowledge base and calendar for
9
+ full context on every person and conversation. This is an interactive skill —
10
+ the user triggers it by asking to draft or reply to emails.
11
+
12
+ ## Trigger
13
+
14
+ Run when the user asks to draft, reply to, or respond to an email.
15
+
16
+ ## Prerequisites
17
+
18
+ - Knowledge base populated (from `extract-entities` skill)
19
+ - Synced email data in `~/.cache/fit/basecamp/apple_mail/`
20
+
21
+ ## Inputs
22
+
23
+ - `knowledge/People/*.md` — person context
24
+ - `knowledge/Organizations/*.md` — organization context
25
+ - `~/.cache/fit/basecamp/apple_mail/*.md` — email threads
26
+ - `~/.cache/fit/basecamp/apple_calendar/*.json` — calendar events (for
27
+ scheduling)
28
+ - `drafts/last_processed` — timestamp of last processing run
29
+ - `drafts/drafted` — list of drafted email IDs (one per line)
30
+ - `drafts/ignored` — list of ignored email IDs (one per line)
31
+
32
+ ## Outputs
33
+
34
+ - `drafts/{email_id}_draft.md` — draft email files
35
+ - `drafts/last_processed` — updated timestamp
36
+ - `drafts/drafted` — updated with newly drafted IDs
37
+ - `drafts/ignored` — updated with newly ignored IDs
38
+
39
+ ---
40
+
41
+ ## Critical: Always Look Up Context First
42
+
43
+ **BEFORE drafting any email, you MUST look up the person/organization in the
44
+ knowledge base.**
45
+
46
+ When the user says "draft an email to Monica" or mentions ANY person:
47
+
48
+ 1. **STOP** — Do not draft anything yet
49
+ 2. **SEARCH** — Look them up: `rg -l "Monica" knowledge/`
50
+ 3. **READ** — Read their note: `cat "knowledge/People/Monica Smith.md"`
51
+ 4. **UNDERSTAND** — Extract role, organization, relationship history, open items
52
+ 5. **THEN DRAFT** — Only now draft the email, using this context
53
+
54
+ ## Key Principles
55
+
56
+ **Ask, don't guess:**
57
+
58
+ - If intent is unclear, ASK what the email should be about
59
+ - If a person has multiple contexts, ASK which one
60
+ - **WRONG:** "Here are three variants — pick one"
61
+ - **RIGHT:** "I see Akhilesh is involved in Rowboat and banking. Which topic?"
62
+
63
+ **Be decisive, not generic:**
64
+
65
+ - Once you know the context, draft ONE email — no multiple versions
66
+ - Every draft must be personalized from knowledge base context
67
+ - Infer tone and approach from context
68
+
69
+ ## Processing Flow
70
+
71
+ ### Step 1: Scan for New Emails
72
+
73
+ Find unprocessed emails using the scan script:
74
+
75
+ bash scripts/scan-emails.sh
76
+
77
+ This outputs tab-separated `email_id<TAB>subject` for each email not yet in
78
+ `drafts/drafted` or `drafts/ignored`.
79
+
80
+ ### Step 2: Parse Email
81
+
82
+ Each email file is markdown with headers:
83
+
84
+ - `# Subject Line`
85
+ - `**Thread ID:** <id>`
86
+ - `**Message Count:** <count>`
87
+ - `### From: Name <email@example.com>`
88
+ - `**Date:** <date>`
89
+
90
+ ### Step 3: Classify Email
91
+
92
+ **IGNORE** (append ID to `drafts/ignored`):
93
+
94
+ - Newsletters, marketing, automated notifications
95
+ - Spam or irrelevant cold outreach
96
+ - Outbound emails from user with no reply
97
+
98
+ **DRAFT response for:**
99
+
100
+ - Meeting requests or scheduling
101
+ - Personal emails from known contacts
102
+ - Business inquiries
103
+ - Follow-ups on existing conversations
104
+ - Emails requesting information or action
105
+
106
+ ### Step 4: Gather Context
107
+
108
+ **Knowledge Base (REQUIRED for every draft):**
109
+
110
+ ```bash
111
+ rg -l "sender_name" knowledge/
112
+ cat "knowledge/People/Sender Name.md"
113
+ cat "knowledge/Organizations/Company Name.md"
114
+ ```
115
+
116
+ **Calendar (for scheduling emails):**
117
+
118
+ ```bash
119
+ ls ~/.cache/fit/basecamp/apple_calendar/ 2>/dev/null
120
+ cat "$HOME/.cache/fit/basecamp/apple_calendar/event123.json"
121
+ ```
122
+
123
+ ### Step 5: Create Draft
124
+
125
+ Write draft to `drafts/{email_id}_draft.md`:
126
+
127
+ ```markdown
128
+ # Draft Response
129
+
130
+ **Original Email ID:** {id}
131
+ **Original Subject:** {subject}
132
+ **From:** {sender}
133
+ **Date Processed:** {date}
134
+
135
+ ---
136
+
137
+ ## Context Used
138
+ - Calendar: {relevant info or N/A}
139
+ - Knowledge: {relevant notes or N/A}
140
+
141
+ ---
142
+
143
+ ## Draft Response
144
+
145
+ Subject: Re: {subject}
146
+
147
+ {personalized draft body}
148
+
149
+ ---
150
+
151
+ ## Notes
152
+ {why this response was crafted this way}
153
+ ```
154
+
155
+ **Guidelines:**
156
+
157
+ - Draft ONE email — no multiple versions
158
+ - Reference past interactions naturally
159
+ - Match the tone of the incoming email
160
+ - For scheduling: propose specific times from calendar
161
+ - If unsure about intent, ask a clarifying question
162
+
163
+ ### Step 6: Update State
164
+
165
+ After each email, update the state files:
166
+
167
+ ```bash
168
+ echo "$EMAIL_ID" >> drafts/drafted # or drafts/ignored
169
+ date -u '+%Y-%m-%dT%H:%M:%SZ' > drafts/last_processed
170
+ ```
171
+
172
+ ### Step 7: Summary
173
+
174
+ ```
175
+ ## Processing Summary
176
+ **Emails Scanned:** X
177
+ **Drafts Created:** Y
178
+ **Ignored:** Z
179
+
180
+ ### Drafts Created:
181
+ - {id}: {subject} — {reason}
182
+
183
+ ### Ignored:
184
+ - {id}: {subject} — {reason}
185
+ ```
186
+
187
+ ## Constraints
188
+
189
+ - Never actually send emails — only create drafts
190
+ - Be conservative with ignore — when in doubt, create a draft
191
+ - For ambiguous emails, create a draft with a note explaining the ambiguity
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ # Scan for unprocessed emails and output their IDs and subjects.
3
+ #
4
+ # Usage: bash scripts/scan-emails.sh
5
+ #
6
+ # Checks ~/.cache/fit/basecamp/apple_mail/ for email files
7
+ # not yet listed in drafts/drafted or drafts/ignored.
8
+ # Outputs tab-separated: email_id<TAB>subject
9
+
10
+ set -euo pipefail
11
+
12
+ MAIL_DIRS=(
13
+ "$HOME/.cache/fit/basecamp/apple_mail"
14
+ )
15
+
16
+ for dir in "${MAIL_DIRS[@]}"; do
17
+ [ -d "$dir" ] || continue
18
+ for file in "$dir"/*.md; do
19
+ [ -f "$file" ] || continue
20
+
21
+ # Extract ID from filename (without extension)
22
+ EMAIL_ID="$(basename "$file" .md)"
23
+
24
+ # Skip if already drafted or ignored
25
+ rg -qxF "$EMAIL_ID" drafts/drafted 2>/dev/null && continue
26
+ rg -qxF "$EMAIL_ID" drafts/ignored 2>/dev/null && continue
27
+
28
+ # Extract subject from first H1 heading
29
+ SUBJECT="$(rg -m1 '^# ' "$file" 2>/dev/null | sed 's/^# //')"
30
+
31
+ printf '%s\t%s\n' "$EMAIL_ID" "$SUBJECT"
32
+ done
33
+ done
@@ -0,0 +1,466 @@
1
+ ---
2
+ name: extract-entities
3
+ description: Process synced email/calendar files from ~/.cache/fit/basecamp/ and ad-hoc document files (e.g. from ~/Desktop/ or ~/Downloads/) to extract structured knowledge into knowledge/ as Obsidian-compatible markdown notes. Use on a schedule, when the user asks to process/extract entities, or when invoked by another skill (e.g. organize-files). Builds the core knowledge graph from raw data.
4
+ ---
5
+
6
+ # Extract Entities
7
+
8
+ Process synced email and calendar files from `~/.cache/fit/basecamp/` and
9
+ extract structured knowledge into `knowledge/` as Obsidian-compatible markdown
10
+ notes. This is the core knowledge graph builder — it transforms raw data from
11
+ the sync skills into actionable, linked notes.
12
+
13
+ Also accepts **ad-hoc document files** passed by other skills (e.g. the
14
+ **`organize-files`** skill passes documents found in `~/Desktop/` and
15
+ `~/Downloads/`).
16
+
17
+ ## Trigger
18
+
19
+ Run this skill:
20
+
21
+ - On a schedule (every 15 minutes) for synced data
22
+ - When the user asks to process/extract entities from synced data
23
+ - When invoked by another skill with ad-hoc file paths (e.g.
24
+ **`organize-files`** after organizing `~/Desktop/` and `~/Downloads/`)
25
+
26
+ ## Prerequisites
27
+
28
+ - Synced data in `~/.cache/fit/basecamp/` (from `sync-apple-mail` or
29
+ `sync-apple-calendar` skills), **and/or**
30
+ - Ad-hoc file paths provided by the calling skill or user
31
+ - User identity configured in `USER.md` (Name, Email, Domain)
32
+
33
+ ## Inputs
34
+
35
+ ### Synced data (scheduled processing)
36
+
37
+ - `~/.cache/fit/basecamp/apple_mail/*.md` — synced email threads
38
+ - `~/.cache/fit/basecamp/apple_calendar/*.json` — synced calendar events
39
+
40
+ ### Ad-hoc files (from other skills or user)
41
+
42
+ - Arbitrary file paths passed as input (e.g.
43
+ `~/Downloads/Documents/Proposal.pdf`, `~/Desktop/Meeting_Notes.md`)
44
+ - Supported formats: `.pdf`, `.txt`, `.md`, `.rtf`, `.doc`, `.docx`, `.csv`,
45
+ `.xlsx`
46
+ - Typically provided by the **`organize-files`** skill after organizing
47
+ `~/Desktop/` and `~/Downloads/`
48
+
49
+ ### State tracking
50
+
51
+ - `~/.cache/fit/basecamp/state/graph_processed` — tracks which files have been
52
+ processed (TSV)
53
+ - `USER.md` — user identity (Name, Email, Domain) for self-exclusion
54
+
55
+ ## Outputs
56
+
57
+ - `knowledge/People/*.md` — person notes
58
+ - `knowledge/Organizations/*.md` — organization notes
59
+ - `knowledge/Projects/*.md` — project notes
60
+ - `knowledge/Topics/*.md` — topic notes
61
+ - `~/.cache/fit/basecamp/state/graph_processed` — updated with newly processed
62
+ files
63
+
64
+ ---
65
+
66
+ ## Before Starting
67
+
68
+ 1. Read `USER.md` to get the user's name, email, and domain
69
+ 2. Find new/changed files to process:
70
+
71
+ python3 scripts/state.py check
72
+
73
+ This outputs one file path per line for all source files that are new or
74
+ have changed since last processing.
75
+
76
+ ### Ad-hoc file inputs
77
+
78
+ When invoked with ad-hoc file paths (e.g. by the **`organize-files`** skill),
79
+ process those files directly instead of scanning `~/.cache/fit/basecamp/`. Check
80
+ each file against `graph_processed` the same way — skip if the hash hasn't
81
+ changed.
82
+
83
+ **Process in batches of 10 files per run.**
84
+
85
+ ## Step 0: Build Knowledge Index
86
+
87
+ Before processing, scan all existing notes to build an index:
88
+
89
+ ```bash
90
+ find knowledge/People knowledge/Organizations knowledge/Projects knowledge/Topics -name "*.md" 2>/dev/null
91
+ ```
92
+
93
+ For each note, extract key fields:
94
+
95
+ ```bash
96
+ head -20 "knowledge/People/Sarah Chen.md"
97
+ ```
98
+
99
+ Build a mental index:
100
+
101
+ ```
102
+ PEOPLE:
103
+ | Name | Email | Organization | Role | Aliases |
104
+
105
+ ORGANIZATIONS:
106
+ | Name | Domain | Aliases |
107
+
108
+ PROJECTS:
109
+ | Name | Status | Aliases |
110
+
111
+ TOPICS:
112
+ | Name | Keywords | Aliases |
113
+ ```
114
+
115
+ ## Step 1: Determine Source Type and Filter
116
+
117
+ ### Determine type
118
+
119
+ - Has `Meeting:` or `Attendees:` or `Transcript:` → **meeting** (can create
120
+ notes)
121
+ - Has `From:` and `To:` or `Subject:` → **email** (can only update existing
122
+ notes)
123
+ - Is in `Voice Memos/` folder → **voice memo** (can create notes)
124
+ - Is in `apple_calendar/` → **calendar event** (enrich existing notes only)
125
+ - Is an **ad-hoc document** (from `~/Desktop/`, `~/Downloads/`, or passed by
126
+ another skill) → **document** (can create notes)
127
+
128
+ ### Filter: Skip These Sources
129
+
130
+ **ALWAYS process — never skip:**
131
+
132
+ - Calendar events — always process regardless of whether attendees are internal
133
+ or external. Internal-only meetings are valuable for enriching project and
134
+ topic notes (meeting context, decisions, agenda items). Only skip all-day
135
+ placeholder events with no attendees and no description (e.g. "Block", "OOO").
136
+
137
+ **SKIP entirely (don't process):**
138
+
139
+ - Newsletters (unsubscribe links, "View in browser", bulk sender indicators)
140
+ - Marketing emails (promotional language, no-reply senders)
141
+ - Automated notifications (GitHub, Jira, Slack, CI/CD, shipping updates)
142
+ - Spam or cold outreach from unknown senders with no existing relationship
143
+ - Product update emails, release notes, changelogs
144
+ - Social media notifications
145
+ - Receipts and order confirmations
146
+ - Calendar invite emails that are just logistics
147
+ - Mass emails (many recipients, mailing list headers)
148
+
149
+ **PROCESS (but only update existing notes):**
150
+
151
+ - Emails from people who already have notes in `knowledge/People/`
152
+ - Emails that reference existing projects or organizations
153
+
154
+ **PROCESS (can create new notes):**
155
+
156
+ - Meeting transcripts with external attendees
157
+ - Voice memos
158
+
159
+ **Exception — Warm Intros:** If an email is a warm introduction from someone who
160
+ has a note, AND they're introducing a new person, create a note for the
161
+ introduced person.
162
+
163
+ Warm intro signals:
164
+
165
+ - Subject contains "Intro:", "Introduction:", "Meet", "Connecting"
166
+ - Body contains "introduce you to", "want to connect", "meet [Name]"
167
+ - New person is CC'd
168
+
169
+ ## Step 2: Read and Parse Source File
170
+
171
+ ### For emails
172
+
173
+ Extract: Date, Subject, From, To/Cc, Thread ID, Body.
174
+
175
+ ### For meetings
176
+
177
+ Extract: Date, Attendees, Transcript/Notes.
178
+
179
+ ### For ad-hoc documents
180
+
181
+ Extract: Date (file modification date), Filename, Source path, Content.
182
+
183
+ - `.md`, `.txt`, `.rtf` — read directly
184
+ - `.pdf` — extract text (`pdftotext` or `mdcat` if available)
185
+ - `.csv` — read as-is, look for names/emails/orgs in columns
186
+ - `.doc`, `.docx` — extract text (`textutil -convert txt` on macOS)
187
+
188
+ Ad-hoc documents follow **meeting** rules: they **can create** new entity notes.
189
+
190
+ ### 2a: Exclude Self
191
+
192
+ Never create or update notes for:
193
+
194
+ - The user (matches name, email, or @domain from `USER.md`)
195
+ - Anyone @{user.domain} (colleagues at user's company)
196
+
197
+ ### 2b: Extract All Name Variants
198
+
199
+ Collect every way entities are referenced:
200
+
201
+ **People:** Full names, first names, last names, initials, email addresses,
202
+ roles/titles, pronouns with clear antecedents.
203
+
204
+ **Organizations:** Full names, short names, abbreviations, email domains.
205
+
206
+ **Projects:** Explicit names, descriptive references ("the pilot", "the deal").
207
+
208
+ ## Step 3: Look Up Existing Notes
209
+
210
+ For each variant, search the knowledge index built in Step 0.
211
+
212
+ ```bash
213
+ rg -l "Sarah Chen|sarah@acme.com" knowledge/
214
+ cat "knowledge/People/Sarah Chen.md"
215
+ ```
216
+
217
+ **Matching criteria:**
218
+
219
+ | Source has | Note has | Match if |
220
+ | ------------------------ | ------------------------ | ------------------------- |
221
+ | First name "Sarah" | Full name "Sarah Chen" | Same organization context |
222
+ | Email "sarah@acme.com" | Email field | Exact match |
223
+ | Email domain "@acme.com" | Organization "Acme Corp" | Domain matches org |
224
+ | Any variant | Aliases field | Listed in aliases |
225
+
226
+ ## Step 4: Resolve Entities to Canonical Names
227
+
228
+ Build a resolution map from every source reference to its canonical form:
229
+
230
+ ```
231
+ RESOLVED:
232
+ - "Sarah Chen" → [[People/Sarah Chen]]
233
+ - "sarah@acme.com" → [[People/Sarah Chen]]
234
+ - "Acme" → [[Organizations/Acme Corp]]
235
+
236
+ NEW ENTITIES (meeting — create notes):
237
+ - "Jennifer" (CTO) → Create [[People/Jennifer]]
238
+
239
+ NEW ENTITIES (email — do NOT create):
240
+ - "Random Person" → Skip
241
+
242
+ AMBIGUOUS:
243
+ - "Mike" (no context) → Skip
244
+ ```
245
+
246
+ **Disambiguation priority:** Email match > Organization context > Role match >
247
+ Aliases > Recency.
248
+
249
+ ## Step 5: Identify New Entities (Meetings Only)
250
+
251
+ For entities not resolved to existing notes, apply the **"Would I prep for this
252
+ person?"** test:
253
+
254
+ **CREATE a note for:**
255
+
256
+ - Decision makers or key contacts at customers, prospects, partners
257
+ - Investors or potential investors
258
+ - Candidates being interviewed
259
+ - Advisors or mentors with ongoing relationships
260
+ - Introducers who connect you to valuable contacts
261
+
262
+ **DO NOT create notes for:**
263
+
264
+ - Transactional service providers (bank employees, support reps)
265
+ - One-time administrative contacts
266
+ - Large group meeting attendees you didn't interact with
267
+ - Internal colleagues (@user.domain)
268
+ - Assistants handling only logistics
269
+
270
+ For people who don't get their own note, add to the Organization note's
271
+ `## Contacts` section instead.
272
+
273
+ ### Role Inference
274
+
275
+ If role is not explicit, infer from context:
276
+
277
+ - Organizer of cross-company meeting → likely senior or partnerships
278
+ - Technical questions → likely engineering
279
+ - Pricing questions → likely procurement or finance
280
+ - "I'll need to check with my team" → manager
281
+ - "I can make that call" → decision maker
282
+
283
+ Format: `**Role:** Product Lead (inferred from evaluation discussions)`
284
+
285
+ ## Step 6: Extract Content
286
+
287
+ For each entity that has or will have a note, extract:
288
+
289
+ ### Decisions
290
+
291
+ Signals: "We decided...", "We agreed...", "Let's go with...", "Approved",
292
+ "Confirmed"
293
+
294
+ ### Commitments
295
+
296
+ Signals: "I'll...", "We'll...", "Can you...", "Please send...", "By Friday"
297
+ Extract: Owner, action, deadline, status (open).
298
+
299
+ ### Key Facts
300
+
301
+ Extract **substantive** information only:
302
+
303
+ - Specific numbers (budget, team size, timeline)
304
+ - Preferences or working style
305
+ - Background information
306
+ - Technical requirements
307
+ - What was discussed or proposed
308
+
309
+ **NEVER include:** Meta-commentary about missing data, placeholder text, or data
310
+ quality observations. If no key facts exist, leave the section empty.
311
+
312
+ ### Open Items
313
+
314
+ Include commitments and next steps only:
315
+
316
+ ```markdown
317
+ - [ ] Send API documentation — by Friday
318
+ - [ ] Schedule follow-up call with CTO
319
+ ```
320
+
321
+ **NEVER include:** "Find their email", "Add their role", "Research company
322
+ background"
323
+
324
+ ### Activity Summary
325
+
326
+ One line per source:
327
+
328
+ ```markdown
329
+ - **2025-01-15** (meeting): Kickoff for [[Projects/Acme Integration]]. [[People/David Kim]] needs API access.
330
+ ```
331
+
332
+ Always use canonical names with absolute paths (`[[People/Name]]`,
333
+ `[[Organizations/Name]]`).
334
+
335
+ ### Summary
336
+
337
+ 2-3 sentences answering: "Who is this person and why do I know them?" Focus on
338
+ the relationship, not the communication method.
339
+
340
+ **Good:** "VP Engineering at [[Organizations/Acme Corp]] leading the
341
+ [[Projects/Acme Integration]] pilot." **Bad:** "Attendee on the scheduled
342
+ meeting (Aug 12, 2024)."
343
+
344
+ ## Step 7: Detect State Changes
345
+
346
+ Review extracted content for signals that existing note fields need updating:
347
+
348
+ ### Project Status Changes
349
+
350
+ | Signal | New Status |
351
+ | ------------------------------------- | ---------- |
352
+ | "approved" / "signed" / "green light" | active |
353
+ | "on hold" / "pausing" / "delayed" | on hold |
354
+ | "cancelled" / "not proceeding" | cancelled |
355
+ | "launched" / "completed" / "shipped" | completed |
356
+ | "exploring" / "considering" | planning |
357
+
358
+ ### Open Item Resolution
359
+
360
+ | Signal | Action |
361
+ | ---------------------------- | --------------- |
362
+ | "Here's the X you requested" | Mark X complete |
363
+ | "I've sent the X" | Mark X complete |
364
+ | "X is done" / "X is ready" | Mark X complete |
365
+
366
+ Change `- [ ]` to `- [x]` with completion date.
367
+
368
+ ### Role/Title Changes
369
+
370
+ - New title in email signature
371
+ - "I've been promoted to..."
372
+ - Different role than what's in the note
373
+
374
+ ### Relationship Changes
375
+
376
+ - "I've joined [New Company]"
377
+ - "We signed the contract" → prospect → customer
378
+ - New email domain for known person
379
+
380
+ **Be conservative:** Only apply clear, unambiguous state changes. If uncertain,
381
+ add to activity log but don't change fields.
382
+
383
+ Log state changes in activity with `[Field → value]` notation:
384
+
385
+ ```markdown
386
+ - **2025-01-20** (email): Leadership approved pilot. [Status → active]
387
+ ```
388
+
389
+ ## Step 8: Check for Duplicates
390
+
391
+ Before writing:
392
+
393
+ - Check activity log for existing entries on this date from this source
394
+ - Compare key facts against existing — skip duplicates
395
+ - Check open items — don't add same item twice
396
+ - If new info contradicts existing, note both versions with "(needs
397
+ clarification)"
398
+
399
+ ## Step 9: Write Updates
400
+
401
+ **Write one file at a time. Do not batch writes.**
402
+
403
+ ### For NEW entities (meetings only)
404
+
405
+ Create the note file using the templates in
406
+ [references/TEMPLATES.md](references/TEMPLATES.md).
407
+
408
+ ### For EXISTING entities
409
+
410
+ Read the current note, then apply targeted edits:
411
+
412
+ - Add new activity entry at the TOP of the Activity section (reverse
413
+ chronological)
414
+ - Update Last seen date
415
+ - Add new key facts (if not duplicates)
416
+ - Update open items (mark completed, add new ones)
417
+ - Apply state changes to fields
418
+
419
+ Use precise edits — don't rewrite the entire file.
420
+
421
+ ## Step 10: Ensure Bidirectional Links
422
+
423
+ After writing, verify links go both ways:
424
+
425
+ | If you add... | Then also add... |
426
+ | ---------------------- | -------------------------------------------- |
427
+ | Person → Organization | Organization → Person (in People section) |
428
+ | Person → Project | Project → Person (in People section) |
429
+ | Project → Organization | Organization → Project (in Projects section) |
430
+
431
+ Always use absolute links: `[[People/Sarah Chen]]`,
432
+ `[[Organizations/Acme Corp]]`, `[[Projects/Acme Integration]]`.
433
+
434
+ ## Step 11: Update Graph State
435
+
436
+ After processing each file, update the state:
437
+
438
+ python3 scripts/state.py update "$FILE"
439
+
440
+ ## Source Type Rules Summary
441
+
442
+ | Source Type | Creates Notes? | Updates Notes? | Detects State Changes? |
443
+ | ----------------------- | --------------- | -------------- | ---------------------- |
444
+ | Calendar event | No | Yes (always) | Yes |
445
+ | Meeting | Yes | Yes | Yes |
446
+ | Voice memo | Yes | Yes | Yes |
447
+ | Ad-hoc document | Yes | Yes | Yes |
448
+ | Email (known contact) | No | Yes | Yes |
449
+ | Email (unknown contact) | No (SKIP) | No | No |
450
+ | Email (warm intro) | Yes (exception) | Yes | Yes |
451
+
452
+ ## Quality Checklist
453
+
454
+ Before completing, verify:
455
+
456
+ - [ ] Correctly identified source as meeting or email
457
+ - [ ] Applied correct rules (meetings create, emails only update)
458
+ - [ ] Excluded self and @user.domain from entity extraction
459
+ - [ ] Applied "Would I prep?" test to each person
460
+ - [ ] Used absolute paths `[[Folder/Name]]` in ALL links
461
+ - [ ] Summaries describe relationship, not communication method
462
+ - [ ] Key facts are substantive (no filler)
463
+ - [ ] Open items are commitments (no "find their email" tasks)
464
+ - [ ] State changes logged with `[Field → value]` notation
465
+ - [ ] Bidirectional links are consistent
466
+ - [ ] Graph state updated for processed files