@forwardimpact/outpost 3.8.1 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/config/scheduler.json +12 -6
  2. package/package.json +1 -1
  3. package/src/agent-runner.js +27 -0
  4. package/src/kb-manager.js +54 -1
  5. package/src/outpost.js +12 -5
  6. package/src/privilege.js +45 -0
  7. package/templates/.claude/agents/chief-of-staff.md +9 -7
  8. package/templates/.claude/agents/concierge.md +6 -4
  9. package/templates/.claude/agents/head-hunter.md +6 -4
  10. package/templates/.claude/agents/librarian.md +14 -10
  11. package/templates/.claude/agents/postman.md +6 -4
  12. package/templates/.claude/agents/recruiter.md +8 -5
  13. package/templates/.claude/skills/anarlog-follow/references/coaching.md +3 -3
  14. package/templates/.claude/skills/anarlog-trim/SKILL.md +9 -3
  15. package/templates/.claude/skills/candidate-report/SKILL.md +4 -3
  16. package/templates/.claude/skills/changelog/SKILL.md +10 -10
  17. package/templates/.claude/skills/deck-create/SKILL.md +23 -22
  18. package/templates/.claude/skills/deck-review/SKILL.md +29 -25
  19. package/templates/.claude/skills/doc-create/SKILL.md +17 -16
  20. package/templates/.claude/skills/extract-entities/SKILL.md +4 -2
  21. package/templates/.claude/skills/meeting-prep/SKILL.md +2 -1
  22. package/templates/.claude/skills/organize-files/SKILL.md +9 -5
  23. package/templates/.claude/skills/person-identify/SKILL.md +2 -2
  24. package/templates/.claude/skills/person-lookup/SKILL.md +3 -3
  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-track/SKILL.md +2 -2
  29. package/templates/.claude/skills/req-workday/references/status-mapping.md +1 -1
  30. package/templates/.claude/skills/sync-apple-calendar/SKILL.md +6 -2
  31. package/templates/.claude/skills/sync-apple-mail/SKILL.md +3 -1
  32. package/templates/.claude/skills/sync-apple-mail/references/SCHEMA.md +1 -1
  33. package/templates/.claude/skills/sync-teams/SKILL.md +13 -1
  34. package/templates/.claude/skills/sync-teams/scripts/idb-reader.mjs +67 -34
  35. package/templates/.claude/skills/upstream-instructions/SKILL.md +2 -1
  36. package/templates/CLAUDE.md +27 -38
