feat: mount user repo as read-only with writable shannon overlay (#273)

* feat: mount user repo as read-only with deliverables bind-mount overlay

* feat: add playground and .playwright-cli overlay mounts

* feat: add filesystem context to pipeline-testing prompts

* fix: use explicit REPO_PATH in filesystem prompt for clarity

* fix: update filesystem prompts with playground notes and absolute screenshot paths

* feat: namespace writable overlays under .shannon/ to avoid polluting host repo

* refactor: rename playground to scratchpad

* fix: redirect playwright-cli output to writable .shannon/ overlay

* fix: pre-create .shannon/ overlay mount points for Linux compatibility

* fix: exclude nested node_modules and dist from Docker build context

* fix: enforce LF line endings for shell scripts on Windows
This commit is contained in:
ezl-keygraph
2026-04-03 23:46:28 +05:30
committed by GitHub
parent 99629c2b66
commit 77e300d52a
45 changed files with 293 additions and 240 deletions
+16 -4
View File
@@ -12,7 +12,7 @@ import { ensureImage, ensureInfra, randomSuffix, spawnWorker } from '../docker.j
import { buildEnvFlags, isRouterConfigured, loadEnv, validateCredentials } from '../env.js';
import { getCredentialsPath, getWorkspacesDir, initHome } from '../home.js';
import { isLocal } from '../mode.js';
import { ensureDeliverables, resolveConfig, resolveRepo } from '../paths.js';
import { resolveConfig, resolveRepo } from '../paths.js';
import { displaySplash } from '../splash.js';
export interface StartArgs {
@@ -42,7 +42,6 @@ export async function start(args: StartArgs): Promise<void> {
// 3. Resolve paths
const repo = resolveRepo(args.repo);
const config = args.config ? resolveConfig(args.config) : undefined;
ensureDeliverables(repo.hostPath);
// 4. Ensure workspaces dir is writable by container user (UID 1001)
const workspacesDir = getWorkspacesDir();
@@ -68,7 +67,20 @@ export async function start(args: StartArgs): Promise<void> {
const workspace =
args.workspace ?? `${new URL(args.url).hostname.replace(/[^a-zA-Z0-9-]/g, '-')}_shannon-${Date.now()}`;
// 9. Resolve credentials — mount single file to fixed container path
// 9. Create writable overlay directories (mounted over :ro repo paths inside container)
const workspacePath = path.join(workspacesDir, workspace);
for (const dir of ['deliverables', 'scratchpad', '.playwright-cli']) {
const dirPath = path.join(workspacePath, dir);
fs.mkdirSync(dirPath, { recursive: true });
fs.chmodSync(dirPath, 0o777);
}
// 10. Pre-create overlay mount points (Linux :ro mounts can't auto-create them)
const shannonDir = path.join(repo.hostPath, '.shannon');
for (const dir of ['deliverables', 'scratchpad', '.playwright-cli']) {
fs.mkdirSync(path.join(shannonDir, dir), { recursive: true });
}
const credentialsPath = getCredentialsPath();
const hasCredentials = fs.existsSync(credentialsPath);
@@ -101,7 +113,7 @@ export async function start(args: StartArgs): Promise<void> {
...(hasCredentials && { credentials: credentialsPath }),
...(promptsDir && { promptsDir }),
...(outputDir && { outputDir }),
...(workspace && { workspace }),
workspace,
...(args.pipelineTesting && { pipelineTesting: true }),
});
+9 -5
View File
@@ -194,7 +194,7 @@ export interface WorkerOptions {
credentials?: string;
promptsDir?: string;
outputDir?: string;
workspace?: string;
workspace: string;
pipelineTesting?: boolean;
}
@@ -214,7 +214,13 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess {
// Volume mounts
args.push('-v', `${opts.workspacesDir}:/app/workspaces`);
args.push('-v', `${opts.repo.hostPath}:${opts.repo.containerPath}`);
args.push('-v', `${opts.repo.hostPath}:${opts.repo.containerPath}:ro`);
// Writable overlays: shadow .shannon/ inside the :ro repo with workspace-backed dirs
const workspacePath = path.join(opts.workspacesDir, opts.workspace);
args.push('-v', `${path.join(workspacePath, 'deliverables')}:${opts.repo.containerPath}/.shannon/deliverables`);
args.push('-v', `${path.join(workspacePath, 'scratchpad')}:${opts.repo.containerPath}/.shannon/scratchpad`);
args.push('-v', `${path.join(workspacePath, '.playwright-cli')}:${opts.repo.containerPath}/.shannon/.playwright-cli`);
// Local mode: mount prompts for live editing
if (opts.promptsDir) {
@@ -253,9 +259,7 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess {
if (opts.outputDir) {
args.push('--output', '/app/output');
}
if (opts.workspace) {
args.push('--workspace', opts.workspace);
}
args.push('--workspace', opts.workspace);
if (opts.pipelineTesting) {
args.push('--pipeline-testing');
}
-9
View File
@@ -76,12 +76,3 @@ export function resolveConfig(configArg: string): MountPair {
containerPath: `/app/configs/${basename}`,
};
}
/**
* Ensure the deliverables directory exists and is writable by the container user.
*/
export function ensureDeliverables(repoHostPath: string): void {
const deliverables = path.join(repoHostPath, 'deliverables');
fs.mkdirSync(deliverables, { recursive: true });
fs.chmodSync(deliverables, 0o777);
}