@forwardimpact/outpost 3.9.0 → 3.11.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 (40) hide show
  1. package/package.json +1 -1
  2. package/src/kb-manager.js +27 -1
  3. package/templates/.claude/agents/chief-of-staff.md +9 -7
  4. package/templates/.claude/agents/concierge.md +6 -4
  5. package/templates/.claude/agents/head-hunter.md +6 -4
  6. package/templates/.claude/agents/librarian.md +14 -10
  7. package/templates/.claude/agents/postman.md +6 -4
  8. package/templates/.claude/agents/recruiter.md +8 -5
  9. package/templates/.claude/skills/anarlog-follow/SKILL.md +2 -2
  10. package/templates/.claude/skills/anarlog-follow/references/coaching.md +3 -3
  11. package/templates/.claude/skills/anarlog-trim/SKILL.md +9 -3
  12. package/templates/.claude/skills/candidate-report/SKILL.md +4 -3
  13. package/templates/.claude/skills/changelog/SKILL.md +10 -10
  14. package/templates/.claude/skills/deck-create/SKILL.md +23 -22
  15. package/templates/.claude/skills/deck-review/SKILL.md +36 -26
  16. package/templates/.claude/skills/deck-review/assets/slide-annotator.js +100 -15
  17. package/templates/.claude/skills/doc-create/SKILL.md +17 -16
  18. package/templates/.claude/skills/extract-entities/SKILL.md +7 -4
  19. package/templates/.claude/skills/extract-entities/references/recruitment.md +13 -3
  20. package/templates/.claude/skills/meeting-prep/SKILL.md +5 -3
  21. package/templates/.claude/skills/organize-files/SKILL.md +9 -5
  22. package/templates/.claude/skills/person-identify/SKILL.md +2 -2
  23. package/templates/.claude/skills/person-lookup/SKILL.md +3 -3
  24. package/templates/.claude/skills/req-decide/SKILL.md +4 -2
  25. package/templates/.claude/skills/req-forget/references/report-template.md +1 -1
  26. package/templates/.claude/skills/req-scan/references/fallbacks.md +3 -3
  27. package/templates/.claude/skills/req-scan/references/sources.md +5 -5
  28. package/templates/.claude/skills/req-screen/SKILL.md +4 -2
  29. package/templates/.claude/skills/req-track/SKILL.md +19 -15
  30. package/templates/.claude/skills/req-track/references/fields.md +4 -2
  31. package/templates/.claude/skills/req-workday/SKILL.md +4 -2
  32. package/templates/.claude/skills/req-workday/references/status-mapping.md +1 -1
  33. package/templates/.claude/skills/req-workday/references/templates.md +3 -1
  34. package/templates/.claude/skills/sync-apple-calendar/SKILL.md +6 -2
  35. package/templates/.claude/skills/sync-apple-mail/SKILL.md +3 -1
  36. package/templates/.claude/skills/sync-apple-mail/references/SCHEMA.md +1 -1
  37. package/templates/.claude/skills/sync-teams/SKILL.md +13 -1
  38. package/templates/.claude/skills/sync-teams/scripts/idb-reader.mjs +67 -34
  39. package/templates/.claude/skills/upstream-instructions/SKILL.md +2 -1
  40. package/templates/CLAUDE.md +27 -38
@@ -13,6 +13,11 @@
13
13
  * (b) resolve each highlight to its SOURCE line/column + context lines by
14
14
  * reading the page’s own source file. No build step, no server, no deps.
15
15
  *
16
+ * The connected folder is remembered across reloads (the directory handle is
17
+ * stored in IndexedDB). On reload it reconnects silently if the browser still
18
+ * grants permission; otherwise the button reads “Reconnect folder” and one
19
+ * click re-grants access without re-picking the folder.
20
+ *
16
21
  * Optional host hook: if the page defines `window.deckGoto(index)` the panel’s
17
22
  * “Go” buttons will navigate to the right slide. Without it, the tool still
18
23
  * works (it falls back to scrollIntoView).
@@ -60,7 +65,8 @@
60
65
  /* ----------------------------- state ------------------------------------ */
61
66
  var state = loadLocal(); // { version, tool, target, annotations:[] }
62
67
  var review = false;
63
- var dirHandle = null; // File System Access directory handle
68
+ var dirHandle = null; // File System Access directory handle (active)
69
+ var rememberedHandle = null; // handle from a prior session, awaiting re-grant
64
70
  var sourceText = null; // page source (for line/column)
65
71
  var pending = null; // annotation awaiting note + confirm
66
72
  var ui = {}; // DOM refs
@@ -150,6 +156,7 @@
150
156
  ui.count = ui.panel.querySelector(".sa-count");
