@pixelguild/loom 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 (117) hide show
  1. package/dist/cli/index.d.ts +3 -0
  2. package/dist/cli/index.d.ts.map +1 -0
  3. package/dist/cli/index.js +45 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/cli/init.d.ts +9 -0
  6. package/dist/cli/init.d.ts.map +1 -0
  7. package/dist/cli/init.js +129 -0
  8. package/dist/cli/init.js.map +1 -0
  9. package/dist/cli/output.d.ts +6 -0
  10. package/dist/cli/output.d.ts.map +1 -0
  11. package/dist/cli/output.js +20 -0
  12. package/dist/cli/output.js.map +1 -0
  13. package/dist/cli/status.d.ts +13 -0
  14. package/dist/cli/status.d.ts.map +1 -0
  15. package/dist/cli/status.js +69 -0
  16. package/dist/cli/status.js.map +1 -0
  17. package/dist/config/config-loader.d.ts +9 -0
  18. package/dist/config/config-loader.d.ts.map +1 -0
  19. package/dist/config/config-loader.js +64 -0
  20. package/dist/config/config-loader.js.map +1 -0
  21. package/dist/config/types.d.ts +32 -0
  22. package/dist/config/types.d.ts.map +1 -0
  23. package/dist/config/types.js +2 -0
  24. package/dist/config/types.js.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +32 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/lib/slugify.d.ts +2 -0
  30. package/dist/lib/slugify.d.ts.map +1 -0
  31. package/dist/lib/slugify.js +9 -0
  32. package/dist/lib/slugify.js.map +1 -0
  33. package/dist/lib/tokens.d.ts +2 -0
  34. package/dist/lib/tokens.d.ts.map +1 -0
  35. package/dist/lib/tokens.js +15 -0
  36. package/dist/lib/tokens.js.map +1 -0
  37. package/dist/providers/ollama-provider.d.ts +11 -0
  38. package/dist/providers/ollama-provider.d.ts.map +1 -0
  39. package/dist/providers/ollama-provider.js +37 -0
  40. package/dist/providers/ollama-provider.js.map +1 -0
  41. package/dist/providers/openai-provider.d.ts +10 -0
  42. package/dist/providers/openai-provider.d.ts.map +1 -0
  43. package/dist/providers/openai-provider.js +25 -0
  44. package/dist/providers/openai-provider.js.map +1 -0
  45. package/dist/providers/provider-factory.d.ts +5 -0
  46. package/dist/providers/provider-factory.d.ts.map +1 -0
  47. package/dist/providers/provider-factory.js +19 -0
  48. package/dist/providers/provider-factory.js.map +1 -0
  49. package/dist/providers/types.d.ts +11 -0
  50. package/dist/providers/types.d.ts.map +1 -0
  51. package/dist/providers/types.js +2 -0
  52. package/dist/providers/types.js.map +1 -0
  53. package/dist/providers/vertex-provider.d.ts +12 -0
  54. package/dist/providers/vertex-provider.d.ts.map +1 -0
  55. package/dist/providers/vertex-provider.js +32 -0
  56. package/dist/providers/vertex-provider.js.map +1 -0
  57. package/dist/server.d.ts +4 -0
  58. package/dist/server.d.ts.map +1 -0
  59. package/dist/server.js +143 -0
  60. package/dist/server.js.map +1 -0
  61. package/dist/storage/archive.d.ts +22 -0
  62. package/dist/storage/archive.d.ts.map +1 -0
  63. package/dist/storage/archive.js +114 -0
  64. package/dist/storage/archive.js.map +1 -0
  65. package/dist/storage/context-file.d.ts +16 -0
  66. package/dist/storage/context-file.d.ts.map +1 -0
  67. package/dist/storage/context-file.js +68 -0
  68. package/dist/storage/context-file.js.map +1 -0
  69. package/dist/storage/database.d.ts +28 -0
  70. package/dist/storage/database.d.ts.map +1 -0
  71. package/dist/storage/database.js +195 -0
  72. package/dist/storage/database.js.map +1 -0
  73. package/dist/storage/intelligent-archive.d.ts +36 -0
  74. package/dist/storage/intelligent-archive.d.ts.map +1 -0
  75. package/dist/storage/intelligent-archive.js +183 -0
  76. package/dist/storage/intelligent-archive.js.map +1 -0
  77. package/dist/tools/archive-context.d.ts +7 -0
  78. package/dist/tools/archive-context.d.ts.map +1 -0
  79. package/dist/tools/archive-context.js +60 -0
  80. package/dist/tools/archive-context.js.map +1 -0
  81. package/dist/tools/consult-peer.d.ts +18 -0
  82. package/dist/tools/consult-peer.d.ts.map +1 -0
  83. package/dist/tools/consult-peer.js +48 -0
  84. package/dist/tools/consult-peer.js.map +1 -0
  85. package/dist/tools/create-manifest.d.ts +14 -0
  86. package/dist/tools/create-manifest.d.ts.map +1 -0
  87. package/dist/tools/create-manifest.js +23 -0
  88. package/dist/tools/create-manifest.js.map +1 -0
  89. package/dist/tools/find-pattern.d.ts +14 -0
  90. package/dist/tools/find-pattern.d.ts.map +1 -0
  91. package/dist/tools/find-pattern.js +13 -0
  92. package/dist/tools/find-pattern.js.map +1 -0
  93. package/dist/tools/get-context.d.ts +9 -0
  94. package/dist/tools/get-context.d.ts.map +1 -0
  95. package/dist/tools/get-context.js +11 -0
  96. package/dist/tools/get-context.js.map +1 -0
  97. package/dist/tools/get-manifest.d.ts +21 -0
  98. package/dist/tools/get-manifest.d.ts.map +1 -0
  99. package/dist/tools/get-manifest.js +53 -0
  100. package/dist/tools/get-manifest.js.map +1 -0
  101. package/dist/tools/get-session-status.d.ts +3 -0
  102. package/dist/tools/get-session-status.d.ts.map +1 -0
  103. package/dist/tools/get-session-status.js +20 -0
  104. package/dist/tools/get-session-status.js.map +1 -0
  105. package/dist/tools/log-context.d.ts +13 -0
  106. package/dist/tools/log-context.d.ts.map +1 -0
  107. package/dist/tools/log-context.js +29 -0
  108. package/dist/tools/log-context.js.map +1 -0
  109. package/dist/tools/save-pattern.d.ts +15 -0
  110. package/dist/tools/save-pattern.d.ts.map +1 -0
  111. package/dist/tools/save-pattern.js +19 -0
  112. package/dist/tools/save-pattern.js.map +1 -0
  113. package/dist/types.d.ts +54 -0
  114. package/dist/types.d.ts.map +1 -0
  115. package/dist/types.js +2 -0
  116. package/dist/types.js.map +1 -0
  117. package/package.json +53 -0