@@ -1,32 +1,38 @@
1
1
  {
2
2
  "agents": {
3
3
  "postman": {
4
- "kb": "~/Documents/Personal",
4
+ "kb": "~/.local/share/fit/outpost/Team",
5
+ "privilege": "full",
5
6
  "schedule": { "type": "cron", "expression": "*/15 8-18 * * 1-5" },
6
7
  "enabled": true
7
8
  },
8
9
  "concierge": {
9
- "kb": "~/Documents/Personal",
10
+ "kb": "~/.local/share/fit/outpost/Team",
11
+ "privilege": "full",
10
12
  "schedule": { "type": "cron", "expression": "*/30 8-18 * * 1-5" },
11
13
  "enabled": true
12
14
  },
13
15
  "librarian": {
14
- "kb": "~/Documents/Personal",
16
+ "kb": "~/.local/share/fit/outpost/Team",
17
+ "privilege": "restricted",
15
18
  "schedule": { "type": "cron", "expression": "0 9,12,15,18 * * 1-5" },
16
19
  "enabled": true
17
20
  },
18
21
  "chief-of-staff": {
19
- "kb": "~/Documents/Personal",
22
+ "kb": "~/.local/share/fit/outpost/Team",
23
+ "privilege": "restricted",
20
24
  "schedule": { "type": "cron", "expression": "0 7,18 * * 1-5" },
21
25
  "enabled": true
22
26
  },
23
27
  "recruiter": {
24
- "kb": "~/Documents/Personal",
28
+ "kb": "~/.local/share/fit/outpost/Team",
29
+ "privilege": "restricted",
25
30
  "schedule": { "type": "cron", "expression": "0 8,12,17 * * 1-5" },
26
31
  "enabled": true
27
32
  },
28
33
  "head-hunter": {
29
- "kb": "~/Documents/Personal",
34
+ "kb": "~/.local/share/fit/outpost/Team",
35
+ "privilege": "restricted",
30
36
  "schedule": { "type": "cron", "expression": "0 9 * * 1-5" },
31
37
  "enabled": true
32
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/outpost",
3
- "version": "3.8.1",
3
+ "version": "3.10.0",
4
4
  "description": "Personal operations center — context from email, calendar, and knowledge assembled so preparation is continuous, not a morning scramble.",
5
5
  "homepage": "https://www.forwardimpact.team",
6
6
  "repository": {
@@ -11,6 +11,7 @@ import {
11
11
  loadManifest,
12
12
  draftSkills,
13
13
  } from "./posture.js";
14
+ import { resolvePrivilege, disclaimFor } from "./privilege.js";
14
15
  import { buildSpawnEnv } from "./spawn-env.js";
15
16
 
16
17
  /**
@@ -176,6 +177,24 @@ export class AgentRunner {
176
177
  * @param {Record<string, string>} [configEnv] - Extra env vars from config
177
178
  */
178
179
  async wake(agentName, agent, state, configEnv) {
180
+ // Resolve the mandatory privilege level before any work. A missing or
181
+ // invalid level is fail-closed: log and skip the wake — no agent process is
182
+ // spawned with a guessed privilege. The level lives in the user-only trust
183
+ // root, so a spawned agent cannot raise its own.
184
+ let level;
185
+ try {
186
+ level = resolvePrivilege(agent);
187
+ } catch (err) {
188
+ this.#log(
189
+ JSON.stringify({
190
+ event: "outpost.privilege.rejected",
191
+ agent: agentName,
192
+ error: err.message,
193
+ }),
194
+ );
195
+ return;
196
+ }
197
+
179
198
  if (!agent.kb) {
180
199
  this.#log(`Agent ${agentName}: no "kb" specified, skipping.`);
181
200
  return;
@@ -191,6 +210,13 @@ export class AgentRunner {
191
210
  const claude = await this.#findClaude();
192
211
 
193
212
  this.#log(`Waking agent: ${agentName} (kb: ${agent.kb})`);
213
+ this.#log(
214
+ JSON.stringify({
215
+ event: "outpost.privilege.resolved",
216
+ agent: agentName,
217
+ level,
218
+ }),
219
+ );
194
220
 
195
221
  const as = (state.agents[agentName] ||= {});
196
222
  as.status = "active";
@@ -226,6 +252,7 @@ export class AgentRunner {
226
252
  env,
227
253
  kbPath,
228
254
  this.#runtime,
255
+ disclaimFor(level),
229
256
  );
230
257
  this.#activeChildren.add(pid);
231
258
 
package/src/kb-manager.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * KBManager — knowledge base init/update operations.
3
3
  */
4
4
 
5
- import { join, dirname, resolve } from "node:path";
5
+ import { join, dirname, resolve, basename } from "node:path";
6
6
  import { homedir } from "node:os";
7
7
  import { createLogger } from "@forwardimpact/libtelemetry";
8
8
 
@@ -23,6 +23,33 @@ export class KBManager {
23
23
  this.#logger = createLogger("outpost", runtime);
24
24
  }
25
25
 
26
+ /**
27
+ * Resolve a knowledge-base name to its path under the XDG data home,
28
+ * `~/.local/share/fit/outpost/<name>`. The argument is a single path segment,
29
+ * never an arbitrary filesystem path, so a provisioned KB always lands outside
30
+ * TCC-protected folders. The name is validated — not sanitised — against the
31
+ * same rule as `agent-path.js`: a name carrying `/`, `\`, `..`, NUL, or a
32
+ * leading `~` could steer the KB back inside `~/Documents`, reopening the TCC
33
+ * hole, so it is rejected rather than rewritten.
34
+ * @param {string} name - The KB name (e.g. `Team`, `personal`).
35
+ * @returns {string} Absolute path under the data home.
36
+ * @throws {Error} when `name` is empty, non-string, or an unsafe segment.
37
+ */
38
+ static kbPathForName(name) {
39
+ if (
40
+ typeof name !== "string" ||
41
+ name.length === 0 ||
42
+ name.includes("/") ||
43
+ name.includes("\\") ||
44
+ name.includes("..") ||
45
+ name.includes("\0") ||
46
+ name.startsWith("~")
47
+ ) {
48
+ throw new Error(`unsafe KB name: ${JSON.stringify(name)}`);
49
+ }
50
+ return join(homedir(), ".local/share/fit/outpost", name);
51
+ }
52
+
26
53
  /**
27
54
  * Test whether a path exists, via the async fs surface.
28
55
  * @param {string} p
@@ -187,6 +214,7 @@ export class KBManager {
187
214
  await this.#ensureDir(join(dest, d));
188
215
 
189
216
  await this.copyBundledFiles(templateDir, dest);
217
+ await this.#linkIntoDocuments(dest);
190
218
 
191
219
  this.#logger.info(
192
220
  `Knowledge base initialized at ${dest}\n\nNext steps:\n 1. cd ${dest} && npx apm install\n 2. claude\n 3. Run the person-identify skill to populate your identity`,
@@ -194,6 +222,31 @@ export class KBManager {
194
222
  return { ok: true, value: { dest } };
195
223
  }
196
224
 
225
+ /**
226
+ * Create a navigation symlink at `~/Documents/<name>` pointing to the KB.
227
+ * The KB data itself stays under the XDG data home, outside TCC-protected
228
+ * folders — this is only a convenience pointer so the KB is easy to find and
229
+ * open from Finder. Best-effort: a pre-existing entry is left untouched, and
230
+ * any failure (e.g. macOS denying write access to `~/Documents`) is logged,
231
+ * never fatal, because the KB is already provisioned at `dest`.
232
+ * @param {string} dest - Absolute path to the provisioned KB.
233
+ * @returns {Promise<void>}
234
+ */
235
+ async #linkIntoDocuments(dest) {
236
+ const link = join(homedir(), "Documents", basename(dest));
237
+ if (await this.#exists(link)) {
238
+ this.#logger.info(` Skipped ${link}: already exists`);
239
+ return;
240
+ }
241
+ try {
242
+ await this.#ensureDir(dirname(link));
243
+ await this.#fs.symlink(dest, link, "dir");
244
+ this.#logger.info(` Linked ${link} -> ${dest}`);
245
+ } catch (err) {
246
+ this.#logger.info(` Could not link into ~/Documents: ${err.message}`);
247
+ }
248
+ }
249
+
197
250
  /**
198
251
  * Update an existing knowledge base with the latest bundled files.
199
252
  * @param {string} targetPath
package/src/outpost.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // fit-outpost Wake due agents once and exit
5
5
  // fit-outpost daemon Run continuously (poll every 60s)
6
6
  // fit-outpost wake <agent> Wake a specific agent immediately
7
- // fit-outpost init <path> Initialize a new knowledge base
7
+ // fit-outpost init [name] Initialize a knowledge base by name (default: Team)
8
8
  // fit-outpost update [path] Update KB with latest CLAUDE.md, agents and skills (defaults to current directory)
9
9
  // fit-outpost stop Gracefully stop daemon and all running agents
10
10
  // fit-outpost validate Validate agent definitions exist
@@ -58,7 +58,7 @@ function buildDefinition(version) {
58
58
  },
59
59
  {
60
60
  name: "init",
61
- args: "<path>",
61
+ args: "[name]",
62
62
  description: "Initialize a new knowledge base",
63
63
  },
64
64
  {
@@ -433,13 +433,20 @@ export async function run(runtime, version) {
433
433
  return 1;
434
434
  },
435
435
  init: async () => {
436
- if (!args[0]) {
437
- cli.usageError("missing required argument <path>");
436
+ // `init [name]` provisions a KB by name under the data home (default
437
+ // `Team`), never an arbitrary path — so the substrate cannot be
438
+ // steered back into a TCC-protected folder. An unsafe name is refused.
439
+ const name = args[0] ?? "Team";
440
+ let target;
441
+ try {
442
+ target = KBManager.kbPathForName(name);
443
+ } catch {
444
+ cli.usageError(`invalid KB name "${name}"`);
438
445
  return 2;
439
446
  }
440
447
  const tpl = await requireTemplateDir();
441
448
  if (tpl === null) return 1;
442
- const result = await kbManager.init(args[0], tpl);
449
+ const result = await kbManager.init(target, tpl);
443
450
  if (!result.ok) {
444
451
  proc.stderr.write(result.error + "\n");
445
452
  return result.code;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Privilege — resolve an agent's least-privilege execution level and map it to
3
+ * the hop-2 spawn disclaim flag.
4
+ *
5
+ * The level governs the macOS reach the daemon grants a woken agent. `full`
6
+ * keeps today's single-grant model (the child inherits `fit-outpost.app` as its
7
+ * responsible process, so Full Disk Access and Automation flow to it); a
8
+ * `restricted` agent is held responsible for itself, so those grants are not
9
+ * extended and it can reach only non-TCC-protected substrate.
10
+ *
11
+ * The level is mandatory and lives in the same user-only trust root as the
12
+ * spawn-env allow-set and the state roots, so an agent cannot raise its own
13
+ * level. Patterned on `posture.js` but with no `effective*` coercion and no
14
+ * default — a missing or unrecognised value throws.
15
+ */
16
+
17
+ /** The two privilege levels, in declaration order. */
18
+ export const PRIVILEGE_LEVELS = ["full", "restricted"];
19
+
20
+ /**
21
+ * Resolve an agent's declared privilege level. The level is mandatory: a
22
+ * missing or unrecognised value throws — there is no default.
23
+ * @param {{ privilege?: string }} agent - One agent's config.
24
+ * @returns {"full"|"restricted"} The declared level.
25
+ * @throws {Error} when `agent.privilege` is not one of {@link PRIVILEGE_LEVELS}.
26
+ */
27
+ export function resolvePrivilege(agent) {
28
+ const level = agent?.privilege;
29
+ if (!PRIVILEGE_LEVELS.includes(level)) {
30
+ throw new Error(
31
+ `invalid privilege "${level}"; expected one of ${PRIVILEGE_LEVELS.join(", ")}`,
32
+ );
33
+ }
34
+ return level;
35
+ }
36
+
37
+ /**
38
+ * Map a level to the hop-2 disclaim flag: `restricted` self-disclaims (`1`),
39
+ * `full` keeps the inherited responsible process (`0`).
40
+ * @param {"full"|"restricted"} level
41
+ * @returns {0|1}
42
+ */
43
+ export function disclaimFor(level) {
44
+ return level === "restricted" ? 1 : 0;
45
+ }
@@ -14,16 +14,18 @@ single briefing.
14
14
 
15
15
  ## Priorities
16
16
 
17
- `Knowledge/Priorities/` is the backbone of every briefing. Read it each wake (it
18
- is also listed under Inputs) and frame the whole briefing around what advances or
19
- threatens the user's priorities.
17
+ `Knowledge/Priorities/` is the backbone of every briefing. Read it and
18
+ `Knowledge/Conditions/` (the live constraints that shape how priorities are
19
+ pursued see Operating Context in CLAUDE.md) each wake (both are also listed
20
+ under Inputs) and frame the whole briefing around what advances or threatens the
21
+ user's priorities.
20
22
 
21
23
  - **Always consider them.** Tie the schedule, the top actions, and the pipeline
22
24
  back to the priority each one serves.
23
25
  - **Always escalate risks.** Consolidate every `## Priority Watch` flag from the
24
26
  sibling triage files — plus anything you find in your own reads — into a
25
- `## Priority Watch` section in the briefing, each item naming the priority, the
26
- evidence, and the risk. A signal that could contradict, block, or slow a
27
+ `## Priority Watch` section in the briefing, each item naming the priority,
28
+ the evidence, and the risk. A signal that could contradict, block, or slow a
27
29
  priority is the most important thing the briefing surfaces.
28
30
 
29
31
  ## Inputs
@@ -37,7 +39,7 @@ authoritative current-state summaries:
37
39
  - `~/.cache/fit/outpost/state/recruiter_triage.md`
38
40
  - `~/.cache/fit/outpost/state/head_hunter_triage.md`
39
41
 
40
- Plus directly: `Knowledge/Priorities/`, `Drafts/`,
42
+ Plus directly: `Knowledge/Priorities/`, `Knowledge/Conditions/`, `Drafts/`,
41
43
  `~/.cache/fit/outpost/apple_calendar/`, and unchecked `- [ ]` items in
42
44
  `Knowledge/`.
43
45
 
@@ -64,7 +66,7 @@ and "Still Outstanding".
64
66
 
65
67
  ## Output
66
68
 
67
- ```
69
+ ```text
68
70
  Decision: {morning/evening} briefing — {key insight about today}
69
71
  Action: Created Briefings/{YYYY-MM-DD}-{morning|evening}.md
70
72
  ```
@@ -18,11 +18,13 @@ recordings.
18
18
 
19
19
  ## Priorities
20
20
 
21
- At the start of every wake, before acting, read `Knowledge/Priorities/`. The
22
- user's priorities are the lens for all your work this wake.
21
+ At the start of every wake, before acting, read `Knowledge/Priorities/` and
22
+ `Knowledge/Conditions/` (which constrains them see Operating Context in
23
+ CLAUDE.md). The user's priorities are the lens for all your work this wake.
23
24
 
24
25
  - **Always consider them.** Weigh each action against whether it advances a
25
- priority, and favour work that does.
26
+ priority, and favour work that does. Let the active conditions shape how you
27
+ act on it.
26
28
  - **Always flag risks.** When you encounter a chat, email, transcript, or any
27
29
  other signal that could **contradict, block, or slow** a priority, record it
28
30
  under a `## Priority Watch` heading in your triage report — name the priority,
@@ -53,7 +55,7 @@ over **anarlog-process** (catch-up work).
53
55
 
54
56
  After acting, emit exactly:
55
57
 
56
- ```
58
+ ```text
57
59
  Decision: {what you observed and why you chose this action}
58
60
  Action: {what you did, e.g. "meeting-prep for 2pm with Sarah Chen"}
59
61
  Priority Watch: {priority at risk + one-line why, or "none"}
@@ -21,11 +21,13 @@ benchmark promising matches, and write prospect notes for the user to review.
21
21
 
22
22
  ## Priorities
23
23
 
24
- At the start of every wake, before acting, read `Knowledge/Priorities/`. The
25
- user's priorities are the lens for all your work this wake.
24
+ At the start of every wake, before acting, read `Knowledge/Priorities/` and
25
+ `Knowledge/Conditions/` (which constrains them see Operating Context in
26
+ CLAUDE.md). The user's priorities are the lens for all your work this wake.
26
27
 
27
28
  - **Always consider them.** Weigh each action against whether it advances a
28
- priority, and favour work that does.
29
+ priority, and favour work that does. Let the active conditions shape how you
30
+ act on it.
29
31
  - **Always flag risks.** When you encounter a chat, email, transcript, or any
30
32
  other signal that could **contradict, block, or slow** a priority, record it
31
33
  under a `## Priority Watch` heading in your triage report — name the priority,
@@ -63,7 +65,7 @@ wake — the chief-of-staff reads it.
63
65
 
64
66
  ## Output
65
67
 
66
- ```
68
+ ```text
67
69
  Decision: {source chosen and why}
68
70
  Action: {what was scanned, e.g. "scanned HN Who Wants to Be Hired March 2026, 47 posts"}
69
71
  Prospects: {N} new ({strong} strong, {moderate} moderate), {total} total
@@ -16,11 +16,13 @@ you process new data into the knowledge graph and keep everything organized.
16
16
 
17
17
  ## Priorities
18
18
 
19
- At the start of every wake, before acting, read `Knowledge/Priorities/`. The
20
- user's priorities are the lens for all your work this wake.
19
+ At the start of every wake, before acting, read `Knowledge/Priorities/` and
20
+ `Knowledge/Conditions/` (which constrains them see Operating Context in
21
+ CLAUDE.md). The user's priorities are the lens for all your work this wake.
21
22
 
22
23
  - **Always consider them.** Weigh each action against whether it advances a
23
- priority, and favour work that does.
24
+ priority, and favour work that does. Let the active conditions shape how you
25
+ act on it.
24
26
  - **Always flag risks.** When you encounter a chat, email, transcript, or any
25
27
  other signal that could **contradict, block, or slow** a priority, record it
26
28
  under a `## Priority Watch` heading in your triage report — name the priority,
@@ -31,18 +33,20 @@ user's priorities are the lens for all your work this wake.
31
33
 
32
34
  Assess what needs processing:
33
35
 
34
- 1. Check for unprocessed synced files (mail and calendar data):
36
+ 1. Check for unprocessed synced files (mail and calendar data):
35
37
 
36
- node .claude/skills/extract-entities/scripts/state.mjs check
38
+ ```text
39
+ node .claude/skills/extract-entities/scripts/state.mjs check
40
+ ```
37
41
 
38
- 2. Count existing knowledge graph entities:
42
+ 2. Count existing knowledge graph entities:
39
43
 
40
- ls Knowledge/People/ Knowledge/Organizations/ Knowledge/Projects/
41
- Knowledge/Topics/ Knowledge/Priorities/ 2>/dev/null | wc -l
44
+ ls Knowledge/People/ Knowledge/Organizations/ Knowledge/Projects/
45
+ Knowledge/Topics/ Knowledge/Priorities/ 2>/dev/null | wc -l
42
46
 
43
47
  Write triage results to `~/.cache/fit/outpost/state/librarian_triage.md`:
44
48
 
45
- ```
49
+ ```text
46
50
  # Knowledge Triage — {YYYY-MM-DD HH:MM}
47
51
  ## Pending Processing
48
52
  - {count} unprocessed synced files
@@ -64,7 +68,7 @@ Choose the most valuable action:
64
68
 
65
69
  After acting, output exactly:
66
70
 
67
- ```
71
+ ```text
68
72
  Decision: {what you observed and why you chose this action}
69
73
  Action: {what you did, e.g. "extract-entities on 7 files"}
70
74
  Priority Watch: {priority at risk + one-line why, or "none"}
@@ -17,11 +17,13 @@ and Teams, triage what's new, take the most valuable action.
17
17
 
18
18
  ## Priorities
19
19
 
20
- At the start of every wake, before acting, read `Knowledge/Priorities/`. The
21
- user's priorities are the lens for all your work this wake.
20
+ At the start of every wake, before acting, read `Knowledge/Priorities/` and
21
+ `Knowledge/Conditions/` (which constrains them see Operating Context in
22
+ CLAUDE.md). The user's priorities are the lens for all your work this wake.
22
23
 
23
24
  - **Always consider them.** Weigh each action against whether it advances a
24
- priority, and favour work that does.
25
+ priority, and favour work that does. Let the active conditions shape how you
26
+ act on it.
25
27
  - **Always flag risks.** When you encounter a chat, email, transcript, or any
26
28
  other signal that could **contradict, block, or slow** a priority, record it
27
29
  under a `## Priority Watch` heading in your triage report — name the priority,
@@ -54,7 +56,7 @@ email only.
54
56
 
55
57
  After acting, emit exactly:
56
58
 
57
- ```
59
+ ```text
58
60
  Decision: {what you observed and why you chose this action}
59
61
  Action: {what you did, e.g. "draft-emails for thread 123"}
60
62
  Priority Watch: {priority at risk + one-line why, or "none"}
@@ -22,11 +22,13 @@ assessment and recommendation references the standard.
22
22
 
23
23
  ## Priorities
24
24
 
25
- At the start of every wake, before acting, read `Knowledge/Priorities/`. The
26
- user's priorities are the lens for all your work this wake.
25
+ At the start of every wake, before acting, read `Knowledge/Priorities/` and
26
+ `Knowledge/Conditions/` (which constrains them see Operating Context in
27
+ CLAUDE.md). The user's priorities are the lens for all your work this wake.
27
28
 
28
29
  - **Always consider them.** Weigh each action against whether it advances a
29
- priority, and favour work that does.
30
+ priority, and favour work that does. Let the active conditions shape how you
31
+ act on it.
30
32
  - **Always flag risks.** When you encounter a chat, email, transcript, or any
31
33
  other signal that could **contradict, block, or slow** a priority, record it
32
34
  under a `## Priority Watch` heading in your triage report — name the priority,
@@ -60,11 +62,12 @@ screen > sync. Stage 3 **never** triggers automatically — only on user request
60
62
 
61
63
  Triage state goes to `~/.cache/fit/outpost/state/recruiter_triage.md` every wake
62
64
  (the chief-of-staff reads it): needs-action by stage, recently processed
63
- candidates, pipeline totals by stage/track, aggregate diversity, retention flags.
65
+ candidates, pipeline totals by stage/track, aggregate diversity, retention
66
+ flags.
64
67
 
65
68
  ## Output
66
69
 
67
- ```
70
+ ```text
68
71
  Decision: {observation and chosen action}
69
72
  Action: {e.g. "req-screen for John Smith against J060 forward-deployed"}
70
73
  Stage: {1 | 2 | sync | erasure}
@@ -6,7 +6,7 @@ Reference for `anarlog-follow` Phases 2 and 3.
6
6
 
7
7
  ### For interviews
8
8
 
9
- ```
9
+ ```text
10
10
  Following: {Title}
11
11
  Type: {Interview type}
12
12
  Candidate: {Name} — {current role} at {employer}
@@ -27,7 +27,7 @@ Watching for: {specific signals at this interview stage}
27
27
 
28
28
  ### For general meetings
29
29
 
30
- ```
30
+ ```text
31
31
  Following: {Title}
32
32
  Attendees: {names with roles}
33
33
 
@@ -66,7 +66,7 @@ Suggested topics:
66
66
 
67
67
  Output **only when actionable**. Each nudge: 1–3 lines max.
68
68
 
69
- ```
69
+ ```text
70
70
  Probe deeper: {Name} mentioned {topic} — ask for a specific example
71
71
  Gap: screening flagged {skill} as uncertain. Try: "{question}"
72
72
  Confirmed: {Name} demonstrated {skill} at {level} — "{brief quote}"
@@ -46,9 +46,11 @@ Run this skill:
46
46
  ### Step 0 — Validate the session
47
47
 
48
48
  1. Confirm the session directory exists:
49
- ```
49
+
50
+ ```text
50
51
  ~/Library/Application Support/anarlog/sessions/{uuid}/
51
52
  ```
53
+
52
54
  2. Confirm `transcript.json` exists and has at least one transcript with words.
53
55
  3. Read `_meta.json` to get the session title for context.
54
56
 
@@ -132,7 +134,8 @@ json.dump(data, open(path, 'w'), indent=2)
132
134
  ```
133
135
 
134
136
  4. Print a summary:
135
- ```
137
+
138
+ ```text
136
139
  Trimmed: {title}
137
140
  Before: {original_words} words, {original_duration}
138
141
  After: {new_words} words, {new_duration}
@@ -147,11 +150,14 @@ people who did not consent to being recorded. The full audio file must be
147
150
  deleted to respect participant privacy.
148
151
 
149
152
  1. Delete the audio file:
153
+
150
154
  ```bash
151
155
  rm "~/Library/Application Support/anarlog/sessions/{uuid}/audio.mp3"
152
156
  ```
157
+
153
158
  2. Confirm deletion and inform the user:
154
- ```
159
+
160
+ ```text
155
161
  Audio deleted: audio.mp3 removed (recording contained unconsented content beyond the meeting)
156
162
  ```
157
163
 
@@ -50,7 +50,8 @@ before deciding whether to invest interview time.
50
50
  - [ ] Verdict class matches the overall assessment.
51
51
  - [ ] Report fits on a single A4 page (browser print preview).
52
52
  - [ ] CSS is inlined in the `<style>` block.
53
- - [ ] Footer shows the author name and role from `~/.cache/fit/outpost/state/identity.md`.
53
+ - [ ] Footer shows the author name and role from
54
+ `~/.cache/fit/outpost/state/identity.md`.
54
55
  - [ ] Written as if the candidate will read it; no special-category data.
55
56
 
56
57
  </do_confirm_checklist>
@@ -61,7 +62,7 @@ before deciding whether to invest interview time.
61
62
 
62
63
  Read whatever exists for the candidate:
63
64
 
64
- ```
65
+ ```text
65
66
  Knowledge/Candidates/{Name}/brief.md # required
66
67
  Knowledge/Candidates/{Name}/screening.md # if produced by req-screen
67
68
  Knowledge/Candidates/{Name}/interview-*.md # if produced by req-assess
@@ -118,7 +119,7 @@ preview overflows, cut content.
118
119
 
119
120
  Save the completed HTML to:
120
121
 
121
- ```
122
+ ```text
122
123
  Drafts/{Recipient}-{CandidateSurname}-Report.html
123
124
  ```
124
125
 
@@ -11,8 +11,8 @@ teammates syncing the same filesystem can see what changed and why.
11
11
 
12
12
  This tracks **graph content** — notes under `Knowledge/People/`,
13
13
  `Organizations/`, `Projects/`, `Topics/`, `Candidates/`, `Priorities/`, and the
14
- other subdirectories. It does **not** track changes to instructions (`CLAUDE.md`,
15
- agents, skills) — that is the `upstream-instructions` skill's job.
14
+ other subdirectories. It does **not** track changes to instructions
15
+ (`CLAUDE.md`, agents, skills) — that is the `upstream-instructions` skill's job.
16
16
 
17
17
  ## Trigger
18
18
 
@@ -40,16 +40,16 @@ agents, skills) — that is the `upstream-instructions` skill's job.
40
40
 
41
41
  ## Ethics
42
42
 
43
- `Knowledge/` is shared with the team. Every entry obeys the KB's integrity rules:
44
- objective and factual, work-relevant, no personal judgments. Assume the person a
45
- note is about will read its changelog entry. Describe **what changed in the
46
- graph**, not opinions about the people in it.
43
+ `Knowledge/` is shared with the team. Every entry obeys the KB's integrity
44
+ rules: objective and factual, work-relevant, no personal judgments. Assume the
45
+ person a note is about will read its changelog entry. Describe
46
+ **what changed in the graph**, not opinions about the people in it.
47
47
 
48
48
  <do_confirm_checklist goal="Verify the changelog is accurate and shareable">
49
49
 
50
50
  - [ ] Exactly one `Knowledge/CHANGELOG.md`; no stray per-folder changelogs.
51
- - [ ] Every entry names its **Scope** — the specific note(s) or folder(s) touched,
52
- by full path.
51
+ - [ ] Every entry names its **Scope** — the specific note(s) or folder(s)
52
+ touched, by full path.
53
53
  - [ ] Each entry has **Who** (author, from identity), **What**, and **Why**.
54
54
  - [ ] Descriptions are specific enough to be useful (not "updated some notes").
55
55
  - [ ] Dates are the date the change was actually made, not guessed.
@@ -104,8 +104,8 @@ Scope lists every note touched.
104
104
 
105
105
  ### 4. Write the changelog
106
106
 
107
- Create or update `Knowledge/CHANGELOG.md` (newest first). Group entries under one
108
- heading per day; one bullet per logical change:
107
+ Create or update `Knowledge/CHANGELOG.md` (newest first). Group entries under
108
+ one heading per day; one bullet per logical change:
109
109
 
110
110
  ```markdown
111
111
  # Knowledge Changelog