Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM mcr.microsoft.com/devcontainers/typescript-node:22-bookworm

USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends sqlite3 jq ripgrep \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

# GitHub's signed apt repository supplies current gh releases on both CPU families.
RUN mkdir -p -m 755 /etc/apt/keyrings \
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list \
&& apt-get update && apt-get install -y --no-install-recommends gh \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

# Resolve the latest AQE release; post-create also refreshes cached images.
# Transformers is the tutorial's explicit local-embedding opt-in (see README).
ARG AQE_VERSION=latest
ARG TRANSFORMERS_VERSION=4.2.0
ARG CLAUDE_VERSION=latest
ARG CODEX_VERSION=latest
USER node
RUN npm install -g --no-fund --no-audit \
"agentic-qe@${AQE_VERSION}" \
"@huggingface/transformers@${TRANSFORMERS_VERSION}" \
"@anthropic-ai/claude-code@${CLAUDE_VERSION}" \
"@openai/codex@${CODEX_VERSION}"
13 changes: 13 additions & 0 deletions .devcontainer/check-workspace.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Sourced before any mutation: refuse an AQE development checkout or another repo.
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
node -e 'const p=require("./package.json"); if(p.name!=="checkout-demo" || !p.private) { console.error("Refusing setup outside the checkout-demo tutorial repository."); process.exit(1) }'
test -f LAB.md && test -f seed/aqe-seed-patterns.json || {
echo 'Tutorial LAB.md / seed files missing; refusing setup.' >&2
exit 1
}
if [ -L .agentic-qe ] || [ -L .agentic-qe/memory.db ]; then
echo 'Refusing linked AQE data: the tutorial must not write another workspace memory.' >&2
exit 1
fi
28 changes: 28 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "HUSTEF 2026 · The 70% Problem",
"build": { "dockerfile": "Dockerfile", "context": "." },
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/tutorial,type=bind",
"workspaceFolder": "/workspaces/tutorial",
"remoteUser": "node",
"updateRemoteUserUID": true,
"hostRequirements": { "cpus": 4, "memory": "8gb", "storage": "32gb" },
"containerEnv": { "NEXT_TELEMETRY_DISABLED": "1" },
"forwardPorts": [3000],
"portsAttributes": {
"3000": { "label": "Checkout demo", "onAutoForward": "notify" }
},
"otherPortsAttributes": { "onAutoForward": "ignore" },
"postCreateCommand": "bash .devcontainer/post-create.sh",
"waitFor": "postCreateCommand",
"customizations": {
"vscode": {
"extensions": ["anthropic.claude-code", "openai.chatgpt", "esbenp.prettier-vscode"],
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"editor.fontSize": 16,
"terminal.integrated.fontSize": 16
}
},
"codespaces": { "openFiles": ["docs/DEVCONTAINER.md", "LAB.md"] }
}
}
28 changes: 28 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/check-workspace.sh"

# Do not echo environment variables: Codespaces can inject participant secrets.
sudo -n apt-get update -qq
sudo -n apt-get install -y --no-install-recommends gh
npm install -g --no-fund --no-audit agentic-qe@latest @anthropic-ai/claude-code@latest @openai/codex@latest
npm ci --no-fund --no-audit

# A rebuild/resume must never reinitialize a participant's existing learning DB.
if [ ! -e .agentic-qe ]; then
aqe init --auto --skip-code-index
elif [ ! -f .agentic-qe/memory.db ]; then
echo 'Incomplete existing AQE setup. Preserve .agentic-qe and ask a facilitator to inspect it.' >&2
exit 1
else
echo 'Preserving existing AQE setup and learning data.'
fi
node -e 'const p=require("node:child_process").execSync("npm root -g").toString().trim(); console.log(`Using AQE ${require(`${p}/agentic-qe/package.json`).version}`)'

# No fake payment keys, and no overwriting a participant's .env.local.
if [ ! -e .env.local ] && [ ! -L .env.local ]; then
(umask 077; printf '# Optional: add your own Stripe TEST keys from .env.example.\n' > .env.local)
fi

