beth-copilot 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -1
- package/README.md +51 -62
- package/assets/beth-questioning.png +0 -0
- package/assets/yellowstone-beth.png +0 -0
- package/bin/cli.js +19 -410
- package/dist/__tests__/inject-skills.test.d.ts +9 -0
- package/dist/__tests__/inject-skills.test.d.ts.map +1 -0
- package/dist/__tests__/inject-skills.test.js +143 -0
- package/dist/__tests__/inject-skills.test.js.map +1 -0
- package/dist/__tests__/skills/disambiguation.test.d.ts +10 -0
- package/dist/__tests__/skills/disambiguation.test.d.ts.map +1 -0
- package/dist/__tests__/skills/disambiguation.test.js +192 -0
- package/dist/__tests__/skills/disambiguation.test.js.map +1 -0
- package/dist/__tests__/skills/hook-injection.test.d.ts +11 -0
- package/dist/__tests__/skills/hook-injection.test.d.ts.map +1 -0
- package/dist/__tests__/skills/hook-injection.test.js +173 -0
- package/dist/__tests__/skills/hook-injection.test.js.map +1 -0
- package/dist/__tests__/skills/mapping-completeness.test.d.ts +17 -0
- package/dist/__tests__/skills/mapping-completeness.test.d.ts.map +1 -0
- package/dist/__tests__/skills/mapping-completeness.test.js +281 -0
- package/dist/__tests__/skills/mapping-completeness.test.js.map +1 -0
- package/dist/__tests__/skills/pipeline-integration.test.d.ts +18 -0
- package/dist/__tests__/skills/pipeline-integration.test.d.ts.map +1 -0
- package/dist/__tests__/skills/pipeline-integration.test.js +234 -0
- package/dist/__tests__/skills/pipeline-integration.test.js.map +1 -0
- package/dist/__tests__/skills/skill-routing.test.d.ts +15 -0
- package/dist/__tests__/skills/skill-routing.test.d.ts.map +1 -0
- package/dist/__tests__/skills/skill-routing.test.js +723 -0
- package/dist/__tests__/skills/skill-routing.test.js.map +1 -0
- package/dist/__tests__/skills/trigger-coverage.test.d.ts +24 -0
- package/dist/__tests__/skills/trigger-coverage.test.d.ts.map +1 -0
- package/dist/__tests__/skills/trigger-coverage.test.js +746 -0
- package/dist/__tests__/skills/trigger-coverage.test.js.map +1 -0
- package/dist/__tests__/smoke.test.js +13 -0
- package/dist/__tests__/smoke.test.js.map +1 -1
- package/dist/__tests__/verify-skills.test.d.ts +9 -0
- package/dist/__tests__/verify-skills.test.d.ts.map +1 -0
- package/dist/__tests__/verify-skills.test.js +78 -0
- package/dist/__tests__/verify-skills.test.js.map +1 -0
- package/dist/cli/commands/beads.e2e.test.d.ts +4 -2
- package/dist/cli/commands/beads.e2e.test.d.ts.map +1 -1
- package/dist/cli/commands/beads.e2e.test.js +97 -38
- package/dist/cli/commands/beads.e2e.test.js.map +1 -1
- package/dist/cli/commands/cli-edge-cases.e2e.test.js +1 -1
- package/dist/cli/commands/cli-edge-cases.e2e.test.js.map +1 -1
- package/dist/cli/commands/close.d.ts +11 -46
- package/dist/cli/commands/close.d.ts.map +1 -1
- package/dist/cli/commands/close.e2e.test.d.ts +4 -20
- package/dist/cli/commands/close.e2e.test.d.ts.map +1 -1
- package/dist/cli/commands/close.e2e.test.js +23 -204
- package/dist/cli/commands/close.e2e.test.js.map +1 -1
- package/dist/cli/commands/close.js +26 -240
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/close.test.d.ts +7 -9
- package/dist/cli/commands/close.test.d.ts.map +1 -1
- package/dist/cli/commands/close.test.js +44 -424
- package/dist/cli/commands/close.test.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +5 -22
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.e2e.test.js +3 -59
- package/dist/cli/commands/doctor.e2e.test.js.map +1 -1
- package/dist/cli/commands/doctor.js +38 -111
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/doctor.test.js +32 -234
- package/dist/cli/commands/doctor.test.js.map +1 -1
- package/dist/cli/commands/framework-isolation.test.d.ts +1 -1
- package/dist/cli/commands/framework-isolation.test.js +2 -3
- package/dist/cli/commands/framework-isolation.test.js.map +1 -1
- package/dist/cli/commands/help.e2e.test.js +1 -5
- package/dist/cli/commands/help.e2e.test.js.map +1 -1
- package/dist/cli/commands/init-logic.e2e.test.js +12 -2
- package/dist/cli/commands/init-logic.e2e.test.js.map +1 -1
- package/dist/cli/commands/init.test.js +4 -21
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/cli/commands/land.d.ts +3 -15
- package/dist/cli/commands/land.d.ts.map +1 -1
- package/dist/cli/commands/land.js +13 -68
- package/dist/cli/commands/land.js.map +1 -1
- package/dist/cli/commands/land.test.d.ts +0 -1
- package/dist/cli/commands/land.test.d.ts.map +1 -1
- package/dist/cli/commands/land.test.js +2 -57
- package/dist/cli/commands/land.test.js.map +1 -1
- package/dist/cli/commands/mcp.e2e.test.js +3 -3
- package/dist/cli/commands/mcp.e2e.test.js.map +1 -1
- package/dist/cli/commands/pipeline.e2e.test.js +23 -26
- package/dist/cli/commands/pipeline.e2e.test.js.map +1 -1
- package/dist/cli/commands/pre-push-guard.d.ts +2 -12
- package/dist/cli/commands/pre-push-guard.d.ts.map +1 -1
- package/dist/cli/commands/pre-push-guard.e2e.test.js +1 -1
- package/dist/cli/commands/pre-push-guard.e2e.test.js.map +1 -1
- package/dist/cli/commands/pre-push-guard.js +2 -47
- package/dist/cli/commands/pre-push-guard.js.map +1 -1
- package/dist/cli/commands/pre-push-guard.test.d.ts +0 -1
- package/dist/cli/commands/pre-push-guard.test.d.ts.map +1 -1
- package/dist/cli/commands/pre-push-guard.test.js +15 -98
- package/dist/cli/commands/pre-push-guard.test.js.map +1 -1
- package/dist/cli/commands/quickstart-expanded.e2e.test.d.ts +1 -1
- package/dist/cli/commands/quickstart-expanded.e2e.test.js +3 -30
- package/dist/cli/commands/quickstart-expanded.e2e.test.js.map +1 -1
- package/dist/cli/commands/quickstart.d.ts +0 -1
- package/dist/cli/commands/quickstart.d.ts.map +1 -1
- package/dist/cli/commands/quickstart.js +2 -60
- package/dist/cli/commands/quickstart.js.map +1 -1
- package/dist/cli/commands/quickstart.test.js +10 -104
- package/dist/cli/commands/quickstart.test.js.map +1 -1
- package/dist/cli/commands/update.d.ts +35 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.e2e.test.d.ts +24 -0
- package/dist/cli/commands/update.e2e.test.d.ts.map +1 -0
- package/dist/cli/commands/update.e2e.test.js +240 -0
- package/dist/cli/commands/update.e2e.test.js.map +1 -0
- package/dist/cli/commands/update.js +255 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/core/agents/frontmatter.test.js +1 -1
- package/dist/core/agents/frontmatter.test.js.map +1 -1
- package/dist/core/agents/handoffs.test.js +1 -1
- package/dist/core/agents/handoffs.test.js.map +1 -1
- package/dist/core/agents/loader.d.ts +4 -2
- package/dist/core/agents/loader.d.ts.map +1 -1
- package/dist/core/agents/loader.js +5 -3
- package/dist/core/agents/loader.js.map +1 -1
- package/dist/core/agents/loader.test.js +42 -4
- package/dist/core/agents/loader.test.js.map +1 -1
- package/dist/core/agents/suite.test.js +8 -7
- package/dist/core/agents/suite.test.js.map +1 -1
- package/dist/core/agents/tools.test.js +10 -8
- package/dist/core/agents/tools.test.js.map +1 -1
- package/dist/core/agents/types.test.js +1 -1
- package/dist/core/agents/types.test.js.map +1 -1
- package/dist/core/skills/loader.test.js +1 -1
- package/dist/core/skills/loader.test.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/pathValidation.d.ts +0 -5
- package/dist/lib/pathValidation.d.ts.map +1 -1
- package/dist/lib/pathValidation.js +0 -11
- package/dist/lib/pathValidation.js.map +1 -1
- package/dist/lib/pathValidation.test.js +2 -14
- package/dist/lib/pathValidation.test.js.map +1 -1
- package/package.json +3 -6
- package/sbom.json +259 -371
- package/templates/.github/agents/beth.agent.md +194 -122
- package/templates/.github/agents/developer.agent.md +30 -22
- package/templates/.github/agents/product-manager.agent.md +15 -6
- package/templates/.github/agents/researcher.agent.md +10 -7
- package/templates/.github/agents/security-reviewer.agent.md +16 -7
- package/templates/.github/agents/tester.agent.md +16 -8
- package/templates/.github/agents/ux-designer.agent.md +12 -9
- package/templates/.github/copilot-instructions.md +33 -4
- package/templates/.github/copilot-mcp-config.json +12 -0
- package/templates/.github/dependabot.yml +68 -0
- package/templates/.github/hooks/scripts/inject-skills.mjs +139 -0
- package/templates/.github/hooks/scripts/verify-skills.mjs +47 -0
- package/templates/.github/hooks/skill-enforcement.json +18 -0
- package/templates/.github/pull_request_template.md +48 -0
- package/templates/.github/skills/framer-components/SKILL.md +0 -0
- package/templates/.github/skills/prd/SKILL.md +0 -0
- package/templates/.github/skills/security-analysis/SKILL.md +798 -798
- package/templates/.github/skills/shadcn-ui/SKILL.md +561 -561
- package/templates/.github/skills/vercel-react-best-practices/AGENTS.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/SKILL.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/advanced-use-latest.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-api-routes.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-defer-await.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-dependencies.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-parallel.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-conditional.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-preload.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/client-event-listeners.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/client-swr-dedup.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-cache-function-results.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-cache-property-access.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-cache-storage.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-combine-iterations.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-early-exit.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-index-maps.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-length-check-first.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-min-max-loop.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-activity.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-dependencies.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-derived-state.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-memo.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-transitions.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-auth-actions.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-cache-lru.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-cache-react.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-dedup-props.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-serialization.md +0 -0
- package/templates/.github/skills/web-design-guidelines/SKILL.md +0 -0
- package/templates/.vscode/settings.json +16 -16
- package/templates/AGENTS.md +59 -98
- package/templates/Backlog.md +80 -80
- package/assets/beth-portrait-small.txt +0 -13
- package/assets/beth-portrait.txt +0 -60
- package/bin/beth-animation.sh +0 -155
- package/bin/lib/animation.js +0 -189
- package/bin/lib/pathValidation.js +0 -233
- package/bin/lib/pathValidation.test.js +0 -280
package/bin/cli.js
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname, join, relative } from 'path';
|
|
5
|
-
import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
5
|
+
import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync, unlinkSync, chmodSync } from 'fs';
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
7
|
import { execSync, spawn } from 'child_process';
|
|
8
|
-
import { validateBeadsPath, validateBinaryPath } from './lib/pathValidation.js';
|
|
9
8
|
|
|
10
9
|
const require = createRequire(import.meta.url);
|
|
11
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -518,59 +517,6 @@ async function checkForUpdates() {
|
|
|
518
517
|
}
|
|
519
518
|
}
|
|
520
519
|
|
|
521
|
-
function getBeadsPath() {
|
|
522
|
-
// Check if bd is available in PATH
|
|
523
|
-
try {
|
|
524
|
-
logDebug('Checking if bd is in PATH...');
|
|
525
|
-
execSync('bd --version', { stdio: 'ignore' });
|
|
526
|
-
logDebug('Found bd in PATH');
|
|
527
|
-
return 'bd';
|
|
528
|
-
} catch {
|
|
529
|
-
logDebug('bd not in PATH, checking common locations...');
|
|
530
|
-
// Check common installation paths based on platform
|
|
531
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
532
|
-
const isWindows = process.platform === 'win32';
|
|
533
|
-
|
|
534
|
-
const commonPaths = isWindows ? [
|
|
535
|
-
// Windows: npm global, Go bin, local apps
|
|
536
|
-
join(process.env.APPDATA || '', 'npm', 'bd.cmd'),
|
|
537
|
-
join(homeDir, 'AppData', 'Roaming', 'npm', 'bd.cmd'),
|
|
538
|
-
join(homeDir, 'AppData', 'Local', 'Microsoft', 'WindowsApps', 'bd.exe'),
|
|
539
|
-
join(homeDir, 'go', 'bin', 'bd.exe'),
|
|
540
|
-
join(process.env.GOPATH || join(homeDir, 'go'), 'bin', 'bd.exe'),
|
|
541
|
-
] : [
|
|
542
|
-
// Unix: homebrew, npm global, go bin, local bin
|
|
543
|
-
'/opt/homebrew/bin/bd',
|
|
544
|
-
'/usr/local/bin/bd',
|
|
545
|
-
join(homeDir, '.local', 'bin', 'bd'),
|
|
546
|
-
join(homeDir, 'bin', 'bd'),
|
|
547
|
-
join(homeDir, '.npm-global', 'bin', 'bd'),
|
|
548
|
-
join(homeDir, 'go', 'bin', 'bd'),
|
|
549
|
-
join(process.env.GOPATH || join(homeDir, 'go'), 'bin', 'bd'),
|
|
550
|
-
];
|
|
551
|
-
|
|
552
|
-
for (const bdPath of commonPaths) {
|
|
553
|
-
logDebug(`Checking: ${bdPath}`);
|
|
554
|
-
if (existsSync(bdPath)) {
|
|
555
|
-
logDebug(`Found at: ${bdPath}`);
|
|
556
|
-
return bdPath;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
logDebug('bd not found in any common location');
|
|
561
|
-
return null;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function isBeadsInstalled() {
|
|
566
|
-
return getBeadsPath() !== null;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function isBeadsInitialized(cwd) {
|
|
570
|
-
// Check if .beads directory exists in the project
|
|
571
|
-
return existsSync(join(cwd, '.beads'));
|
|
572
|
-
}
|
|
573
|
-
|
|
574
520
|
async function promptYesNo(question) {
|
|
575
521
|
const readline = await import('readline');
|
|
576
522
|
const rl = readline.createInterface({
|
|
@@ -601,180 +547,6 @@ async function promptForInput(question) {
|
|
|
601
547
|
});
|
|
602
548
|
}
|
|
603
549
|
|
|
604
|
-
/**
|
|
605
|
-
* Installs the beads CLI globally via npm.
|
|
606
|
-
*
|
|
607
|
-
* SECURITY NOTE - shell:true usage:
|
|
608
|
-
* - Required for cross-platform npm execution (npm.cmd on Windows, npm on Unix)
|
|
609
|
-
* - Arguments are HARDCODED - no user input is passed to the shell
|
|
610
|
-
* - Command injection risk: NONE (no dynamic/user-supplied values)
|
|
611
|
-
*
|
|
612
|
-
* Alternative considered: Using platform-specific binary names (npm.cmd vs npm)
|
|
613
|
-
* would eliminate shell:true but adds complexity and edge cases for non-standard installs.
|
|
614
|
-
*
|
|
615
|
-
* @returns {Promise<boolean>} True if installation succeeded and was verified
|
|
616
|
-
*/
|
|
617
|
-
async function installBeads() {
|
|
618
|
-
const isWindows = process.platform === 'win32';
|
|
619
|
-
const isMac = process.platform === 'darwin';
|
|
620
|
-
|
|
621
|
-
log('\nInstalling beads CLI via npm...', COLORS.cyan);
|
|
622
|
-
logInfo('npm install -g @beads/bd');
|
|
623
|
-
|
|
624
|
-
// SECURITY: shell:true is required for cross-platform npm execution.
|
|
625
|
-
// All arguments are hardcoded constants - no user input reaches the shell.
|
|
626
|
-
return new Promise((resolve) => {
|
|
627
|
-
const child = spawn('npm', ['install', '-g', '@beads/bd'], {
|
|
628
|
-
stdio: 'inherit',
|
|
629
|
-
shell: true
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
child.on('close', (code) => {
|
|
633
|
-
if (code === 0) {
|
|
634
|
-
// CRITICAL: Verify installation actually worked before claiming success
|
|
635
|
-
// npm can exit 0 even when the package isn't properly installed
|
|
636
|
-
const verifiedPath = getBeadsPath();
|
|
637
|
-
if (verifiedPath) {
|
|
638
|
-
logSuccess('beads CLI installed and verified!');
|
|
639
|
-
resolve(true);
|
|
640
|
-
} else {
|
|
641
|
-
logWarning('npm reported success but beads CLI not found in PATH.');
|
|
642
|
-
logInfo('This can happen if npm global bin is not in your PATH.');
|
|
643
|
-
if (globalThis.VERBOSE) {
|
|
644
|
-
showPathDiagnostics();
|
|
645
|
-
} else {
|
|
646
|
-
logInfo('Run with --verbose for PATH diagnostics.');
|
|
647
|
-
}
|
|
648
|
-
console.log('');
|
|
649
|
-
showBeadsAlternatives(isWindows, isMac);
|
|
650
|
-
resolve(false);
|
|
651
|
-
}
|
|
652
|
-
} else {
|
|
653
|
-
logError('npm install failed.');
|
|
654
|
-
console.log('');
|
|
655
|
-
showBeadsAlternatives(isWindows, isMac);
|
|
656
|
-
resolve(false);
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
child.on('error', () => {
|
|
661
|
-
logError('Failed to run npm.');
|
|
662
|
-
logInfo('Make sure npm is installed and in your PATH.');
|
|
663
|
-
resolve(false);
|
|
664
|
-
});
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
function showBeadsAlternatives(isWindows, isMac) {
|
|
669
|
-
logInfo('Alternative installation methods:');
|
|
670
|
-
if (isWindows) {
|
|
671
|
-
logInfo(' PowerShell: irm https://raw.githubusercontent.com/steveyegge/beads/main/install.ps1 | iex');
|
|
672
|
-
logInfo(' Go: go install github.com/steveyegge/beads/cmd/bd@latest');
|
|
673
|
-
} else {
|
|
674
|
-
if (isMac) {
|
|
675
|
-
logInfo(' Homebrew: brew install beads');
|
|
676
|
-
}
|
|
677
|
-
logInfo(' Script: curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash');
|
|
678
|
-
logInfo(' Go: go install github.com/steveyegge/beads/cmd/bd@latest');
|
|
679
|
-
}
|
|
680
|
-
logInfo('');
|
|
681
|
-
logInfo('Learn more: https://github.com/steveyegge/beads');
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Initializes beads in the current project directory.
|
|
686
|
-
*
|
|
687
|
-
* SECURITY NOTE - shell:true usage:
|
|
688
|
-
* - bdPath is validated via getBeadsPath() which only returns paths that:
|
|
689
|
-
* 1. Pass execSync('bd --version') verification, OR
|
|
690
|
-
* 2. Exist on disk (verified via existsSync) from a HARDCODED list of paths
|
|
691
|
-
* - Arguments are HARDCODED ('init') - no user input is passed to the shell
|
|
692
|
-
* - Command injection risk: LOW (bdPath is validated, no user input in args)
|
|
693
|
-
*
|
|
694
|
-
* The shell:true is used for PATH resolution consistency, though it could be
|
|
695
|
-
* eliminated since we have an absolute path. Kept for consistency with other
|
|
696
|
-
* spawn calls and to handle edge cases in shell script wrappers.
|
|
697
|
-
*
|
|
698
|
-
* @param {string} cwd - Current working directory (validated by caller)
|
|
699
|
-
* @returns {Promise<boolean>} True if initialization succeeded
|
|
700
|
-
*/
|
|
701
|
-
async function initializeBeads(cwd) {
|
|
702
|
-
log('\nInitializing beads in project...', COLORS.cyan);
|
|
703
|
-
|
|
704
|
-
const bdPath = getBeadsPath();
|
|
705
|
-
if (!bdPath) {
|
|
706
|
-
logWarning('Failed to initialize beads. Run manually: bd init');
|
|
707
|
-
return false;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
// SECURITY: bdPath is validated by getBeadsPath() (existsSync check).
|
|
711
|
-
// Only 'init' argument is passed - no user input reaches the shell.
|
|
712
|
-
return new Promise((resolve) => {
|
|
713
|
-
const child = spawn(bdPath, ['init'], {
|
|
714
|
-
stdio: 'inherit',
|
|
715
|
-
shell: true,
|
|
716
|
-
cwd
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
child.on('close', (code) => {
|
|
720
|
-
if (code === 0) {
|
|
721
|
-
logSuccess('beads initialized successfully!');
|
|
722
|
-
resolve(true);
|
|
723
|
-
} else {
|
|
724
|
-
logWarning('Failed to initialize beads. Run manually: bd init');
|
|
725
|
-
resolve(false);
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
child.on('error', () => {
|
|
730
|
-
logWarning('Failed to initialize beads. Run manually: bd init');
|
|
731
|
-
resolve(false);
|
|
732
|
-
});
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Runs `bd doctor` to verify beads configuration health.
|
|
738
|
-
*
|
|
739
|
-
* SECURITY NOTE - shell:true usage:
|
|
740
|
-
* - bdPath is validated via getBeadsPath() (same as initializeBeads)
|
|
741
|
-
* - Arguments are HARDCODED ('doctor') - no user input is passed to the shell
|
|
742
|
-
* - Command injection risk: LOW (bdPath is validated, no user input in args)
|
|
743
|
-
*
|
|
744
|
-
* @returns {Promise<boolean>} True if bd doctor passed
|
|
745
|
-
*/
|
|
746
|
-
async function runBeadsDoctor() {
|
|
747
|
-
log('\nRunning beads doctor to verify configuration...', COLORS.cyan);
|
|
748
|
-
|
|
749
|
-
const bdPath = getBeadsPath();
|
|
750
|
-
if (!bdPath) {
|
|
751
|
-
logWarning('Cannot run beads doctor: bd not found.');
|
|
752
|
-
return false;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
return new Promise((resolve) => {
|
|
756
|
-
const child = spawn(bdPath, ['doctor'], {
|
|
757
|
-
stdio: 'inherit',
|
|
758
|
-
shell: true,
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
child.on('close', (code) => {
|
|
762
|
-
if (code === 0) {
|
|
763
|
-
logSuccess('beads doctor passed!');
|
|
764
|
-
resolve(true);
|
|
765
|
-
} else {
|
|
766
|
-
logWarning('beads doctor reported issues. Run "bd doctor" manually to investigate.');
|
|
767
|
-
resolve(false);
|
|
768
|
-
}
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
child.on('error', () => {
|
|
772
|
-
logWarning('Failed to run beads doctor. Run "bd doctor" manually.');
|
|
773
|
-
resolve(false);
|
|
774
|
-
});
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
|
|
778
550
|
const BETH_GUARD_BEGIN = '# --- BEGIN BETH GUARD ---';
|
|
779
551
|
const BETH_GUARD_END = '# --- END BETH GUARD ---';
|
|
780
552
|
|
|
@@ -817,35 +589,6 @@ ${BETH_GUARD_END}
|
|
|
817
589
|
`;
|
|
818
590
|
}
|
|
819
591
|
|
|
820
|
-
/**
|
|
821
|
-
* Install the pre-push guard into .beads/hooks/pre-push.
|
|
822
|
-
* Appends the guard section after the beads integration section.
|
|
823
|
-
* Idempotent — skips if guard is already installed.
|
|
824
|
-
*
|
|
825
|
-
* @param {string} cwd - Project root directory
|
|
826
|
-
*/
|
|
827
|
-
function installPrePushGuard(cwd) {
|
|
828
|
-
const hookPath = join(cwd, '.beads', 'hooks', 'pre-push');
|
|
829
|
-
|
|
830
|
-
if (!existsSync(hookPath)) {
|
|
831
|
-
logWarning('Pre-push hook not found (.beads/hooks/pre-push). Skipping guard installation.');
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
const content = readFileSync(hookPath, 'utf-8');
|
|
836
|
-
|
|
837
|
-
// Already installed?
|
|
838
|
-
if (content.includes(BETH_GUARD_BEGIN)) {
|
|
839
|
-
logSuccess('Pre-push branch guard already installed');
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// Append guard after existing content
|
|
844
|
-
const guardScript = generateGuardScript();
|
|
845
|
-
writeFileSync(hookPath, content.trimEnd() + '\n' + guardScript, 'utf-8');
|
|
846
|
-
logSuccess('Installed pre-push branch guard (blocks direct pushes to main)');
|
|
847
|
-
}
|
|
848
|
-
|
|
849
592
|
function showHelp() {
|
|
850
593
|
showBethBannerStatic({ showQuickHelp: false });
|
|
851
594
|
console.log(`${COLORS.bright}Beth${COLORS.reset} - AI Orchestrator for GitHub Copilot
|
|
@@ -853,18 +596,18 @@ function showHelp() {
|
|
|
853
596
|
${COLORS.bright}Usage:${COLORS.reset}
|
|
854
597
|
npx beth-copilot init [options] Initialize Beth in current directory
|
|
855
598
|
npx beth-copilot doctor Check system health and dependencies
|
|
856
|
-
npx beth-copilot close <id> [opts] Close issue with dependency enforcement
|
|
857
599
|
npx beth-copilot land [opts] Automated session completion (test, commit, push)
|
|
858
600
|
npx beth-copilot pre-push-guard Run branch discipline checks (used by git hook)
|
|
859
|
-
npx beth-copilot
|
|
601
|
+
npx beth-copilot update [options] Update project files to latest templates
|
|
602
|
+
npx beth-copilot quickstart Run init + doctor
|
|
860
603
|
npx beth-copilot help Show this help message
|
|
861
604
|
|
|
862
605
|
${COLORS.bright}Options:${COLORS.reset}
|
|
863
606
|
--force Overwrite existing files
|
|
864
607
|
--skip-backlog Don't create Backlog.md
|
|
865
608
|
--skip-mcp Don't create mcp.json.example
|
|
866
|
-
--skip-beads Skip beads check (not recommended)
|
|
867
609
|
--verbose Show detailed diagnostics on errors
|
|
610
|
+
--check-only Check for updates without modifying files
|
|
868
611
|
|
|
869
612
|
${COLORS.bright}Examples:${COLORS.reset}
|
|
870
613
|
npx beth-copilot init Set up Beth in current project
|
|
@@ -939,7 +682,7 @@ function copyDirRecursive(src, dest, options = {}) {
|
|
|
939
682
|
}
|
|
940
683
|
|
|
941
684
|
async function init(options = {}) {
|
|
942
|
-
const { force = false, skipBacklog = false, skipMcp = false
|
|
685
|
+
const { force = false, skipBacklog = false, skipMcp = false } = options;
|
|
943
686
|
const cwd = process.cwd();
|
|
944
687
|
|
|
945
688
|
// Check for updates
|
|
@@ -1053,142 +796,10 @@ ${COLORS.yellow}╔════════════════════
|
|
|
1053
796
|
logWarning('No files were copied. Use --force to overwrite existing files.');
|
|
1054
797
|
}
|
|
1055
798
|
|
|
1056
|
-
// Check for beads CLI (REQUIRED for Beth)
|
|
1057
|
-
if (!skipBeads) {
|
|
1058
|
-
console.log('');
|
|
1059
|
-
log('Checking beads (required for task tracking)...', COLORS.cyan);
|
|
1060
|
-
|
|
1061
|
-
let bdPath = getBeadsPath();
|
|
1062
|
-
|
|
1063
|
-
// Loop until beads is installed
|
|
1064
|
-
while (!bdPath) {
|
|
1065
|
-
logWarning('beads CLI is not installed.');
|
|
1066
|
-
logInfo('Beth requires beads for task tracking. Agents use it to coordinate work.');
|
|
1067
|
-
logInfo('Learn more: https://github.com/steveyegge/beads');
|
|
1068
|
-
console.log('');
|
|
1069
|
-
|
|
1070
|
-
const shouldInstallBeads = await promptYesNo('Install beads CLI now? (required)');
|
|
1071
|
-
if (shouldInstallBeads) {
|
|
1072
|
-
const installed = await installBeads();
|
|
1073
|
-
if (installed) {
|
|
1074
|
-
// Re-check for beads after installation
|
|
1075
|
-
bdPath = getBeadsPath();
|
|
1076
|
-
if (!bdPath) {
|
|
1077
|
-
console.log('');
|
|
1078
|
-
logWarning('beads installed but not found in common paths.');
|
|
1079
|
-
logInfo('The installer may have placed it in a custom location.');
|
|
1080
|
-
console.log('');
|
|
1081
|
-
logInfo('Please try one of these options:');
|
|
1082
|
-
logInfo(' 1. Open a NEW terminal and run: npx beth-copilot init');
|
|
1083
|
-
logInfo(' 2. Add ~/.local/bin to your PATH and retry');
|
|
1084
|
-
logInfo(' 3. Run: source ~/.bashrc (or ~/.zshrc) then retry');
|
|
1085
|
-
console.log('');
|
|
1086
|
-
|
|
1087
|
-
const retryCheck = await promptYesNo('Retry detection? (select No to enter path manually)');
|
|
1088
|
-
if (retryCheck) {
|
|
1089
|
-
bdPath = getBeadsPath();
|
|
1090
|
-
continue;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// Allow manual path entry
|
|
1094
|
-
const customPath = await promptForInput('Enter full path to bd binary (or press Enter to retry installation):');
|
|
1095
|
-
if (customPath) {
|
|
1096
|
-
const validation = validateBeadsPath(customPath);
|
|
1097
|
-
if (validation.valid) {
|
|
1098
|
-
bdPath = validation.normalizedPath;
|
|
1099
|
-
logSuccess(`Found beads at: ${bdPath}`);
|
|
1100
|
-
} else {
|
|
1101
|
-
logError(`Invalid path: ${validation.error}`);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
} else {
|
|
1106
|
-
console.log('');
|
|
1107
|
-
logError('Installation script failed.');
|
|
1108
|
-
logInfo('You can try installing manually:');
|
|
1109
|
-
logInfo(' curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash');
|
|
1110
|
-
console.log('');
|
|
1111
|
-
}
|
|
1112
|
-
} else {
|
|
1113
|
-
console.log('');
|
|
1114
|
-
logError('beads is REQUIRED for Beth to function.');
|
|
1115
|
-
logInfo('Beth agents use beads to track tasks, dependencies, and coordinate work.');
|
|
1116
|
-
logInfo('Without beads, the multi-agent workflow will not work correctly.');
|
|
1117
|
-
console.log('');
|
|
1118
|
-
|
|
1119
|
-
const tryAgain = await promptYesNo('Would you like to try installing beads?');
|
|
1120
|
-
if (!tryAgain) {
|
|
1121
|
-
logError('Cannot continue without beads. Exiting.');
|
|
1122
|
-
logInfo('Install beads manually and run "npx beth-copilot init" again:');
|
|
1123
|
-
logInfo(' npm install -g @beads/bd');
|
|
1124
|
-
process.exit(1);
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// Show path info if not in standard PATH
|
|
1130
|
-
if (bdPath && bdPath !== 'bd') {
|
|
1131
|
-
logSuccess(`beads CLI found at: ${bdPath}`);
|
|
1132
|
-
const isWindows = process.platform === 'win32';
|
|
1133
|
-
if (isWindows) {
|
|
1134
|
-
logInfo('Tip: Ensure npm global bin is in your PATH to use "bd" directly.');
|
|
1135
|
-
} else {
|
|
1136
|
-
logInfo('Tip: Add ~/.local/bin or npm global bin to your PATH to use "bd" directly.');
|
|
1137
|
-
}
|
|
1138
|
-
} else {
|
|
1139
|
-
logSuccess('beads CLI is installed');
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// Initialize beads in the project if not already done
|
|
1143
|
-
if (!isBeadsInitialized(cwd)) {
|
|
1144
|
-
logInfo('beads not initialized in this project.');
|
|
1145
|
-
let initialized = false;
|
|
1146
|
-
|
|
1147
|
-
while (!initialized) {
|
|
1148
|
-
const shouldInitBeads = await promptYesNo('Initialize beads now? (required)');
|
|
1149
|
-
if (shouldInitBeads) {
|
|
1150
|
-
initialized = await initializeBeads(cwd);
|
|
1151
|
-
if (!initialized) {
|
|
1152
|
-
logWarning('Initialization failed. Let\'s try again.');
|
|
1153
|
-
}
|
|
1154
|
-
} else {
|
|
1155
|
-
logError('beads must be initialized for Beth to work correctly.');
|
|
1156
|
-
logInfo('The .beads directory stores task tracking data used by all agents.');
|
|
1157
|
-
console.log('');
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
} else {
|
|
1161
|
-
logSuccess('beads is initialized in this project');
|
|
1162
|
-
}
|
|
1163
|
-
} else {
|
|
1164
|
-
logWarning('Skipped beads check (--skip-beads). Beth may not function correctly.');
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
// Run bd doctor to verify beads configuration
|
|
1168
|
-
if (!skipBeads && getBeadsPath() && isBeadsInitialized(cwd)) {
|
|
1169
|
-
await runBeadsDoctor();
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
// Install pre-push guard hook
|
|
1173
|
-
if (!skipBeads && isBeadsInitialized(cwd)) {
|
|
1174
|
-
installPrePushGuard(cwd);
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
799
|
// Final verification
|
|
1178
800
|
console.log('');
|
|
1179
801
|
log('Verifying installation...', COLORS.cyan);
|
|
1180
|
-
|
|
1181
|
-
const finalBeadsOk = skipBeads || getBeadsPath();
|
|
1182
|
-
const finalBeadsInit = skipBeads || isBeadsInitialized(cwd);
|
|
1183
|
-
|
|
1184
|
-
if (finalBeadsOk && finalBeadsInit) {
|
|
1185
|
-
logSuccess('All dependencies installed and configured!');
|
|
1186
|
-
} else {
|
|
1187
|
-
if (!finalBeadsOk) logError('beads CLI not found');
|
|
1188
|
-
if (!finalBeadsInit) logError('beads not initialized in project');
|
|
1189
|
-
logError('Setup incomplete. Please resolve issues above and run init again.');
|
|
1190
|
-
process.exit(1);
|
|
1191
|
-
}
|
|
802
|
+
logSuccess('All files installed and configured!');
|
|
1192
803
|
|
|
1193
804
|
// Next steps
|
|
1194
805
|
console.log(`
|
|
@@ -1207,15 +818,15 @@ ${COLORS.cyan}"They broke my wings and forgot I had claws."${COLORS.reset}
|
|
|
1207
818
|
}
|
|
1208
819
|
|
|
1209
820
|
// Input validation constants
|
|
1210
|
-
const ALLOWED_COMMANDS = ['init', 'help', '--help', '-h', 'doctor', 'quickstart', '
|
|
1211
|
-
const ALLOWED_FLAGS = ['--force', '--skip-backlog', '--skip-mcp', '--
|
|
821
|
+
const ALLOWED_COMMANDS = ['init', 'help', '--help', '-h', 'doctor', 'quickstart', 'pre-push-guard', 'update', 'land'];
|
|
822
|
+
const ALLOWED_FLAGS = ['--force', '--skip-backlog', '--skip-mcp', '--verbose', '--reason', '-r', '-f', '--skip-tests', '--message', '-m', '--dry-run', '--check-only'];
|
|
1212
823
|
const MAX_ARG_LENGTH = 50;
|
|
1213
824
|
|
|
1214
825
|
// Validate and sanitize input
|
|
1215
826
|
function validateArgs(args) {
|
|
1216
|
-
// The '
|
|
827
|
+
// The 'land' and 'update' commands handle their own arg validation
|
|
1217
828
|
const command = args[0]?.toLowerCase();
|
|
1218
|
-
if (command === '
|
|
829
|
+
if (command === 'land' || command === 'update') return;
|
|
1219
830
|
|
|
1220
831
|
for (const arg of args) {
|
|
1221
832
|
// Prevent excessively long arguments (log injection, DoS)
|
|
@@ -1241,7 +852,6 @@ const options = {
|
|
|
1241
852
|
force: args.includes('--force'),
|
|
1242
853
|
skipBacklog: args.includes('--skip-backlog'),
|
|
1243
854
|
skipMcp: args.includes('--skip-mcp'),
|
|
1244
|
-
skipBeads: args.includes('--skip-beads'),
|
|
1245
855
|
verbose: args.includes('--verbose'),
|
|
1246
856
|
};
|
|
1247
857
|
|
|
@@ -1249,8 +859,8 @@ const options = {
|
|
|
1249
859
|
globalThis.VERBOSE = options.verbose;
|
|
1250
860
|
|
|
1251
861
|
// Validate unknown flags (exclude --help which is handled as a command)
|
|
1252
|
-
// Skip for '
|
|
1253
|
-
if (command !== '
|
|
862
|
+
// Skip for 'land' and 'update' commands which handle their own arg parsing
|
|
863
|
+
if (command !== 'land' && command !== 'update') {
|
|
1254
864
|
const unknownFlags = args.filter(arg => arg.startsWith('--') && !ALLOWED_FLAGS.includes(arg) && arg !== '--help');
|
|
1255
865
|
if (unknownFlags.length > 0) {
|
|
1256
866
|
logError(`Unknown flag: ${unknownFlags[0].slice(0, MAX_ARG_LENGTH)}`);
|
|
@@ -1283,14 +893,6 @@ switch (command) {
|
|
|
1283
893
|
await quickstart(options);
|
|
1284
894
|
}
|
|
1285
895
|
break;
|
|
1286
|
-
case 'close':
|
|
1287
|
-
{
|
|
1288
|
-
const { close } = await loadTsCommand('close');
|
|
1289
|
-
// Pass raw args after 'close' — the command handles its own parsing
|
|
1290
|
-
const closeArgs = process.argv.slice(3);
|
|
1291
|
-
await close(closeArgs);
|
|
1292
|
-
}
|
|
1293
|
-
break;
|
|
1294
896
|
case 'land':
|
|
1295
897
|
{
|
|
1296
898
|
const { land } = await loadTsCommand('land');
|
|
@@ -1299,6 +901,13 @@ switch (command) {
|
|
|
1299
901
|
await land(landArgs);
|
|
1300
902
|
}
|
|
1301
903
|
break;
|
|
904
|
+
case 'update':
|
|
905
|
+
{
|
|
906
|
+
const { update } = await loadTsCommand('update');
|
|
907
|
+
const updateArgs = process.argv.slice(3);
|
|
908
|
+
await update(updateArgs);
|
|
909
|
+
}
|
|
910
|
+
break;
|
|
1302
911
|
case 'pre-push-guard':
|
|
1303
912
|
{
|
|
1304
913
|
const { prePushGuard } = await loadTsCommand('pre-push-guard');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for .github/hooks/scripts/inject-skills.mjs
|
|
3
|
+
*
|
|
4
|
+
* Tests the SubagentStart hook that deterministically injects skill context
|
|
5
|
+
* for each agent type. Verifies correct skill mapping, output format,
|
|
6
|
+
* and graceful handling of edge cases.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=inject-skills.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inject-skills.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/inject-skills.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for .github/hooks/scripts/inject-skills.mjs
|
|
3
|
+
*
|
|
4
|
+
* Tests the SubagentStart hook that deterministically injects skill context
|
|
5
|
+
* for each agent type. Verifies correct skill mapping, output format,
|
|
6
|
+
* and graceful handling of edge cases.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
const SCRIPT_PATH = join(process.cwd(), '.github/hooks/scripts/inject-skills.mjs');
|
|
12
|
+
const PROJECT_ROOT = process.cwd();
|
|
13
|
+
/** Helper: pipe JSON input to inject-skills.mjs and parse the JSON output */
|
|
14
|
+
function runHook(input) {
|
|
15
|
+
const result = execFileSync('node', [SCRIPT_PATH], {
|
|
16
|
+
input: JSON.stringify(input),
|
|
17
|
+
encoding: 'utf8',
|
|
18
|
+
cwd: PROJECT_ROOT,
|
|
19
|
+
timeout: 10000,
|
|
20
|
+
});
|
|
21
|
+
return JSON.parse(result);
|
|
22
|
+
}
|
|
23
|
+
/** Helper: extract additionalContext string from hook output */
|
|
24
|
+
function getContext(input) {
|
|
25
|
+
const output = runHook(input);
|
|
26
|
+
return output.hookSpecificOutput?.additionalContext ?? '';
|
|
27
|
+
}
|
|
28
|
+
// ─── Core behavior ─────────────────────────────────────────────────────────
|
|
29
|
+
describe('inject-skills.mjs: output structure', () => {
|
|
30
|
+
it('should always set continue: true', () => {
|
|
31
|
+
const output = runHook({ agent_type: 'developer', cwd: PROJECT_ROOT });
|
|
32
|
+
expect(output.continue).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
it('should include hookSpecificOutput with SubagentStart event name', () => {
|
|
35
|
+
const output = runHook({ agent_type: 'developer', cwd: PROJECT_ROOT });
|
|
36
|
+
expect(output.hookSpecificOutput).toBeDefined();
|
|
37
|
+
expect(output.hookSpecificOutput.hookEventName).toBe('SubagentStart');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
// ─── UX Designer (includes ui-ux-pro-max) ──────────────────────────────────
|
|
41
|
+
describe('inject-skills.mjs: ux-designer', () => {
|
|
42
|
+
const ctx = () => getContext({ agent_type: 'ux-designer', cwd: PROJECT_ROOT });
|
|
43
|
+
it('should inject web-design-guidelines into context', () => {
|
|
44
|
+
expect(ctx()).toContain('.github/skills/web-design-guidelines/SKILL.md');
|
|
45
|
+
expect(ctx()).toContain('Skills loaded into context');
|
|
46
|
+
});
|
|
47
|
+
it('should mandate readFile for framer-components', () => {
|
|
48
|
+
expect(ctx()).toContain('.github/skills/framer-components/SKILL.md');
|
|
49
|
+
expect(ctx()).toContain('Skills to load via readFile');
|
|
50
|
+
});
|
|
51
|
+
it('should mandate readFile for ui-ux-pro-max', () => {
|
|
52
|
+
expect(ctx()).toContain('.github/prompts/ui-ux-pro-max/PROMPT.md');
|
|
53
|
+
});
|
|
54
|
+
it('should include the NON-NEGOTIABLE header', () => {
|
|
55
|
+
expect(ctx()).toContain('SKILL ENFORCEMENT');
|
|
56
|
+
expect(ctx()).toContain('NON-NEGOTIABLE');
|
|
57
|
+
});
|
|
58
|
+
it('should identify the agent type in the context', () => {
|
|
59
|
+
expect(ctx()).toContain('You are `ux-designer`');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
// ─── Developer ─────────────────────────────────────────────────────────────
|
|
63
|
+
describe('inject-skills.mjs: developer', () => {
|
|
64
|
+
const ctx = () => getContext({ agent_type: 'developer', cwd: PROJECT_ROOT });
|
|
65
|
+
it('should inject vercel-react-best-practices SKILL.md into context', () => {
|
|
66
|
+
expect(ctx()).toContain('.github/skills/vercel-react-best-practices/SKILL.md');
|
|
67
|
+
expect(ctx()).toContain('Skills loaded into context');
|
|
68
|
+
});
|
|
69
|
+
it('should mandate readFile for shadcn-ui', () => {
|
|
70
|
+
expect(ctx()).toContain('.github/skills/shadcn-ui/SKILL.md');
|
|
71
|
+
});
|
|
72
|
+
it('should mandate readFile for vercel-react-best-practices AGENTS.md', () => {
|
|
73
|
+
expect(ctx()).toContain('.github/skills/vercel-react-best-practices/AGENTS.md');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
// ─── Product Manager ───────────────────────────────────────────────────────
|
|
77
|
+
describe('inject-skills.mjs: product-manager', () => {
|
|
78
|
+
const ctx = () => getContext({ agent_type: 'product-manager', cwd: PROJECT_ROOT });
|
|
79
|
+
it('should mandate readFile for prd skill', () => {
|
|
80
|
+
expect(ctx()).toContain('.github/skills/prd/SKILL.md');
|
|
81
|
+
});
|
|
82
|
+
it('should NOT have "Skills loaded into context" (no inject files)', () => {
|
|
83
|
+
expect(ctx()).not.toContain('Skills loaded into context');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
// ─── Security Reviewer ─────────────────────────────────────────────────────
|
|
87
|
+
describe('inject-skills.mjs: security-reviewer', () => {
|
|
88
|
+
const ctx = () => getContext({ agent_type: 'security-reviewer', cwd: PROJECT_ROOT });
|
|
89
|
+
it('should mandate readFile for security-analysis', () => {
|
|
90
|
+
expect(ctx()).toContain('.github/skills/security-analysis/SKILL.md');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
// ─── Tester ────────────────────────────────────────────────────────────────
|
|
94
|
+
describe('inject-skills.mjs: tester', () => {
|
|
95
|
+
const ctx = () => getContext({ agent_type: 'tester', cwd: PROJECT_ROOT });
|
|
96
|
+
it('should inject web-design-guidelines into context', () => {
|
|
97
|
+
expect(ctx()).toContain('.github/skills/web-design-guidelines/SKILL.md');
|
|
98
|
+
expect(ctx()).toContain('Skills loaded into context');
|
|
99
|
+
});
|
|
100
|
+
it('should NOT have readFile mandate (no readFile files)', () => {
|
|
101
|
+
expect(ctx()).not.toContain('Skills to load via readFile');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
// ─── Researcher ────────────────────────────────────────────────────────────
|
|
105
|
+
describe('inject-skills.mjs: researcher', () => {
|
|
106
|
+
const ctx = () => getContext({ agent_type: 'researcher', cwd: PROJECT_ROOT });
|
|
107
|
+
it('should inject web-search skill into context', () => {
|
|
108
|
+
expect(ctx()).toContain('.github/skills/web-search/SKILL.md');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
// ─── Edge cases ────────────────────────────────────────────────────────────
|
|
112
|
+
describe('inject-skills.mjs: edge cases', () => {
|
|
113
|
+
it('should pass through unknown agent types with continue: true', () => {
|
|
114
|
+
const output = runHook({ agent_type: 'unknown-agent', cwd: PROJECT_ROOT });
|
|
115
|
+
expect(output.continue).toBe(true);
|
|
116
|
+
expect(output).not.toHaveProperty('hookSpecificOutput');
|
|
117
|
+
});
|
|
118
|
+
it('should handle missing agent_type gracefully', () => {
|
|
119
|
+
const output = runHook({ cwd: PROJECT_ROOT });
|
|
120
|
+
expect(output.continue).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
it('should handle malformed JSON input gracefully', () => {
|
|
123
|
+
const result = execFileSync('node', [SCRIPT_PATH], {
|
|
124
|
+
input: 'not json at all',
|
|
125
|
+
encoding: 'utf8',
|
|
126
|
+
cwd: PROJECT_ROOT,
|
|
127
|
+
timeout: 10000,
|
|
128
|
+
});
|
|
129
|
+
const output = JSON.parse(result);
|
|
130
|
+
expect(output.continue).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
it('should handle empty input gracefully', () => {
|
|
133
|
+
const result = execFileSync('node', [SCRIPT_PATH], {
|
|
134
|
+
input: '',
|
|
135
|
+
encoding: 'utf8',
|
|
136
|
+
cwd: PROJECT_ROOT,
|
|
137
|
+
timeout: 10000,
|
|
138
|
+
});
|
|
139
|
+
const output = JSON.parse(result);
|
|
140
|
+
expect(output.continue).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
//# sourceMappingURL=inject-skills.test.js.map
|