151
157
  ui.status = ui.panel.querySelector(".sa-status");
152
158
  ui.list = ui.panel.querySelector(".sa-list");
159
+ ui.connectBtn = ui.panel.querySelector('[data-act="connect"]');
153
160
  ui.note = ui.pop.querySelector("textarea");
154
161
  var onlyThisSlide = false;
155
162
 
@@ -435,28 +442,105 @@
435
442
  if (ui.count) ui.count.textContent = state.annotations.length;
436
443
  }
437
444
 
445
+ /* Persist the connected directory handle across reloads. FileSystemHandles are
446
+ * structured-cloneable, so they live in IndexedDB (localStorage is strings
447
+ * only). Re-granting read/write permission after a reload needs a user
448
+ * gesture, so a remembered folder reconnects with one click via the same
449
+ * "Connect folder" button rather than the full directory picker. Keyed by
450
+ * cfg.target so distinct decks in a folder don't clobber each other. */
451
+ var IDB_NAME = "slide-annotator", IDB_STORE = "handles";
452
+ function idbOpen() {
453
+ return new Promise(function (resolve, reject) {
454
+ if (!window.indexedDB) { reject(new Error("no-idb")); return; }
455
+ var req = indexedDB.open(IDB_NAME, 1);
456
+ req.onupgradeneeded = function () { req.result.createObjectStore(IDB_STORE); };
457
+ req.onsuccess = function () { resolve(req.result); };
458
+ req.onerror = function () { reject(req.error); };
459
+ });
460
+ }
461
+ function idbGet(key) {
462
+ return idbOpen().then(function (db) {
463
+ return new Promise(function (resolve, reject) {
464
+ var r = db.transaction(IDB_STORE, "readonly").objectStore(IDB_STORE).get(key);
465
+ r.onsuccess = function () { resolve(r.result || null); };
466
+ r.onerror = function () { reject(r.error); };
467
+ });
468
+ }).catch(function () { return null; });
469
+ }
470
+ function idbSet(key, val) {
471
+ return idbOpen().then(function (db) {
472
+ return new Promise(function (resolve, reject) {
473
+ var tx = db.transaction(IDB_STORE, "readwrite");
474
+ tx.objectStore(IDB_STORE).put(val, key);
475
+ tx.oncomplete = function () { resolve(true); };
476
+ tx.onerror = function () { reject(tx.error); };
477
+ });
478
+ }).catch(function () { return false; });
479
+ }
480
+
481
+ function updateConnectLabel() {
482
+ if (!ui.connectBtn) return;
483
+ ui.connectBtn.textContent = dirHandle ? "Folder connected"
484
+ : rememberedHandle ? "Reconnect folder" : "Connect folder";
485
+ }
486
+
487
+ // Adopt a (freshly picked or re-granted) handle: link source, load any
488
+ // sidecar, and remember it for next time. Shared by connect and restore.
489
+ async function adoptFolder(handle) {
490
+ dirHandle = handle;
491
+ // read page source → enables source line/column resolution
492
+ try {
493
+ var fh = await dirHandle.getFileHandle(cfg.target);
494
+ sourceText = await (await fh.getFile()).text();
495
+ state.annotations.forEach(function (a) { a.source = locateInSource(a); });
496
+ } catch (e) { sourceText = null; setStatus("Connected, but '" + cfg.target + "' not found in folder — line numbers unavailable."); }
497
+ // load an existing sidecar if present
498
+ try {
499
+ var sh = await dirHandle.getFileHandle(SIDECAR);
500
+ mergeLoaded(JSON.parse(await (await sh.getFile()).text()));
501
+ } catch (e) { /* none yet */ }
502
+ await idbSet(cfg.target, dirHandle);
503
+ rememberedHandle = null;
504
+ updateConnectLabel();
505
+ persistLocal(); renderPanel(); renderHighlights();
506
+ setStatus("Connected. Saving to " + SIDECAR + (sourceText ? " · source linked" : ""));
507
+ }
508
+
438
509
  async function connectFolder() {
510
+ // Prefer a folder remembered from a prior session: re-granting permission is
511
+ // a single click (this click IS the required user gesture), no picker.
512
+ if (rememberedHandle) {
513
+ try {
514
+ var perm = await rememberedHandle.requestPermission({ mode: "readwrite" });
515
+ if (perm === "granted") { await adoptFolder(rememberedHandle); return; }
516
+ } catch (e) { /* fall through to the picker */ }
517
+ rememberedHandle = null; updateConnectLabel();
518
+ }
439
519
  if (!window.showDirectoryPicker) { setStatus("Folder access unsupported in this browser. Use Save (downloads the JSON)."); return; }
440
520
  try {
441
- dirHandle = await window.showDirectoryPicker({ mode: "readwrite" });
442
- // read page source → enables source line/column resolution
443
- try {
444
- var fh = await dirHandle.getFileHandle(cfg.target);
445
- sourceText = await (await fh.getFile()).text();
446
- state.annotations.forEach(function (a) { a.source = locateInSource(a); });
447
- } catch (e) { sourceText = null; setStatus("Connected, but '" + cfg.target + "' not found in folder — line numbers unavailable."); }
448
- // load an existing sidecar if present
449
- try {
450
- var sh = await dirHandle.getFileHandle(SIDECAR);
451
- mergeLoaded(JSON.parse(await (await sh.getFile()).text()));
452
- } catch (e) { /* none yet */ }
453
- persistLocal(); renderPanel(); renderHighlights();
454
- setStatus("Connected. Saving to " + SIDECAR + (sourceText ? " · source linked" : ""));
521
+ await adoptFolder(await window.showDirectoryPicker({ mode: "readwrite" }));
455
522
  } catch (e) {
456
523
  if (e && e.name !== "AbortError") setStatus("Folder access blocked (" + e.name + "). Use Save to download the JSON instead.");
457
524
  }
458
525
  }
