@gamaze/hicortex 0.17.6 → 0.18.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 (50) hide show
  1. package/README.md +26 -25
  2. package/assets/dashboard.html +121 -5
  3. package/assets/{context.html → identity.html} +18 -18
  4. package/assets/viz.html +19 -7
  5. package/dist/claude-md.d.ts +2 -1
  6. package/dist/claude-md.js +2 -1
  7. package/dist/cli-args.d.ts +9 -0
  8. package/dist/cli-args.js +16 -0
  9. package/dist/cli.js +29 -20
  10. package/dist/consolidate.d.ts +15 -0
  11. package/dist/consolidate.js +30 -3
  12. package/dist/dashboard.d.ts +58 -1
  13. package/dist/dashboard.js +27 -1
  14. package/dist/extensions.d.ts +1 -1
  15. package/dist/extensions.js +1 -1
  16. package/dist/health.d.ts +68 -0
  17. package/dist/health.js +73 -0
  18. package/dist/identity-cli.d.ts +90 -0
  19. package/dist/{context-cli.js → identity-cli.js} +66 -48
  20. package/dist/{context-store.d.ts → identity-store.d.ts} +94 -31
  21. package/dist/{context-store.js → identity-store.js} +212 -71
  22. package/dist/index.d.ts +12 -5
  23. package/dist/index.js +57 -29
  24. package/dist/init.d.ts +44 -8
  25. package/dist/init.js +142 -37
  26. package/dist/{lessons-context.d.ts → learnings-identity.d.ts} +32 -21
  27. package/dist/{lessons-context.js → learnings-identity.js} +50 -39
  28. package/dist/mcp-server.d.ts +2 -0
  29. package/dist/mcp-server.js +168 -58
  30. package/dist/memory-instructions.d.ts +6 -6
  31. package/dist/memory-instructions.js +6 -6
  32. package/dist/nightly.js +65 -6
  33. package/dist/paths.js +1 -1
  34. package/dist/recall-hook-cli.d.ts +1 -1
  35. package/dist/recall-hook-cli.js +3 -3
  36. package/dist/recall-index.js +5 -2
  37. package/dist/status.d.ts +2 -2
  38. package/dist/status.js +11 -9
  39. package/dist/telemetry.d.ts +10 -0
  40. package/dist/type-classify.js +4 -1
  41. package/dist/type-labels.d.ts +30 -0
  42. package/dist/type-labels.js +43 -0
  43. package/dist/types.d.ts +28 -0
  44. package/dist/uninstall.d.ts +12 -0
  45. package/dist/uninstall.js +21 -3
  46. package/dist/viz.d.ts +24 -11
  47. package/dist/viz.js +97 -32
  48. package/hermes-plugin/hicortex/README.md +4 -2
  49. package/package.json +2 -2
  50. package/dist/context-cli.d.ts +0 -69
@@ -1,54 +1,66 @@
1
1
  "use strict";
2
2
  /**
3
- * context-cli — `hicortex context show|edit`, the secondary/headless edit
4
- * surface for the standing context layer (spec 2026-07-12 §6). The Web UI
5
- * (`/context/ui`) is primary; this exists for boxes without a browser.
3
+ * identity-cli — `hicortex identity show|edit`, the secondary/headless edit
4
+ * surface for the standing identity layer (spec 2026-07-12 §6; renamed 0.18
5
+ * #264 from `context`). The Web UI (`/identity/ui`) is primary; this exists
6
+ * for boxes without a browser.
6
7
  *
7
- * hicortex context show [name] GET /context → print all sections, or one
8
- * hicortex context edit <name> GET section → $EDITOR → PUT if changed
8
+ * hicortex identity show [name] GET /identity → print all sections, or one
9
+ * hicortex identity edit <name> GET section → $EDITOR → PUT if changed
9
10
  *
10
- * URL/token resolution mirrors lessons-context.ts:44-49 (client mode
11
+ * The legacy `hicortex context ...` command remains as a hidden alias so old
12
+ * scripts and muscle memory keep working (#264 backcompat).
13
+ *
14
+ * URL/token resolution mirrors learnings-identity.ts:44-49 (client mode →
11
15
  * config.serverUrl; server mode → http://127.0.0.1:<port>; token from
12
16
  * config.authToken) — explicitly NOT the hardcoded 127.0.0.1:8787 of
13
17
  * status.ts. Fails soft with a clear message + non-zero exit on any server
14
18
  * error, distinguishing a down server from an HTTP error (esp. 404 = server
15
19
  * too old / wrong endpoint), like the OC plugin's describeGetFailure.
20
+ *
21
+ * The CLI targets the new `/identity` endpoint. A 0.18+ server (this package)
22
+ * also keeps `/context` as an alias, so a new CLI against a not-yet-upgraded
23
+ * server falls back through that alias via describeFailure's 404 hint.
16
24
  */
