knitbrain 0.1.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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/dist/ccr/store.d.ts +57 -0
  4. package/dist/ccr/store.js +195 -0
  5. package/dist/ccr/store.js.map +1 -0
  6. package/dist/dashboard.d.ts +23 -0
  7. package/dist/dashboard.js +125 -0
  8. package/dist/dashboard.js.map +1 -0
  9. package/dist/engine/agents.d.ts +36 -0
  10. package/dist/engine/agents.js +88 -0
  11. package/dist/engine/agents.js.map +1 -0
  12. package/dist/engine/calibration.d.ts +29 -0
  13. package/dist/engine/calibration.js +72 -0
  14. package/dist/engine/calibration.js.map +1 -0
  15. package/dist/engine/feedback.d.ts +30 -0
  16. package/dist/engine/feedback.js +82 -0
  17. package/dist/engine/feedback.js.map +1 -0
  18. package/dist/engine/knowledge.d.ts +22 -0
  19. package/dist/engine/knowledge.js +154 -0
  20. package/dist/engine/knowledge.js.map +1 -0
  21. package/dist/engine/memory.d.ts +35 -0
  22. package/dist/engine/memory.js +93 -0
  23. package/dist/engine/memory.js.map +1 -0
  24. package/dist/engine/meter.d.ts +40 -0
  25. package/dist/engine/meter.js +61 -0
  26. package/dist/engine/meter.js.map +1 -0
  27. package/dist/engine/skills.d.ts +33 -0
  28. package/dist/engine/skills.js +97 -0
  29. package/dist/engine/skills.js.map +1 -0
  30. package/dist/engine/teams.d.ts +28 -0
  31. package/dist/engine/teams.js +58 -0
  32. package/dist/engine/teams.js.map +1 -0
  33. package/dist/engine/workflow.d.ts +18 -0
  34. package/dist/engine/workflow.js +40 -0
  35. package/dist/engine/workflow.js.map +1 -0
  36. package/dist/hooks/index.d.ts +2 -0
  37. package/dist/hooks/index.js +47 -0
  38. package/dist/hooks/index.js.map +1 -0
  39. package/dist/hooks/pretooluse.d.ts +23 -0
  40. package/dist/hooks/pretooluse.js +38 -0
  41. package/dist/hooks/pretooluse.js.map +1 -0
  42. package/dist/hub/client.d.ts +26 -0
  43. package/dist/hub/client.js +64 -0
  44. package/dist/hub/client.js.map +1 -0
  45. package/dist/hub/server.d.ts +23 -0
  46. package/dist/hub/server.js +85 -0
  47. package/dist/hub/server.js.map +1 -0
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js +82 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/mcp/tools.d.ts +40 -0
  52. package/dist/mcp/tools.js +461 -0
  53. package/dist/mcp/tools.js.map +1 -0
  54. package/dist/measure.d.ts +27 -0
  55. package/dist/measure.js +25 -0
  56. package/dist/measure.js.map +1 -0
  57. package/dist/optimizer/ast.d.ts +19 -0
  58. package/dist/optimizer/ast.js +189 -0
  59. package/dist/optimizer/ast.js.map +1 -0
  60. package/dist/optimizer/code.d.ts +10 -0
  61. package/dist/optimizer/code.js +231 -0
  62. package/dist/optimizer/code.js.map +1 -0
  63. package/dist/optimizer/json.d.ts +9 -0
  64. package/dist/optimizer/json.js +68 -0
  65. package/dist/optimizer/json.js.map +1 -0
  66. package/dist/optimizer/router.d.ts +39 -0
  67. package/dist/optimizer/router.js +137 -0
  68. package/dist/optimizer/router.js.map +1 -0
  69. package/dist/optimizer/text.d.ts +21 -0
  70. package/dist/optimizer/text.js +88 -0
  71. package/dist/optimizer/text.js.map +1 -0
  72. package/dist/optimizer/types.d.ts +13 -0
  73. package/dist/optimizer/types.js +2 -0
  74. package/dist/optimizer/types.js.map +1 -0
  75. package/dist/paths.d.ts +20 -0
  76. package/dist/paths.js +44 -0
  77. package/dist/paths.js.map +1 -0
  78. package/dist/platforms.d.ts +51 -0
  79. package/dist/platforms.js +157 -0
  80. package/dist/platforms.js.map +1 -0
  81. package/dist/profile.d.ts +3 -0
  82. package/dist/profile.js +169 -0
  83. package/dist/profile.js.map +1 -0
  84. package/dist/proxy/cache-aligner.d.ts +9 -0
  85. package/dist/proxy/cache-aligner.js +15 -0
  86. package/dist/proxy/cache-aligner.js.map +1 -0
  87. package/dist/proxy/index.d.ts +2 -0
  88. package/dist/proxy/index.js +37 -0
  89. package/dist/proxy/index.js.map +1 -0
  90. package/dist/proxy/optimize-request.d.ts +51 -0
  91. package/dist/proxy/optimize-request.js +111 -0
  92. package/dist/proxy/optimize-request.js.map +1 -0
  93. package/dist/proxy/server.d.ts +31 -0
  94. package/dist/proxy/server.js +104 -0
  95. package/dist/proxy/server.js.map +1 -0
  96. package/dist/server.d.ts +19 -0
  97. package/dist/server.js +54 -0
  98. package/dist/server.js.map +1 -0
  99. package/dist/setup.d.ts +28 -0
  100. package/dist/setup.js +96 -0
  101. package/dist/setup.js.map +1 -0
  102. package/dist/tokenizer.d.ts +23 -0
  103. package/dist/tokenizer.js +25 -0
  104. package/dist/tokenizer.js.map +1 -0
  105. package/dist/version.d.ts +3 -0
  106. package/dist/version.js +4 -0
  107. package/dist/version.js.map +1 -0
  108. package/package.json +66 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Piyush Dua
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # Knit Brain
2
+
3
+ [![npm](https://img.shields.io/npm/v/knitbrain)](https://www.npmjs.com/package/knitbrain)
4
+ [![ci](https://github.com/PDgit12/knitbrain/actions/workflows/ci.yml/badge.svg)](https://github.com/PDgit12/knitbrain/actions/workflows/ci.yml)
5
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+ [![node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](package.json)
7
+
8
+ > The local-first brain for coding agents: per-project memory, task-tier workflow routing, and lossless context compression — measured ~56% on real sessions, reproducible with one command.
9
+
10
+ Pure TypeScript. No Python, no native binaries, no network beyond `npm install`.
11
+
12
+ ```bash
13
+ npx knitbrain profile # measure what it would save on YOUR real sessions — before installing anything
14
+ ```
15
+
16
+ ## The honest number
17
+
18
+ Most tools in this space quote their best workload ("up to 90%!"). We publish the number nobody else does: the **whole-session average** — every tool result from real coding sessions, *including* the blocks that don't compress.
19
+
20
+ **On 3.08M tokens of tool results from 70 real Claude Code sessions: 55.8% saved overall, lossless.** Every original recoverable byte-for-byte.
21
+
22
+ | shape | % of real burn | saved |
23
+ |---|---|---|
24
+ | code & file reads | 47% | 59.6% |
25
+ | repetitive logs | 18% | 72.3% |
26
+ | short prose (reports, summaries) | 15% | 19.5% |
27
+ | long prose | 8% | 68.7% |
28
+ | test output | 6% | 46.4% |
29
+ | JSON | 5% | 65.8% |
30
+
31
+ Measured the way others measure — single best-case workloads — we land 60–99% (import graphs 98.9%, whole files 88.8%, body-heavy code 71.6%). But that's not the number you'll feel; the whole-session average is.
32
+
33
+ **Don't take our word for any of this.** `knitbrain profile` runs the actual optimizer over your own transcripts (`~/.claude/projects` by default) and prints *your* number. Local only — nothing is uploaded.
34
+
35
+ ## Why this and not a point tool
36
+
37
+ Compression-only layers shrink tokens but remember nothing. Memory-only layers remember but burn your window. Knit Brain is one substrate doing both, plus the workflow layer that makes agents use them:
38
+
39
+ - **Memory** — per-project learnings, session handoffs, a knowledge graph (imports/exports/blast-radius), on-demand skills that compound across tasks.
40
+ - **Lossless optimization** — structure-preserving skeletons (JSON keeps its schema, code keeps its signatures via tree-sitter AST), cross-turn dedup of re-sent bulk, sentence anchoring for prose — all reversible through a content-addressed store.
41
+ - **Workflow intelligence** — a deterministic tier classifier (inquiry/trivial/standard/complex) routing how much process a task deserves, with guardrailed agent generation and a shared team board.
42
+ - **Self-healing, not self-confident** — two feedback loops run continuously: TOIN backs off any compression kind that gets over-retrieved, and the classifier shifts its own thresholds after 3 wrong-verdict votes (`knitbrain_record_false_positive`). Wrong tuning costs efficiency, never correctness.
43
+
44
+ ## Architecture
45
+
46
+ ```
47
+ agent (Claude Code / Cursor / Codex) your app (API key)
48
+ │ │
49
+ ▼ ▼
50
+ ┌──────────────────────────┐ ┌──────────────────────────────┐
51
+ │ knitbrain · MCP server │ │ knitbrain-proxy (loopback) │
52
+ │ 25 tools │ │ rolling window — old turns │
53
+ │ ├ memory: learnings, │ │ compressed harder · exact │
54
+ │ │ handoffs, sessions │ │ repeats deduped to markers │
55
+ │ ├ knowledge graph │ │ · your directive verbatim │
56
+ │ ├ classifier + FP loop │ │ · CacheAligner prefix │
57
+ │ ├ skills · agents │ └──────────────┬───────────────┘
58
+ │ ├ team board · meter │ │ smaller request
59
+ │ └ optimize / retrieve │ ▼
60
+ └────────────┬─────────────┘ LLM provider (Anthropic
61
+ │ every data payload /v1/messages · OpenAI
62
+ ▼ /v1/chat/completions)
63
+ ┌──────────────────────────────────────────────┐
64
+ │ optimizer router │
65
+ │ json → schema-preserving skeleton │
66
+ │ code → tree-sitter AST body elision │
67
+ │ logs → template dedup + anchor │
68
+ │ prose → sentence anchor (TOIN-gated) │
69
+ └────────────────────┬─────────────────────────┘
70
+ ▼ skeleton + ⟨ccr:hash⟩
71
+ ┌──────────────────────────────────────────────┐ ┌─────────────────┐
72
+ │ CCR store — lossless, content-addressed │◀───▶│ live dashboard │
73
+ │ (sha256 = handle) · integrity-checked reads │ │ 127.0.0.1:8790 │
74
+ │ hot → cold gzip → budgeted purge │ └─────────────────┘
75
+ └──────────────────────────────────────────────┘
76
+ self-healing: TOIN backs off over-retrieved kinds ·
77
+ classifier recalibrates after 3 wrong-verdict votes
78
+ ```
79
+
80
+ **One brain, two doors, one lossless store:**
81
+
82
+ - **MCP server** (`knitbrain`) — 25 tools: memory (learnings, session handoff), knowledge graph (imports/exports/dependents), workflow classification with a self-healing false-positive loop (3 wrong-verdict votes shift the threshold), a `knitbrain_run` orchestrator (task → skill → agents → directive), an on-demand skills engine, project-specific agent generation, a shared team board, a **context-window meter** (warns and tells the agent to save a handoff before the window blows), and explicit `optimize`/`retrieve`. Every data payload flows through one dispatch chokepoint where it's compressed structure-preservingly and tagged with a `⟨ccr:hash⟩` handle.
83
+ - **Proxy** (`knitbrain-proxy`) — a loopback HTTP proxy in front of the LLM API (provider auto-detected per request: Anthropic `/v1/messages`, OpenAI `/v1/chat/completions`). Compresses the full request — old turns harder than recent ones, exact repeats across turns collapsed to a marker, pasted bulk inside your message compressed while your directive stays verbatim — and streams the response back.
84
+ - **CCR store** — content-addressed (SHA-256 = handle), integrity-checked on every read, atomic writes, tiered retention (hot → cold gzip archive → budgeted purge). The pristine original is always one `retrieve` away, which is what makes aggressive compression safe.
85
+ - **Live dashboard** — context meter, tokens saved, CCR tiers, self-tuning stats, knowledge graph, skills, recent learnings, team board. All stores are cross-process fresh: what the agent writes, the dashboard shows on the next tick.
86
+
87
+ ## Quickstart
88
+
89
+ ```bash
90
+ npm install -g knitbrain
91
+
92
+ knitbrain profile # your savings, on your transcripts, before you commit to anything
93
+
94
+ # in your project:
95
+ knitbrain setup # detects your platform (Claude Code / Cursor / VS Code / Codex)
96
+ # and writes its NATIVE integration: .mcp.json, slash commands,
97
+ # rules files — non-clobbering
98
+
99
+ knitbrain dashboard # live local dashboard (127.0.0.1:8790)
100
+
101
+ # optional — route LLM requests through the optimizer (API-key setups):
102
+ knitbrain-proxy # listens on 127.0.0.1:8788
103
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:8788
104
+
105
+ # teams — shared optimized sessions (one URL + one token):
106
+ knitbrain hub # start the team hub (host runs this once)
107
+ knitbrain join <hub-url> <token> <name> # everyone else; postings mirror automatically
108
+ ```
109
+
110
+ ## If you pay per token
111
+
112
+ Agent loops re-send the entire conversation on every turn, so input tokens dominate the bill — usually by an order of magnitude over output. That makes context the thing worth optimizing:
113
+
114
+ - **The proxy shrinks the request itself, on the wire.** ~56% fewer context tokens means a proportionally smaller input bill on the bulk of every request, every turn, compounding over a session.
115
+ - **It stacks with provider prompt caching.** CacheAligner keeps the system prefix byte-stable across turns, so cache hits (which providers discount heavily) happen more often instead of breaking on whitespace drift.
116
+ - **It can never make a request more expensive.** The never-expand guard is enforced by tests: output tokens ≤ input tokens, always.
117
+ - **On a subscription instead?** Same mechanics, different currency: fewer tokens per turn means the context window fills slower — fewer compactions, fewer lost-context restarts, longer useful sessions.
118
+
119
+ Run `knitbrain profile` to see the percentage on your own workload before believing any of this.
120
+
121
+ ## Guarantees (enforced by gated tests, not promises)
122
+
123
+ - **Lossless** — every compressed payload recovers byte-for-byte from CCR; the round-trip test gates the build.
124
+ - **Never-expand** — output tokens ≤ input tokens, always.
125
+ - **Governance verbatim** — your instructions and protocol/classification text are never skeletonized.
126
+ - **Local-first** — proxy, hub, and dashboard bind `127.0.0.1` by default; nothing leaves your machine.
127
+ - **Reproducible claims** — every number in this README comes from `knitbrain profile` or `npm run bench`, both of which you can run yourself.
128
+
129
+ ## Development
130
+
131
+ ```bash
132
+ npm install
133
+ npm run verify # typecheck → lint → test → build → consistency → bench (all must pass)
134
+ npm run e2e # built-artifact E2E: stdio session + real-file compression
135
+ npm run audit:prod # cold-start proof: clone → install → pack → installed binaries → all 25 tools
136
+ ```
137
+
138
+ Current proof status: **159 tests passing**, and the production audit (`audit:prod`) passes — fresh clone, clean install, packed tarball installed into a new project, all 25 tools and both binaries verified working. One opt-in test (live LLM endpoint) requires your own API key: `KNITBRAIN_LIVE_TEST=1 ANTHROPIC_API_KEY=… npm test`.
139
+
140
+ ## License
141
+
142
+ MIT © Piyush Dua
@@ -0,0 +1,57 @@
1
+ /**
2
+ * CCR (Compress-Cache-Retrieve) store — the lossless safety net, tiered.
3
+ *
4
+ * Content-addressed: the SHA-256 of the original IS the handle (dedup +
5
+ * integrity). Tiered so disk stays bounded WITHOUT losing anything you might
6
+ * need: HOT (recent, stored as-is) → COLD (gzip-compressed, kept long-term,
7
+ * still fully recoverable) → PURGE (only as a warned last resort, by budget).
8
+ * File existence is authoritative; the manifest holds tiering metadata only.
9
+ */
10
+ export interface CCRStore {
11
+ /** Store an original, return its content-hash handle. Idempotent. */
12
+ put(original: string): string;
13
+ /** Retrieve the exact original by handle (hot or cold). Throws if absent/corrupt. */
14
+ get(handle: string): string;
15
+ /** Whether a handle is present in any tier. */
16
+ has(handle: string): boolean;
17
+ /** Which tier a handle lives in. */
18
+ tierOf(handle: string): "hot" | "cold" | "absent";
19
+ /** Move a handle HOT → COLD (gzip). No-op if not hot. */
20
+ demote(handle: string): void;
21
+ /** Move a handle COLD → HOT (re-warm). No-op if not cold. */
22
+ promote(handle: string): void;
23
+ /** Apply a retention policy: demote stale hot entries, purge over-budget cold. */
24
+ maintain(policy: MaintainPolicy): {
25
+ demoted: number;
26
+ purged: number;
27
+ };
28
+ /** Tier counts. */
29
+ stats(): CCRStats;
30
+ }
31
+ export interface MaintainPolicy {
32
+ /** Demote hot entries whose lastUsed is older than this many ms. */
33
+ hotMaxAgeMs?: number;
34
+ /** Keep at most this many hot entries (demote least-recently-used beyond it). */
35
+ hotMaxEntries?: number;
36
+ /** Keep at most this many cold entries (purge fewest-retrieved/oldest beyond it). */
37
+ coldMaxEntries?: number;
38
+ }
39
+ export interface CCRStats {
40
+ total: number;
41
+ hot: number;
42
+ cold: number;
43
+ }
44
+ /** Thrown when a requested handle is in no tier (purged or never stored). */
45
+ export declare class CCRMissingError extends Error {
46
+ readonly handle: string;
47
+ constructor(handle: string);
48
+ }
49
+ /** Thrown when stored bytes no longer hash to their handle (corruption). */
50
+ export declare class CCRIntegrityError extends Error {
51
+ readonly handle: string;
52
+ constructor(handle: string);
53
+ }
54
+ /** SHA-256 hex digest of a UTF-8 string. */
55
+ export declare function sha256(text: string): string;
56
+ /** Filesystem-backed, tiered CCR store rooted at `root`. */
57
+ export declare function createFileCCRStore(root: string): CCRStore;
@@ -0,0 +1,195 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { gunzipSync, gzipSync } from "node:zlib";
5
+ /** Thrown when a requested handle is in no tier (purged or never stored). */
6
+ export class CCRMissingError extends Error {
7
+ handle;
8
+ constructor(handle) {
9
+ super(`CCR handle not found: ${handle} (purged or never stored — re-run the tool to regenerate)`);
10
+ this.handle = handle;
11
+ this.name = "CCRMissingError";
12
+ }
13
+ }
14
+ /** Thrown when stored bytes no longer hash to their handle (corruption). */
15
+ export class CCRIntegrityError extends Error {
16
+ handle;
17
+ constructor(handle) {
18
+ super(`CCR integrity check failed for handle: ${handle}`);
19
+ this.handle = handle;
20
+ this.name = "CCRIntegrityError";
21
+ }
22
+ }
23
+ /** SHA-256 hex digest of a UTF-8 string. */
24
+ export function sha256(text) {
25
+ return createHash("sha256").update(text, "utf8").digest("hex");
26
+ }
27
+ const HANDLE_RE = /^[0-9a-f]{64}$/;
28
+ /** Filesystem-backed, tiered CCR store rooted at `root`. */
29
+ export function createFileCCRStore(root) {
30
+ const coldDir = join(root, "cold");
31
+ const manifestPath = join(root, "manifest.json");
32
+ mkdirSync(coldDir, { recursive: true });
33
+ const meta = new Map();
34
+ loadManifest();
35
+ function loadManifest() {
36
+ if (!existsSync(manifestPath))
37
+ return;
38
+ try {
39
+ const raw = JSON.parse(readFileSync(manifestPath, "utf8"));
40
+ for (const [k, v] of Object.entries(raw))
41
+ meta.set(k, v);
42
+ }
43
+ catch {
44
+ // corrupt manifest is non-fatal — file existence is authoritative.
45
+ }
46
+ }
47
+ function saveManifest() {
48
+ const obj = {};
49
+ for (const [k, v] of meta.entries())
50
+ obj[k] = v;
51
+ const tmp = join(root, `.manifest.${process.pid}.tmp`);
52
+ writeFileSync(tmp, JSON.stringify(obj), "utf8");
53
+ renameSync(tmp, manifestPath);
54
+ }
55
+ // SECURITY: a handle is ONLY ever a 64-hex SHA-256. Reject anything else
56
+ // before it touches a filesystem path (prevents traversal + existence probes).
57
+ const assertHandle = (h) => {
58
+ if (!HANDLE_RE.test(h))
59
+ throw new CCRMissingError(h);
60
+ };
61
+ const hotPath = (h) => join(root, h);
62
+ const coldPath = (h) => join(coldDir, `${h}.gz`);
63
+ const touch = (h) => {
64
+ const m = meta.get(h) ?? { lastUsed: 0, retrievals: 0 };
65
+ meta.set(h, { lastUsed: Date.now(), retrievals: m.retrievals });
66
+ };
67
+ function writeAtomic(path, data) {
68
+ const tmp = `${path}.${process.pid}.tmp`;
69
+ writeFileSync(tmp, data);
70
+ renameSync(tmp, path);
71
+ }
72
+ function listHot() {
73
+ return readdirSync(root).filter((f) => HANDLE_RE.test(f));
74
+ }
75
+ function listCold() {
76
+ return readdirSync(coldDir)
77
+ .filter((f) => f.endsWith(".gz"))
78
+ .map((f) => f.slice(0, -3))
79
+ .filter((h) => HANDLE_RE.test(h));
80
+ }
81
+ function doDemote(handle) {
82
+ if (!existsSync(hotPath(handle)))
83
+ return false;
84
+ const data = readFileSync(hotPath(handle), "utf8");
85
+ writeAtomic(coldPath(handle), gzipSync(Buffer.from(data, "utf8")));
86
+ rmSync(hotPath(handle), { force: true });
87
+ touch(handle);
88
+ return true;
89
+ }
90
+ return {
91
+ put(original) {
92
+ const handle = sha256(original);
93
+ if (!existsSync(hotPath(handle)) && !existsSync(coldPath(handle))) {
94
+ writeAtomic(hotPath(handle), original);
95
+ }
96
+ meta.set(handle, { lastUsed: Date.now(), retrievals: meta.get(handle)?.retrievals ?? 0 });
97
+ saveManifest();
98
+ return handle;
99
+ },
100
+ has(handle) {
101
+ if (!HANDLE_RE.test(handle))
102
+ return false;
103
+ return existsSync(hotPath(handle)) || existsSync(coldPath(handle));
104
+ },
105
+ tierOf(handle) {
106
+ if (!HANDLE_RE.test(handle))
107
+ return "absent";
108
+ if (existsSync(hotPath(handle)))
109
+ return "hot";
110
+ if (existsSync(coldPath(handle)))
111
+ return "cold";
112
+ return "absent";
113
+ },
114
+ get(handle) {
115
+ assertHandle(handle);
116
+ if (existsSync(hotPath(handle))) {
117
+ const data = readFileSync(hotPath(handle), "utf8");
118
+ if (sha256(data) !== handle)
119
+ throw new CCRIntegrityError(handle);
120
+ const m = meta.get(handle) ?? { lastUsed: 0, retrievals: 0 };
121
+ meta.set(handle, { lastUsed: Date.now(), retrievals: m.retrievals + 1 });
122
+ return data;
123
+ }
124
+ if (existsSync(coldPath(handle))) {
125
+ const data = gunzipSync(readFileSync(coldPath(handle))).toString("utf8");
126
+ if (sha256(data) !== handle)
127
+ throw new CCRIntegrityError(handle);
128
+ const m = meta.get(handle) ?? { lastUsed: 0, retrievals: 0 };
129
+ meta.set(handle, { lastUsed: Date.now(), retrievals: m.retrievals + 1 });
130
+ return data;
131
+ }
132
+ throw new CCRMissingError(handle);
133
+ },
134
+ demote(handle) {
135
+ if (!HANDLE_RE.test(handle))
136
+ return;
137
+ if (doDemote(handle))
138
+ saveManifest();
139
+ },
140
+ promote(handle) {
141
+ if (!HANDLE_RE.test(handle))
142
+ return;
143
+ if (!existsSync(coldPath(handle)))
144
+ return;
145
+ const data = gunzipSync(readFileSync(coldPath(handle))).toString("utf8");
146
+ writeAtomic(hotPath(handle), data);
147
+ rmSync(coldPath(handle), { force: true });
148
+ touch(handle);
149
+ saveManifest();
150
+ },
151
+ maintain(policy) {
152
+ const now = Date.now();
153
+ const lastUsed = (h) => meta.get(h)?.lastUsed ?? 0;
154
+ const retrievals = (h) => meta.get(h)?.retrievals ?? 0;
155
+ // 1) Demote hot → cold (never delete): by age, then by hot-entry cap (LRU).
156
+ const hot = listHot();
157
+ const toDemote = new Set();
158
+ if (policy.hotMaxAgeMs !== undefined) {
159
+ for (const h of hot) {
160
+ if (now - lastUsed(h) > policy.hotMaxAgeMs)
161
+ toDemote.add(h);
162
+ }
163
+ }
164
+ if (policy.hotMaxEntries !== undefined) {
165
+ const survivors = hot.filter((h) => !toDemote.has(h)).sort((a, b) => lastUsed(b) - lastUsed(a));
166
+ for (const h of survivors.slice(policy.hotMaxEntries))
167
+ toDemote.add(h);
168
+ }
169
+ let demoted = 0;
170
+ for (const h of toDemote) {
171
+ if (doDemote(h))
172
+ demoted += 1;
173
+ }
174
+ // 2) Purge cold (last resort) by budget: fewest retrievals, then oldest.
175
+ let purged = 0;
176
+ if (policy.coldMaxEntries !== undefined) {
177
+ const cold = listCold().sort((a, b) => retrievals(a) - retrievals(b) || lastUsed(a) - lastUsed(b));
178
+ for (const h of cold.slice(policy.coldMaxEntries)) {
179
+ rmSync(coldPath(h), { force: true });
180
+ meta.delete(h);
181
+ purged += 1;
182
+ }
183
+ }
184
+ if (demoted > 0 || purged > 0)
185
+ saveManifest();
186
+ return { demoted, purged };
187
+ },
188
+ stats() {
189
+ const hot = listHot().length;
190
+ const cold = listCold().length;
191
+ return { total: hot + cold, hot, cold };
192
+ },
193
+ };
194
+ }
195
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/ccr/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type Server } from "node:http";
2
+ import type { CCRStore } from "./ccr/store.js";
3
+ import type { Knowledge } from "./engine/knowledge.js";
4
+ import type { Memory } from "./engine/memory.js";
5
+ import type { Feedback } from "./engine/feedback.js";
6
+ import type { SkillsStore } from "./engine/skills.js";
7
+ import type { TeamBoard } from "./engine/teams.js";
8
+ import type { Meter } from "./engine/meter.js";
9
+ export interface DashboardDeps {
10
+ ccr: CCRStore;
11
+ memory: Memory;
12
+ feedback: Feedback;
13
+ team: TeamBoard;
14
+ meter: Meter;
15
+ /** Optional: project knowledge graph (per-project; absent in global mode). */
16
+ knowledge?: Knowledge;
17
+ /** Optional: skills store. */
18
+ skills?: SkillsStore;
19
+ }
20
+ /** One JSON snapshot of everything the dashboard shows. */
21
+ export declare function dashboardState(deps: DashboardDeps): Record<string, unknown>;
22
+ /** Loopback-only dashboard server: GET / (page), GET /api/state (JSON). */
23
+ export declare function createDashboardServer(deps: DashboardDeps): Server;
@@ -0,0 +1,125 @@
1
+ import { createServer } from "node:http";
2
+ import { fetchHubBoard, loadHubConfig } from "./hub/client.js";
3
+ /** Knowledge-graph summary: file count + the highest-fanout files (blast radius). */
4
+ function knowledgeSummary(k) {
5
+ const files = k.listFiles();
6
+ const fanout = files
7
+ .map((file) => ({ file, dependents: k.queryDependents(file).length }))
8
+ .filter((f) => f.dependents > 0)
9
+ .sort((a, b) => b.dependents - a.dependents)
10
+ .slice(0, 5);
11
+ return { files: files.length, topFanout: fanout };
12
+ }
13
+ /** One JSON snapshot of everything the dashboard shows. */
14
+ export function dashboardState(deps) {
15
+ const meter = deps.meter.read();
16
+ const learnings = deps.memory.listLearnings();
17
+ return {
18
+ meter,
19
+ ccr: deps.ccr.stats(),
20
+ feedback: deps.feedback.stats(),
21
+ board: deps.team.board().map((e) => ({ id: e.id, author: e.author, ts: e.ts, summary: e.summary.slice(0, 200) })),
22
+ learnings: learnings.length,
23
+ recentLearnings: learnings.slice(-5).reverse().map((l) => ({ date: l.date, summary: l.summary.slice(0, 160) })),
24
+ knowledge: deps.knowledge ? knowledgeSummary(deps.knowledge) : null,
25
+ skills: deps.skills
26
+ ? deps.skills.list().map((s) => ({ name: s.name, uses: s.uses, triggers: s.triggers.slice(0, 6), updatedAt: s.updatedAt }))
27
+ : null,
28
+ generatedAt: new Date().toISOString(),
29
+ };
30
+ }
31
+ const PAGE = `<!doctype html>
32
+ <html lang="en"><head><meta charset="utf-8"><title>Knit Brain</title>
33
+ <style>
34
+ :root { color-scheme: dark; }
35
+ body { font: 14px/1.5 ui-monospace, monospace; background: #0d1117; color: #e6edf3; margin: 2rem auto; max-width: 880px; padding: 0 1rem; }
36
+ h1 { font-size: 1.2rem; letter-spacing: .04em; }
37
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: .8rem; margin: 1rem 0; }
38
+ .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: .9rem 1rem; }
39
+ .big { font-size: 1.6rem; font-weight: 700; }
40
+ .label { color: #8b949e; font-size: .75rem; text-transform: uppercase; letter-spacing: .08em; }
41
+ .bar { height: 10px; background: #21262d; border-radius: 5px; overflow: hidden; margin-top: .5rem; }
42
+ .fill { height: 100%; transition: width .4s; }
43
+ .ok { background: #3fb950; } .warn { background: #d29922; } .handoff { background: #f85149; }
44
+ .advice { margin-top: .5rem; color: #8b949e; }
45
+ table { width: 100%; border-collapse: collapse; margin-top: .4rem; }
46
+ td, th { text-align: left; padding: .25rem .5rem; border-bottom: 1px solid #21262d; }
47
+ </style></head><body>
48
+ <h1>🧠 knitbrain — live</h1>
49
+ <div class="grid">
50
+ <div class="card"><div class="label">Context window</div><div class="big" id="pct">–</div>
51
+ <div class="bar"><div class="fill ok" id="fill" style="width:0%"></div></div>
52
+ <div class="advice" id="advice"></div></div>
53
+ <div class="card"><div class="label">Tokens saved (session)</div><div class="big" id="saved">–</div></div>
54
+ <div class="card"><div class="label">CCR store (hot / cold)</div><div class="big" id="ccr">–</div></div>
55
+ <div class="card"><div class="label">Learnings</div><div class="big" id="learnings">–</div></div>
56
+ </div>
57
+ <div class="card"><div class="label">Self-tuning (retrieval rate per kind)</div><table id="fb"></table></div>
58
+ <div class="card" style="margin-top:.8rem"><div class="label">Knowledge graph (top blast radius)</div><div class="advice" id="kfiles"></div><table id="kg"></table></div>
59
+ <div class="card" style="margin-top:.8rem"><div class="label">Skills</div><table id="skills"></table></div>
60
+ <div class="card" style="margin-top:.8rem"><div class="label">Recent learnings</div><table id="recent"></table></div>
61
+ <div class="card" style="margin-top:.8rem"><div class="label">Team board</div><table id="board"></table></div>
62
+ <script>
63
+ async function tick() {
64
+ try {
65
+ const s = await (await fetch("/api/state")).json();
66
+ document.getElementById("pct").textContent = s.meter.usedPct + "%";
67
+ const fill = document.getElementById("fill");
68
+ fill.style.width = Math.min(100, s.meter.usedPct) + "%";
69
+ fill.className = "fill " + s.meter.status;
70
+ document.getElementById("advice").textContent = s.meter.advice;
71
+ document.getElementById("saved").textContent = s.meter.savedTokens.toLocaleString();
72
+ document.getElementById("ccr").textContent = s.ccr.hot + " / " + s.ccr.cold;
73
+ document.getElementById("learnings").textContent = s.learnings;
74
+ document.getElementById("fb").innerHTML = "<tr><th>kind</th><th>compressed</th><th>retrieved</th><th>rate</th><th>state</th></tr>" +
75
+ s.feedback.map(f => \`<tr><td>\${f.kind}</td><td>\${f.compressions}</td><td>\${f.retrievals}</td><td>\${f.rate}</td><td>\${f.skipping ? "backing off" : "active"}</td></tr>\`).join("");
76
+ const esc = (v) => String(v).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");
77
+ if (s.knowledge) {
78
+ document.getElementById("kfiles").textContent = s.knowledge.files + " files indexed";
79
+ document.getElementById("kg").innerHTML = "<tr><th>file</th><th>dependents</th></tr>" +
80
+ (s.knowledge.topFanout.length ? s.knowledge.topFanout.map(f => \`<tr><td>\${esc(f.file)}</td><td>\${f.dependents}</td></tr>\`).join("") : "<tr><td colspan=2>— run a scan —</td></tr>");
81
+ } else {
82
+ document.getElementById("kfiles").textContent = "no project scope (start the dashboard inside a project)";
83
+ }
84
+ document.getElementById("skills").innerHTML = "<tr><th>skill</th><th>uses</th><th>triggers</th><th>updated</th></tr>" +
85
+ (s.skills && s.skills.length ? s.skills.map(k => \`<tr><td>\${esc(k.name)}</td><td>\${k.uses}</td><td>\${esc(k.triggers.join(", "))}</td><td>\${esc(k.updatedAt.slice(0,10))}</td></tr>\`).join("") : "<tr><td colspan=4>—</td></tr>");
86
+ document.getElementById("recent").innerHTML = "<tr><th>date</th><th>learning</th></tr>" +
87
+ (s.recentLearnings.length ? s.recentLearnings.map(l => \`<tr><td>\${esc(l.date)}</td><td>\${esc(l.summary)}</td></tr>\`).join("") : "<tr><td colspan=2>—</td></tr>");
88
+ document.getElementById("board").innerHTML = "<tr><th>who</th><th>when</th><th>finding</th></tr>" +
89
+ (s.board.length ? s.board.map(b => \`<tr><td>\${esc(b.author)}</td><td>\${esc(b.ts.slice(11,19))}</td><td>\${esc(b.summary)}</td></tr>\`).join("") : "<tr><td colspan=3>—</td></tr>");
90
+ } catch {}
91
+ }
92
+ tick(); setInterval(tick, 2000);
93
+ </script></body></html>`;
94
+ /** Loopback-only dashboard server: GET / (page), GET /api/state (JSON). */
95
+ export function createDashboardServer(deps) {
96
+ return createServer((req, res) => {
97
+ if (req.url === "/api/state") {
98
+ void (async () => {
99
+ const state = dashboardState(deps);
100
+ // COMMON view: merge the team hub's board (best-effort, never blocks long).
101
+ const hub = loadHubConfig();
102
+ if (hub) {
103
+ const remote = await fetchHubBoard(hub);
104
+ const local = state["board"];
105
+ const seen = new Set(local.map((e) => e.id));
106
+ state["board"] = [
107
+ ...local,
108
+ ...remote.filter((e) => !seen.has(e.id)).map((e) => ({ ...e, author: `${e.author} (hub)` })),
109
+ ];
110
+ }
111
+ res.writeHead(200, { "content-type": "application/json" });
112
+ res.end(JSON.stringify(state));
113
+ })();
114
+ return;
115
+ }
116
+ if (req.url === "/" || req.url === "/index.html") {
117
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
118
+ res.end(PAGE);
119
+ return;
120
+ }
121
+ res.writeHead(404, { "content-type": "application/json" });
122
+ res.end(JSON.stringify({ error: "not found" }));
123
+ });
124
+ }
125
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAqB/D,qFAAqF;AACrF,SAAS,gBAAgB,CAAC,CAAY;IACpC,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,KAAK;SACjB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;SACrE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC;SAC/B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;SAC3C,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACf,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AACpD,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,cAAc,CAAC,IAAmB;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAChC,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;IAC9C,OAAO;QACL,KAAK;QACL,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE;QAC/B,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACjH,SAAS,EAAE,SAAS,CAAC,MAAM;QAC3B,eAAe,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/G,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI;QACnE,MAAM,EAAE,IAAI,CAAC,MAAM;YACjB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;YAC3H,CAAC,CAAC,IAAI;QACR,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACtC,CAAC;AACJ,CAAC;AAED,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA8DW,CAAC;AAEzB,2EAA2E;AAC3E,MAAM,UAAU,qBAAqB,CAAC,IAAmB;IACvD,OAAO,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC/B,IAAI,GAAG,CAAC,GAAG,KAAK,YAAY,EAAE,CAAC;YAC7B,KAAK,CAAC,KAAK,IAAI,EAAE;gBACf,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;gBACnC,4EAA4E;gBAC5E,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;gBAC5B,IAAI,GAAG,EAAE,CAAC;oBACR,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;oBACxC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAA0B,CAAC;oBACtD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBAC7C,KAAK,CAAC,OAAO,CAAC,GAAG;wBACf,GAAG,KAAK;wBACR,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,QAAQ,EAAE,CAAC,CAAC;qBAC7F,CAAC;gBACJ,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YACjC,CAAC,CAAC,EAAE,CAAC;YACL,OAAO;QACT,CAAC;QACD,IAAI,GAAG,CAAC,GAAG,KAAK,GAAG,IAAI,GAAG,CAAC,GAAG,KAAK,aAAa,EAAE,CAAC;YACjD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;YACnE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACd,OAAO;QACT,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,36 @@
1
+ export interface DomainProposal {
2
+ /** Short domain name (e.g. "proxy"). */
3
+ name: string;
4
+ /** Glob the agent is scoped to (guardrail #1). */
5
+ scope: string;
6
+ /** Files detected in the domain. */
7
+ files: string[];
8
+ /** Suggested tool allowlist (guardrail #2). */
9
+ tools: string[];
10
+ /** Whether a review/verify gate is recommended (guardrail #3). */
11
+ reviewGate: boolean;
12
+ /** Suggested context-token budget (guardrail #4). */
13
+ contextBudget: number;
14
+ }
15
+ export interface AgentSpec {
16
+ name: string;
17
+ description?: string;
18
+ scope?: string;
19
+ tools?: string[];
20
+ reviewGate?: boolean;
21
+ contextBudget?: number;
22
+ }
23
+ /**
24
+ * Auto-detect candidate domain agents from the knowledge graph: group source
25
+ * files by their directory; each directory with ≥2 files becomes a proposal
26
+ * with sensible, project-specific guardrails. The agent then interviews the
27
+ * user to confirm/edit before create_agent writes them.
28
+ */
29
+ export declare function proposeAgents(files: string[]): DomainProposal[];
30
+ /**
31
+ * Render a project-specific subagent definition with all four guardrails baked
32
+ * in: file/domain scope, allowed-tools allowlist, review gate, context budget.
33
+ */
34
+ export declare function generateAgentMarkdown(spec: AgentSpec): string;
35
+ /** Write a generated agent to the project's .claude/agents directory. */
36
+ export declare function writeAgent(projectRoot: string, spec: AgentSpec): string;