459
526
 
527
+ // On load, try to restore a previously connected folder. queryPermission needs
528
+ // no gesture; if it's still "granted" we reconnect silently. If it's "prompt",
529
+ // we keep the handle so the next "Connect folder" click reconnects with one
530
+ // grant dialog (no picker). Handles cleared/denied storage are ignored.
531
+ function restoreFolder() {
532
+ idbGet(cfg.target).then(function (handle) {
533
+ if (!handle || typeof handle.queryPermission !== "function") return;
534
+ handle.queryPermission({ mode: "readwrite" }).then(function (perm) {
535
+ if (perm === "granted") { adoptFolder(handle); }
536
+ else {
537
+ rememberedHandle = handle; updateConnectLabel();
538
+ if (review) setStatus("Folder '" + cfg.target + "' remembered — click Reconnect folder.");
539
+ }
540
+ }).catch(function () {});
541
+ });
542
+ }
543
+
460
544
  async function saveDisk() {
461
545
  if (sourceText) state.annotations.forEach(function (a) { if (!a.source) a.source = locateInSource(a); });
462
546
  var json = JSON.stringify(serialize(), null, 2);
@@ -591,4 +675,5 @@
591
675
  };
592
676
 
593
677
  updateCount();
678
+ restoreFolder();
594
679
  })();
@@ -34,22 +34,23 @@ submission, brief, or any multi-page PDF that is not a slide deck.
34
34
 
35
35
  ## Workflow
36
36
 
37
- 1. Check `Knowledge/` for relevant context about the company, product, team,
38
- projects, or people mentioned.
39
- 2. Ensure Playwright is installed:
40
- `bun install playwright && bunx playwright install chromium`
41
- 3. Create a self-contained HTML file with all CSS inlined. The HTML must handle
42
- its own page layout — see **HTML Document Rules** below.
43
- 4. Run the conversion script:
44
-
45
- node .claude/skills/doc-create/scripts/convert-to-pdf.mjs <input.html> [output.pdf]
46
-
47
- If output is omitted, the PDF is written alongside the HTML file with the
48
- same name.
49
-
50
- 5. Read the PDF back to visually verify it renders correctly. Check each page
51
- for overflow, clipped content, and correct page breaks. Fix and re-render if
52
- needed.
37
+ 1. Check `Knowledge/` for relevant context about the company, product, team,
38
+ projects, or people mentioned.
39
+ 2. Ensure Playwright is installed:
40
+ `bun install playwright && bunx playwright install chromium`
41
+ 3. Create a self-contained HTML file with all CSS inlined. The HTML must handle
42
+ its own page layout — see **HTML Document Rules** below.
43
+ 4. Run the conversion script:
44
+
45
+ ```text
46
+ node .claude/skills/doc-create/scripts/convert-to-pdf.mjs <input.html> [output.pdf]
47
+ ```
48
+
49
+ If output is omitted, the PDF is written alongside the HTML file with the
50
+ same name.
51
+ 5. Read the PDF back to visually verify it renders correctly. Check each page
52
+ for overflow, clipped content, and correct page breaks. Fix and re-render if
53
+ needed.
53
54
 
54
55
  **Do NOT show HTML code to the user. Just create the PDF and deliver it.**
55
56
 
@@ -41,8 +41,9 @@ under `Knowledge/`. The core knowledge-graph builder.
41
41
  auto-created.
42
42
  - `Knowledge/Conditions/` — created when cross-cutting patterns are detected, or
43
43
  updated.
