@gamaze/hicortex 0.17.6 → 0.18.1
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.
- package/README.md +30 -28
- package/assets/dashboard.html +121 -5
- package/assets/{context.html → identity.html} +18 -18
- package/assets/viz.html +19 -7
- package/dist/claude-md.d.ts +2 -1
- package/dist/claude-md.js +2 -1
- package/dist/cli-args.d.ts +9 -0
- package/dist/cli-args.js +16 -0
- package/dist/cli.js +29 -20
- package/dist/consolidate.d.ts +15 -0
- package/dist/consolidate.js +30 -3
- package/dist/dashboard.d.ts +58 -1
- package/dist/dashboard.js +27 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/health.d.ts +68 -0
- package/dist/health.js +73 -0
- package/dist/identity-cli.d.ts +90 -0
- package/dist/{context-cli.js → identity-cli.js} +66 -48
- package/dist/{context-store.d.ts → identity-store.d.ts} +94 -31
- package/dist/{context-store.js → identity-store.js} +212 -71
- package/dist/index.d.ts +12 -5
- package/dist/index.js +57 -29
- package/dist/init.d.ts +44 -8
- package/dist/init.js +142 -37
- package/dist/learnings-identity.d.ts +149 -0
- package/dist/{lessons-context.js → learnings-identity.js} +96 -52
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +217 -68
- package/dist/memory-instructions.d.ts +6 -6
- package/dist/memory-instructions.js +6 -6
- package/dist/nightly.js +65 -6
- package/dist/paths.js +1 -1
- package/dist/recall-hook-cli.d.ts +1 -1
- package/dist/recall-hook-cli.js +3 -3
- package/dist/recall-index.js +5 -2
- package/dist/status.d.ts +2 -2
- package/dist/status.js +11 -9
- package/dist/telemetry.d.ts +10 -0
- package/dist/type-classify.js +4 -1
- package/dist/type-labels.d.ts +30 -0
- package/dist/type-labels.js +43 -0
- package/dist/types.d.ts +28 -0
- package/dist/uninstall.d.ts +12 -0
- package/dist/uninstall.js +21 -3
- package/dist/viz.d.ts +24 -11
- package/dist/viz.js +97 -32
- package/hermes-plugin/hicortex/README.md +4 -2
- package/package.json +2 -2
- package/dist/context-cli.d.ts +0 -69
- package/dist/lessons-context.d.ts +0 -102
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ The plugin connects to `http://127.0.0.1:8787` by default. For a remote server,
|
|
|
67
67
|
|
|
68
68
|
| When | What | How |
|
|
69
69
|
|------|------|-----|
|
|
70
|
-
| Agent start | Standing
|
|
70
|
+
| Agent start | Standing identity (`## Identity`) + recent lessons fetched fresh and injected | CC SessionStart hook (calls `hicortex learnings-identity`; `lessons-context` kept as an alias so existing installed hooks don't break) / Hermes plugin `system_prompt_block` / OC `before_agent_start` hook |
|
|
71
71
|
| Every prompt (0.14) | A compact **recall index** of relevant memories is injected — one line per memory; the agent lazy-loads full content with `hicortex_get` only when needed | All three harnesses call server `POST /recall-index` per turn: CC UserPromptSubmit hook (`hicortex recall-hook`), Hermes plugin `prefetch` (0.7.0; falls back to `/search` injection against a pre-0.14 server), OC `before_agent_start` hook (fires per inbound message). Turn-based dedup per session; resets on new session/compaction. Fail-soft |
|
|
72
72
|
| Nightly | Denoise sessions → POST /distill → server distills + embeds + stores → consolidate (score, reflect, link, decay) | Automatic pipeline — no manual steps |
|
|
73
73
|
|
|
@@ -118,42 +118,43 @@ The run is resumable — interrupt it any time and it continues where it stopped
|
|
|
118
118
|
|
|
119
119
|
## Agent Tools (MCP)
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
10 canonical tools available via MCP (plus `hicortex_lessons` as a backcompat alias of `hicortex_learnings`):
|
|
122
122
|
|
|
123
123
|
- **hicortex_search** — Semantic search across all stored memories
|
|
124
124
|
- **hicortex_get** — Fetch one memory's full content by id (0.14) — the lazy-load counterpart of the recall index; fetching marks the memory as used
|
|
125
125
|
- **hicortex_recent** — Get recent decisions and project state (queryless recall; renamed in 0.12)
|
|
126
126
|
- **hicortex_ingest** — Store a memory directly
|
|
127
|
-
- **
|
|
127
|
+
- **hicortex_learnings** — Get actionable Learnings from reflection (`hicortex_lessons` is kept as a backcompat alias)
|
|
128
128
|
- **hicortex_index** — Get the knowledge domain index (what topics are stored)
|
|
129
129
|
- **hicortex_graph** — Graph traversal: neighbors, hubs, shortest paths
|
|
130
130
|
- **hicortex_update** — Fix incorrect memories (re-embeds on content change)
|
|
131
131
|
- **hicortex_delete** — Remove memories with cascade cleanup
|
|
132
|
+
- **hicortex_identity** — Fetch your standing identity layer on demand (all sections, or one by name; pass `agent` on multi-agent installs to scope to a specific agent)
|
|
132
133
|
|
|
133
134
|
Explicit learnings: call `hicortex_ingest` directly (capture is otherwise automatic, nightly).
|
|
134
135
|
|
|
135
|
-
##
|
|
136
|
+
## Identity Layer
|
|
136
137
|
|
|
137
|
-
Beyond auto-distilled memories and lessons, Hicortex holds a **hand-edited
|
|
138
|
+
Beyond auto-distilled memories and lessons, Hicortex holds a **hand-edited identity layer** — standing "who you are + how to work" Markdown injected into every session at start. Unlike memories, it is **never distilled, scored, or decayed**: what you write stays verbatim until you change it.
|
|
138
139
|
|
|
139
|
-
- **Storage:** plain files on the server at `~/.hicortex/
|
|
140
|
-
- **Edit:** the web editor at `http://localhost:8787/
|
|
141
|
-
- **Delivery:** injected into the harnesses listed in `
|
|
140
|
+
- **Storage:** plain files on the server at `~/.hicortex/identity/*.md` — one file per section (recommended starter sections `user.md` + `rules.md`, which you create — nothing is pre-populated; add more by dropping in a file). It lives outside the memories table; consolidation never touches it.
|
|
141
|
+
- **Edit:** the web editor at `http://localhost:8787/identity/ui` (one tab per section, Save), or the CLI `hicortex identity show [name]` / `hicortex identity edit <name>`.
|
|
142
|
+
- **Delivery:** injected into the harnesses listed in `identityClients` (default `["cc"]`; `"all"` or any subset of `cc`/`hermes`/`oc` — all three are supported since 0.13).
|
|
142
143
|
- **Deletion** is filesystem-only — remove the file on the server (as the daemon user).
|
|
143
144
|
|
|
144
|
-
### Per-agent
|
|
145
|
+
### Per-agent identity (0.13)
|
|
145
146
|
|
|
146
|
-
One server serves a fleet of distinct-persona agents. Each agent can have its **own**
|
|
147
|
+
One server serves a fleet of distinct-persona agents. Each agent can have its **own** identity, resolved server-side into one of three modes:
|
|
147
148
|
|
|
148
149
|
- **`override`** (default when an `agents/<id>/` dir exists) — the agent's sections win **per section name**, falling back to the global set for any section it doesn't define.
|
|
149
150
|
- **`global`** — the shared global set (the 0.12 behavior).
|
|
150
151
|
- **`off`** — inject nothing for that agent.
|
|
151
152
|
|
|
152
|
-
- **Agent id:** Hermes and OC scope per profile/agent automatically. **CC is global by default** — it sends no `?agent=`, so all your CC machines share one global
|
|
153
|
-
- **Storage:** per-agent sections live at `~/.hicortex/
|
|
154
|
-
- **Config:** `
|
|
155
|
-
- **Edit:** the web editor's scope selector (`Global` | `<agent>`; inherited sections shown dimmed), or `hicortex
|
|
156
|
-
- **Backward compatible:** no `?agent=` and no `
|
|
153
|
+
- **Agent id:** Hermes and OC scope per profile/agent automatically. **CC is global by default** — it sends no `?agent=`, so all your CC machines share one global identity (one user = one identity across machines). `agentName` is an explicit opt-in: set it with `init --agent-name <name>` and CC will send `?agent=<config.agentName>`; clear it with `init --agent-name ""` to return to global. The id is on the same strict allowlist as section names (it becomes a path). Shown by `hicortex status` (or `(not set — global identity)` when unset).
|
|
154
|
+
- **Storage:** per-agent sections live at `~/.hicortex/identity/agents/<id>/*.md`; the global reader never descends into `agents/`.
|
|
155
|
+
- **Config:** `identityAgents` maps agent id → mode; a dropped-in `agents/<id>/` dir alone means `override` with no config. Editing `identityAgents` needs a daemon restart; dropping in a dir takes effect immediately.
|
|
156
|
+
- **Edit:** the web editor's scope selector (`Global` | `<agent>`; inherited sections shown dimmed), or `hicortex identity show|edit --agent <id>`.
|
|
157
|
+
- **Backward compatible:** no `?agent=` and no `identityAgents`/`agents/` dir → every agent gets the global set.
|
|
157
158
|
|
|
158
159
|
## CLI Commands
|
|
159
160
|
|
|
@@ -168,11 +169,11 @@ npx @gamaze/hicortex classify-domains # Backfill domain tags over the c
|
|
|
168
169
|
npx @gamaze/hicortex classify-types # Reclassify memory types (episode/fact/decision)
|
|
169
170
|
npx @gamaze/hicortex dedup # Preview near-duplicate memory clusters (dry run, no changes)
|
|
170
171
|
npx @gamaze/hicortex dedup --apply # Merge near-duplicate clusters (backs up the DB first)
|
|
171
|
-
npx @gamaze/hicortex
|
|
172
|
-
npx @gamaze/hicortex
|
|
173
|
-
npx @gamaze/hicortex
|
|
174
|
-
npx @gamaze/hicortex init --agent-name <name>
|
|
175
|
-
npx @gamaze/hicortex init --agent-name ""
|
|
172
|
+
npx @gamaze/hicortex identity show [name] # Print the standing identity layer (see Identity Layer)
|
|
173
|
+
npx @gamaze/hicortex identity edit <name> # Edit an identity section in $EDITOR
|
|
174
|
+
npx @gamaze/hicortex identity show --agent <id> # Show a specific agent's resolved identity (0.13)
|
|
175
|
+
npx @gamaze/hicortex init --agent-name <name> # Opt in to a per-agent identity id (default: unset — shared global identity)
|
|
176
|
+
npx @gamaze/hicortex init --agent-name "" # Clear it back to global identity
|
|
176
177
|
npx @gamaze/hicortex init --repair-config # Recover from a malformed config.json (see below)
|
|
177
178
|
npx @gamaze/hicortex telemetry # Show exactly what anonymous telemetry sends
|
|
178
179
|
npx @gamaze/hicortex status # Show config, DB stats
|
|
@@ -215,17 +216,18 @@ Config at `~/.hicortex/config.json`. Created by `init`. Key options:
|
|
|
215
216
|
| `ollamaFlushEvery` | Flush ollama's accumulated memory every N scoring calls. **Off by default (0)** — opt-in only for an **ollama** install whose runner RSS growth (~171 MB/call) swap-thrashes long consolidations on a RAM-constrained box; N=15 caps a cycle at ~2.5 GB. Gated on the provider being ollama (local **or** remote) — no effect for non-ollama providers. Only you can judge whether your ollama endpoint actually suffers the growth (a managed/cloud ollama host may not), so it stays off until you set it. |
|
|
216
217
|
| `ollamaFlushWaitMs` | Milliseconds to wait after an ollama flush for the runner to exit + release memory (default 180000 = 3 min). |
|
|
217
218
|
| `authToken` | Bearer token for endpoint auth. Generated on first `init` in server mode. Find the active token with `hicortex status` or in `~/.hicortex/config.json`. |
|
|
218
|
-
| `corsAllowedOrigins` | Browser origins allowed to read cross-origin responses, e.g. `["https://ui.example.com"]`. **Empty by default** — the server sends no `Access-Control-Allow-Origin` and never `Allow-Credentials`, so no external web page can read its data. The bundled `/viz` and `/
|
|
219
|
+
| `corsAllowedOrigins` | Browser origins allowed to read cross-origin responses, e.g. `["https://ui.example.com"]`. **Empty by default** — the server sends no `Access-Control-Allow-Origin` and never `Allow-Credentials`, so no external web page can read its data. The bundled `/viz` and `/identity/ui` pages are same-origin and need no entry. |
|
|
219
220
|
| `licenseKey` | Commercial license key (optional; for display in `hicortex status`) |
|
|
220
221
|
| `domains` | Your memory domain list (`[{name, description}]`). Scaffolded by `init`; edit freely — see [Memory Domains & Tags](#memory-domains--tags) |
|
|
221
222
|
| `weakPrimaryFloor` | Minimum similarity for a no-fit memory to keep a weak domain association (default: 0.45) |
|
|
222
223
|
| `moduleIndexTokenBudget` | Max tokens for domain index in lessons context (default: 500) |
|
|
223
224
|
| `lessonsLimit` | Max lessons injected into an agent's session-start context (default: 10). Lessons are ranked per session by project/domain affinity + recency + strength + access, so each session sees its most-relevant slice. Lower = leaner system prompts. |
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `agentName` | This install's per-agent
|
|
225
|
+
| `identityClients` | Which harnesses inject the [identity layer](#identity-layer) at session start (default `["cc"]`; `"all"` or any subset of `cc`/`hermes`/`oc`) |
|
|
226
|
+
| `identityAgents` | Per-agent identity modes (0.13): `{ "<id>": "override" \| "global" \| "off" }`. Absent + no `agents/<id>/` dir → every agent gets the global set. Boot-time (restart to apply) — see [Per-agent identity](#per-agent-identity-013) |
|
|
227
|
+
| `agentName` | This install's per-agent identity id sent as `?agent=`. **Unset by default** (CC shares the global identity — no `?agent=` sent). Explicit opt-in via `init --agent-name <name>`; `init --agent-name ""` clears it. An empty/whitespace value equals unset |
|
|
227
228
|
| `captureCooldownHours` | Success-cooldown (hours) for the **capture watchdog** (0.17). The capture timer polls every ~20 min; the watchdog captures only if more than this has elapsed since the last *successful* capture (`state.lastNightly`). Default `6` (≈4 captures/day). A failed preflight retries on the next poll (~20 min) — so a transient fire-instant network miss costs minutes, not a day (#239) |
|
|
228
229
|
| `consolidationHours` | Hours (0–23, local) for the **consolidation** timer — the full nightly (capture + distill + score + reflect + link). Installed for **server/co-located only** (clients have no local DB). Default `[10, 22]`: the 22:00 evening slot runs after the day's capture waves (same-day results); the 10:00 morning slot runs *after* the morning capture so wake-up pushes are caught. Omitted on clients |
|
|
230
|
+
| `timerJitterSeconds` | Max random delay (seconds) added to **generated** consolidation timers (#256), so a fleet doesn't all fire on the same minute (thundering-herd → LLM-backend contention). systemd: a single `RandomizedDelaySec=<n>`; launchd has no native equivalent so a per-install randomized `Minute` offset is baked into every `StartCalendarInterval` dict (sub-60s values no-op on launchd). Default `3600` (≈±30 min spread on the 2-slot/day cadence); `0` disables. Affects timers on the next `init` (re-init rewrites the unit files; installs that don't re-init keep their existing timers) |
|
|
229
231
|
| `consolidateMaxLlmCalls` | Ceiling on total LLM calls across all classify-tier consolidation stages (content-domain, link discovery, supersession) per run. A runaway **backstop**, not a throughput throttle — on a free local model the binding constraint is the nightly unit's wall-clock timeout, not call count. Default `5000` (was a hard-coded 200 that starved link/supersession during a classification backlog) |
|
|
230
232
|
| `memorySoftCap` | Soft cap on the memory corpus (default 10000). When the corpus exceeds this, the nightly's capacity-eviction stage removes the lowest-`effectiveStrength` memories (ties broken by oldest access) until under the cap — the active forgetting mechanism that bounds DB size, vector-index RAM, and consolidation workload. `0` disables eviction (indefinite growth — the pre-#245 behaviour). The evicted tail is cold by construction (effectiveStrength is the same decay-weighted score the recall ranker uses, so these were not surfacing in the top-k anyway). At 10K memories the load + JS sort is <100 ms |
|
|
231
233
|
| `updateChannel` | Release channel pinned into the generated daemon/timer ExecStart for **npx-thin** installs (global-binary installs use the absolute binary and are unaffected). A dist-tag (`"rc"`, `"next"`) or an exact version (`"0.17.1"`). E.g. `"rc"` → the timer runs `npx -y @gamaze/hicortex@rc nightly`, so the host tracks the rc dist-tag (an internal fleet can ride rc through a pre-promotion soak). Validated as `[\w.\-]+` (rejects anything that'd break the unit/plist templates). Absent → auto-detect (bare on `latest`, else `@next`). (0.17.1) |
|
|
@@ -268,9 +270,9 @@ Full docs: [hicortex.gamaze.com/docs/configuration.html](https://hicortex.gamaze
|
|
|
268
270
|
| `/recall-index` | POST | Yes | Pushed recall index (0.14): `{session_id, prompt}` → compact one-line-per-memory block (or `null`); `{session_id, reset: true}` clears the session's dedup state. Appearing in the index marks memories *shown*, never *used* |
|
|
269
271
|
| `/memory` | GET | Yes | Fetch one memory by `?id=` (0.14). Marks it as used (strengthens) — the lazy-load counterpart of `/recall-index`. Response includes a server-rendered `citation` (id, date, origin agent) — agents are instructed to cite memories that shape their answers, so memory influence is always visible to the user (0.14.1) |
|
|
270
272
|
| `/recent` | GET | Yes | Recent memories, queryless recall (renamed from `/context` in 0.12) |
|
|
271
|
-
| `/
|
|
272
|
-
| `/
|
|
273
|
-
| `/
|
|
273
|
+
| `/identity` | GET / PUT | Yes | Standing [identity layer](#identity-layer): read all sections / partial-upsert named sections. `?agent=<id>` selects a [per-agent scope](#per-agent-identity-013) (server resolves override/global/off + merge); invalid id → 400. Recall-style query params on GET → 400 (use `/recent`). (`/context` remains as a backcompat alias.) |
|
|
274
|
+
| `/identity/ui` | GET | No* | Web editor for the identity layer (shell served without auth, like `/viz`; data via `/identity`) |
|
|
275
|
+
| `/learnings` | GET | Yes | Learnings + memory index (used by CC SessionStart hook). `/lessons` is a backcompat alias for the same handler |
|
|
274
276
|
| `/ingest` | POST | Yes | Legacy: accept a single pre-distilled memory from older clients |
|
|
275
277
|
| `/sse` | GET | Yes | MCP SSE stream for agent connections |
|
|
276
278
|
| `/messages` | POST | Yes | MCP message endpoint |
|
|
@@ -311,7 +313,7 @@ LLM selection is **user-controlled**: `npx @gamaze/hicortex init` detects candid
|
|
|
311
313
|
| Custom provider | `llmBaseUrl` + `llmApiKey` | Any OpenAI-compatible endpoint |
|
|
312
314
|
| Hicortex env vars | `HICORTEX_LLM_BASE_URL` + `HICORTEX_LLM_API_KEY` | Override at runtime |
|
|
313
315
|
|
|
314
|
-
If no LLM is configured, the server starts in **recall-only mode**: search, lessons, and
|
|
316
|
+
If no LLM is configured, the server starts in **recall-only mode**: search, lessons, and identity work; `/distill` and consolidation are disabled. Run `npx @gamaze/hicortex init` to configure.
|
|
315
317
|
|
|
316
318
|
## Database
|
|
317
319
|
|
package/assets/dashboard.html
CHANGED
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
<div class="hc-nav-links">
|
|
123
123
|
<a href="/dashboard" data-nav-active>Dashboard</a>
|
|
124
124
|
<a href="/viz">Graph</a>
|
|
125
|
-
<a href="/
|
|
125
|
+
<a href="/identity/ui">Identity</a>
|
|
126
126
|
<span class="hc-nav-disabled" title="Coming soon — #250">Self-improvement</span>
|
|
127
127
|
</div>
|
|
128
128
|
</nav>
|
|
@@ -183,6 +183,10 @@
|
|
|
183
183
|
<div id="digest-dedup"></div>
|
|
184
184
|
<h2 id="digest-tokens-section" style="margin-top:16px;display:none;">Token usage breakdown by stage</h2>
|
|
185
185
|
<div id="digest-tokens"></div>
|
|
186
|
+
<div id="digest-budget-section" style="margin-top:16px;display:none;">
|
|
187
|
+
<div class="sub" style="margin-bottom:6px;">Consolidation + LLM usage — two usage-vs-budget bars: per-run LLM calls spent vs the consolidation ceiling, and this billing period's tokens vs the monthly fair-use cap. The amber pill appears only when the call cap was hit. The tokens bar renders only when a monthly cap is configured (cap > 0); uncapped installs (the self-hosted default) show the absolute token count instead.</div>
|
|
188
|
+
<div id="digest-budget"></div>
|
|
189
|
+
</div>
|
|
186
190
|
</div>
|
|
187
191
|
|
|
188
192
|
<div class="sub muted" style="margin-top:24px;">
|
|
@@ -194,7 +198,7 @@
|
|
|
194
198
|
<script>
|
|
195
199
|
"use strict";
|
|
196
200
|
// ---------------------------------------------------------------------------
|
|
197
|
-
// Token handling — mirror /viz + /
|
|
201
|
+
// Token handling — mirror /viz + /identity/ui: ?token= handoff (stripped on
|
|
198
202
|
// load), localStorage persistence, 401 prompt. Same-origin fetch only.
|
|
199
203
|
// ---------------------------------------------------------------------------
|
|
200
204
|
const TOKEN_KEY = "hicortex-dashboard-token";
|
|
@@ -234,6 +238,17 @@ let compKey = "by_type";
|
|
|
234
238
|
|
|
235
239
|
const $ = (id) => document.getElementById(id);
|
|
236
240
|
|
|
241
|
+
// #264 WS2: human-term labels for the internal memory_type enum. The data
|
|
242
|
+
// blob carries the RAW enum keys (a stable JSON contract, also relied on by
|
|
243
|
+
// tests); only the rendered label changes. Unknown keys pass through.
|
|
244
|
+
const TYPE_LABELS = {
|
|
245
|
+
fact: "Knowledge",
|
|
246
|
+
episode: "Experience",
|
|
247
|
+
decision: "Decisions",
|
|
248
|
+
lesson: "Learnings"
|
|
249
|
+
};
|
|
250
|
+
function labelForType(t) { return TYPE_LABELS[t] || t; }
|
|
251
|
+
|
|
237
252
|
// ---------------------------------------------------------------------------
|
|
238
253
|
// Render: headline
|
|
239
254
|
// ---------------------------------------------------------------------------
|
|
@@ -419,7 +434,10 @@ function renderComposition(comp, key) {
|
|
|
419
434
|
// attacker-influenced (any authenticated /distill client sets source_agent
|
|
420
435
|
// verbatim). Injected into both a title="..." attribute and text — close
|
|
421
436
|
// the attribute and add an on* handler otherwise.
|
|
422
|
-
|
|
437
|
+
// #264 WS2: when the composition key is by_type, show the human-term label
|
|
438
|
+
// (Knowledge/Experience/...) instead of the raw enum value.
|
|
439
|
+
const display = compKey === "by_type" ? labelForType(k) : k;
|
|
440
|
+
const ek = escapeHtml(display);
|
|
423
441
|
return `<div class="bar-row"><span class="k" title="${ek}">${ek}</span><span class="track"><span class="fill" style="width:${pct}%"></span></span><span class="n">${v}</span></div>`;
|
|
424
442
|
}).join("");
|
|
425
443
|
}
|
|
@@ -434,7 +452,7 @@ function linkId(id) {
|
|
|
434
452
|
// is a server hex token but escape the text anyway (defense-in-depth).
|
|
435
453
|
return `<a href="/memory?id=${encodeURIComponent(id)}" title="Open memory">${escapeHtml(id.slice(0, 8))}</a>`;
|
|
436
454
|
}
|
|
437
|
-
function renderDigest(digest) {
|
|
455
|
+
function renderDigest(digest, tokens) {
|
|
438
456
|
$("digest-date").textContent = digest.date || "—";
|
|
439
457
|
const s = digest.stages;
|
|
440
458
|
const stages = [
|
|
@@ -444,6 +462,8 @@ function renderDigest(digest) {
|
|
|
444
462
|
`<span class="stage-pill">supersession: ${s.supersession}</span>`,
|
|
445
463
|
s.evicted !== undefined && s.evicted > 0 ? `<span class="stage-pill warn">evicted: ${s.evicted}</span>` : "",
|
|
446
464
|
s.tokens !== undefined ? `<span class="stage-pill">tokens: ${escapeHtml(formatTokens(s.tokens))}</span>` : "",
|
|
465
|
+
// #255 CR: budget exhaustion pill lives in the dedicated budget-usage bar
|
|
466
|
+
// below (alert state on top of the continuous used/max bar), not here.
|
|
447
467
|
].join(" ");
|
|
448
468
|
$("digest-stages").innerHTML = stages;
|
|
449
469
|
// Per-stage token breakdown (#246) — only when the day's run metered tokens
|
|
@@ -464,6 +484,102 @@ function renderDigest(digest) {
|
|
|
464
484
|
$("digest-tokens-section").style.display = "none";
|
|
465
485
|
}
|
|
466
486
|
|
|
487
|
+
// #255 CR + tokens-vs-cap: TWO usage-vs-budget bars in this section.
|
|
488
|
+
//
|
|
489
|
+
// Bar 1 — Consolidation calls (per-run): budget_calls_used / budget_max_calls.
|
|
490
|
+
// Renders whenever the day's run carried both (consolidation ran). The
|
|
491
|
+
// amber "budget exhausted" pill sits on top as the alert state.
|
|
492
|
+
//
|
|
493
|
+
// Bar 2 — LLM tokens (monthly): tokens.used / tokens.cap, where cap is the
|
|
494
|
+
// configured `llmTokensPerMonth` fair-use cap. CONDITIONAL (see the
|
|
495
|
+
// section doc): cap > 0 → render a used/cap bar in the SAME green/amber/
|
|
496
|
+
// red style; cap <= 0 (the self-hosted default, "unlimited") → render the
|
|
497
|
+
// absolute usage figure only (no broken "X / 0" bar). Hidden entirely
|
|
498
|
+
// when used is 0 (no metered run yet) — mirroring the headline stat.
|
|
499
|
+
//
|
|
500
|
+
// The section shows when EITHER bar renders. XSS-safe: only numbers flow
|
|
501
|
+
// in (Number + toLocaleString / formatTokens), the only string (deferred
|
|
502
|
+
// JSON) is escapeHtml'd.
|
|
503
|
+
const used = s.budget_calls_used;
|
|
504
|
+
const cap = s.budget_max_calls;
|
|
505
|
+
const haveCallsBar = typeof used === "number" && typeof cap === "number" && cap > 0;
|
|
506
|
+
|
|
507
|
+
// Tokens monthly accrual comes from headline.tokens (state.llmTokensThisPeriod,
|
|
508
|
+
// the same source the throttle check reads). cap <= 0 = unlimited.
|
|
509
|
+
const tokUsed = tokens && typeof tokens.used === "number" ? tokens.used : 0;
|
|
510
|
+
const tokCap = tokens && typeof tokens.cap === "number" ? tokens.cap : 0;
|
|
511
|
+
const haveTokensBar = tokUsed > 0 && tokCap > 0;
|
|
512
|
+
const haveTokensAbsolute = tokUsed > 0 && !(tokCap > 0); // uncapped: figure only
|
|
513
|
+
|
|
514
|
+
if (haveCallsBar || haveTokensBar || haveTokensAbsolute) {
|
|
515
|
+
const pieces = [];
|
|
516
|
+
|
|
517
|
+
// Bar 1: Consolidation calls.
|
|
518
|
+
if (haveCallsBar) {
|
|
519
|
+
const pct = Math.min(100, (used / cap) * 100);
|
|
520
|
+
const exhausted = s.budget_exhausted === true;
|
|
521
|
+
const over = used >= cap;
|
|
522
|
+
const near = pct >= 85 && !over;
|
|
523
|
+
const barColor = over ? "var(--bad, #e05555)" : near ? "var(--warn, #e0b055)" : "var(--ok, #61c98f)";
|
|
524
|
+
const deferred = s.budget_deferred_by_stage ?? {};
|
|
525
|
+
const pill = exhausted
|
|
526
|
+
? ` <span class="stage-pill warn" title="Consolidation budget exhausted — LLM-bound stages deferred remaining work. Per-stage refused-request counts: ${escapeHtml(JSON.stringify(deferred))}">budget exhausted</span>`
|
|
527
|
+
: "";
|
|
528
|
+
pieces.push(`
|
|
529
|
+
<div style="margin-bottom:10px;">
|
|
530
|
+
<div class="muted" style="font-size:12px;margin-bottom:4px;text-transform:uppercase;letter-spacing:0.04em;">Consolidation calls (this run)</div>
|
|
531
|
+
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
|
532
|
+
<div style="flex:1;min-width:200px;">
|
|
533
|
+
<div class="muted" style="font-size:13px;margin-bottom:4px;">
|
|
534
|
+
${used.toLocaleString()} / ${cap.toLocaleString()} LLM calls${exhausted ? " — exhausted" : ""}
|
|
535
|
+
</div>
|
|
536
|
+
<div style="height:6px;background:rgba(128,128,128,0.2);border-radius:3px;overflow:hidden;">
|
|
537
|
+
<div style="width:${pct.toFixed(1)}%;height:100%;background:${barColor};"></div>
|
|
538
|
+
</div>
|
|
539
|
+
</div>${pill}
|
|
540
|
+
</div>
|
|
541
|
+
</div>`);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Bar 2: LLM tokens (monthly). Conditional on cap > 0 for the bar form;
|
|
545
|
+
// otherwise show the absolute usage figure (uncapped installs).
|
|
546
|
+
if (haveTokensBar) {
|
|
547
|
+
const pct = Math.min(100, (tokUsed / tokCap) * 100);
|
|
548
|
+
const over = tokUsed >= tokCap;
|
|
549
|
+
const near = pct >= 85 && !over;
|
|
550
|
+
const barColor = over ? "var(--bad, #e05555)" : near ? "var(--warn, #e0b055)" : "var(--ok, #61c98f)";
|
|
551
|
+
pieces.push(`
|
|
552
|
+
<div>
|
|
553
|
+
<div class="muted" style="font-size:12px;margin-bottom:4px;text-transform:uppercase;letter-spacing:0.04em;">LLM tokens (monthly)</div>
|
|
554
|
+
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
|
555
|
+
<div style="flex:1;min-width:200px;">
|
|
556
|
+
<div class="muted" style="font-size:13px;margin-bottom:4px;">
|
|
557
|
+
${escapeHtml(formatTokens(tokUsed))} / ${escapeHtml(formatTokens(tokCap))} tokens${over ? " — over cap" : ""}
|
|
558
|
+
</div>
|
|
559
|
+
<div style="height:6px;background:rgba(128,128,128,0.2);border-radius:3px;overflow:hidden;">
|
|
560
|
+
<div style="width:${pct.toFixed(1)}%;height:100%;background:${barColor};"></div>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
563
|
+
</div>
|
|
564
|
+
</div>`);
|
|
565
|
+
} else if (haveTokensAbsolute) {
|
|
566
|
+
// Unlimited case (cap <= 0, the self-hosted default): no denominator to
|
|
567
|
+
// draw a bar against, so show the absolute usage figure only. Never
|
|
568
|
+
// render a broken "X / 0" bar.
|
|
569
|
+
pieces.push(`
|
|
570
|
+
<div>
|
|
571
|
+
<div class="muted" style="font-size:12px;margin-bottom:4px;text-transform:uppercase;letter-spacing:0.04em;">LLM tokens (monthly) <span style="text-transform:none;letter-spacing:0;">— uncapped</span></div>
|
|
572
|
+
<div class="muted" style="font-size:13px;">${escapeHtml(formatTokens(tokUsed))} tokens this month</div>
|
|
573
|
+
</div>`);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
$("digest-budget").innerHTML = pieces.join("");
|
|
577
|
+
$("digest-budget-section").style.display = "";
|
|
578
|
+
} else {
|
|
579
|
+
$("digest-budget").innerHTML = "";
|
|
580
|
+
$("digest-budget-section").style.display = "none";
|
|
581
|
+
}
|
|
582
|
+
|
|
467
583
|
if (digest.sample.length === 0) {
|
|
468
584
|
$("digest-sample").innerHTML = `<div class="empty">No new memories that day.</div>`;
|
|
469
585
|
} else {
|
|
@@ -517,7 +633,7 @@ async function load() {
|
|
|
517
633
|
renderHeadline(state.headline);
|
|
518
634
|
renderGrowth(state.series, $("split").value, $("filter").value);
|
|
519
635
|
renderComposition(state.composition, compKey);
|
|
520
|
-
renderDigest(state.digest);
|
|
636
|
+
renderDigest(state.digest, state.headline.tokens);
|
|
521
637
|
|
|
522
638
|
// Populate the growth-filter from source_agent + domain union (best-effort —
|
|
523
639
|
// a v1 affordance; the series already carries per-day totals, so the filter
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>Hicortex —
|
|
6
|
+
<title>Hicortex — identity</title>
|
|
7
7
|
<!--
|
|
8
|
-
Hicortex standing-
|
|
9
|
-
Self-contained: inline CSS/JS, ZERO external
|
|
10
|
-
edit surface for the
|
|
8
|
+
Hicortex standing-identity editor (#165, 0.12 — spec 2026-07-12 §5; renamed
|
|
9
|
+
from "context" in 0.18 #264). Self-contained: inline CSS/JS, ZERO external
|
|
10
|
+
requests (CSP-safe). The PRIMARY edit surface for the identity layer.
|
|
11
11
|
|
|
12
|
-
One tab per section, tabs built dynamically from GET /
|
|
12
|
+
One tab per section, tabs built dynamically from GET /identity `sections`
|
|
13
13
|
(the server serves any allowlist-named *.md as a section — nothing hardcoded).
|
|
14
14
|
Each tab is a <textarea>; Save PUTs only the active section (partial upsert).
|
|
15
15
|
|
|
16
16
|
The page SHELL is public (exempted in createAuthMiddleware like /viz); the
|
|
17
|
-
GET/PUT /
|
|
17
|
+
GET/PUT /identity data calls are bearer-only with a localhost bypass, so on the
|
|
18
18
|
server box no token is needed. Remote browsers hand off a token via
|
|
19
19
|
?token=<t> (stripped from the URL on load), localStorage, or a 401 prompt.
|
|
20
20
|
|
|
@@ -192,14 +192,14 @@
|
|
|
192
192
|
<div class="hc-nav-links">
|
|
193
193
|
<a href="/dashboard">Dashboard</a>
|
|
194
194
|
<a href="/viz">Graph</a>
|
|
195
|
-
<a href="/
|
|
195
|
+
<a href="/identity/ui" data-nav-active>Identity</a>
|
|
196
196
|
<span class="hc-nav-disabled" title="Coming soon — #250">Self-improvement</span>
|
|
197
197
|
</div>
|
|
198
198
|
</nav>
|
|
199
199
|
<header>
|
|
200
|
-
<h1>Hicortex —
|
|
200
|
+
<h1>Hicortex — identity</h1>
|
|
201
201
|
<label class="meta" for="scope">scope</label>
|
|
202
|
-
<select id="scope" title="Which agent's
|
|
202
|
+
<select id="scope" title="Which agent's identity to edit"></select>
|
|
203
203
|
<input type="text" id="newagent" placeholder="new-agent-id" autocomplete="off" spellcheck="false">
|
|
204
204
|
<button class="btn secondary small" id="addagent">Add agent</button>
|
|
205
205
|
<span class="meta" id="clients"></span>
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
"use strict";
|
|
252
252
|
(function () {
|
|
253
253
|
// =========================================================================
|
|
254
|
-
// Section-name allowlist — mirrors the server contract (
|
|
254
|
+
// Section-name allowlist — mirrors the server contract (identity-store.ts
|
|
255
255
|
// SECTION_NAME_RE / SECTION_NAME_MAX). Client check is UX; the server also
|
|
256
256
|
// enforces it and is the authority.
|
|
257
257
|
// =========================================================================
|
|
@@ -346,14 +346,14 @@
|
|
|
346
346
|
var afterAuth = null; // action to retry after a successful token prompt
|
|
347
347
|
|
|
348
348
|
// Per-agent scope (0.13). scope = null → the global set; otherwise an agent
|
|
349
|
-
// id, and edits target PUT /
|
|
349
|
+
// id, and edits target PUT /identity?agent=<id>. `agents` is the id -> mode
|
|
350
350
|
// map from the bare (global) GET; `origins` is the id -> "global"|"agent"
|
|
351
351
|
// provenance from an agent-scope GET (used to dim inherited tabs).
|
|
352
352
|
var scope = null;
|
|
353
353
|
var agents = {};
|
|
354
354
|
var origins = {};
|
|
355
355
|
|
|
356
|
-
// ?agent= query for the active scope. Written so the literal fetch("/
|
|
356
|
+
// ?agent= query for the active scope. Written so the literal fetch("/identity"
|
|
357
357
|
// survives the self-containment test; the server does the resolution.
|
|
358
358
|
function scopeQS() {
|
|
359
359
|
return scope ? "?agent=" + encodeURIComponent(scope) : "";
|
|
@@ -447,11 +447,11 @@
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
// =========================================================================
|
|
450
|
-
// Load — GET /
|
|
450
|
+
// Load — GET /identity, build tabs from the returned sections keys.
|
|
451
451
|
// =========================================================================
|
|
452
452
|
function load() {
|
|
453
453
|
setMsg("Loading…", "dim");
|
|
454
|
-
fetch("/
|
|
454
|
+
fetch("/identity" + scopeQS(), { headers: authHeaders() })
|
|
455
455
|
.then(function (resp) {
|
|
456
456
|
if (resp.status === 401) { afterAuth = load; showTokenPrompt(); throw new Error("unauthorized"); }
|
|
457
457
|
if (!resp.ok) {
|
|
@@ -497,7 +497,7 @@
|
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
// =========================================================================
|
|
500
|
-
// Save — PUT /
|
|
500
|
+
// Save — PUT /identity with ONLY the active section (partial upsert).
|
|
501
501
|
// =========================================================================
|
|
502
502
|
function save() {
|
|
503
503
|
if (!active) return;
|
|
@@ -515,7 +515,7 @@
|
|
|
515
515
|
|
|
516
516
|
saveBtn.disabled = true;
|
|
517
517
|
setMsg("Saving…", "dim");
|
|
518
|
-
fetch("/
|
|
518
|
+
fetch("/identity" + scopeQS(), {
|
|
519
519
|
method: "PUT",
|
|
520
520
|
headers: authHeaders({ "Content-Type": "application/json" }),
|
|
521
521
|
body: JSON.stringify(payload),
|
|
@@ -576,7 +576,7 @@
|
|
|
576
576
|
}
|
|
577
577
|
|
|
578
578
|
// =========================================================================
|
|
579
|
-
// Scope switching — reload the selected agent's (or the global)
|
|
579
|
+
// Scope switching — reload the selected agent's (or the global) identity.
|
|
580
580
|
// Warns before discarding unsaved edits in the current scope.
|
|
581
581
|
// =========================================================================
|
|
582
582
|
function anyDirty() {
|
|
@@ -623,7 +623,7 @@
|
|
|
623
623
|
try { localStorage.setItem("hicortexToken", v); } catch (e) { /* ignore */ }
|
|
624
624
|
tokenErr.style.display = "none";
|
|
625
625
|
// Verify with a cheap GET before closing; retry the pending action on success.
|
|
626
|
-
fetch("/
|
|
626
|
+
fetch("/identity", { headers: authHeaders() }).then(function (resp) {
|
|
627
627
|
if (resp.status === 401) { tokenErr.style.display = "block"; return; }
|
|
628
628
|
overlay.classList.remove("open");
|
|
629
629
|
var next = afterAuth || load;
|
package/assets/viz.html
CHANGED
|
@@ -249,7 +249,7 @@
|
|
|
249
249
|
<nav class="hc-nav-links">
|
|
250
250
|
<a href="/dashboard">Dashboard</a>
|
|
251
251
|
<a href="/viz" data-nav-active>Graph</a>
|
|
252
|
-
<a href="/
|
|
252
|
+
<a href="/identity/ui">Identity</a>
|
|
253
253
|
<span class="hc-nav-disabled" title="Coming soon — #250">Self-improvement</span>
|
|
254
254
|
</nav>
|
|
255
255
|
</div>
|
|
@@ -259,7 +259,7 @@
|
|
|
259
259
|
<div class="row" data-tip="Show only memories from one knowledge domain. Node colors match domains (see legend)."><label for="f-domain">Domain</label>
|
|
260
260
|
<select id="f-domain"><option value="">all</option></select>
|
|
261
261
|
</div>
|
|
262
|
-
<div class="row" data-tip="Show only one memory type (
|
|
262
|
+
<div class="row" data-tip="Show only one memory type (Knowledge, Experience, Decisions, Learnings)."><label for="f-type">Type</label>
|
|
263
263
|
<select id="f-type"><option value="">all</option></select>
|
|
264
264
|
</div>
|
|
265
265
|
<div class="row" data-tip="Hide memories weaker than this. Strength decays over time and grows with use and links — node size shows it."><label for="f-strength">Min strength</label>
|
|
@@ -388,6 +388,18 @@
|
|
|
388
388
|
var metaEl = document.getElementById("meta");
|
|
389
389
|
var selDomain = document.getElementById("f-domain");
|
|
390
390
|
var selType = document.getElementById("f-type");
|
|
391
|
+
|
|
392
|
+
// #264 WS2: human-term labels for the internal memory_type enum. Node data
|
|
393
|
+
// carries the RAW enum (the /graph JSON contract filters on it); only the
|
|
394
|
+
// rendered strings change. Unknown values pass through unchanged.
|
|
395
|
+
var TYPE_LABELS = {
|
|
396
|
+
fact: "Knowledge",
|
|
397
|
+
episode: "Experience",
|
|
398
|
+
decision: "Decisions",
|
|
399
|
+
lesson: "Learnings"
|
|
400
|
+
};
|
|
401
|
+
function labelForType(t) { return TYPE_LABELS[t] || t; }
|
|
402
|
+
|
|
391
403
|
var rngStrength = document.getElementById("f-strength");
|
|
392
404
|
var rngStrengthVal = document.getElementById("f-strength-val");
|
|
393
405
|
var inpLimit = document.getElementById("f-limit");
|
|
@@ -604,7 +616,7 @@
|
|
|
604
616
|
dataVersion++;
|
|
605
617
|
|
|
606
618
|
fillSelect(selDomain, data.domains || []);
|
|
607
|
-
fillSelect(selType, data.types || []);
|
|
619
|
+
fillSelect(selType, data.types || [], labelForType);
|
|
608
620
|
buildDomainLegend(data.domains || []);
|
|
609
621
|
buildEdgeLegend(data.edges || []);
|
|
610
622
|
computeLabeled();
|
|
@@ -697,12 +709,12 @@
|
|
|
697
709
|
}
|
|
698
710
|
}
|
|
699
711
|
|
|
700
|
-
function fillSelect(sel, values) {
|
|
712
|
+
function fillSelect(sel, values, labeler) {
|
|
701
713
|
var current = sel.value;
|
|
702
714
|
while (sel.options.length > 1) sel.remove(1);
|
|
703
715
|
values.forEach(function (v) {
|
|
704
716
|
var opt = document.createElement("option");
|
|
705
|
-
opt.value = v; opt.textContent = v;
|
|
717
|
+
opt.value = v; opt.textContent = labeler ? labeler(v) : v;
|
|
706
718
|
sel.appendChild(opt);
|
|
707
719
|
});
|
|
708
720
|
sel.value = values.indexOf(current) >= 0 ? current : "";
|
|
@@ -733,7 +745,7 @@
|
|
|
733
745
|
|
|
734
746
|
function tooltipHtml(n) {
|
|
735
747
|
return "<b>" + esc(labelText(n)) + "</b><br>" +
|
|
736
|
-
esc((n.domain || "no domain") + " · " + (n.memory_type || "?") +
|
|
748
|
+
esc((n.domain || "no domain") + " · " + labelForType(n.memory_type || "?") +
|
|
737
749
|
" · strength " + (typeof n.strength === "number" ? n.strength.toFixed(2) : "?"));
|
|
738
750
|
}
|
|
739
751
|
|
|
@@ -1070,7 +1082,7 @@
|
|
|
1070
1082
|
["ID", n.id],
|
|
1071
1083
|
["Domain", n.domain || "—"],
|
|
1072
1084
|
["Project", n.project || "—"],
|
|
1073
|
-
["Type", n.memory_type || "—"],
|
|
1085
|
+
["Type", labelForType(n.memory_type || "—")],
|
|
1074
1086
|
["Strength", typeof n.strength === "number" ? n.strength.toFixed(3) : "—"],
|
|
1075
1087
|
["Links", String(n.linkCount) + (n.isHub ? " (hub)" : "")],
|
|
1076
1088
|
["Created", n.created_at || "—"]
|
package/dist/claude-md.d.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* CLAUDE.md block management — removeLessonsBlock only (0.9.0+).
|
|
3
3
|
*
|
|
4
4
|
* Lesson injection was removed in 0.9.0: lessons are now fetched at query time
|
|
5
|
-
* via the CC SessionStart hook (`hicortex
|
|
5
|
+
* via the CC SessionStart hook (`hicortex learnings-identity`, aliased as the
|
|
6
|
+
* legacy `lessons-context`) and by the Hermes
|
|
6
7
|
* plugin's prefetch/system_prompt_block. File-based injection caused EPERM
|
|
7
8
|
* errors on macOS, stale blocks, and unwinnable multi-file bookkeeping.
|
|
8
9
|
*
|
package/dist/claude-md.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* CLAUDE.md block management — removeLessonsBlock only (0.9.0+).
|
|
4
4
|
*
|
|
5
5
|
* Lesson injection was removed in 0.9.0: lessons are now fetched at query time
|
|
6
|
-
* via the CC SessionStart hook (`hicortex
|
|
6
|
+
* via the CC SessionStart hook (`hicortex learnings-identity`, aliased as the
|
|
7
|
+
* legacy `lessons-context`) and by the Hermes
|
|
7
8
|
* plugin's prefetch/system_prompt_block. File-based injection caused EPERM
|
|
8
9
|
* errors on macOS, stale blocks, and unwinnable multi-file bookkeeping.
|
|
9
10
|
*
|
package/dist/cli-args.d.ts
CHANGED
|
@@ -14,3 +14,12 @@ export declare class MissingFlagValueError extends Error {
|
|
|
14
14
|
* `--server` as the agent name — a typo must be loud.
|
|
15
15
|
*/
|
|
16
16
|
export declare function readValueFlag(argv: string[], flag: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Normalize a raw CLI command word. Hidden aliases resolve to their canonical
|
|
19
|
+
* name so the cli.ts dispatch switch only spells the canonical form. Aliases
|
|
20
|
+
* today (#264 rename): `context` → `identity` and `lessons-context` →
|
|
21
|
+
* `learnings-identity`; kept so old scripts, installed hooks, and muscle memory
|
|
22
|
+
* keep working. Pure + side-effect-free (unlike cli.ts, which dispatches on
|
|
23
|
+
* import) so it can be unit-tested directly.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveCommandAlias(raw: string | undefined): string | undefined;
|
package/dist/cli-args.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.MissingFlagValueError = void 0;
|
|
9
9
|
exports.readValueFlag = readValueFlag;
|
|
10
|
+
exports.resolveCommandAlias = resolveCommandAlias;
|
|
10
11
|
/** Thrown when a value-flag is present but missing its value. */
|
|
11
12
|
class MissingFlagValueError extends Error {
|
|
12
13
|
}
|
|
@@ -28,3 +29,18 @@ function readValueFlag(argv, flag) {
|
|
|
28
29
|
}
|
|
29
30
|
return val;
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Normalize a raw CLI command word. Hidden aliases resolve to their canonical
|
|
34
|
+
* name so the cli.ts dispatch switch only spells the canonical form. Aliases
|
|
35
|
+
* today (#264 rename): `context` → `identity` and `lessons-context` →
|
|
36
|
+
* `learnings-identity`; kept so old scripts, installed hooks, and muscle memory
|
|
37
|
+
* keep working. Pure + side-effect-free (unlike cli.ts, which dispatches on
|
|
38
|
+
* import) so it can be unit-tested directly.
|
|
39
|
+
*/
|
|
40
|
+
function resolveCommandAlias(raw) {
|
|
41
|
+
if (raw === "context")
|
|
42
|
+
return "identity";
|
|
43
|
+
if (raw === "lessons-context")
|
|
44
|
+
return "learnings-identity";
|
|
45
|
+
return raw;
|
|
46
|
+
}
|