17
25
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.ContextCliError = void 0;
19
- exports.resolveContextTarget = resolveContextTarget;
26
+ exports.runContextCommand = exports.getContext = exports.resolveContextTarget = exports.ContextCliError = exports.IdentityCliError = void 0;
27
+ exports.resolveIdentityTarget = resolveIdentityTarget;
20
28
  exports.loadConfig = loadConfig;
21
29
  exports.sectionChanged = sectionChanged;
22
- exports.getContext = getContext;
30
+ exports.getIdentity = getIdentity;
23
31
  exports.putSection = putSection;
24
32
  exports.formatAllSections = formatAllSections;
25
33
  exports.formatOneSection = formatOneSection;
26
34
  exports.runEdit = runEdit;
27
35
  exports.extractAgentFlag = extractAgentFlag;
28
- exports.runContextCommand = runContextCommand;
36
+ exports.runIdentityCommand = runIdentityCommand;
29
37
  const node_fs_1 = require("node:fs");
30
38
  const node_path_1 = require("node:path");
31
39
  const node_os_1 = require("node:os");
32
40
  const node_child_process_1 = require("node:child_process");
33
- const context_store_js_1 = require("./context-store.js");
41
+ const identity_store_js_1 = require("./identity-store.js");
34
42
  const paths_js_1 = require("./paths.js");
35
43
  const DEFAULT_PORT = 8787;
36
44
  const REQUEST_TIMEOUT_MS = 5000;
37
45
  /** Thrown for any expected, user-facing failure. cli.ts prints .message + exits 1. */