44
- - `Knowledge/Roles/`, `Knowledge/Candidates/*/brief.md` — enriched with inferred
45
- metadata.
44
+ - `Knowledge/Roles/*.md`, `Knowledge/Candidates/*/brief.md` — enriched with
45
+ inferred metadata. New role stubs created with `**Status:** open`; update to
46
+ `**Status:** closed` when a role closes.
46
47
  - `~/.cache/fit/outpost/state/graph_processed` — updated.
47
48
 
48
49
  <do_confirm_checklist goal="Verify the batch produced clean, linked,
@@ -76,7 +77,8 @@ writes.
76
77
  ### 0. Load context and pick the batch
77
78
 
78
79
  Read the user's identity from `~/.cache/fit/outpost/state/identity.md` (run the
79
- `person-identify` skill first if it is missing or stale). Find new/changed files:
80
+ `person-identify` skill first if it is missing or stale). Find new/changed
81
+ files:
80
82
 
81
83
  ```bash
82
84
  node scripts/state.mjs check
@@ -151,7 +153,8 @@ filler or meta-commentary.
151
153
  domain-lead inference):
152
154
  [references/recruitment.md](references/recruitment.md).
153
155
  - **Priority links** (Step 7c): rules in
154
- [references/links.md](references/links.md#priorities-step-7c). **Never auto-create.**
156
+ [references/links.md](references/links.md#priorities-step-7c).
157
+ **Never auto-create.**
155
158
  - **Conditions** (cross-cutting states affecting ≥ 3 entities):
156
159
  [references/conditions.md](references/conditions.md).
157
160
 
@@ -3,6 +3,13 @@
3
3
  Reference for `extract-entities` Step 7b. Enrich `Knowledge/Roles/` and
4
4
  `Knowledge/Candidates/` with metadata that no single source carries.
5
5
 
6
+ All Role files are flat in `Knowledge/Roles/`. The `**Status:**` field
7
+ distinguishes:
8
+
9
+ - **`Status: open`** — active openings (use for new candidates and table
10
+ rebuilds).
11
+ - **`Status: closed`** — completed/withdrawn roles (read-only).
12
+
6
13
  ## Requisition number detection
7
14
 
8
15
  Scan email subjects and bodies for requisition numbers (e.g. 7-digit Workday
@@ -10,9 +17,12 @@ IDs).
10
17
 
11
18
  1. `ls Knowledge/Roles/ | grep "{req_number}"` — does a Role file exist?
12
19
  2. **No file:** create a stub using the Role-stub template in `req-track` Step
13
- 0b. Search `rg "{req_number}" Knowledge/` for context to enrich it.
14
- 3. **File exists:** check whether the email provides new metadata (hiring
15
- manager, recruiter, locations) and update the Role file.
20
+ 0b, with `**Status:** open`. Search `rg "{req_number}" Knowledge/` for
21
+ context to enrich it.
22
+ 3. **File exists:** check the `**Status:**` field. If `open`, check whether the
23
+ email provides new metadata (hiring manager, recruiter, locations) and update
24
+ the Role file. If `closed`, link for historical reference only; do not add
25
+ new candidates or rebuild tables.
16
26
 
17
27
  ## Hiring manager — calendar inference
18
28
 
@@ -25,9 +25,11 @@ meetings.
25
25
  - `Knowledge/People/*.md` — attendee context
26
26
  - `Knowledge/Organizations/*.md` — company context
27
27
  - `Knowledge/Projects/*.md` — project context
28
- - `Knowledge/Priorities/*.md` — active priorities and strategic context for framing
28
+ - `Knowledge/Priorities/*.md` — active priorities and strategic context for
29
+ framing
29
30
  - `Knowledge/Candidates/*/brief.md` — candidate context (for interview meetings)
30
- - `Knowledge/Roles/*.md` — role/requisition context (for interview meetings)
31
+ - `Knowledge/Roles/*.md` — role/requisition context (for interview meetings);
32
+ check the `**Status:**` field to distinguish active from historical reqs
31
33
 
32
34
  ## Outputs
33
35
 
@@ -144,7 +146,7 @@ When preparing for interview meetings (title contains "Interview", "Screening",
144
146
 
145
147
  1. **Read the candidate brief:** `Knowledge/Candidates/{Name}/brief.md`
146
148
  2. **Read the Role file:** Look up the `Req` field and read the corresponding
147
- `Knowledge/Roles/*.md` file.
149
+ `Knowledge/Roles/*.md` file; check the `**Status:**` field for context.
148
150
  3. **Include in the briefing:**
149
151
  - Candidate's current status, skills, and screening recommendation
150
152
  - Role context: hiring manager, domain lead, remaining positions
@@ -61,7 +61,9 @@ Run when the user asks to find, organize, clean up, or tidy files on their Mac.
61
61
 
62
62
  Get an overview of both directories:
63
63
 
64
- node scripts/summarize.mjs
64
+ ```text
65
+ node scripts/summarize.mjs
66
+ ```
65
67
 
66
68
  ## Finding Files
67
69
 
@@ -79,8 +81,10 @@ find ~/Desktop -maxdepth 1 \( -name "Screenshot*" -o -name "Screen Shot*" \)
79
81
  Organize a directory into type-based subdirectories (Documents, Images,
80
82
  Archives, Installers, Screenshots):
81
83
 
82
- node scripts/organize-by-type.mjs ~/Downloads
83
- node scripts/organize-by-type.mjs ~/Desktop
84
+ ```text
85
+ node scripts/organize-by-type.mjs ~/Downloads
86
+ node scripts/organize-by-type.mjs ~/Desktop
87
+ ```
84
88
 
85
89
  The script creates subdirectories and moves matching files. It does NOT delete
86
90
  anything.
@@ -106,7 +110,7 @@ After organizing, collect the paths of document files and invoke the
106
110
 
107
111
  **Plan:**
108
112
 
109
- ```
113
+ ```text
110
114
  Organization Plan: Desktop & Downloads Cleanup
111
115
 
112
116
  Found 47 files to organize:
@@ -123,7 +127,7 @@ Should I proceed?
123
127
 
124
128
  **Results:**
125
129
 
126
- ```
130
+ ```text
127
131
  Organization Complete
128
132
 
129
133
  Moved 47 files:
@@ -98,7 +98,7 @@ Key attributes returned (names per Active Directory schema):
98
98
 
99
99
  - To look up **someone else**, use the sibling `person-lookup` skill — it takes
100
100
  free-text input (email or name), searches the Global Catalog forest-wide
101
- (`ldap://$dc:3268 -b ''`), handles multiple matches, and does **not** touch the
102
- identity cache.
101
+ (`ldap://$dc:3268 -b ''`), handles multiple matches, and does **not** touch
102
+ the identity cache.
103
103
  - Not Active Directory? The same `ldapsearch -Y GSSAPI` shape works against any
104
104
  Kerberos-backed LDAP directory; only the attribute names differ.
@@ -86,9 +86,9 @@ The argument is free text: an email, a full name, or just a surname.
86
86
  since the OU convention is organization-specific. Narrow with an email for an
87
87
  exact hit.
88
88
  - **Silent partial results.** Under load the directory occasionally returns an
89
- entry's DN with no attributes (exit 0, no error). Every attribute fetch retries
90
- with backoff, so a throttled response never masquerades as a person with a
91
- blank title or email.
89
+ entry's DN with no attributes (exit 0, no error). Every attribute fetch
90
+ retries with backoff, so a throttled response never masquerades as a person
91
+ with a blank title or email.
92
92
  - **No cache.** This skill prints and exits. It never touches
93
93
  `~/.cache/fit/outpost/state/identity.md` — that file is owned solely by
94
94
  `person-identify`.
@@ -39,7 +39,8 @@ This is **Stage 3** of the three-stage pipeline:
39
39
  - Standard data via `fit-pathway`.
40
40
  - `Knowledge/Candidates/Insights.md` — cross-candidate context.
41
41
  - `Knowledge/Roles/*.md` — the candidate's requisition (positions, hiring
42
- manager, domain lead).
42
+ manager, domain lead). Check the `**Status:**` field to distinguish active
43
+ from historical reqs.
43
44
  - `Knowledge/Priorities/*.md` — strategic context.
44
45
  - Other active candidates at the same level — relative positioning.
45
46
 
@@ -118,7 +119,8 @@ ls Knowledge/Roles/ | grep "{req_number}"
118
119
  cat "Knowledge/Roles/{matching file}"
119
120
  ```
120
121
 
121
- Capture remaining positions, hiring manager, domain lead, goal alignment, other
122
+ Check the `**Status:**` field. Capture remaining positions, hiring manager,
123
+ domain lead, goal alignment, other
122
124
  candidates on the same req, and channel (hr / vendor). Frame the recommendation
123
125
  in terms of strategic impact.
124
126
 
@@ -62,6 +62,6 @@ rg "{Name}" Knowledge/ ~/.cache/fit/outpost/
62
62
 
63
63
  Expected: no matches except this erasure report.
64
64
 
65
- ```
65
+ ```text
66
66
 
67
67
  ```
@@ -18,7 +18,7 @@ limits.
18
18
 
19
19
  Search by skill + availability:
20
20
 
21
- ```
21
+ ```text
22
22
  WebFetch URL: https://api.github.com/search/users?q=%22data+engineering%22+%22open+to+work%22&per_page=30&sort=joined&order=desc
23
23
  WebFetch URL: https://api.github.com/search/users?q=%22full+stack%22+%22available+for+hire%22&per_page=30&sort=joined&order=desc
24
24
  WebFetch URL: https://api.github.com/search/users?q=%22devops%22+%22looking+for%22&per_page=30&sort=joined&order=desc
@@ -26,7 +26,7 @@ WebFetch URL: https://api.github.com/search/users?q=%22devops%22+%22looking+for%
26
26
 
27
27
  Search repos with README signals:
28
28
 
29
- ```
29
+ ```text
30
30
  WebFetch URL: https://api.github.com/search/repositories?q=%22hire+me%22+in:readme&sort=updated&order=desc&per_page=10
31
31
  ```
32
32
 
@@ -38,7 +38,7 @@ Manchester, Edinburgh.
38
38
  Try broader tags: `jobsearch`, `career`, `remotework`, `job`, `hiring`. Or pull
39
39
  from a tag and filter by title/description:
40
40
 
41
- ```
41
+ ```text
42
42
  WebFetch URL: https://dev.to/api/articles?tag=career&per_page=25
43
43
  ```
44
44
 
@@ -6,14 +6,14 @@ Reference for `req-scan` Step 2 (fetch & scan). One source per wake cycle.
6
6
 
7
7
  Monthly thread, posted on the 1st.
8
8
 
9
- ```
9
+ ```text
10
10
  WebFetch URL: https://hn.algolia.com/api/v1/search?query=%22Who+wants+to+be+hired%22&tags=ask_hn&hitsPerPage=5
11
11
  ```
12
12
 
13
13
  The first hit whose title matches "Who wants to be hired?" with `created_at` in
14
14
  the current or previous month is the target thread.
15
15
 
16
- ```
16
+ ```text
17
17
  WebFetch URL: https://hn.algolia.com/api/v1/items/{objectID}
18
18
  ```
19
19
 
@@ -34,7 +34,7 @@ WebFetch URL: https://hn.algolia.com/api/v1/items/{objectID}
34
34
 
35
35
  Search by location (rotate one query per wake):
36
36
 
37
- ```
37
+ ```text
38
38
  WebFetch URL: https://api.github.com/search/users?q=%22open+to+work%22+location:UK&per_page=30&sort=joined&order=desc
39
39
  WebFetch URL: https://api.github.com/search/users?q=%22open+to+work%22+location:Europe&per_page=30&sort=joined&order=desc
40
40
  WebFetch URL: https://api.github.com/search/users?q=%22looking+for+work%22+location:remote&per_page=30&sort=joined&order=desc
@@ -47,7 +47,7 @@ Alternate bio phrases to rotate across wakes: `"available for hire"`,
47
47
 
48
48
  Fetch each promising candidate's full profile:
49
49
 
50
- ```
50
+ ```text
51
51
  WebFetch URL: https://api.github.com/users/{login}
52
52
  ```
53
53
 
@@ -60,7 +60,7 @@ profiles per wake (1 search + 5 profile fetches = 6 requests).
60
60
 
61
61
  ## 3. dev.to
62
62
 
63
- ```
63
+ ```text
64
64
  WebFetch URL: https://dev.to/api/articles?tag=opentowork&per_page=25
65
65
  WebFetch URL: https://dev.to/api/articles?tag=lookingforwork&per_page=25
66
66
  ```
@@ -39,7 +39,8 @@ This is **Stage 1** of a three-stage hiring pipeline:
39
39
  - Target role (optional).
40
40
  - Existing `Knowledge/Candidates/{Name}/brief.md`, if any.
41
41
  - `Knowledge/Roles/*.md` matching the candidate's `Req` (provides `Level`,
42
- `Discipline`, `Hiring manager`, `Domain lead`).
42
+ `Discipline`, `Hiring manager`, `Domain lead`, and the `**Status:**` field).
43
+ Look up by Req number or filename substring.
43
44
 
44
45
  ## Outputs
45
46
 
@@ -82,7 +83,8 @@ cat "Knowledge/Roles/{matching file}"
82
83
 
83
84
  Use the Role's `Level` and `Discipline` as the target unless the user specified
84
85
  a different target. Capture `Hiring manager` and `Domain lead` for the screening
85
- header.
86
+ header. Note the `**Status:**` field, but don't let historical (`closed`) roles
87
+ block screening of still-active candidates.
86
88
 
87
89
  If no target is available, estimate one using the level heuristics in
88
90
  [references/rubric.md](references/rubric.md#level-estimation-heuristics).
@@ -29,7 +29,8 @@ pipeline from scattered email threads.
29
29
  - `~/.cache/fit/outpost/apple_mail/attachments/` — CV/resume attachments.
30
30
  - `~/.cache/fit/outpost/apple_calendar/*.json` — calendar events (for
31
31
  cross-source inference).
32
- - `Knowledge/Roles/*.md` — open role/requisition files (metadata inheritance).
32
+ - `Knowledge/Roles/*.md` — role/requisition files (check the `**Status:**`
33
+ field: `open` for active roles, `closed` for historical).
33
34
  - `~/.cache/fit/outpost/state/graph_processed` — processed-file index (shared
34
35
  with `extract-entities`).
35
36
  - `~/.cache/fit/outpost/state/identity.md` — user identity for self-exclusion
@@ -40,8 +41,8 @@ pipeline from scattered email threads.
40
41
  - `Knowledge/Candidates/{Full Name}/brief.md` — candidate profile note.
41
42
  - `Knowledge/Candidates/{Full Name}/CV.pdf` (or `CV.docx`) — local CV copy.
42
43
  - `Knowledge/Candidates/{Full Name}/headshot.jpeg` — candidate photo.
43
- - `Knowledge/Roles/*.md` — created/updated role files (Candidates tables
44
- rebuilt).
44
+ - `Knowledge/Roles/*.md` — role files created/updated; the `**Status:**` field
45
+ determines visibility (open/closed).
45
46
  - `~/.cache/fit/outpost/state/graph_processed` — updated with processed threads.
46
47
 
47
48
  <do_confirm_checklist goal="Verify candidate processing batch is complete and
@@ -71,8 +72,8 @@ Process **10 files per run**.
71
72
  ### 1. Load context and pick the batch
72
73
 
73
74
  Read the user's name, email, and domain from
74
- `~/.cache/fit/outpost/state/identity.md` (run the `person-identify` skill first if
75
- it is missing or stale). List new or changed source files:
75
+ `~/.cache/fit/outpost/state/identity.md` (run the `person-identify` skill first
76
+ if it is missing or stale). List new or changed source files:
76
77
 
77
78
  ```bash
78
79
  node .claude/skills/extract-entities/scripts/state.mjs check
@@ -94,21 +95,24 @@ links.
94
95
 
95
96
  ### 3. Sync `Knowledge/Roles/`
96
97
 
97
- This keeps role metadata current and enables inheritance.
98
+ Role files are flat here; the `**Status:**` field is `open` or `closed`.
98
99
 
99
100
  1. Read each Role file's Info block to map Req → Role file path, Hiring manager,
100
- Domain lead, recruiter, Channel.
101
- 2. Find Reqs referenced by candidate briefs but missing a Role file:
102
- `rg "^\*\*Req:\*\*" Knowledge/Candidates/*/brief.md`. For each missing Req,
103
- create a stub using the **Role file stub** in
104
- [references/templates.md](references/templates.md), then enrich by searching
105
- the graph: `rg "{req_number}" Knowledge/`.
106
- 3. Rebuild each Role file's `## Candidates` table by scanning briefs:
101
+ Domain lead, recruiter, Channel. Look up by filename substring so a req-less
102
+ role stays findable: `ls Knowledge/Roles/ | grep "{partial_name_or_req}"`.
103
+ 2. Find Reqs in candidate briefs missing a Role file:
104
+ `rg "^\*\*Req:\*\*" Knowledge/Candidates/*/brief.md`. Check all Role files
105
+ first; for a genuinely missing open role, create a stub with `**Status:**
106
+ open` using the **Role file stub** in
107
+ [references/templates.md](references/templates.md), then enrich:
108
+ `rg "{req_number}" Knowledge/`.
109
+ 3. Rebuild each **open** Role file's `## Candidates` table by scanning briefs:
107
110
  `rg -l "Req:.*{req_number}" Knowledge/Candidates/*/brief.md`. Use the **Role
