beth-copilot 1.0.12 → 1.0.14

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 (79) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +173 -32
  3. package/bin/cli.js +111 -193
  4. package/dist/cli/commands/doctor.d.ts +25 -0
  5. package/dist/cli/commands/doctor.d.ts.map +1 -0
  6. package/dist/cli/commands/doctor.js +238 -0
  7. package/dist/cli/commands/doctor.js.map +1 -0
  8. package/dist/cli/commands/doctor.test.d.ts +6 -0
  9. package/dist/cli/commands/doctor.test.d.ts.map +1 -0
  10. package/dist/cli/commands/doctor.test.js +137 -0
  11. package/dist/cli/commands/doctor.test.js.map +1 -0
  12. package/dist/cli/commands/index.d.ts +7 -0
  13. package/dist/cli/commands/index.d.ts.map +1 -0
  14. package/dist/cli/commands/index.js +10 -0
  15. package/dist/cli/commands/index.js.map +1 -0
  16. package/dist/cli/commands/quickstart.d.ts +18 -0
  17. package/dist/cli/commands/quickstart.d.ts.map +1 -0
  18. package/dist/cli/commands/quickstart.js +141 -0
  19. package/dist/cli/commands/quickstart.js.map +1 -0
  20. package/dist/core/agents/index.d.ts +6 -0
  21. package/dist/core/agents/index.d.ts.map +1 -0
  22. package/dist/core/agents/index.js +6 -0
  23. package/dist/core/agents/index.js.map +1 -0
  24. package/dist/core/agents/loader.d.ts +49 -0
  25. package/dist/core/agents/loader.d.ts.map +1 -0
  26. package/dist/core/agents/loader.js +217 -0
  27. package/dist/core/agents/loader.js.map +1 -0
  28. package/dist/core/agents/loader.test.d.ts +7 -0
  29. package/dist/core/agents/loader.test.d.ts.map +1 -0
  30. package/dist/core/agents/loader.test.js +144 -0
  31. package/dist/core/agents/loader.test.js.map +1 -0
  32. package/dist/core/agents/types.d.ts +77 -0
  33. package/dist/core/agents/types.d.ts.map +1 -0
  34. package/dist/core/agents/types.js +8 -0
  35. package/dist/core/agents/types.js.map +1 -0
  36. package/dist/core/agents/types.test.d.ts +6 -0
  37. package/dist/core/agents/types.test.d.ts.map +1 -0
  38. package/dist/core/agents/types.test.js +254 -0
  39. package/dist/core/agents/types.test.js.map +1 -0
  40. package/dist/core/skills/index.d.ts +6 -0
  41. package/dist/core/skills/index.d.ts.map +1 -0
  42. package/dist/core/skills/index.js +6 -0
  43. package/dist/core/skills/index.js.map +1 -0
  44. package/dist/core/skills/loader.d.ts +69 -0
  45. package/dist/core/skills/loader.d.ts.map +1 -0
  46. package/dist/core/skills/loader.js +243 -0
  47. package/dist/core/skills/loader.js.map +1 -0
  48. package/dist/core/skills/loader.test.d.ts +7 -0
  49. package/dist/core/skills/loader.test.d.ts.map +1 -0
  50. package/dist/core/skills/loader.test.js +184 -0
  51. package/dist/core/skills/loader.test.js.map +1 -0
  52. package/dist/core/skills/types.d.ts +58 -0
  53. package/dist/core/skills/types.d.ts.map +1 -0
  54. package/dist/core/skills/types.js +8 -0
  55. package/dist/core/skills/types.js.map +1 -0
  56. package/dist/index.d.ts +10 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +14 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/lib/index.d.ts +7 -0
  61. package/dist/lib/index.d.ts.map +1 -0
  62. package/dist/lib/index.js +7 -0
  63. package/dist/lib/index.js.map +1 -0
  64. package/dist/lib/pathValidation.d.ts +69 -0
  65. package/dist/lib/pathValidation.d.ts.map +1 -0
  66. package/dist/lib/pathValidation.js +185 -0
  67. package/dist/lib/pathValidation.js.map +1 -0
  68. package/dist/lib/pathValidation.test.d.ts +9 -0
  69. package/dist/lib/pathValidation.test.d.ts.map +1 -0
  70. package/dist/lib/pathValidation.test.js +195 -0
  71. package/dist/lib/pathValidation.test.js.map +1 -0
  72. package/package.json +15 -3
  73. package/templates/.github/agents/developer.agent.md +9 -0
  74. package/templates/.github/agents/product-manager.agent.md +9 -0
  75. package/templates/.github/agents/researcher.agent.md +9 -0
  76. package/templates/.github/agents/security-reviewer.agent.md +9 -2
  77. package/templates/.github/agents/tester.agent.md +9 -0
  78. package/templates/.github/agents/ux-designer.agent.md +9 -0
  79. package/templates/.github/copilot-instructions.md +3 -3
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Skill Loader Tests
3
+ *
4
+ * Tests for parsing SKILL.md files into typed SkillDefinition objects.
5
+ */
6
+ import { describe, it } from 'node:test';
7
+ import assert from 'node:assert';
8
+ import { join } from 'node:path';
9
+ import { loadSkills, loadSkill, getSkillById, extractTriggers, buildTriggerMap, findMatchingSkills, DEFAULT_SKILLS_DIR, SKILL_FILE_NAME, } from './loader.js';
10
+ // Test against templates directory
11
+ const TEMPLATES_SKILLS_DIR = join(process.cwd(), 'templates', '.github', 'skills');
12
+ describe('Skill Loader', () => {
13
+ describe('loadSkills', () => {
14
+ it('should load all skills from templates directory', () => {
15
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
16
+ assert.strictEqual(result.errors.length, 0, `Unexpected errors: ${JSON.stringify(result.errors)}`);
17
+ assert.ok(result.skills.length >= 5, `Expected at least 5 skills, got ${result.skills.length}`);
18
+ // Check expected skills exist
19
+ const skillIds = result.skills.map((s) => s.id);
20
+ assert.ok(skillIds.includes('prd'), 'Should include prd skill');
21
+ assert.ok(skillIds.includes('shadcn-ui'), 'Should include shadcn-ui skill');
22
+ assert.ok(skillIds.includes('vercel-react-best-practices'), 'Should include react best practices skill');
23
+ });
24
+ it('should return error for non-existent directory', () => {
25
+ const result = loadSkills('/non/existent/path');
26
+ assert.strictEqual(result.skills.length, 0);
27
+ assert.strictEqual(result.errors.length, 1);
28
+ assert.ok(result.errors[0].message.includes('not found'));
29
+ });
30
+ });
31
+ describe('loadSkill', () => {
32
+ it('should parse PRD skill correctly', () => {
33
+ const filePath = join(TEMPLATES_SKILLS_DIR, 'prd', 'SKILL.md');
34
+ const result = loadSkill(filePath, 'prd');
35
+ assert.ok(!('error' in result), `Unexpected error: ${JSON.stringify(result)}`);
36
+ const { skill } = result;
37
+ assert.strictEqual(skill.id, 'prd');
38
+ assert.strictEqual(skill.frontmatter.name, 'prd');
39
+ assert.ok(skill.frontmatter.description?.includes('PRD'));
40
+ assert.ok(skill.body.length > 100, 'Should have substantial body content');
41
+ assert.ok(skill.triggers.length > 0, 'Should have extracted triggers');
42
+ });
43
+ it('should extract sourcePath correctly', () => {
44
+ const filePath = join(TEMPLATES_SKILLS_DIR, 'prd', 'SKILL.md');
45
+ const result = loadSkill(filePath, 'prd');
46
+ assert.ok(!('error' in result));
47
+ const { skill } = result;
48
+ assert.strictEqual(skill.sourcePath, filePath);
49
+ });
50
+ });
51
+ describe('extractTriggers', () => {
52
+ it('should extract triggers from "Triggers on:" pattern', () => {
53
+ const description = 'Generate PRDs. Triggers on: create a prd, write prd for, plan this feature.';
54
+ const triggers = extractTriggers(description);
55
+ assert.ok(triggers.includes('create a prd'));
56
+ assert.ok(triggers.includes('write prd for'));
57
+ assert.ok(triggers.includes('plan this feature'));
58
+ });
59
+ it('should extract triggers from "Use when:" pattern', () => {
60
+ const description = 'Component library skill. Use when: shadcn, ui component, install button';
61
+ const triggers = extractTriggers(description);
62
+ assert.ok(triggers.includes('shadcn'));
63
+ assert.ok(triggers.includes('ui component'));
64
+ assert.ok(triggers.includes('install button'));
65
+ });
66
+ it('should extract quoted phrases', () => {
67
+ const description = 'Use when asked to "create a prd" or "write requirements"';
68
+ const triggers = extractTriggers(description);
69
+ assert.ok(triggers.includes('create a prd'));
70
+ assert.ok(triggers.includes('write requirements'));
71
+ });
72
+ it('should deduplicate triggers', () => {
73
+ const description = 'Triggers on: create a prd. Use "create a prd" often.';
74
+ const triggers = extractTriggers(description);
75
+ const prdCount = triggers.filter((t) => t === 'create a prd').length;
76
+ assert.strictEqual(prdCount, 1, 'Should not have duplicate triggers');
77
+ });
78
+ it('should return empty array for undefined description', () => {
79
+ const triggers = extractTriggers(undefined);
80
+ assert.deepStrictEqual(triggers, []);
81
+ });
82
+ it('should return empty array for description without triggers', () => {
83
+ const triggers = extractTriggers('Just a simple description with no trigger patterns.');
84
+ assert.deepStrictEqual(triggers, []);
85
+ });
86
+ });
87
+ describe('buildTriggerMap', () => {
88
+ it('should build a map from triggers to skills', () => {
89
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
90
+ const triggerMap = buildTriggerMap(result);
91
+ assert.ok(triggerMap.size > 0, 'Should have triggers in map');
92
+ // PRD skill should be findable by its triggers
93
+ const prdSkill = result.skills.find((s) => s.id === 'prd');
94
+ if (prdSkill && prdSkill.triggers.length > 0) {
95
+ const mappedSkill = triggerMap.get(prdSkill.triggers[0]);
96
+ assert.ok(mappedSkill, 'Should find skill by trigger');
97
+ assert.strictEqual(mappedSkill?.id, 'prd');
98
+ }
99
+ });
100
+ it('should use lowercase keys for case-insensitive matching', () => {
101
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
102
+ const triggerMap = buildTriggerMap(result);
103
+ // All keys should be lowercase
104
+ for (const key of triggerMap.keys()) {
105
+ assert.strictEqual(key, key.toLowerCase(), `Key "${key}" should be lowercase`);
106
+ }
107
+ });
108
+ });
109
+ describe('findMatchingSkills', () => {
110
+ it('should find skills matching a query', () => {
111
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
112
+ const triggerMap = buildTriggerMap(result);
113
+ // Find PRD skill with a relevant query
114
+ const prdSkill = result.skills.find((s) => s.id === 'prd');
115
+ if (prdSkill && prdSkill.triggers.length > 0) {
116
+ const query = `I want to ${prdSkill.triggers[0]} for my feature`;
117
+ const matches = findMatchingSkills(query, triggerMap);
118
+ assert.ok(matches.length > 0, 'Should find matching skill');
119
+ assert.ok(matches.some((s) => s.id === 'prd'), 'Should include PRD skill');
120
+ }
121
+ });
122
+ it('should be case-insensitive', () => {
123
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
124
+ const triggerMap = buildTriggerMap(result);
125
+ const matchesLower = findMatchingSkills('create a prd', triggerMap);
126
+ const matchesUpper = findMatchingSkills('CREATE A PRD', triggerMap);
127
+ assert.deepStrictEqual(matchesLower.map((s) => s.id), matchesUpper.map((s) => s.id), 'Case should not affect results');
128
+ });
129
+ it('should return empty array for non-matching query', () => {
130
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
131
+ const triggerMap = buildTriggerMap(result);
132
+ const matches = findMatchingSkills('xyzzy plugh nothing matches this', triggerMap);
133
+ assert.deepStrictEqual(matches, []);
134
+ });
135
+ it('should deduplicate skills that match multiple triggers', () => {
136
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
137
+ const triggerMap = buildTriggerMap(result);
138
+ // Find a skill with multiple triggers
139
+ const multiTriggerSkill = result.skills.find((s) => s.triggers.length > 1);
140
+ if (multiTriggerSkill) {
141
+ // Create a query that contains multiple triggers for the same skill
142
+ const query = multiTriggerSkill.triggers.join(' and ');
143
+ const matches = findMatchingSkills(query, triggerMap);
144
+ const skillCount = matches.filter((s) => s.id === multiTriggerSkill.id).length;
145
+ assert.strictEqual(skillCount, 1, 'Should not duplicate skills');
146
+ }
147
+ });
148
+ });
149
+ describe('getSkillById', () => {
150
+ it('should find skill by ID (case-insensitive)', () => {
151
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
152
+ const prd = getSkillById(result, 'PRD');
153
+ assert.ok(prd, 'Should find PRD skill');
154
+ assert.strictEqual(prd?.frontmatter.name, 'prd');
155
+ const prdLower = getSkillById(result, 'prd');
156
+ assert.strictEqual(prd?.id, prdLower?.id, 'Case should not matter');
157
+ });
158
+ it('should return undefined for non-existent skill', () => {
159
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
160
+ const notFound = getSkillById(result, 'non-existent-skill');
161
+ assert.strictEqual(notFound, undefined);
162
+ });
163
+ });
164
+ describe('constants', () => {
165
+ it('should export correct constants', () => {
166
+ assert.strictEqual(DEFAULT_SKILLS_DIR, '.github/skills');
167
+ assert.strictEqual(SKILL_FILE_NAME, 'SKILL.md');
168
+ });
169
+ });
170
+ });
171
+ describe('Skill frontmatter structure compatibility', () => {
172
+ it('all loaded skills should have valid frontmatter structure', () => {
173
+ const result = loadSkills(TEMPLATES_SKILLS_DIR);
174
+ for (const skill of result.skills) {
175
+ // Required fields
176
+ assert.ok(skill.frontmatter.name, `${skill.id} should have name`);
177
+ // Body should be non-empty
178
+ assert.ok(skill.body.length > 0, `${skill.id} should have body content`);
179
+ // Triggers should be an array
180
+ assert.ok(Array.isArray(skill.triggers), `${skill.id} triggers should be array`);
181
+ }
182
+ });
183
+ });
184
+ //# sourceMappingURL=loader.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.test.js","sourceRoot":"","sources":["../../../src/core/skills/loader.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Skill Schema Types
3
+ *
4
+ * Type definitions for Beth skills parsed from SKILL.md files.
5
+ * Skills are domain-specific knowledge modules that agents load on-demand.
6
+ */
7
+ /**
8
+ * Skill frontmatter parsed from YAML.
9
+ * This is the metadata block at the top of SKILL.md files.
10
+ */
11
+ export interface SkillFrontmatter {
12
+ /** Display name of the skill */
13
+ name: string;
14
+ /** Description containing trigger phrases */
15
+ description?: string;
16
+ }
17
+ /**
18
+ * Fully parsed skill definition.
19
+ * Combines frontmatter metadata with the markdown body (skill content).
20
+ */
21
+ export interface SkillDefinition {
22
+ /** Unique identifier derived from directory name (e.g., 'prd' from 'skills/prd/SKILL.md') */
23
+ id: string;
24
+ /** Parsed YAML frontmatter */
25
+ frontmatter: SkillFrontmatter;
26
+ /** Raw markdown body (the skill content to inject) */
27
+ body: string;
28
+ /** Source file path for debugging */
29
+ sourcePath: string;
30
+ /** Trigger phrases extracted from description */
31
+ triggers: string[];
32
+ }
33
+ /**
34
+ * Result of loading skills from a directory.
35
+ */
36
+ export interface SkillLoadResult {
37
+ /** Successfully loaded skills */
38
+ skills: SkillDefinition[];
39
+ /** Errors encountered during loading */
40
+ errors: SkillLoadError[];
41
+ }
42
+ /**
43
+ * Error encountered while loading a skill file.
44
+ */
45
+ export interface SkillLoadError {
46
+ /** Path to the problematic file */
47
+ filePath: string;
48
+ /** Human-readable error message */
49
+ message: string;
50
+ /** Original error for debugging */
51
+ cause?: Error;
52
+ }
53
+ /**
54
+ * Map of trigger phrases to skill definitions.
55
+ * Used for quick lookup when processing user input.
56
+ */
57
+ export type TriggerMap = Map<string, SkillDefinition>;
58
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/skills/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IAEb,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,6FAA6F;IAC7F,EAAE,EAAE,MAAM,CAAC;IAEX,8BAA8B;IAC9B,WAAW,EAAE,gBAAgB,CAAC;IAE9B,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IAEb,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IAEnB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,MAAM,EAAE,eAAe,EAAE,CAAC;IAE1B,wCAAwC;IACxC,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IAEjB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAEhB,mCAAmC;IACnC,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Skill Schema Types
3
+ *
4
+ * Type definitions for Beth skills parsed from SKILL.md files.
5
+ * Skills are domain-specific knowledge modules that agents load on-demand.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/skills/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Beth CLI - TypeScript Core
3
+ *
4
+ * Main exports for the Beth CLI runtime.
5
+ */
6
+ export * from './core/agents/index.js';
7
+ export * from './core/skills/index.js';
8
+ export * from './lib/index.js';
9
+ export * from './cli/commands/index.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,wBAAwB,CAAC;AAGvC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Beth CLI - TypeScript Core
3
+ *
4
+ * Main exports for the Beth CLI runtime.
5
+ */
6
+ // Core exports - Agents
7
+ export * from './core/agents/index.js';
8
+ // Core exports - Skills
9
+ export * from './core/skills/index.js';
10
+ // Library utilities
11
+ export * from './lib/index.js';
12
+ // CLI commands
13
+ export * from './cli/commands/index.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAwB;AACxB,cAAc,wBAAwB,CAAC;AAEvC,wBAAwB;AACxB,cAAc,wBAAwB,CAAC;AAEvC,oBAAoB;AACpB,cAAc,gBAAgB,CAAC;AAE/B,eAAe;AACf,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Library Utilities
3
+ *
4
+ * Shared utilities for the Beth CLI.
5
+ */
6
+ export * from './pathValidation.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Library Utilities
3
+ *
4
+ * Shared utilities for the Beth CLI.
5
+ */
6
+ export * from './pathValidation.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Path validation utilities for user-supplied binary paths.
3
+ * Prevents path traversal attacks, injection, and execution of unintended binaries.
4
+ */
5
+ /**
6
+ * Validation result type
7
+ */
8
+ export interface ValidationResult {
9
+ /** Whether the path is valid */
10
+ valid: boolean;
11
+ /** Error message if invalid */
12
+ error?: string;
13
+ /** Normalized absolute path if valid */
14
+ normalizedPath?: string;
15
+ }
16
+ /**
17
+ * Options for validateBinaryPath
18
+ */
19
+ export interface ValidateBinaryPathOptions {
20
+ /** Require absolute path */
21
+ requireAbsolute?: boolean;
22
+ /** Check if file exists */
23
+ checkExists?: boolean;
24
+ /** Check if file is executable */
25
+ verifyExecutable?: boolean;
26
+ /** If provided, only allow these binary names */
27
+ allowedBasenames?: string[] | null;
28
+ }
29
+ /**
30
+ * Result of checking if a file is executable
31
+ */
32
+ export interface ExecutableCheckResult {
33
+ exists: boolean;
34
+ executable: boolean;
35
+ }
36
+ /**
37
+ * Check if a path contains traversal sequences
38
+ * @param inputPath - The path to check
39
+ * @returns True if traversal sequences found
40
+ */
41
+ export declare function containsTraversal(inputPath: string): boolean;
42
+ /**
43
+ * Check if a path contains shell injection characters
44
+ * @param inputPath - The path to check
45
+ * @returns True if injection characters found
46
+ */
47
+ export declare function containsShellInjection(inputPath: string): boolean;
48
+ /**
49
+ * Check if a file exists and is executable
50
+ * @param filePath - Absolute path to check
51
+ */
52
+ export declare function checkExecutable(filePath: string): ExecutableCheckResult;
53
+ /**
54
+ * Validate a user-supplied binary path
55
+ * @param inputPath - The path provided by the user
56
+ * @param options - Validation options
57
+ */
58
+ export declare function validateBinaryPath(inputPath: string, options?: ValidateBinaryPathOptions): ValidationResult;
59
+ /**
60
+ * Validate a binary path specifically for the beads (bd) CLI
61
+ * @param inputPath - The path to validate
62
+ */
63
+ export declare function validateBeadsPath(inputPath: string): ValidationResult;
64
+ /**
65
+ * Validate a binary path specifically for the backlog CLI
66
+ * @param inputPath - The path to validate
67
+ */
68
+ export declare function validateBacklogPath(inputPath: string): ValidationResult;
69
+ //# sourceMappingURL=pathValidation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathValidation.d.ts","sourceRoot":"","sources":["../../src/lib/pathValidation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gCAAgC;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,4BAA4B;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2BAA2B;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iDAAiD;IACjD,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAM5D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAMjE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAkCvE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,yBAA8B,GACtC,gBAAgB,CAqGlB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAMrE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAMvE"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Path validation utilities for user-supplied binary paths.
3
+ * Prevents path traversal attacks, injection, and execution of unintended binaries.
4
+ */
5
+ import { existsSync, statSync, accessSync, constants } from 'fs';
6
+ import { resolve, normalize, isAbsolute } from 'path';
7
+ // Characters that could be used for shell injection
8
+ // Note: backslash is allowed as Windows path separator
9
+ const SHELL_INJECTION_CHARS = /[;&|`$(){}[\]<>!'"]/;
10
+ // Path traversal sequences
11
+ const TRAVERSAL_PATTERNS = [
12
+ /\.\.[/\\]/, // ../ or ..\
13
+ /[/\\]\.\.[/\\]/, // /../ or \..\
14
+ /[/\\]\.\.$/, // ends with /.. or \..
15
+ /^\.\.[/\\]/, // starts with ../ or ..\
16
+ /^\.\.$/, // just ".."
17
+ ];
18
+ /**
19
+ * Check if a path contains traversal sequences
20
+ * @param inputPath - The path to check
21
+ * @returns True if traversal sequences found
22
+ */
23
+ export function containsTraversal(inputPath) {
24
+ if (!inputPath || typeof inputPath !== 'string') {
25
+ return false;
26
+ }
27
+ return TRAVERSAL_PATTERNS.some(pattern => pattern.test(inputPath));
28
+ }
29
+ /**
30
+ * Check if a path contains shell injection characters
31
+ * @param inputPath - The path to check
32
+ * @returns True if injection characters found
33
+ */
34
+ export function containsShellInjection(inputPath) {
35
+ if (!inputPath || typeof inputPath !== 'string') {
36
+ return false;
37
+ }
38
+ return SHELL_INJECTION_CHARS.test(inputPath);
39
+ }
40
+ /**
41
+ * Check if a file exists and is executable
42
+ * @param filePath - Absolute path to check
43
+ */
44
+ export function checkExecutable(filePath) {
45
+ const result = { exists: false, executable: false };
46
+ try {
47
+ if (!existsSync(filePath)) {
48
+ return result;
49
+ }
50
+ result.exists = true;
51
+ const stats = statSync(filePath);
52
+ if (!stats.isFile()) {
53
+ return result;
54
+ }
55
+ // On Windows, check file extension for executability
56
+ if (process.platform === 'win32') {
57
+ const executableExtensions = ['.exe', '.cmd', '.bat', '.com', '.ps1'];
58
+ const ext = filePath.toLowerCase().slice(filePath.lastIndexOf('.'));
59
+ result.executable = executableExtensions.includes(ext);
60
+ }
61
+ else {
62
+ // On Unix, check execute permission
63
+ try {
64
+ accessSync(filePath, constants.X_OK);
65
+ result.executable = true;
66
+ }
67
+ catch {
68
+ result.executable = false;
69
+ }
70
+ }
71
+ }
72
+ catch {
73
+ // File access error
74
+ }
75
+ return result;
76
+ }
77
+ /**
78
+ * Validate a user-supplied binary path
79
+ * @param inputPath - The path provided by the user
80
+ * @param options - Validation options
81
+ */
82
+ export function validateBinaryPath(inputPath, options = {}) {
83
+ const { requireAbsolute = false, checkExists = true, verifyExecutable = true, allowedBasenames = null, } = options;
84
+ // Basic type and empty check
85
+ if (!inputPath || typeof inputPath !== 'string') {
86
+ return { valid: false, error: 'Path cannot be empty' };
87
+ }
88
+ // Trim whitespace
89
+ const trimmedPath = inputPath.trim();
90
+ if (trimmedPath.length === 0) {
91
+ return { valid: false, error: 'Path cannot be empty' };
92
+ }
93
+ // Length limit to prevent DoS
94
+ if (trimmedPath.length > 4096) {
95
+ return { valid: false, error: 'Path exceeds maximum length (4096 characters)' };
96
+ }
97
+ // Check for null bytes (path injection)
98
+ if (trimmedPath.includes('\0')) {
99
+ return { valid: false, error: 'Path contains invalid characters (null byte)' };
100
+ }
101
+ // Check for path traversal
102
+ if (containsTraversal(trimmedPath)) {
103
+ return { valid: false, error: 'Path contains directory traversal sequences (../)' };
104
+ }
105
+ // Check for shell injection characters
106
+ if (containsShellInjection(trimmedPath)) {
107
+ return {
108
+ valid: false,
109
+ error: 'Path contains potentially dangerous characters. Use an absolute path without special characters.'
110
+ };
111
+ }
112
+ // Normalize the path
113
+ let normalizedPath;
114
+ try {
115
+ normalizedPath = normalize(trimmedPath);
116
+ // After normalization, check again for traversal (could be obfuscated)
117
+ if (containsTraversal(normalizedPath)) {
118
+ return { valid: false, error: 'Path resolves to a directory traversal' };
119
+ }
120
+ // Resolve to absolute path
121
+ normalizedPath = resolve(normalizedPath);
122
+ }
123
+ catch (err) {
124
+ const message = err instanceof Error ? err.message : 'unknown error';
125
+ return { valid: false, error: `Invalid path format: ${message}` };
126
+ }
127
+ // Check if absolute path is required
128
+ if (requireAbsolute && !isAbsolute(trimmedPath)) {
129
+ return { valid: false, error: 'Path must be an absolute path' };
130
+ }
131
+ // Check allowed basenames (whitelist specific binaries)
132
+ if (allowedBasenames && allowedBasenames.length > 0) {
133
+ // Extract basename handling both Unix and Windows separators
134
+ // This is necessary because on Unix, basename() doesn't handle Windows paths
135
+ const pathParts = normalizedPath.split(/[\\/]/);
136
+ const name = pathParts[pathParts.length - 1] || '';
137
+ const nameWithoutExt = name.replace(/\.(exe|cmd|bat|com)$/i, '');
138
+ const allowed = allowedBasenames.some(allowedName => {
139
+ const allowedLower = allowedName.toLowerCase();
140
+ return name.toLowerCase() === allowedLower ||
141
+ nameWithoutExt.toLowerCase() === allowedLower;
142
+ });
143
+ if (!allowed) {
144
+ return {
145
+ valid: false,
146
+ error: `Binary '${name}' is not in the allowed list: ${allowedBasenames.join(', ')}`
147
+ };
148
+ }
149
+ }
150
+ // Check if file exists
151
+ if (checkExists) {
152
+ const execCheck = checkExecutable(normalizedPath);
153
+ if (!execCheck.exists) {
154
+ return { valid: false, error: `File not found: ${normalizedPath}` };
155
+ }
156
+ // Check if executable
157
+ if (verifyExecutable && !execCheck.executable) {
158
+ return { valid: false, error: `File is not executable: ${normalizedPath}` };
159
+ }
160
+ }
161
+ return { valid: true, normalizedPath };
162
+ }
163
+ /**
164
+ * Validate a binary path specifically for the beads (bd) CLI
165
+ * @param inputPath - The path to validate
166
+ */
167
+ export function validateBeadsPath(inputPath) {
168
+ return validateBinaryPath(inputPath, {
169
+ checkExists: true,
170
+ verifyExecutable: true,
171
+ allowedBasenames: ['bd', 'bd.exe', 'bd.cmd'],
172
+ });
173
+ }
174
+ /**
175
+ * Validate a binary path specifically for the backlog CLI
176
+ * @param inputPath - The path to validate
177
+ */
178
+ export function validateBacklogPath(inputPath) {
179
+ return validateBinaryPath(inputPath, {
180
+ checkExists: true,
181
+ verifyExecutable: true,
182
+ allowedBasenames: ['backlog', 'backlog.exe', 'backlog.cmd'],
183
+ });
184
+ }
185
+ //# sourceMappingURL=pathValidation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathValidation.js","sourceRoot":"","sources":["../../src/lib/pathValidation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEtD,oDAAoD;AACpD,uDAAuD;AACvD,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AAEpD,2BAA2B;AAC3B,MAAM,kBAAkB,GAAG;IACzB,WAAW,EAAE,aAAa;IAC1B,gBAAgB,EAAE,eAAe;IACjC,YAAY,EAAG,uBAAuB;IACtC,YAAY,EAAE,yBAAyB;IACvC,QAAQ,EAAM,YAAY;CAC3B,CAAC;AAoCF;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiB;IACtD,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,qBAAqB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,MAAM,GAA0B,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IAE3E,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC;QAErB,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACpB,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,qDAAqD;QACrD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;YACtE,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;YACpE,MAAM,CAAC,UAAU,GAAG,oBAAoB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACzD,CAAC;aAAM,CAAC;YACN,oCAAoC;YACpC,IAAI,CAAC;gBACH,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;gBACrC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,UAAU,GAAG,KAAK,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oBAAoB;IACtB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAAiB,EACjB,UAAqC,EAAE;IAEvC,MAAM,EACJ,eAAe,GAAG,KAAK,EACvB,WAAW,GAAG,IAAI,EAClB,gBAAgB,GAAG,IAAI,EACvB,gBAAgB,GAAG,IAAI,GACxB,GAAG,OAAO,CAAC;IAEZ,6BAA6B;IAC7B,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IACzD,CAAC;IAED,kBAAkB;IAClB,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;IACrC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IACzD,CAAC;IAED,8BAA8B;IAC9B,IAAI,WAAW,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QAC9B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,+CAA+C,EAAE,CAAC;IAClF,CAAC;IAED,wCAAwC;IACxC,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,8CAA8C,EAAE,CAAC;IACjF,CAAC;IAED,2BAA2B;IAC3B,IAAI,iBAAiB,CAAC,WAAW,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,mDAAmD,EAAE,CAAC;IACtF,CAAC;IAED,uCAAuC;IACvC,IAAI,sBAAsB,CAAC,WAAW,CAAC,EAAE,CAAC;QACxC,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,kGAAkG;SAC1G,CAAC;IACJ,CAAC;IAED,qBAAqB;IACrB,IAAI,cAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,cAAc,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QAExC,uEAAuE;QACvE,IAAI,iBAAiB,CAAC,cAAc,CAAC,EAAE,CAAC;YACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,wCAAwC,EAAE,CAAC;QAC3E,CAAC;QAED,2BAA2B;QAC3B,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACrE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,wBAAwB,OAAO,EAAE,EAAE,CAAC;IACpE,CAAC;IAED,qCAAqC;IACrC,IAAI,eAAe,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC;IAClE,CAAC;IAED,wDAAwD;IACxD,IAAI,gBAAgB,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,6DAA6D;QAC7D,6EAA6E;QAC7E,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACnD,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;QAEjE,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;YAClD,MAAM,YAAY,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;YAC/C,OAAO,IAAI,CAAC,WAAW,EAAE,KAAK,YAAY;gBACnC,cAAc,CAAC,WAAW,EAAE,KAAK,YAAY,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,WAAW,IAAI,iCAAiC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aACrF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,SAAS,GAAG,eAAe,CAAC,cAAc,CAAC,CAAC;QAElD,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;YACtB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,cAAc,EAAE,EAAE,CAAC;QACtE,CAAC;QAED,sBAAsB;QACtB,IAAI,gBAAgB,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;YAC9C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,2BAA2B,cAAc,EAAE,EAAE,CAAC;QAC9E,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,kBAAkB,CAAC,SAAS,EAAE;QACnC,WAAW,EAAE,IAAI;QACjB,gBAAgB,EAAE,IAAI;QACtB,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC;KAC7C,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACnD,OAAO,kBAAkB,CAAC,SAAS,EAAE;QACnC,WAAW,EAAE,IAAI;QACjB,gBAAgB,EAAE,IAAI;QACtB,gBAAgB,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,aAAa,CAAC;KAC5D,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Unit tests for TypeScript path validation utilities.
3
+ * Run with: node --test dist/lib/pathValidation.test.js
4
+ *
5
+ * These tests mirror the JavaScript tests in bin/lib/pathValidation.test.js
6
+ * to ensure the TypeScript port maintains the same behavior.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=pathValidation.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathValidation.test.d.ts","sourceRoot":"","sources":["../../src/lib/pathValidation.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}