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
@@ -0,0 +1,137 @@
1
+ import { isJson, compressJson } from "./json.js";
2
+ import { isCode, compressCode } from "./code.js";
3
+ import { compressCodeAst } from "./ast.js";
4
+ import { compressText, compressShortProse } from "./text.js";
5
+ import { countTokens } from "../tokenizer.js";
6
+ /**
7
+ * Minimum token saving (%) for compression to be kept. Below this we pass the
8
+ * original through unchanged — the optimizer must NEVER make a payload larger.
9
+ */
10
+ const MIN_SAVING_PCT = 5;
11
+ /** Anchor fallback applies to outputs at least this many lines long. */
12
+ const ANCHOR_MIN_LINES = 40;
13
+ /** If structural compression saves less than this %, try the anchor. */
14
+ const ANCHOR_TRIGGER_PCT = 35;
15
+ const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
16
+ /** Lines worth rescuing from an elided middle (failures live mid-output). */
17
+ const IMPORTANT_LINE = /\b(FAIL|FAILED|✗|Error:|error TS\d+|Exception|Traceback|fatal:|panic:)\b/;
18
+ const MAX_RESCUED = 12;
19
+ /**
20
+ * Anchor elision — the universal fallback for long low-structure output
21
+ * (build logs, test runs, prose, mixed snippets): keep the head (intro/
22
+ * context) and tail (summaries/errors land at the end), rescue failure lines
23
+ * from the middle, elide the rest to a counted marker. Head/tail scale with
24
+ * length. Profiled on 69 real transcripts: these shapes are ~70% of burn.
25
+ * Lossless via CCR; TOIN backs off any shape that gets over-retrieved.
26
+ */
27
+ function anchorSkeleton(text, handle) {
28
+ const lines = text.split("\n");
29
+ const headN = clamp(Math.round(lines.length * 0.15), 8, 25);
30
+ const tailN = clamp(Math.round(lines.length * 0.12), 6, 18);
31
+ const middle = lines.slice(headN, lines.length - tailN);
32
+ const rescued = middle.filter((l) => IMPORTANT_LINE.test(l)).slice(0, MAX_RESCUED);
33
+ const head = lines.slice(0, headN).join("\n");
34
+ const tail = lines.slice(-tailN).join("\n");
35
+ const rescueBlock = rescued.length > 0 ? `\n${rescued.join("\n")}` : "";
36
+ return `${head}\n⟪… ${middle.length - rescued.length} lines elided · exact original: ⟨ccr:${handle}⟩ …⟫${rescueBlock}\n${tail}`;
37
+ }
38
+ /** Deterministic content-type detection (no ML). JSON is strict; code is heuristic. */
39
+ export function detect(text) {
40
+ if (isJson(text))
41
+ return "json";
42
+ if (isCode(text))
43
+ return "code";
44
+ return "text";
45
+ }
46
+ /** Claude-Code Read format: lines prefixed ` 123→content`. */
47
+ const LINE_NUM_RE = /^\s{0,8}\d+→/;
48
+ /**
49
+ * Detect host-Read line numbering (the dominant in-session tool-result shape;
50
+ * the prefixes defeat code parsing if left in). Returns the stripped content
51
+ * when ≥60% of lines carry the prefix, else null.
52
+ */
53
+ export function stripLineNumbers(text) {
54
+ const lines = text.split("\n");
55
+ if (lines.length < 10)
56
+ return null;
57
+ let prefixed = 0;
58
+ for (const l of lines)
59
+ if (LINE_NUM_RE.test(l))
60
+ prefixed += 1;
61
+ if (prefixed < lines.length * 0.6)
62
+ return null;
63
+ return lines.map((l) => l.replace(LINE_NUM_RE, "")).join("\n");
64
+ }
65
+ export function compress(text, ccr, options = {}) {
66
+ // Host-Read line numbering (` 123→…`) defeats structural parsing — strip
67
+ // it, compress the underlying content, but store the TRUE original (with
68
+ // numbers) in CCR and point the skeleton's handle at it. Lossless.
69
+ const stripped = stripLineNumbers(text);
70
+ // Code routes through the tree-sitter AST handler when its WASM parsers are
71
+ // warm (lazy background init), else the heuristic brace scanner.
72
+ const allowProse = options.allowProse ?? true;
73
+ const byType = (t, type) => {
74
+ if (type === "json")
75
+ return compressJson(t, ccr);
76
+ if (type === "code")
77
+ return compressCodeAst(t, ccr) ?? compressCode(t, ccr);
78
+ // Short prose (too few lines for the anchor fallback): try the TOIN-gated
79
+ // sentence anchor; longer or sentence-poor text takes the line handler.
80
+ if (allowProse && t.split("\n").length < ANCHOR_MIN_LINES) {
81
+ const prose = compressShortProse(t, ccr);
82
+ if (prose !== null)
83
+ return prose;
84
+ }
85
+ return compressText(t, ccr);
86
+ };
87
+ let contentType;
88
+ let skeleton;
89
+ let handle;
90
+ if (stripped !== null) {
91
+ const inner = byType(stripped, detect(stripped));
92
+ contentType = inner.contentType; // handler may refine (e.g., text → prose)
93
+ handle = ccr.put(text);
94
+ skeleton = inner.skeleton.replace(/⟨ccr:[0-9a-f]{64}⟩/g, `⟨ccr:${handle}⟩`);
95
+ }
96
+ else {
97
+ const result = byType(text, detect(text));
98
+ contentType = result.contentType;
99
+ skeleton = result.skeleton;
100
+ handle = result.handle;
101
+ }
102
+ const originalTokens = countTokens(text);
103
+ let skeletonTokens = countTokens(skeleton);
104
+ let savedPct = originalTokens === 0
105
+ ? 0
106
+ : Math.round((1 - skeletonTokens / originalTokens) * 1000) / 10;
107
+ // ANCHOR FALLBACK: long output where structure didn't pay → head+tail keep.
108
+ const lineCount = text.split("\n").length;
109
+ if (savedPct < ANCHOR_TRIGGER_PCT && lineCount >= ANCHOR_MIN_LINES) {
110
+ const anchorHandle = handle || ccr.put(text);
111
+ const anchored = anchorSkeleton(text, anchorHandle);
112
+ const anchoredTokens = countTokens(anchored);
113
+ if (anchoredTokens < skeletonTokens) {
114
+ skeleton = anchored;
115
+ handle = anchorHandle;
116
+ skeletonTokens = anchoredTokens;
117
+ savedPct =
118
+ originalTokens === 0
119
+ ? 0
120
+ : Math.round((1 - skeletonTokens / originalTokens) * 1000) / 10;
121
+ }
122
+ }
123
+ // NEVER-EXPAND GUARD: if compression isn't meaningfully smaller, pass through.
124
+ if (savedPct < MIN_SAVING_PCT) {
125
+ return {
126
+ skeleton: text,
127
+ handle: "",
128
+ contentType,
129
+ compressed: false,
130
+ originalTokens,
131
+ skeletonTokens: originalTokens,
132
+ savedPct: 0,
133
+ };
134
+ }
135
+ return { skeleton, handle, contentType, compressed: true, originalTokens, skeletonTokens, savedPct };
136
+ }
137
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/optimizer/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { CCRStore } from "../ccr/store.js";
2
+ import type { CompressResult } from "./types.js";
3
+ /**
4
+ * Text/log handler. Two passes:
5
+ * 1. line dedup — logs and command output repeat heavily; identical lines
6
+ * collapse to one occurrence with a ×N count (order of first appearance
7
+ * kept, so the narrative still reads).
8
+ * 2. whitespace normalize — trailing spaces + blank-line runs.
9
+ * The router's never-expand guard ensures this is never a net loss, and the
10
+ * pristine original is always in CCR.
11
+ */
12
+ export declare function compressText(original: string, ccr: CCRStore): CompressResult;
13
+ /**
14
+ * Short-prose sentence anchor — for prose too short for the line-based anchor
15
+ * (under ~40 lines) but still sentence-rich: keep the opening (topic/intent)
16
+ * and closing (conclusion) sentences, elide the middle to a counted marker.
17
+ * Prose is information-dense, so callers gate this behind TOIN ("prose" kind):
18
+ * if agents keep paging the originals back, it backs off automatically.
19
+ * Returns null when there aren't enough sentences to anchor.
20
+ */
21
+ export declare function compressShortProse(original: string, ccr: CCRStore): CompressResult | null;
@@ -0,0 +1,88 @@
1
+ /** Below this many lines, line-dedup isn't worth attempting. */
2
+ const MIN_LINES_FOR_DEDUP = 20;
3
+ /**
4
+ * Text/log handler. Two passes:
5
+ * 1. line dedup — logs and command output repeat heavily; identical lines
6
+ * collapse to one occurrence with a ×N count (order of first appearance
7
+ * kept, so the narrative still reads).
8
+ * 2. whitespace normalize — trailing spaces + blank-line runs.
9
+ * The router's never-expand guard ensures this is never a net loss, and the
10
+ * pristine original is always in CCR.
11
+ */
12
+ export function compressText(original, ccr) {
13
+ const handle = ccr.put(original);
14
+ const lines = original.split("\n");
15
+ // Near-duplicate grouping: logs repeat the same TEMPLATE with volatile
16
+ // bits (timestamps, counters, ids, hashes). Normalize those away for
17
+ // grouping; the kept representative is the first real occurrence.
18
+ const normalize = (l) => l
19
+ .replace(/\d{4}-\d{2}-\d{2}[T ][\d:.]+Z?/g, "⟪ts⟫")
20
+ .replace(/\b[0-9a-f]{7,64}\b/g, "⟪hex⟫")
21
+ .replace(/\d+/g, "⟪n⟫");
22
+ let body;
23
+ if (lines.length >= MIN_LINES_FOR_DEDUP) {
24
+ const counts = new Map();
25
+ const firstSeen = new Map();
26
+ const order = [];
27
+ for (const line of lines) {
28
+ const key = normalize(line);
29
+ const seen = counts.get(key);
30
+ if (seen === undefined) {
31
+ counts.set(key, 1);
32
+ firstSeen.set(key, line);
33
+ order.push(key);
34
+ }
35
+ else {
36
+ counts.set(key, seen + 1);
37
+ }
38
+ }
39
+ // Only restructure when repetition is substantial (≥25% duplicate templates).
40
+ if (order.length <= lines.length * 0.75) {
41
+ body = order
42
+ .map((key) => {
43
+ const n = counts.get(key);
44
+ const line = firstSeen.get(key);
45
+ return n > 1 && line.trim().length > 0 ? `${line} ⟪×${n} similar⟫` : line;
46
+ })
47
+ .join("\n");
48
+ body += `\n⟪${lines.length} lines → ${order.length} unique templates · exact original: ccr⟫`;
49
+ }
50
+ else {
51
+ body = original;
52
+ }
53
+ }
54
+ else {
55
+ body = original;
56
+ }
57
+ const normalized = body.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n");
58
+ const skeleton = normalized === original ? original : `${normalized}\n⟨ccr:${handle}⟩`;
59
+ return { skeleton, handle, contentType: "text" };
60
+ }
61
+ /** Sentence boundary: terminator, whitespace, then a plausible sentence opener. */
62
+ const SENTENCE_SPLIT = /(?<=[.!?:])\s+(?=[A-Z0-9⟪`"'*-])/;
63
+ /** Sentence-anchor needs at least this many sentences to be worth it.
64
+ * The inline 64-hex handle costs ~45 tokens, so the kept set must stay small
65
+ * for the elision to clear the never-expand guard on typical short blocks. */
66
+ const MIN_SENTENCES = 8;
67
+ const HEAD_SENTENCES = 2;
68
+ const TAIL_SENTENCES = 1;
69
+ /**
70
+ * Short-prose sentence anchor — for prose too short for the line-based anchor
71
+ * (under ~40 lines) but still sentence-rich: keep the opening (topic/intent)
72
+ * and closing (conclusion) sentences, elide the middle to a counted marker.
73
+ * Prose is information-dense, so callers gate this behind TOIN ("prose" kind):
74
+ * if agents keep paging the originals back, it backs off automatically.
75
+ * Returns null when there aren't enough sentences to anchor.
76
+ */
77
+ export function compressShortProse(original, ccr) {
78
+ const sentences = original.split(SENTENCE_SPLIT);
79
+ if (sentences.length < MIN_SENTENCES)
80
+ return null;
81
+ const head = sentences.slice(0, HEAD_SENTENCES).join(" ");
82
+ const tail = sentences.slice(-TAIL_SENTENCES).join(" ");
83
+ const elided = sentences.length - HEAD_SENTENCES - TAIL_SENTENCES;
84
+ const handle = ccr.put(original);
85
+ const skeleton = `${head}\n⟪… ${elided} sentences elided · exact original: ⟨ccr:${handle}⟩ …⟫\n${tail}`;
86
+ return { skeleton, handle, contentType: "prose" };
87
+ }
88
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/optimizer/text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /** Content categories the router can detect and the handlers produce.
2
+ * "prose" is produced (not detected): short prose that took the sentence
3
+ * anchor — its own TOIN kind so over-retrieval backs it off independently. */
4
+ export type ContentType = "json" | "code" | "text" | "prose";
5
+ /** Output of compressing one payload: the skeleton view + its CCR handle. */
6
+ export interface CompressResult {
7
+ /** The compressed, still-readable skeleton (carries the ⟨ccr:…⟩ handle). */
8
+ readonly skeleton: string;
9
+ /** Full CCR handle for byte-for-byte recovery of the original. */
10
+ readonly handle: string;
11
+ /** Detected content type. */
12
+ readonly contentType: ContentType;
13
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/optimizer/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ /** Knit Brain's local-first home. Override with KNITBRAIN_HOME (used in tests). */
2
+ export declare function knitbrainHome(): string;
3
+ /** Root directory for the (global, content-addressed) CCR store. */
4
+ export declare function ccrRoot(): string;
5
+ /** Stable per-project id derived from the working directory. */
6
+ export declare function projectId(): string;
7
+ /** Per-project memory directory (learnings + sessions). */
8
+ export declare function memoryRoot(): string;
9
+ /** Per-project knowledge-graph cache directory. */
10
+ export declare function knowledgeRoot(): string;
11
+ /** Per-project TOIN feedback directory (compression self-tuning). */
12
+ export declare function feedbackRoot(): string;
13
+ /** Per-project team board directory (shared compressed context). */
14
+ export declare function teamRoot(): string;
15
+ /** Per-project context-meter directory (token-window tracking). */
16
+ export declare function meterRoot(): string;
17
+ /** Per-project skills store (find-or-write playbooks). */
18
+ export declare function skillsRoot(): string;
19
+ /** Per-project classifier-calibration directory (false-positive loop). */
20
+ export declare function calibrationRoot(): string;
package/dist/paths.js ADDED
@@ -0,0 +1,44 @@
1
+ import { createHash } from "node:crypto";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ /** Knit Brain's local-first home. Override with KNITBRAIN_HOME (used in tests). */
5
+ export function knitbrainHome() {
6
+ return process.env["KNITBRAIN_HOME"] ?? join(homedir(), ".knitbrain");
7
+ }
8
+ /** Root directory for the (global, content-addressed) CCR store. */
9
+ export function ccrRoot() {
10
+ return join(knitbrainHome(), "ccr");
11
+ }
12
+ /** Stable per-project id derived from the working directory. */
13
+ export function projectId() {
14
+ return createHash("sha256").update(process.cwd()).digest("hex").slice(0, 16);
15
+ }
16
+ /** Per-project memory directory (learnings + sessions). */
17
+ export function memoryRoot() {
18
+ return join(knitbrainHome(), "projects", projectId(), "memory");
19
+ }
20
+ /** Per-project knowledge-graph cache directory. */
21
+ export function knowledgeRoot() {
22
+ return join(knitbrainHome(), "projects", projectId(), "knowledge");
23
+ }
24
+ /** Per-project TOIN feedback directory (compression self-tuning). */
25
+ export function feedbackRoot() {
26
+ return join(knitbrainHome(), "projects", projectId(), "feedback");
27
+ }
28
+ /** Per-project team board directory (shared compressed context). */
29
+ export function teamRoot() {
30
+ return join(knitbrainHome(), "projects", projectId(), "team");
31
+ }
32
+ /** Per-project context-meter directory (token-window tracking). */
33
+ export function meterRoot() {
34
+ return join(knitbrainHome(), "projects", projectId(), "meter");
35
+ }
36
+ /** Per-project skills store (find-or-write playbooks). */
37
+ export function skillsRoot() {
38
+ return join(knitbrainHome(), "projects", projectId(), "skills");
39
+ }
40
+ /** Per-project classifier-calibration directory (false-positive loop). */
41
+ export function calibrationRoot() {
42
+ return join(knitbrainHome(), "projects", projectId(), "calibration");
43
+ }
44
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,mFAAmF;AACnF,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AACxE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,OAAO;IACrB,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,SAAS;IACvB,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,QAAQ,CAAC,CAAC;AAClE,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,WAAW,CAAC,CAAC;AACrE,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AACpE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,QAAQ;IACtB,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAChE,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,SAAS;IACvB,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;AACjE,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,QAAQ,CAAC,CAAC;AAClE,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,aAAa,CAAC,CAAC;AACvE,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { SetupConfig } from "./setup.js";
2
+ /**
3
+ * Platform adapter matrix — each coding platform gets its NATIVE integration
4
+ * artifacts, not a lowest-common-denominator. The MCP server is the universal
5
+ * core; adapters add what each platform uniquely supports (slash commands,
6
+ * rules files, config shapes). The proxy is only suggested where the platform
7
+ * supports a base-URL override.
8
+ */
9
+ export interface Artifact {
10
+ /** Path relative to the project root. */
11
+ path: string;
12
+ content: string;
13
+ /** Merge strategy: json-merges for shared config files, write for ours. */
14
+ mode: "write" | "json-merge-mcp" | "json-merge-hooks";
15
+ }
16
+ /** Hook wiring for Claude Code settings.json (Layer 2 enforcement). */
17
+ export declare const KNITBRAIN_HOOKS: {
18
+ readonly PreToolUse: readonly [{
19
+ readonly matcher: "Read";
20
+ readonly hooks: readonly [{
21
+ readonly type: "command";
22
+ readonly command: "knitbrain-hook pretooluse";
23
+ }];
24
+ }];
25
+ readonly PreCompact: readonly [{
26
+ readonly matcher: "";
27
+ readonly hooks: readonly [{
28
+ readonly type: "command";
29
+ readonly command: "knitbrain-hook precompact";
30
+ }];
31
+ }];
32
+ };
33
+ /** Claude Code: .mcp.json + native slash commands. */
34
+ export declare function claudeArtifacts(cfg: SetupConfig): Artifact[];
35
+ /** Cursor: .cursor/mcp.json + an always-on rules file. */
36
+ export declare function cursorArtifacts(): Artifact[];
37
+ /** VS Code (Copilot): .vscode/mcp.json (uses "servers" key) + instructions. */
38
+ export declare function vscodeArtifacts(): Artifact[];
39
+ /** Codex CLI: global config — print a snippet rather than touching ~/.codex. */
40
+ export declare function codexSnippet(cfg: SetupConfig): string;
41
+ /**
42
+ * Slash-command registry — what the AGENT can run itself on each platform.
43
+ * Surfaced by knitbrain_run so the loop is autonomous: knitbrain says which
44
+ * host command to invoke, the agent invokes it.
45
+ */
46
+ export declare function slashCommands(platform: string): Array<{
47
+ cmd: string;
48
+ when: string;
49
+ }>;
50
+ /** Apply an artifact list into a project, non-clobbering. Returns written paths. */
51
+ export declare function applyArtifacts(root: string, artifacts: Artifact[], cfg: SetupConfig): string[];
@@ -0,0 +1,157 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ /** Hook wiring for Claude Code settings.json (Layer 2 enforcement). */
4
+ export const KNITBRAIN_HOOKS = {
5
+ PreToolUse: [
6
+ {
7
+ matcher: "Read",
8
+ hooks: [{ type: "command", command: "knitbrain-hook pretooluse" }],
9
+ },
10
+ ],
11
+ PreCompact: [
12
+ {
13
+ matcher: "",
14
+ hooks: [{ type: "command", command: "knitbrain-hook precompact" }],
15
+ },
16
+ ],
17
+ };
18
+ const NOTATION_GUIDE = `Knit Brain compresses large tool outputs into skeletons. A \`⟨ccr:HASH⟩\` marker means the exact original is stored locally — call the \`knitbrain_retrieve\` tool with that hash to read it byte-for-byte. Check \`knitbrain_context_meter\` periodically; when it says to, save a handoff with \`knitbrain_save_handoff\` and start a fresh session (\`knitbrain_load_session\` restores everything). When the user states a task, call \`knitbrain_run\` first and follow its directive (skill + agents + commands).
19
+
20
+ **Reading files:** for any file you expect to be large (>~150 lines) or that you only need to navigate (find a function, check structure), use \`knitbrain_read\` instead of the host's raw read — same information shape at ~70-90% fewer tokens, exact original one \`knitbrain_retrieve\` away. Use the raw read only when you need every line verbatim right now (e.g. just before editing a specific region).`;
21
+ /**
22
+ * Terse mode — output-side token optimization (the input side is the
23
+ * optimizer/CCR). Answer telegraphically: same technical content, far fewer
24
+ * tokens. Levels mirror common practice (lite/full/ultra).
25
+ */
26
+ const TERSE_MODE = `## Terse mode (output tokens)
27
+
28
+ Answer terse. Same facts, fewer words:
29
+ - Drop filler, pleasantries, hedging ("I'd be happy to", "it seems that", "you might want to consider").
30
+ - Drop articles where meaning survives. Fragments OK.
31
+ - Tables/bullets over prose. Code over description.
32
+ - Never drop: technical content, numbers, file paths, caveats that change decisions.
33
+ - Levels: lite = drop filler only · full (default) = fragments OK · ultra = bare telegraphic.
34
+ - User says "verbose"/"explain fully" → switch off for that answer.
35
+
36
+ Example — verbose: "The reason your component re-renders is likely that you're creating a new object reference on each render; consider useMemo."
37
+ Terse: "New object ref each render → re-render. Wrap in useMemo."`;
38
+ /** Claude Code: .mcp.json + native slash commands. */
39
+ export function claudeArtifacts(cfg) {
40
+ return [
41
+ { path: ".mcp.json", content: "", mode: "json-merge-mcp" },
42
+ { path: ".claude/settings.json", content: "", mode: "json-merge-hooks" },
43
+ {
44
+ path: ".claude/commands/meter.md",
45
+ mode: "write",
46
+ content: `---\ndescription: Show the Knit Brain context-window meter (usage %, tokens saved, handoff advice)\n---\n\nCall the \`knitbrain_context_meter\` tool and present the reading clearly: usage %, tokens saved this session, and the advice line. If status is "handoff", follow the advice now.\n`,
47
+ },
48
+ {
49
+ path: ".claude/commands/handoff.md",
50
+ mode: "write",
51
+ content: `---\ndescription: Save a Knit Brain session handoff (goal, state, next steps) so a fresh session resumes cleanly\n---\n\nSummarize the current goal, completed work, in-flight files, and concrete next steps, then call \`knitbrain_save_handoff\` with that summary as \`state\`. Confirm to the user that a fresh session will resume via \`knitbrain_load_session\`.\n`,
52
+ },
53
+ {
54
+ path: ".claude/rules/knitbrain.md",
55
+ mode: "write",
56
+ content: `# Knit Brain\n\n${NOTATION_GUIDE}\n\n${TERSE_MODE}\n\nProxy (optional, API-key setups): start \`knitbrain-proxy\` and set \`ANTHROPIC_BASE_URL=${cfg.proxyEnv["ANTHROPIC_BASE_URL"]}\`.\n`,
57
+ },
58
+ ];
59
+ }
60
+ /** Cursor: .cursor/mcp.json + an always-on rules file. */
61
+ export function cursorArtifacts() {
62
+ return [
63
+ { path: ".cursor/mcp.json", content: "", mode: "json-merge-mcp" },
64
+ {
65
+ path: ".cursor/rules/knitbrain.mdc",
66
+ mode: "write",
67
+ content: `---\ndescription: Knit Brain memory + token optimization\nalwaysApply: true\n---\n\n${NOTATION_GUIDE}\n\n${TERSE_MODE}\n`,
68
+ },
69
+ ];
70
+ }
71
+ /** VS Code (Copilot): .vscode/mcp.json (uses "servers" key) + instructions. */
72
+ export function vscodeArtifacts() {
73
+ return [
74
+ { path: ".vscode/mcp.json", content: "", mode: "json-merge-mcp" },
75
+ {
76
+ path: ".github/instructions/knitbrain.instructions.md",
77
+ mode: "write",
78
+ content: `---\napplyTo: "**"\n---\n\n${NOTATION_GUIDE}\n\n${TERSE_MODE}\n`,
79
+ },
80
+ ];
81
+ }
82
+ /** Codex CLI: global config — print a snippet rather than touching ~/.codex. */
83
+ export function codexSnippet(cfg) {
84
+ return [
85
+ "# Add to ~/.codex/config.toml :",
86
+ "[mcp_servers.knitbrain]",
87
+ 'command = "knitbrain"',
88
+ "",
89
+ "# Optional proxy (Codex supports base-URL override):",
90
+ `# export OPENAI_BASE_URL=${cfg.proxyEnv["OPENAI_BASE_URL"]}`,
91
+ ].join("\n");
92
+ }
93
+ /**
94
+ * Slash-command registry — what the AGENT can run itself on each platform.
95
+ * Surfaced by knitbrain_run so the loop is autonomous: knitbrain says which
96
+ * host command to invoke, the agent invokes it.
97
+ */
98
+ export function slashCommands(platform) {
99
+ if (platform === "claude-code") {
100
+ return [
101
+ { cmd: "/meter", when: "check context window (knitbrain meter)" },
102
+ { cmd: "/handoff", when: "save session handoff before clearing" },
103
+ { cmd: "/clear", when: "after handoff saved — start fresh window" },
104
+ { cmd: "/compact", when: "mid-task when window warn but can't clear yet" },
105
+ ];
106
+ }
107
+ if (platform === "cursor") {
108
+ return [{ cmd: "@knitbrain rules", when: "re-read knitbrain usage rules" }];
109
+ }
110
+ return [];
111
+ }
112
+ /** VS Code's mcp.json uses {"servers": …}; everyone else uses {"mcpServers": …}. */
113
+ function mcpKeyFor(path) {
114
+ return path.startsWith(".vscode/") ? "servers" : "mcpServers";
115
+ }
116
+ /** Apply an artifact list into a project, non-clobbering. Returns written paths. */
117
+ export function applyArtifacts(root, artifacts, cfg) {
118
+ const written = [];
119
+ for (const a of artifacts) {
120
+ const full = join(root, a.path);
121
+ mkdirSync(dirname(full), { recursive: true });
122
+ let content = a.content;
123
+ if (a.mode === "json-merge-mcp" || a.mode === "json-merge-hooks") {
124
+ let parsed = {};
125
+ if (existsSync(full)) {
126
+ try {
127
+ parsed = JSON.parse(readFileSync(full, "utf8"));
128
+ }
129
+ catch {
130
+ parsed = {};
131
+ }
132
+ }
133
+ if (a.mode === "json-merge-mcp") {
134
+ const key = mcpKeyFor(a.path);
135
+ const servers = { ...(parsed[key] ?? {}), ...cfg.mcpServers };
136
+ content = JSON.stringify({ ...parsed, [key]: servers }, null, 2) + "\n";
137
+ }
138
+ else {
139
+ // Merge our hook entries into existing hooks, deduped by command —
140
+ // never clobber the user's own hooks.
141
+ const hooks = { ...(parsed["hooks"] ?? {}) };
142
+ for (const [event, entries] of Object.entries(KNITBRAIN_HOOKS)) {
143
+ const existing = (hooks[event] ?? []);
144
+ const ours = entries.filter((e) => !existing.some((x) => x.hooks?.some((h) => h.command === e.hooks[0].command)));
145
+ hooks[event] = [...existing, ...ours];
146
+ }
147
+ content = JSON.stringify({ ...parsed, hooks }, null, 2) + "\n";
148
+ }
149
+ }
150
+ const tmp = `${full}.${process.pid}.tmp`;
151
+ writeFileSync(tmp, content, "utf8");
152
+ renameSync(tmp, full);
153
+ written.push(a.path);
154
+ }
155
+ return written;
156
+ }
157
+ //# sourceMappingURL=platforms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platforms.js","sourceRoot":"","sources":["../src/platforms.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function classifyShape(t: string): string;
2
+ /** Run the profile and print the report. Returns the overall saved percent. */
3
+ export declare function runProfile(args: string[], log?: (line: string) => void): Promise<number>;