108
- Candidates table** format from `references/templates.md`. Sort by First seen,
109
- newest first.
111
+ Candidates table** format from `references/templates.md`, newest first.
110
112
  4. If a Role file has a hiring manager but no domain lead, walk the
111
113
  `**Reports to:**` chain in `Knowledge/People/` to a VP or senior leader.
114
+ 5. When a role closes (filled, cancelled, or frozen 6+ months), set its
115
+ `**Status:**` field to `closed` — only on a clear signal.
112
116
 
113
117
  ### 4. Identify recruitment threads
114
118
 
@@ -43,8 +43,10 @@ Field map and resolution rules for Step 2 of `req-track`.
43
43
 
44
44
  Stop at the first match:
45
45
 
46
- 1. **Req-first inheritance** — look up `Knowledge/Roles/*.md` for the matching
47
- Req; inherit Hiring manager and Domain lead from the Role file.
46
+ 1. **Req-first inheritance** — search `Knowledge/Roles/` for a file matching the
47
+ Req number or role description (use filename substring lookup for req-less
48
+ roles); check the `**Status:**` field (open/closed), then inherit Hiring
49
+ manager and Domain lead from the Role file.
48
50
  2. **Calendar inference** —
49
51
  `rg -l "{Candidate Name}" ~/.cache/fit/outpost/apple_calendar/`. The non-user
