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,40 @@
1
+ /**
2
+ * Context-window meter — tracks how much of the model's context window the
3
+ * session has consumed and tells the agent (and the dashboard) when it's time
4
+ * to save a handoff and clear.
5
+ *
6
+ * Sources: the proxy reports the OPTIMIZED request size per turn (the true
7
+ * context the model sees); the MCP side adds tool-output tokens as a floor
8
+ * when no proxy is in the loop.
9
+ */
10
+ export interface MeterReading {
11
+ /** Best-known tokens occupying the context window right now. */
12
+ usedTokens: number;
13
+ /** The window budget being metered against. */
14
+ windowTokens: number;
15
+ /** 0–100. */
16
+ usedPct: number;
17
+ /** Tokens the optimizer saved this session (before − after). */
18
+ savedTokens: number;
19
+ status: "ok" | "warn" | "handoff";
20
+ /** Human advice matching the status. */
21
+ advice: string;
22
+ }
23
+ export interface Meter {
24
+ /** Proxy turn: the full optimized request size IS the current context. */
25
+ onRequest(originalTokens: number, optimizedTokens: number): void;
26
+ /** MCP-side: a tool emitted `tokens` into the conversation (additive floor). */
27
+ onToolOutput(tokens: number): void;
28
+ read(): MeterReading;
29
+ /** New session: reset usage (savings history is kept). */
30
+ reset(): void;
31
+ }
32
+ export interface MeterOptions {
33
+ /** Context window budget in tokens. Default 200k (Claude-class). */
34
+ windowTokens?: number;
35
+ /** warn at this fraction. Default 0.7. */
36
+ warnAt?: number;
37
+ /** advise handoff+clear at this fraction. Default 0.85. */
38
+ handoffAt?: number;
39
+ }
40
+ export declare function createMeter(root: string, opts?: MeterOptions): Meter;
@@ -0,0 +1,61 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ export function createMeter(root, opts = {}) {
4
+ const windowTokens = opts.windowTokens ?? 200_000;
5
+ const warnAt = opts.warnAt ?? 0.7;
6
+ const handoffAt = opts.handoffAt ?? 0.85;
7
+ mkdirSync(root, { recursive: true });
8
+ const path = join(root, "meter.json");
9
+ let state = { lastRequestTokens: 0, toolTokens: 0, savedTokens: 0 };
10
+ // Proxy, MCP server, and dashboard all share this store across processes —
11
+ // re-read disk before every public operation so no reader serves stale state.
12
+ const reload = () => {
13
+ if (!existsSync(path))
14
+ return;
15
+ try {
16
+ state = { ...state, ...JSON.parse(readFileSync(path, "utf8")) };
17
+ }
18
+ catch {
19
+ /* keep current in-memory state */
20
+ }
21
+ };
22
+ reload();
23
+ const save = () => {
24
+ const tmp = `${path}.${process.pid}.tmp`;
25
+ writeFileSync(tmp, JSON.stringify(state), "utf8");
26
+ renameSync(tmp, path);
27
+ };
28
+ return {
29
+ onRequest(originalTokens, optimizedTokens) {
30
+ reload();
31
+ // The optimized request is the authoritative context size this turn.
32
+ state.lastRequestTokens = optimizedTokens;
33
+ state.toolTokens = 0; // request already contains prior tool outputs
34
+ state.savedTokens += Math.max(0, originalTokens - optimizedTokens);
35
+ save();
36
+ },
37
+ onToolOutput(tokens) {
38
+ reload();
39
+ state.toolTokens += tokens;
40
+ save();
41
+ },
42
+ read() {
43
+ reload();
44
+ const usedTokens = state.lastRequestTokens + state.toolTokens;
45
+ const usedPct = Math.min(100, Math.round((usedTokens / windowTokens) * 1000) / 10);
46
+ const frac = usedTokens / windowTokens;
47
+ const status = frac >= handoffAt ? "handoff" : frac >= warnAt ? "warn" : "ok";
48
+ const advice = status === "handoff"
49
+ ? `Context ${usedPct}% full — SAVE A HANDOFF NOW (knitbrain_save_handoff with goal/state/next steps), then clear the session and resume; knitbrain_load_session restores everything.`
50
+ : status === "warn"
51
+ ? `Context ${usedPct}% full — finish the current step, then consider knitbrain_save_handoff before starting anything large.`
52
+ : `Context ${usedPct}% full — healthy.`;
53
+ return { usedTokens, windowTokens, usedPct, savedTokens: state.savedTokens, status, advice };
54
+ },
55
+ reset() {
56
+ state = { lastRequestTokens: 0, toolTokens: 0, savedTokens: state.savedTokens };
57
+ save();
58
+ },
59
+ };
60
+ }
61
+ //# sourceMappingURL=meter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meter.js","sourceRoot":"","sources":["../../src/engine/meter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Skills engine — find-or-write executable playbooks.
3
+ *
4
+ * Skills are made ON-DEMAND when the user states a task (never pre-installed,
5
+ * never downloaded): knitbrain drafts a telegraphic (caveman-method) playbook
6
+ * seeded from past learnings, the agent refines it while working, and the
7
+ * refined version is saved back. Skills persist and compound; agents are
8
+ * disposable workers briefed with them.
9
+ */
10
+ export interface Skill {
11
+ id: string;
12
+ name: string;
13
+ /** Trigger keywords for matching future tasks. */
14
+ triggers: string[];
15
+ /** Telegraphic playbook body (caveman-method: max knowledge per token). */
16
+ body: string;
17
+ uses: number;
18
+ updatedAt: string;
19
+ }
20
+ export interface SkillsStore {
21
+ /** Best skill for a task, or null. Bumps `uses` on hit. */
22
+ find(task: string): Skill | null;
23
+ /** Draft a NEW telegraphic skill skeleton for a task (not persisted). */
24
+ draft(task: string, seedLessons: string[]): string;
25
+ /** Persist a skill (create or update by name). */
26
+ save(input: {
27
+ name: string;
28
+ body: string;
29
+ triggers?: string[];
30
+ }): Skill;
31
+ list(): Skill[];
32
+ }
33
+ export declare function createSkillsStore(root: string): SkillsStore;
@@ -0,0 +1,97 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ const tokenize = (s) => s.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2);
5
+ export function createSkillsStore(root) {
6
+ mkdirSync(root, { recursive: true });
7
+ const path = join(root, "skills.json");
8
+ const load = () => {
9
+ if (!existsSync(path))
10
+ return [];
11
+ try {
12
+ return JSON.parse(readFileSync(path, "utf8"));
13
+ }
14
+ catch {
15
+ return [];
16
+ }
17
+ };
18
+ const persist = (skills) => {
19
+ const tmp = `${path}.${process.pid}.tmp`;
20
+ writeFileSync(tmp, JSON.stringify(skills, null, 2), "utf8");
21
+ renameSync(tmp, path);
22
+ };
23
+ return {
24
+ find(task) {
25
+ const terms = new Set(tokenize(task));
26
+ let best = null;
27
+ let bestScore = 0;
28
+ const all = load();
29
+ for (const s of all) {
30
+ let score = 0;
31
+ for (const t of s.triggers)
32
+ if (terms.has(t))
33
+ score += 2;
34
+ for (const t of tokenize(s.name))
35
+ if (terms.has(t))
36
+ score += 1;
37
+ if (score > bestScore) {
38
+ best = s;
39
+ bestScore = score;
40
+ }
41
+ }
42
+ if (best && bestScore >= 2) {
43
+ best.uses += 1;
44
+ persist(all);
45
+ return best;
46
+ }
47
+ return null;
48
+ },
49
+ draft(task, seedLessons) {
50
+ // Telegraphic skeleton (caveman method): no filler, fragments OK,
51
+ // sections the agent fills while working, then saves via skill_save.
52
+ const pitfalls = seedLessons.length > 0
53
+ ? seedLessons.map((l) => `- ${l}`).join("\n")
54
+ : "- (none known. add what bites.)";
55
+ return `# skill: ${task.slice(0, 64)}
56
+
57
+ GOAL: ${task}
58
+
59
+ STEPS:
60
+ 1. ground first: query_imports/dependents on touched files.
61
+ 2. smallest correct change. verify before claim.
62
+ 3. gates green before done.
63
+
64
+ CHECKS:
65
+ - lossless? never-expand? tests pass?
66
+
67
+ PITFALLS (from memory):
68
+ ${pitfalls}
69
+
70
+ AFTER: refine this skill w/ what you learned → knitbrain_skill_save (same name). Skill compound.`;
71
+ },
72
+ save(input) {
73
+ const all = load();
74
+ const triggers = input.triggers && input.triggers.length > 0 ? input.triggers : tokenize(input.name);
75
+ const existing = all.find((s) => s.name === input.name);
76
+ if (existing) {
77
+ existing.body = input.body;
78
+ existing.triggers = [...new Set([...existing.triggers, ...triggers])];
79
+ existing.updatedAt = new Date().toISOString();
80
+ persist(all);
81
+ return existing;
82
+ }
83
+ const skill = {
84
+ id: createHash("sha256").update(input.name + Date.now()).digest("hex").slice(0, 8),
85
+ name: input.name,
86
+ triggers,
87
+ body: input.body,
88
+ uses: 0,
89
+ updatedAt: new Date().toISOString(),
90
+ };
91
+ persist([...all, skill]);
92
+ return skill;
93
+ },
94
+ list: load,
95
+ };
96
+ }
97
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/engine/skills.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { CCRStore } from "../ccr/store.js";
2
+ /** One posting on the shared board: a compressed skeleton + a CCR handle to the full original. */
3
+ export interface BoardEntry {
4
+ id: string;
5
+ author: string;
6
+ /** Compressed/skeleton view (cheap for other agents to read). */
7
+ summary: string;
8
+ /** CCR handle to the pristine original. */
9
+ handle: string;
10
+ ts: string;
11
+ }
12
+ export interface TeamBoard {
13
+ /** Post a finding: store the full original in CCR, keep a compressed summary on the board. */
14
+ post(author: string, content: string): BoardEntry;
15
+ /** The shared board — skeletons only (page originals via get). */
16
+ board(): BoardEntry[];
17
+ /** Fetch the full original of an entry by id (byte-for-byte from CCR). */
18
+ get(id: string): string | null;
19
+ /** Clear the board (entries only; CCR originals remain until tiered out). */
20
+ clear(): void;
21
+ }
22
+ /**
23
+ * Shared compressed-context board. Parallel agents post findings as skeletons
24
+ * (cheap for everyone to read); the full original is one CCR lookup away. This
25
+ * is what makes N parallel agents NOT multiply context cost. Local-first; the
26
+ * networked multi-user hub builds on top of this later.
27
+ */
28
+ export declare function createTeamBoard(root: string, ccr: CCRStore): TeamBoard;
@@ -0,0 +1,58 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { compress } from "../optimizer/router.js";
5
+ /**
6
+ * Shared compressed-context board. Parallel agents post findings as skeletons
7
+ * (cheap for everyone to read); the full original is one CCR lookup away. This
8
+ * is what makes N parallel agents NOT multiply context cost. Local-first; the
9
+ * networked multi-user hub builds on top of this later.
10
+ */
11
+ export function createTeamBoard(root, ccr) {
12
+ mkdirSync(root, { recursive: true });
13
+ const path = join(root, "board.json");
14
+ const load = () => {
15
+ if (!existsSync(path))
16
+ return [];
17
+ try {
18
+ return JSON.parse(readFileSync(path, "utf8"));
19
+ }
20
+ catch {
21
+ return [];
22
+ }
23
+ };
24
+ const save = (entries) => {
25
+ const tmp = `${path}.${process.pid}.tmp`;
26
+ writeFileSync(tmp, JSON.stringify(entries, null, 2), "utf8");
27
+ renameSync(tmp, path);
28
+ };
29
+ return {
30
+ post(author, content) {
31
+ const r = compress(content, ccr);
32
+ // Always keep the pristine original recoverable, compressed or not.
33
+ const handle = r.compressed ? r.handle : ccr.put(content);
34
+ const entry = {
35
+ id: createHash("sha256").update(author + content + Date.now()).digest("hex").slice(0, 8),
36
+ author,
37
+ summary: r.compressed ? r.skeleton : content,
38
+ handle,
39
+ ts: new Date().toISOString(),
40
+ };
41
+ save([...load(), entry]);
42
+ return entry;
43
+ },
44
+ board() {
45
+ return load();
46
+ },
47
+ get(id) {
48
+ const entry = load().find((e) => e.id === id);
49
+ if (!entry)
50
+ return null;
51
+ return ccr.get(entry.handle);
52
+ },
53
+ clear() {
54
+ save([]);
55
+ },
56
+ };
57
+ }
58
+ //# sourceMappingURL=teams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams.js","sourceRoot":"","sources":["../../src/engine/teams.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAwBlD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,GAAa;IACzD,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAEtC,MAAM,IAAI,GAAG,GAAiB,EAAE;QAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAiB,CAAC;QAChE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;IACF,MAAM,IAAI,GAAG,CAAC,OAAqB,EAAQ,EAAE;QAC3C,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;QACzC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7D,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,OAAO;QACL,IAAI,CAAC,MAAM,EAAE,OAAO;YAClB,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACjC,oEAAoE;YACpE,MAAM,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAe;gBACxB,EAAE,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;gBACxF,MAAM;gBACN,OAAO,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO;gBAC5C,MAAM;gBACN,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aAC7B,CAAC;YACF,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;YACzB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK;YACH,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QACD,GAAG,CAAC,EAAE;YACJ,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;YACxB,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC;QACD,KAAK;YACH,IAAI,CAAC,EAAE,CAAC,CAAC;QACX,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type Tier = "inquiry" | "trivial" | "standard" | "complex";
2
+ export interface Classification {
3
+ tier: Tier;
4
+ phases: string[];
5
+ autoPlanMode: boolean;
6
+ reason: string;
7
+ }
8
+ /**
9
+ * Deterministic tier router (no ML). Mirrors Knit's classifier shape:
10
+ * inquiry → just answer; trivial → execute; standard → light loop;
11
+ * complex → plan-mode + full phases.
12
+ *
13
+ * `scopeAdjust` is the calibration dial from the false-positive loop: positive
14
+ * raises the file-count bar for "complex" (classifier was over-sensitive),
15
+ * negative lowers it. Keyword-triggered complex is exempt unless the bar was
16
+ * raised — explicit risk words stay authoritative until users vote otherwise.
17
+ */
18
+ export declare function classifyTask(description: string, files?: string[], scopeAdjust?: number): Classification;
@@ -0,0 +1,40 @@
1
+ const COMPLEX = /\b(architect|architecture|refactor|migrat|redesign|rewrite|security|schema|breaking|orchestrat|protocol|concurren)/i;
2
+ const TRIVIAL = /\b(typo|rename|comment|bump|format|lint|whitespace|one[- ]?line)\b/i;
3
+ const INQUIRY = /^\s*(how|what|why|where|which|who|can|does|do|is|are|should|could|would|when)\b|[?]\s*$/i;
4
+ /**
5
+ * Deterministic tier router (no ML). Mirrors Knit's classifier shape:
6
+ * inquiry → just answer; trivial → execute; standard → light loop;
7
+ * complex → plan-mode + full phases.
8
+ *
9
+ * `scopeAdjust` is the calibration dial from the false-positive loop: positive
10
+ * raises the file-count bar for "complex" (classifier was over-sensitive),
11
+ * negative lowers it. Keyword-triggered complex is exempt unless the bar was
12
+ * raised — explicit risk words stay authoritative until users vote otherwise.
13
+ */
14
+ export function classifyTask(description, files = [], scopeAdjust = 0) {
15
+ const fileCount = files.filter((f) => f.trim().length > 0).length;
16
+ if (fileCount === 0 && INQUIRY.test(description) && !COMPLEX.test(description)) {
17
+ return { tier: "inquiry", phases: [], autoPlanMode: false, reason: "question with no files to touch" };
18
+ }
19
+ const complexAt = Math.max(2, 4 + scopeAdjust);
20
+ const keywordComplex = COMPLEX.test(description) && scopeAdjust <= 0;
21
+ if (keywordComplex || fileCount >= complexAt) {
22
+ const calNote = scopeAdjust !== 0 ? ` (calibrated: threshold ${complexAt} files)` : "";
23
+ return {
24
+ tier: "complex",
25
+ phases: ["RESEARCH", "PLAN", "EXECUTE", "REVIEW", "LEARN"],
26
+ autoPlanMode: true,
27
+ reason: (keywordComplex ? "high-risk keyword detected" : `${fileCount} files in scope`) + calNote,
28
+ };
29
+ }
30
+ if (TRIVIAL.test(description) || (fileCount <= 1 && description.length < 60)) {
31
+ return { tier: "trivial", phases: ["EXECUTE"], autoPlanMode: false, reason: "small, low-risk change" };
32
+ }
33
+ return {
34
+ tier: "standard",
35
+ phases: ["RESEARCH", "EXECUTE", "REVIEW", "LEARN"],
36
+ autoPlanMode: false,
37
+ reason: "standard multi-step change",
38
+ };
39
+ }
40
+ //# sourceMappingURL=workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.js","sourceRoot":"","sources":["../../src/engine/workflow.ts"],"names":[],"mappings":"AASA,MAAM,OAAO,GAAG,qHAAqH,CAAC;AACtI,MAAM,OAAO,GAAG,qEAAqE,CAAC;AACtF,MAAM,OAAO,GAAG,0FAA0F,CAAC;AAE3G;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,WAAmB,EAAE,QAAkB,EAAE,EAAE,WAAW,GAAG,CAAC;IACrF,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;IAElE,IAAI,SAAS,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,iCAAiC,EAAE,CAAC;IACzG,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC;IAC/C,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,WAAW,IAAI,CAAC,CAAC;IACrE,IAAI,cAAc,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,2BAA2B,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACvF,OAAO;YACL,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC;YAC1D,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,GAAG,SAAS,iBAAiB,CAAC,GAAG,OAAO;SAClG,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,WAAW,CAAC,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC;QAC7E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC;IACzG,CAAC;IACD,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC;QAClD,YAAY,EAAE,KAAK;QACnB,MAAM,EAAE,4BAA4B;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * knitbrain-hook — host lifecycle hooks (Layer 2, per-platform enhancement on
4
+ * top of the universal knitbrain_read steering).
5
+ *
6
+ * knitbrain-hook pretooluse stdin: PreToolUse JSON → deny+redirect large raw Reads
7
+ * knitbrain-hook precompact auto-save a handoff BEFORE the host compacts
8
+ *
9
+ * Hooks must NEVER break the host: any internal error exits 0 silently.
10
+ */
11
+ import { createMemory } from "../engine/memory.js";
12
+ import { memoryRoot } from "../paths.js";
13
+ import { decidePreToolUse } from "./pretooluse.js";
14
+ function readStdin() {
15
+ return new Promise((resolve) => {
16
+ let data = "";
17
+ process.stdin.on("data", (c) => (data += c));
18
+ process.stdin.on("end", () => resolve(data));
19
+ setTimeout(() => resolve(data), 2000); // never hang the host
20
+ });
21
+ }
22
+ async function main() {
23
+ const mode = process.argv[2];
24
+ try {
25
+ if (mode === "pretooluse") {
26
+ const input = JSON.parse(await readStdin());
27
+ const decision = decidePreToolUse(input);
28
+ if (decision)
29
+ process.stdout.write(JSON.stringify(decision));
30
+ return;
31
+ }
32
+ if (mode === "precompact") {
33
+ // The host is about to compact — capture a resumable handoff first so
34
+ // nothing is lost to compaction. load_session restores it next time.
35
+ const memory = createMemory(memoryRoot());
36
+ const prior = memory.loadSession().handoff ?? "";
37
+ const note = `[auto-handoff @ ${new Date().toISOString()}] Host compaction imminent. If resuming after a clear: re-run knitbrain_load_session, re-check knitbrain_context_meter, and continue from the state below.\n${prior}`;
38
+ memory.saveHandoff(note);
39
+ return;
40
+ }
41
+ }
42
+ catch {
43
+ // swallow — a hook failure must never break the host session
44
+ }
45
+ }
46
+ main().then(() => process.exit(0), () => process.exit(0));
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAwB,MAAM,iBAAiB,CAAC;AAEzE,SAAS,SAAS;IAChB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,sBAAsB;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC;QACH,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,SAAS,EAAE,CAAoB,CAAC;YAC/D,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;YACzC,IAAI,QAAQ;gBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;QACD,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;YAC1B,sEAAsE;YACtE,qEAAqE;YACrE,MAAM,MAAM,GAAG,YAAY,CAAC,UAAU,EAAE,CAAC,CAAC;YAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC;YACjD,MAAM,IAAI,GAAG,mBAAmB,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,+JAA+J,KAAK,EAAE,CAAC;YAC/N,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;IAC/D,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,IAAI,CACT,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EACrB,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CACtB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * PreToolUse hook logic (pure — IO injected for tests).
3
+ *
4
+ * When the host is about to raw-Read a LARGE file, deny with a reason that
5
+ * redirects the agent to `knitbrain_read` — turning the rules-file steering
6
+ * into automatic ENFORCEMENT. Small files pass through untouched. This uses
7
+ * the stable PreToolUse deny contract (works across host versions, unlike
8
+ * PostToolUse output replacement).
9
+ */
10
+ export interface PreToolUseInput {
11
+ tool_name?: string;
12
+ tool_input?: {
13
+ file_path?: string;
14
+ [k: string]: unknown;
15
+ };
16
+ cwd?: string;
17
+ }
18
+ /** Files larger than this are denied in favor of knitbrain_read. */
19
+ export declare const READ_REDIRECT_BYTES = 20000;
20
+ export declare function decidePreToolUse(input: PreToolUseInput, io?: {
21
+ exists: (p: string) => boolean;
22
+ sizeOf: (p: string) => number;
23
+ }): Record<string, unknown> | null;
@@ -0,0 +1,38 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { isAbsolute, relative } from "node:path";
3
+ /** Files larger than this are denied in favor of knitbrain_read. */
4
+ export const READ_REDIRECT_BYTES = 20_000;
5
+ export function decidePreToolUse(input, io = {
6
+ exists: existsSync,
7
+ sizeOf: (p) => statSync(p).size,
8
+ }) {
9
+ if (input.tool_name !== "Read")
10
+ return null;
11
+ const path = input.tool_input?.file_path;
12
+ if (typeof path !== "string" || path.length === 0)
13
+ return null;
14
+ if (!io.exists(path))
15
+ return null;
16
+ let size;
17
+ try {
18
+ size = io.sizeOf(path);
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ if (size <= READ_REDIRECT_BYTES)
24
+ return null;
25
+ const cwd = input.cwd ?? process.cwd();
26
+ const rel = isAbsolute(path) ? relative(cwd, path) : path;
27
+ // Never redirect reads outside the project — knitbrain_read is project-scoped.
28
+ if (rel.startsWith(".."))
29
+ return null;
30
+ return {
31
+ hookSpecificOutput: {
32
+ hookEventName: "PreToolUse",
33
+ permissionDecision: "deny",
34
+ permissionDecisionReason: `Large file (${Math.round(size / 1024)}KB). Use knitbrain_read with path "${rel}" instead — optimized skeleton (~70-90% fewer tokens), exact original via knitbrain_retrieve. Raw Read only for the specific region you are about to edit.`,
35
+ },
36
+ };
37
+ }
38
+ //# sourceMappingURL=pretooluse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pretooluse.js","sourceRoot":"","sources":["../../src/hooks/pretooluse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAiBjD,oEAAoE;AACpE,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAE1C,MAAM,UAAU,gBAAgB,CAC9B,KAAsB,EACtB,KAAwE;IACtE,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;CAChC;IAED,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,EAAE,SAAS,CAAC;IACzC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/D,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAElC,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,IAAI,IAAI,mBAAmB;QAAE,OAAO,IAAI,CAAC;IAE7C,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,+EAA+E;IAC/E,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,OAAO;QACL,kBAAkB,EAAE;YAClB,aAAa,EAAE,YAAY;YAC3B,kBAAkB,EAAE,MAAM;YAC1B,wBAAwB,EAAE,eAAe,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,sCAAsC,GAAG,4JAA4J;SACtQ;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ /** Where this machine's hub membership lives. */
2
+ export interface HubConfig {
3
+ url: string;
4
+ token: string;
5
+ /** Display name for this member's postings. */
6
+ member: string;
7
+ }
8
+ /** `knitbrain join <url> <token> [member]` writes this. */
9
+ export declare function saveHubConfig(cfg: HubConfig): string;
10
+ export declare function loadHubConfig(): HubConfig | null;
11
+ /**
12
+ * Mirror a board posting to the hub — FIRE-AND-FORGET. Never throws, never
13
+ * blocks: a dead/unreachable hub must never slow local work.
14
+ */
15
+ export declare function mirrorToHub(cfg: HubConfig, entry: {
16
+ author: string;
17
+ summary: string;
18
+ original: string;
19
+ }): void;
20
+ /** Best-effort hub board fetch (for the dashboard's merged view). */
21
+ export declare function fetchHubBoard(cfg: HubConfig, timeoutMs?: number): Promise<Array<{
22
+ id: string;
23
+ author: string;
24
+ summary: string;
25
+ ts: string;
26
+ }>>;
@@ -0,0 +1,64 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { knitbrainHome } from "../paths.js";
4
+ function configPath() {
5
+ return join(knitbrainHome(), "hub.json");
6
+ }
7
+ /** `knitbrain join <url> <token> [member]` writes this. */
8
+ export function saveHubConfig(cfg) {
9
+ mkdirSync(knitbrainHome(), { recursive: true });
10
+ const path = configPath();
11
+ const tmp = `${path}.${process.pid}.tmp`;
12
+ writeFileSync(tmp, JSON.stringify(cfg, null, 2), { encoding: "utf8", mode: 0o600 });
13
+ renameSync(tmp, path);
14
+ return path;
15
+ }
16
+ export function loadHubConfig() {
17
+ const path = configPath();
18
+ if (!existsSync(path))
19
+ return null;
20
+ try {
21
+ const cfg = JSON.parse(readFileSync(path, "utf8"));
22
+ return cfg.url && cfg.token ? cfg : null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * Mirror a board posting to the hub — FIRE-AND-FORGET. Never throws, never
30
+ * blocks: a dead/unreachable hub must never slow local work.
31
+ */
32
+ export function mirrorToHub(cfg, entry) {
33
+ const controller = new AbortController();
34
+ const timer = setTimeout(() => controller.abort(), 2000);
35
+ void fetch(`${cfg.url.replace(/\/+$/, "")}/board`, {
36
+ method: "POST",
37
+ headers: { "content-type": "application/json", authorization: `Bearer ${cfg.token}` },
38
+ body: JSON.stringify({ ...entry, author: cfg.member || entry.author }),
39
+ signal: controller.signal,
40
+ })
41
+ .catch(() => { })
42
+ .finally(() => clearTimeout(timer));
43
+ }
44
+ /** Best-effort hub board fetch (for the dashboard's merged view). */
45
+ export async function fetchHubBoard(cfg, timeoutMs = 1500) {
46
+ const controller = new AbortController();
47
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
48
+ try {
49
+ const res = await fetch(`${cfg.url.replace(/\/+$/, "")}/board`, {
50
+ headers: { authorization: `Bearer ${cfg.token}` },
51
+ signal: controller.signal,
52
+ });
53
+ if (!res.ok)
54
+ return [];
55
+ return (await res.json());
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ finally {
61
+ clearTimeout(timer);
62
+ }
63
+ }
64
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/hub/client.ts"],"names":[],"mappings":"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"}