38
- class ContextCliError extends Error {
46
+ class IdentityCliError extends Error {
39
47
  }
40
- exports.ContextCliError = ContextCliError;
48
+ exports.IdentityCliError = IdentityCliError;
49
+ /** Backcompat alias (#264). */
50
+ exports.ContextCliError = IdentityCliError;
41
51
  /**
42
52
  * Resolve the server URL + token from a parsed config object. Pure + exported
43
- * so it is unit-testable without a live config. Follows lessons-context.ts.
53
+ * so it is unit-testable without a live config. Follows learnings-identity.ts.
44
54
  */
45
- function resolveContextTarget(config) {
55
+ function resolveIdentityTarget(config) {
46
56
  const baseUrl = config.mode === "client" && typeof config.serverUrl === "string"
47
57
  ? config.serverUrl.replace(/\/+$/, "")
48
58
  : `http://127.0.0.1:${config.port ?? DEFAULT_PORT}`;
49
59
  const authToken = typeof config.authToken === "string" ? config.authToken : undefined;
50
60
  return { baseUrl, authToken };
51
61
  }
62
+ /** Backcompat alias (#264). */
63
+ exports.resolveContextTarget = resolveIdentityTarget;
52
64
  /** Read ~/.hicortex/config.json (or $HICORTEX_HOME/config.json). Missing → {}. */
53
65
  function loadConfig() {
54
66
  try {
@@ -62,9 +74,9 @@ function loadConfig() {
62
74
  function sectionChanged(before, after) {
63
75
  return before !== after;
64
76
  }
65
- /** Append `?agent=<id>` to a /context URL when an agent scope is targeted. */
66
- function contextUrl(baseUrl, agent) {
67
- return `${baseUrl}/context${agent ? `?agent=${encodeURIComponent(agent)}` : ""}`;
77
+ /** Append `?agent=<id>` to a /identity URL when an agent scope is targeted. */
78
+ function identityUrl(baseUrl, agent) {
79
+ return `${baseUrl}/identity${agent ? `?agent=${encodeURIComponent(agent)}` : ""}`;
68
80
  }
69
81
  function authHeaders(token) {
70
82
  return token ? { Authorization: `Bearer ${token}` } : {};
@@ -72,14 +84,15 @@ function authHeaders(token) {
72
84
  /**
73
85
  * Human-readable failure. status === null ⇒ the fetch itself threw (server
74
86
  * unreachable). 404 is called out because it means the server predates the
75
- * /context layer, not a network fault mirrors index.ts describeGetFailure.
87
+ * /identity rename (it may still serve the legacy /context alias), not a
88
+ * network fault — mirrors index.ts describeGetFailure.
76
89
  */
77
90
  function describeFailure(status, bodyError) {
78
91
  const suffix = bodyError ? `: ${bodyError}` : "";
79
92
  if (status === null)
80
93
  return "server unreachable (connection refused or timed out)";
81
94
  if (status === 404)
82
- return `HTTP 404 — /context not found; the server is likely too old (needs 0.12+)${suffix}`;
95
+ return `HTTP 404 — /identity not found; the server is likely too old (needs 0.18+; pre-0.18 serves /context)${suffix}`;
83
96
  if (status === 401)
84
97
  return `HTTP 401 — unauthorized; check authToken in config${suffix}`;
85
98
  return `server returned HTTP ${status}${suffix}`;
@@ -93,35 +106,37 @@ async function readBodyError(resp) {
93
106
  return undefined;
94
107
  }
95
108
  }
96
- /** GET /context. Throws ContextCliError with a clear message on any failure. */
97
- async function getContext(target, agent) {
109
+ /** GET /identity. Throws IdentityCliError with a clear message on any failure. */
110
+ async function getIdentity(target, agent) {
98
111
  let resp;
99
112
  try {
100
- resp = await fetch(contextUrl(target.baseUrl, agent), {
113
+ resp = await fetch(identityUrl(target.baseUrl, agent), {
101
114
  headers: authHeaders(target.authToken),
102
115
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
103
116
  });
104
117
  }
105
118
  catch {
106
- throw new ContextCliError(`GET /context failed — ${describeFailure(null)}`);
119
+ throw new IdentityCliError(`GET /identity failed — ${describeFailure(null)}`);
107
120
  }
108
121
  if (!resp.ok) {
109
- throw new ContextCliError(`GET /context failed — ${describeFailure(resp.status, await readBodyError(resp))}`);
122
+ throw new IdentityCliError(`GET /identity failed — ${describeFailure(resp.status, await readBodyError(resp))}`);
110
123
  }
111
124
  const data = (await resp.json());
112
125
  // Old-server echo guard: a pre-0.13 server ignores ?agent= and answers 200
113
126
  // with the GLOBAL set. Editing off that (then PUTting) would clobber shared
114
- // context. Refuse unless the server echoes the agent it was asked for.
127
+ // identity. Refuse unless the server echoes the agent it was asked for.
115
128
  if (agent && data.agent !== agent) {
116
- throw new ContextCliError(`server did not echo agent '${agent}' — it likely predates per-agent context (needs 0.13+)`);
129
+ throw new IdentityCliError(`server did not echo agent '${agent}' — it likely predates per-agent identity (needs 0.13+)`);
117
130
  }
118
131
  return data;
119
132
  }
120
- /** PUT one section. Throws ContextCliError with a clear message on any failure. */
133
+ /** Backcompat alias (#264). */
134
+ exports.getContext = getIdentity;
135
+ /** PUT one section. Throws IdentityCliError with a clear message on any failure. */
121
136
  async function putSection(target, name, content, agent) {
122
137
  let resp;
123
138
  try {
124
- resp = await fetch(contextUrl(target.baseUrl, agent), {
139
+ resp = await fetch(identityUrl(target.baseUrl, agent), {
125
140
  method: "PUT",
126
141
  headers: { "Content-Type": "application/json", ...authHeaders(target.authToken) },
127
142
  body: JSON.stringify({ sections: { [name]: content } }),
@@ -129,10 +144,10 @@ async function putSection(target, name, content, agent) {
129
144
  });
130
145
  }
131
146
  catch {
132
- throw new ContextCliError(`PUT /context failed — ${describeFailure(null)}`);
147
+ throw new IdentityCliError(`PUT /identity failed — ${describeFailure(null)}`);
133
148
  }
134
149
  if (!resp.ok) {
135
- throw new ContextCliError(`PUT /context failed — ${describeFailure(resp.status, await readBodyError(resp))}`);
150
+ throw new IdentityCliError(`PUT /identity failed — ${describeFailure(resp.status, await readBodyError(resp))}`);
136
151
  }
137
152
  // Old-server echo guard on the WRITE too: a pre-0.13 server silently accepts
138
153
  // the PUT as a GLOBAL write and returns 200 without echoing `agent`. Treat a
@@ -141,8 +156,8 @@ async function putSection(target, name, content, agent) {
141
156
  if (agent) {
142
157
  const body = (await resp.json().catch(() => ({})));
143
158
  if (body.agent !== agent) {
144
- throw new ContextCliError(`PUT /context?agent=${agent} — server did not echo the agent; it likely predates per-agent ` +
145
- `context (needs 0.13+). It may have written to the GLOBAL scope — verify the server version.`);
159
+ throw new IdentityCliError(`PUT /identity?agent=${agent} — server did not echo the agent; it likely predates per-agent ` +
160
+ `identity (needs 0.13+). It may have written to the GLOBAL scope — verify the server version.`);
146
161
  }
147
162
  }
148
163
  }
@@ -154,7 +169,7 @@ function formatAllSections(data) {
154
169
  const names = Object.keys(data.sections);
155
170
  const parts = [];
156
171
  if (names.length === 0) {
157
- parts.push("(no context sections)");
172
+ parts.push("(no identity sections)");
158
173
  }
159
174
  else {
160
175
  for (const name of names) {
@@ -202,7 +217,7 @@ const defaultSpawn = (file) => {
202
217
  // Commands
203
218
  // ---------------------------------------------------------------------------
204
219
  async function runShow(name, agent) {
205
- const data = await getContext(resolveContextTarget(loadConfig()), agent);
220
+ const data = await getIdentity(resolveIdentityTarget(loadConfig()), agent);
206
221
  if (name) {
207
222
  const body = formatOneSection(data, name);
208
223
  if (body === null) {
@@ -221,19 +236,19 @@ async function runShow(name, agent) {
221
236
  * always cleaned up. `spawn` is injectable for tests.
222
237
  */
223
238
  async function runEdit(name, spawn = defaultSpawn, agent) {
224
- if (!(0, context_store_js_1.isValidSectionName)(name)) {
225
- throw new ContextCliError(`Invalid section name '${name}'. Must match ^[a-z0-9][a-z0-9_-]*$ (max ${context_store_js_1.SECTION_NAME_MAX} chars).`);
239
+ if (!(0, identity_store_js_1.isValidSectionName)(name)) {
240
+ throw new IdentityCliError(`Invalid section name '${name}'. Must match ^[a-z0-9][a-z0-9_-]*$ (max ${identity_store_js_1.SECTION_NAME_MAX} chars).`);
226
241
  }
227
- const target = resolveContextTarget(loadConfig());
228
- const data = await getContext(target, agent);
242
+ const target = resolveIdentityTarget(loadConfig());
243
+ const data = await getIdentity(target, agent);
229
244
  const before = data.sections[name] ?? "";
230
- const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "hicortex-ctx-"));
245
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "hicortex-id-"));
231
246
  const file = (0, node_path_1.join)(dir, `${name}.md`);
232
247
  try {
233
248
  (0, node_fs_1.writeFileSync)(file, before, "utf-8");
234
249
  const ran = spawn(file);
235
250
  if (!ran) {
236
- throw new ContextCliError("No editor available. Set $EDITOR, or install nano or vi.");
251
+ throw new IdentityCliError("No editor available. Set $EDITOR, or install nano or vi.");
237
252
  }
238
253
  const after = (0, node_fs_1.readFileSync)(file, "utf-8");
239
254
  if (!sectionChanged(before, after)) {
@@ -261,7 +276,7 @@ function extractAgentFlag(args) {
261
276
  // A missing value (end of args) or the next token being another flag is a
262
277
  // typo — never let it silently fall through to the global scope.
263
278
  if (val === undefined || val.startsWith("-")) {
264
- throw new ContextCliError("--agent requires a value, e.g. --agent alice");
279
+ throw new IdentityCliError("--agent requires a value, e.g. --agent alice");
265
280
  }
266
281
  agent = val;
267
282
  i++;
@@ -271,12 +286,13 @@ function extractAgentFlag(args) {
271
286
  }
272
287
  return { agent, rest };
273
288
  }
274
- /** Dispatch for `hicortex context <sub>`. Throws ContextCliError on bad usage/failure. */
275
- async function runContextCommand(args) {
289
+ /** Dispatch for `hicortex identity <sub>` (and the hidden `context` alias).
290
+ * Throws IdentityCliError on bad usage/failure. */
291
+ async function runIdentityCommand(args) {
276
292
  const { agent, rest } = extractAgentFlag(args);
277
293
  // Fast-fail on a bad agent id before any HTTP call (the server enforces too).
278
- if (agent !== undefined && !(0, context_store_js_1.isValidAgentId)(agent)) {
279
- throw new ContextCliError(`Invalid agent id '${agent}'. Must match ^[a-z0-9][a-z0-9_-]*$ (max ${context_store_js_1.SECTION_NAME_MAX} chars).`);
294
+ if (agent !== undefined && !(0, identity_store_js_1.isValidAgentId)(agent)) {
295
+ throw new IdentityCliError(`Invalid agent id '${agent}'. Must match ^[a-z0-9][a-z0-9_-]*$ (max ${identity_store_js_1.SECTION_NAME_MAX} chars).`);
280
296
  }
281
297
  const sub = rest[0];
282
298
  switch (sub) {
@@ -285,10 +301,12 @@ async function runContextCommand(args) {
285
301
  return;
286
302
  case "edit":
287
303
  if (!rest[1])
288
- throw new ContextCliError("Usage: hicortex context edit <name> [--agent <id>]");
304
+ throw new IdentityCliError("Usage: hicortex identity edit <name> [--agent <id>]");
289
305
  await runEdit(rest[1], undefined, agent);
290
306
  return;
291
307
  default:
292
- throw new ContextCliError("Usage: hicortex context <show [name] | edit <name>> [--agent <id>]");
308
+ throw new IdentityCliError("Usage: hicortex identity <show [name] | edit <name>> [--agent <id>]");
293
309
  }
294
310
  }
311
+ /** Backcompat alias (#264). */
312
+ exports.runContextCommand = runIdentityCommand;
@@ -1,15 +1,16 @@
1
1
  /**
2
- * Context layer (L2) — file-backed storage (0.12.0, spec 2026-07-12).
2
+ * Identity layer — file-backed storage (formerly "Context Layer" / L2; 0.12.0,
3
+ * spec 2026-07-12; renamed 0.18 #264).
3
4
  *
4
- * The context layer is hand-edited Markdown ("who you are + how to work") stored
5
- * as plain files in `<hicortex-home>/context/*.md` — one file per section, one
5
+ * The identity layer is hand-edited Markdown ("who you are + how to work") stored
6
+ * as plain files in `<hicortex-home>/identity/*.md` — one file per section, one
6
7
  * Web UI tab. It lives OUTSIDE the memories table: never distilled, scored,
7
8
  * decayed, or pruned. As plain files this guarantee is structural — no code in
8
9
  * consolidate.ts / distiller.ts references this directory.
9
10
  *
10
11
  * This module is pure file-layer logic (no express) so it is unit-testable and
11
12
  * reusable by the CLI. The daemon resolves `<hicortex-home>` exactly as it
12
- * resolves the DB dir (dirname of the resolved DB path) and passes the context
13
+ * resolves the DB dir (dirname of the resolved DB path) and passes the identity
13
14
  * dir in — nothing here hardcodes `~`.
14
15
  *
15
16
  * Security contract (spec §1): client-supplied section names are NEVER joined
@@ -17,6 +18,14 @@
17
18
  * itself appends `.md`. Reads skip symlinks (lstat, not stat). Writes validate
18
19
  * ALL names before touching the disk (atomic request semantics), go via
19
20
  * temp-file-then-rename, and keep a one-generation `<name>.md.bak` undo.
21
+ *
22
+ * Backcompat (#264): the pre-rename directory was `<hicortex-home>/context/`.
23
+ * `migrateIdentityDir` renames it to `identity/` on the next init/nightly run
24
+ * when the old dir exists and the new does not. When BOTH exist (partial
25
+ * migration, a disk-full renameSync failure, or an operator re-creating
26
+ * `context/`), reads ADDITIVELY MERGE: per-section `identity/` wins, and
27
+ * legacy-only sections are included so nothing is lost — at both the global
28
+ * and the per-agent (`agents/<id>/`) level.
20
29
  */
21
30
  /** A valid section name: lowercase alnum start, then alnum / `_` / `-`. */
22
31
  export declare const SECTION_NAME_RE: RegExp;
@@ -36,11 +45,11 @@ export declare class InvalidSectionNameError extends Error {
36
45
  }
37
46
  /** Resolution mode for a given agent id (spec §3). */
38
47
  export type AgentMode = "override" | "global" | "off";
39
- /** Reserved subdir under <context>/ holding per-agent sections. NOT a section. */
48
+ /** Reserved subdir under <identity>/ holding per-agent sections. NOT a section. */
40
49
  export declare const AGENTS_DIR = "agents";
41
50
  /**
42
51
  * Agent ids share the section-name allowlist: they are joined into a filesystem
43
- * path (`context/agents/<id>`), so this is the same security contract, not just
52
+ * path (`identity/agents/<id>`), so this is the same security contract, not just
44
53
  * hygiene.
45
54
  */
46
55
  export declare const isValidAgentId: typeof isValidSectionName;
@@ -72,21 +81,41 @@ export interface AgentIdentity {
72
81
  * - absent, or empty/whitespace-only → null id, "unset". Empty string == unset
73
82
  * everywhere (this is the value `init --agent-name ""` writes-then-clears to
74
83
  * opt back out): CC's default is NO `?agent=`, so all CC boxes for one user
75
- * share the global context — one user = one identity across machines.
84
+ * share the global identity — one user = one identity across machines.
76
85
  * There is NO hostname fallback: a hostname-derived default would silently give
77
- * every machine its own context, the opposite of the shared-identity default.
86
+ * every machine its own identity, the opposite of the shared-identity default.
78
87
  */
79
88
  export declare function resolveAgentIdentity(config: Record<string, unknown>): AgentIdentity;
80
89
  /**
81
- * Normalize the raw `contextAgents` config value into a map of valid agent id →
90
+ * Normalize the raw `identityAgents` config value into a map of valid agent id →
82
91
  * mode. An entry is kept only when its key passes isValidAgentId AND its value
83
92
  * is one of the three modes; everything else is dropped and its key collected
84
- * for a one-time boot warning (mirrors resolveContextClients).
93
+ * for a one-time boot warning (mirrors resolveIdentityClients).
94
+ *
95
+ * Backcompat (#264): the pre-rename key was `contextAgents`. Callers that want
96
+ * to honour it should pass the already-resolved value here (see
97
+ * `resolveIdentityConfig` in mcp-server.ts which reads new + old).
85
98
  */
86
- export declare function resolveContextAgents(raw: unknown): {
99
+ export declare function resolveIdentityAgents(raw: unknown): {
87
100
  agents: Record<string, AgentMode>;
88
101
  dropped: string[];
89
102
  };
103
+ /**
104
+ * Rename `<home>/context/` → `<home>/identity/` when the legacy dir exists and
105
+ * the new dir does not. Idempotent + safe: when both exist (a partial
106
+ * migration, or the operator created both) NOTHING is renamed — the caller
107
+ * falls back to a legacy read instead. Returns a description of what happened
108
+ * for a one-time boot log.
109
+ *
110
+ * Called from init and nightly so the migration lands on the next run of either
111
+ * command an existing install hits.
112
+ */
113
+ export declare function migrateIdentityDir(home: string): {
114
+ renamed: boolean;
115
+ from: string;
116
+ to: string;
117
+ reason?: string;
118
+ };
90
119
  export interface ReadResult {
91
120
  sections: Record<string, string>;
92
121
  /** ISO timestamp of the latest included file's mtime, or null when none. */
@@ -95,7 +124,7 @@ export interface ReadResult {
95
124
  mtimes: Record<string, number>;
96
125
  }
97
126
  /**
98
- * Enumerate the context dir and return the served sections. Only regular files
127
+ * Enumerate the identity dir and return the served sections. Only regular files
99
128
  * whose basename (sans `.md`) passes the allowlist are included; symlinks are
100
129
  * skipped (lstat). `<name>.md.bak` and temp files never match the `*.md` filter.
101
130
  *
@@ -103,6 +132,14 @@ export interface ReadResult {
103
132
  * null }` and NEVER creates the dir or throws.
104
133
  */
105
134
  export declare function readSections(dir: string): ReadResult;
135
+ /**
136
+ * Read the identity dir, additively merging the legacy `context/` sibling so
137
+ * nothing is lost when both dirs exist (post-#264 safety net for a partial/no
138
+ * migration: a disk-full renameSync failure, or an operator re-creating
139
+ * `context/`). Per-section, `identity/` wins; legacy-only sections are included
140
+ * (union). Pure function over the two dirs — used by the GET handler.
141
+ */
142
+ export declare function readSectionsWithFallback(identityDir: string): ReadResult;
106
143
  /**
107
144
  * Partial upsert of the named sections. Contract (spec §1):
108
145
  * - Validate ALL names first; any invalid → throw InvalidSectionNameError and
@@ -134,29 +171,55 @@ export declare function writeSections(dir: string, sections: Record<string, stri
134
171
  /** Total UTF-8 byte size of all sections (used for the >16 KB warn). */
135
172
  export declare function totalBytes(sections: Record<string, string>): number;
136
173
  /** Warn threshold: this layer bypasses token budgeting and injects every session. */
174
+ export declare const IDENTITY_SIZE_WARN_BYTES = 16384;
175
+ /** Backcompat alias for the pre-rename name (#264). */
137
176
  export declare const CONTEXT_SIZE_WARN_BYTES = 16384;
138
- /** Harness names that may inject the context layer. */
177
+ /** Harness names that may inject the identity layer. */
178
+ export declare const KNOWN_IDENTITY_CLIENTS: readonly ["cc", "hermes", "oc"];
179
+ /** Backcompat alias for the pre-rename name (#264). */
139
180
  export declare const KNOWN_CONTEXT_CLIENTS: readonly ["cc", "hermes", "oc"];
140
- export interface ResolvedContextClients {
181
+ export interface ResolvedIdentityClients {
141
182
  /** The resolved, de-duped list of known client names. */
142
183
  clients: string[];
143
184
  /** Unknown names dropped from an array value (for a one-time boot warning). */
144
185
  dropped: string[];
145
186
  }
187
+ /** Backcompat alias for the pre-rename name (#264). */
188
+ export type ResolvedContextClients = ResolvedIdentityClients;
146
189
  /**
147
- * Normalize the raw `contextClients` config value (spec §2):
190
+ * Normalize the raw `identityClients` config value (spec §2):
148
191
  * - `"all"` (any case) → ["cc","hermes","oc"]
149
192
  * - array → lowercase, keep known names (de-duped), collect dropped unknowns
150
193
  * - missing / non-array-non-"all" → default ["cc"]
151
- * The resolved list is echoed by GET /context as `clients` so each harness's
194
+ * The resolved list is echoed by GET /identity as `clients` so each harness's
152
195
  * hook can self-gate without its own config.
153
196
  */
154
- export declare function resolveContextClients(raw: unknown): ResolvedContextClients;
197
+ export declare function resolveIdentityClients(raw: unknown): ResolvedIdentityClients;
198
+ /**
199
+ * Read the identity-clients config value from a raw config object, honouring
200
+ * the new key (`identityClients`) and falling back to the legacy
201
+ * `contextClients` (#264) with a one-time deprecation signal. Returns the
202
+ * resolved clients, dropped unknowns, and whether the value came from the
203
+ * legacy key.
204
+ */
205
+ export declare function resolveIdentityClientsConfig(config: Record<string, unknown> | undefined | null): ResolvedIdentityClients & {
206
+ legacy: boolean;
207
+ };
208
+ /**
209
+ * Read the identity-agents config value from a raw config object, honouring
210
+ * the new key (`identityAgents`) and falling back to the legacy
211
+ * `contextAgents` (#264) with a one-time deprecation signal.
212
+ */
213
+ export declare function resolveIdentityAgentsConfig(config: Record<string, unknown> | undefined | null): {
214
+ agents: Record<string, AgentMode>;
215
+ dropped: string[];
216
+ legacy: boolean;
217
+ };
155
218
  /**
156
- * Classify the on-disk state of `context/agents/<id>`, hardened against a
219
+ * Classify the on-disk state of `identity/agents/<id>`, hardened against a
157
220
  * symlink planted at EITHER the `agents` ROOT or the `<id>` leaf — lstat/readdir
158
221
  * follow a symlinked intermediate path component, so checking only the leaf
159
- * would let a symlinked root escape the context root entirely. BOTH must be
222
+ * would let a symlinked root escape the identity root entirely. BOTH must be
160
223
  * real, non-symlink directories.
161
224
  * - "absent" — the root or the leaf does not exist (fresh install / no dir for
162
225
  * this agent). A normal, safe "no agent sections" case.
@@ -167,24 +230,24 @@ export declare function resolveContextClients(raw: unknown): ResolvedContextClie
167
230
  * rethrow (a permissions fault must never masquerade as "empty", matching
168
231
  * readSections). The single source of truth for the four call sites below.
169
232
  */
170
- export declare function agentDirState(contextDir: string, agentId: string): "absent" | "real" | "unsafe";
233
+ export declare function agentDirState(identityDir: string, agentId: string): "absent" | "real" | "unsafe";
171
234
  /**
172
235
  * Resolve the mode for a request `?agent=<id>` (spec §3):
173
236
  * 1. config entry present → that mode;
174
- * 2. else `context/agents/<id>/` present as a REAL directory (root + leaf both
237
+ * 2. else `identity/agents/<id>/` present as a REAL directory (root + leaf both
175
238
  * real, non-symlink) → "override" (dropping in a dir is intent, no config);
176
239
  * 3. else → "global".
177
240
  * A symlinked/non-dir root or leaf never counts as "present" (agentDirState).
178
241
  */
179
- export declare function resolveAgentMode(contextDir: string, agentId: string, contextAgents: Record<string, AgentMode>): AgentMode;
242
+ export declare function resolveAgentMode(identityDir: string, agentId: string, identityAgents: Record<string, AgentMode>): AgentMode;
180
243
  /**
181
244
  * Enumerate the agents the UI selector should offer: the union of allowlisted
182
- * REAL directories under `context/agents/` and every configured `contextAgents`
245
+ * REAL directories under `identity/agents/` and every configured `identityAgents`
183
246
  * key, each mapped to its resolved mode (config wins over presence). A missing
184
247
  * agents dir (fresh install) yields the config keys only; a symlinked/non-dir
185
248
  * agents root is treated as "no dirs" (config keys only).
186
249
  */
187
- export declare function listAgents(contextDir: string, contextAgents: Record<string, AgentMode>): Record<string, AgentMode>;
250
+ export declare function listAgents(identityDir: string, identityAgents: Record<string, AgentMode>): Record<string, AgentMode>;
188
251
  export interface ResolvedRead {
189
252
  sections: Record<string, string>;
190
253
  updatedAt: string | null;
@@ -202,7 +265,7 @@ export interface ResolvedRead {
202
265
  * reading (A1): a symlinked or non-directory `agents/<id>` contributes NO agent
203
266
  * sections regardless of how override was selected (config or presence).
204
267
  */
205
- export declare function readResolvedSections(contextDir: string, agentId: string | null, contextAgents: Record<string, AgentMode>): ResolvedRead;
268
+ export declare function readResolvedSections(identityDir: string, agentId: string | null, identityAgents: Record<string, AgentMode>): ResolvedRead;
206
269
  export interface HandlerResult {
207
270
  status: number;
208
271
  body: unknown;
@@ -210,17 +273,17 @@ export interface HandlerResult {
210
273
  warn?: string;
211
274
  }
212
275
  /**
213
- * GET /context. Stale-client tripwire first: recall moved to /recent, so
276
+ * GET /identity. Stale-client tripwire first: recall moved to /recent, so
214
277
  * project/limit/privacy on this route mean a legacy recall caller — return a
215
- * loud 400 rather than silently degrading to an empty context-layer response.
216
- * (A bare GET /context with no params is the legitimate context-layer read and
278
+ * loud 400 rather than silently degrading to an empty identity-layer response.
279
+ * (A bare GET /identity with no params is the legitimate identity-layer read and
217
280
  * is served normally — the two are indistinguishable at the wire, so a
218
281
  * paramless legacy caller is covered by the migration docs, not this guard.)
219
282
  */
220
- export declare function handleContextGet(contextDir: string, clients: string[], query: Record<string, unknown>, contextAgents?: Record<string, AgentMode>): HandlerResult;
283
+ export declare function handleIdentityGet(identityDir: string, clients: string[], query: Record<string, unknown>, identityAgents?: Record<string, AgentMode>): HandlerResult;
221
284
  /**
222
- * PUT /context. Validates the body shape and section content types, then
285
+ * PUT /identity. Validates the body shape and section content types, then
223
286
  * delegates to writeSections (which owns the name allowlist + atomicity +
224
287
  * symlink safety). Throws are left to the adapter to turn into a 500.
225
288
  */
226
- export declare function handleContextPut(contextDir: string, body: unknown, query?: Record<string, unknown>, contextAgents?: Record<string, AgentMode>): HandlerResult;
289
+ export declare function handleIdentityPut(identityDir: string, body: unknown, query?: Record<string, unknown>, identityAgents?: Record<string, AgentMode>): HandlerResult;