printf '\nContainer ready. Next run: bash .devcontainer/prepare-workshop.sh\n'
printf 'Then: bash .devcontainer/verify.sh, sign in to your agent, and open LAB.md.\n'
30 changes: 30 additions & 0 deletions .devcontainer/prepare-workshop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Explicit participant action, never a restart hook: indexing and seed import write memory.
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/check-workspace.sh"
test -f .agentic-qe/memory.db || { echo 'Run post-create.sh first.' >&2; exit 1; }
MARKER=.agentic-qe/devcontainer-workshop-prepared
if [ -f "$MARKER" ]; then
echo 'Workshop baseline already prepared; preserving your graph and learned patterns.'
bash .devcontainer/verify.sh
exit 0
fi

# SQLite backup includes committed WAL content. Keep a timestamped copy before writes.
mkdir -p .agentic-qe/backups
BACKUP=".agentic-qe/backups/pre-workshop-$(date -u +%Y%m%dT%H%M%SZ)-$$.db"
sqlite3 .agentic-qe/memory.db ".backup '$BACKUP'"
test "$(sqlite3 -readonly "$BACKUP" 'PRAGMA integrity_check;')" = ok
printf 'Pre-workshop memory backup: %s\n' "$BACKUP"

# Indexing with a provider key can make paid LLM calls. Prepare a purely local baseline.
# A minimal child environment excludes every provider credential/endpoint rather
# than maintaining a list of key names. Parent-shell credentials stay untouched.
env -i HOME="$HOME" PATH="$PATH" aqe code index src/
aqe hg stats
aqe memory usage
aqe learning import -i seed/aqe-seed-patterns.json
aqe learning stats
# Verification must succeed before the completion marker is written.
bash .devcontainer/verify.sh
date -u +%FT%TZ > "$MARKER"
26 changes: 26 additions & 0 deletions .devcontainer/smoke-app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Exercise the no-key app surfaces without calling Stripe or making a payment.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
LOG="$(mktemp)"
PAGE="$(mktemp)"
if curl -s http://127.0.0.1:3000/ > /dev/null; then
echo 'Port 3000 is already in use; stop that server before the smoke check.' >&2
exit 1
fi
node node_modules/next/dist/bin/next dev --hostname 0.0.0.0 --port 3000 > "$LOG" 2>&1 &
SERVER_PID=$!
trap 'kill "$SERVER_PID" 2>/dev/null || true; wait "$SERVER_PID" 2>/dev/null || true; rm -f "$LOG" "$PAGE"' EXIT
ready=0
for _ in $(seq 1 90); do
if curl -fsS http://127.0.0.1:3000/ > /dev/null 2>&1; then ready=1; break; fi
if ! kill -0 "$SERVER_PID" 2>/dev/null; then cat "$LOG"; exit 1; fi
sleep 1
done
if [ "$ready" != 1 ]; then cat "$LOG"; exit 1; fi
curl -fsS http://127.0.0.1:3000/ -o "$PAGE"
rg -q 'Premium Wireless Headphones' "$PAGE"
curl -fsS http://127.0.0.1:3000/cart -o "$PAGE"
rg -q 'cart' "$PAGE"
test "$(curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/api/orders)" = 400
echo 'Catalog, cart, and missing-order validation: OK (no payment attempted)'
93 changes: 93 additions & 0 deletions .devcontainer/test-lifecycle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
# Focused failure/preservation tests with fake tooling, never a real learning DB.
set -euo pipefail
SOURCE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FIXTURE="$(mktemp -d)"
trap 'rm -rf "$FIXTURE"' EXIT
mkdir -p "$FIXTURE/.devcontainer" "$FIXTURE/bin"
cp "$SOURCE/post-create.sh" "$SOURCE/prepare-workshop.sh" "$SOURCE/check-workspace.sh" "$FIXTURE/.devcontainer/"
mkdir -p "$FIXTURE/seed"
mkdir -p "$FIXTURE/global/agentic-qe"
printf '{"version":"latest-test-double"}\n' > "$FIXTURE/global/agentic-qe/package.json"
printf '{"name":"checkout-demo","private":true}\n' > "$FIXTURE/package.json"
touch "$FIXTURE/LAB.md" "$FIXTURE/seed/aqe-seed-patterns.json"
cat > "$FIXTURE/bin/npm" <<'MOCK'
#!/usr/bin/env bash
if [ "${1:-}" = root ]; then echo "$FAKE_GLOBAL"; fi
exit "${NPM_FAIL:-0}"
MOCK
printf '#!/usr/bin/env bash\nexit 0\n' > "$FIXTURE/bin/sudo"
cat > "$FIXTURE/bin/sqlite3" <<'MOCK'
#!/usr/bin/env bash
if [ "$1" = -readonly ]; then echo ok; exit 0; fi
# Simulate a successful backup only; no SQLite database is used by these tests.
if [[ "$2" == .backup* ]]; then dest="${2#*\'}"; dest="${dest%\'}"; cp "$1" "$dest"; fi
MOCK
cat > "$FIXTURE/bin/aqe" <<'MOCK'
#!/usr/bin/env bash
if [ "$1" = --version ]; then echo latest-test-double; exit 0; fi
TRACE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/trace"
printf '%s\n' "$*" >> "$TRACE"
if [ "$1" = init ]; then mkdir -p .agentic-qe; printf 'fake database' > .agentic-qe/memory.db; fi
if [ "$1" = code ] && [ -n "${ANTHROPIC_API_KEY:-}" ]; then exit 99; fi
exit 0
MOCK
printf '#!/usr/bin/env bash\nexit "${VERIFY_FAIL:-0}"\n' > "$FIXTURE/.devcontainer/verify.sh"
chmod +x "$FIXTURE/bin/"*
export PATH="$FIXTURE/bin:$PATH" TRACE="$FIXTURE/trace" FAKE_GLOBAL="$FIXTURE/global"
cd "$FIXTURE"