@@ -0,0 +1,195 @@
1
+ import Database from 'better-sqlite3';
2
+ import crypto from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ export class LoomDatabase {
6
+ db;
7
+ constructor(dbPath) {
8
+ const dir = path.dirname(dbPath);
9
+ fs.mkdirSync(dir, { recursive: true });
10
+ this.db = new Database(dbPath);
11
+ this.db.pragma('journal_mode = WAL');
12
+ this.migrate();
13
+ }
14
+ migrate() {
15
+ this.db.exec(`
16
+ CREATE TABLE IF NOT EXISTS patterns (
17
+ id TEXT PRIMARY KEY,
18
+ name TEXT NOT NULL,
19
+ tags TEXT NOT NULL DEFAULT '[]',
20
+ source_project TEXT,
21
+ content TEXT NOT NULL,
22
+ embedding_hash TEXT,
23
+ created_at INTEGER NOT NULL,
24
+ last_used_at INTEGER NOT NULL,
25
+ use_count INTEGER NOT NULL DEFAULT 0
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS projects (
29
+ path TEXT PRIMARY KEY,
30
+ name TEXT NOT NULL,
31
+ last_active_at INTEGER NOT NULL,
32
+ session_count INTEGER NOT NULL DEFAULT 0,
33
+ privacy_level TEXT NOT NULL DEFAULT 'none'
34
+ );
35
+ `);
36
+ const ftsExists = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='patterns_fts'").get();
37
+ if (!ftsExists) {
38
+ this.db.exec(`
39
+ CREATE VIRTUAL TABLE patterns_fts USING fts5(
40
+ name, tags, content, content=patterns, content_rowid=rowid
41
+ );
42
+
43
+ CREATE TRIGGER IF NOT EXISTS patterns_ai AFTER INSERT ON patterns BEGIN
44
+ INSERT INTO patterns_fts(rowid, name, tags, content)
45
+ VALUES (new.rowid, new.name, new.tags, new.content);
46
+ END;
47
+
48
+ CREATE TRIGGER IF NOT EXISTS patterns_ad AFTER DELETE ON patterns BEGIN
49
+ INSERT INTO patterns_fts(patterns_fts, rowid, name, tags, content)
50
+ VALUES ('delete', old.rowid, old.name, old.tags, old.content);
51
+ END;
52
+
53
+ CREATE TRIGGER IF NOT EXISTS patterns_au AFTER UPDATE ON patterns BEGIN
54
+ INSERT INTO patterns_fts(patterns_fts, rowid, name, tags, content)
55
+ VALUES ('delete', old.rowid, old.name, old.tags, old.content);
56
+ INSERT INTO patterns_fts(rowid, name, tags, content)
57
+ VALUES (new.rowid, new.name, new.tags, new.content);
58
+ END;
59
+ `);
60
+ }
61
+ }
62
+ savePattern(input) {
63
+ const id = crypto.randomUUID();
64
+ const now = Math.floor(Date.now() / 1000);
65
+ const tagsJson = JSON.stringify(input.tags);
66
+ this.db.prepare(`
67
+ INSERT INTO patterns (id, name, tags, source_project, content, created_at, last_used_at, use_count)
68
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0)
69
+ `).run(id, input.name, tagsJson, input.sourceProject, input.content, now, now);
70
+ return {
71
+ id,
72
+ name: input.name,
73
+ tags: input.tags,
74
+ source_project: input.sourceProject,
75
+ content: input.content,
76
+ embedding_hash: null,
77
+ created_at: now,
78
+ last_used_at: now,
79
+ use_count: 0,
80
+ };
81
+ }
82
+ getPattern(id) {
83
+ const row = this.db.prepare('SELECT * FROM patterns WHERE id = ?').get(id);
84
+ if (!row)
85
+ return undefined;
86
+ return this.rowToPattern(row);
87
+ }
88
+ findPatterns(query, opts) {
89
+ const limit = opts?.limit ?? 5;
90
+ // Try FTS5 first, then fall back to LIKE-based search for fuzzy matching
91
+ let rows = this.findPatternsFts(query, opts, limit);
92
+ if (rows.length === 0) {
93
+ rows = this.findPatternsLike(query, opts, limit);
94
+ }
95
+ if (rows.length === 0) {
96
+ return [];
97
+ }
98
+ const now = Math.floor(Date.now() / 1000);
99
+ const updateStmt = this.db.prepare('UPDATE patterns SET use_count = use_count + 1, last_used_at = ? WHERE id = ?');
100
+ const updateMany = this.db.transaction((ids) => {
101
+ for (const id of ids) {
102
+ updateStmt.run(now, id);
103
+ }
104
+ });
105
+ updateMany(rows.map(r => r.id));
106
+ return rows.map(row => ({
107
+ ...this.rowToPattern(row),
108
+ use_count: row.use_count + 1,
109
+ last_used_at: now,
110
+ }));
111
+ }
112
+ findPatternsFts(query, opts, limit) {
113
+ const params = [];
114
+ // FTS5 query: wrap individual terms in quotes to handle special characters
115
+ const ftsQuery = query
116
+ .split(/\s+/)
117
+ .filter(term => term.length > 0)
118
+ .map(term => `"${term}"`)
119
+ .join(' ');
120
+ let sql = `
121
+ SELECT patterns.* FROM patterns_fts
122
+ JOIN patterns ON patterns.rowid = patterns_fts.rowid
123
+ WHERE patterns_fts MATCH ?
124
+ `;
125
+ params.push(ftsQuery);
126
+ if (opts?.tags && opts.tags.length > 0) {
127
+ for (const tag of opts.tags) {
128
+ sql += ' AND patterns.tags LIKE ?';
129
+ params.push(`%"${tag}"%`);
130
+ }
131
+ }
132
+ if (opts?.project) {
133
+ sql += ' AND patterns.source_project = ?';
134
+ params.push(opts.project);
135
+ }
136
+ sql += ' ORDER BY rank LIMIT ?';
137
+ params.push(limit);
138
+ return this.db.prepare(sql).all(...params);
139
+ }
140
+ findPatternsLike(query, opts, limit) {
141
+ const params = [];
142
+ const likePattern = `%${query}%`;
143
+ const normalizedPattern = `%${query.replace(/\s+/g, '').toLowerCase()}%`;
144
+ // Match against raw text OR space-normalized text (e.g. "setup" matches "Set up")
145
+ let sql = `
146
+ SELECT * FROM patterns
147
+ WHERE (
148
+ name LIKE ? OR content LIKE ? OR tags LIKE ?
149
+ OR REPLACE(LOWER(name), ' ', '') LIKE ?
150
+ OR REPLACE(LOWER(content), ' ', '') LIKE ?
151
+ )
152
+ `;
153
+ params.push(likePattern, likePattern, likePattern, normalizedPattern, normalizedPattern);
154
+ if (opts?.tags && opts.tags.length > 0) {
155
+ for (const tag of opts.tags) {
156
+ sql += ' AND tags LIKE ?';
157
+ params.push(`%"${tag}"%`);
158
+ }
159
+ }
160
+ if (opts?.project) {
161
+ sql += ' AND source_project = ?';
162
+ params.push(opts.project);
163
+ }
164
+ sql += ' ORDER BY last_used_at DESC LIMIT ?';
165
+ params.push(limit);
166
+ return this.db.prepare(sql).all(...params);
167
+ }
168
+ upsertProject(projectPath, name) {
169
+ const now = Math.floor(Date.now() / 1000);
170
+ this.db.prepare(`
171
+ INSERT INTO projects (path, name, last_active_at, session_count, privacy_level)
172
+ VALUES (?, ?, ?, 0, 'none')
173
+ ON CONFLICT(path) DO UPDATE SET
174
+ last_active_at = excluded.last_active_at,
175
+ name = excluded.name
176
+ `).run(projectPath, name, now);
177
+ }
178
+ listProjects() {
179
+ return this.db.prepare('SELECT * FROM projects ORDER BY last_active_at DESC').all();
180
+ }
181
+ listTables() {
182
+ const rows = this.db.prepare("SELECT name FROM sqlite_master WHERE type IN ('table', 'shadow') ORDER BY name").all();
183
+ return rows.map(r => r.name);
184
+ }
185
+ close() {
186
+ this.db.close();
187
+ }
188
+ rowToPattern(row) {
189
+ return {
190
+ ...row,
191
+ tags: JSON.parse(row.tags),
192
+ };
193
+ }
194
+ }
195
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { ContextFile } from './context-file.js';
2
+ export interface IntelligentArchiveResult {
3
+ archived: boolean;
4
+ archiveFile?: string;
5
+ method: 'intelligent' | 'mechanical';
6
+ warning?: string;
7
+ }
8
+ /**
9
+ * Builds the prompt sent to the Claude subprocess for intelligent archiving.
10
+ * Instructs Claude to produce exactly three sections with token budgets.
11
+ */
12
+ export declare function buildArchivePrompt(contextContent: string, carryForward: string | null): string;
13
+ /**
14
+ * Reads the most recent archive and extracts the ## Carry-Forward Summary section.
15
+ * Returns null if no archives exist or if the latest archive has no carry-forward section.
16
+ */
17
+ export declare function getLatestCarryForward(contextFile: ContextFile): string | null;
18
+ /**
19
+ * Validates that the Claude subprocess output contains all three required sections.
20
+ */
21
+ export declare function validateArchiveOutput(output: string): boolean;
22
+ /**
23
+ * Checks whether the `claude` CLI is available on the system PATH.
24
+ */
25
+ export declare function isClaudeAvailable(): boolean;
26
+ /**
27
+ * Performs an intelligent archive by spawning a Claude CLI subprocess.
28
+ *
29
+ * Reads the current context, gets carry-forward from the latest archive,
30
+ * builds a prompt, and asks Claude to produce a structured narrative archive.
31
+ *
32
+ * On success: writes the archive file and rewrites context.md with an Active State header.
33
+ * On failure: returns a result with archived=false and a warning message.
34
+ */
35
+ export declare function performIntelligentArchive(projectRoot: string, contextFile: ContextFile): Promise<IntelligentArchiveResult>;
36
+ //# sourceMappingURL=intelligent-archive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intelligent-archive.d.ts","sourceRoot":"","sources":["../../src/storage/intelligent-archive.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKhD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,aAAa,GAAG,YAAY,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CA+B9F;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAc7E;AA+BD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAY7D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAO3C;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,WAAW,GACvB,OAAO,CAAC,wBAAwB,CAAC,CAyEnC"}
@@ -0,0 +1,183 @@
1
+ import { execFileSync, execFile as execFileCb } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { generateArchiveFilename } from './archive.js';
4
+ const execFile = promisify(execFileCb);
5
+ /**
6
+ * Builds the prompt sent to the Claude subprocess for intelligent archiving.
7
+ * Instructs Claude to produce exactly three sections with token budgets.
8
+ */
9
+ export function buildArchivePrompt(contextContent, carryForward) {
10
+ const carryForwardSection = carryForward
11
+ ? `<previous-archive-carry-forward>\n${carryForward}\n</previous-archive-carry-forward>`
12
+ : 'No previous archives exist for this project.';
13
+ return `You are an archiving assistant for a software development session log. Your job is to compress the session context into a structured archive that preserves all important information while reducing token count.
14
+
15
+ ${carryForwardSection}
16
+
17
+ <current-context>
18
+ ${contextContent}
19
+ </current-context>
20
+
21
+ Produce a markdown document with EXACTLY these three sections (use ## headings):
22
+
23
+ ## Carry-Forward Summary
24
+ (~2000 tokens) Compress ALL project history — both the previous carry-forward (if any) and the current context — into a single unified summary. Include project purpose, architecture decisions, current state, and anything a future session needs to know. This section accumulates across archives.
25
+
26
+ ## Session Narrative
27
+ (~2000 tokens) Tell the chronological story of THIS session only. Use cause-and-effect: what was attempted, what happened, what was learned. Include specific error messages, version numbers, and configuration details that would be needed to reproduce or continue the work.
28
+
29
+ ## Reference
30
+ (~1000 tokens) Structured quick-reference with these sub-headings:
31
+ ### Decisions Made
32
+ ### Files Changed
33
+ ### Issues Resolved
34
+ ### Dead Ends
35
+ ### Open Questions
36
+ ### Current State
37
+
38
+ Output ONLY the markdown document. No preamble, no explanation.`;
39
+ }
40
+ /**
41
+ * Reads the most recent archive and extracts the ## Carry-Forward Summary section.
42
+ * Returns null if no archives exist or if the latest archive has no carry-forward section.
43
+ */
44
+ export function getLatestCarryForward(contextFile) {
45
+ const archives = contextFile.listArchives();
46
+ if (archives.length === 0) {
47
+ return null;
48
+ }
49
+ // Archives are sorted alphabetically by listArchives(), so the last one is the latest
50
+ const latestArchive = archives[archives.length - 1];
51
+ const content = contextFile.readArchive(latestArchive);
52
+ if (!content) {
53
+ return null;
54
+ }
55
+ return extractCarryForwardSection(content);
56
+ }
57
+ /**
58
+ * Extracts the ## Carry-Forward Summary section from archive content.
59
+ * Returns everything between "## Carry-Forward Summary" and the next "## " heading (or end of file).
60
+ */
61
+ function extractCarryForwardSection(content) {
62
+ const lines = content.split('\n');
63
+ let startIndex = -1;
64
+ for (let i = 0; i < lines.length; i++) {
65
+ if (lines[i].trim() === '## Carry-Forward Summary') {
66
+ startIndex = i + 1;
67
+ continue;
68
+ }
69
+ // If we've found the start and hit the next ## heading, stop
70
+ if (startIndex >= 0 && /^## /.test(lines[i])) {
71
+ const section = lines.slice(startIndex, i).join('\n').trim();
72
+ return section || null;
73
+ }
74
+ }
75
+ // If we found the heading but no subsequent ## heading, take everything to the end
76
+ if (startIndex >= 0) {
77
+ const section = lines.slice(startIndex).join('\n').trim();
78
+ return section || null;
79
+ }
80
+ return null;
81
+ }
82
+ /**
83
+ * Validates that the Claude subprocess output contains all three required sections.
84
+ */
85
+ export function validateArchiveOutput(output) {
86
+ if (!output || output.trim().length === 0) {
87
+ return false;
88
+ }
89
+ const requiredSections = [
90
+ '## Carry-Forward Summary',
91
+ '## Session Narrative',
92
+ '## Reference',
93
+ ];
94
+ return requiredSections.every(section => output.includes(section));
95
+ }
96
+ /**
97
+ * Checks whether the `claude` CLI is available on the system PATH.
98
+ */
99
+ export function isClaudeAvailable() {
100
+ try {
101
+ execFileSync('which', ['claude'], { stdio: 'pipe' });
102
+ return true;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ }
108
+ /**
109
+ * Performs an intelligent archive by spawning a Claude CLI subprocess.
110
+ *
111
+ * Reads the current context, gets carry-forward from the latest archive,
112
+ * builds a prompt, and asks Claude to produce a structured narrative archive.
113
+ *
114
+ * On success: writes the archive file and rewrites context.md with an Active State header.
115
+ * On failure: returns a result with archived=false and a warning message.
116
+ */
117
+ export async function performIntelligentArchive(projectRoot, contextFile) {
118
+ const content = contextFile.read();
119
+ if (!content || content.trim().length === 0) {
120
+ return {
121
+ archived: false,
122
+ method: 'intelligent',
123
+ warning: 'No context content to archive',
124
+ };
125
+ }
126
+ const carryForward = getLatestCarryForward(contextFile);
127
+ const prompt = buildArchivePrompt(content, carryForward);
128
+ let output;
129
+ try {
130
+ const { stdout } = await execFile('claude', ['-p', prompt, '--dangerously-skip-permissions'], {
131
+ timeout: 60_000,
132
+ maxBuffer: 1024 * 1024,
133
+ cwd: projectRoot,
134
+ });
135
+ output = stdout;
136
+ }
137
+ catch (error) {
138
+ const message = error instanceof Error ? error.message : String(error);
139
+ console.error(`[loom] Intelligent archive failed: ${message}`);
140
+ return {
141
+ archived: false,
142
+ method: 'intelligent',
143
+ warning: `Claude subprocess failed: ${message}`,
144
+ };
145
+ }
146
+ if (!validateArchiveOutput(output)) {
147
+ console.error('[loom] Intelligent archive output failed validation');
148
+ return {
149
+ archived: false,
150
+ method: 'intelligent',
151
+ warning: 'Claude subprocess output did not contain all required sections',
152
+ };
153
+ }
154
+ // Generate archive filename and write the archive
155
+ const existingArchives = contextFile.listArchives();
156
+ const today = new Date().toISOString().slice(0, 10);
157
+ const archiveFilename = generateArchiveFilename(existingArchives, today);
158
+ const archiveContent = [
159
+ `# Archive: ${archiveFilename}`,
160
+ `Archived: ${new Date().toISOString()}`,
161
+ `Method: intelligent`,
162
+ '',
163
+ output.trim(),
164
+ '',
165
+ ].join('\n');
166
+ contextFile.writeArchive(archiveFilename, archiveContent);
167
+ // Rewrite context.md with Active State header
168
+ const newContent = [
169
+ '# Session Context',
170
+ '',
171
+ '## Active State',
172
+ `Archived to: ${archiveFilename}`,
173
+ `Method: intelligent`,
174
+ '',
175
+ ].join('\n');
176
+ contextFile.write(newContent);
177
+ return {
178
+ archived: true,
179
+ archiveFile: archiveFilename,
180
+ method: 'intelligent',
181
+ };
182
+ }
183
+ //# sourceMappingURL=intelligent-archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intelligent-archive.js","sourceRoot":"","sources":["../../src/storage/intelligent-archive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { LoomContext, ArchiveResult } from '../types.js';
2
+ interface ArchiveContextInput {
3
+ force?: boolean;
4
+ }
5
+ export declare function handleArchiveContext(ctx: LoomContext, input: ArchiveContextInput): Promise<ArchiveResult>;
6
+ export {};
7
+ //# sourceMappingURL=archive-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-context.d.ts","sourceRoot":"","sources":["../../src/tools/archive-context.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE9D,UAAU,mBAAmB;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,aAAa,CAAC,CAyCxB"}
@@ -0,0 +1,60 @@
1
+ import { ContextFile } from '../storage/context-file.js';
2
+ import { performMechanicalArchive } from '../storage/archive.js';
3
+ import { isClaudeAvailable, performIntelligentArchive } from '../storage/intelligent-archive.js';
4
+ import { countTokens } from '../lib/tokens.js';
5
+ export async function handleArchiveContext(ctx, input) {
6
+ const archiveThreshold = ctx.config.archive_thresholds.archive;
7
+ const contextFile = new ContextFile(ctx.projectRoot);
8
+ contextFile.initialize();
9
+ const content = contextFile.read();
10
+ const previousTokenCount = countTokens(content);
11
+ if (!input.force && previousTokenCount <= archiveThreshold) {
12
+ return {
13
+ archived: false,
14
+ message: `Context is ${previousTokenCount} tokens (threshold: ${archiveThreshold}). Use force: true to archive anyway.`,
15
+ };
16
+ }
17
+ // Try intelligent archiving if Claude CLI is available
18
+ if (isClaudeAvailable()) {
19
+ const intelligentResult = await performIntelligentArchive(ctx.projectRoot, contextFile);
20
+ if (intelligentResult.archived) {
21
+ const newContent = contextFile.read();
22
+ const newTokenCount = countTokens(newContent);
23
+ return {
24
+ archived: true,
25
+ archiveFile: intelligentResult.archiveFile,
26
+ previousTokenCount,
27
+ newTokenCount,
28
+ method: 'intelligent',
29
+ };
30
+ }
31
+ // Intelligent archive failed — fall through to mechanical with the warning
32
+ console.error(`[loom] Falling back to mechanical archive: ${intelligentResult.warning}`);
33
+ return fallbackToMechanical(ctx.projectRoot, contextFile, previousTokenCount, intelligentResult.warning);
34
+ }
35
+ // Claude CLI not available — use mechanical archiving directly
36
+ const claudeWarning = 'Claude CLI not found on PATH. Using mechanical archiving. Install Claude Code for intelligent archives.';
37
+ console.error(`[loom] ${claudeWarning}`);
38
+ return fallbackToMechanical(ctx.projectRoot, contextFile, previousTokenCount, claudeWarning);
39
+ }
40
+ function fallbackToMechanical(projectRoot, contextFile, previousTokenCount, warning) {
41
+ const result = performMechanicalArchive(projectRoot);
42
+ if (!result.archived) {
43
+ return {
44
+ ...result,
45
+ method: 'mechanical',
46
+ warning,
47
+ };
48
+ }
49
+ const newContent = contextFile.read();
50
+ const newTokenCount = countTokens(newContent);
51
+ return {
52
+ archived: true,
53
+ archiveFile: result.archiveFile,
54
+ previousTokenCount,
55
+ newTokenCount,
56
+ method: 'mechanical',
57
+ warning,
58
+ };
59
+ }
60
+ //# sourceMappingURL=archive-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-context.js","sourceRoot":"","sources":["../../src/tools/archive-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAO/C,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,GAAgB,EAChB,KAA0B;IAE1B,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC;IAC/D,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACrD,WAAW,CAAC,UAAU,EAAE,CAAC;IAEzB,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;IACnC,MAAM,kBAAkB,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAEhD,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,kBAAkB,IAAI,gBAAgB,EAAE,CAAC;QAC3D,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,cAAc,kBAAkB,uBAAuB,gBAAgB,uCAAuC;SACxH,CAAC;IACJ,CAAC;IAED,uDAAuD;IACvD,IAAI,iBAAiB,EAAE,EAAE,CAAC;QACxB,MAAM,iBAAiB,GAAG,MAAM,yBAAyB,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QAExF,IAAI,iBAAiB,CAAC,QAAQ,EAAE,CAAC;YAC/B,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;YACtC,MAAM,aAAa,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;YAE9C,OAAO;gBACL,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE,iBAAiB,CAAC,WAAW;gBAC1C,kBAAkB;gBAClB,aAAa;gBACb,MAAM,EAAE,aAAa;aACtB,CAAC;QACJ,CAAC;QAED,2EAA2E;QAC3E,OAAO,CAAC,KAAK,CAAC,8CAA8C,iBAAiB,CAAC,OAAO,EAAE,CAAC,CAAC;QACzF,OAAO,oBAAoB,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC3G,CAAC;IAED,+DAA+D;IAC/D,MAAM,aAAa,GAAG,yGAAyG,CAAC;IAChI,OAAO,CAAC,KAAK,CAAC,UAAU,aAAa,EAAE,CAAC,CAAC;IACzC,OAAO,oBAAoB,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,CAAC,CAAC;AAC/F,CAAC;AAED,SAAS,oBAAoB,CAC3B,WAAmB,EACnB,WAAwB,EACxB,kBAA0B,EAC1B,OAAgB;IAEhB,MAAM,MAAM,GAAG,wBAAwB,CAAC,WAAW,CAAC,CAAC;IAErD,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrB,OAAO;YACL,GAAG,MAAM;YACT,MAAM,EAAE,YAAY;YACpB,OAAO;SACR,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;IACtC,MAAM,aAAa,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IAE9C,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,kBAAkB;QAClB,aAAa;QACb,MAAM,EAAE,YAAY;QACpB,OAAO;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { LoomContext } from '../types.js';
2
+ interface ConsultPeerInput {
3
+ problem: string;
4
+ context: string;
5
+ code?: string;
6
+ question: string;
7
+ provider?: string;
8
+ }
9
+ interface ConsultPeerResult {
10
+ provider?: string;
11
+ model?: string;
12
+ response?: string;
13
+ tokens_used?: number;
14
+ error?: string;
15
+ }
16
+ export declare function handleConsultPeer(ctx: LoomContext, input: ConsultPeerInput): Promise<ConsultPeerResult>;
17
+ export {};
18
+ //# sourceMappingURL=consult-peer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consult-peer.d.ts","sourceRoot":"","sources":["../../src/tools/consult-peer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,iBAAiB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiBD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAuC5B"}
@@ -0,0 +1,48 @@
1
+ import { createProvider, resolveProviderName } from '../providers/provider-factory.js';
2
+ function buildPrompt(input) {
3
+ const parts = [
4
+ `## Problem\n${input.problem}`,
5
+ `## Context\n${input.context}`,
6
+ ];
7
+ if (input.code) {
8
+ parts.push(`## Code\n\`\`\`\n${input.code}\n\`\`\``);
9
+ }
10
+ parts.push(`## Question\n${input.question}`);
11
+ return parts.join('\n\n');
12
+ }
13
+ export async function handleConsultPeer(ctx, input) {
14
+ const { peer_consultation } = ctx.config;
15
+ if (!peer_consultation.enabled) {
16
+ return { error: 'Peer consultation is disabled in config' };
17
+ }
18
+ const providerName = resolveProviderName(input.provider, ctx.config);
19
+ if (!peer_consultation.allowed_providers.includes(providerName)) {
20
+ return { error: `Provider "${providerName}" is not in allowed_providers` };
21
+ }
22
+ let provider;
23
+ try {
24
+ provider = createProvider(providerName, ctx.config);
25
+ }
26
+ catch (err) {
27
+ const msg = err instanceof Error ? err.message : String(err);
28
+ return { error: `Failed to create provider: ${msg}` };
29
+ }
30
+ if (!provider.isAvailable()) {
31
+ return { error: `Provider "${providerName}" is not available. Check credentials/configuration.` };
32
+ }
33
+ const prompt = buildPrompt(input);
34
+ try {
35
+ const result = await provider.call(prompt);
36
+ return {
37
+ provider: providerName,
38
+ model: result.model,
39
+ response: result.response,
40
+ tokens_used: result.tokens_used,
41
+ };
42
+ }
43
+ catch (err) {
44
+ const msg = err instanceof Error ? err.message : String(err);
45
+ return { error: `Provider call failed: ${msg}` };
46
+ }
47
+ }
48
+ //# sourceMappingURL=consult-peer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consult-peer.js","sourceRoot":"","sources":["../../src/tools/consult-peer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAkBvF,SAAS,WAAW,CAAC,KAAuB;IAC1C,MAAM,KAAK,GAAG;QACZ,eAAe,KAAK,CAAC,OAAO,EAAE;QAC9B,eAAe,KAAK,CAAC,OAAO,EAAE;KAC/B,CAAC;IAEF,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,oBAAoB,KAAK,CAAC,IAAI,UAAU,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,gBAAgB,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE7C,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAgB,EAChB,KAAuB;IAEvB,MAAM,EAAE,iBAAiB,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;IAEzC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;QAC/B,OAAO,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC;IAC9D,CAAC;IAED,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAErE,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAChE,OAAO,EAAE,KAAK,EAAE,aAAa,YAAY,+BAA+B,EAAE,CAAC;IAC7E,CAAC;IAED,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,cAAc,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,EAAE,KAAK,EAAE,8BAA8B,GAAG,EAAE,EAAE,CAAC;IACxD,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;QAC5B,OAAO,EAAE,KAAK,EAAE,aAAa,YAAY,sDAAsD,EAAE,CAAC;IACpG,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAElC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3C,OAAO;YACL,QAAQ,EAAE,YAAY;YACtB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,WAAW,EAAE,MAAM,CAAC,WAAW;SAChC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,EAAE,KAAK,EAAE,yBAAyB,GAAG,EAAE,EAAE,CAAC;IACnD,CAAC;AACH,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { LoomContext } from '../types.js';
2
+ interface CreateManifestInput {
3
+ name: string;
4
+ content: string;
5
+ }
6
+ interface CreateManifestResult {
7
+ name: string;
8
+ slug: string;
9
+ path: string;
10
+ launch_command: string;
11
+ }
12
+ export declare function handleCreateManifest(ctx: LoomContext, input: CreateManifestInput): Promise<CreateManifestResult>;
13
+ export {};
14
+ //# sourceMappingURL=create-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-manifest.d.ts","sourceRoot":"","sources":["../../src/tools/create-manifest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,oBAAoB;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAsB/B"}
@@ -0,0 +1,23 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { slugify } from '../lib/slugify.js';
4
+ export async function handleCreateManifest(ctx, input) {
5
+ const slug = slugify(input.name);
6
+ const filename = `${slug}.md`;
7
+ const relativePath = path.join('docs', 'loom', 'manifests', filename);
8
+ const absolutePath = path.join(ctx.manifestsDir, filename);
9
+ const launchCommand = `claude --dangerously-skip-permissions -p "Read ${relativePath} and implement it fully, logging progress to Loom as you go"`;
10
+ const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
11
+ const header = `# Manifest: ${input.name}\nGenerated: ${timestamp}\nSource project: ${ctx.projectRoot}\n`;
12
+ const footer = `\n---\nLaunch command:\n\`\`\`bash\n${launchCommand}\n\`\`\`\n`;
13
+ const fullContent = `${header}\n${input.content}\n${footer}`;
14
+ fs.mkdirSync(ctx.manifestsDir, { recursive: true });
15
+ fs.writeFileSync(absolutePath, fullContent);
16
+ return {
17
+ name: input.name,
18
+ slug,
19
+ path: relativePath,
20
+ launch_command: launchCommand,
21
+ };
22
+ }
23
+ //# sourceMappingURL=create-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-manifest.js","sourceRoot":"","sources":["../../src/tools/create-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAe5C,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,GAAgB,EAChB,KAA0B;IAE1B,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,GAAG,IAAI,KAAK,CAAC;IAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IACtE,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAC3D,MAAM,aAAa,GAAG,kDAAkD,YAAY,8DAA8D,CAAC;IAEnJ,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAE1E,MAAM,MAAM,GAAG,eAAe,KAAK,CAAC,IAAI,gBAAgB,SAAS,qBAAqB,GAAG,CAAC,WAAW,IAAI,CAAC;IAC1G,MAAM,MAAM,GAAG,uCAAuC,aAAa,YAAY,CAAC;IAChF,MAAM,WAAW,GAAG,GAAG,MAAM,KAAK,KAAK,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;IAE7D,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IAE5C,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI;QACJ,IAAI,EAAE,YAAY;QAClB,cAAc,EAAE,aAAa;KAC9B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { LoomContext, PatternRecord } from '../types.js';
2
+ interface FindPatternInput {
3
+ query: string;
4
+ tags?: string[];
5
+ project?: string;
6
+ limit?: number;
7
+ }
8
+ interface FindPatternResult {
9
+ patterns: PatternRecord[];
10
+ total: number;
11
+ }
12
+ export declare function handleFindPattern(ctx: LoomContext, input: FindPatternInput): Promise<FindPatternResult>;
13
+ export {};
14
+ //# sourceMappingURL=find-pattern.d.ts.map