@forwardimpact/outpost 3.12.0 → 3.13.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 (129) hide show
  1. package/README.md +2 -2
  2. package/bin/fit-outpost.js +3 -2
  3. package/package.json +6 -5
  4. package/src/agent-path.js +10 -6
  5. package/src/agent-runner.js +28 -24
  6. package/src/index.js +2 -3
  7. package/src/kb-manager.js +75 -20
  8. package/src/kb-validator.js +762 -0
  9. package/src/outpost.js +108 -39
  10. package/src/posture.js +3 -3
  11. package/src/privilege.js +11 -11
  12. package/src/scheduler.js +14 -12
  13. package/src/socket-server.js +19 -18
  14. package/src/spawn-env.js +7 -6
  15. package/src/state-manager.js +2 -2
  16. package/templates/.claude/agents/chief-of-staff.md +32 -24
  17. package/templates/.claude/agents/concierge.md +26 -17
  18. package/templates/.claude/agents/head-hunter.md +34 -26
  19. package/templates/.claude/agents/librarian.md +26 -18
  20. package/templates/.claude/agents/postman.md +26 -18
  21. package/templates/.claude/agents/recruiter.md +38 -27
  22. package/templates/.claude/skills/anarlog-follow/SKILL.md +91 -66
  23. package/templates/.claude/skills/anarlog-follow/references/coaching.md +22 -21
  24. package/templates/.claude/skills/anarlog-follow/references/sessions.md +52 -0
  25. package/templates/.claude/skills/anarlog-process/SKILL.md +108 -62
  26. package/templates/.claude/skills/anarlog-process/references/extraction.md +41 -29
  27. package/templates/.claude/skills/anarlog-process/references/sessions.md +92 -58
  28. package/templates/.claude/skills/anarlog-process/scripts/scan.mjs +402 -160
  29. package/templates/.claude/skills/candidate-report/SKILL.md +41 -36
  30. package/templates/.claude/skills/candidate-report/references/report-template.html +10 -10
  31. package/templates/.claude/skills/candidate-report/references/report.css +4 -4
  32. package/templates/.claude/skills/candidate-report/references/rubric.md +15 -14
  33. package/templates/.claude/skills/candidate-report/scripts/render-pdf.mjs +2 -2
  34. package/templates/.claude/skills/changelog/SKILL.md +122 -67
  35. package/templates/.claude/skills/deck-create/SKILL.md +90 -32
  36. package/templates/.claude/skills/deck-create/references/slide.css +4 -4
  37. package/templates/.claude/skills/deck-create/scripts/convert-to-pdf.mjs +6 -6
  38. package/templates/.claude/skills/deck-review/SKILL.md +63 -58
  39. package/templates/.claude/skills/deck-review/assets/slide-annotator.js +58 -49
  40. package/templates/.claude/skills/deck-summarize/SKILL.md +55 -45
  41. package/templates/.claude/skills/deck-summarize/references/brief-template.md +5 -4
  42. package/templates/.claude/skills/deck-summarize/scripts/extract-pptx.mjs +8 -7
  43. package/templates/.claude/skills/doc-collab/SKILL.md +39 -34
  44. package/templates/.claude/skills/doc-create/SKILL.md +27 -23
  45. package/templates/.claude/skills/doc-create/scripts/convert-to-pdf.mjs +6 -6
  46. package/templates/.claude/skills/draft-emails/SKILL.md +63 -53
  47. package/templates/.claude/skills/draft-emails/references/template.md +2 -2
  48. package/templates/.claude/skills/draft-emails/scripts/scan-emails.mjs +15 -8
  49. package/templates/.claude/skills/draft-emails/scripts/send-email.mjs +19 -11
  50. package/templates/.claude/skills/extract-entities/SKILL.md +76 -75
  51. package/templates/.claude/skills/extract-entities/references/TEMPLATES.md +6 -5
  52. package/templates/.claude/skills/extract-entities/references/conditions.md +20 -18
  53. package/templates/.claude/skills/extract-entities/references/content.md +10 -9
  54. package/templates/.claude/skills/extract-entities/references/links.md +37 -13
  55. package/templates/.claude/skills/extract-entities/references/recruitment.md +30 -24
  56. package/templates/.claude/skills/extract-entities/references/resolution.md +14 -14
  57. package/templates/.claude/skills/extract-entities/references/sources.md +9 -9
  58. package/templates/.claude/skills/extract-entities/references/templates-conditions.md +12 -6
  59. package/templates/.claude/skills/extract-entities/references/templates-people-orgs.md +29 -10
  60. package/templates/.claude/skills/extract-entities/references/templates-priorities.md +10 -4
  61. package/templates/.claude/skills/extract-entities/references/templates-projects-topics.md +19 -7
  62. package/templates/.claude/skills/extract-entities/scripts/state.mjs +9 -8
  63. package/templates/.claude/skills/meeting-prep/SKILL.md +42 -36
  64. package/templates/.claude/skills/organize-files/SKILL.md +27 -22
  65. package/templates/.claude/skills/organize-files/scripts/organize-by-type.mjs +5 -4
  66. package/templates/.claude/skills/organize-files/scripts/summarize.mjs +4 -4
  67. package/templates/.claude/skills/person-identify/SKILL.md +77 -27
  68. package/templates/.claude/skills/person-identify/scripts/identify.sh +125 -26
  69. package/templates/.claude/skills/person-lookup/SKILL.md +34 -30
  70. package/templates/.claude/skills/person-lookup/scripts/lookup.sh +46 -17
  71. package/templates/.claude/skills/req-assess/SKILL.md +44 -36
  72. package/templates/.claude/skills/req-assess/references/interview-template.md +7 -2
  73. package/templates/.claude/skills/req-assess/references/panel-template.md +7 -2
  74. package/templates/.claude/skills/req-assess/references/rubric.md +13 -13
  75. package/templates/.claude/skills/req-bundle/SKILL.md +164 -0
  76. package/templates/.claude/skills/req-bundle/references/matching.md +70 -0
  77. package/templates/.claude/skills/req-bundle/references/pdf-structure.md +74 -0
  78. package/templates/.claude/skills/req-bundle/scripts/split-bundle.mjs +377 -0
  79. package/templates/.claude/skills/req-decide/SKILL.md +52 -41
  80. package/templates/.claude/skills/req-decide/references/rubric.md +11 -10
  81. package/templates/.claude/skills/req-decide/references/template.md +2 -2
  82. package/templates/.claude/skills/req-forget/SKILL.md +58 -39
  83. package/templates/.claude/skills/req-forget/references/classify.md +15 -13
  84. package/templates/.claude/skills/req-forget/references/locations.md +27 -22
  85. package/templates/.claude/skills/req-forget/references/report-template.md +24 -16
  86. package/templates/.claude/skills/req-scan/SKILL.md +27 -25
  87. package/templates/.claude/skills/req-scan/references/fallbacks.md +9 -9
  88. package/templates/.claude/skills/req-scan/references/filters.md +13 -13
  89. package/templates/.claude/skills/req-scan/references/sources.md +7 -7
  90. package/templates/.claude/skills/req-scan/references/state.md +1 -1
  91. package/templates/.claude/skills/req-scan/references/template.md +11 -1
  92. package/templates/.claude/skills/req-scan/scripts/state.mjs +3 -2
  93. package/templates/.claude/skills/req-screen/SKILL.md +96 -50
  94. package/templates/.claude/skills/req-screen/references/rubric.md +46 -50
  95. package/templates/.claude/skills/req-screen/references/scoring.md +79 -0
  96. package/templates/.claude/skills/req-screen/references/template.md +29 -17
  97. package/templates/.claude/skills/req-track/SKILL.md +76 -76
  98. package/templates/.claude/skills/req-track/references/fields.md +31 -27
  99. package/templates/.claude/skills/req-track/references/overlays.md +42 -0
  100. package/templates/.claude/skills/req-track/references/signals.md +12 -12
  101. package/templates/.claude/skills/req-track/references/statuses.md +3 -2
  102. package/templates/.claude/skills/req-track/references/templates.md +28 -27
  103. package/templates/.claude/skills/req-workday/SKILL.md +69 -40
  104. package/templates/.claude/skills/req-workday/references/brief.md +81 -0
  105. package/templates/.claude/skills/req-workday/references/status-mapping.md +5 -5
  106. package/templates/.claude/skills/req-workday/references/templates.md +11 -78
  107. package/templates/.claude/skills/req-workday/references/xlsx-format.md +26 -7
  108. package/templates/.claude/skills/req-workday/scripts/parse-workday.mjs +113 -35
  109. package/templates/.claude/skills/send-chat/SKILL.md +58 -54
  110. package/templates/.claude/skills/sync-apple-calendar/SKILL.md +23 -19
  111. package/templates/.claude/skills/sync-apple-calendar/references/SCHEMA.md +7 -7
  112. package/templates/.claude/skills/sync-apple-calendar/scripts/query.mjs +6 -6
  113. package/templates/.claude/skills/sync-apple-calendar/scripts/sync.mjs +9 -8
  114. package/templates/.claude/skills/sync-apple-mail/SKILL.md +20 -14
  115. package/templates/.claude/skills/sync-apple-mail/references/SCHEMA.md +10 -10
  116. package/templates/.claude/skills/sync-apple-mail/scripts/parse-emlx.mjs +9 -9
  117. package/templates/.claude/skills/sync-apple-mail/scripts/sync-helpers.mjs +16 -13
  118. package/templates/.claude/skills/sync-apple-mail/scripts/sync.mjs +3 -3
  119. package/templates/.claude/skills/sync-teams/SKILL.md +46 -43
  120. package/templates/.claude/skills/sync-teams/scripts/idb-reader.mjs +22 -21
  121. package/templates/.claude/skills/sync-teams/scripts/leveldb-reader.mjs +4 -4
  122. package/templates/.claude/skills/sync-teams/scripts/sync.mjs +4 -4
  123. package/templates/.claude/skills/upstream-instructions/SKILL.md +43 -41
  124. package/templates/.claude/skills/upstream-instructions/references/examples.md +5 -5
  125. package/templates/CLAUDE.md +97 -85
  126. package/templates/MIGRATION.md +359 -0
  127. package/templates/registry.yaml +25 -0
  128. package/templates/.claude/skills/anarlog-follow/scripts/follow.mjs +0 -243
  129. package/templates/.claude/skills/anarlog-trim/SKILL.md +0 -183
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  <!-- BEGIN:description — Do not edit. Generated from package.json. -->
4
4
 