# Running in the AQE development repo (or any other package) must fail before npm.
printf '{"name":"agentic-qe"}\n' > package.json
if bash .devcontainer/post-create.sh; then exit 1; fi
test ! -e "$TRACE"
printf '{"name":"checkout-demo","private":true}\n' > package.json

# Failed dependency install must not initialize memory or create env configuration.
if NPM_FAIL=1 bash .devcontainer/post-create.sh; then exit 1; fi
test ! -e .agentic-qe
test ! -e .env.local

# A fresh setup initializes once and reruns preserve participant files byte-for-byte.
bash .devcontainer/post-create.sh
test "$(wc -l < "$TRACE")" -eq 1
printf 'participant secret placeholder\n' > .env.local
cp .env.local original-env
cp .agentic-qe/memory.db original-db
bash .devcontainer/post-create.sh
test "$(wc -l < "$TRACE")" -eq 1
cmp .env.local original-env
cmp .agentic-qe/memory.db original-db

# Linked AQE data must be refused, including a dangling link.
mv .agentic-qe held-fake-aqe
ln -s held-fake-aqe .agentic-qe
if bash .devcontainer/post-create.sh; then exit 1; fi
rm .agentic-qe
mv held-fake-aqe .agentic-qe

# Even a dangling .env.local link is left untouched.
mv .env.local held-env
ln -s nonexistent-participant-env .env.local
bash .devcontainer/post-create.sh
test -L .env.local
test ! -e nonexistent-participant-env
rm .env.local
mv held-env .env.local

# Partial setup is never silently replaced. This is a fake fixture, not a DB.
mv .agentic-qe/memory.db held-fake-db
if bash .devcontainer/post-create.sh; then exit 1; fi
test ! -e .agentic-qe/memory.db
mv held-fake-db .agentic-qe/memory.db

