Files
social-media/bootstrap-vdp-agentic.sh
Jonathan Bourdon b6eb692c27
Some checks failed
Backend CI/CD / build_and_deploy (push) Has been cancelled
Frontend CI/CD / build_and_deploy (push) Has been cancelled
chore: moving towards agentic development
2026-04-24 21:12:26 -04:00

1416 lines
27 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
PROJECT_RAW="${1:-}"
if [[ -z "$PROJECT_RAW" ]]; then
echo "Usage: $0 <project-name> [--no-install]"
echo "Example: $0 surveyable"
exit 1
fi
NO_INSTALL=false
for arg in "$@"; do
case "$arg" in
--no-install) NO_INSTALL=true ;;
esac
done
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "Missing required command: $1"
exit 1
fi
}
require_cmd dotnet
require_cmd npm
# Fix old npm config warning:
# npm warn Unknown builtin config "globalignorefile"
if npm config get globalignorefile >/dev/null 2>&1; then
npm config delete globalignorefile >/dev/null 2>&1 || true
fi
to_pascal_case() {
echo "$1" \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9]+/ /g' \
| awk '{
out="";
for (i=1; i<=NF; i++) {
word=$i;
out = out toupper(substr(word,1,1)) substr(word,2);
}
print out;
}'
}
PROJECT_DIR="$PROJECT_RAW"
PASCAL="$(to_pascal_case "$PROJECT_RAW")"
if [[ -z "$PASCAL" ]]; then
echo "Project name must contain at least one letter or number."
exit 1
fi
API_NAME="${PASCAL}.Api"
TEST_NAME="${PASCAL}.Tests"
SLNX_NAME="${PASCAL}.slnx"
BACKEND_PORT=5080
FRONTEND_PORT=5173
COMPOSE_PORT=8080
echo "Project directory: $PROJECT_DIR"
echo ".NET solution: $SLNX_NAME"
echo "API project: $API_NAME"
echo "Test project: $TEST_NAME"
echo "Backend dev URL: http://0.0.0.0:$BACKEND_PORT"
echo "Frontend dev URL: http://0.0.0.0:$FRONTEND_PORT"
echo
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
mkdir -p backend/src backend/tests docs deploy/caddy shared/openapi scripts
########################################
# Backend: .NET 10 API + Tests + .slnx
########################################
cd backend
dotnet new sln -n "$PASCAL"
if [[ ! -f "$SLNX_NAME" ]]; then
echo "Expected $SLNX_NAME, but dotnet did not create it."
echo "Detected SDK:"
dotnet --version
exit 1
fi
dotnet new webapi -n "$API_NAME" -o "src/$API_NAME" --framework net10.0
dotnet new xunit -n "$TEST_NAME" -o "tests/$TEST_NAME" --framework net10.0
dotnet sln "$SLNX_NAME" add "src/$API_NAME/$API_NAME.csproj"
dotnet sln "$SLNX_NAME" add "tests/$TEST_NAME/$TEST_NAME.csproj"
dotnet add "tests/$TEST_NAME/$TEST_NAME.csproj" reference "src/$API_NAME/$API_NAME.csproj"
API_DIR="src/$API_NAME"
rm -f "$API_DIR/WeatherForecast.cs" || true
mkdir -p "$API_DIR/Endpoints/Health"
mkdir -p "$API_DIR/Contracts/Health"
mkdir -p "$API_DIR/Domain"
mkdir -p "$API_DIR/Infrastructure"
mkdir -p "$API_DIR/Properties"
cat > "$API_DIR/Properties/launchSettings.json" <<EOF
{
"\$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"$API_NAME": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://0.0.0.0:$BACKEND_PORT",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
EOF
cat > "$API_DIR/Contracts/Health/HealthResponse.cs" <<EOF
namespace $API_NAME.Contracts.Health;
public sealed record HealthResponse(
string Status,
DateTimeOffset ServerTimeUtc
);
EOF
cat > "$API_DIR/Contracts/Health/HelloResponse.cs" <<EOF
namespace $API_NAME.Contracts.Health;
public sealed record HelloResponse(
string Message
);
EOF
cat > "$API_DIR/Endpoints/Health/HealthEndpoints.cs" <<EOF
using $API_NAME.Contracts.Health;
namespace $API_NAME.Endpoints.Health;
public static class HealthEndpoints
{
public static RouteGroupBuilder MapHealthEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api");
group.MapGet("/health", () =>
Results.Ok(new HealthResponse("ok", DateTimeOffset.UtcNow)))
.WithName("GetHealth")
.WithOpenApi();
group.MapGet("/hello", () =>
Results.Ok(new HelloResponse("Hello from the .NET API")))
.WithName("GetHello")
.WithOpenApi();
return group;
}
}
EOF
cat > "$API_DIR/Program.cs" <<EOF
using Microsoft.AspNetCore.Http.Json;
using $API_NAME.Endpoints.Health;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
});
const string DevCorsPolicy = "DevCors";
builder.Services.AddCors(options =>
{
options.AddPolicy(DevCorsPolicy, policy =>
{
policy
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetIsOriginAllowed(origin =>
{
if (string.IsNullOrWhiteSpace(origin)) return false;
var uri = new Uri(origin);
return uri.Host is "localhost" or "127.0.0.1"
|| uri.Host.StartsWith("192.168.")
|| uri.Host.StartsWith("10.")
|| uri.Host.StartsWith("172.");
});
});
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseCors(DevCorsPolicy);
}
app.MapHealthEndpoints();
app.Run();
EOF
cat > "$API_DIR/Dockerfile" <<EOF
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY backend/$SLNX_NAME backend/
COPY backend/src/$API_NAME/$API_NAME.csproj backend/src/$API_NAME/
COPY backend/tests/$TEST_NAME/$TEST_NAME.csproj backend/tests/$TEST_NAME/
RUN dotnet restore backend/$SLNX_NAME
COPY backend/ backend/
RUN dotnet publish backend/src/$API_NAME/$API_NAME.csproj \\
-c Release \\
-o /app/publish \\
--no-restore
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://0.0.0.0:8080
ENV ASPNETCORE_ENVIRONMENT=Production
EXPOSE 8080
ENTRYPOINT ["dotnet", "$API_NAME.dll"]
EOF
cd ..
########################################
# Frontend: Vue + TypeScript + OpenAPI
########################################
npm create vite@latest frontend -- --template vue-ts --no-interactive
cd frontend
npm pkg set scripts.dev="vite --host 0.0.0.0 --port $FRONTEND_PORT"
npm pkg set scripts.preview="vite preview --host 0.0.0.0 --port 4173"
npm pkg set scripts.api:schema="node scripts/fetch-openapi.mjs"
npm pkg set scripts.api:types="openapi-typescript ../shared/openapi/openapi.json -o src/api/schema.d.ts"
npm pkg set scripts.api:update="npm run api:schema && npm run api:types"
npm pkg set devDependencies.typescript="~5.9.3"
npm pkg set devDependencies.openapi-typescript="7.13.0"
npm pkg set dependencies.openapi-fetch="0.17.0"
mkdir -p src/app src/pages src/features/health/components src/components src/layouts src/router src/stores src/api scripts
rm -f src/main.ts
cat > src/app/main.ts <<'EOF'
import { createApp } from 'vue'
import App from '../App.vue'
createApp(App).mount('#app')
EOF
sed -i 's|/src/main.ts|/src/app/main.ts|g' index.html
cat > src/App.vue <<'EOF'
<script setup lang="ts">
import HomePage from './pages/HomePage.vue'
</script>
<template>
<HomePage />
</template>
EOF
cat > src/pages/HomePage.vue <<'EOF'
<script setup lang="ts">
import HealthPanel from '../features/health/components/HealthPanel.vue'
</script>
<template>
<main class="page">
<section class="hero">
<h1>Project is ready</h1>
<p>Vue + TypeScript frontend, .NET backend, OpenAPI sync, Docker Compose, Caddy, and agentic workflow docs.</p>
</section>
<HealthPanel />
</main>
</template>
<style scoped>
.page {
max-width: 960px;
margin: 0 auto;
padding: 3rem 1.5rem;
font-family: system-ui, sans-serif;
}
.hero {
margin-bottom: 2rem;
}
.hero h1 {
margin: 0 0 0.5rem;
font-size: 2.5rem;
}
.hero p {
margin: 0;
color: #555;
}
</style>
EOF
cat > src/features/health/types.ts <<'EOF'
export type HealthStatus = {
status: string
serverTimeUtc: string
}
EOF
cat > src/features/health/api.ts <<EOF
import { api } from '../../api/client'
export async function getHealth() {
const { data, error } = await api.GET('/api/health')
if (error) {
throw error
}
return data
}
EOF
cat > src/features/health/components/HealthPanel.vue <<'EOF'
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getHealth } from '../api'
const loading = ref(true)
const error = ref<string | null>(null)
const status = ref<string | null>(null)
const serverTimeUtc = ref<string | null>(null)
onMounted(async () => {
try {
const result = await getHealth()
status.value = result?.status ?? null
serverTimeUtc.value = result?.serverTimeUtc ?? null
} catch (err) {
error.value = err instanceof Error ? err.message : 'Failed to call backend'
} finally {
loading.value = false
}
})
</script>
<template>
<section class="panel">
<h2>Backend health</h2>
<p v-if="loading">Checking backend...</p>
<p v-else-if="error" class="error">{{ error }}</p>
<dl v-else>
<div>
<dt>Status</dt>
<dd>{{ status }}</dd>
</div>
<div>
<dt>Server UTC time</dt>
<dd>{{ serverTimeUtc }}</dd>
</div>
</dl>
</section>
</template>
<style scoped>
.panel {
border: 1px solid #ddd;
border-radius: 12px;
padding: 1rem;
}
.panel h2 {
margin-top: 0;
}
dl {
display: grid;
gap: 0.75rem;
}
dt {
font-weight: 700;
}
dd {
margin: 0.25rem 0 0;
}
.error {
color: #b00020;
}
</style>
EOF
cat > src/api/client.ts <<EOF
import createClient from 'openapi-fetch'
import type { paths } from './schema'
export const api = createClient<paths>({
baseUrl: import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:$BACKEND_PORT',
})
EOF
cat > src/api/schema.d.ts <<'EOF'
export interface paths {
'/api/health': {
get: {
responses: {
200: {
content: {
'application/json': {
status: string
serverTimeUtc: string
}
}
}
}
}
}
}
EOF
cat > scripts/fetch-openapi.mjs <<EOF
import { writeFile, mkdir } from 'node:fs/promises';
const url = process.env.OPENAPI_URL ?? 'http://localhost:$BACKEND_PORT/openapi/v1.json';
const output = new URL('../../shared/openapi/openapi.json', import.meta.url);
console.log(\`Fetching OpenAPI schema from \${url}\`);
const response = await fetch(url);
if (!response.ok) {
throw new Error(\`Failed to fetch OpenAPI schema: \${response.status} \${response.statusText}\`);
}
const schema = await response.text();
await mkdir(new URL('../../shared/openapi', import.meta.url), { recursive: true });
await writeFile(output, schema);
console.log(\`Wrote \${output.pathname}\`);
EOF
cat > vite.config.ts <<EOF
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
host: '0.0.0.0',
port: $FRONTEND_PORT,
strictPort: false,
proxy: {
'/api': {
target: 'http://localhost:$BACKEND_PORT',
changeOrigin: true,
},
},
},
preview: {
host: '0.0.0.0',
},
})
EOF
cat > Dockerfile <<EOF
FROM node:22-alpine AS build
WORKDIR /app
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
FROM caddy:2-alpine AS runtime
COPY --from=build /app/dist /srv
EOF
if [[ "$NO_INSTALL" == "false" ]]; then
npm install
fi
cd ..
########################################
# Root scripts
########################################
cat > scripts/dev-backend.sh <<EOF
#!/usr/bin/env bash
set -euo pipefail
dotnet run --project backend/src/$API_NAME/$API_NAME.csproj --urls http://0.0.0.0:$BACKEND_PORT
EOF
cat > scripts/dev-frontend.sh <<EOF
#!/usr/bin/env bash
set -euo pipefail
cd frontend
npm run dev
EOF
cat > scripts/update-openapi.sh <<EOF
#!/usr/bin/env bash
set -euo pipefail
cd frontend
npm run api:update
EOF
chmod +x scripts/*.sh
########################################
# Docker Compose + Caddy
########################################
cat > docker-compose.yml <<EOF
services:
api:
build:
context: .
dockerfile: backend/src/$API_NAME/Dockerfile
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://0.0.0.0:8080
expose:
- "8080"
web:
build:
context: .
dockerfile: frontend/Dockerfile
depends_on:
- api
ports:
- "$COMPOSE_PORT:80"
volumes:
- ./deploy/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
EOF
cat > deploy/caddy/Caddyfile <<'EOF'
:80 {
root * /srv
encode gzip zstd
handle /api/* {
reverse_proxy api:8080
}
handle {
try_files {path} /index.html
file_server
}
}
EOF
########################################
# Docs + Agentic workflow
########################################
mkdir -p docs/FEATURES docs/TASKS docs/PROMPTS docs/DECISIONS
cat > README.md <<EOF
# $PASCAL
Monorepo with:
- Backend: .NET 10 Web API
- Frontend: Vue 3 + TypeScript + Vite
- API contract: OpenAPI generated from backend, TypeScript generated for frontend
- Deployment: Docker Compose + Caddy
- Agentic workflow docs for Codex, Claude, ChatGPT, and other coding agents
## Local development
Terminal 1:
\`\`\`bash
./scripts/dev-backend.sh
\`\`\`
Terminal 2:
\`\`\`bash
./scripts/dev-frontend.sh
\`\`\`
Frontend:
\`\`\`txt
http://localhost:$FRONTEND_PORT
http://<this-machine-lan-ip>:$FRONTEND_PORT
\`\`\`
Backend:
\`\`\`txt
http://localhost:$BACKEND_PORT
http://<this-machine-lan-ip>:$BACKEND_PORT
\`\`\`
## Update frontend API types from backend
The backend must be running first.
\`\`\`bash
./scripts/update-openapi.sh
\`\`\`
This writes:
\`\`\`txt
shared/openapi/openapi.json
frontend/src/api/schema.d.ts
\`\`\`
## Docker Compose
\`\`\`bash
docker compose up --build
\`\`\`
Then open:
\`\`\`txt
http://localhost:$COMPOSE_PORT
http://<this-machine-lan-ip>:$COMPOSE_PORT
\`\`\`
## Solution
\`\`\`bash
dotnet build backend/$SLNX_NAME
dotnet test backend/$SLNX_NAME
\`\`\`
## Agentic workflow
Start here:
\`\`\`txt
docs/AGENTIC_WORKFLOW.md
\`\`\`
Use feature specs, task files, and prompt templates instead of asking agents to work from vague chat history.
EOF
cat > docs/ARCHITECTURE.md <<EOF
# Architecture
## Backend
\`\`\`txt
backend/
├─ $SLNX_NAME
├─ src/$API_NAME/
│ ├─ Contracts/
│ ├─ Domain/
│ ├─ Endpoints/
│ └─ Infrastructure/
└─ tests/$TEST_NAME/
\`\`\`
The backend intentionally starts with one API project plus one test project.
Split into more projects only when the pressure is real:
- domain logic becomes hard to isolate
- infrastructure dependencies pollute the API project
- tests need clearer boundaries
- multiple apps need shared backend code
## Frontend
\`\`\`txt
frontend/src/
├─ api/
├─ app/
├─ components/
├─ features/
├─ layouts/
├─ pages/
├─ router/
└─ stores/
\`\`\`
## Feature pattern
Frontend features should usually look like:
\`\`\`txt
features/example/
├─ api.ts
├─ types.ts
└─ components/
\`\`\`
Backend endpoints should usually look like:
\`\`\`txt
Endpoints/Example/
Contracts/Example/
\`\`\`
## API contract
The backend exposes OpenAPI at:
\`\`\`txt
/openapi/v1.json
\`\`\`
The frontend updates its TypeScript API model with:
\`\`\`bash
./scripts/update-openapi.sh
\`\`\`
## Contract rule
Do not manually duplicate backend DTOs into the frontend.
Flow:
\`\`\`txt
Backend contracts -> OpenAPI -> frontend TypeScript types
\`\`\`
EOF
cat > docs/DEVELOPMENT_WORKFLOW.md <<EOF
# Development Workflow
## Before coding
Read:
1. README.md
2. AGENTS.md
3. docs/ARCHITECTURE.md
4. docs/AGENTIC_WORKFLOW.md
5. docs/DEVELOPMENT_WORKFLOW.md
6. Relevant feature spec in docs/FEATURES/
7. Relevant task file in docs/TASKS/
## Backend
\`\`\`bash
./scripts/dev-backend.sh
\`\`\`
## Frontend
\`\`\`bash
./scripts/dev-frontend.sh
\`\`\`
## API model sync
When backend request/response models change:
\`\`\`bash
./scripts/update-openapi.sh
\`\`\`
Then check the frontend TypeScript errors.
## Recommended first commit
After verifying the scaffold:
\`\`\`bash
git init
git add .
git commit -m "chore: bootstrap project"
\`\`\`
EOF
cat > docs/AGENTIC_WORKFLOW.md <<'EOF'
# Agentic Workflow
This repository is designed to be worked on by humans and coding agents.
The goal is to avoid the common failure mode where the first AI prompt creates a lot of code and every later prompt degrades because the agent loses architectural context.
## Core rule
Agents do not work from vague chat history.
Agents work from repository files:
1. `AGENTS.md`
2. `docs/ARCHITECTURE.md`
3. `docs/DEVELOPMENT_WORKFLOW.md`
4. `docs/FEATURES/<feature>.md`
5. `docs/TASKS/<feature>/<task>.md`
6. `docs/PROMPTS/<prompt-template>.md`
## Workflow loop
For every meaningful change:
1. Define or update a feature spec in `docs/FEATURES/`
2. Create a small task in `docs/TASKS/<feature>/`
3. Use the correct prompt template from `docs/PROMPTS/`
4. Ask the agent to implement only that task
5. Review the diff
6. Run tests/builds
7. Update OpenAPI when backend contracts change
8. Commit the task separately
## Unit of work
A task should usually be small enough to finish in one agent session.
Good task examples:
- Add `POST /api/auth/login`
- Add `LoginPage.vue`
- Add typed API wrapper for auth
- Add validation for registration email
- Add integration test for health endpoint
Bad task examples:
- Build auth
- Make the whole SaaS
- Redesign the app
- Add billing and dashboard and admin
## Backend task pattern
Backend changes should generally touch:
```txt
backend/src/<App>.Api/Endpoints/<Feature>/
backend/src/<App>.Api/Contracts/<Feature>/
backend/tests/<App>.Tests/
```
Do not create new backend projects unless there is a clear architectural reason.
## Frontend task pattern
Frontend changes should generally touch:
```txt
frontend/src/features/<feature>/
frontend/src/pages/
frontend/src/api/
```
Prefer feature folders over global folders.
## Contract workflow
When backend endpoints or contracts change:
```bash
./scripts/dev-backend.sh
./scripts/update-openapi.sh
```
Then check frontend TypeScript errors and fix affected frontend code.
## Agent guardrails
Agents must not:
- invent architecture
- perform broad unrelated refactors
- manually duplicate generated API types
- ignore existing docs
- change deployment defaults without updating docs
- silently replace the chosen backend/frontend structure
Agents should:
- make small changes
- update feature specs when behavior changes
- update task files if implementation scope changes
- run or list validation commands
EOF
cat > docs/FEATURE_TEMPLATE.md <<'EOF'
# Feature: <Feature Name>
## Status
Draft | In Progress | Complete
## Goal
Describe the user-facing outcome.
## User stories
- As a <user>, I want <capability>, so that <benefit>.
## Backend
### Endpoints
- `GET /api/...`
- `POST /api/...`
### Contracts
- `<RequestName>`
- `<ResponseName>`
### Domain rules
- Rule 1
- Rule 2
## Frontend
### Pages
- `<PageName>`
### Components
- `<ComponentName>`
### State
- Local component state
- Store state, if needed
## API contract
Describe what needs to appear in OpenAPI.
## Done when
- [ ] Backend endpoint implemented
- [ ] Backend tests added
- [ ] OpenAPI updated
- [ ] Frontend calls generated API types/client
- [ ] Frontend UI implemented
- [ ] Build/test passes
## Notes
Architectural notes, edge cases, and decisions.
EOF
cat > docs/TASK_TEMPLATE.md <<'EOF'
# Task: <Task Name>
## Feature
`docs/FEATURES/<feature>.md`
## Goal
One concrete change.
## Context
What the agent needs to know before coding.
## Files likely to change
- `backend/src/...`
- `frontend/src/...`
## Constraints
- Follow existing architecture.
- Do not refactor unrelated files.
- Do not manually duplicate generated API types.
- Keep the diff focused.
## Implementation steps
1. Step one
2. Step two
3. Step three
## Done when
- [ ] Code implemented
- [ ] Tests/build pass
- [ ] OpenAPI regenerated if backend contracts changed
- [ ] Docs updated if behavior changed
## Validation commands
```bash
dotnet test backend/*.slnx
cd frontend && npm run build
```
EOF
cat > docs/PROMPT_TEMPLATE.md <<'EOF'
# General Agent Prompt Template
You are working in this repository.
## Read first
- `AGENTS.md`
- `README.md`
- `docs/ARCHITECTURE.md`
- `docs/DEVELOPMENT_WORKFLOW.md`
- `docs/AGENTIC_WORKFLOW.md`
- `<feature spec>`
- `<task file>`
## Task
Implement:
`<paste task title or path>`
## Rules
- Implement only this task.
- Do not refactor unrelated code.
- Follow existing backend/frontend structure.
- Backend feature code goes under `Endpoints/<Feature>` and `Contracts/<Feature>`.
- Frontend feature code goes under `frontend/src/features/<feature>`.
- If backend contracts change, update OpenAPI using `./scripts/update-openapi.sh`.
- Do not manually duplicate generated API types.
## Output expected
- Code changes
- Tests where appropriate
- Minimal docs update if behavior changed
- Summary of what changed
- Validation commands run
EOF
cat > docs/PROMPTS/backend-task.md <<'EOF'
# Backend Task Prompt
You are implementing a backend task in this repository.
## Read first
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/DEVELOPMENT_WORKFLOW.md`
- `docs/AGENTIC_WORKFLOW.md`
- Relevant feature spec in `docs/FEATURES/`
- Relevant task file in `docs/TASKS/`
## Instructions
- Implement only the requested backend task.
- Use the existing one-project backend structure.
- Put endpoints under `Endpoints/<Feature>/`.
- Put request/response contracts under `Contracts/<Feature>/`.
- Add or update tests under `backend/tests/`.
- Do not introduce repository/service layers unless the task explicitly requires it.
- Do not split the backend into multiple projects.
## After coding
Run or recommend:
```bash
dotnet build backend/*.slnx
dotnet test backend/*.slnx
./scripts/update-openapi.sh
```
EOF
cat > docs/PROMPTS/frontend-task.md <<'EOF'
# Frontend Task Prompt
You are implementing a frontend task in this repository.
## Read first
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/DEVELOPMENT_WORKFLOW.md`
- `docs/AGENTIC_WORKFLOW.md`
- Relevant feature spec in `docs/FEATURES/`
- Relevant task file in `docs/TASKS/`
## Instructions
- Implement only the requested frontend task.
- Put feature code under `frontend/src/features/<feature>/`.
- Put route-level pages under `frontend/src/pages/`.
- Use generated API types from `frontend/src/api/schema.d.ts`.
- Do not manually recreate backend DTOs.
- Do not create global services unless the feature truly needs shared behavior.
## After coding
Run or recommend:
```bash
cd frontend
npm run build
```
EOF
cat > docs/PROMPTS/debug.md <<'EOF'
# Debug Prompt
You are debugging a specific failure in this repository.
## Read first
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/DEVELOPMENT_WORKFLOW.md`
## Error
Paste the full error here.
## Instructions
- Identify the most likely root cause.
- Make the smallest safe fix.
- Do not refactor unrelated code.
- Explain how to verify the fix.
## Validation
List the exact command(s) to run after the fix.
EOF
cat > docs/PROMPTS/refactor.md <<'EOF'
# Refactor Prompt
You are refactoring part of this repository.
## Read first
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/AGENTIC_WORKFLOW.md`
- Relevant feature spec
## Scope
Describe the exact refactor boundary.
## Rules
- Preserve behavior.
- Do not add features.
- Keep the diff focused.
- Update docs if structure or workflow changes.
- Run tests/builds after.
## Validation
```bash
dotnet test backend/*.slnx
cd frontend && npm run build
```
EOF
cat > docs/FEATURES/health.md <<'EOF'
# Feature: Health
## Status
Complete
## Goal
Verify that the frontend can call the backend through the generated OpenAPI TypeScript contract.
## Backend
### Endpoints
- `GET /api/health`
- `GET /api/hello`
### Contracts
- `HealthResponse`
- `HelloResponse`
## Frontend
### Components
- `HealthPanel.vue`
### API
- `features/health/api.ts`
## Done when
- [x] Backend exposes health endpoint
- [x] Frontend calls health endpoint
- [x] OpenAPI can generate frontend types
EOF
mkdir -p docs/TASKS/health
cat > docs/TASKS/health/001-verify-health-slice.md <<'EOF'
# Task: Verify health vertical slice
## Feature
`docs/FEATURES/health.md`
## Goal
Verify the generated project works end-to-end.
## Steps
1. Run the backend.
2. Run the frontend.
3. Open the frontend from localhost and optionally from another LAN machine.
4. Regenerate OpenAPI types.
5. Build/test.
## Validation commands
```bash
./scripts/dev-backend.sh
./scripts/dev-frontend.sh
./scripts/update-openapi.sh
dotnet test backend/*.slnx
cd frontend && npm run build
```
## Done when
- [ ] Health panel shows backend status
- [ ] OpenAPI types regenerate successfully
- [ ] Backend tests pass
- [ ] Frontend build passes
EOF
cat > docs/DECISIONS/0001-use-simple-dotnet-vue-monorepo.md <<EOF
# Decision 0001: Use simple .NET + Vue monorepo
## Status
Accepted
## Context
The project needs to move fast while remaining understandable to humans and coding agents.
## Decision
Use:
- one .NET API project
- one .NET test project
- one Vue TypeScript frontend
- OpenAPI as the backend/frontend contract
- feature-first frontend folders
- endpoint/contract backend folders
## Consequences
Good:
- low ceremony
- easy for agents to understand
- easy to split later if real pressure appears
Tradeoff:
- the API project may grow large over time
## Revisit when
- multiple backend apps need shared code
- infrastructure dependencies become hard to manage
- domain logic becomes too large for one project
EOF
cat > AGENTS.md <<EOF
# AGENTS
This repository is designed for human + AI agent collaboration.
## Read order
Before making code changes, read:
1. README.md
2. docs/AGENTIC_WORKFLOW.md
3. docs/ARCHITECTURE.md
4. docs/DEVELOPMENT_WORKFLOW.md
5. Relevant file in docs/FEATURES/
6. Relevant file in docs/TASKS/
## Core rules
- Do not invent architecture.
- Keep the backend simple unless a task explicitly asks for a split.
- Backend default is one API project plus one test project.
- The solution file is \`backend/$SLNX_NAME\`.
- Backend feature code goes under \`Endpoints/<Feature>\` and \`Contracts/<Feature>\`.
- Frontend feature code goes under \`frontend/src/features/<feature>\`.
- Frontend API types are generated from backend OpenAPI.
- Do not manually duplicate backend DTOs into frontend code.
- If backend DTOs or endpoints change, run \`./scripts/update-openapi.sh\`.
- Dev servers must use HTTP and bind to \`0.0.0.0\` for LAN access.
- Avoid broad refactors unless the task explicitly asks for one.
## Task discipline
Agents should work from task files in \`docs/TASKS/\`.
A good task:
- has a clear goal
- names the relevant feature
- has a small scope
- lists validation commands
If no task exists, create one before implementing a meaningful feature.
## Validation
For backend changes:
\`\`\`bash
dotnet build backend/$SLNX_NAME
dotnet test backend/$SLNX_NAME
\`\`\`
For frontend changes:
\`\`\`bash
cd frontend
npm run build
\`\`\`
For contract changes:
\`\`\`bash
./scripts/update-openapi.sh
\`\`\`
EOF
cat > .gitignore <<'EOF'
# .NET
bin/
obj/
TestResults/
*.user
*.suo
# Node
node_modules/
dist/
.vite/
# Env
.env
.env.*
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
EOF
echo
echo "Bootstrap complete."
echo
echo "Next:"
echo " cd $PROJECT_DIR"
echo " ./scripts/dev-backend.sh"
echo " ./scripts/dev-frontend.sh"
echo
echo "After backend is running:"
echo " ./scripts/update-openapi.sh"
echo
echo "Agentic workflow:"
echo " docs/AGENTIC_WORKFLOW.md"