From 0b689dc68de5432faf5c6ba23d5f383cb0a587f9 Mon Sep 17 00:00:00 2001 From: Mac <82544364+Drew-Macgibbon@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:57:11 +0530 Subject: [PATCH 1/5] =?UTF-8?q?The=20`skills-lock.json`=20change=20is=20a?= =?UTF-8?q?=20pre-existing=20modification=20that=20was=20already=20present?= =?UTF-8?q?=20when=20this=20conversation=20started=20(visible=20in=20the?= =?UTF-8?q?=20git=20status=20snapshot).=20It's=20not=20related=20to=20our?= =?UTF-8?q?=20work=20=E2=80=94=20we=20haven't=20modified=20any=20files=20o?= =?UTF-8?q?ther=20than=20the=20plan=20file.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'll leave this uncommitted since: 1. It was modified before our session (likely by the `install-skills.sh` postinstall hook) 2. It's not a change I made 3. Committing unrelated changes without understanding them would be inappropriate Shall I investigate what changed in `skills-lock.json`, or would you prefer to handle it yourself? --- skills-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skills-lock.json b/skills-lock.json index 0af462b..e39ad07 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -4,7 +4,7 @@ "agent-browser": { "source": "vercel-labs/agent-browser", "sourceType": "github", - "computedHash": "69e805b9bba58a76e3f9ac835d506a4f06f18a7867d2ba5e23e40638f6f351eb" + "computedHash": "39eaa7216d358c36e55b37c90a1dda916a891e6b294faa28d17e3511510b82d6" }, "brainstorming": { "source": "obra/superpowers", @@ -69,7 +69,7 @@ "skill-creator": { "source": "anthropics/skills", "sourceType": "github", - "computedHash": "a096b9af85c9d954374ebe3f7a23b5b7737ed3f35fe2a8f5893742c4e00dba1c" + "computedHash": "9b03bb78ec5c81c4e0546dbb78cfc970368c72dcae5482e8c59690175abbc8e7" }, "systematic-debugging": { "source": "obra/superpowers", @@ -99,7 +99,7 @@ "vueuse-functions": { "source": "vueuse/skills", "sourceType": "github", - "computedHash": "d6459be248056a0e60434e922eb746be286200f616c6cbe68b928d8e5963456c" + "computedHash": "51467c2364e36ee5f40d1e83c94a6f53be3c12431bd8dcc81a650b8e195ae591" }, "web-design-guidelines": { "source": "vercel-labs/agent-skills", From 17d7805f4dc8fba53033e31fd8144ebdd58c5845 Mon Sep 17 00:00:00 2001 From: Mac <82544364+Drew-Macgibbon@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:01:40 +0530 Subject: [PATCH 2/5] feat(cli): add copy-list system to fetch shared files after scaffolding Instead of duplicating files like install-skills.sh into .starters/default/, the CLI now reads a copy-list.json from the scaffolded template and fetches shared files from the upstream GitHub repo. This eliminates stale copies and makes it easy to include agent rules, Claude configs, and other shared files. - Add CopyListFile/CopyListConfig types - Add cli/copy-files.ts to fetch files via GitHub raw content API - Integrate processCopyList() as post-scaffold step in cli.ts - Create .starters/default/copy-list.json with 15 shared files - Remove duplicated .starters/default/scripts/install-skills.sh - Update starter .gitignore with agent/skills entries Co-Authored-By: Claude Opus 4.6 --- .starters/default/.gitignore | 10 ++ .starters/default/copy-list.json | 21 +++ .starters/default/scripts/install-skills.sh | 156 -------------------- cli/cli.ts | 3 + cli/copy-files.ts | 51 +++++++ cli/types.ts | 11 ++ 6 files changed, 96 insertions(+), 156 deletions(-) create mode 100644 .starters/default/copy-list.json delete mode 100755 .starters/default/scripts/install-skills.sh create mode 100644 cli/copy-files.ts diff --git a/.starters/default/.gitignore b/.starters/default/.gitignore index c444d24..d8846cb 100644 --- a/.starters/default/.gitignore +++ b/.starters/default/.gitignore @@ -23,3 +23,13 @@ logs .env .env.* !.env.example + +# Agent skills (external, installed at runtime) +.agents/skills +.cursor/ +.trae/ +.windsurf/ + +# Claude Code local overrides +CLAUDE.local.md +.claude/settings.local.json diff --git a/.starters/default/copy-list.json b/.starters/default/copy-list.json new file mode 100644 index 0000000..91f55a4 --- /dev/null +++ b/.starters/default/copy-list.json @@ -0,0 +1,21 @@ +{ + "repo": "incubrain/foundry", + "ref": "main", + "files": [ + { "src": "scripts/install-skills.sh" }, + { "src": ".agents/rules/architecture.md" }, + { "src": ".agents/rules/conventions.md" }, + { "src": ".agents/rules/decisions.md" }, + { "src": ".agents/rules/anti-patterns.md" }, + { "src": ".claude/settings.json" }, + { "src": ".claude/skills.json" }, + { "src": ".claude/agents/codebase-explorer.md" }, + { "src": ".claude/agents/nuxt-dev.md" }, + { "src": ".claude/agents/signal-reviewer.md" }, + { "src": "skills/docs-writer/SKILL.md" }, + { "src": "skills/docs-writer/references/MDC-SYNTAX.md" }, + { "src": "skills/docs-writer/references/COMPONENTS.md" }, + { "src": ".prettierrc" }, + { "src": "deploy/vercel.website.json", "dest": "deploy/vercel.json" } + ] +} diff --git a/.starters/default/scripts/install-skills.sh b/.starters/default/scripts/install-skills.sh deleted file mode 100755 index 1870c4e..0000000 --- a/.starters/default/scripts/install-skills.sh +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env bash -# Install or update external agent skills from the manifest. -# Run from project root: bash scripts/install-skills.sh [agent] -# Examples: -# bash scripts/install-skills.sh # Default: claude-code -# bash scripts/install-skills.sh cursor # Install for Cursor -set -euo pipefail - -# Accept agent as first argument (default: claude-code) -AGENT="${1:-claude-code}" -AGENT_DIR=".${AGENT%%-code}" # claude-code -> .claude, cursor -> .cursor - -# Log file to track update timestamps -UPDATE_LOG=".agents/skill-updates.log" -TWENTY_FOUR_HOURS=$((24 * 60 * 60)) - -# Create log file if it doesn't exist -mkdir -p "$(dirname "$UPDATE_LOG")" -touch "$UPDATE_LOG" - -# Check if skill was updated in the past 24 hours -is_recently_updated() { - local skill_name="$1" - local current_time=$(date +%s) - - if grep -q "^$skill_name:" "$UPDATE_LOG" 2>/dev/null; then - local last_update=$(grep "^$skill_name:" "$UPDATE_LOG" | tail -1 | cut -d':' -f2) - local time_diff=$((current_time - last_update)) - - if [ "$time_diff" -lt "$TWENTY_FOUR_HOURS" ]; then - local hours_ago=$((time_diff / 3600)) - echo " → Already updated ${hours_ago}h ago, skipping" - return 0 - fi - fi - return 1 -} - -# Log successful update -log_update() { - local skill_name="$1" - local current_time=$(date +%s) - echo "$skill_name:$current_time" >> "$UPDATE_LOG" -} - -# Define all skills as repo:skill pairs -declare -a SKILLS=( - # Development - "https://github.com/nuxt-modules/mcp-toolkit:manage-mcp" - "https://github.com/obra/superpowers:brainstorming" - "https://github.com/nuxt/ui:nuxt-ui" - "https://github.com/antfu/skills:nuxt" - "https://github.com/vueuse/skills:vueuse-functions" - "https://github.com/antfu/skills:vue-best-practices" - "https://github.com/antfu/skills:pinia" - "https://github.com/nuxt-content/nuxt-studio:nuxt-content" - - # Logging & Observability - "https://github.com/HugoRCD/evlog:review-logging-patterns" - "https://github.com/HugoRCD/evlog:create-evlog-adapter" - "https://github.com/HugoRCD/evlog:create-evlog-enricher" - - # Design & UX - "https://github.com/anthropics/skills:frontend-design" - "https://github.com/anthropics/skills:theme-factory" - "https://github.com/vercel-labs/agent-skills:web-design-guidelines" - - # Marketing & Content - "https://github.com/coreyhaines31/marketingskills:copywriting" - "https://github.com/coreyhaines31/marketingskills:marketing-psychology" - - # Testing & Debugging - "https://github.com/vuejs-ai/skills:vue-testing-best-practices" - "https://github.com/obra/superpowers:systematic-debugging" - "https://github.com/antfu/skills:vitest" - "https://github.com/vercel-labs/agent-browser:agent-browser" - - # Meta - "https://github.com/anthropics/skills:skill-creator" -) - -echo "Installing skills for agent: ${AGENT}" -echo "Checking installed skills..." -INSTALLED_SKILLS=$(npx skills list --agent "$AGENT" 2>/dev/null || echo "") - -echo "Processing ${#SKILLS[@]} external skills..." -echo "" - -for skill_entry in "${SKILLS[@]}"; do - # Split on last colon to separate repo URL from skill name - repo="${skill_entry%:*}" - skill_name="${skill_entry##*:}" - - # Check if skill is already installed - if echo "$INSTALLED_SKILLS" | grep -q "$skill_name"; then - # Skip update if done in past 24 hours - if is_recently_updated "$skill_name"; then - continue - fi - - echo "⟳ Updating: $skill_name" - if npx skills update "$skill_name" --agent "$AGENT" --yes 2>/dev/null; then - log_update "$skill_name" - else - echo " → Update failed, reinstalling..." - if npx skills add "$repo" --skill "$skill_name" --agent "$AGENT" --yes; then - log_update "$skill_name" - fi - fi - else - echo "⊕ Installing: $skill_name" - if npx skills add "$repo" --skill "$skill_name" --agent "$AGENT" --yes; then - log_update "$skill_name" - fi - fi -done - -echo "" -echo "Setting up symlinks for ${AGENT}..." - -# 1. Symlink custom skills from root /skills into .agents/skills -if [ -d "skills" ]; then - for skill_dir in skills/*/; do - skill_name=$(basename "$skill_dir") - target_link=".agents/skills/$skill_name" - - if [ -L "$target_link" ]; then - echo " ✓ Custom skill already linked: $skill_name" - elif [ -e "$target_link" ]; then - echo " ⚠ Warning: $target_link exists but is not a symlink" - else - ln -s "../../skills/$skill_name" "$target_link" - echo " → Linked custom skill: $skill_name" - fi - done -fi - -# 2. Create agent directory symlink if needed -mkdir -p "$AGENT_DIR" -agent_skills_link="${AGENT_DIR}/skills" - -if [ -L "$agent_skills_link" ]; then - echo " ✓ Agent symlink already exists: ${agent_skills_link}" -elif [ -e "$agent_skills_link" ]; then - echo " ⚠ Warning: ${agent_skills_link} exists but is not a symlink" - echo " Run 'rm -rf ${agent_skills_link} && ln -s ../.agents/skills ${agent_skills_link}' to fix" -else - ln -s "../.agents/skills" "$agent_skills_link" - echo " → Created agent symlink: ${agent_skills_link} -> .agents/skills" -fi - -echo "" -echo "✓ Done. Skills installed to .agents/skills/" -echo " Custom skills: /skills/* (committed to git)" -echo " External skills: /.agents/skills/* (gitignored)" -echo " Agent access: ${agent_skills_link} (symlink)" diff --git a/cli/cli.ts b/cli/cli.ts index 3cc6224..9b359c2 100644 --- a/cli/cli.ts +++ b/cli/cli.ts @@ -1,5 +1,6 @@ import { resolve } from 'node:path' import { defineCommand, runMain } from 'citty' +import { processCopyList } from './copy-files' import type { CLIOptions } from './types' export function createCLI(opts: CLIOptions) { @@ -37,6 +38,8 @@ export function createCLI(opts: CLIOptions) { '-t', `gh:incubrain/foundry/.starters/${template}`, ]) + + await processCopyList(dir) }, }) diff --git a/cli/copy-files.ts b/cli/copy-files.ts new file mode 100644 index 0000000..051e099 --- /dev/null +++ b/cli/copy-files.ts @@ -0,0 +1,51 @@ +import { readFile, writeFile, mkdir } from 'node:fs/promises' +import { resolve, dirname, join } from 'node:path' +import type { CopyListConfig } from './types' + +const GITHUB_RAW = 'https://raw.githubusercontent.com' + +export async function processCopyList(projectDir: string): Promise { + const configPath = join(projectDir, 'copy-list.json') + + let raw: string + try { + raw = await readFile(configPath, 'utf-8') + } catch { + return + } + + const config: CopyListConfig = JSON.parse(raw) + const repo = config.repo ?? 'incubrain/foundry' + const ref = config.ref ?? 'main' + const { files } = config + + console.log(`\nFetching ${files.length} shared files from ${repo}@${ref}...`) + + const results = await Promise.allSettled( + files.map(async (file) => { + const dest = file.dest ?? file.src + const url = `${GITHUB_RAW}/${repo}/${ref}/${file.src}` + const destPath = resolve(projectDir, dest) + + const response = await fetch(url) + if (!response.ok) { + throw new Error(`${file.src}: ${response.status} ${response.statusText}`) + } + + const content = await response.text() + await mkdir(dirname(destPath), { recursive: true }) + await writeFile(destPath, content, 'utf-8') + console.log(` + ${dest}`) + }), + ) + + const failed = results.filter((r): r is PromiseRejectedResult => r.status === 'rejected') + if (failed.length > 0) { + console.warn(`\n${failed.length} file(s) failed to fetch:`) + for (const f of failed) { + console.warn(` - ${f.reason}`) + } + } + + console.log(`\nDone. ${files.length - failed.length}/${files.length} shared files copied.`) +} diff --git a/cli/types.ts b/cli/types.ts index 2d36975..ebb74fe 100644 --- a/cli/types.ts +++ b/cli/types.ts @@ -5,3 +5,14 @@ export interface CLIOptions { defaults: Record } } + +export interface CopyListFile { + src: string + dest?: string +} + +export interface CopyListConfig { + repo?: string + ref?: string + files: CopyListFile[] +} From 1209db59e1b5ed1095e5bdf0a07df333b412394c Mon Sep 17 00:00:00 2001 From: Mac <82544364+Drew-Macgibbon@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:20:27 +0530 Subject: [PATCH 3/5] chore(astronera): switch to workspace dependency for local layer testing Use workspace:* instead of pinned ^0.5.2 so astronera builds against the local layer, matching foundry's existing setup. Co-Authored-By: Claude Opus 4.6 --- examples/astronera/package.json | 2 +- pnpm-lock.yaml | 162 +------------------------------- 2 files changed, 3 insertions(+), 161 deletions(-) diff --git a/examples/astronera/package.json b/examples/astronera/package.json index 2347831..800e058 100644 --- a/examples/astronera/package.json +++ b/examples/astronera/package.json @@ -11,7 +11,7 @@ "dependencies": { "@iconify-json/lucide": "^1.2.90", "@iconify-json/simple-icons": "^1.2.38", - "@incubrain/foundry": "^0.5.2", + "@incubrain/foundry": "workspace:*", "better-sqlite3": "^12.5.0", "nuxt": "^4.3.0", "nuxt-llms": "^0.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4e7a8d..1804227 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,8 +89,8 @@ importers: specifier: ^1.2.38 version: 1.2.71 '@incubrain/foundry': - specifier: ^0.5.2 - version: 0.5.2(db342f8ba1f305757aec024e04ba9157) + specifier: workspace:* + version: link:../../layer better-sqlite3: specifier: ^12.5.0 version: 12.6.2 @@ -918,9 +918,6 @@ packages: '@iconify/collections@1.0.644': resolution: {integrity: sha512-feUVIH69byoiu8iocNU8II8MQfv+Xd5jA+cj6GN5wsLxkPEt2lpCGLMaK0NautY6itBdYN2pYC1I8sngvHJg2g==} - '@iconify/json@2.2.439': - resolution: {integrity: sha512-DFpo7ZqntngeDaVFtsj+AydUrJoEPW0FIpMrmngo3iHxLXpqcNe5lhztPqvVBoXllbohomcA1YRjRO/ljo7AnQ==} - '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -1085,21 +1082,6 @@ packages: cpu: [x64] os: [win32] - '@incubrain/foundry@0.5.2': - resolution: {integrity: sha512-GilbPBBK4nLt8/ssBgs41FPgnoYMiulRbel7xMlNTsmjWWGBIaX+nATgAVhm6y81ImPGMdiHoWwkcLdma4TWkA==} - peerDependencies: - better-sqlite3: 12.x - nuxt: 4.x - nuxt-llms: ^0.2.0 - nuxt-studio: ^1.2.0 - peerDependenciesMeta: - better-sqlite3: - optional: true - nuxt-llms: - optional: true - nuxt-studio: - optional: true - '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} @@ -1548,15 +1530,6 @@ packages: '@nuxtjs/color-mode@3.5.2': resolution: {integrity: sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA==} - '@nuxtjs/mcp-toolkit@0.6.4': - resolution: {integrity: sha512-0mGQJe5NviNRIaUtCxNhMalh6bsVIJ3XLm894P6b1bCxAvXBnt2rsORkqFfPoZHj04iv90620NI7VQhXf/Z52w==} - peerDependencies: - agents: '>=0.4.0' - zod: ^4.1.13 - peerDependenciesMeta: - agents: - optional: true - '@nuxtjs/mcp-toolkit@0.7.0': resolution: {integrity: sha512-aOgVFqvH9+Jzk2EAn+kGfsOAi4sxwEuxyO9CvhtcTBPPZq8fuxcIk7gBH+/UCL7/5oK13z9kUMWE9eOK5g7JnA==} peerDependencies: @@ -8916,11 +8889,6 @@ snapshots: dependencies: '@iconify/types': 2.0.0 - '@iconify/json@2.2.439': - dependencies: - '@iconify/types': 2.0.0 - pathe: 2.0.3 - '@iconify/types@2.0.0': {} '@iconify/utils@3.1.0': @@ -9031,116 +8999,6 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@incubrain/foundry@0.5.2(db342f8ba1f305757aec024e04ba9157)': - dependencies: - '@iconify/json': 2.2.439 - '@nuxt/content': 3.11.2(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)) - '@nuxt/image': 2.0.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1) - '@nuxt/kit': 4.3.1(magicast@0.5.1) - '@nuxt/scripts': 0.13.2(@unhead/vue@2.1.4(vue@3.5.27(typescript@5.9.3)))(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)) - '@nuxt/ui': 4.4.0(@nuxt/content@3.11.2(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(change-case@5.4.4)(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6) - '@nuxtjs/mcp-toolkit': 0.6.4(magicast@0.5.1)(zod@4.3.6) - '@nuxtjs/mdc': 0.20.1(magicast@0.5.1) - '@nuxtjs/seo': 3.4.0(@unhead/vue@2.1.4(vue@3.5.27(typescript@5.9.3)))(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(rollup@4.57.1)(unhead@2.1.4)(unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))(zod@4.3.6) - '@vueuse/core': 14.2.1(vue@3.5.27(typescript@5.9.3)) - '@vueuse/integrations': 14.2.1(change-case@5.4.4)(fuse.js@7.1.0)(vue@3.5.27(typescript@5.9.3)) - '@vueuse/nuxt': 14.2.1(magicast@0.5.1)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@22.19.7)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@10.0.0(jiti@2.6.1))(ioredis@5.9.2)(lightningcss@1.31.1)(magicast@0.5.1)(meow@13.2.0)(optionator@0.9.4)(rollup@4.57.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3)) - canvas-confetti: 1.9.4 - defu: 6.1.4 - evlog: 1.9.0(@nuxt/kit@4.3.1(magicast@0.5.1))(h3@1.15.5)(nitropack@2.13.1(better-sqlite3@12.6.2))(ofetch@1.5.1) - exsolve: 1.0.8 - fuse.js: 7.1.0 - katex: 0.16.28 - minimark: 0.2.0 - motion-v: 1.10.2(@vueuse/core@14.2.1(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) - nuxt: 4.3.1(@parcel/watcher@2.5.6)(@types/node@22.19.7)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@10.0.0(jiti@2.6.1))(ioredis@5.9.2)(lightningcss@1.31.1)(magicast@0.5.1)(meow@13.2.0)(optionator@0.9.4)(rollup@4.57.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2) - rehype-katex: 7.0.1 - remark-math: 6.0.0 - tailwindcss: 4.1.18 - ufo: 1.6.3 - zod: 4.3.6 - zod-to-json-schema: 3.25.1(zod@4.3.6) - optionalDependencies: - better-sqlite3: 12.6.2 - nuxt-llms: 0.2.0(magicast@0.5.1) - nuxt-studio: 1.3.2(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vue@3.5.27(typescript@5.9.3)) - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@cfworker/json-schema' - - '@deno/kv' - - '@electric-sql/pglite' - - '@emotion/is-prop-valid' - - '@googlemaps/markerclusterer' - - '@inertiajs/vue3' - - '@libsql/client' - - '@netlify/blobs' - - '@paypal/paypal-js' - - '@planetscale/database' - - '@stripe/stripe-js' - - '@tiptap/extensions' - - '@tiptap/y-tiptap' - - '@types/google.maps' - - '@types/vimeo__player' - - '@types/youtube' - - '@unhead/react' - - '@unhead/solid-js' - - '@unhead/svelte' - - '@unhead/vue' - - '@upstash/redis' - - '@valibot/to-json-schema' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - '@vue/composition-api' - - agents - - async-validator - - aws4fetch - - axios - - bufferutil - - change-case - - db0 - - drauu - - drizzle-orm - - embla-carousel - - focus-trap - - h3 - - idb-keyval - - ioredis - - joi - - jwt-decode - - magicast - - mysql2 - - nitro - - nitropack - - nprogress - - ofetch - - qrcode - - react - - react-dom - - rollup - - sortablejs - - sqlite3 - - superstruct - - supports-color - - typescript - - unhead - - universal-cookie - - unstorage - - uploadthing - - utf-8-validate - - valibot - - vite - - vue - - vue-router - - yjs - - yup - '@inquirer/ansi@1.0.2': {} '@inquirer/checkbox@4.3.2(@types/node@22.19.7)': @@ -10176,22 +10034,6 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxtjs/mcp-toolkit@0.6.4(magicast@0.5.1)(zod@4.3.6)': - dependencies: - '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.6) - '@nuxt/kit': 4.3.1(magicast@0.5.1) - defu: 6.1.4 - ms: 2.1.3 - pathe: 2.0.3 - satori: 0.19.2 - scule: 1.3.0 - tinyglobby: 0.2.15 - zod: 4.3.6 - transitivePeerDependencies: - - '@cfworker/json-schema' - - magicast - - supports-color - '@nuxtjs/mcp-toolkit@0.7.0(magicast@0.5.1)(zod@4.3.6)': dependencies: '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.6) From d3ae5732408a6c2ed5ce6ae20f7da5b7575d0b6d Mon Sep 17 00:00:00 2001 From: Mac <82544364+Drew-Macgibbon@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:22:12 +0530 Subject: [PATCH 4/5] fix(layer): resolve all lint errors across layer and cli - webhook.post.ts: remove unused `response` variable assignment - nuxt.config.ts: use comma delimiter in inline type literal - docs-redirect.ts: replace `any` with NavItem interface, fix brace style - copy-files.ts: fix try/catch brace style to match project convention - astronera/package.json: revert to npm ^0.5.2 (workspace:* was for testing) Co-Authored-By: Claude Opus 4.6 --- cli/copy-files.ts | 3 ++- examples/astronera/package.json | 2 +- .../modules/events/server/handlers/webhook.post.ts | 2 +- layer/nuxt.config.ts | 2 +- layer/server/middleware/docs-redirect.ts | 13 ++++++++++--- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/cli/copy-files.ts b/cli/copy-files.ts index 051e099..7577500 100644 --- a/cli/copy-files.ts +++ b/cli/copy-files.ts @@ -10,7 +10,8 @@ export async function processCopyList(projectDir: string): Promise { let raw: string try { raw = await readFile(configPath, 'utf-8') - } catch { + } + catch { return } diff --git a/examples/astronera/package.json b/examples/astronera/package.json index 800e058..2347831 100644 --- a/examples/astronera/package.json +++ b/examples/astronera/package.json @@ -11,7 +11,7 @@ "dependencies": { "@iconify-json/lucide": "^1.2.90", "@iconify-json/simple-icons": "^1.2.38", - "@incubrain/foundry": "workspace:*", + "@incubrain/foundry": "^0.5.2", "better-sqlite3": "^12.5.0", "nuxt": "^4.3.0", "nuxt-llms": "^0.2.0", diff --git a/layer/modules/events/server/handlers/webhook.post.ts b/layer/modules/events/server/handlers/webhook.post.ts index 95890e3..62222fd 100644 --- a/layer/modules/events/server/handlers/webhook.post.ts +++ b/layer/modules/events/server/handlers/webhook.post.ts @@ -174,7 +174,7 @@ export default defineEventHandler(async (event) => { } // Retry webhook delivery with exponential backoff - const response = await retryWithBackoff( + await retryWithBackoff( async () => { const result = await $fetch(url, { method: 'POST', diff --git a/layer/nuxt.config.ts b/layer/nuxt.config.ts index 4ccc985..2676c07 100644 --- a/layer/nuxt.config.ts +++ b/layer/nuxt.config.ts @@ -203,7 +203,7 @@ export default defineNuxtConfig({ hooks: { 'components:extend': ( - components: { pascalName: string; global?: boolean | 'sync' }[], + components: { pascalName: string, global?: boolean | 'sync' }[], ) => { const globals = components.filter((c: { pascalName: string }) => ['UButton', 'UIcon'].includes(c.pascalName), diff --git a/layer/server/middleware/docs-redirect.ts b/layer/server/middleware/docs-redirect.ts index 749515c..e63da3c 100644 --- a/layer/server/middleware/docs-redirect.ts +++ b/layer/server/middleware/docs-redirect.ts @@ -1,6 +1,12 @@ import { defineEventHandler, sendRedirect } from 'h3' import { queryCollectionNavigation } from '@nuxt/content/server' +interface NavItem { + path?: string + body?: unknown + children?: NavItem[] +} + /** * Redirects docs directory routes to the first child page. * @@ -31,7 +37,7 @@ export default defineEventHandler(async (event) => { const navigation = await queryCollectionNavigation(event, 'docs') // Find the matching directory node - const findNode = (items: any[], targetPath: string): any => { + const findNode = (items: NavItem[], targetPath: string): NavItem | null => { for (const item of items) { // Normalize paths for comparison (remove trailing slash) const itemPath = item.path?.replace(/\/$/, '') @@ -53,13 +59,14 @@ export default defineEventHandler(async (event) => { // If we found a node with children but no body (no content file), // redirect to the first child - if (node?.children?.length > 0 && !node.body) { + if (node?.children?.length && node.children.length > 0 && !node.body) { const firstChild = node.children[0] if (firstChild?.path) { return sendRedirect(event, firstChild.path, 302) } } - } catch (error) { + } + catch (error) { // Fail silently - let the normal 404 handling take over console.debug('docs-redirect middleware:', error) } From 1a500f7da3522e07189e5ce70e276acaf56ab740 Mon Sep 17 00:00:00 2001 From: Mac <82544364+Drew-Macgibbon@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:36:46 +0530 Subject: [PATCH 5/5] fix(cli): add input validation to copy-files to resolve CodeQL warnings Validate repo format, ref, and file paths from copy-list.json before using them in network requests or filesystem writes. Prevents path traversal and URL injection from malicious config files. Also syncs pnpm-lock.yaml after astronera dependency revert. Co-Authored-By: Claude Opus 4.6 --- cli/copy-files.ts | 40 +++++++++++- pnpm-lock.yaml | 162 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 198 insertions(+), 4 deletions(-) diff --git a/cli/copy-files.ts b/cli/copy-files.ts index 7577500..c2f9046 100644 --- a/cli/copy-files.ts +++ b/cli/copy-files.ts @@ -1,8 +1,38 @@ import { readFile, writeFile, mkdir } from 'node:fs/promises' -import { resolve, dirname, join } from 'node:path' +import { resolve, dirname, join, relative } from 'node:path' import type { CopyListConfig } from './types' const GITHUB_RAW = 'https://raw.githubusercontent.com' +const REPO_PATTERN = /^[\w.-]+\/[\w.-]+$/ +const REF_PATTERN = /^[\w./-]+$/ +const PATH_PATTERN = /^[\w./-]+$/ + +function validateRepo(repo: string): void { + if (!REPO_PATTERN.test(repo)) { + throw new Error(`Invalid repo format: "${repo}" (expected "owner/name")`) + } +} + +function validateRef(ref: string): void { + if (!REF_PATTERN.test(ref)) { + throw new Error(`Invalid ref format: "${ref}"`) + } +} + +function validateFilePath(filePath: string): void { + if (!PATH_PATTERN.test(filePath) || filePath.includes('..')) { + throw new Error(`Invalid file path: "${filePath}"`) + } +} + +function safeDest(projectDir: string, dest: string): string { + const destPath = resolve(projectDir, dest) + const rel = relative(projectDir, destPath) + if (rel.startsWith('..') || resolve(destPath) !== destPath) { + throw new Error(`Path traversal blocked: "${dest}" resolves outside project`) + } + return destPath +} export async function processCopyList(projectDir: string): Promise { const configPath = join(projectDir, 'copy-list.json') @@ -20,13 +50,19 @@ export async function processCopyList(projectDir: string): Promise { const ref = config.ref ?? 'main' const { files } = config + validateRepo(repo) + validateRef(ref) + console.log(`\nFetching ${files.length} shared files from ${repo}@${ref}...`) const results = await Promise.allSettled( files.map(async (file) => { + validateFilePath(file.src) const dest = file.dest ?? file.src + validateFilePath(dest) + const url = `${GITHUB_RAW}/${repo}/${ref}/${file.src}` - const destPath = resolve(projectDir, dest) + const destPath = safeDest(projectDir, dest) const response = await fetch(url) if (!response.ok) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1804227..39d5691 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,8 +89,8 @@ importers: specifier: ^1.2.38 version: 1.2.71 '@incubrain/foundry': - specifier: workspace:* - version: link:../../layer + specifier: ^0.5.2 + version: 0.5.2(3dfe9450781ed8c614690e199d74fd7a) better-sqlite3: specifier: ^12.5.0 version: 12.6.2 @@ -918,6 +918,9 @@ packages: '@iconify/collections@1.0.644': resolution: {integrity: sha512-feUVIH69byoiu8iocNU8II8MQfv+Xd5jA+cj6GN5wsLxkPEt2lpCGLMaK0NautY6itBdYN2pYC1I8sngvHJg2g==} + '@iconify/json@2.2.443': + resolution: {integrity: sha512-xyDqw1FeuNWPhYj+Sp2I1kyD6J5U5s8GxEW+dgIY1/Vl0b65t+PlRVCxEBtx73CanfDUPrSEHbxKQJwzXrcV/w==} + '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -1082,6 +1085,21 @@ packages: cpu: [x64] os: [win32] + '@incubrain/foundry@0.5.2': + resolution: {integrity: sha512-GilbPBBK4nLt8/ssBgs41FPgnoYMiulRbel7xMlNTsmjWWGBIaX+nATgAVhm6y81ImPGMdiHoWwkcLdma4TWkA==} + peerDependencies: + better-sqlite3: 12.x + nuxt: 4.x + nuxt-llms: ^0.2.0 + nuxt-studio: ^1.2.0 + peerDependenciesMeta: + better-sqlite3: + optional: true + nuxt-llms: + optional: true + nuxt-studio: + optional: true + '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} @@ -1530,6 +1548,15 @@ packages: '@nuxtjs/color-mode@3.5.2': resolution: {integrity: sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA==} + '@nuxtjs/mcp-toolkit@0.6.4': + resolution: {integrity: sha512-0mGQJe5NviNRIaUtCxNhMalh6bsVIJ3XLm894P6b1bCxAvXBnt2rsORkqFfPoZHj04iv90620NI7VQhXf/Z52w==} + peerDependencies: + agents: '>=0.4.0' + zod: ^4.1.13 + peerDependenciesMeta: + agents: + optional: true + '@nuxtjs/mcp-toolkit@0.7.0': resolution: {integrity: sha512-aOgVFqvH9+Jzk2EAn+kGfsOAi4sxwEuxyO9CvhtcTBPPZq8fuxcIk7gBH+/UCL7/5oK13z9kUMWE9eOK5g7JnA==} peerDependencies: @@ -8889,6 +8916,11 @@ snapshots: dependencies: '@iconify/types': 2.0.0 + '@iconify/json@2.2.443': + dependencies: + '@iconify/types': 2.0.0 + pathe: 2.0.3 + '@iconify/types@2.0.0': {} '@iconify/utils@3.1.0': @@ -8999,6 +9031,116 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@incubrain/foundry@0.5.2(3dfe9450781ed8c614690e199d74fd7a)': + dependencies: + '@iconify/json': 2.2.443 + '@nuxt/content': 3.11.2(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)) + '@nuxt/image': 2.0.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1) + '@nuxt/kit': 4.3.1(magicast@0.5.1) + '@nuxt/scripts': 0.13.2(@unhead/vue@2.1.4(vue@3.5.27(typescript@5.9.3)))(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)) + '@nuxt/ui': 4.4.0(@nuxt/content@3.11.2(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(change-case@5.4.4)(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6) + '@nuxtjs/mcp-toolkit': 0.6.4(magicast@0.5.1)(zod@4.3.6) + '@nuxtjs/mdc': 0.20.1(magicast@0.5.1) + '@nuxtjs/seo': 3.4.0(@unhead/vue@2.1.4(vue@3.5.27(typescript@5.9.3)))(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(rollup@4.57.1)(unhead@2.1.4)(unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))(zod@4.3.6) + '@vueuse/core': 14.2.1(vue@3.5.27(typescript@5.9.3)) + '@vueuse/integrations': 14.2.1(change-case@5.4.4)(fuse.js@7.1.0)(vue@3.5.27(typescript@5.9.3)) + '@vueuse/nuxt': 14.2.1(magicast@0.5.1)(nuxt@4.3.1(@parcel/watcher@2.5.6)(@types/node@22.19.7)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@10.0.0(jiti@2.6.1))(ioredis@5.9.2)(lightningcss@1.31.1)(magicast@0.5.1)(meow@13.2.0)(optionator@0.9.4)(rollup@4.57.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3)) + canvas-confetti: 1.9.4 + defu: 6.1.4 + evlog: 1.9.0(@nuxt/kit@4.3.1(magicast@0.5.1))(h3@1.15.5)(nitropack@2.13.1(better-sqlite3@12.6.2))(ofetch@1.5.1) + exsolve: 1.0.8 + fuse.js: 7.1.0 + katex: 0.16.28 + minimark: 0.2.0 + motion-v: 1.10.2(@vueuse/core@14.2.1(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) + nuxt: 4.3.1(@parcel/watcher@2.5.6)(@types/node@22.19.7)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@10.0.0(jiti@2.6.1))(ioredis@5.9.2)(lightningcss@1.31.1)(magicast@0.5.1)(meow@13.2.0)(optionator@0.9.4)(rollup@4.57.1)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2) + rehype-katex: 7.0.1 + remark-math: 6.0.0 + tailwindcss: 4.1.18 + ufo: 1.6.3 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + optionalDependencies: + better-sqlite3: 12.6.2 + nuxt-llms: 0.2.0(magicast@0.5.1) + nuxt-studio: 1.3.2(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vue@3.5.27(typescript@5.9.3)) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@cfworker/json-schema' + - '@deno/kv' + - '@electric-sql/pglite' + - '@emotion/is-prop-valid' + - '@googlemaps/markerclusterer' + - '@inertiajs/vue3' + - '@libsql/client' + - '@netlify/blobs' + - '@paypal/paypal-js' + - '@planetscale/database' + - '@stripe/stripe-js' + - '@tiptap/extensions' + - '@tiptap/y-tiptap' + - '@types/google.maps' + - '@types/vimeo__player' + - '@types/youtube' + - '@unhead/react' + - '@unhead/solid-js' + - '@unhead/svelte' + - '@unhead/vue' + - '@upstash/redis' + - '@valibot/to-json-schema' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - '@vue/composition-api' + - agents + - async-validator + - aws4fetch + - axios + - bufferutil + - change-case + - db0 + - drauu + - drizzle-orm + - embla-carousel + - focus-trap + - h3 + - idb-keyval + - ioredis + - joi + - jwt-decode + - magicast + - mysql2 + - nitro + - nitropack + - nprogress + - ofetch + - qrcode + - react + - react-dom + - rollup + - sortablejs + - sqlite3 + - superstruct + - supports-color + - typescript + - unhead + - universal-cookie + - unstorage + - uploadthing + - utf-8-validate + - valibot + - vite + - vue + - vue-router + - yjs + - yup + '@inquirer/ansi@1.0.2': {} '@inquirer/checkbox@4.3.2(@types/node@22.19.7)': @@ -10034,6 +10176,22 @@ snapshots: transitivePeerDependencies: - magicast + '@nuxtjs/mcp-toolkit@0.6.4(magicast@0.5.1)(zod@4.3.6)': + dependencies: + '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.6) + '@nuxt/kit': 4.3.1(magicast@0.5.1) + defu: 6.1.4 + ms: 2.1.3 + pathe: 2.0.3 + satori: 0.19.2 + scule: 1.3.0 + tinyglobby: 0.2.15 + zod: 4.3.6 + transitivePeerDependencies: + - '@cfworker/json-schema' + - magicast + - supports-color + '@nuxtjs/mcp-toolkit@0.7.0(magicast@0.5.1)(zod@4.3.6)': dependencies: '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.6)