@forwardimpact/outpost 3.0.3

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 (120) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +15 -0
  3. package/bin/fit-outpost.js +3 -0
  4. package/config/scheduler.json +34 -0
  5. package/package.json +63 -0
  6. package/src/agent-runner.js +189 -0
  7. package/src/index.js +5 -0
  8. package/src/kb-manager.js +228 -0
  9. package/src/outpost.js +417 -0
  10. package/src/scheduler.js +195 -0
  11. package/src/socket-server.js +336 -0
  12. package/src/state-manager.js +126 -0
  13. package/templates/.claude/agents/chief-of-staff.md +58 -0
  14. package/templates/.claude/agents/concierge.md +46 -0
  15. package/templates/.claude/agents/head-hunter.md +57 -0
  16. package/templates/.claude/agents/librarian.md +60 -0
  17. package/templates/.claude/agents/postman.md +47 -0
  18. package/templates/.claude/agents/recruiter.md +63 -0
  19. package/templates/.claude/settings.json +88 -0
  20. package/templates/.claude/skills/candidate-report/SKILL.md +139 -0
  21. package/templates/.claude/skills/candidate-report/references/report-template.html +203 -0
  22. package/templates/.claude/skills/candidate-report/references/report.css +438 -0
  23. package/templates/.claude/skills/candidate-report/references/rubric.md +76 -0
  24. package/templates/.claude/skills/candidate-report/scripts/render-pdf.mjs +48 -0
  25. package/templates/.claude/skills/deck-create/SKILL.md +75 -0
  26. package/templates/.claude/skills/deck-create/references/slide.css +35 -0
  27. package/templates/.claude/skills/deck-create/scripts/convert-to-pdf.mjs +47 -0
  28. package/templates/.claude/skills/deck-summarize/SKILL.md +139 -0
  29. package/templates/.claude/skills/deck-summarize/references/brief-template.md +85 -0
  30. package/templates/.claude/skills/deck-summarize/scripts/extract-pptx.mjs +210 -0
  31. package/templates/.claude/skills/doc-collab/SKILL.md +112 -0
  32. package/templates/.claude/skills/doc-create/SKILL.md +105 -0
  33. package/templates/.claude/skills/doc-create/scripts/convert-to-pdf.mjs +50 -0
  34. package/templates/.claude/skills/draft-emails/SKILL.md +141 -0
  35. package/templates/.claude/skills/draft-emails/references/template.md +26 -0
  36. package/templates/.claude/skills/draft-emails/scripts/scan-emails.mjs +66 -0
  37. package/templates/.claude/skills/draft-emails/scripts/send-email.mjs +139 -0
  38. package/templates/.claude/skills/extract-entities/SKILL.md +187 -0
  39. package/templates/.claude/skills/extract-entities/references/TEMPLATES.md +14 -0
  40. package/templates/.claude/skills/extract-entities/references/conditions.md +55 -0
  41. package/templates/.claude/skills/extract-entities/references/content.md +104 -0
  42. package/templates/.claude/skills/extract-entities/references/links.md +48 -0
  43. package/templates/.claude/skills/extract-entities/references/recruitment.md +57 -0
  44. package/templates/.claude/skills/extract-entities/references/resolution.md +71 -0
  45. package/templates/.claude/skills/extract-entities/references/sources.md +70 -0
  46. package/templates/.claude/skills/extract-entities/references/templates-conditions.md +36 -0
  47. package/templates/.claude/skills/extract-entities/references/templates-goals-priorities.md +63 -0
  48. package/templates/.claude/skills/extract-entities/references/templates-people-orgs.md +67 -0
  49. package/templates/.claude/skills/extract-entities/references/templates-projects-topics.md +68 -0
  50. package/templates/.claude/skills/extract-entities/scripts/state.mjs +131 -0
  51. package/templates/.claude/skills/hyprnote-follow/SKILL.md +174 -0
  52. package/templates/.claude/skills/hyprnote-follow/references/coaching.md +118 -0
  53. package/templates/.claude/skills/hyprnote-follow/references/debrief.md +59 -0
  54. package/templates/.claude/skills/hyprnote-follow/references/meeting-types.md +14 -0
  55. package/templates/.claude/skills/hyprnote-follow/scripts/follow.mjs +243 -0
  56. package/templates/.claude/skills/hyprnote-process/SKILL.md +147 -0
  57. package/templates/.claude/skills/hyprnote-process/references/extraction.md +84 -0
  58. package/templates/.claude/skills/hyprnote-process/references/sessions.md +81 -0
  59. package/templates/.claude/skills/hyprnote-process/scripts/scan.mjs +246 -0
  60. package/templates/.claude/skills/hyprnote-trim/SKILL.md +177 -0
  61. package/templates/.claude/skills/manage-tasks/SKILL.md +152 -0
  62. package/templates/.claude/skills/manage-tasks/references/format.md +73 -0
  63. package/templates/.claude/skills/meeting-prep/SKILL.md +180 -0
  64. package/templates/.claude/skills/organize-files/SKILL.md +146 -0
  65. package/templates/.claude/skills/organize-files/scripts/organize-by-type.mjs +105 -0
  66. package/templates/.claude/skills/organize-files/scripts/summarize.mjs +91 -0
  67. package/templates/.claude/skills/req-assess/SKILL.md +137 -0
  68. package/templates/.claude/skills/req-assess/references/interview-template.md +62 -0
  69. package/templates/.claude/skills/req-assess/references/panel-template.md +117 -0
  70. package/templates/.claude/skills/req-assess/references/rubric.md +65 -0
  71. package/templates/.claude/skills/req-decide/SKILL.md +163 -0
  72. package/templates/.claude/skills/req-decide/references/rubric.md +63 -0
  73. package/templates/.claude/skills/req-decide/references/template.md +118 -0
  74. package/templates/.claude/skills/req-forget/SKILL.md +154 -0
  75. package/templates/.claude/skills/req-forget/references/classify.md +23 -0
  76. package/templates/.claude/skills/req-forget/references/locations.md +59 -0
  77. package/templates/.claude/skills/req-forget/references/report-template.md +67 -0
  78. package/templates/.claude/skills/req-scan/SKILL.md +145 -0
  79. package/templates/.claude/skills/req-scan/references/fallbacks.md +76 -0
  80. package/templates/.claude/skills/req-scan/references/filters.md +83 -0
  81. package/templates/.claude/skills/req-scan/references/sources.md +76 -0
  82. package/templates/.claude/skills/req-scan/references/state.md +54 -0
  83. package/templates/.claude/skills/req-scan/references/template.md +36 -0
  84. package/templates/.claude/skills/req-scan/scripts/state.mjs +396 -0
  85. package/templates/.claude/skills/req-screen/SKILL.md +151 -0
  86. package/templates/.claude/skills/req-screen/references/rubric.md +94 -0
  87. package/templates/.claude/skills/req-screen/references/template.md +91 -0
  88. package/templates/.claude/skills/req-track/SKILL.md +189 -0
  89. package/templates/.claude/skills/req-track/references/fields.md +114 -0
  90. package/templates/.claude/skills/req-track/references/signals.md +42 -0
  91. package/templates/.claude/skills/req-track/references/statuses.md +32 -0
  92. package/templates/.claude/skills/req-track/references/templates.md +127 -0
  93. package/templates/.claude/skills/req-workday/SKILL.md +162 -0
  94. package/templates/.claude/skills/req-workday/references/field-mapping.md +40 -0
  95. package/templates/.claude/skills/req-workday/references/status-mapping.md +38 -0
  96. package/templates/.claude/skills/req-workday/references/templates.md +126 -0
  97. package/templates/.claude/skills/req-workday/references/xlsx-format.md +56 -0
  98. package/templates/.claude/skills/req-workday/scripts/parse-workday.mjs +313 -0
  99. package/templates/.claude/skills/send-chat/SKILL.md +170 -0
  100. package/templates/.claude/skills/sync-apple-calendar/SKILL.md +145 -0
  101. package/templates/.claude/skills/sync-apple-calendar/references/SCHEMA.md +80 -0
  102. package/templates/.claude/skills/sync-apple-calendar/scripts/query.mjs +307 -0
  103. package/templates/.claude/skills/sync-apple-calendar/scripts/sync.mjs +332 -0
  104. package/templates/.claude/skills/sync-apple-mail/SKILL.md +151 -0
  105. package/templates/.claude/skills/sync-apple-mail/references/SCHEMA.md +115 -0
  106. package/templates/.claude/skills/sync-apple-mail/scripts/parse-emlx.mjs +389 -0
  107. package/templates/.claude/skills/sync-apple-mail/scripts/sync-helpers.mjs +372 -0
  108. package/templates/.claude/skills/sync-apple-mail/scripts/sync.mjs +239 -0
  109. package/templates/.claude/skills/sync-teams/SKILL.md +177 -0
  110. package/templates/.claude/skills/sync-teams/scripts/idb-reader.mjs +147 -0
  111. package/templates/.claude/skills/sync-teams/scripts/leveldb-reader.mjs +288 -0
  112. package/templates/.claude/skills/sync-teams/scripts/sync.mjs +381 -0
  113. package/templates/.claude/skills/upstream-skill/SKILL.md +130 -0
  114. package/templates/.claude/skills/upstream-skill/references/examples.md +80 -0
  115. package/templates/.claude/skills/weekly-update/SKILL.md +141 -0
  116. package/templates/.claude/skills/weekly-update/references/template.md +55 -0
  117. package/templates/CLAUDE.md +134 -0
  118. package/templates/USER.md +3 -0
  119. package/templates/apm.yml +5 -0
  120. package/templates/knowledge/Briefings/.gitkeep +0 -0
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Scan for unprocessed Hyprnote sessions.
4
+ *
5
+ * Compares session _memo.md and _summary.md files against the graph_processed
6
+ * state file to identify sessions that need processing. Reports unprocessed
7
+ * sessions with title, date, and content preview.
8
+ *
9
+ * Usage:
10
+ * node scripts/scan.mjs List unprocessed sessions
11
+ * node scripts/scan.mjs --changed Also detect changed (re-edited) sessions
12
+ * node scripts/scan.mjs --json Output as JSON
13
+ * node scripts/scan.mjs --count Just print the count
14
+ */
15
+
16
+ import { createHash } from "node:crypto";
17
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { homedir } from "node:os";
20
+
21
+ const HOME = homedir();
22
+ const SESSIONS_DIR = join(
23
+ HOME,
24
+ "Library/Application Support/hyprnote/sessions",
25
+ );
26
+ const STATE_FILE = join(HOME, ".cache/fit/outpost/state/graph_processed");
27
+
28
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
29
+ console.log(`scan — find unprocessed Hyprnote sessions
30
+
31
+ Usage:
32
+ node scripts/scan.mjs [options]
33
+
34
+ Options:
35
+ --changed Also detect sessions whose memo/summary hash has changed
36
+ --json Output as JSON array
37
+ --count Just print the unprocessed count (for scripting)
38
+ --limit N Max sessions to display (default: 20)
39
+ -h, --help Show this help message
40
+
41
+ Sessions dir: ~/Library/Application Support/hyprnote/sessions/
42
+ State file: ~/.cache/fit/outpost/state/graph_processed`);
43
+ process.exit(0);
44
+ }
45
+
46
+ const args = process.argv.slice(2);
47
+ const detectChanged = args.includes("--changed");
48
+ const jsonOutput = args.includes("--json");
49
+ const countOnly = args.includes("--count");
50
+ const limitIdx = args.indexOf("--limit");
51
+ const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) || 20 : 20;
52
+
53
+ // --- Load state ---
54
+
55
+ const state = new Map();
56
+ if (existsSync(STATE_FILE)) {
57
+ const text = readFileSync(STATE_FILE, "utf8");
58
+ for (const line of text.split("\n")) {
59
+ if (!line) continue;
60
+ const idx = line.indexOf("\t");
61
+ if (idx === -1) continue;
62
+ state.set(line.slice(0, idx), line.slice(idx + 1));
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Compute SHA-256 hash of file contents.
68
+ */
69
+ function fileHash(filePath) {
70
+ return createHash("sha256").update(readFileSync(filePath)).digest("hex");
71
+ }
72
+
73
+ /**
74
+ * Check if a file needs processing (new or changed).
75
+ */
76
+ function needsProcessing(filePath) {
77
+ const storedHash = state.get(filePath);
78
+ if (!storedHash) return { needed: true, reason: "new" };
79
+ if (detectChanged) {
80
+ const currentHash = fileHash(filePath);
81
+ if (currentHash !== storedHash) return { needed: true, reason: "changed" };
82
+ }
83
+ return { needed: false, reason: null };
84
+ }
85
+
86
+ /**
87
+ * Extract title and date from a memo file.
88
+ */
89
+ function parseMemo(memoPath) {
90
+ try {
91
+ const content = readFileSync(memoPath, "utf8");
92
+
93
+ // Skip empty/whitespace-only memos
94
+ const body = content.replace(/---[\s\S]*?---/, "").trim();
95
+ if (!body || body === " ") return null;
96
+
97
+ // Extract title from first H1
98
+ const titleMatch = content.match(/^#\s+(.+)/m);
99
+ const title = titleMatch ? titleMatch[1].trim() : null;
100
+
101
+ // Extract date from content or fall back to file mtime
102
+ const dateMatch = content.match(/\d{4}-\d{2}-\d{2}/);
103
+ let date = dateMatch ? dateMatch[0] : null;
104
+
105
+ if (!date) {
106
+ const stat = statSync(memoPath);
107
+ date = stat.mtime.toISOString().slice(0, 10);
108
+ }
109
+
110
+ return { title, date, preview: body.slice(0, 150).replace(/\n/g, " ") };
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Read _meta.json for session metadata.
118
+ */
119
+ function readMeta(sessionDir) {
120
+ const metaPath = join(sessionDir, "_meta.json");
121
+ if (!existsSync(metaPath)) return null;
122
+ try {
123
+ return JSON.parse(readFileSync(metaPath, "utf8"));
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ // --- Scan sessions ---
130
+
131
+ if (!existsSync(SESSIONS_DIR)) {
132
+ console.error(`Hyprnote sessions directory not found: ${SESSIONS_DIR}`);
133
+ process.exit(1);
134
+ }
135
+
136
+ const sessionIds = readdirSync(SESSIONS_DIR);
137
+ const unprocessed = [];
138
+ let totalWithMemos = 0;
139
+ let processedCount = 0;
140
+
141
+ for (const uuid of sessionIds) {
142
+ const sessionPath = join(SESSIONS_DIR, uuid);
143
+ const stat = statSync(sessionPath, { throwIfNoEntry: false });
144
+ if (!stat || !stat.isDirectory()) continue;
145
+
146
+ const memoPath = join(sessionPath, "_memo.md");
147
+ const summaryPath = join(sessionPath, "_summary.md");
148
+ const hasMemo = existsSync(memoPath);
149
+ const hasSummary = existsSync(summaryPath);
150
+
151
+ if (!hasMemo && !hasSummary) continue;
152
+
153
+ totalWithMemos++;
154
+
155
+ // Check memo
156
+ const memoCheck = hasMemo
157
+ ? needsProcessing(memoPath)
158
+ : { needed: false, reason: null };
159
+
160
+ // Check summary
161
+ const summaryCheck = hasSummary
162
+ ? needsProcessing(summaryPath)
163
+ : { needed: false, reason: null };
164
+
165
+ if (!memoCheck.needed && !summaryCheck.needed) {
166
+ processedCount++;
167
+ continue;
168
+ }
169
+
170
+ // Parse memo for display info
171
+ const memo = hasMemo ? parseMemo(memoPath) : null;
172
+ if (hasMemo && !memo) {
173
+ // Empty memo, no summary → skip
174
+ if (!hasSummary) continue;
175
+ }
176
+
177
+ // Read meta for title fallback
178
+ const meta = readMeta(sessionPath);
179
+
180
+ const title = memo?.title || meta?.title || uuid.slice(0, 8);
181
+ const date =
182
+ memo?.date ||
183
+ (meta?.created_at ? meta.created_at.slice(0, 10) : null) ||
184
+ statSync(sessionPath).mtime.toISOString().slice(0, 10);
185
+
186
+ unprocessed.push({
187
+ uuid,
188
+ title,
189
+ date,
190
+ hasMemo,
191
+ hasSummary,
192
+ memoReason: memoCheck.reason,
193
+ summaryReason: summaryCheck.reason,
194
+ preview: memo?.preview || "(summary only)",
195
+ memoPath: hasMemo ? memoPath : null,
196
+ summaryPath: hasSummary ? summaryPath : null,
197
+ });
198
+ }
199
+
200
+ // Sort by date descending (newest first)
201
+ unprocessed.sort((a, b) => b.date.localeCompare(a.date));
202
+
203
+ // --- Output ---
204
+
205
+ if (countOnly) {
206
+ console.log(unprocessed.length);
207
+ process.exit(0);
208
+ }
209
+
210
+ if (jsonOutput) {
211
+ console.log(JSON.stringify(unprocessed.slice(0, limit), null, 2));
212
+ process.exit(0);
213
+ }
214
+
215
+ // Formatted output
216
+ console.log(
217
+ `Sessions: ${totalWithMemos} total, ${processedCount} processed, ${unprocessed.length} unprocessed`,
218
+ );
219
+
220
+ if (unprocessed.length === 0) {
221
+ console.log("\nAll sessions are up to date.");
222
+ process.exit(0);
223
+ }
224
+
225
+ console.log("");
226
+ const display = unprocessed.slice(0, limit);
227
+ for (const s of display) {
228
+ const flags = [];
229
+ if (s.memoReason) flags.push(`memo:${s.memoReason}`);
230
+ if (s.summaryReason) flags.push(`summary:${s.summaryReason}`);
231
+ const sources = [];
232
+ if (s.hasMemo) sources.push("memo");
233
+ if (s.hasSummary) sources.push("summary");
234
+
235
+ console.log(
236
+ `${s.date} | ${s.title} | ${sources.join("+")} | ${flags.join(", ")}`,
237
+ );
238
+ console.log(` ${s.uuid}`);
239
+ if (s.preview && s.preview !== "(summary only)") {
240
+ console.log(` ${s.preview.slice(0, 100)}…`);
241
+ }
242
+ }
243
+
244
+ if (unprocessed.length > limit) {
245
+ console.log(`\n... and ${unprocessed.length - limit} more`);
246
+ }
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: hyprnote-trim
3
+ description: Trim a Hyprnote transcript to its logical end. Recordings are often left running after a meeting finishes — this skill finds the natural conclusion (goodbyes, sign-offs) and cuts the transcript there. Use when the user asks to trim, cut, or clean up a Hyprnote transcript.
4
+ ---
5
+
6
+ # Trim Transcript
7
+
8
+ Find the logical end of a Hyprnote meeting transcript and trim everything after
9
+ it. Meetings recorded with Hyprnote often have trailing noise — the mic stays on
10
+ after goodbyes, capturing ambient sound, unrelated chatter, or silence. This
11
+ skill identifies the natural conclusion and edits the transcript in place.
12
+
13
+ ## Trigger
14
+
15
+ Run this skill:
16
+
17
+ - When the user asks to trim, cut, or clean up a Hyprnote transcript
18
+ - When given a specific session ID to trim
19
+ - When another skill (e.g., hyprnote-process) flags a transcript as having
20
+ excessive trailing content
21
+
22
+ ## Prerequisites
23
+
24
+ - Hyprnote installed with session data at
25
+ `~/Library/Application Support/hyprnote/sessions/`
26
+
27
+ ## Inputs
28
+
29
+ - **Session ID** — a UUID identifying the Hyprnote session to trim
30
+ - `~/Library/Application Support/hyprnote/sessions/{uuid}/transcript.json` — the
31
+ word-level transcript
32
+
33
+ ## Outputs
34
+
35
+ - `~/Library/Application Support/hyprnote/sessions/{uuid}/transcript.json` —
36
+ edited in place with words after the logical end removed
37
+ - `~/Library/Application Support/hyprnote/sessions/{uuid}/audio.mp3` — deleted.
38
+ Trimming indicates the recording captured audio beyond the consented meeting,
39
+ so the full audio must be removed to respect participant privacy.
40
+ - Printed summary: original duration, trim point, new duration, words removed
41
+
42
+ ---
43
+
44
+ ## Steps
45
+
46
+ ### Step 0 — Validate the session
47
+
48
+ 1. Confirm the session directory exists:
49
+ ```
50
+ ~/Library/Application Support/hyprnote/sessions/{uuid}/
51
+ ```
52
+ 2. Confirm `transcript.json` exists and has at least one transcript with words.
53
+ 3. Read `_meta.json` to get the session title for context.
54
+
55
+ ### Step 1 — Reconstruct readable text
56
+
57
+ Convert the word-level transcript into readable text with timestamps. Group
58
+ words into lines by speaker channel and approximate sentence boundaries. The
59
+ goal is a human-readable view you can analyze for the logical end.
60
+
61
+ Use this approach:
62
+
63
+ ```python
64
+ #!/usr/bin/env bun
65
+ import json
66
+
67
+ data = json.load(open('transcript.json'))
68
+ words = data['transcripts'][0]['words']
69
+
70
+ # Reconstruct text grouped by ~30-second windows with channel labels
71
+ current_min = -1
72
+ for i, w in enumerate(words):
73
+ minute_mark = int(w['start_ms'] / 30000) # 30-second buckets
74
+ if minute_mark != current_min:
75
+ current_min = minute_mark
76
+ mins = w['start_ms'] / 60000
77
+ print(f'\n[{mins:.1f}m ch{w["channel"]}]', end='')
78
+ print(w['text'], end='')
79
+ ```
80
+
81
+ ### Step 2 — Identify the logical end
82
+
83
+ Read through the reconstructed text and find the **first point where the meeting
84
+ has clearly concluded**. Look for these signals, roughly in order of strength:
85
+
86
+ **Strong ending signals (any one is sufficient):**
87
+
88
+ - Explicit farewells: "bye", "bye bye", "goodbye", "take care", "have a good
89
+ day/evening/weekend", "cheers"
90
+ - Final thank-yous followed by no substantive content: "thank you so much",
91
+ "thanks a lot", "thanks everyone"
92
+ - Meeting close phrases: "that's all", "we're done", "let's wrap up", "I'll let
93
+ you go"
94
+
95
+ **Supporting signals (strengthen the case but not sufficient alone):**
96
+
97
+ - Long silence gaps (>30 seconds) after a farewell exchange
98
+ - Channel drops — only one speaker remains after goodbyes
99
+ - Shift to clearly unrelated content (ambient noise transcribed as fragments)
100
+ - Filler-only content: repeated "um", "uh", fragments with no meaning
101
+
102
+ **The trim point** is the end of the last meaningful farewell exchange. Include
103
+ the final "bye" / "thank you" / "take care" from both parties if present, then
104
+ cut everything after.
105
+
106
+ ### Step 3 — Confirm with the user
107
+
108
+ Before modifying the file, show the user:
109
+
110
+ 1. The **session title** and **original duration**
111
+ 2. The **last ~20 words before the proposed trim point** (as readable text)
112
+ 3. The **first ~20 words after the proposed trim point** (what will be removed)
113
+ 4. The **new duration** and **number of words being removed**
114
+
115
+ Wait for the user to approve before proceeding.
116
+
117
+ ### Step 4 — Trim the transcript
118
+
119
+ Once approved:
120
+
121
+ 1. Read the current `transcript.json` (fresh read, not cached).
122
+ 2. Slice the words array at the identified index.
123
+ 3. Write the modified JSON back to `transcript.json`.
124
+
125
+ ```python
126
+ import json
127
+
128
+ path = f'~/Library/Application Support/hyprnote/sessions/{uuid}/transcript.json'
129
+ data = json.load(open(path))
130
+ data['transcripts'][0]['words'] = data['transcripts'][0]['words'][:trim_index]
131
+ json.dump(data, open(path, 'w'), indent=2)
132
+ ```
133
+
134
+ 4. Print a summary:
135
+ ```
136
+ Trimmed: {title}
137
+ Before: {original_words} words, {original_duration}
138
+ After: {new_words} words, {new_duration}
139
+ Removed: {removed_words} words ({removed_duration} of trailing content)
140
+ ```
141
+
142
+ ### Step 5 — Delete the audio recording
143
+
144
+ The fact that a transcript needs trimming means the recording captured audio
145
+ beyond the consented meeting — ambient conversation, unrelated chatter, or other
146
+ people who did not consent to being recorded. The full audio file must be
147
+ deleted to respect participant privacy.
148
+
149
+ 1. Delete the audio file:
150
+ ```bash
151
+ rm "~/Library/Application Support/hyprnote/sessions/{uuid}/audio.mp3"
152
+ ```
153
+ 2. Confirm deletion and inform the user:
154
+ ```
155
+ Audio deleted: audio.mp3 removed (recording contained unconsented content beyond the meeting)
156
+ ```
157
+
158
+ This step is **not optional** and does **not require separate user
159
+ confirmation** — the user already approved the trim, which implicitly
160
+ acknowledges the recording went beyond the meeting boundary.
161
+
162
+ ### Step 6 — Verify
163
+
164
+ Read back the last 10 words of the trimmed transcript to confirm the file was
165
+ written correctly and ends at the expected point. Confirm `audio.mp3` no longer
166
+ exists in the session directory.
167
+
168
+ ---
169
+
170
+ ## Quality checklist
171
+
172
+ - [ ] Session ID validated and transcript exists
173
+ - [ ] Logical end identified based on farewell/closing signals
174
+ - [ ] Trim point shown to user and approved before modification
175
+ - [ ] Transcript file written correctly with valid JSON
176
+ - [ ] Audio recording deleted (contains unconsented content beyond meeting)
177
+ - [ ] Post-trim verification confirms expected ending and audio removed
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: manage-tasks
3
+ description: Create, update, list, and close tasks on per-person task boards in knowledge/Tasks/. Manages task lifecycle, extracts action items from other skills, and keeps boards current. Use when the user asks to add, update, or review tasks, or when chained from extract-entities or hyprnote-process.
4
+ ---
5
+
6
+ # Manage Tasks
7
+
8
+ Manage per-person task boards in `knowledge/Tasks/`. Each person has a single
9
+ living document tracking open, in-progress, blocked, and recently completed
10
+ tasks. Task boards are the **canonical source** for task tracking — other notes
11
+ link to them rather than duplicating.
12
+
13
+ ## Trigger
14
+
15
+ - The user asks to add, update, close, or list tasks.
16
+ - Chained from `extract-entities` or `hyprnote-process` with extracted action
17
+ items.
18
+ - The user asks to see someone's task board or workload.
19
+ - Scheduled housekeeping (prune done items, flag overdue).
20
+
21
+ ## Prerequisites
22
+
23
+ - `knowledge/Tasks/` directory exists.
24
+ - User identity configured in `USER.md`.
25
+
26
+ ## Inputs
27
+
28
+ User-initiated: person name and task details from the request.
29
+
30
+ Chained from other skills, action items in this shape:
31
+
32
+ ```
33
+ TASKS:
34
+ - Owner: {Full Name}
35
+ Task: {Description — starts with a verb}
36
+ Priority: high|medium|low
37
+ Due: YYYY-MM-DD (if known)
38
+ Source: meeting|email
39
+ Source date: YYYY-MM-DD
40
+ Project: {Project Name} (if applicable)
41
+ Context: {brief context}
42
+ ```
43
+
44
+ ## Outputs
45
+
46
+ - `knowledge/Tasks/{Person Name}.md` — created or updated.
47
+
48
+ <do_confirm_checklist goal="Verify task board changes are clean and consistent">
49
+
50
+ - [ ] Task title is actionable and starts with a verb.
51
+ - [ ] Priority is set; medium-default omitted.
52
+ - [ ] Due date present only when there's a real deadline.
53
+ - [ ] Project link `[[Projects/Name]]` set when applicable.
54
+ - [ ] No duplicate tasks on the board.
55
+ - [ ] All backlinks use absolute paths `[[Folder/Name]]`.
56
+ - [ ] Context line is concise (1–2 lines max).
57
+ - [ ] New boards include all four sections.
58
+ - [ ] Housekeeping pruned `## Recently Done` to the last 14 days.
59
+
60
+ </do_confirm_checklist>
61
+
62
+ Board layout, entry format, conventions, and useful queries:
63
+ [references/format.md](references/format.md).
64
+
65
+ ## Procedure
66
+
67
+ ### 1. Resolve the person
68
+
69
+ Read `USER.md`, then resolve the target name to its canonical form:
70
+
71
+ ```bash
72
+ ls knowledge/Tasks/
73
+ rg "{name}" knowledge/People/
74
+ ```
75
+
76
+ If no board exists yet, create one (Step 4).
77
+
78
+ ### 2. Read the current board
79
+
80
+ ```bash
81
+ cat "knowledge/Tasks/{Person Name}.md"
82
+ ```
83
+
84
+ Parse the existing entries to: avoid duplicates, understand current workload,
85
+ and find the right insertion point.
86
+
87
+ ### 3. Perform the operation
88
+
89
+ Pick **add**, **update**, **close**, **list**, or **housekeeping** based on the
90
+ request.
91
+
92
+ #### Add
93
+
94
+ 1. Check for duplicates. If a near-duplicate exists, update it instead.
95
+ 2. Pick the section per the routing rules in
96
+ [references/format.md](references/format.md#section-routing).
97
+ 3. Append the entry at the **bottom** of that section using the format from
98
+ `references/format.md`.
99
+ 4. Verify any referenced Project note exists.
100
+
101
+ #### Update
102
+
103
+ Find the task by its bold title (fuzzy match OK). Apply changes:
104
+
105
+ - **Status change:** move the entire entry between sections.
106
+ - **Priority change:** update the `| {priority} |` segment.
107
+ - **Due date change:** update or add `| due YYYY-MM-DD |`.
108
+ - **Add context:** append to the indented line.
109
+
110
+ Use Edit for targeted modifications.
111
+
112
+ #### Close
113
+
114
+ Find the task, remove it from its section, and add to the **top** of
115
+ `## Recently Done` using the closed-task format in
116
+ [references/format.md](references/format.md#closed-task-format).
117
+
118
+ #### List
119
+
120
+ Run the queries in [references/format.md](references/format.md#useful-queries)
121
+ and present results grouped by person or project as the user's question
122
+ warrants.
123
+
124
+ #### Housekeeping (scheduled)
125
+
126
+ 1. Prune entries older than 14 days from `## Recently Done`.
127
+ 2. Flag overdue tasks (due in the past, still in `## Open` or `## In Progress`)
128
+ — report to the user; do **not** auto-modify.
129
+ 3. Merge duplicates on the same board.
130
+ 4. Spot-check `[[People/]]`, `[[Projects/]]`, `[[Goals/]]` references point to
131
+ existing notes.
132
+
133
+ ### 4. Write updates
134
+
135
+ For a new board, create `knowledge/Tasks/{Person Name}.md` with all four
136
+ sections (template in
137
+ [references/format.md](references/format.md#board-structure)).
138
+
139
+ For an existing board, use Edit for targeted changes — never rewrite the whole
140
+ file.
141
+
142
+ ### 5. Migrate open items (one-time)
143
+
144
+ When first setting up boards, scan source notes for `## Open items` sections:
145
+
146
+ ```bash
147
+ rg -l "## Open items" knowledge/People/ knowledge/Projects/ knowledge/Goals/
148
+ ```
149
+
150
+ Convert each item into a task entry on the relevant person's board. **Do not**
151
+ remove the original — it stays as the historical record; the board becomes the
152
+ living tracker.
@@ -0,0 +1,73 @@
1
+ # Task Board Format
2
+
3
+ Reference data for `manage-tasks`.
4
+
5
+ ## Board structure
6
+
7
+ Each task board is a markdown file with four sections, always present (even if
8
+ empty), in this order:
9
+
10
+ ```markdown
11
+ # {Person Name}
12
+
13
+ ## In Progress
14
+
15
+ ## Open
16
+
17
+ ## Blocked
18
+
19
+ ## Recently Done
20
+ ```
21
+
22
+ ## Task entry
23
+
24
+ ```markdown
25
+ - [ ] **{Task title}** | {priority} | due {YYYY-MM-DD} | [[Projects/Name]] → [[Goals/Name]]
26
+ {Context line with source info and backlinks.}
27
+ ```
28
+
29
+ ### Conventions
30
+
31
+ - **Priorities:** `high` | `medium` | `low`. Omit when medium (the default).
32
+ - **Due dates:** include only when there is a real deadline. Format
33
+ `due YYYY-MM-DD`.
34
+ - **Goal link:** append `→ [[Goals/Name]]` only when the task clearly serves a
35
+ specific Goal — not every task needs one.
36
+ - **Titles are the ID:** keep them unique within a board. No task IDs.
37
+ - **Recently Done:** keep the last 14 days; older entries are pruned during
38
+ housekeeping.
39
+
40
+ ## Closed-task format
41
+
42
+ Closed entries drop priority, due date, project link, and context:
43
+
44
+ ```markdown
45
+ - [x] **{Task title}** | completed {YYYY-MM-DD}
46
+ ```
47
+
48
+ ## Section routing
49
+
50
+ - Default add target: `## Open`.
51
+ - Move to `## In Progress` when the user says "I'm working on" / "started".
52
+ - Move to `## Blocked` when the user describes a wait condition; include what's
53
+ needed and from whom in the context line.
54
+ - Move to `## Recently Done` when closed.
55
+
56
+ ## Useful queries
57
+
58
+ ```bash
59
+ # All open / in-progress tasks
60
+ rg "^- \[ \] \*\*" knowledge/Tasks/
61
+
62
+ # Tasks for a specific project
63
+ rg "Projects/{Name}" knowledge/Tasks/
64
+
65
+ # High priority tasks
66
+ rg "\| high \|" knowledge/Tasks/
67
+
68
+ # Find every due date — compare against today to spot overdue
69
+ rg "due 20[0-9]{2}-[0-9]{2}-[0-9]{2}" knowledge/Tasks/
70
+
71
+ # Blocked tasks with their context
72
+ rg -A1 "^- \[ \]" knowledge/Tasks/ | rg -B1 "Waiting on"
73
+ ```