@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,54 @@
1
+ # State Script Reference
2
+
3
+ `req-scan` uses `node scripts/state.mjs <command> [args]` for **all** state-file
4
+ operations. Do **not** write bespoke scripts to update cursor, seen, prospects,
5
+ failures, or log files.
6
+
7
+ ## Cursor (source rotation)
8
+
9
+ ```bash
10
+ node scripts/state.mjs cursor list
11
+ node scripts/state.mjs cursor get github_open_to_work
12
+ node scripts/state.mjs cursor set github_open_to_work "2026-03-09T22:00:00Z" "UK-done_next:Europe"
13
+ ```
14
+
15
+ ## Seen (deduplication)
16
+
17
+ ```bash
18
+ # exit 0 = seen, 1 = new
19
+ node scripts/state.mjs seen check github_open_to_work mxmxmx333
20
+
21
+ node scripts/state.mjs seen add github_open_to_work mxmxmx333
22
+ node scripts/state.mjs seen batch github_open_to_work id1 id2 id3 id4
23
+ ```
24
+
25
+ ## Prospects
26
+
27
+ ```bash
28
+ node scripts/state.mjs prospect add "Hasan Cam" github_open_to_work strong "J060-J070 platform"
29
+ node scripts/state.mjs prospect list --limit 10
30
+ node scripts/state.mjs prospect count
31
+ ```
32
+
33
+ ## Failures
34
+
35
+ ```bash
36
+ # Source selection — skip if ≥ 3
37
+ node scripts/state.mjs failure get mastodon_hachyderm
38
+
39
+ node scripts/state.mjs failure increment mastodon_hachyderm
40
+ node scripts/state.mjs failure reset github_open_to_work
41
+ ```
42
+
43
+ ## Logging
44
+
45
+ ```bash
46
+ node scripts/state.mjs log-wake github_open_to_work "Primary query: 'open to work' location:UK — 30 results, 2 new prospects"
47
+ node scripts/state.mjs log "Manual note about source rotation"
48
+ ```
49
+
50
+ ## Summary
51
+
52
+ ```bash
53
+ node scripts/state.mjs summary
54
+ ```
@@ -0,0 +1,36 @@
1
+ # Prospect Note Template
2
+
3
+ Reference for `req-scan` Step 4. Save to `knowledge/Prospects/{Name}.md`.
4
+
5
+ ```markdown
6
+ # {Name}
7
+
8
+ ## Info
9
+ **Source:** [{platform}]({permalink})
10
+ **Date found:** {YYYY-MM-DD}
11
+ **Location:** {location or "Not specified"}
12
+ **Estimated level:** {J040–J110} (confidence: {high/medium/low})
13
+ **Best track fit:** {forward_deployed / platform / either}
14
+ **Match strength:** {strong / moderate}
15
+
16
+ ## Profile
17
+ {2–4 sentences: who they are, what they do, why they match. Reference
18
+ specific agent-aligned standard skill IDs in parentheses where possible.}
19
+
20
+ ## Agent-Aligned Standard Alignment
21
+ **Matching skills:** {comma-separated skill IDs}
22
+ **Key strengths:** {what stands out relative to the standard}
23
+ **Gaps:** {notable missing skills for the estimated role}
24
+
25
+ ## Source Post
26
+ > {Brief excerpt or summary of the original post — 2-3 lines max}
27
+
28
+ ## Notes
29
+ {Additional observations: polymath signals, AI-tool usage, unusual
30
+ background combinations, anything noteworthy for the user.}
31
+ ```
32
+
33
+ ## Naming
34
+
35
+ Use the candidate's display name as given. If only a username is available, use
36
+ the username. **Never fabricate real names.**
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Manage head-hunter agent state files.
4
+ *
5
+ * Provides atomic operations on the 5 state files used by the head-hunter agent:
6
+ * cursor.tsv — source rotation state (source, last_checked, position)
7
+ * seen.tsv — deduplication index (source, id, date)
8
+ * prospects.tsv — prospect index (name, source, date, strength, level)
9
+ * failures.tsv — consecutive failure counts (source, count)
10
+ * log.md — append-only activity log
11
+ *
12
+ * Usage:
13
+ * node scripts/state.mjs cursor get <source>
14
+ * node scripts/state.mjs cursor set <source> <timestamp> <position>
15
+ * node scripts/state.mjs seen check <source> <id>
16
+ * node scripts/state.mjs seen add <source> <id> [<date>]
17
+ * node scripts/state.mjs prospect add <name> <source> <strength> <level>
18
+ * node scripts/state.mjs prospect list [--limit N]
19
+ * node scripts/state.mjs failure get <source>
20
+ * node scripts/state.mjs failure increment <source>
21
+ * node scripts/state.mjs failure reset <source>
22
+ * node scripts/state.mjs log <message>
23
+ * node scripts/state.mjs log-wake <source> <summary>
24
+ * node scripts/state.mjs summary
25
+ */
26
+
27
+ import {
28
+ appendFileSync,
29
+ existsSync,
30
+ mkdirSync,
31
+ readFileSync,
32
+ writeFileSync,
33
+ } from "node:fs";
34
+ import { join } from "node:path";
35
+ import { homedir } from "node:os";
36
+
37
+ const HOME = homedir();
38
+ const STATE_DIR = join(HOME, ".cache/fit/outpost/head-hunter");
39
+
40
+ const PATHS = {
41
+ cursor: join(STATE_DIR, "cursor.tsv"),
42
+ seen: join(STATE_DIR, "seen.tsv"),
43
+ prospects: join(STATE_DIR, "prospects.tsv"),
44
+ failures: join(STATE_DIR, "failures.tsv"),
45
+ log: join(STATE_DIR, "log.md"),
46
+ };
47
+
48
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
49
+ console.log(`state — manage head-hunter agent state
50
+
51
+ Usage:
52
+ node scripts/state.mjs <command> [args]
53
+
54
+ Cursor commands (source rotation):
55
+ cursor get <source> Get current cursor for source
56
+ cursor set <source> <timestamp> <position> Update cursor position
57
+ cursor list List all cursors
58
+
59
+ Seen commands (deduplication):
60
+ seen check <source> <id> Check if ID was already seen (exit 0=seen, 1=new)
61
+ seen add <source> <id> [<date>] Mark ID as seen (date defaults to today)
62
+ seen batch <source> <id1> <id2> ... Mark multiple IDs as seen
63
+
64
+ Prospect commands:
65
+ prospect add <name> <source> <strength> <level> Add a new prospect
66
+ prospect list [--limit N] List recent prospects
67
+ prospect count Count total prospects
68
+
69
+ Failure commands:
70
+ failure get <source> Get failure count for source
71
+ failure increment <source> Increment failure count
72
+ failure reset <source> Reset failure count to 0
73
+
74
+ Log commands:
75
+ log <message> Append raw text to log.md
76
+ log-wake <source> <summary> Append a formatted wake cycle entry
77
+
78
+ Summary:
79
+ summary Print state overview
80
+
81
+ State dir: ~/.cache/fit/outpost/head-hunter/`);
82
+ process.exit(0);
83
+ }
84
+
85
+ // --- Ensure state directory exists ---
86
+
87
+ mkdirSync(STATE_DIR, { recursive: true });
88
+
89
+ // --- File helpers ---
90
+
91
+ function readTsv(file) {
92
+ if (!existsSync(file)) return [];
93
+ return readFileSync(file, "utf8")
94
+ .split("\n")
95
+ .filter((l) => l.trim());
96
+ }
97
+
98
+ function writeTsv(file, lines) {
99
+ writeFileSync(file, lines.join("\n") + (lines.length ? "\n" : ""));
100
+ }
101
+
102
+ function appendTsv(file, line) {
103
+ appendFileSync(file, line + "\n");
104
+ }
105
+
106
+ function today() {
107
+ return new Date().toISOString().slice(0, 10);
108
+ }
109
+
110
+ // --- Cursor operations ---
111
+
112
+ function cursorGet(source) {
113
+ const lines = readTsv(PATHS.cursor);
114
+ const line = lines.find((l) => l.startsWith(source + "\t"));
115
+ if (!line) {
116
+ console.log(`No cursor for source: ${source}`);
117
+ return null;
118
+ }
119
+ const [, timestamp, position] = line.split("\t");
120
+ console.log(`${source}\t${timestamp}\t${position}`);
121
+ return { source, timestamp, position };
122
+ }
123
+
124
+ function cursorSet(source, timestamp, position) {
125
+ const lines = readTsv(PATHS.cursor);
126
+ const newLine = `${source}\t${timestamp}\t${position}`;
127
+ const idx = lines.findIndex((l) => l.startsWith(source + "\t"));
128
+ if (idx !== -1) {
129
+ lines[idx] = newLine;
130
+ } else {
131
+ lines.push(newLine);
132
+ }
133
+ writeTsv(PATHS.cursor, lines);
134
+ console.log(`Cursor updated: ${newLine}`);
135
+ }
136
+
137
+ function cursorList() {
138
+ const lines = readTsv(PATHS.cursor);
139
+ if (lines.length === 0) {
140
+ console.log("No cursors.");
141
+ return;
142
+ }
143
+ for (const line of lines) {
144
+ console.log(line);
145
+ }
146
+ }
147
+
148
+ // --- Seen operations ---
149
+
150
+ function seenCheck(source, id) {
151
+ const lines = readTsv(PATHS.seen);
152
+ const found = lines.some((l) => {
153
+ const parts = l.split("\t");
154
+ return parts[0] === source && parts[1] === id;
155
+ });
156
+ if (found) {
157
+ console.log(`SEEN: ${source}\t${id}`);
158
+ process.exit(0);
159
+ } else {
160
+ console.log(`NEW: ${source}\t${id}`);
161
+ process.exit(1);
162
+ }
163
+ }
164
+
165
+ function seenAdd(source, id, date) {
166
+ appendTsv(PATHS.seen, `${source}\t${id}\t${date || today()}`);
167
+ console.log(`Marked seen: ${source}\t${id}\t${date || today()}`);
168
+ }
169
+
170
+ function seenBatch(source, ids) {
171
+ const d = today();
172
+ const lines = ids.map((id) => `${source}\t${id}\t${d}`);
173
+ appendFileSync(PATHS.seen, lines.join("\n") + "\n");
174
+ console.log(`Marked ${ids.length} IDs as seen for ${source}`);
175
+ }
176
+
177
+ // --- Prospect operations ---
178
+
179
+ function prospectAdd(name, source, strength, level) {
180
+ const d = today();
181
+ appendTsv(PATHS.prospects, `${name}\t${source}\t${d}\t${strength}\t${level}`);
182
+ console.log(`Prospect added: ${name} (${strength}, ${level})`);
183
+ }
184
+
185
+ function prospectList(limit) {
186
+ const lines = readTsv(PATHS.prospects);
187
+ if (lines.length === 0) {
188
+ console.log("No prospects.");
189
+ return;
190
+ }
191
+ // Show most recent first
192
+ const display = lines.slice(-limit).reverse();
193
+ for (const line of display) {
194
+ console.log(line);
195
+ }
196
+ if (lines.length > limit) {
197
+ console.log(`\n... ${lines.length - limit} more (${lines.length} total)`);
198
+ }
199
+ }
200
+
201
+ function prospectCount() {
202
+ const lines = readTsv(PATHS.prospects);
203
+ console.log(lines.length);
204
+ }
205
+
206
+ // --- Failure operations ---
207
+
208
+ function failureGet(source) {
209
+ const lines = readTsv(PATHS.failures);
210
+ const line = lines.find((l) => l.startsWith(source + "\t"));
211
+ if (!line) {
212
+ console.log(`0`);
213
+ return 0;
214
+ }
215
+ const count = parseInt(line.split("\t")[1], 10) || 0;
216
+ console.log(`${count}`);
217
+ return count;
218
+ }
219
+
220
+ function failureIncrement(source) {
221
+ const lines = readTsv(PATHS.failures);
222
+ const idx = lines.findIndex((l) => l.startsWith(source + "\t"));
223
+ if (idx !== -1) {
224
+ const count = parseInt(lines[idx].split("\t")[1], 10) || 0;
225
+ lines[idx] = `${source}\t${count + 1}`;
226
+ } else {
227
+ lines.push(`${source}\t1`);
228
+ }
229
+ writeTsv(PATHS.failures, lines);
230
+ const newCount = parseInt(
231
+ lines.find((l) => l.startsWith(source + "\t")).split("\t")[1],
232
+ 10,
233
+ );
234
+ console.log(`Failures for ${source}: ${newCount}`);
235
+ if (newCount >= 3) {
236
+ console.log(
237
+ `WARNING: ${source} has ${newCount} consecutive failures (suspended at ≥3)`,
238
+ );
239
+ }
240
+ }
241
+
242
+ function failureReset(source) {
243
+ const lines = readTsv(PATHS.failures);
244
+ const idx = lines.findIndex((l) => l.startsWith(source + "\t"));
245
+ if (idx !== -1) {
246
+ lines[idx] = `${source}\t0`;
247
+ writeTsv(PATHS.failures, lines);
248
+ }
249
+ console.log(`Failures reset for ${source}`);
250
+ }
251
+
252
+ // --- Log operations ---
253
+
254
+ function logAppend(message) {
255
+ appendFileSync(PATHS.log, message + "\n");
256
+ console.log("Log entry appended.");
257
+ }
258
+
259
+ function logWake(source, summary) {
260
+ const timestamp = new Date().toISOString().slice(0, 16).replace("T", " ");
261
+ const entry = `\n## ${today()} ${timestamp.slice(11)} — Wake (${source})\n\n${summary}\n\n---\n`;
262
+ appendFileSync(PATHS.log, entry);
263
+ console.log(`Wake cycle logged for ${source}`);
264
+ }
265
+
266
+ // --- Summary ---
267
+
268
+ function summary() {
269
+ const cursors = readTsv(PATHS.cursor);
270
+ const seen = readTsv(PATHS.seen);
271
+ const prospects = readTsv(PATHS.prospects);
272
+ const failures = readTsv(PATHS.failures);
273
+
274
+ console.log("=== Head-Hunter State Summary ===\n");
275
+
276
+ console.log(`Sources: ${cursors.length}`);
277
+ for (const line of cursors) {
278
+ const [source, timestamp, position] = line.split("\t");
279
+ const failLine = failures.find((l) => l.startsWith(source + "\t"));
280
+ const failCount = failLine ? parseInt(failLine.split("\t")[1], 10) : 0;
281
+ const status = failCount >= 3 ? " [SUSPENDED]" : "";
282
+ console.log(` ${source}: last=${timestamp}, pos=${position}${status}`);
283
+ }
284
+
285
+ console.log(`\nSeen: ${seen.length} entries`);
286
+ console.log(`Prospects: ${prospects.length} total`);
287
+
288
+ // Strength breakdown
289
+ const byStrength = {};
290
+ for (const line of prospects) {
291
+ const strength = line.split("\t")[3] || "unknown";
292
+ byStrength[strength] = (byStrength[strength] || 0) + 1;
293
+ }
294
+ for (const [k, v] of Object.entries(byStrength)) {
295
+ console.log(` ${k}: ${v}`);
296
+ }
297
+
298
+ // Recent prospects (last 5)
299
+ if (prospects.length > 0) {
300
+ console.log("\nRecent prospects:");
301
+ for (const line of prospects.slice(-5)) {
302
+ const [name, source, date, strength, level] = line.split("\t");
303
+ console.log(
304
+ ` ${date} | ${name} | ${strength} | ${level} | via ${source}`,
305
+ );
306
+ }
307
+ }
308
+ }
309
+
310
+ // --- CLI Router ---
311
+
312
+ const cliArgs = process.argv.slice(2);
313
+ const cmd = cliArgs[0];
314
+ const sub = cliArgs[1];
315
+
316
+ switch (cmd) {
317
+ case "cursor":
318
+ if (sub === "get" && cliArgs[2]) cursorGet(cliArgs[2]);
319
+ else if (sub === "set" && cliArgs[2] && cliArgs[3] && cliArgs[4])
320
+ cursorSet(cliArgs[2], cliArgs[3], cliArgs[4]);
321
+ else if (sub === "list") cursorList();
322
+ else {
323
+ console.error(
324
+ "Usage: cursor get|set|list <source> [<timestamp> <position>]",
325
+ );
326
+ process.exit(1);
327
+ }
328
+ break;
329
+
330
+ case "seen":
331
+ if (sub === "check" && cliArgs[2] && cliArgs[3])
332
+ seenCheck(cliArgs[2], cliArgs[3]);
333
+ else if (sub === "add" && cliArgs[2] && cliArgs[3])
334
+ seenAdd(cliArgs[2], cliArgs[3], cliArgs[4]);
335
+ else if (sub === "batch" && cliArgs[2] && cliArgs.length > 3)
336
+ seenBatch(cliArgs[2], cliArgs.slice(3));
337
+ else {
338
+ console.error("Usage: seen check|add|batch <source> <id> [...]");
339
+ process.exit(1);
340
+ }
341
+ break;
342
+
343
+ case "prospect":
344
+ if (sub === "add" && cliArgs[2] && cliArgs[3] && cliArgs[4] && cliArgs[5])
345
+ prospectAdd(cliArgs[2], cliArgs[3], cliArgs[4], cliArgs[5]);
346
+ else if (sub === "list") {
347
+ const lIdx = cliArgs.indexOf("--limit");
348
+ const lim = lIdx !== -1 ? parseInt(cliArgs[lIdx + 1], 10) || 10 : 10;
349
+ prospectList(lim);
350
+ } else if (sub === "count") prospectCount();
351
+ else {
352
+ console.error(
353
+ "Usage: prospect add|list|count <name> <source> <strength> <level>",
354
+ );
355
+ process.exit(1);
356
+ }
357
+ break;
358
+
359
+ case "failure":
360
+ if (sub === "get" && cliArgs[2]) failureGet(cliArgs[2]);
361
+ else if (sub === "increment" && cliArgs[2]) failureIncrement(cliArgs[2]);
362
+ else if (sub === "reset" && cliArgs[2]) failureReset(cliArgs[2]);
363
+ else {
364
+ console.error("Usage: failure get|increment|reset <source>");
365
+ process.exit(1);
366
+ }
367
+ break;
368
+
369
+ case "log":
370
+ if (cliArgs.length >= 2) logAppend(cliArgs.slice(1).join(" "));
371
+ else {
372
+ console.error("Usage: log <message>");
373
+ process.exit(1);
374
+ }
375
+ break;
376
+
377
+ case "log-wake":
378
+ if (cliArgs[1] && cliArgs.length >= 3)
379
+ logWake(cliArgs[1], cliArgs.slice(2).join(" "));
380
+ else {
381
+ console.error("Usage: log-wake <source> <summary>");
382
+ process.exit(1);
383
+ }
384
+ break;
385
+
386
+ case "summary":
387
+ summary();
388
+ break;
389
+
390
+ default:
391
+ console.error(
392
+ "Unknown command. Run with --help for usage.\n" +
393
+ "Commands: cursor, seen, prospect, failure, log, log-wake, summary",
394
+ );
395
+ process.exit(1);
396
+ }
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: req-screen
3
+ description: >
4
+ Screen candidate CVs against the agent-aligned engineering standard to decide
5
+ whether to invest interview time. Produces a structured screening assessment
6
+ with interview/pass recommendation and suggested interview focus areas.
7
+ Use when the user asks to evaluate a CV or when a new CV is detected.
8
+ ---
9
+
10
+ # Screen CV
11
+
12
+ Screen a candidate's CV against the agent-aligned engineering standard defined
13
+ in `fit-pathway`. The single question this skill answers: **is this candidate
14
+ worth interviewing?** Every assessment is grounded in the standard — no
15
+ subjective impressions.
16
+
17
+ This is **Stage 1** of a three-stage hiring pipeline:
18
+
19
+ 1. **Screen CV** (this skill) — CV arrives → interview or pass.
20
+ 2. `req-assess` — interview transcript arrives → updated evidence.
21
+ 3. `req-decide` — all stages complete → hire / don't hire.
22
+
23
+ ## Trigger
24
+
25
+ - A new CV is added to `knowledge/Candidates/{Name}/`.
26
+ - A CV appears in `~/Downloads/` and is associated with a candidate.
27
+ - The user asks to screen, evaluate, or assess a CV.
28
+ - The user asks "is this person worth interviewing?".
29
+
30
+ ## Prerequisites
31
+
32
+ - `fit-pathway` CLI installed (`bunx fit-pathway` works).
33
+ - A CV file (PDF or DOCX) on the filesystem.
34
+ - Optionally a target role: `{discipline} {level} --track={track}`.
35
+
36
+ ## Inputs
37
+
38
+ - CV file path (e.g. `knowledge/Candidates/{Name}/CV.pdf`).
39
+ - Target role (optional).
40
+ - Existing `knowledge/Candidates/{Name}/brief.md`, if any.
41
+ - `knowledge/Roles/*.md` matching the candidate's `Req` (provides `Level`,
42
+ `Discipline`, `Hiring manager`, `Domain lead`).
43
+
44
+ ## Outputs
45
+
46
+ - `knowledge/Candidates/{Name}/screening.md` — structured assessment.
47
+ - Updated `knowledge/Candidates/{Name}/brief.md` — skills + summary enriched.
48
+
49
+ <do_confirm_checklist goal="Verify the screening is grounded and
50
+ decision-rule-compliant">
51
+
52
+ - [ ] Every claim cites CV evidence or marks "Not evidenced".
53
+ - [ ] Two-level scepticism applied; vague phrases didn't earn levels.
54
+ - [ ] "Not evidenced" skills are counted as gaps in the recommendation.
55
+ - [ ] Recommendation follows the decision rules and threshold rule — match % and
56
+ gap count verified before picking a tier.
57
+ - [ ] "Interview with focus areas" used only for strong candidates with a named
58
+ concern — not as a soft "maybe".
59
+ - [ ] Output file is exactly `screening.md`; any misnamed prior file deleted.
60
+ - [ ] `brief.md` links the screening as `[CV Screening](./screening.md)` and
61
+ Skills/Summary updated via targeted edits.
62
+ - [ ] Gender set only from explicit pronouns/titles.
63
+ - [ ] Recommendation header carries the advisory-only banner.
64
+
65
+ </do_confirm_checklist>
66
+
67
+ ## Procedure
68
+
69
+ ### 1. Read the CV
70
+
71
+ Extract the fields listed in
72
+ [references/rubric.md](references/rubric.md#what-to-extract-from-the-cv).
73
+
74
+ ### 2. Anchor the target role
75
+
76
+ If `brief.md` carries a `Req`, look up the matching Role file:
77
+
78
+ ```bash
79
+ ls knowledge/Roles/ | grep "{req_number}"
80
+ cat "knowledge/Roles/{matching file}"
81
+ ```
82
+
83
+ Use the Role's `Level` and `Discipline` as the target unless the user specified
84
+ a different target. Capture `Hiring manager` and `Domain lead` for the screening
85
+ header.
86
+
87
+ If no target is available, estimate one using the level heuristics in
88
+ [references/rubric.md](references/rubric.md#level-estimation-heuristics).
89
+
90
+ ### 3. Load the standard
91
+
92
+ ```bash
93
+ bunx fit-pathway job {discipline} {level} --track={track}
94
+ bunx fit-pathway job {discipline} {level} --track={track} --skills
95
+ bunx fit-pathway track forward_deployed
96
+ bunx fit-pathway track platform
97
+ ```
98
+
99
+ ### 4. Map CV → standard skills
100
+
101
+ For each skill in the target job, assess the candidate's likely proficiency.
102
+ Look up nuance with `bunx fit-pathway skill {skill_id}`. Use the proficiency
103
+ mapping and the scepticism rule in
104
+ [references/rubric.md](references/rubric.md#proficiency-mapping).
105
+
106
+ ### 5. Assess behaviours
107
+
108
+ ```bash
109
+ bunx fit-pathway behaviour --list
110
+ ```
111
+
112
+ Map CV evidence using the behaviour signals in
113
+ [references/rubric.md](references/rubric.md#behaviour-signals).
114
+
115
+ ### 6. Classify gaps and strengths
116
+
117
+ Optional progression context:
118
+
119
+ ```bash
120
+ bunx fit-pathway progress {discipline} {level} --track={track}
121
+ ```
122
+
123
+ Classify each skill per
124
+ [references/rubric.md](references/rubric.md#skill-alignment-classification).
125
+ Pick the recommendation using the decision rules and threshold rule in
126
+ [references/rubric.md](references/rubric.md#recommendation-decision-rules).
127
+
128
+ ### 7. Write the screening
129
+
130
+ Save to `knowledge/Candidates/{Name}/screening.md` using the template in
131
+ [references/template.md](references/template.md). Include the **Suggested
132
+ Interview Questions** when the recommendation is "Interview" or "Interview with
133
+ focus areas":
134
+
135
+ ```bash
136
+ bunx fit-pathway interview {discipline} {level} --track={track}
137
+ ```
138
+
139
+ Pick 3–5 questions most relevant to the gaps; note which gap each targets.
140
+
141
+ ### 8. Enrich the brief
142
+
143
+ If `brief.md` exists, apply targeted edits:
144
+
145
+ - Add or update `## Skills` with agent-aligned standard skill IDs.
146
+ - Update `## Summary` if the CV provides better context.
147
+ - Set `**Gender:**` only when explicitly stated and not already set.
148
+ - Append `- [CV Screening](./screening.md)` if missing.
149
+
150
+ If no brief exists, tell the user to run `req-track` first to build the
151
+ candidate profile from email threads.