5
- Personal operations center — context from email, calendar, and knowledge
6
- assembled so preparation is continuous, not a morning scramble.
5
+ Personal operations center — context assembled from email, calendar, and
6
+ knowledge so preparation is continuous instead of a morning scramble.
7
7
 
8
8
  <!-- END:description -->
9
9
 
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
- // Thin entry point the sole construction site for the runtime collaborator
3
- // bag, threaded into src/outpost.js's dispatch via run(runtime, version).
2
+ // This file is the thin entry point. It is the sole construction site for the
3
+ // runtime collaborator bag. It threads that bag into the dispatch in
4
+ // src/outpost.js through run(runtime, version).
4
5
  import "@forwardimpact/libpreflight/node22";
5
6
 
6
7
  import { resolveVersion } from "@forwardimpact/libcli";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@forwardimpact/outpost",
3
- "version": "3.12.0",
4
- "description": "Personal operations center — context from email, calendar, and knowledge assembled so preparation is continuous, not a morning scramble.",
3
+ "version": "3.13.0",
4
+ "description": "Personal operations center — context assembled from email, calendar, and knowledge so preparation is continuous instead of a morning scramble.",
5
5
  "homepage": "https://www.forwardimpact.team",
6
6
  "repository": {
7
7
  "type": "git",
@@ -14,12 +14,12 @@
14
14
  {
15
15
  "user": "Empowered Engineers",
16
16
  "goal": "Be Prepared and Productive",
17
- "trigger": "Walking into a meeting cold because context was scattered across email, Slack, and last week's notes.",
17
+ "trigger": "The engineer walks into a meeting cold because context is scattered across email, Slack, and last week's notes.",
18
18
  "bigHire": "keep track of people, projects, and threads without depending on memory.",
19
19
  "littleHire": "walk into every meeting already oriented.",
20
20
  "competesWith": "morning email scan; re-reading calendar invites; personal notes; asking colleagues for context",
21
21
  "forces": {
22
- "push": "Context-switching compounds; starting cold wastes everyone's time.",
22
+ "push": "Context-switching compounds. Starting cold wastes everyone's time.",
23
23
  "pull": "Continuous awareness without continuous effort — context assembled.",
24
24
  "habit": "Morning routines and tracking systems that feel 'good enough.'",
25
25
  "anxiety": "Delegating awareness to a system feels like losing control."
@@ -51,7 +51,8 @@
51
51
  "@forwardimpact/libcli": "^0.1.0",
52
52
  "@forwardimpact/libpreflight": "^0.1.0",
53
53
  "@forwardimpact/libtelemetry": "^0.1.33",
54
- "@forwardimpact/libutil": "^0.1.0"
54
+ "@forwardimpact/libutil": "^0.1.0",
55
+ "yaml": "^2.9.0"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@forwardimpact/libmock": "^0.1.0"
package/src/agent-path.js CHANGED
@@ -1,15 +1,19 @@
1
1
  /**
2
- * agent-path — validate a config-supplied agent name before using it as a
2
+ * agent-path — validate a config-supplied agent name before you use it as a
3
3
  * filesystem path component.
4
4
  *
5
5
  * The agent-state writer maps an agent name to a per-agent state filename.
6
- * A name carrying `/` or `..` segments would let a rewritten `scheduler.json`
7
- * direct writes outside `~/.cache/fit/outpost/state/`. This module validates
8
- * and rejects rather than silently sanitising, so an unexpected path segment
9
- * surfaces as an intrusion signal instead of a quietly rewritten filename.
6
+ * A name that carries `/` or `..` segments would let a rewritten
7
+ * `scheduler.json` direct writes outside `~/.cache/fit/outpost/state/`. This
8
+ * module validates the name and rejects an unsafe one. It never sanitises the
9
+ * name silently. So an unexpected path segment surfaces as an intrusion
10
+ * signal. It does not become a quietly rewritten filename.
10
11
  */
11
12
 
12
- /** Raised when an agent name cannot map to a safe state-file prefix. */
13
+ /**
14
+ * `agentNameToStatePrefix` raises this when an agent name cannot map to a
15
+ * safe state-file prefix.
16
+ */
13
17
  export class UnsafeAgentNameError extends Error {
14
18
  /** @param {string} name */
15
19
  constructor(name) {
@@ -15,14 +15,14 @@ import { resolvePrivilege, disclaimFor } from "./privilege.js";
15
15
  import { buildSpawnEnv } from "./spawn-env.js";
16
16
 
17
17
  /**
18
- * System-prompt directive injected under the `brief` posture. Neutralises any
19
- * draft-side prose in a materialised agent definition and forbids writing the
20
- * posture record.
18
+ * The system-prompt directive the runner injects under the `brief` posture. It
19
+ * neutralises any draft-side prose in a materialised agent definition. It also
20
+ * forbids a write to the posture record.
21
21
  */
22
22
  const BRIEF_DIRECTIVE =
23
23
  "Adoption posture: brief. Run only read-and-brief work. Do not draft or " +
24
- "send content on the user's behalf for delivery to anyone else, do not move " +
25
- "or write files outside the knowledge base, and never write the posture " +
24
+ "send content on the user's behalf for delivery to anyone else. Do not " +
25
+ "move or write files outside the knowledge base. Never write the posture " +
26
26
  "record (~/.fit/outpost/posture.json).";
27
27
 
28
28
  /** Spawn agent CLI processes, capture their output, and update agent state. */
@@ -41,11 +41,11 @@ export class AgentRunner {
41
41
 
42
42
  /**
43
43
  * @param {Object | (() => Object | Promise<{default?: Object}>)} spawn -
44
- * The posix-spawn module, or a (possibly async) loader returning it. A
45
- * loader lets the Bun-FFI module load lazily so plain `node` invocations
46
- * that never wake an agent don't pull in `bun:ffi`.
44
+ * The posix-spawn module, or a loader that returns it. The loader may be
45
+ * async. A loader lets the Bun-FFI module load lazily. So a plain `node`
46
+ * invocation that never wakes an agent does not pull in `bun:ffi`.
47
47
  * @param {import('./state-manager.js').StateManager} stateManager
48
- * @param {Function} logFn - Logging function
48
+ * @param {Function} logFn - The function that writes a log line
49
49
  * @param {string} cacheDir - Cache directory for state files
50
50
  * @param {import("@forwardimpact/libutil/runtime").Runtime} runtime
51
51
  * Injected runtime bag (uses `fs` (async), `proc`, `clock`).
@@ -84,8 +84,8 @@ export class AgentRunner {
84
84
  }
85
85
 
86
86
  /**
87
- * Resolve the injected spawn collaborator to the posix-spawn module,
88
- * invoking and unwrapping a loader thunk on first use.
87
+ * Resolve the injected spawn collaborator to the posix-spawn module. On
88
+ * first use, invoke a loader thunk and unwrap it.
89
89
  * @returns {Promise<Object>}
90
90
  */
91
91
  async #resolveSpawn() {
@@ -97,7 +97,7 @@ export class AgentRunner {
97
97
  }
98
98
 
99
99
  /**
100
- * Test whether a path exists, via the async fs surface.
100
+ * Test whether a path exists, through the async fs surface.
101
101
  * @param {string} p
102
102
  * @returns {Promise<boolean>}
103
103
  */
@@ -148,11 +148,12 @@ export class AgentRunner {
148
148
  }
149
149
 
150
150
  /**
151
- * Resolve the spawn flags the recorded posture adds. Under `brief` (also the
152
- * default when no posture is recorded), deny every draft-side skill by name
153
- * and inject the brief directive a deterministic gate the woken agent
154
- * cannot override under `bypassPermissions`. Under `brief+draft`, add
155
- * nothing. The posture record is read here, never written.
151
+ * Resolve the spawn flags the recorded posture adds. Under `brief`, deny
152
+ * every draft-side skill by name and inject the brief directive. `brief` is
153
+ * also the default when no posture is recorded. The directive is a
154
+ * deterministic gate. The woken agent cannot override it under
155
+ * `bypassPermissions`. Under `brief+draft`, add nothing. This method reads
156
+ * the posture record. It never writes the record.
156
157
  * @returns {Promise<string[]>} Extra args to append to the spawn argv.
157
158
  */
158
159
  async #postureArgs() {
@@ -170,17 +171,20 @@ export class AgentRunner {
170
171
  }
171
172
 
172
173
  /**
173
- * Validate the agent's kb path exists, spawn `claude --agent` with the prompt "Observe and act.", and update agent state to active/idle/failed.
174
+ * Validate that the agent's kb path exists. Spawn `claude --agent` with the
175
+ * prompt "Observe and act." Then update the agent state to active, idle, or
176
+ * failed.
174
177
  * @param {string} agentName
175
178
  * @param {Object} agent
176
179
  * @param {Object} state
177
180
  * @param {Record<string, string>} [configEnv] - Extra env vars from config
178
181
  */
179
182
  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.
183
+ // Resolve the mandatory privilege level before any work. The runner is
184
+ // fail-closed on a missing or invalid level. It logs the problem and skips
185
+ // the wake. It never spawns an agent process with a guessed privilege. The
186
+ // level lives in the user-only trust root, so a spawned agent cannot raise
187
+ // its own.
184
188
  let level;
185
189
  try {
186
190
  level = resolvePrivilege(agent);
@@ -196,13 +200,13 @@ export class AgentRunner {
196
200
  }
197
201
 
198
202
  if (!agent.kb) {
199
- this.#log(`Agent ${agentName}: no "kb" specified, skipping.`);
203
+ this.#log(`Agent ${agentName}: no "kb" specified. Skipped.`);
200
204
  return;
201
205
  }
202
206
  const kbPath = this.#expandPath(agent.kb);
203
207
  if (!(await this.#exists(kbPath))) {
204
208
  this.#log(
205
- `Agent ${agentName}: path "${kbPath}" does not exist, skipping.`,
209
+ `Agent ${agentName}: path "${kbPath}" does not exist. Skipped.`,
206
210
  );
207
211
  return;
208
212
  }
package/src/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  // Public entry point for @forwardimpact/outpost.
2
- // Outpost is primarily a CLI this file exists so the package conforms
3
- // to the repo-wide layout contract. The runtime CLI dispatch lives in
4
- // src/outpost.js.
2
+ // Outpost is primarily a CLI. This file exists so the package conforms to the
3
+ // repo-wide layout contract. The runtime CLI dispatch lives in src/outpost.js.
5
4
  export * from "./outpost.js";
package/src/kb-manager.js CHANGED
@@ -6,7 +6,9 @@ import { join, dirname, resolve, basename } from "node:path";
6
6
  import { homedir } from "node:os";
7
7
  import { createLogger } from "@forwardimpact/libtelemetry";
8
8
 
9
- /** Manage knowledge base lifecycle including initialization, updates, and settings merging. */
9
+ import { LEGACY_ROOTS } from "./kb-validator.js";
10
+
11
+ /** Manage the knowledge base lifecycle: init, update, and settings merge. */
10
12
  export class KBManager {
11
13
  #fs;
12
14
  #logger;
@@ -25,12 +27,13 @@ export class KBManager {
25
27
 
26
28
  /**
27
29
  * 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.
30
+ * `~/.local/share/fit/outpost/<name>`. The argument is a single path
31
+ * segment. It is never an arbitrary filesystem path. So a provisioned KB
32
+ * always lands outside TCC-protected folders. This method validates the name
33
+ * against the same rule as `agent-path.js`. It does not sanitise the name. A
34
+ * name that carries `/`, `\`, `..`, NUL, or a leading `~` could steer the KB
35
+ * back inside `~/Documents` and reopen the TCC hole. So this method rejects
36
+ * the name. It never rewrites the name.
34
37
  * @param {string} name - The KB name (e.g. `Team`, `personal`).
35
38
  * @returns {string} Absolute path under the data home.
36
39
  * @throws {Error} when `name` is empty, non-string, or an unsafe segment.
@@ -51,7 +54,7 @@ export class KBManager {
51
54
  }
52
55
 
53
56
  /**
54
- * Test whether a path exists, via the async fs surface.
57
+ * Test whether a path exists, through the async fs surface.
55
58
  * @param {string} p
56
59
  * @returns {Promise<boolean>}
57
60
  */
@@ -94,7 +97,7 @@ export class KBManager {
94
97
  }
95
98
 
96
99
  /**
97
- * Copy bundled files (CLAUDE.md, skills, agents) from template to a KB.
100
+ * Copy bundled files (CLAUDE.md, skills, agents) from the template to a KB.
98
101
  * @param {string} tpl - Path to the template directory
99
102
  * @param {string} dest - Path to the target knowledge base
100
103
  * @returns {Promise<void>}
@@ -156,7 +159,7 @@ export class KBManager {
156
159
  }
157
160
 
158
161
  /**
159
- * Merge template settings.json into the destination's settings.json.
162
+ * Merge the template settings.json into the destination's settings.json.
160
163
  * @param {string} tpl - Template directory
161
164
  * @param {string} dest - Knowledge base directory
162
165
  * @returns {Promise<void>}
@@ -206,14 +209,23 @@ export class KBManager {
206
209
  }
207
210
 
208
211
  await this.#ensureDir(dest);
209
- // Create the top-level roots only. `Knowledge/` is the shared graph (synced
210
- // with the team); `Drafts/` and `Briefings/` are personal and local. Entity
211
- // subdirectories (People, Organizations, ...) are created on demand by the
212
- // skills that write into them.
213
- for (const d of ["Knowledge", "Drafts", "Briefings"])
212
+ // Create the tier directories and the personal `Briefings/` root only.
213
+ // The numbered tiers are the knowledge graph and the units of sharing;
214
+ // every other root entry is personal. The skills that write into the
215
+ // entity subdirectories (People, Organizations, ...) create them on
216
+ // demand inside each tier.
217
+ for (const d of [
218
+ "0-Draft",
219
+ "1-Management",
220
+ "2-Confidential",
221
+ "3-Team",
222
+ "4-Public",
223
+ "Briefings",
224
+ ])
214
225
  await this.#ensureDir(join(dest, d));
215
226
 
216
227
  await this.copyBundledFiles(templateDir, dest);
228
+ await this.#installRegistry(templateDir, dest);
217
229
  await this.#linkIntoDocuments(dest);
218
230
 
219
231
  this.#logger.info(
@@ -223,12 +235,13 @@ export class KBManager {
223
235
  }
224
236
 
225
237
  /**
226
- * Create a navigation symlink at `~/Documents/<name>` pointing to the KB.
238
+ * Create a navigation symlink at `~/Documents/<name>` that points to the KB.
227
239
  * 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`.
240
+ * folders. The symlink is only a convenience pointer. It makes the KB easy
241
+ * to find and open from Finder. The method is best-effort. It leaves a
242
+ * pre-existing entry untouched. It logs any failure, for example when macOS
243
+ * denies write access to `~/Documents`. A failure is never fatal, because
244
+ * the KB is already provisioned at `dest`.
232
245
  * @param {string} dest - Absolute path to the provisioned KB.
233
246
  * @returns {Promise<void>}
234
247
  */
@@ -263,10 +276,52 @@ export class KBManager {
263
276
  };
264
277
  }
265
278
  await this.copyBundledFiles(templateDir, dest);
279
+ await this.#installRegistry(templateDir, dest);
280
+ await this.#installMigrationGuide(templateDir, dest);
266
281
  this.#logger.info(`\nKnowledge base updated: ${dest}`);
267
282
  return { ok: true, value: { dest } };
268
283
  }
269
284
 
285
+ /**
286
+ * Install the default `registry.yaml` when the KB has none. The registry
287
+ * is a personal surface humans edit, so this method never overwrites an
288
+ * existing one. `copyBundledFiles` copies named files only and never picks
289
+ * it up.
290
+ * @param {string} tpl - Template directory
291
+ * @param {string} dest - Knowledge base directory
292
+ * @returns {Promise<void>}
293
+ */
294
+ async #installRegistry(tpl, dest) {
295
+ const src = join(tpl, "registry.yaml");
296
+ const destPath = join(dest, "registry.yaml");
297
+ if (!(await this.#exists(src)) || (await this.#exists(destPath))) return;
298
+ await this.#fs.copyFile(src, destPath);
299
+ this.#logger.info(` Installed registry.yaml`);
300
+ }
301
+
302
+ /**
303
+ * Install MIGRATION.md while the KB still carries a legacy layout
304
+ * (`Knowledge/` or `Drafts/` at the root). A conforming KB never receives
305
+ * it, and this method removes nothing.
306
+ * @param {string} tpl - Template directory
307
+ * @param {string} dest - Knowledge base directory
308
+ * @returns {Promise<void>}
309
+ */
310
+ async #installMigrationGuide(tpl, dest) {
311
+ let legacy = false;
312
+ for (const marker of LEGACY_ROOTS) {
313
+ if (await this.#exists(join(dest, marker))) legacy = true;
314
+ }
315
+ if (!legacy) return;
316
+ await this.#fs.copyFile(
317
+ join(tpl, "MIGRATION.md"),
318
+ join(dest, "MIGRATION.md"),
319
+ );
320
+ this.#logger.info(
321
+ ` Installed MIGRATION.md — this knowledge base carries the legacy layout. Follow it to migrate to tiers.`,
322
+ );
323
+ }
324
+
270
325
  /**
271
326
  * @param {string} p
272
327
  * @returns {string}