50
52
  organizer of an interview event is likely the hiring manager.
@@ -38,7 +38,8 @@ integrate with the `req-track` pipeline format.
38
38
 
39
39
  - `Knowledge/Candidates/{Clean Name}/brief.md` — candidate profile.
40
40
  - `Knowledge/Candidates/{Clean Name}/CV.md` — resume text as markdown.
41
- - `Knowledge/Roles/{Req ID} — {Title}.md` — created or updated.
41
+ - `Knowledge/Roles/{Req ID} — {Title}.md` — created or updated with
42
+ `**Status:** open`. Update to `**Status:** closed` when the role closes.
42
43
  - Updated existing briefs when a candidate already exists.
43
44
 
44
45
  <do_confirm_checklist goal="Verify the Workday import is consistent with
@@ -96,7 +97,8 @@ header indices, name annotations) are in
96
97
  ls Knowledge/Roles/ | grep "{Req ID}"
97
98
  ```
98
99
 
99
- Use the **Role file stub** in
100
+ New role files go in `Knowledge/Roles/` with `**Status:** open`. Use the
101
+ **Role file stub** in
100
102
  [references/templates.md](references/templates.md). Resolve the domain lead by:
101
103
 
102
104
  1. `rg "{Req ID}" Knowledge/` — look in project timelines, People notes, Topics
@@ -30,7 +30,7 @@ Empty or unrecognized step → default to `new`.
30
30
  The raw `step` value is always preserved in the parser's JSON output and must be
31
31
  stored in the candidate brief's `## Pipeline` section, e.g.
