klyro 0.1.1

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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/agent/anthropic-adapter.d.ts +75 -0
  4. package/dist/agent/anthropic-adapter.js +297 -0
  5. package/dist/agent/message.d.ts +40 -0
  6. package/dist/agent/message.js +21 -0
  7. package/dist/agent/observation.d.ts +47 -0
  8. package/dist/agent/observation.js +53 -0
  9. package/dist/agent/provider-adapter.d.ts +101 -0
  10. package/dist/agent/provider-adapter.js +254 -0
  11. package/dist/agent/registry.d.ts +42 -0
  12. package/dist/agent/registry.js +86 -0
  13. package/dist/agent/retry.d.ts +34 -0
  14. package/dist/agent/retry.js +91 -0
  15. package/dist/agent/runtime.d.ts +130 -0
  16. package/dist/agent/runtime.js +221 -0
  17. package/dist/agent/worker-spawner.d.ts +41 -0
  18. package/dist/agent/worker-spawner.js +76 -0
  19. package/dist/chat.d.ts +39 -0
  20. package/dist/chat.js +235 -0
  21. package/dist/chat.js.map +1 -0
  22. package/dist/cli/eval.d.ts +75 -0
  23. package/dist/cli/eval.js +190 -0
  24. package/dist/cli/repl.d.ts +15 -0
  25. package/dist/cli/repl.js +194 -0
  26. package/dist/cli/run.d.ts +67 -0
  27. package/dist/cli/run.js +198 -0
  28. package/dist/cli/slash/parser.d.ts +39 -0
  29. package/dist/cli/slash/parser.js +47 -0
  30. package/dist/context/level6.d.ts +45 -0
  31. package/dist/context/level6.js +231 -0
  32. package/dist/context/level7.d.ts +84 -0
  33. package/dist/context/level7.js +156 -0
  34. package/dist/context/project-map.d.ts +48 -0
  35. package/dist/context/project-map.js +438 -0
  36. package/dist/context/repo-map.d.ts +30 -0
  37. package/dist/context/repo-map.js +168 -0
  38. package/dist/context/selector.d.ts +22 -0
  39. package/dist/context/selector.js +37 -0
  40. package/dist/context/snippets.d.ts +17 -0
  41. package/dist/context/snippets.js +15 -0
  42. package/dist/context/tokenizer.d.ts +49 -0
  43. package/dist/context/tokenizer.js +97 -0
  44. package/dist/eval/harness.d.ts +48 -0
  45. package/dist/eval/harness.js +149 -0
  46. package/dist/eval/tasks.d.ts +6 -0
  47. package/dist/eval/tasks.js +98 -0
  48. package/dist/index.d.ts +14 -0
  49. package/dist/index.js +128 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/persistence/audit.d.ts +75 -0
  52. package/dist/persistence/audit.js +15 -0
  53. package/dist/persistence/store.d.ts +67 -0
  54. package/dist/persistence/store.js +106 -0
  55. package/dist/policy/approval.d.ts +35 -0
  56. package/dist/policy/approval.js +54 -0
  57. package/dist/policy/engine.d.ts +76 -0
  58. package/dist/policy/engine.js +163 -0
  59. package/dist/policy/path-guard.d.ts +30 -0
  60. package/dist/policy/path-guard.js +100 -0
  61. package/dist/policy/secret-redactor.d.ts +19 -0
  62. package/dist/policy/secret-redactor.js +52 -0
  63. package/dist/providers.d.ts +23 -0
  64. package/dist/providers.js +83 -0
  65. package/dist/repl.d.ts +9 -0
  66. package/dist/repl.js +170 -0
  67. package/dist/repl.js.map +1 -0
  68. package/dist/tools/fs/edit-file.d.ts +48 -0
  69. package/dist/tools/fs/edit-file.js +84 -0
  70. package/dist/tools/fs/list-dir.d.ts +40 -0
  71. package/dist/tools/fs/list-dir.js +72 -0
  72. package/dist/tools/fs/read-file.d.ts +66 -0
  73. package/dist/tools/fs/read-file.js +75 -0
  74. package/dist/tools/fs/write-file.d.ts +24 -0
  75. package/dist/tools/fs/write-file.js +48 -0
  76. package/dist/tools/git/git-diff.d.ts +35 -0
  77. package/dist/tools/git/git-diff.js +68 -0
  78. package/dist/tools/git/git-status.d.ts +29 -0
  79. package/dist/tools/git/git-status.js +57 -0
  80. package/dist/tools/normalize.d.ts +31 -0
  81. package/dist/tools/normalize.js +88 -0
  82. package/dist/tools/registry.d.ts +23 -0
  83. package/dist/tools/registry.js +87 -0
  84. package/dist/tools/schema.d.ts +29 -0
  85. package/dist/tools/schema.js +183 -0
  86. package/dist/tools/search/dependencies.d.ts +44 -0
  87. package/dist/tools/search/dependencies.js +188 -0
  88. package/dist/tools/search/glob.d.ts +26 -0
  89. package/dist/tools/search/glob.js +88 -0
  90. package/dist/tools/search/grep.d.ts +52 -0
  91. package/dist/tools/search/grep.js +125 -0
  92. package/dist/tools/search/recent-files.d.ts +33 -0
  93. package/dist/tools/search/recent-files.js +100 -0
  94. package/dist/tools/search/search-files.d.ts +39 -0
  95. package/dist/tools/search/search-files.js +110 -0
  96. package/dist/tools/shell/shell-exec.d.ts +60 -0
  97. package/dist/tools/shell/shell-exec.js +133 -0
  98. package/dist/tools/types.d.ts +54 -0
  99. package/dist/tools/types.js +15 -0
  100. package/dist/tools/verify/run-verify.d.ts +60 -0
  101. package/dist/tools/verify/run-verify.js +115 -0
  102. package/dist/tui/app.d.ts +27 -0
  103. package/dist/tui/app.js +79 -0
  104. package/dist/tui/app.test.d.ts +1 -0
  105. package/dist/tui/app.test.js +77 -0
  106. package/dist/tui/status.d.ts +21 -0
  107. package/dist/tui/status.js +13 -0
  108. package/dist/tui/status.test.d.ts +1 -0
  109. package/dist/tui/status.test.js +39 -0
  110. package/dist/tui/transcript.d.ts +40 -0
  111. package/dist/tui/transcript.js +28 -0
  112. package/dist/tui/transcript.test.d.ts +1 -0
  113. package/dist/tui/transcript.test.js +71 -0
  114. package/dist/verification/detect.d.ts +29 -0
  115. package/dist/verification/detect.js +85 -0
  116. package/dist/verification/engine.d.ts +25 -0
  117. package/dist/verification/engine.js +57 -0
  118. package/package.json +58 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Tool registry — single source of truth for tools the model can use.