# A failed verification leaves no marker; retry works and credentials stay local.
if VERIFY_FAIL=1 ANTHROPIC_API_KEY=fake bash .devcontainer/prepare-workshop.sh; then exit 1; fi
test ! -e .agentic-qe/devcontainer-workshop-prepared
ANTHROPIC_API_KEY=fake bash .devcontainer/prepare-workshop.sh
test -f .agentic-qe/devcontainer-workshop-prepared
cp "$TRACE" original-trace
bash .devcontainer/prepare-workshop.sh
cmp "$TRACE" original-trace
cmp .agentic-qe/memory.db original-db
echo 'Lifecycle preservation, incomplete setup, dependency failure, and retry checks: OK'
27 changes: 27 additions & 0 deletions .devcontainer/verify-data.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Validate persisted tutorial data read-only, rather than trusting AQE's import counter.
const { execFileSync } = require('node:child_process');
const { readdirSync } = require('node:fs');
const path = require('node:path');
const db = '.agentic-qe/memory.db';
function query(sql) {
return JSON.parse(execFileSync('sqlite3', ['-readonly', '-json', db, sql], { encoding: 'utf8' }) || '[]');
}
const seeds = require('../seed/aqe-seed-patterns.json').patterns;
const patterns = query('SELECT p.name, e.dimension, length(e.embedding) AS bytes FROM qe_patterns p LEFT JOIN qe_pattern_embeddings e ON e.pattern_id = p.id');
for (const seed of seeds) {
if (!patterns.some(p => p.name === seed.name && p.dimension === 384 && p.bytes > 0)) {
throw new Error(`Seed pattern missing or unembedded: ${seed.name}. Run prepare-workshop.sh; see TROUBLESHOOTING.md.`);
}
}
function files(dir) {
return readdirSync(dir, { withFileTypes: true }).flatMap(e => {
const file = path.join(dir, e.name);
return e.isDirectory() ? files(file) : /\.(ts|tsx)$/.test(e.name) ? [path.resolve(file)] : [];
});
}
const sourceFiles = files('src');
const indexed = new Set(query("SELECT file_path FROM hypergraph_nodes WHERE type = 'file'").map(r => r.file_path));
for (const file of sourceFiles) {
if (!indexed.has(file)) throw new Error(`Source file absent from graph: ${file}`);
}
console.log(`${sourceFiles.length} source files indexed; ${seeds.length}/${seeds.length} checkout seed patterns with 384-dimensional embeddings; ${patterns.length} total patterns: OK`);
55 changes: 55 additions & 0 deletions .devcontainer/verify-mcp.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Real stdio handshake; no provider credentials and no paid model calls.
import { spawn } from 'node:child_process';
import { createInterface } from 'node:readline';
const server = spawn('aqe-mcp', [], {
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, AQE_WORKERS_ENABLED: 'false' },
});
let nextId = 0;
const pending = new Map();
const lines = createInterface({ input: server.stdout });
lines.on('line', line => {
let message;
try { message = JSON.parse(line); } catch { return; }
const waiter = pending.get(message.id);
if (waiter) { pending.delete(message.id); message.error ? waiter.reject(new Error(JSON.stringify(message.error))) : waiter.resolve(message.result); }
});
server.stderr.resume();
server.on('error', error => { for (const waiter of pending.values()) waiter.reject(error); });
server.on('exit', code => { for (const waiter of pending.values()) waiter.reject(new Error(`MCP exited: ${code}`)); });
function request(method, params = {}) {
const id = ++nextId;
return new Promise((resolve, reject) => {
pending.set(id, { resolve, reject });
server.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
});
}
const deadline = setTimeout(() => { console.error('MCP handshake timed out'); server.kill('SIGKILL'); process.exitCode = 1; }, 60000);
try {
await request('initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'hustef-readiness', version: '1.0.0' } });
server.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' })}\n`);
const tools = [];
let cursor;
do {
const result = await request('tools/list', cursor ? { cursor } : {});
tools.push(...result.tools);
cursor = result.nextCursor;
} while (cursor);
const query = tools.find(t => t.name === 'memory_query');
if (!query) throw new Error('AQE memory_query tool missing');
console.log(`AQE MCP handshake and tools/list: OK (${tools.length} tools)`);
const memory = await request('tools/call', { name: 'memory_query', arguments: { pattern: 'payment retry checkout', semantic: true, limit: 2 } });
if (memory.isError) throw new Error(`MCP memory_query failed: ${JSON.stringify(memory.content)}`);
const payload = JSON.parse(memory.content.find(c => c.type === 'text').text);
if (!payload.success || !Array.isArray(payload.data?.entries)) throw new Error(`MCP memory query failed: ${JSON.stringify(payload)}`);
console.log(`AQE MCP memory_query protocol: OK (${payload.data.entries.length} entries returned)`);
if (!payload.data.entries.length) {
console.warn('Recall limitation: this MCP query did not return persisted tutorial data. SQLite seed verification is separate; use LAB.md reports fallback for Exercise 5.');
if (process.argv.includes('--require-recall')) process.exitCode = 1;
}
} finally {
clearTimeout(deadline);
lines.close();
server.stdin.end();
server.kill();
}
21 changes: 21 additions & 0 deletions .devcontainer/verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Read-only readiness checks. Never import patterns, index code, or initialize a DB here.
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/check-workspace.sh"
node -e 'const [a,b]=process.versions.node.split(".").map(Number); if(a<22 || (a===22 && b<13)) process.exit(1)'
node -e 'const p=require("node:child_process").execSync("npm root -g").toString().trim(); for (const n of ["agentic-qe","@huggingface/transformers"]) { const pkg=require(`${p}/${n}/package.json`); console.log(`${n} ${pkg.version}`) }'
command -v claude >/dev/null
command -v codex >/dev/null
command -v gh >/dev/null
test -d node_modules/next
test -f .mcp.json
test -f .claude/agents/v3/qe-test-architect.md
test -f .claude/agents/v3/qe-defect-predictor.md
test -f .claude/agents/v3/qe-queen-coordinator.md
test -f .claude/agents/v3/qe-product-factors-assessor.md
test -f .claude/skills/qcsd-ideation-swarm/SKILL.md
test -f .claude/skills/exploratory-testing-advanced/SKILL.md
test -f .agentic-qe/memory.db
test "$(sqlite3 -readonly .agentic-qe/memory.db 'PRAGMA integrity_check;')" = ok
echo 'Tooling, exercise assets, and SQLite integrity: OK'
node .devcontainer/verify-data.cjs
31 changes: 31 additions & 0 deletions .github/workflows/devcontainer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Workshop devcontainer
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
jobs:
workshop:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7.0.1
with:
persist-credentials: false
- name: Check lifecycle preservation and shell syntax
run: |
for script in .devcontainer/*.sh; do bash -n "$script"; done
bash .devcontainer/test-lifecycle.sh
- name: Build workshop tools with latest AQE
run: docker build -t hustef-workshop .devcontainer
- name: Prepare and exercise the Linux container
run: |
WORKSHOP_RUNNER_UID="$(id -u)"
WORKSHOP_RUNNER_GID="$(id -g)"
trap 'sudo chown -R "$WORKSHOP_RUNNER_UID:$WORKSHOP_RUNNER_GID" "$PWD"' EXIT
docker run --rm --user root \
-v "$PWD:/workspaces/tutorial" -w /workspaces/tutorial \
-e NEXT_TELEMETRY_DISABLED=1 hustef-workshop \
bash -lc 'chown -R node:node /workspaces/tutorial && su node -c "bash .devcontainer/post-create.sh && bash .devcontainer/prepare-workshop.sh && node .devcontainer/verify-mcp.mjs && npm run test:coverage && npx --no-install tsc --noEmit && bash .devcontainer/smoke-app.sh"'
Loading
Loading