32
32
 
33
- ```
33
+ ```text
34
34
  - **2026-02-10**: Applied via LinkedIn — Step: Manager Request to Move Forward (HS)
35
35
  ```
36
36
 
@@ -5,13 +5,15 @@ Reference templates for `req-workday` Steps 1b, 4, and 5.
5
5
  ## Role file stub
6
6
 
7
7
  Use when no Role file exists for the requisition. Filename:
8
- `Knowledge/Roles/{Req ID} — {Short Title}.md`.
8
+ `Knowledge/Roles/{Req ID} — {Short Title}.md`. The `**Status:**` field
9
+ (`open`/`closed`) is updated in place — never move files.
9
10
 
10
11
  ```markdown
11
12
  # {Requisition Title}
12
13
 
13
14
  ## Info
14
15
  **Req:** {Req ID}
16
+ **Status:** open
15
17
  **Title:** {Full title from export}
16
18
  **Level:** {Infer from title: "Principal" → J100, "Staff" → J090, "Director" → J100 M-track, "Senior" → J070}
17
19
  **Track:** {P-track for IC roles, M-track for Director/Manager roles}
@@ -41,7 +41,9 @@ Run the sync as a single Node.js script with embedded SQLite. This avoids N+1
41
41
  process invocations (one per event for attendees) and handles all data