3
+ */
4
+ import { readFileTool } from './fs/read-file.js';
5
+ import { writeFileTool } from './fs/write-file.js';
6
+ import { editFileTool } from './fs/edit-file.js';
7
+ import { listDirTool } from './fs/list-dir.js';
8
+ import { globTool } from './search/glob.js';
9
+ import { grepTool } from './search/grep.js';
10
+ import { searchFilesTool } from './search/search-files.js';
11
+ import { recentFilesTool } from './search/recent-files.js';
12
+ import { dependenciesTool } from './search/dependencies.js';
13
+ import { shellExecTool } from './shell/shell-exec.js';
14
+ import { gitStatusTool } from './git/git-status.js';
15
+ import { gitDiffTool } from './git/git-diff.js';
16
+ import { runVerifyTool } from './verify/run-verify.js';
17
+ import { zodToJsonSchema } from './schema.js';
18
+ export class ToolRegistry {
19
+ tools = new Map();
20
+ register(tool) {
21
+ if (this.tools.has(tool.name)) {
22
+ throw new Error(`Tool already registered: ${tool.name}`);
23
+ }
24
+ this.tools.set(tool.name, tool);
25
+ return this;
26
+ }
27
+ get(name) {
28
+ return this.tools.get(name);
29
+ }
30
+ list() {
31
+ return [...this.tools.values()];
32
+ }
33
+ /** Tools in OpenAI-style tool definition format. */
34
+ toOpenAITools() {
35
+ return this.list().map((t) => ({
36
+ type: 'function',
37
+ function: {
38
+ name: t.name,
39
+ description: t.description,
40
+ parameters: zodToJsonSchema(t.inputSchema),
41
+ },
42
+ }));
43
+ }
44
+ /** Cached JSON Schemas keyed by tool name (for adapter consumers). */
45
+ jsonSchemas() {
46
+ const out = {};
47
+ for (const t of this.list()) {
48
+ out[t.name] = zodToJsonSchema(t.inputSchema);
49
+ }
50
+ return out;
51
+ }
52
+ async execute(name, rawInput, ctx) {
53
+ const tool = this.tools.get(name);
54
+ if (!tool) {
55
+ return { ok: false, error: { code: 'UNKNOWN_TOOL', message: `Unknown tool: ${name}` } };
56
+ }
57
+ const parsed = tool.inputSchema.safeParse(rawInput);
58
+ if (!parsed.success) {
59
+ return {
60
+ ok: false,
61
+ error: {
62
+ code: 'INVALID_INPUT',
63
+ message: `Invalid input for ${name}`,
64
+ details: parsed.error.issues,
65
+ },
66
+ };
67
+ }
68
+ return tool.execute(parsed.data, ctx);
69
+ }
70
+ }
71
+ export const builtinRegistry = () => {
72
+ const r = new ToolRegistry();
73
+ r.register(readFileTool);
74
+ r.register(writeFileTool);
75
+ r.register(editFileTool);
76
+ r.register(listDirTool);
77
+ r.register(globTool);
78
+ r.register(grepTool);
79
+ r.register(searchFilesTool);
80
+ r.register(recentFilesTool);
81
+ r.register(dependenciesTool);
82
+ r.register(shellExecTool);
83
+ r.register(gitStatusTool);
84
+ r.register(gitDiffTool);
85
+ r.register(runVerifyTool);
86
+ return r;
87
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * JSON Schema derivation from Zod 4 schemas.
3
+ *
4
+ * Zod 4 stores constraints on the schema object itself (e.g. `s.minLength`),
5
+ * not in `_zod.def`. We read them directly. For Zod 3 the constraints live
6
+ * in `_def`, so this module falls back to that.
7
+ *
8
+ * Supports: string, number, integer, boolean, enum, literal, array, object,
9
+ * optional, nullable, default, union, description, min/max length, min/max.
10
+ */
11
+ import type { z } from 'zod';
12
+ export interface JsonSchema {
13
+ type?: string;
14
+ description?: string;
15
+ enum?: unknown[];
16
+ properties?: Record<string, JsonSchema>;
17
+ required?: string[];
18
+ items?: JsonSchema;
19
+ additionalProperties?: boolean | JsonSchema;
20
+ default?: unknown;
21
+ minimum?: number;
22
+ maximum?: number;
23
+ minLength?: number;
24
+ maxLength?: number;
25
+ pattern?: string;
26
+ anyOf?: JsonSchema[];
27
+ format?: string;
28
+ }
29
+ export declare function zodToJsonSchema(schema: z.ZodType<unknown>): JsonSchema;
@@ -0,0 +1,183 @@
1
+ /**
2
+ * JSON Schema derivation from Zod 4 schemas.
3
+ *
4
+ * Zod 4 stores constraints on the schema object itself (e.g. `s.minLength`),
5
+ * not in `_zod.def`. We read them directly. For Zod 3 the constraints live
6
+ * in `_def`, so this module falls back to that.
7
+ *
8
+ * Supports: string, number, integer, boolean, enum, literal, array, object,
9
+ * optional, nullable, default, union, description, min/max length, min/max.
10
+ */
11
+ function getType(s) {
12
+ const zod4 = s._zod?.def?.type;
13
+ if (zod4)
14
+ return zod4;
15
+ return s._def?.typeName;
16
+ }
17
+ function getDesc(s) {
18
+ return s.description ?? s._zod?.def?.description ?? s._def?.description;
19
+ }
20
+ export function zodToJsonSchema(schema) {
21
+ return convert(schema, new Set());
22
+ }
23
+ function convert(schema, seen) {
24
+ if (!schema || typeof schema !== 'object')
25
+ return { type: 'object' };
26
+ if (seen.has(schema))
27
+ return { type: 'object' };
28
+ seen.add(schema);
29
+ const typeName = getType(schema);
30
+ const desc = getDesc(schema);
31
+ const result = {};
32
+ if (desc)
33
+ result.description = desc;
34
+ switch (typeName) {
35
+ case 'ZodString':
36
+ case 'string': {
37
+ result.type = 'string';
38
+ if (typeof schema.minLength === 'number')
39
+ result.minLength = schema.minLength;
40
+ if (typeof schema.maxLength === 'number')
41
+ result.maxLength = schema.maxLength;
42
+ if (schema.pattern) {
43
+ result.pattern = typeof schema.pattern === 'string' ? schema.pattern : schema.pattern.source ?? undefined;
44
+ }
45
+ break;
46
+ }
47
+ case 'ZodNumber':
48
+ case 'number': {
49
+ result.type = schema.isInt === true ? 'integer' : 'number';
50
+ if (typeof schema.minValue === 'number')
51
+ result.minimum = schema.minValue;
52
+ if (typeof schema.maxValue === 'number')
53
+ result.maximum = schema.maxValue;
54
+ break;
55
+ }
56
+ case 'ZodBigInt':
57
+ case 'bigint':
58
+ result.type = 'integer';
59
+ break;
60
+ case 'ZodBoolean':
61
+ case 'boolean':
62
+ result.type = 'boolean';
63
+ break;
64
+ case 'ZodNull':
65
+ case 'null':
66
+ result.type = 'null';
67
+ break;
68
+ case 'ZodDate':
69
+ case 'date':
70
+ result.type = 'string';
71
+ result.format = 'date-time';
72
+ break;
73
+ case 'ZodArray':
74
+ case 'array':
75
+ result.type = 'array';
76
+ if (schema.element)
77
+ result.items = convert(schema.element, seen);
78
+ break;
79
+ case 'ZodObject':
80
+ case 'object': {
81
+ result.type = 'object';
82
+ const shape = schema.shape ?? {};
83
+ const props = {};
84
+ const required = [];
85
+ for (const [key, value] of Object.entries(shape)) {
86
+ const child = convert(value, seen);
87
+ props[key] = child;
88
+ const childType = getType(value);
89
+ if (childType !== 'ZodOptional' && childType !== 'ZodDefault' && childType !== 'optional' && childType !== 'default') {
90
+ required.push(key);
91
+ }
92
+ }
93
+ result.properties = props;
94
+ if (required.length > 0)
95
+ result.required = required;
96
+ result.additionalProperties = false;
97
+ break;
98
+ }
99
+ case 'ZodEnum':
100
+ case 'enum': {
101
+ const entries = schema.entries ??
102
+ schema.values ??
103
+ schema._zod?.def?.entries ??
104
+ {};
105
+ const values = Object.values(entries);
106
+ if (values.length > 0) {
107
+ result.type = typeof values[0] === 'number' ? 'number' : 'string';
108
+ result.enum = values;
109
+ }
110
+ else {
111
+ result.type = 'string';
112
+ }
113
+ break;
114
+ }
115
+ case 'ZodLiteral':
116
+ case 'literal': {
117
+ const v = schema.value;
118
+ if (typeof v === 'string')
119
+ result.type = 'string';
120
+ else if (typeof v === 'number')
121
+ result.type = 'number';
122
+ else if (typeof v === 'boolean')
123
+ result.type = 'boolean';
124
+ else if (v === null)
125
+ result.type = 'null';
126
+ result.enum = [v];
127
+ break;
128
+ }
129
+ case 'ZodUnion':
130
+ case 'union':
131
+ case 'ZodDiscriminatedUnion':
132
+ case 'discriminated_union': {
133
+ const options = schema.options ?? [];
134
+ result.anyOf = options.map((o) => convert(o, seen));
135
+ break;
136
+ }
137
+ case 'ZodOptional':
138
+ case 'optional':
139
+ case 'ZodNullable':
140
+ case 'nullable': {
141
+ const inner = schema.innerType ?? schema._zod?.def?.innerType;
142
+ if (inner)
143
+ return convert(inner, seen);
144
+ break;
145
+ }
146
+ case 'ZodDefault':
147
+ case 'default': {
148
+ const inner = schema.innerType ?? schema._zod?.def?.innerType;
149
+ if (inner) {
150
+ const innerSchema = convert(inner, seen);
151
+ // Zod 3: defaultValue is a function. Zod 4: it's a value on _zod.def.
152
+ const dv = schema.defaultValue ??
153
+ schema._zod?.def?.defaultValue;
154
+ if (typeof dv === 'function') {
155
+ try {
156
+ innerSchema.default = dv();
157
+ }
158
+ catch {
159
+ /* ignore */
160
+ }
161
+ }
162
+ else if (dv !== undefined) {
163
+ innerSchema.default = dv;
164
+ }
165
+ return innerSchema;
166
+ }
167
+ break;
168
+ }
169
+ case 'ZodPipe':
170
+ case 'pipe':
171
+ case 'ZodTransform':
172
+ case 'transform':
173
+ if (schema.innerType)
174
+ return convert(schema.innerType, seen);
175
+ result.type = 'object';
176
+ result.additionalProperties = true;
177
+ break;
178
+ default:
179
+ result.type = 'object';
180
+ result.additionalProperties = true;
181
+ }
182
+ return result;
183
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * dependencies — surface direct dependencies of the repo.
3
+ *
4
+ * Reads package.json / requirements.txt / go.mod / Cargo.toml / pyproject.toml
5
+ * and returns the first-party name + version list. Does NOT recurse into
6
+ * transitive deps (that's a "lockfile parse" feature, not Level 6).
7
+ */
8
+ import { z } from 'zod';
9
+ declare const InputSchema: z.ZodObject<{
10
+ cwd: z.ZodOptional<z.ZodString>;
11
+ ecosystem: z.ZodOptional<z.ZodEnum<{
12
+ auto: "auto";
13
+ npm: "npm";
14
+ python: "python";
15
+ go: "go";
16
+ rust: "rust";
17
+ }>>;
18
+ maxResults: z.ZodOptional<z.ZodNumber>;
19
+ }, z.core.$strip>;
20
+ export interface Dep {
21
+ name: string;
22
+ version?: string;
23
+ ecosystem: 'npm' | 'python' | 'go' | 'rust';
24
+ }
25
+ export interface DependenciesOutput {
26
+ ecosystem?: string;
27
+ dependencies: Dep[];
28
+ source?: string;
29
+ }
30
+ export declare const dependenciesTool: import("../types.js").Tool<{
31
+ cwd?: string | undefined;
32
+ ecosystem?: "auto" | "npm" | "python" | "go" | "rust" | undefined;
33
+ maxResults?: number | undefined;
34
+ }, {
35
+ dependencies: never[];
36
+ ecosystem?: undefined;
37
+ source?: undefined;
38
+ } | {
39
+ ecosystem: "npm" | "python" | "go" | "rust" | undefined;
40
+ source: string;
41
+ dependencies: Dep[];
42
+ }>;
43
+ export type DependenciesInput = z.infer<typeof InputSchema>;
44
+ export {};
@@ -0,0 +1,188 @@
1
+ /**
2
+ * dependencies — surface direct dependencies of the repo.
3
+ *
4
+ * Reads package.json / requirements.txt / go.mod / Cargo.toml / pyproject.toml
5
+ * and returns the first-party name + version list. Does NOT recurse into
6
+ * transitive deps (that's a "lockfile parse" feature, not Level 6).
7
+ */
8
+ import * as fs from 'node:fs/promises';
9
+ import * as path from 'node:path';
10
+ import { z } from 'zod';
11
+ import { defineTool } from '../types.js';
12
+ import { resolveWithinCwd } from '../../policy/path-guard.js';
13
+ import { safe } from '../normalize.js';
14
+ const InputSchema = z.object({
15
+ cwd: z.string().optional(),
16
+ ecosystem: z.enum(['auto', 'npm', 'python', 'go', 'rust']).optional().describe('Force a specific ecosystem; default auto-detect'),
17
+ maxResults: z.number().int().min(1).max(500).optional().describe('Cap (default 200)'),
18
+ });
19
+ const DEFAULT_MAX = 200;
20
+ const MAX_BYTES = 64 * 1024;
21
+ async function readJson(p) {
22
+ try {
23
+ const s = await fs.stat(p);
24
+ if (!s.isFile() || s.size > MAX_BYTES)
25
+ return null;
26
+ return JSON.parse(await fs.readFile(p, 'utf-8'));
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ async function readText(p) {
33
+ try {
34
+ const s = await fs.stat(p);
35
+ if (!s.isFile() || s.size > MAX_BYTES)
36
+ return null;
37
+ return await fs.readFile(p, 'utf-8');
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ function parseNpmDeps(pkg) {
44
+ const out = [];
45
+ for (const key of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
46
+ const d = pkg[key];
47
+ if (!d || typeof d !== 'object')
48
+ continue;
49
+ const section = d;
50
+ for (const [name, version] of Object.entries(section)) {
51
+ if (typeof version === 'string')
52
+ out.push({ name, version, ecosystem: 'npm' });
53
+ else
54
+ out.push({ name, ecosystem: 'npm' });
55
+ }
56
+ }
57
+ return out;
58
+ }
59
+ function parsePythonReq(text) {
60
+ const out = [];
61
+ for (const raw of text.split(/\r?\n/)) {
62
+ const l = raw.trim();
63
+ if (!l || l.startsWith('#') || l.startsWith('-'))
64
+ continue;
65
+ // PEP 508: name [@ url] [version_spec] [; markers]
66
+ const m = /^([A-Za-z0-9_.\-]+)(?:\s*([@=<>~!]+)\s*([^\s;]+))?/.exec(l);
67
+ if (!m || !m[1])
68
+ continue;
69
+ const name = m[1];
70
+ const version = m[3];
71
+ const dep = version ? { name, version, ecosystem: 'python' } : { name, ecosystem: 'python' };
72
+ out.push(dep);
73
+ }
74
+ return out;
75
+ }
76
+ function parseGoMod(text) {
77
+ const out = [];
78
+ // require ( ... ) block, or single-line: require foo v1.2.3
79
+ const block = /\brequire\s+\(([\s\S]*?)\)/g;
80
+ let m;
81
+ while ((m = block.exec(text)) !== null) {
82
+ for (const line of m[1].split(/\r?\n/)) {
83
+ const t = line.trim();
84
+ if (!t || t.startsWith('//'))
85
+ continue;
86
+ const parts = t.split(/\s+/);
87
+ if (parts.length >= 2 && parts[0])
88
+ out.push({ name: parts[0], version: parts[1], ecosystem: 'go' });
89
+ }
90
+ }
91
+ for (const line of text.split(/\r?\n/)) {
92
+ const t = line.trim();
93
+ const m2 = /^require\s+([^\s]+)\s+(\S+)/.exec(t);
94
+ if (m2 && m2[1] && m2[2])
95
+ out.push({ name: m2[1], version: m2[2], ecosystem: 'go' });
96
+ }
97
+ return out;
98
+ }
99
+ function parseCargo(text) {
100
+ const out = [];
101
+ let inDeps = false;
102
+ for (const line of text.split(/\r?\n/)) {
103
+ const t = line.trim();
104
+ if (t === '[dependencies]') {
105
+ inDeps = true;
106
+ continue;
107
+ }
108
+ if (t.startsWith('[') && t !== '[dependencies]') {
109
+ inDeps = false;
110
+ continue;
111
+ }
112
+ if (!inDeps)
113
+ continue;
114
+ if (!t || t.startsWith('#'))
115
+ continue;
116
+ const m = /^([A-Za-z0-9_\-]+)\s*=\s*(?:"([^"]+)"|'([^']+)'|(\{[\s\S]*?\}))/.exec(t);
117
+ if (m && m[1]) {
118
+ const version = m[2] ?? m[3] ?? undefined;
119
+ out.push({ name: m[1], version, ecosystem: 'rust' });
120
+ }
121
+ }
122
+ return out;
123
+ }
124
+ export const dependenciesTool = defineTool({
125
+ name: 'dependencies',
126
+ description: 'List direct dependencies of the repo (npm, Python, Go, or Rust). Use to understand the project\'s surface area without reading every manifest file.',
127
+ inputSchema: InputSchema,
128
+ execute: async (input, ctx) => {
129
+ return safe(async () => {
130
+ const base = input.cwd ? resolveWithinCwd(ctx.cwd, input.cwd).resolved : ctx.cwd;
131
+ const max = input.maxResults ?? DEFAULT_MAX;
132
+ const want = input.ecosystem ?? 'auto';
133
+ const tryNpm = async () => {
134
+ const pkg = await readJson(path.join(base, 'package.json'));
135
+ if (!pkg)
136
+ return null;
137
+ return { source: 'package.json', deps: parseNpmDeps(pkg) };
138
+ };
139
+ const tryPy = async () => {
140
+ const t = await readText(path.join(base, 'requirements.txt'));
141
+ if (t)
142
+ return { source: 'requirements.txt', deps: parsePythonReq(t) };
143
+ const pp = await readText(path.join(base, 'pyproject.toml'));
144
+ if (pp) {
145
+ // minimal: only the [project] dependencies = [...] form
146
+ const m = /\[project\][\s\S]*?dependencies\s*=\s*\[([\s\S]*?)\]/.exec(pp);
147
+ if (m && m[1]) {
148
+ const items = m[1].split(',').map((s) => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
149
+ return { source: 'pyproject.toml', deps: items.map((name) => ({ name, ecosystem: 'python' })) };
150
+ }
151
+ }
152
+ return null;
153
+ };
154
+ const tryGo = async () => {
155
+ const t = await readText(path.join(base, 'go.mod'));
156
+ if (!t)
157
+ return null;
158
+ return { source: 'go.mod', deps: parseGoMod(t) };
159
+ };
160
+ const tryCargo = async () => {
161
+ const t = await readText(path.join(base, 'Cargo.toml'));
162
+ if (!t)
163
+ return null;
164
+ return { source: 'Cargo.toml', deps: parseCargo(t) };
165
+ };
166
+ let picked = null;
167
+ if (want === 'npm')
168
+ picked = await tryNpm();
169
+ else if (want === 'python')
170
+ picked = await tryPy();
171
+ else if (want === 'go')
172
+ picked = await tryGo();
173
+ else if (want === 'rust')
174
+ picked = await tryCargo();
175
+ else {
176
+ picked = (await tryNpm()) ?? (await tryPy()) ?? (await tryGo()) ?? (await tryCargo());
177
+ }
178
+ if (!picked) {
179
+ return { dependencies: [] };
180
+ }
181
+ return {
182
+ ecosystem: picked.deps[0]?.ecosystem,
183
+ source: picked.source,
184
+ dependencies: picked.deps.slice(0, max),
185
+ };
186
+ });
187
+ },
188
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * glob — find files matching a pattern. Bounded output.
3
+ */
4
+ import { z } from 'zod';
5
+ declare const InputSchema: z.ZodObject<{
6
+ pattern: z.ZodString;
7
+ cwd: z.ZodOptional<z.ZodString>;
8
+ maxResults: z.ZodOptional<z.ZodNumber>;
9
+ }, z.core.$strip>;
10
+ export interface GlobOutput {
11
+ pattern: string;
12
+ matches: string[];
13
+ truncated: boolean;
14
+ }
15
+ export declare const globTool: import("../types.js").Tool<{
16
+ pattern: string;
17
+ cwd?: string | undefined;
18
+ maxResults?: number | undefined;
19
+ }, {
20
+ pattern: string;
21
+ matches: string[];
22
+ truncated: false;
23
+ }>;
24
+ export declare function globToRegex(pattern: string): RegExp;
25
+ export type GlobInput = z.infer<typeof InputSchema>;
26
+ export {};
@@ -0,0 +1,88 @@
1
+ /**
2
+ * glob — find files matching a pattern. Bounded output.
3
+ */
4
+ import * as path from 'node:path';
5
+ import * as fs from 'node:fs/promises';
6
+ import { z } from 'zod';
7
+ import { defineTool } from '../types.js';
8
+ import { resolveWithinCwd } from '../../policy/path-guard.js';
9
+ import { safe } from '../normalize.js';
10
+ const InputSchema = z.object({
11
+ pattern: z.string().min(1).describe('Glob pattern, e.g. "**/*.ts" or "src/**/*.json"'),
12
+ cwd: z.string().optional().describe('Base directory; defaults to workspace cwd'),
13
+ maxResults: z.number().int().min(1).max(10_000).optional().describe('Cap results (default 1000)'),
14
+ });
15
+ const DEFAULT_MAX = 1000;
16
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.turbo', 'coverage', '.klyro']);
17
+ export const globTool = defineTool({
18
+ name: 'glob',
19
+ description: 'Find files matching a glob pattern. Standard glob syntax (** for recursion, * for any segment). Skips common build dirs.',
20
+ inputSchema: InputSchema,
21
+ execute: async (input, ctx) => {
22
+ return safe(async () => {
23
+ const base = input.cwd ? resolveWithinCwd(ctx.cwd, input.cwd).resolved : ctx.cwd;
24
+ const matcher = globToRegex(input.pattern);
25
+ const max = input.maxResults ?? DEFAULT_MAX;
26
+ const matches = [];
27
+ let truncated = false;
28
+ await walk(base, base, matcher, max + 1, matches, () => (truncated = true));
29
+ // Normalize to relative, forward-slash paths from ctx.cwd (so
30
+ // tool inputs/outputs are stable across Windows and POSIX).
31
+ const rel = matches.slice(0, max).map((m) => path.relative(ctx.cwd, m).split(path.sep).join('/'));
32
+ return { pattern: input.pattern, matches: rel, truncated };
33
+ });
34
+ },
35
+ });
36
+ export function globToRegex(pattern) {
37
+ // Convert glob to regex. Special-cases: a leading `**/` should match
38
+ // zero or more path segments (so `**/*.ts` matches both `a.ts` and
39
+ // `sub/a.ts`), and a bare `**` should match anything including paths
40
+ // with no separators.
41
+ let p = pattern;
42
+ const anchor = p.startsWith('**/') ? '^(?:.*/)?' : p === '**' ? '^.*' : '^';
43
+ if (p.startsWith('**/'))
44
+ p = p.slice(3);
45
+ const escaped = p
46
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
47
+ .replace(/\*\*/g, '::DOUBLESTAR::')
48
+ .replace(/\*/g, '[^/]*')
49
+ .replace(/\?/g, '[^/]')
50
+ .replace(/::DOUBLESTAR::/g, '.*');
51
+ return new RegExp(`${anchor}${escaped}$`);
52
+ }
53
+ async function walk(root, dir, matcher, cap, out, onCap) {
54
+ if (out.length >= cap)
55
+ return;
56
+ let names;
57
+ try {
58
+ names = await fs.readdir(dir);
59
+ }
60
+ catch {
61
+ return;
62
+ }
63
+ for (const name of names) {
64
+ if (out.length >= cap)
65
+ return;
66
+ if (SKIP_DIRS.has(name))
67
+ continue;
68
+ const full = path.join(dir, name);
69
+ let isDir = false;
70
+ try {
71
+ const s = await fs.lstat(full);
72
+ if (s.isSymbolicLink())
73
+ continue; // skip symlinks for safety
74
+ isDir = s.isDirectory();
75
+ }
76
+ catch {
77
+ continue;
78
+ }
79
+ const rel = path.relative(root, full).split(path.sep).join('/');
80
+ if (matcher.test(rel)) {
81
+ out.push(full);
82
+ if (out.length >= cap)
83
+ onCap();
84
+ }
85
+ if (isDir)
86
+ await walk(root, full, matcher, cap, out, onCap);
87
+ }
88
+ }