opencarly 1.0.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 (59) hide show
  1. package/README.md +78 -0
  2. package/bin/install.js +304 -0
  3. package/commands/carly-manager.md +69 -0
  4. package/dist/config/discovery.d.ts +22 -0
  5. package/dist/config/discovery.d.ts.map +1 -0
  6. package/dist/config/discovery.js +43 -0
  7. package/dist/config/discovery.js.map +1 -0
  8. package/dist/config/index.d.ts +7 -0
  9. package/dist/config/index.d.ts.map +1 -0
  10. package/dist/config/index.js +7 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/config/manifest.d.ts +39 -0
  13. package/dist/config/manifest.d.ts.map +1 -0
  14. package/dist/config/manifest.js +139 -0
  15. package/dist/config/manifest.js.map +1 -0
  16. package/dist/config/schema.d.ts +663 -0
  17. package/dist/config/schema.d.ts.map +1 -0
  18. package/dist/config/schema.js +208 -0
  19. package/dist/config/schema.js.map +1 -0
  20. package/dist/engine/brackets.d.ts +26 -0
  21. package/dist/engine/brackets.d.ts.map +1 -0
  22. package/dist/engine/brackets.js +49 -0
  23. package/dist/engine/brackets.js.map +1 -0
  24. package/dist/engine/index.d.ts +8 -0
  25. package/dist/engine/index.d.ts.map +1 -0
  26. package/dist/engine/index.js +8 -0
  27. package/dist/engine/index.js.map +1 -0
  28. package/dist/engine/loader.d.ts +82 -0
  29. package/dist/engine/loader.d.ts.map +1 -0
  30. package/dist/engine/loader.js +147 -0
  31. package/dist/engine/loader.js.map +1 -0
  32. package/dist/engine/matcher.d.ts +43 -0
  33. package/dist/engine/matcher.d.ts.map +1 -0
  34. package/dist/engine/matcher.js +174 -0
  35. package/dist/engine/matcher.js.map +1 -0
  36. package/dist/engine/trimmer.d.ts +91 -0
  37. package/dist/engine/trimmer.d.ts.map +1 -0
  38. package/dist/engine/trimmer.js +236 -0
  39. package/dist/engine/trimmer.js.map +1 -0
  40. package/dist/formatter/formatter.d.ts +23 -0
  41. package/dist/formatter/formatter.d.ts.map +1 -0
  42. package/dist/formatter/formatter.js +129 -0
  43. package/dist/formatter/formatter.js.map +1 -0
  44. package/dist/index.d.ts +15 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +484 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/session/session.d.ts +60 -0
  49. package/dist/session/session.d.ts.map +1 -0
  50. package/dist/session/session.js +394 -0
  51. package/dist/session/session.js.map +1 -0
  52. package/package.json +59 -0
  53. package/templates/.opencarly/commands.json +96 -0
  54. package/templates/.opencarly/context.json +44 -0
  55. package/templates/.opencarly/domains/development.md +13 -0
  56. package/templates/.opencarly/domains/global.md +13 -0
  57. package/templates/.opencarly/domains/security.md +14 -0
  58. package/templates/.opencarly/domains/testing.md +12 -0
  59. package/templates/.opencarly/manifest.json +43 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * OpenCarly Domain Matcher