42
42
  transformation in one pass:
43
43
 
44
- node scripts/sync.mjs [--days N]
44
+ ```text
45
+ node scripts/sync.mjs [--days N]
46
+ ```
45
47
 
46
48
  - `--days N` — how many days back to sync (default: 30)
47
49
 
@@ -101,7 +103,9 @@ Each `{event_id}.json` file:
101
103
  After syncing, use the query script to filter events by date or time window.
102
104
  **Agents should use this script instead of writing bespoke calendar parsers.**
103
105
 
104
- node scripts/query.mjs [options]
106
+ ```text
107
+ node scripts/query.mjs [options]
108
+ ```
105
109
 
106
110
  ### Time filters (combinable)
107
111
 
@@ -48,7 +48,9 @@ their email.
48
48
  Run the sync as a single Node.js script with embedded SQLite. This avoids N+1
49
49
  process invocations and handles all data transformation in one pass:
50
50
 
51
- node scripts/sync.mjs [--days N]
51
+ ```text
52
+ node scripts/sync.mjs [--days N]
53
+ ```
52
54
 
53
55
  - `--days N` — how many days back to look on first sync (default: 30)
54
56
 
@@ -106,7 +106,7 @@ used by the `recipients` table.
106
106
 
107
107
  Attachment files on disk follow this path structure:
108
108
 
109
- ```
109
+ ```text
110
110
  ~/Library/Mail/V10/.../Attachments/{message_ROWID}/{attachment_id}/{filename}
111
111
  ```
112
112