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
1 change: 1 addition & 0 deletions .github/workflows/setup-repo-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ jobs:
run: |
bash tests/setup-repo/test-python-assets.sh
bash tests/setup-repo/test-ci-assets.sh
bash tests/setup-repo/test-node-assets.sh
bash tests/setup-repo/test-scaffold.sh
2 changes: 1 addition & 1 deletion plugins/core/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "core",
"version": "0.3.0",
"version": "0.4.0",
"description": "Core Offworld Labs skills, commands, agents, and hooks shared across all repos.",
"author": {
"name": "Offworld Labs"
Expand Down
26 changes: 15 additions & 11 deletions plugins/core/skills/setup-repo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,26 @@ interactive parts and the report.
(`git rev-parse --is-inside-work-tree`). If it is not, offer to run
`git init`; abort if the user declines.

2. **Determine the stack.** Detect from existing files: `pyproject.toml` or
`requirements*.txt` → `python`. If ambiguous or empty, ask the user to choose
`python` or `none` (ts-frontend / ts-backend arrive in a later phase).
2. **Determine the stack.** Detect from existing files: `pyproject.toml` /
`requirements*.txt` → `python`; `package.json` / `tsconfig.json` → TypeScript
(then ask whether it's `ts-frontend` — a React/Vite app — or `ts-backend` — a
Node service). If ambiguous or empty, ask the user to choose `python`,
`ts-frontend`, `ts-backend`, or `none`.

3. **Scaffold the files.** Run the engine, which never overwrites existing files:
`bash "$ENGINE" . <stack>`
Relay its `WRITTEN` / `SKIPPED` output to the user.

4. **Install dependencies.** For `python`, use `uv` (the org standard, a fast
drop-in for pip that reads the same `requirements.txt`): create a venv and
install with
`uv venv && uv pip install -r requirements.txt -r requirements-dev.txt`.
If `uv` isn't installed, fall back to
`pip install -r requirements.txt -r requirements-dev.txt` in an active
virtualenv. Report the command and result; if the toolchain is unavailable,
skip and note it rather than failing.
4. **Install dependencies.**
- **Python:** use `uv` (the org standard, a fast drop-in for pip that reads the
same `requirements.txt`): `uv venv && uv pip install -r requirements.txt -r requirements-dev.txt`.
Fall back to `pip install -r requirements.txt -r requirements-dev.txt` in an
active virtualenv if `uv` is absent.
- **ts-frontend / ts-backend:** run `npm install` (this generates
`package-lock.json` — remind the user to commit it, since the CI workflow uses
`npm ci`).
Report the command and result; if the toolchain is unavailable, skip and note it
rather than failing.

5. **Flesh out CLAUDE.md.** The stub was just written. Ask the user for a
one-or-two-line description of what this repo does, then fill in the
Expand Down
31 changes: 31 additions & 0 deletions plugins/core/skills/setup-repo/assets/ci/ci-node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm

- name: Install dependencies
run: npm ci

- name: Typecheck
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Test
run: npm test
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
{ ignores: ["dist", "node_modules"] },
js.configs.recommended,
...tseslint.configs.recommended,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
dist/
coverage/
*.log
.env
.DS_Store
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "service",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"typecheck": "tsc --noEmit",
"lint": "eslint .",
"test": "vitest run"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/node": "^22.5.0",
"eslint": "^9.9.0",
"tsx": "^4.19.0",
"typescript": "^5.6.2",
"typescript-eslint": "^8.3.0",
"vitest": "^4.1.4"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { expect, test } from "vitest";
import { version } from "./index";

test("version returns the placeholder", () => {
expect(version()).toBe("0.0.0");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function version(): string {
return "0.0.0";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noEmit": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"types": ["node", "vitest/globals"]
},
"include": ["src"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
environment: "node",
globals: true,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import reactHooks from "eslint-plugin-react-hooks";

export default tseslint.config(
{ ignores: ["dist", "node_modules"] },
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{ts,tsx}"],
plugins: { "react-hooks": reactHooks },
rules: { ...reactHooks.configs.recommended.rules },
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
dist/
coverage/
*.log
.env
.DS_Store
12 changes: 12 additions & 0 deletions plugins/core/skills/setup-repo/assets/stack/ts-frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"typecheck": "tsc --noEmit",
"lint": "eslint .",
"test": "vitest run"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@testing-library/react": "^16.0.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0",
"jsdom": "^25.0.0",
"typescript": "^5.6.2",
"typescript-eslint": "^8.3.0",
"vite": "^5.4.2",
"vitest": "^4.1.4"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { expect, test } from "vitest";
import { version } from "./index";

test("version returns the placeholder", () => {
expect(version()).toBe("0.0.0");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function version(): string {
return "0.0.0";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

const root = document.getElementById("root");
if (root) {
createRoot(root).render(
<StrictMode>
<h1>App</h1>
</StrictMode>,
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noEmit": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"types": ["vitest/globals"]
},
"include": ["src"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";

export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
globals: true,
},
});
26 changes: 24 additions & 2 deletions plugins/core/skills/setup-repo/scripts/scaffold-repo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
# Deterministic file-scaffolding engine for the core:setup-repo skill.
# Copies bundled assets into a target repo without clobbering existing files.
# Usage: scaffold-repo.sh <target_dir> <stack>
# <stack>: python | none (ts-frontend / ts-backend land in Phase 2)
# <stack>: python | ts-frontend | ts-backend | none
set -euo pipefail

TARGET="${1:?target dir required}"
STACK="${2:-none}"
ASSETS="$(cd "$(dirname "${BASH_SOURCE[0]}")/../assets" && pwd)"

case "$STACK" in
python|none) ;;
python|ts-frontend|ts-backend|none) ;;
*) echo "unknown stack: $STACK" >&2; exit 2 ;;
esac

Expand Down Expand Up @@ -51,6 +51,28 @@ case "$STACK" in
copy "$ASSETS/stack/python/tests/.gitkeep" "$TARGET/tests/.gitkeep"
copy "$ASSETS/ci/ci-python.yml" "$TARGET/.github/workflows/ci.yml"
;;
ts-frontend)
copy "$ASSETS/stack/ts-frontend/package.json" "$TARGET/package.json"
copy "$ASSETS/stack/ts-frontend/tsconfig.json" "$TARGET/tsconfig.json"
copy "$ASSETS/stack/ts-frontend/eslint.config.js" "$TARGET/eslint.config.js"
copy "$ASSETS/stack/ts-frontend/vite.config.ts" "$TARGET/vite.config.ts"
copy "$ASSETS/stack/ts-frontend/gitignore" "$TARGET/.gitignore"
copy "$ASSETS/stack/ts-frontend/index.html" "$TARGET/index.html"
copy "$ASSETS/stack/ts-frontend/src/index.ts" "$TARGET/src/index.ts"
copy "$ASSETS/stack/ts-frontend/src/index.test.ts" "$TARGET/src/index.test.ts"
copy "$ASSETS/stack/ts-frontend/src/main.tsx" "$TARGET/src/main.tsx"
copy "$ASSETS/ci/ci-node.yml" "$TARGET/.github/workflows/ci.yml"
;;
ts-backend)
copy "$ASSETS/stack/ts-backend/package.json" "$TARGET/package.json"
copy "$ASSETS/stack/ts-backend/tsconfig.json" "$TARGET/tsconfig.json"
copy "$ASSETS/stack/ts-backend/eslint.config.js" "$TARGET/eslint.config.js"
copy "$ASSETS/stack/ts-backend/vitest.config.ts" "$TARGET/vitest.config.ts"
copy "$ASSETS/stack/ts-backend/gitignore" "$TARGET/.gitignore"
copy "$ASSETS/stack/ts-backend/src/index.ts" "$TARGET/src/index.ts"
copy "$ASSETS/stack/ts-backend/src/index.test.ts" "$TARGET/src/index.test.ts"
copy "$ASSETS/ci/ci-node.yml" "$TARGET/.github/workflows/ci.yml"
;;
none) ;;
esac

Expand Down
Loading
Loading