* fix: read resources from dist/ to prevent branch-drift in npm-link setups
initResources() reads extensions, prompts, skills, and agents from
src/resources/ — which points into the live working tree when gsd is
installed via npm link. Switching branches in the gsd repo changes
src/resources/ for ALL projects using gsd, causing stale or broken
extensions to be synced to ~/.gsd/agent/ on next launch.
Fix: the build step now copies src/resources/ to dist/resources/.
At runtime, resource-loader.ts and loader.ts prefer dist/resources/
(stable, set at build time) over src/resources/ (live working tree).
Fallback to src/resources/ is preserved for setups without a build.
Also adds npm run dev watch-resources watcher that syncs src/resources/
to dist/resources/ on file changes, running alongside tsc --watch.
* fix: cache prompt templates per session to prevent cross-session invalidation
When two gsd sessions run concurrently, the second session's
initResources() overwrites ~/.gsd/agent/ templates on disk. The first
session then reads a newer template that expects variables its in-memory
code doesn't know about, causing 'template declares {{X}} but no value
was provided' crashes that hang auto-mode indefinitely.
Fix: cache each template on first read. A running session uses the
template versions from when it first loaded them, immune to later
disk overwrites by other sessions.
58 lines
2 KiB
JavaScript
58 lines
2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Watch src/resources/ and sync changes to dist/resources/.
|
|
*
|
|
* Runs alongside `tsc --watch` to ensure non-TS resources (prompts, agents,
|
|
* skills, workflow files) are kept in sync with the build output.
|
|
*
|
|
* This solves the `npm link` branch-drift problem: without dist/resources/,
|
|
* `initResources()` reads from src/resources/ which changes with git branch
|
|
* switches, causing stale extensions to be synced to ~/.gsd/agent/ for ALL
|
|
* projects using gsd.
|
|
*/
|
|
|
|
import { watch } from 'node:fs'
|
|
import { cpSync, mkdirSync, rmSync } from 'node:fs'
|
|
import { resolve, dirname } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
const src = resolve(__dirname, '..', 'src', 'resources')
|
|
const dest = resolve(__dirname, '..', 'dist', 'resources')
|
|
|
|
function sync() {
|
|
// Remove dest first to mirror deletions from src (prevents stale files)
|
|
rmSync(dest, { recursive: true, force: true })
|
|
mkdirSync(dest, { recursive: true })
|
|
cpSync(src, dest, { recursive: true, force: true })
|
|
}
|
|
|
|
// Initial sync
|
|
sync()
|
|
process.stderr.write(`[watch-resources] Initial sync done\n`)
|
|
|
|
// Watch for changes — recursive, debounced.
|
|
// fs.watch({ recursive: true }) is supported on macOS and Windows.
|
|
// On Linux (Node <20.13) it throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM.
|
|
// Fall back to polling on unsupported platforms.
|
|
let timer = null
|
|
const onChange = () => {
|
|
if (timer) clearTimeout(timer)
|
|
timer = setTimeout(() => {
|
|
sync()
|
|
process.stderr.write(`[watch-resources] Synced at ${new Date().toLocaleTimeString()}\n`)
|
|
}, 300)
|
|
}
|
|
|
|
try {
|
|
watch(src, { recursive: true }, onChange)
|
|
} catch {
|
|
// Fallback: poll every 2s (Linux without recursive watch support)
|
|
process.stderr.write(`[watch-resources] fs.watch recursive not supported, falling back to polling\n`)
|
|
setInterval(() => {
|
|
try { sync() } catch {}
|
|
}, 2000)
|
|
}
|
|
|
|
process.stderr.write(`[watch-resources] Watching src/resources/ → dist/resources/\n`)
|