3
+ *
4
+ * Scans user prompts for domain recall keywords and star-commands.
5
+ * Handles global and per-domain exclusions.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Star-command detection
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * Detect star-commands in the prompt.
12
+ * e.g. "*brief *dev explain this" -> ["brief", "dev"]
13
+ */
14
+ export function detectStarCommands(prompt) {
15
+ const commands = [];
16
+ // Use matchAll to avoid global RegExp state mutation race conditions
17
+ const matches = prompt.matchAll(/\*([a-zA-Z]\w*)/g);
18
+ for (const match of matches) {
19
+ commands.push(match[1].toLowerCase());
20
+ }
21
+ return [...new Set(commands)]; // deduplicate
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // Path and Glob Matching
25
+ // ---------------------------------------------------------------------------
26
+ const globRegexCache = new Map();
27
+ function globToRegExp(glob) {
28
+ if (globRegexCache.has(glob))
29
+ return globRegexCache.get(glob);
30
+ let escaped = "";
31
+ for (let i = 0; i < glob.length; i++) {
32
+ const c = glob[i];
33
+ if (/[.+^${}()|[\]\\]/.test(c)) {
34
+ escaped += "\\" + c;
35
+ }
36
+ else {
37
+ escaped += c;
38
+ }
39
+ }
40
+ const regexStr = "^" + escaped.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, ".") + "$";
41
+ const regex = new RegExp(regexStr);
42
+ globRegexCache.set(glob, regex);
43
+ return regex;
44
+ }
45
+ /**
46
+ * Check if a file path matches any of the given glob patterns.
47
+ */
48
+ export function isPathMatch(filePath, patterns) {
49
+ for (const pattern of patterns) {
50
+ if (globToRegExp(pattern).test(filePath)) {
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+ /**
57
+ * Extracts possible file paths from a user prompt.
58
+ * Looks for words containing `/` or a file extension (like `.ts`).
59
+ */
60
+ function extractPathsFromPrompt(prompt) {
61
+ const words = prompt.split(/\s+/);
62
+ const paths = [];
63
+ for (const word of words) {
64
+ // Strip trailing punctuation
65
+ const cleanWord = word.replace(/[.,;:!?)$'"]+$/, "").replace(/^['"(]+/, "");
66
+ if (cleanWord.includes("/") || /\.[a-z0-9]{1,4}$/i.test(cleanWord)) {
67
+ paths.push(cleanWord);
68
+ }
69
+ }
70
+ return [...new Set(paths)];
71
+ }
72
+ // ---------------------------------------------------------------------------
73
+ // Keyword matching
74
+ // ---------------------------------------------------------------------------
75
+ /**
76
+ * Check if any keywords from the list appear in the prompt (case-insensitive substring match).
77
+ * Returns the list of matching keywords.
78
+ */
79
+ function findMatchingKeywords(prompt, keywords) {
80
+ const promptLower = prompt.toLowerCase();
81
+ const matches = [];
82
+ for (const keyword of keywords) {
83
+ const keywordLower = keyword.toLowerCase().trim();
84
+ if (keywordLower === "")
85
+ continue;
86
+ // Escape regex special chars and do boundary match
87
+ const escaped = keywordLower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
88
+ if (new RegExp(`(^|\\W)${escaped}($|\\W)`, "i").test(promptLower)) {
89
+ matches.push(keyword);
90
+ }
91
+ }
92
+ return matches;
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Main matching function
96
+ // ---------------------------------------------------------------------------
97
+ /**
98
+ * Match domains against a user prompt.
99
+ *
100
+ * Algorithm:
101
+ * 1. Check globalExclude - if any match, skip all domain matching
102
+ * 2. Collect always-on active domains
103
+ * 3. For each active, non-alwaysOn domain:
104
+ * a. Check per-domain exclude keywords
105
+ * b. Check recall keywords
106
+ * 4. Detect star-commands
107
+ */
108
+ export function matchDomains(prompt, manifest, activeFiles = []) {
109
+ const result = {
110
+ matched: {},
111
+ matchedPaths: {},
112
+ excluded: {},
113
+ globalExcluded: [],
114
+ starCommands: [],
115
+ alwaysOn: [],
116
+ };
117
+ // 1. Check global exclusions
118
+ if (manifest.globalExclude.length > 0) {
119
+ const globalMatches = findMatchingKeywords(prompt, manifest.globalExclude);
120
+ if (globalMatches.length > 0) {
121
+ result.globalExcluded = globalMatches;
122
+ // Still detect star-commands even when globally excluded
123
+ result.starCommands = detectStarCommands(prompt);
124
+ return result;
125
+ }
126
+ }
127
+ // Extract possible paths from user prompt and combine with activeFiles
128
+ const promptPaths = extractPathsFromPrompt(prompt);
129
+ const allActiveFiles = [...new Set([...activeFiles, ...promptPaths])];
130
+ // 2-3. Process each domain
131
+ for (const [name, domain] of Object.entries(manifest.domains)) {
132
+ // Skip inactive domains
133
+ if (domain.state === "inactive")
134
+ continue;
135
+ // Collect always-on domains
136
+ if (domain.alwaysOn) {
137
+ result.alwaysOn.push(name);
138
+ continue; // always-on domains don't need keyword matching
139
+ }
140
+ // Check per-domain exclusions
141
+ if (domain.exclude.length > 0) {
142
+ const excludeMatches = findMatchingKeywords(prompt, domain.exclude);
143
+ if (excludeMatches.length > 0) {
144
+ result.excluded[name] = excludeMatches;
145
+ continue;
146
+ }
147
+ }
148
+ // Check file paths first
149
+ if (domain.paths && domain.paths.length > 0) {
150
+ const pathMatches = [];
151
+ for (const file of allActiveFiles) {
152
+ if (isPathMatch(file, domain.paths)) {
153
+ pathMatches.push(file);
154
+ }
155
+ }
156
+ if (pathMatches.length > 0) {
157
+ result.matchedPaths[name] = pathMatches;
158
+ // Skip keyword recall check if path triggered it
159
+ continue;
160
+ }
161
+ }
162
+ // Check recall keywords
163
+ if (domain.recall.length > 0) {
164
+ const recallMatches = findMatchingKeywords(prompt, domain.recall);
165
+ if (recallMatches.length > 0) {
166
+ result.matched[name] = recallMatches;
167
+ }
168
+ }
169
+ }
170
+ // 4. Detect star-commands
171
+ result.starCommands = detectStarCommands(prompt);
172
+ return result;
173
+ }
174
+ //# sourceMappingURL=matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.js","sourceRoot":"","sources":["../../src/engine/matcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * OpenCarly Smart Tool Output Trimmer
3
+ *
4
+ * Multi-factor scoring system that determines which tool outputs in
5
+ * conversation history are safe to trim. Replaces stale outputs with
6
+ * compact summaries to reduce token usage without degrading AI quality.
7
+ *
8
+ * Factors considered:
9
+ * - Age (turns since message)
10
+ * - Superseded file reads (same file read again more recently)
11
+ * - Post-read file edits (file was modified after being read)
12
+ * - Output size (larger = better trim candidate)
13
+ * - Tool type (ephemeral tools like bash/glob are safer to trim)
14
+ * - Tiny output protection (don't bother trimming small outputs)
15
+ */
16
+ import { type TrimmingConfig } from "../config/schema";
17
+ /**
18
+ * Minimal types mirroring the OpenCode SDK Part/Message shapes.
19
+ * We use structural typing so we don't need to import the SDK directly.
20
+ */
21
+ interface ToolStateCompleted {
22
+ status: "completed";
23
+ input: Record<string, unknown>;
24
+ output: string;
25
+ title: string;
26
+ metadata: Record<string, unknown>;
27
+ time: {
28
+ start: number;
29
+ end: number;
30
+ compacted?: number;
31
+ };
32
+ }
33
+ interface ToolStateOther {
34
+ status: "pending" | "running" | "error";
35
+ input: Record<string, unknown>;
36
+ [key: string]: unknown;
37
+ }
38
+ type ToolState = ToolStateCompleted | ToolStateOther;
39
+ interface ToolPart {
40
+ type: "tool";
41
+ tool: string;
42
+ callID: string;
43
+ state: ToolState;
44
+ [key: string]: unknown;
45
+ }
46
+ interface TextPart {
47
+ type: "text";
48
+ text: string;
49
+ [key: string]: unknown;
50
+ }
51
+ type AnyPart = ToolPart | TextPart | {
52
+ type: string;
53
+ [key: string]: unknown;
54
+ };
55
+ interface TransformMessage {
56
+ info: {
57
+ role: "user" | "assistant";
58
+ time: {
59
+ created: number;
60
+ };
61
+ [key: string]: unknown;
62
+ };
63
+ parts: AnyPart[];
64
+ }
65
+ export interface TrimStats {
66
+ /** Number of tool outputs trimmed */
67
+ partsTrimmed: number;
68
+ /** Estimated tokens saved from tool output trimming (excludes carly blocks) */
69
+ tokensSaved: number;
70
+ /** Number of carly-rules blocks stripped */
71
+ carlyBlocksStripped: number;
72
+ /** Estimated tokens saved from carly-rules block removal */
73
+ carlyTokensSaved: number;
74
+ /** Files read or edited in the last 5 turns */
75
+ activeFiles: string[];
76
+ }
77
+ /**
78
+ * Trim stale tool outputs and carly-rules blocks from message history.
79
+ *
80
+ * Algorithm:
81
+ * 1. Build TrimContext (catalog all file operations for cross-referencing)
82
+ * 2. For each message (except the last preserveLastN):
83
+ * - Strip <carly-rules> from text parts
84
+ * - For each completed tool part without a compacted timestamp:
85
+ * - Calculate relevance score
86
+ * - If score < threshold: replace output with compact summary
87
+ * 3. Return stats for logging
88
+ */
89
+ export declare function trimMessageHistory(messages: TransformMessage[], config: TrimmingConfig): TrimStats;
90
+ export {};
91
+ //# sourceMappingURL=trimmer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trimmer.d.ts","sourceRoot":"","sources":["../../src/engine/trimmer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMxE;;;GAGG;AAEH,UAAU,kBAAkB;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,KAAK,SAAS,GAAG,kBAAkB,GAAG,cAAc,CAAC;AAErD,UAAU,QAAQ;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,QAAQ;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,KAAK,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE9E,UAAU,gBAAgB;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAC3B,IAAI,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAyMD,MAAM,WAAW,SAAS;IACxB,qCAAqC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,MAAM,EAAE,cAAc,GACrB,SAAS,CAoEX"}
@@ -0,0 +1,236 @@
1
+ /**
2
+ * OpenCarly Smart Tool Output Trimmer
3
+ *
4
+ * Multi-factor scoring system that determines which tool outputs in
5
+ * conversation history are safe to trim. Replaces stale outputs with
6
+ * compact summaries to reduce token usage without degrading AI quality.
7
+ *
8
+ * Factors considered:
9
+ * - Age (turns since message)
10
+ * - Superseded file reads (same file read again more recently)
11
+ * - Post-read file edits (file was modified after being read)
12
+ * - Output size (larger = better trim candidate)
13
+ * - Tool type (ephemeral tools like bash/glob are safer to trim)
14
+ * - Tiny output protection (don't bother trimming small outputs)
15
+ */
16
+ import { TRIM_THRESHOLDS } from "../config/schema";
17
+ class TrimContext {
18
+ fileOps = new Map();
19
+ constructor(messages) {
20
+ for (let mi = 0; mi < messages.length; mi++) {
21
+ for (const part of messages[mi].parts) {
22
+ if (part.type !== "tool")
23
+ continue;
24
+ const toolPart = part;
25
+ if (toolPart.state.status !== "completed" && toolPart.state.status !== "error")
26
+ continue;
27
+ const filePath = toolPart.state.input.filePath;
28
+ if (!filePath)
29
+ continue;
30
+ if (toolPart.tool === "read" || toolPart.tool === "edit" || toolPart.tool === "write") {
31
+ const ops = this.fileOps.get(filePath);
32
+ const entry = { messageIndex: mi, op: toolPart.tool };
33
+ if (ops) {
34
+ ops.push(entry);
35
+ }
36
+ else {
37
+ this.fileOps.set(filePath, [entry]);
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ /** Check if the same file was read in a later message */
44
+ hasNewerRead(filePath, messageIndex) {
45
+ const ops = this.fileOps.get(filePath);
46
+ if (!ops)
47
+ return false;
48
+ return ops.some((op) => op.op === "read" && op.messageIndex > messageIndex);
49
+ }
50
+ /** Check if the file was edited or written after this message */
51
+ wasEditedAfter(filePath, messageIndex) {
52
+ const ops = this.fileOps.get(filePath);
53
+ if (!ops)
54
+ return false;
55
+ return ops.some((op) => (op.op === "edit" || op.op === "write") &&
56
+ op.messageIndex > messageIndex);
57
+ }
58
+ /** Get unique file paths operated on in the last N messages */
59
+ getRecentFiles(totalMessages, lastN) {
60
+ const recentPaths = new Set();
61
+ const startIdx = Math.max(0, totalMessages - lastN);
62
+ for (const [filePath, ops] of this.fileOps.entries()) {
63
+ if (ops.some(op => op.messageIndex >= startIdx)) {
64
+ recentPaths.add(filePath);
65
+ }
66
+ }
67
+ return Array.from(recentPaths);
68
+ }
69
+ }
70
+ // ---------------------------------------------------------------------------
71
+ // Scoring
72
+ // ---------------------------------------------------------------------------
73
+ /** Ephemeral tool types that are cheap to re-run */
74
+ const EPHEMERAL_TOOLS = new Set(["bash", "glob", "grep"]);
75
+ /** Minimum token estimate to bother trimming */
76
+ const MIN_TRIM_TOKENS = 100;
77
+ /**
78
+ * Rough token estimate from string length.
79
+ * ~4 chars per token is a reasonable approximation for code/text.
80
+ */
81
+ function estimateTokens(text) {
82
+ return Math.ceil(text.length / 4);
83
+ }
84
+ /**
85
+ * Score a tool part for trimming relevance.
86
+ *
87
+ * Returns 0-100 where lower = more trimmable.
88
+ * Parts that should never be trimmed return 100+.
89
+ */
90
+ function scoreToolPart(toolPart, messageIndex, totalMessages, context) {
91
+ const state = toolPart.state;
92
+ // Only trim completed tool outputs
93
+ if (state.status !== "completed")
94
+ return 200;
95
+ const completed = state;
96
+ // Already trimmed by us or by OpenCode's own compaction
97
+ if (completed.time.compacted)
98
+ return 200;
99
+ // Don't bother trimming tiny outputs
100
+ const tokenEstimate = estimateTokens(completed.output);
101
+ if (tokenEstimate < MIN_TRIM_TOKENS)
102
+ return 200;
103
+ let score = 100;
104
+ const turnsAgo = totalMessages - 1 - messageIndex;
105
+ // --- Factor 1: Age decay (6 points per turn) ---
106
+ score -= turnsAgo * 6;
107
+ // --- Factor 2: Superseded file reads ---
108
+ if (toolPart.tool === "read") {
109
+ const filePath = toolPart.state.input.filePath;
110
+ if (filePath && context.hasNewerRead(filePath, messageIndex)) {
111
+ score -= 60;
112
+ }
113
+ }
114
+ // --- Factor 3: File was edited after this read ---
115
+ if (toolPart.tool === "read") {
116
+ const filePath = toolPart.state.input.filePath;
117
+ if (filePath && context.wasEditedAfter(filePath, messageIndex)) {
118
+ score -= 50;
119
+ }
120
+ }
121
+ // --- Factor 4: Output size (larger = better trim candidate) ---
122
+ if (tokenEstimate > 2000) {
123
+ score -= 15;
124
+ }
125
+ else if (tokenEstimate > 500) {
126
+ score -= 8;
127
+ }
128
+ // --- Factor 5: Ephemeral tool types ---
129
+ if (EPHEMERAL_TOOLS.has(toolPart.tool)) {
130
+ score -= 10;
131
+ }
132
+ return Math.max(0, score);
133
+ }
134
+ // ---------------------------------------------------------------------------
135
+ // Trim summary generation
136
+ // ---------------------------------------------------------------------------
137
+ /**
138
+ * Build a compact summary to replace trimmed tool output.
139
+ */
140
+ function buildTrimSummary(toolPart, tokensSaved) {
141
+ const completed = toolPart.state;
142
+ const title = completed.title || toolPart.tool;
143
+ if (toolPart.tool === "read") {
144
+ const filePath = toolPart.state.input.filePath || "unknown file";
145
+ const lineCount = completed.output.split("\n").length;
146
+ return (`[Trimmed by OpenCarly] Read ${filePath} (${lineCount} lines, ~${tokensSaved} tokens saved)\n` +
147
+ `Re-read this file if its contents are needed.`);
148
+ }
149
+ if (toolPart.tool === "bash") {
150
+ const command = toolPart.state.input.command || "unknown command";
151
+ // Show first 80 chars of command
152
+ const shortCmd = command.length > 80 ? command.slice(0, 77) + "..." : command;
153
+ return (`[Trimmed by OpenCarly] Ran: ${shortCmd} (~${tokensSaved} tokens saved)\n` +
154
+ `Re-run this command if output is needed.`);
155
+ }
156
+ if (toolPart.tool === "glob" || toolPart.tool === "grep") {
157
+ const pattern = toolPart.state.input.pattern || "";
158
+ return (`[Trimmed by OpenCarly] ${toolPart.tool}: ${pattern} (~${tokensSaved} tokens saved)\n` +
159
+ `Re-run this search if results are needed.`);
160
+ }
161
+ // Generic fallback
162
+ return (`[Trimmed by OpenCarly] ${title} (~${tokensSaved} tokens saved)\n` +
163
+ `Tool output trimmed from history.`);
164
+ }
165
+ /**
166
+ * Trim stale tool outputs and carly-rules blocks from message history.
167
+ *
168
+ * Algorithm:
169
+ * 1. Build TrimContext (catalog all file operations for cross-referencing)
170
+ * 2. For each message (except the last preserveLastN):
171
+ * - Strip <carly-rules> from text parts
172
+ * - For each completed tool part without a compacted timestamp:
173
+ * - Calculate relevance score
174
+ * - If score < threshold: replace output with compact summary
175
+ * 3. Return stats for logging
176
+ */
177
+ export function trimMessageHistory(messages, config) {
178
+ // Step 1: Build context for cross-referencing file operations
179
+ const context = new TrimContext(messages);
180
+ const totalMessages = messages.length;
181
+ const stats = {
182
+ partsTrimmed: 0,
183
+ tokensSaved: 0,
184
+ carlyBlocksStripped: 0,
185
+ carlyTokensSaved: 0,
186
+ activeFiles: context.getRecentFiles(totalMessages, 5),
187
+ };
188
+ if (!config.enabled)
189
+ return stats;
190
+ const threshold = TRIM_THRESHOLDS[config.mode] ?? 40;
191
+ const protectedStart = Math.max(0, totalMessages - config.preserveLastN);
192
+ // Step 2: Process each message
193
+ for (let mi = 0; mi < totalMessages; mi++) {
194
+ const message = messages[mi];
195
+ for (let pi = 0; pi < message.parts.length; pi++) {
196
+ const part = message.parts[pi];
197
+ // --- Strip <carly-rules> from text parts (all messages) ---
198
+ if (part.type === "text") {
199
+ const textPart = part;
200
+ if (typeof textPart.text === "string" && textPart.text.includes("<carly-rules>")) {
201
+ const tokensBefore = estimateTokens(textPart.text);
202
+ const textBefore = textPart.text;
203
+ textPart.text = textPart.text
204
+ .replace(/<carly-rules>[\s\S]*?<\/carly-rules>/g, "")
205
+ .trim();
206
+ if (textPart.text !== textBefore) {
207
+ const tokensAfter = estimateTokens(textPart.text);
208
+ stats.carlyBlocksStripped++;
209
+ stats.carlyTokensSaved += Math.max(0, tokensBefore - tokensAfter);
210
+ }
211
+ }
212
+ continue;
213
+ }
214
+ // --- Tool output trimming (skip protected messages) ---
215
+ if (mi >= protectedStart)
216
+ continue;
217
+ if (part.type !== "tool")
218
+ continue;
219
+ const toolPart = part;
220
+ const score = scoreToolPart(toolPart, mi, totalMessages, context);
221
+ if (score < threshold) {
222
+ const completed = toolPart.state;
223
+ const tokensBefore = estimateTokens(completed.output);
224
+ const summary = buildTrimSummary(toolPart, tokensBefore);
225
+ const tokensAfter = estimateTokens(summary);
226
+ // Replace output with summary
227
+ completed.output = summary;
228
+ completed.time.compacted = Date.now();
229
+ stats.partsTrimmed++;
230
+ stats.tokensSaved += Math.max(0, tokensBefore - tokensAfter);
231
+ }
232
+ }
233
+ }
234
+ return stats;
235
+ }
236
+ //# sourceMappingURL=trimmer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trimmer.js","sourceRoot":"","sources":["../../src/engine/trimmer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * OpenCarly Rule Formatter
3
+ *
4
+ * Formats loaded rules into a text block for injection into the system prompt.
5
+ * Output is wrapped in <carly-rules> XML tags.
6
+ */
7
+ import type { LoadedRules } from "../engine/loader";
8
+ /**
9
+ * Format loaded rules into an injectable text block.
10
+ *
11
+ * Output sections (in order):
12
+ * 1. Context bracket status
13
+ * 2. Bracket-specific rules
14
+ * 3. Active star-commands (prominent)
15
+ * 4. DEVMODE instruction
16
+ * 5. Loaded domains summary
17
+ * 6. Always-on domain rules
18
+ * 7. Keyword-matched domain rules
19
+ * 8. Exclusion notices
20
+ * 9. Available (not loaded) domains
21
+ */
22
+ export declare function formatRules(loaded: LoadedRules): string;
23
+ //# sourceMappingURL=formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../src/formatter/formatter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAkBpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CA8HvD"}