@kkeve/suite 0.0.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 (63) hide show
  1. package/dist/agent/factory.d.ts +25 -0
  2. package/dist/agent/factory.js +99 -0
  3. package/dist/agent/index.d.ts +3 -0
  4. package/dist/agent/index.js +7 -0
  5. package/dist/agent/loader.d.ts +22 -0
  6. package/dist/agent/loader.js +117 -0
  7. package/dist/agent/types.d.ts +9 -0
  8. package/dist/agent/types.js +2 -0
  9. package/dist/agent-loop/complete-task.d.ts +28 -0
  10. package/dist/agent-loop/complete-task.js +70 -0
  11. package/dist/agent-loop/index.d.ts +16 -0
  12. package/dist/agent-loop/index.js +26 -0
  13. package/dist/agent-loop/loop.d.ts +42 -0
  14. package/dist/agent-loop/loop.js +282 -0
  15. package/dist/agent-loop/runtime.d.ts +40 -0
  16. package/dist/agent-loop/runtime.js +163 -0
  17. package/dist/agent-loop/types.d.ts +161 -0
  18. package/dist/agent-loop/types.js +105 -0
  19. package/dist/context/index.d.ts +8 -0
  20. package/dist/context/index.js +29 -0
  21. package/dist/context/tokenCalculation.d.ts +55 -0
  22. package/dist/context/tokenCalculation.js +95 -0
  23. package/dist/context/truncation.d.ts +67 -0
  24. package/dist/context/truncation.js +142 -0
  25. package/dist/context/types.d.ts +7 -0
  26. package/dist/context/types.js +27 -0
  27. package/dist/index.d.ts +23 -0
  28. package/dist/index.js +45 -0
  29. package/dist/llm/OpenAIClient.d.ts +31 -0
  30. package/dist/llm/OpenAIClient.js +455 -0
  31. package/dist/llm/constants.d.ts +2 -0
  32. package/dist/llm/constants.js +6 -0
  33. package/dist/llm/errors.d.ts +25 -0
  34. package/dist/llm/errors.js +51 -0
  35. package/dist/llm/index.d.ts +49 -0
  36. package/dist/llm/index.js +158 -0
  37. package/dist/llm/types.d.ts +124 -0
  38. package/dist/llm/types.js +2 -0
  39. package/dist/llm/utils.d.ts +29 -0
  40. package/dist/llm/utils.js +284 -0
  41. package/dist/skill/frontmatter-parser.d.ts +50 -0
  42. package/dist/skill/frontmatter-parser.js +152 -0
  43. package/dist/skill/index.d.ts +12 -0
  44. package/dist/skill/index.js +17 -0
  45. package/dist/skill/loader.d.ts +96 -0
  46. package/dist/skill/loader.js +256 -0
  47. package/dist/tools/ask-user.d.ts +8 -0
  48. package/dist/tools/ask-user.js +47 -0
  49. package/dist/tools/bash.d.ts +47 -0
  50. package/dist/tools/bash.js +213 -0
  51. package/dist/tools/edit-file.d.ts +9 -0
  52. package/dist/tools/edit-file.js +76 -0
  53. package/dist/tools/glob.d.ts +8 -0
  54. package/dist/tools/glob.js +101 -0
  55. package/dist/tools/grep.d.ts +10 -0
  56. package/dist/tools/grep.js +76 -0
  57. package/dist/tools/index.d.ts +24 -0
  58. package/dist/tools/index.js +47 -0
  59. package/dist/tools/read-files.d.ts +8 -0
  60. package/dist/tools/read-files.js +81 -0
  61. package/dist/tools/write-file.d.ts +8 -0
  62. package/dist/tools/write-file.js +63 -0
  63. package/package.json +59 -0
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ /**
3
+ * SKILL.md Frontmatter Parser
4
+ *
5
+ * Reference: gemini-cli skillLoader.ts (skills/skillLoader.ts L34-110)
6
+ * Parses YAML frontmatter from SKILL.md files to extract name, description,
7
+ * and allowed-tools. Uses the 'yaml' npm package for robust YAML parsing,
8
+ * with a fallback simple parser for malformed YAML (e.g. descriptions with colons).
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.FRONTMATTER_REGEX = void 0;
15
+ exports.parseSkillFrontmatter = parseSkillFrontmatter;
16
+ exports.parseFrontmatterYaml = parseFrontmatterYaml;
17
+ const yaml_1 = __importDefault(require("yaml"));
18
+ /**
19
+ * Regex for extracting frontmatter from markdown files.
20
+ * Matches ---\n<yaml content>\n--- at the start of the file.
21
+ */
22
+ exports.FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
23
+ /**
24
+ * Parse frontmatter from a SKILL.md file string.
25
+ *
26
+ * Returns the name, description, allowedTools from the YAML frontmatter,
27
+ * and the body (content after the frontmatter).
28
+ *
29
+ * @param content The full SKILL.md file content
30
+ * @returns Parsed frontmatter and body, or null if no valid frontmatter found
31
+ */
32
+ function parseSkillFrontmatter(content) {
33
+ const match = content.match(exports.FRONTMATTER_REGEX);
34
+ if (!match) {
35
+ return null;
36
+ }
37
+ const frontmatter = parseFrontmatterYaml(match[1]);
38
+ if (!frontmatter) {
39
+ return null;
40
+ }
41
+ // Sanitize name for use as a filename/directory name
42
+ const sanitizedName = frontmatter.name.replace(/[:\\/<>*?"|]/g, '-');
43
+ // Body is the content after the closing ---
44
+ const body = match[2]?.trim() ?? '';
45
+ return {
46
+ name: sanitizedName,
47
+ description: frontmatter.description,
48
+ allowedTools: frontmatter.allowedTools,
49
+ outputType: frontmatter.outputType,
50
+ body,
51
+ };
52
+ }
53
+ /**
54
+ * Parse YAML frontmatter content using the yaml library
55
+ * with a fallback to simple key-value parsing.
56
+ *
57
+ * Reference: gemini-cli skillLoader.ts parseFrontmatter (L41-61)
58
+ *
59
+ * Uses YAML.parse() (yaml v2 API) instead of the deprecated YAML.load().
60
+ */
61
+ function parseFrontmatterYaml(yamlContent) {
62
+ // Try YAML parsing first (handles complex cases like multi-line descriptions, arrays)
63
+ try {
64
+ const parsed = yaml_1.default.parse(yamlContent);
65
+ if (parsed && typeof parsed === 'object') {
66
+ const record = parsed;
67
+ const name = typeof record.name === 'string' ? record.name : undefined;
68
+ const description = typeof record.description === 'string' ? record.description : undefined;
69
+ if (!name || !description) {
70
+ return null;
71
+ }
72
+ // Extract allowed-tools (array or string)
73
+ let allowedTools;
74
+ const allowedToolsRaw = record['allowed-tools'];
75
+ if (Array.isArray(allowedToolsRaw)) {
76
+ allowedTools = allowedToolsRaw.map(String);
77
+ }
78
+ else if (typeof allowedToolsRaw === 'string') {
79
+ allowedTools = allowedToolsRaw.split(/\s+/).filter(Boolean);
80
+ }
81
+ // Extract output-type (string)
82
+ const outputType = typeof record['output-type'] === 'string' ? record['output-type'] : undefined;
83
+ return { name, description, allowedTools, outputType };
84
+ }
85
+ }
86
+ catch (yamlError) {
87
+ // YAML parsing failed — fall back to simple parser
88
+ console.warn('[SkillLoader] YAML frontmatter parsing failed, using simple parser:', yamlError);
89
+ }
90
+ return parseSimpleFrontmatter(yamlContent);
91
+ }
92
+ /**
93
+ * Simple frontmatter parser that extracts name, description, and allowed-tools fields.
94
+ * Handles cases where values contain colons that would break YAML parsing.
95
+ *
96
+ * Reference: gemini-cli skillLoader.ts parseSimpleFrontmatter (L67-110)
97
+ */
98
+ function parseSimpleFrontmatter(content) {
99
+ const lines = content.split(/\r?\n/);
100
+ let name;
101
+ let description;
102
+ let allowedTools;
103
+ let outputType;
104
+ for (let i = 0; i < lines.length; i++) {
105
+ const line = lines[i];
106
+ // Match "name:" at the start of the line
107
+ const nameMatch = line.match(/^\s*name:\s*(.*)$/);
108
+ if (nameMatch) {
109
+ name = nameMatch[1].trim();
110
+ continue;
111
+ }
112
+ // Match "description:" at the start of the line
113
+ const descMatch = line.match(/^\s*description:\s*(.*)$/);
114
+ if (descMatch) {
115
+ const descLines = [descMatch[1].trim()];
116
+ // Check for multi-line description (indented continuation lines)
117
+ while (i + 1 < lines.length) {
118
+ const nextLine = lines[i + 1];
119
+ // If next line is indented, it's a continuation of the description
120
+ if (nextLine.match(/^[ \t]+\S/)) {
121
+ descLines.push(nextLine.trim());
122
+ i++;
123
+ }
124
+ else {
125
+ break;
126
+ }
127
+ }
128
+ description = descLines.filter(Boolean).join(' ');
129
+ continue;
130
+ }
131
+ // Match "output-type:" at the start of the line
132
+ const outputMatch = line.match(/^\s*output-type:\s*(.*)$/);
133
+ if (outputMatch) {
134
+ outputType = outputMatch[1].trim();
135
+ continue;
136
+ }
137
+ // Match "allowed-tools:" at the start of the line
138
+ const toolsMatch = line.match(/^\s*allowed-tools:\s*(.*)$/);
139
+ if (toolsMatch) {
140
+ const toolsRaw = toolsMatch[1].trim();
141
+ // Simple format: space-separated list (e.g. "Bash(shell:*) Bash(git:*) ask_user_questions")
142
+ if (toolsRaw) {
143
+ allowedTools = toolsRaw.split(/\s+/).filter(Boolean);
144
+ }
145
+ continue;
146
+ }
147
+ }
148
+ if (name !== undefined && description !== undefined) {
149
+ return { name, description, allowedTools, outputType };
150
+ }
151
+ return null;
152
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Skill module — SKILL.md discovery, loading, and execution
3
+ *
4
+ * Provides the SkillLoader for discovering SKILL.md files with
5
+ * Progressive Disclosure (frontmatter scan → full content → references),
6
+ * and the SkillExecutor for running skills through the AgentLoop.
7
+ *
8
+ * Reference: gemini-cli skillLoader.ts + skill-extraction-agent.ts
9
+ */
10
+ export { SkillLoader } from './loader';
11
+ export type { SkillDefinition } from './loader';
12
+ export { parseSkillFrontmatter, FRONTMATTER_REGEX } from './frontmatter-parser';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ /**
3
+ * Skill module — SKILL.md discovery, loading, and execution
4
+ *
5
+ * Provides the SkillLoader for discovering SKILL.md files with
6
+ * Progressive Disclosure (frontmatter scan → full content → references),
7
+ * and the SkillExecutor for running skills through the AgentLoop.
8
+ *
9
+ * Reference: gemini-cli skillLoader.ts + skill-extraction-agent.ts
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.FRONTMATTER_REGEX = exports.parseSkillFrontmatter = exports.SkillLoader = void 0;
13
+ var loader_1 = require("./loader");
14
+ Object.defineProperty(exports, "SkillLoader", { enumerable: true, get: function () { return loader_1.SkillLoader; } });
15
+ var frontmatter_parser_1 = require("./frontmatter-parser");
16
+ Object.defineProperty(exports, "parseSkillFrontmatter", { enumerable: true, get: function () { return frontmatter_parser_1.parseSkillFrontmatter; } });
17
+ Object.defineProperty(exports, "FRONTMATTER_REGEX", { enumerable: true, get: function () { return frontmatter_parser_1.FRONTMATTER_REGEX; } });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Skill Loader — discovers and loads SKILL.md files
3
+ *
4
+ * Reference: gemini-cli skillLoader.ts (skills/skillLoader.ts)
5
+ *
6
+ * Progressive Disclosure three levels:
7
+ * 1. scanSkillDirs() → scan all SKILL.md frontmatter (name + description only)
8
+ * 2. loadSkillContent() → load the full SKILL.md body for a matched skill
9
+ * 3. loadSkillReference() → load a specific reference file from the skill's references/ dir
10
+ *
11
+ * Skill directory scan priority:
12
+ * - packages/agent/skills/x/SKILL.md — project built-in skills (canonical location)
13
+ * - Future: user-defined skill directories (from config)
14
+ */
15
+ /**
16
+ * A loaded skill definition.
17
+ * Reference: gemini-cli SkillDefinition (skills/skillLoader.ts L17-32)
18
+ */
19
+ export interface SkillDefinition {
20
+ /** The unique name of the skill (sanitized from frontmatter). */
21
+ name: string;
22
+ /** A concise description of what the skill does. */
23
+ description: string;
24
+ /** The absolute path to the skill's SKILL.md file on disk. */
25
+ location: string;
26
+ /** The core logic/instructions of the skill (SKILL.md body). */
27
+ body: string;
28
+ /** Whether the skill is currently disabled. */
29
+ disabled?: boolean;
30
+ /** Whether the skill is a built-in skill. */
31
+ isBuiltin?: boolean;
32
+ /** Allowed tools for this skill (from frontmatter). */
33
+ allowedTools?: string[];
34
+ /** Output artifact type for Pipeline routing (e.g. 'code_knowledge', 'case_list'). */
35
+ outputType?: string;
36
+ }
37
+ /**
38
+ * Skill loader for discovering and loading SKILL.md files.
39
+ *
40
+ * Usage:
41
+ * ```typescript
42
+ * const loader = new SkillLoader(['packages/agent/demo/skills'])
43
+ * const skills = await loader.scanSkillDirs()
44
+ * // Find matching skill
45
+ * const matched = loader.matchSkill('test case generation', skills)
46
+ * // Load full content
47
+ * const content = await loader.loadSkillContent(matched.name)
48
+ * ```
49
+ */
50
+ export declare class SkillLoader {
51
+ private skillDirs;
52
+ private skillCache;
53
+ constructor(skillDirs: string[]);
54
+ /**
55
+ * Level 1: Scan skill directories and load all SKILL.md frontmatter.
56
+ * Returns SkillDefinition objects with name, description, location, and body.
57
+ *
58
+ * Reference: gemini-cli loadSkillsFromDir (skills/skillLoader.ts L115-159)
59
+ */
60
+ scanSkillDirs(): Promise<SkillDefinition[]>;
61
+ /**
62
+ * Level 2: Load the full SKILL.md body content for a specific skill.
63
+ * If the skill was already scanned, returns the cached body.
64
+ * Otherwise, loads it from disk.
65
+ */
66
+ loadSkillContent(skillName: string): Promise<string | null>;
67
+ /**
68
+ * Level 3: Load a reference file from a skill's directory.
69
+ * Reference files are typically in a references/ subdirectory next to SKILL.md.
70
+ */
71
+ loadSkillReference(skillName: string, referencePath: string): Promise<string | null>;
72
+ /**
73
+ * Match a skill by query string against available skills' names and descriptions.
74
+ * Uses simple keyword matching — finds the skill whose name or description
75
+ * best matches the query.
76
+ */
77
+ matchSkill(query: string, availableSkills: SkillDefinition[]): SkillDefinition | null;
78
+ /**
79
+ * Get a cached skill definition by name.
80
+ */
81
+ getCachedSkill(skillName: string): SkillDefinition | undefined;
82
+ /**
83
+ * Get all cached skill definitions.
84
+ */
85
+ getAllCachedSkills(): SkillDefinition[];
86
+ /**
87
+ * Load all skills from a directory.
88
+ * Reference: gemini-cli loadSkillsFromDir (skills/skillLoader.ts L115-159)
89
+ */
90
+ private loadSkillsFromDir;
91
+ /**
92
+ * Load a single skill from a SKILL.md file.
93
+ * Reference: gemini-cli loadSkillFromFile (skills/skillLoader.ts L164-192)
94
+ */
95
+ private loadSkillFromFile;
96
+ }
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ /**
3
+ * Skill Loader — discovers and loads SKILL.md files
4
+ *
5
+ * Reference: gemini-cli skillLoader.ts (skills/skillLoader.ts)
6
+ *
7
+ * Progressive Disclosure three levels:
8
+ * 1. scanSkillDirs() → scan all SKILL.md frontmatter (name + description only)
9
+ * 2. loadSkillContent() → load the full SKILL.md body for a matched skill
10
+ * 3. loadSkillReference() → load a specific reference file from the skill's references/ dir
11
+ *
12
+ * Skill directory scan priority:
13
+ * - packages/agent/skills/x/SKILL.md — project built-in skills (canonical location)
14
+ * - Future: user-defined skill directories (from config)
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.SkillLoader = void 0;
51
+ const fs = __importStar(require("node:fs/promises"));
52
+ const path = __importStar(require("node:path"));
53
+ const glob_1 = require("glob");
54
+ const frontmatter_parser_1 = require("./frontmatter-parser");
55
+ /**
56
+ * Skill loader for discovering and loading SKILL.md files.
57
+ *
58
+ * Usage:
59
+ * ```typescript
60
+ * const loader = new SkillLoader(['packages/agent/demo/skills'])
61
+ * const skills = await loader.scanSkillDirs()
62
+ * // Find matching skill
63
+ * const matched = loader.matchSkill('test case generation', skills)
64
+ * // Load full content
65
+ * const content = await loader.loadSkillContent(matched.name)
66
+ * ```
67
+ */
68
+ class SkillLoader {
69
+ skillDirs;
70
+ skillCache = new Map();
71
+ constructor(skillDirs) {
72
+ this.skillDirs = skillDirs;
73
+ }
74
+ /**
75
+ * Level 1: Scan skill directories and load all SKILL.md frontmatter.
76
+ * Returns SkillDefinition objects with name, description, location, and body.
77
+ *
78
+ * Reference: gemini-cli loadSkillsFromDir (skills/skillLoader.ts L115-159)
79
+ */
80
+ async scanSkillDirs() {
81
+ const discoveredSkills = [];
82
+ for (const dir of this.skillDirs) {
83
+ const skillsFromDir = await this.loadSkillsFromDir(dir);
84
+ for (const skill of skillsFromDir) {
85
+ this.skillCache.set(skill.name, skill);
86
+ discoveredSkills.push(skill);
87
+ }
88
+ }
89
+ return discoveredSkills;
90
+ }
91
+ /**
92
+ * Level 2: Load the full SKILL.md body content for a specific skill.
93
+ * If the skill was already scanned, returns the cached body.
94
+ * Otherwise, loads it from disk.
95
+ */
96
+ async loadSkillContent(skillName) {
97
+ const cached = this.skillCache.get(skillName);
98
+ if (cached) {
99
+ return cached.body;
100
+ }
101
+ // Try to find and load the skill file
102
+ for (const dir of this.skillDirs) {
103
+ const absoluteSearchPath = path.resolve(dir);
104
+ try {
105
+ const pattern = ['SKILL.md', '*/SKILL.md'];
106
+ const skillFiles = await (0, glob_1.glob)(pattern, {
107
+ cwd: absoluteSearchPath,
108
+ absolute: true,
109
+ nodir: true,
110
+ ignore: ['**/node_modules/**', '**/.git/**'],
111
+ });
112
+ for (const skillFile of skillFiles) {
113
+ const skill = await this.loadSkillFromFile(skillFile);
114
+ if (skill && skill.name === skillName) {
115
+ this.skillCache.set(skill.name, skill);
116
+ return skill.body;
117
+ }
118
+ }
119
+ }
120
+ catch (err) {
121
+ console.warn(`[SkillLoader] Failed to search skill '${skillName}' in ${absoluteSearchPath}:`, err instanceof Error ? err.message : err);
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+ /**
127
+ * Level 3: Load a reference file from a skill's directory.
128
+ * Reference files are typically in a references/ subdirectory next to SKILL.md.
129
+ */
130
+ async loadSkillReference(skillName, referencePath) {
131
+ const cached = this.skillCache.get(skillName);
132
+ if (!cached) {
133
+ return null;
134
+ }
135
+ // The skill's location is the SKILL.md file path
136
+ // References are in the same directory or a references/ subdirectory
137
+ const skillDir = path.dirname(cached.location);
138
+ const refAbsolutePath = path.resolve(skillDir, referencePath);
139
+ try {
140
+ return await fs.readFile(refAbsolutePath, 'utf-8');
141
+ }
142
+ catch {
143
+ console.warn(`[SkillLoader] Reference file not found: ${refAbsolutePath}`);
144
+ return null;
145
+ }
146
+ }
147
+ /**
148
+ * Match a skill by query string against available skills' names and descriptions.
149
+ * Uses simple keyword matching — finds the skill whose name or description
150
+ * best matches the query.
151
+ */
152
+ matchSkill(query, availableSkills) {
153
+ if (availableSkills.length === 0) {
154
+ return null;
155
+ }
156
+ const lowerQuery = query.toLowerCase();
157
+ const queryWords = lowerQuery.split(/\s+/);
158
+ // Score each skill by how many query words appear in name/description
159
+ let bestMatch = null;
160
+ let bestScore = 0;
161
+ for (const skill of availableSkills) {
162
+ if (skill.disabled)
163
+ continue;
164
+ const searchableText = `${skill.name} ${skill.description}`.toLowerCase();
165
+ let score = 0;
166
+ for (const word of queryWords) {
167
+ if (searchableText.includes(word)) {
168
+ score++;
169
+ }
170
+ }
171
+ // Bonus for exact name match
172
+ if (skill.name.toLowerCase() === lowerQuery) {
173
+ score += 10;
174
+ }
175
+ // Bonus for name being a substring of query
176
+ if (lowerQuery.includes(skill.name.toLowerCase())) {
177
+ score += 5;
178
+ }
179
+ if (score > bestScore) {
180
+ bestScore = score;
181
+ bestMatch = skill;
182
+ }
183
+ }
184
+ return bestMatch;
185
+ }
186
+ /**
187
+ * Get a cached skill definition by name.
188
+ */
189
+ getCachedSkill(skillName) {
190
+ return this.skillCache.get(skillName);
191
+ }
192
+ /**
193
+ * Get all cached skill definitions.
194
+ */
195
+ getAllCachedSkills() {
196
+ return Array.from(this.skillCache.values());
197
+ }
198
+ // ===== Private methods =====
199
+ /**
200
+ * Load all skills from a directory.
201
+ * Reference: gemini-cli loadSkillsFromDir (skills/skillLoader.ts L115-159)
202
+ */
203
+ async loadSkillsFromDir(dir) {
204
+ const discoveredSkills = [];
205
+ try {
206
+ const absoluteSearchPath = path.resolve(dir);
207
+ const stats = await fs.stat(absoluteSearchPath).catch(() => null);
208
+ if (!stats || !stats.isDirectory()) {
209
+ return [];
210
+ }
211
+ const pattern = ['SKILL.md', '*/SKILL.md'];
212
+ const skillFiles = await (0, glob_1.glob)(pattern, {
213
+ cwd: absoluteSearchPath,
214
+ absolute: true,
215
+ nodir: true,
216
+ ignore: ['**/node_modules/**', '**/.git/**'],
217
+ });
218
+ for (const skillFile of skillFiles) {
219
+ const skill = await this.loadSkillFromFile(skillFile);
220
+ if (skill) {
221
+ discoveredSkills.push(skill);
222
+ }
223
+ }
224
+ }
225
+ catch (error) {
226
+ console.warn(`[SkillLoader] Error discovering skills in ${dir}:`, error);
227
+ }
228
+ return discoveredSkills;
229
+ }
230
+ /**
231
+ * Load a single skill from a SKILL.md file.
232
+ * Reference: gemini-cli loadSkillFromFile (skills/skillLoader.ts L164-192)
233
+ */
234
+ async loadSkillFromFile(filePath) {
235
+ try {
236
+ const content = await fs.readFile(filePath, 'utf-8');
237
+ const parsed = (0, frontmatter_parser_1.parseSkillFrontmatter)(content);
238
+ if (!parsed) {
239
+ return null;
240
+ }
241
+ return {
242
+ name: parsed.name,
243
+ description: parsed.description,
244
+ location: filePath,
245
+ body: parsed.body,
246
+ allowedTools: parsed.allowedTools,
247
+ outputType: parsed.outputType,
248
+ };
249
+ }
250
+ catch (error) {
251
+ console.warn(`[SkillLoader] Error parsing skill file ${filePath}:`, error);
252
+ return null;
253
+ }
254
+ }
255
+ }
256
+ exports.SkillLoader = SkillLoader;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ask_user Tool — ask the user a question and wait for confirmation
3
+ *
4
+ * Publishes an input_required event via eventBus,
5
+ * handled by the upper layer (Server) for user interaction.
6
+ */
7
+ import type { ToolDefinition } from '../agent-loop/types';
8
+ export declare const askUserTool: ToolDefinition;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * ask_user Tool — ask the user a question and wait for confirmation
4
+ *
5
+ * Publishes an input_required event via eventBus,
6
+ * handled by the upper layer (Server) for user interaction.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.askUserTool = void 0;
10
+ const v4_1 = require("zod/v4");
11
+ const AskUserSchema = v4_1.z.object({
12
+ /** Question to ask the user */
13
+ question: v4_1.z.string().describe('Question to ask the user'),
14
+ /** Optional choices for the user */
15
+ options: v4_1.z.array(v4_1.z.object({
16
+ label: v4_1.z.string().describe('Option label'),
17
+ description: v4_1.z.string().optional().describe('Option description'),
18
+ })).optional().describe('Array of options for the user to choose from'),
19
+ /** Type of confirmation needed */
20
+ type: v4_1.z.enum(['confirm', 'choice', 'text']).default('choice').describe('Type: confirm (yes/no), choice (select), text (free input)'),
21
+ });
22
+ exports.askUserTool = {
23
+ name: 'ask_user',
24
+ description: `Ask the user a question and wait for their response.
25
+ Use this when:
26
+ - PRD and code implementation are inconsistent and need confirmation
27
+ - Multiple approaches are possible and user preference is needed
28
+ - Information is missing and cannot be determined from available context
29
+
30
+ The skill execution pauses until the user responds.`,
31
+ inputSchema: AskUserSchema,
32
+ async execute(args) {
33
+ // In AgentLoop mode, this would be handled by the event system
34
+ // For Phase 1 (hybrid mode), we use a callback approach
35
+ const callback = globalThis.__askUserCallback;
36
+ if (callback) {
37
+ const answer = await callback(args.question, args.options);
38
+ return { answer, confirmed: true };
39
+ }
40
+ // Fallback: no user interaction available — tell LLM to decide on its own
41
+ console.warn(`[ask_user] No callback registered. Question: ${args.question}`);
42
+ return {
43
+ answer: `Unable to reach user. Please decide based on available information and continue. Do not call ask_user again.`,
44
+ confirmed: true,
45
+ };
46
+ },
47
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Bash Tool — shell command execution with scope-based permission control
3
+ *
4
+ * Scope patterns (aligned with keve-agent SKILL.md allowed-tools):
5
+ * Bash(git:*) → only git commands allowed
6
+ * Bash(npx:*) → only npx commands allowed
7
+ * Bash(npm:*) → only npm commands allowed
8
+ * Bash(node:*) → only node commands allowed
9
+ * Bash(shell:*) → any command allowed
10
+ *
11
+ * When LLM calls bash tool, the 'scope' parameter is validated against
12
+ * the skill's allowed-tools list by the ToolCaller.
13
+ */
14
+ import { z } from 'zod/v4';
15
+ import type { ToolDefinition } from '../agent-loop/types';
16
+ export declare const BashInputSchema: z.ZodObject<{
17
+ command: z.ZodString;
18
+ scope: z.ZodDefault<z.ZodString>;
19
+ cwd: z.ZodOptional<z.ZodString>;
20
+ timeout: z.ZodDefault<z.ZodNumber>;
21
+ }, z.core.$strip>;
22
+ export type BashInput = z.infer<typeof BashInputSchema>;
23
+ /**
24
+ * Validate that a command matches the allowed scope.
25
+ * Extracts the first word (the binary name) and checks against scope.
26
+ *
27
+ * @param command - The shell command to validate
28
+ * @param allowedScopes - Array of allowed scopes (e.g., ['git', 'npx', 'shell'])
29
+ * @returns true if command is within allowed scopes
30
+ */
31
+ export declare function isCommandInScope(command: string, allowedScopes: string[]): boolean;
32
+ /**
33
+ * Parse Bash(scope:*) patterns from allowed-tools list.
34
+ * Input: ['Bash(git:*)', 'Bash(npx:*)', 'codegraph_impact']
35
+ * Output: ['git', 'npx']
36
+ */
37
+ export declare function parseBashScopes(allowedTools: string[]): string[];
38
+ /**
39
+ * Check if a command attempts to write to a read-only path.
40
+ * Uses static analysis of the command string.
41
+ *
42
+ * @param command - The shell command to check
43
+ * @param readOnlyPaths - Array of directory paths that are read-only
44
+ * @returns Error message if write is detected, or null if command is safe
45
+ */
46
+ export declare function checkReadOnlyPathWrite(command: string, readOnlyPaths: string[]): string | null;
47
+ export declare const bashTool: ToolDefinition;