* Build a working directory that has: * - The /scrape and /skillify skills installed under .claude/skills/ * - The browse binary symlinked + find-browse shim (via setupBrowseShims) * - bin/ scripts referenced by the preamble * - A scoped GSTACK_HOME under the workdir so on-disk artifacts
(suffix: string, installSkills: string[] = ['scrape', 'skillify'])
| 62 | * provenance-refusal scenario doesn't need /scrape). |
| 63 | */ |
| 64 | function setupSkillifyWorkdir(suffix: string, installSkills: string[] = ['scrape', 'skillify']): Workdir { |
| 65 | const workDir = fs.mkdtempSync(path.join(os.tmpdir(), `skill-e2e-skillify-${suffix}-`)); |
| 66 | const gstackHome = path.join(workDir, '.gstack-home'); |
| 67 | fs.mkdirSync(gstackHome, { recursive: true }); |
| 68 | |
| 69 | const run = (cmd: string, args: string[]) => |
| 70 | spawnSync(cmd, args, { cwd: workDir, stdio: 'pipe', timeout: 5000 }); |
| 71 | run('git', ['init', '-b', 'main']); |
| 72 | run('git', ['config', 'user.email', 'test@test.com']); |
| 73 | run('git', ['config', 'user.name', 'Test']); |
| 74 | fs.writeFileSync(path.join(workDir, 'README.md'), '# test\n'); |
| 75 | run('git', ['add', '.']); |
| 76 | run('git', ['commit', '-m', 'initial']); |
| 77 | |
| 78 | setupBrowseShims(workDir); |
| 79 | |
| 80 | // Install requested skills. |
| 81 | const skillsDir = path.join(workDir, '.claude', 'skills'); |
| 82 | for (const skill of installSkills) { |
| 83 | const destDir = path.join(skillsDir, skill); |
| 84 | fs.mkdirSync(destDir, { recursive: true }); |
| 85 | fs.copyFileSync(path.join(ROOT, skill, 'SKILL.md'), path.join(destDir, 'SKILL.md')); |
| 86 | } |
| 87 | |
| 88 | // bin/ scripts — preamble references several of these. |
| 89 | const binDir = path.join(workDir, 'bin'); |
| 90 | fs.mkdirSync(binDir, { recursive: true }); |
| 91 | for (const script of [ |
| 92 | 'gstack-timeline-log', 'gstack-slug', 'gstack-config', |
| 93 | 'gstack-update-check', 'gstack-repo-mode', |
| 94 | 'gstack-learnings-log', 'gstack-learnings-search', |
| 95 | ]) { |
| 96 | const src = path.join(ROOT, 'bin', script); |
| 97 | if (fs.existsSync(src)) { |
| 98 | fs.copyFileSync(src, path.join(binDir, script)); |
| 99 | fs.chmodSync(path.join(binDir, script), 0o755); |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | fs.writeFileSync(path.join(workDir, 'CLAUDE.md'), `# Project Instructions |
| 104 | |
| 105 | ## Skill routing |
| 106 | |
| 107 | When the user's request matches an available skill, ALWAYS invoke it via |
| 108 | the Skill tool as your FIRST action. |
| 109 | |
| 110 | Key routing rules: |
| 111 | - /scrape, "scrape", "get data from", "extract from" → invoke scrape |
| 112 | - /skillify, "skillify", "codify this scrape" → invoke skillify |
| 113 | |
| 114 | Environment: |
| 115 | - GSTACK_HOME="${gstackHome}" for all gstack bin scripts. |
| 116 | - bin scripts are at ./bin/ relative to this directory. |
| 117 | - Browse binary is at ${browseBin} — assign to $B (e.g., \`B=${browseBin}\`). |
| 118 | `); |
| 119 | |
| 120 | return { workDir, gstackHome, skillsDir }; |
| 121 | } |
no test coverage detected