agent-arche
Advanced tools
| #!/usr/bin/env node | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const os = require('os'); | ||
| const crypto = require('crypto'); | ||
| const EXCLUDED_DIRS = new Set([ | ||
| '.git', '.next', '.svelte-kit', '.turbo', '.venv', 'build', 'coverage', 'dist', | ||
| 'node_modules', 'target', 'vendor', | ||
| ]); | ||
| const COLD_MEMORY_DIRS = new Set(['archive', 'reviews', 'sessions', 'templates']); | ||
| const SOURCE_EXTENSIONS = new Set(['.c', '.cc', '.cpp', '.cs', '.css', '.go', '.html', '.java', '.js', '.jsx', '.kt', '.php', '.py', '.rb', '.rs', '.scss', '.svelte', '.swift', '.ts', '.tsx', '.vue']); | ||
| const STOP_WORDS = new Set(['about', 'after', 'again', 'also', 'and', 'are', 'but', 'can', 'change', 'code', 'does', 'for', 'from', 'have', 'how', 'into', 'its', 'need', 'project', 'should', 'that', 'the', 'this', 'use', 'using', 'want', 'what', 'when', 'where', 'which', 'with']); | ||
| function parseArgs(argv) { | ||
| const parsed = { command: argv[0] || 'query', task: '', paths: '', budget: 1200, json: false, refresh: false }; | ||
| for (let i = 1; i < argv.length; i += 1) { | ||
| const arg = argv[i]; | ||
| if (arg === '--json') parsed.json = true; | ||
| else if (arg === '--refresh') parsed.refresh = true; | ||
| else if (arg === '--task') parsed.task = argv[++i] || ''; | ||
| else if (arg.startsWith('--task=')) parsed.task = arg.slice(7); | ||
| else if (arg === '--paths') parsed.paths = argv[++i] || ''; | ||
| else if (arg.startsWith('--paths=')) parsed.paths = arg.slice(8); | ||
| else if (arg === '--budget') parsed.budget = Number(argv[++i]) || parsed.budget; | ||
| else if (arg.startsWith('--budget=')) parsed.budget = Number(arg.slice(9)) || parsed.budget; | ||
| } | ||
| parsed.budget = Math.max(200, Math.min(parsed.budget, 8000)); | ||
| return parsed; | ||
| } | ||
| function findRoot(start) { | ||
| let current = path.resolve(start); | ||
| while (true) { | ||
| if (fs.existsSync(path.join(current, '.git')) || fs.existsSync(path.join(current, '.codex'))) return current; | ||
| const parent = path.dirname(current); | ||
| if (parent === current) return path.resolve(start); | ||
| current = parent; | ||
| } | ||
| } | ||
| function walk(dir, predicate, output = []) { | ||
| if (!fs.existsSync(dir)) return output; | ||
| for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { | ||
| if (entry.isSymbolicLink()) continue; | ||
| const full = path.join(dir, entry.name); | ||
| if (entry.isDirectory()) { | ||
| if (!EXCLUDED_DIRS.has(entry.name) && !predicate.skipDir?.(entry.name, full)) walk(full, predicate, output); | ||
| } else if (predicate.file(full)) { | ||
| output.push(full); | ||
| } | ||
| } | ||
| return output; | ||
| } | ||
| function relative(root, file) { | ||
| return path.relative(root, file).replace(/\\/g, '/'); | ||
| } | ||
| function readText(file, limit = 40000) { | ||
| try { | ||
| const buffer = fs.readFileSync(file); | ||
| if (buffer.includes(0)) return ''; | ||
| return buffer.toString('utf8', 0, Math.min(buffer.length, limit)); | ||
| } catch { | ||
| return ''; | ||
| } | ||
| } | ||
| function scalar(frontmatter, key) { | ||
| const match = frontmatter.match(new RegExp(`^${key}:\\s*["']?([^\\n"']+)["']?\\s*$`, 'mi')); | ||
| return match ? match[1].trim() : ''; | ||
| } | ||
| function markdownEntry(root, file) { | ||
| const text = readText(file); | ||
| if (!text) return null; | ||
| const frontmatter = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n/)?.[1] || ''; | ||
| const heading = text.match(/^#\s+(.+)$/m)?.[1]?.trim() || path.basename(file, path.extname(file)); | ||
| const rel = relative(root, file); | ||
| const kind = scalar(frontmatter, 'kind') || scalar(frontmatter, 'type') || (rel === 'memory/manifest.md' ? 'manifest' : 'document'); | ||
| const summary = scalar(frontmatter, 'summary'); | ||
| return { | ||
| id: scalar(frontmatter, 'id') || rel, | ||
| kind, | ||
| file: rel, | ||
| line: 1, | ||
| title: scalar(frontmatter, 'title') || heading, | ||
| summary, | ||
| searchText: `${frontmatter}\n${heading}\n${summary}\n${text.slice(0, 12000)}`, | ||
| }; | ||
| } | ||
| function collectMarkdownFiles(root) { | ||
| const files = []; | ||
| for (const name of ['AGENTS.md', 'CONTEXT.md', 'CONTEXT-MAP.md']) { | ||
| const file = path.join(root, name); | ||
| if (fs.existsSync(file)) files.push(file); | ||
| } | ||
| files.push(...walk(path.join(root, 'docs', 'agents'), { file: (file) => file.endsWith('.md') })); | ||
| const memory = path.join(root, 'memory'); | ||
| files.push(...walk(memory, { | ||
| skipDir: (name) => COLD_MEMORY_DIRS.has(name), | ||
| file: (file) => file.endsWith('.md') && !['_MOC.md', 'README.md'].includes(path.basename(file)), | ||
| })); | ||
| return [...new Set(files)]; | ||
| } | ||
| function collectMarkdown(root) { | ||
| return collectMarkdownFiles(root).map((file) => markdownEntry(root, file)).filter(Boolean); | ||
| } | ||
| function collectBreadcrumbs(root) { | ||
| const entries = []; | ||
| const files = walk(root, { | ||
| skipDir: (name, full) => name === 'memory' || full.includes(`${path.sep}.codex${path.sep}context${path.sep}cache`), | ||
| file: (file) => SOURCE_EXTENSIONS.has(path.extname(file).toLowerCase()), | ||
| }); | ||
| for (const file of files) { | ||
| const lines = readText(file, 250000).split(/\r?\n/); | ||
| for (let index = 0; index < lines.length; index += 1) { | ||
| const marker = lines[index].match(/\barche:\s*(.+)$/i); | ||
| if (!marker) continue; | ||
| const rel = relative(root, file); | ||
| entries.push({ | ||
| id: `${rel}:${index + 1}`, | ||
| kind: 'breadcrumb', | ||
| file: rel, | ||
| line: index + 1, | ||
| title: `Breadcrumb in ${path.basename(file)}`, | ||
| summary: marker[1].trim(), | ||
| searchText: `${rel} ${marker[1]}`, | ||
| }); | ||
| } | ||
| } | ||
| return entries; | ||
| } | ||
| function indexPath(root) { | ||
| const key = crypto.createHash('sha1').update(path.resolve(root)).digest('hex'); | ||
| return path.join(os.tmpdir(), 'agent-arche-context', `${key}.json`); | ||
| } | ||
| function buildIndex(root) { | ||
| const entries = [...collectMarkdown(root), ...collectBreadcrumbs(root)]; | ||
| const documentFiles = collectMarkdownFiles(root).map((file) => relative(root, file)); | ||
| const index = { version: 1, generatedAt: new Date().toISOString(), documentFiles, entries }; | ||
| const target = indexPath(root); | ||
| fs.mkdirSync(path.dirname(target), { recursive: true }); | ||
| fs.writeFileSync(target, `${JSON.stringify(index, null, 2)}\n`); | ||
| return index; | ||
| } | ||
| function loadIndex(root, refresh) { | ||
| const target = indexPath(root); | ||
| if (refresh || !fs.existsSync(target)) return buildIndex(root); | ||
| try { | ||
| const index = JSON.parse(fs.readFileSync(target, 'utf8')); | ||
| if (index.version !== 1 || !Array.isArray(index.entries) || !Array.isArray(index.documentFiles)) return buildIndex(root); | ||
| const currentFiles = collectMarkdownFiles(root); | ||
| const generatedAt = Date.parse(index.generatedAt) || 0; | ||
| const changed = currentFiles.length !== index.documentFiles.length | ||
| || currentFiles.some((file) => fs.statSync(file).mtimeMs > generatedAt); | ||
| return changed ? buildIndex(root) : index; | ||
| } catch { | ||
| return buildIndex(root); | ||
| } | ||
| } | ||
| function terms(value) { | ||
| return [...new Set(value.toLowerCase().match(/[a-z0-9_.\/-]{2,}/g) || [])] | ||
| .filter((term) => !STOP_WORDS.has(term)); | ||
| } | ||
| function rank(entry, task, pathHints) { | ||
| const queryTerms = terms(`${task} ${pathHints}`); | ||
| const text = `${entry.title} ${entry.summary} ${entry.searchText}`.toLowerCase(); | ||
| const file = entry.file.toLowerCase(); | ||
| let score = entry.kind === 'breadcrumb' ? 2 : 0; | ||
| if (task.length > 5 && text.includes(task.toLowerCase())) score += 20; | ||
| for (const term of queryTerms) { | ||
| if (file.includes(term)) score += 8; | ||
| if (entry.title.toLowerCase().includes(term)) score += 5; | ||
| if (text.includes(term)) score += 2; | ||
| } | ||
| for (const hint of pathHints.split(',').map((item) => item.trim().toLowerCase()).filter(Boolean)) { | ||
| if (file.includes(hint) || hint.includes(file)) score += 15; | ||
| } | ||
| if (entry.file.startsWith('memory/handoff/')) score += 2; | ||
| return score; | ||
| } | ||
| function snippet(root, entry, queryTerms) { | ||
| if (entry.kind === 'breadcrumb') return entry.summary; | ||
| const content = readText(path.join(root, entry.file), 50000).replace(/^---[\s\S]*?---\s*/, '').trim(); | ||
| if (!content) return entry.summary; | ||
| const lower = content.toLowerCase(); | ||
| const positions = queryTerms.map((term) => lower.indexOf(term)).filter((position) => position >= 0); | ||
| const start = positions.length ? Math.max(0, Math.min(...positions) - 180) : 0; | ||
| const excerpt = content.slice(start, start + 900).trim(); | ||
| return `${start > 0 ? '…' : ''}${excerpt}${start + 900 < content.length ? '…' : ''}`; | ||
| } | ||
| function query(root, index, options) { | ||
| const queryTerms = terms(`${options.task} ${options.paths}`); | ||
| const ranked = index.entries | ||
| .map((entry) => ({ entry, score: rank(entry, options.task, options.paths) })) | ||
| .filter((item) => item.score > 0) | ||
| .sort((a, b) => b.score - a.score || a.entry.file.localeCompare(b.entry.file)); | ||
| const selected = []; | ||
| let remaining = options.budget * 4; | ||
| for (const item of ranked) { | ||
| if (selected.length >= 6 || remaining < 180) break; | ||
| const excerpt = snippet(root, item.entry, queryTerms).slice(0, Math.min(1000, remaining)); | ||
| const cost = excerpt.length + item.entry.file.length + 80; | ||
| if (cost > remaining && selected.length > 0) continue; | ||
| selected.push({ ...item, excerpt }); | ||
| remaining -= cost; | ||
| } | ||
| return selected; | ||
| } | ||
| function printPack(selected, options) { | ||
| if (options.json) { | ||
| process.stdout.write(`${JSON.stringify({ task: options.task, results: selected }, null, 2)}\n`); | ||
| return; | ||
| } | ||
| if (selected.length === 0) { | ||
| process.stdout.write('No indexed project context matched. Read only task-relevant files.\n'); | ||
| return; | ||
| } | ||
| const lines = ['# Task context pack', '']; | ||
| for (const item of selected) { | ||
| lines.push(`## ${item.entry.file}:${item.entry.line} (${item.entry.kind}, score ${item.score})`); | ||
| lines.push(item.excerpt, ''); | ||
| } | ||
| process.stdout.write(`${lines.join('\n').trim()}\n`); | ||
| } | ||
| function check(index) { | ||
| const issues = []; | ||
| const ids = new Set(); | ||
| for (const entry of index.entries) { | ||
| if (ids.has(entry.id)) issues.push(`duplicate id: ${entry.id}`); | ||
| ids.add(entry.id); | ||
| if (entry.file.startsWith('memory/cards/') && (!entry.summary || entry.kind === 'document')) { | ||
| issues.push(`${entry.file}: cards need kind and summary frontmatter`); | ||
| } | ||
| } | ||
| if (issues.length) { | ||
| process.stderr.write(`${issues.join('\n')}\n`); | ||
| process.exitCode = 1; | ||
| } else { | ||
| process.stdout.write(`Context index valid: ${index.entries.length} entries.\n`); | ||
| } | ||
| } | ||
| const options = parseArgs(process.argv.slice(2)); | ||
| const root = findRoot(process.cwd()); | ||
| const index = loadIndex(root, options.refresh || options.command === 'index'); | ||
| if (options.command === 'index') { | ||
| process.stdout.write(`Indexed ${index.entries.length} project context entries.\n`); | ||
| } else if (options.command === 'check') { | ||
| check(index); | ||
| } else if (options.command === 'harvest') { | ||
| const markers = index.entries.filter((entry) => entry.kind === 'breadcrumb'); | ||
| printPack(markers.map((entry) => ({ entry, score: 1, excerpt: entry.summary })), { ...options, json: options.json }); | ||
| } else if (options.command === 'query') { | ||
| printPack(query(root, index, options), options); | ||
| } else { | ||
| process.stderr.write('Usage: context.cjs index|query|check|harvest [--task text] [--paths a,b] [--budget tokens] [--refresh] [--json]\n'); | ||
| process.exitCode = 1; | ||
| } |
| # Context compiler | ||
| This deterministic helper keeps broad project history out of the model context. | ||
| ```bash | ||
| node .codex/context/context.cjs index | ||
| node .codex/context/context.cjs query --task "fix session expiry" --paths "src/auth/session.ts" | ||
| node .codex/context/context.cjs check | ||
| node .codex/context/context.cjs harvest | ||
| ``` | ||
| `query` returns at most six ranked snippets within a configurable token estimate. It searches durable memory and project instruction documents. Raw session history, reviews, archives, templates, `_MOC.md`, dependencies, and build output stay cold by default. | ||
| The generated index is stored in the OS temporary directory, so retrieval does not dirty the repository or require writes inside `.codex/`. Markdown changes refresh automatically; use `index --refresh` after changing a source breadcrumb. | ||
| Use rare `arche:` source comments only for non-obvious deliberate constraints: | ||
| ```ts | ||
| // arche: limit=global-lock; revisit=measured-contention; memory=DEC-014 | ||
| ``` | ||
| Run `index --refresh` after adding or changing a breadcrumb. |
| import * as readline from "node:readline"; | ||
| import { Writable } from "node:stream"; | ||
| import { stripVTControlCharacters } from "node:util"; | ||
| import pc from "picocolors"; | ||
| export const skillSelectorCancel = Symbol("skill-selector-cancel"); | ||
| const silentOutput = new Writable({ | ||
| write(_chunk, _encoding, callback) { | ||
| callback(); | ||
| }, | ||
| }); | ||
| function displayWidth(value) { | ||
| let width = 0; | ||
| for (const character of stripVTControlCharacters(value)) { | ||
| const code = character.codePointAt(0) ?? 0; | ||
| const wide = (code >= 0x1100 && code <= 0x115f) | ||
| || (code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) | ||
| || (code >= 0xac00 && code <= 0xd7a3) | ||
| || (code >= 0xf900 && code <= 0xfaff) | ||
| || (code >= 0xfe10 && code <= 0xfe6f) | ||
| || (code >= 0xff00 && code <= 0xff60) | ||
| || (code >= 0xffe0 && code <= 0xffe6) | ||
| || (code >= 0x1f000 && code <= 0x1f9ff); | ||
| width += wide ? 2 : 1; | ||
| } | ||
| return width; | ||
| } | ||
| function countVisualRows(lines) { | ||
| const columns = Math.max(1, process.stdout.columns || 80); | ||
| return lines.reduce((total, line) => total + Math.max(1, Math.ceil(displayWidth(line) / columns)), 0); | ||
| } | ||
| function truncate(value, width) { | ||
| if (width <= 0) | ||
| return ""; | ||
| if (displayWidth(value) <= width) | ||
| return value; | ||
| let result = ""; | ||
| for (const character of value) { | ||
| if (displayWidth(`${result}${character}…`) > width) | ||
| break; | ||
| result += character; | ||
| } | ||
| return `${result}…`; | ||
| } | ||
| function wrap(value, width, lineCount) { | ||
| const words = value.trim().split(/\s+/).filter(Boolean); | ||
| const lines = []; | ||
| let current = ""; | ||
| for (const word of words) { | ||
| const candidate = current ? `${current} ${word}` : word; | ||
| if (displayWidth(candidate) <= width) { | ||
| current = candidate; | ||
| continue; | ||
| } | ||
| if (current) | ||
| lines.push(current); | ||
| current = truncate(word, width); | ||
| if (lines.length === lineCount) | ||
| break; | ||
| } | ||
| if (current && lines.length < lineCount) | ||
| lines.push(current); | ||
| while (lines.length < lineCount) | ||
| lines.push(""); | ||
| return lines.slice(0, lineCount); | ||
| } | ||
| export function buildSkillEntries(items, collapsedGroups = new Set()) { | ||
| const groups = new Map(); | ||
| for (const item of items) { | ||
| const groupItems = groups.get(item.group) ?? []; | ||
| groupItems.push(item); | ||
| groups.set(item.group, groupItems); | ||
| } | ||
| const entries = []; | ||
| for (const [group, groupItems] of groups) { | ||
| const collapsed = collapsedGroups.has(group); | ||
| entries.push({ type: "group", group, items: groupItems, collapsed }); | ||
| if (!collapsed) | ||
| entries.push(...groupItems.map((item) => ({ type: "item", item }))); | ||
| } | ||
| return entries; | ||
| } | ||
| function toggleEntry(selected, entry) { | ||
| if (!entry) | ||
| return; | ||
| const items = entry.type === "group" ? entry.items : [entry.item]; | ||
| const allSelected = items.every((item) => selected.has(item.value)); | ||
| for (const item of items) { | ||
| if (allSelected) | ||
| selected.delete(item.value); | ||
| else | ||
| selected.add(item.value); | ||
| } | ||
| } | ||
| export async function skillMultiselect({ message, items, initialSelected, maxVisible = 12, required = false, }) { | ||
| return new Promise((resolve) => { | ||
| const terminalRows = process.stdout.rows || 24; | ||
| const viewportSize = Math.max(3, Math.min(maxVisible, terminalRows - 9)); | ||
| const selected = new Set(initialSelected); | ||
| const collapsedGroups = new Set(); | ||
| const rl = readline.createInterface({ input: process.stdin, output: silentOutput, terminal: false }); | ||
| let cursor = 0; | ||
| let lastRenderHeight = 0; | ||
| if (process.stdin.isTTY) | ||
| process.stdin.setRawMode(true); | ||
| readline.emitKeypressEvents(process.stdin, rl); | ||
| const render = (state = "active") => { | ||
| const lines = []; | ||
| const entries = buildSkillEntries(items, collapsedGroups); | ||
| const icon = state === "active" ? pc.green("◆") : state === "submit" ? pc.green("◇") : pc.red("■"); | ||
| lines.push(`${icon} ${pc.bold(truncate(message, Math.max(20, (process.stdout.columns || 80) - 5)))}`); | ||
| if (state === "active") { | ||
| const visibleStart = Math.max(0, Math.min(cursor - Math.floor(viewportSize / 2), Math.max(0, entries.length - viewportSize))); | ||
| const visibleEntries = entries.slice(visibleStart, visibleStart + viewportSize); | ||
| const columns = process.stdout.columns || 80; | ||
| lines.push(pc.dim("│")); | ||
| for (let index = 0; index < viewportSize; index += 1) { | ||
| const entry = visibleEntries[index]; | ||
| if (!entry) { | ||
| lines.push(pc.dim("│")); | ||
| continue; | ||
| } | ||
| const actualIndex = visibleStart + index; | ||
| const active = actualIndex === cursor; | ||
| const pointer = active ? pc.cyan("❯") : " "; | ||
| if (entry.type === "group") { | ||
| const selectedCount = entry.items.filter((item) => selected.has(item.value)).length; | ||
| const mark = selectedCount === entry.items.length | ||
| ? pc.green("●") | ||
| : selectedCount > 0 | ||
| ? pc.yellow("◐") | ||
| : pc.dim("○"); | ||
| const disclosure = pc.dim(entry.collapsed ? "▸" : "▾"); | ||
| const label = active ? pc.underline(pc.bold(entry.group)) : pc.bold(entry.group); | ||
| lines.push(`${pc.dim("│")} ${pointer} ${disclosure} ${mark} ${label}`); | ||
| continue; | ||
| } | ||
| const groupItems = items.filter((item) => item.group === entry.item.group); | ||
| const tree = groupItems.at(-1) === entry.item ? "└─" : "├─"; | ||
| const mark = selected.has(entry.item.value) ? pc.green("●") : pc.dim("○"); | ||
| const label = active ? pc.underline(entry.item.label) : entry.item.label; | ||
| const hintWidth = Math.max(0, columns - displayWidth(entry.item.label) - 16); | ||
| const hint = hintWidth > 4 ? pc.dim(` (${truncate(entry.item.hint, hintWidth)})`) : ""; | ||
| lines.push(`${pc.dim("│")} ${pointer} ${pc.dim(tree)} ${mark} ${label}${hint}`); | ||
| } | ||
| const hiddenBefore = visibleStart; | ||
| const hiddenAfter = Math.max(0, entries.length - visibleStart - visibleEntries.length); | ||
| const position = [ | ||
| hiddenBefore > 0 ? `↑ ${hiddenBefore} more` : "", | ||
| hiddenAfter > 0 ? `↓ ${hiddenAfter} more` : "", | ||
| ].filter(Boolean).join(" "); | ||
| lines.push(`${pc.dim("│")} ${pc.dim(position)}`); | ||
| const activeEntry = entries[cursor]; | ||
| const detail = activeEntry?.type === "group" | ||
| ? `Toggle all ${activeEntry.items.length} skills in ${activeEntry.group}.` | ||
| : activeEntry | ||
| ? `${activeEntry.item.label}: ${activeEntry.item.hint}.` | ||
| : ""; | ||
| lines.push(pc.dim("│")); | ||
| lines.push(`${pc.dim("│")} ${pc.dim("Description")}`); | ||
| for (const detailLine of wrap(detail, Math.max(20, columns - 7), 2)) { | ||
| lines.push(`${pc.dim("│")} ${pc.dim(detailLine)}`); | ||
| } | ||
| lines.push(pc.dim("│")); | ||
| lines.push(`${pc.dim("│")} ${pc.dim("↑↓ move, ←→ collapse/expand, space select, enter confirm, esc cancel")}`); | ||
| lines.push(pc.dim("└")); | ||
| } | ||
| else if (state === "submit") { | ||
| const labels = items.filter((item) => selected.has(item.value)).map((item) => item.label); | ||
| const summary = labels.length <= 3 | ||
| ? labels.join(", ") | ||
| : `${labels.slice(0, 3).join(", ")} +${labels.length - 3} more`; | ||
| lines.push(`${pc.dim("│")} ${pc.dim(summary)}`); | ||
| } | ||
| else { | ||
| lines.push(`${pc.dim("│")} ${pc.strikethrough(pc.dim("Cancelled"))}`); | ||
| } | ||
| const clear = lastRenderHeight > 0 ? `\x1b[${lastRenderHeight}A\x1b[J` : ""; | ||
| process.stdout.write(`${clear}${lines.join("\n")}\n`); | ||
| lastRenderHeight = countVisualRows(lines); | ||
| }; | ||
| const cleanup = () => { | ||
| process.stdin.removeListener("keypress", onKeypress); | ||
| process.stdout.removeListener("resize", onResize); | ||
| if (process.stdin.isTTY) | ||
| process.stdin.setRawMode(false); | ||
| rl.close(); | ||
| }; | ||
| const finish = (state) => { | ||
| render(state); | ||
| cleanup(); | ||
| resolve(state === "submit" ? [...selected] : skillSelectorCancel); | ||
| }; | ||
| const onResize = () => render(); | ||
| const onKeypress = (_value, key) => { | ||
| const entries = buildSkillEntries(items, collapsedGroups); | ||
| if (key.name === "return") { | ||
| if (!required || selected.size > 0) | ||
| finish("submit"); | ||
| return; | ||
| } | ||
| if (key.name === "escape" || (key.ctrl && key.name === "c")) { | ||
| finish("cancel"); | ||
| return; | ||
| } | ||
| if (key.name === "up") | ||
| cursor = Math.max(0, cursor - 1); | ||
| else if (key.name === "down") | ||
| cursor = Math.min(entries.length - 1, cursor + 1); | ||
| else if (key.name === "space") | ||
| toggleEntry(selected, entries[cursor]); | ||
| else if (key.name === "right") { | ||
| const entry = entries[cursor]; | ||
| if (entry?.type === "group" && entry.collapsed) | ||
| collapsedGroups.delete(entry.group); | ||
| } | ||
| else if (key.name === "left") { | ||
| const entry = entries[cursor]; | ||
| const group = entry?.type === "group" ? entry.group : entry?.item.group; | ||
| if (group) { | ||
| collapsedGroups.add(group); | ||
| cursor = buildSkillEntries(items, collapsedGroups).findIndex((candidate) => candidate.type === "group" && candidate.group === group); | ||
| } | ||
| } | ||
| else | ||
| return; | ||
| render(); | ||
| }; | ||
| process.stdin.on("keypress", onKeypress); | ||
| process.stdout.on("resize", onResize); | ||
| render(); | ||
| }); | ||
| } |
| # Architecture | ||
| Agent Arche separates deterministic context preparation from model judgment. | ||
| ```mermaid | ||
| flowchart TB | ||
| CLI[Installer] --> META[.codex/agent-arche.json] | ||
| CLI --> SKILLS[Selected skills only] | ||
| CLI --> HOOKS[Optional lean hooks] | ||
| CLI --> MEMORY[Optional durable memory] | ||
| CLI --> AGENTS[Optional custom agents] | ||
| REQUEST[User request] --> SESSION[Primary Codex thread] | ||
| HOOKS --> SESSION | ||
| SESSION --> COMPILER[Deterministic context compiler] | ||
| MEMORY --> COMPILER | ||
| COMPILER --> PACK[Budgeted context pack] | ||
| PACK --> SESSION | ||
| SKILLS --> SESSION | ||
| SESSION -->|full scope and divisible task| AGENTS | ||
| AGENTS --> RESULTS[Bounded evidence summaries] | ||
| RESULTS --> SESSION | ||
| ``` | ||
| ## Design boundaries | ||
| - Installation is selective. Uninstalled skill descriptions cannot consume routing context. | ||
| - SessionStart remains fixed-size and does not preload skill bodies. | ||
| - Context filtering, ranking, aggregation, and breadcrumb harvesting happen in Node before model input. | ||
| - Model judgment handles interpretation, implementation, review, and final synthesis. | ||
| - Memory history grows on disk without growing startup context. | ||
| - Multi-agent work is opt-in, capped, and used only for independent or context-noisy work. | ||
| ## Scope composition | ||
| ```text | ||
| skills | ||
| selected skills + canonical metadata | ||
| skills-hooks | ||
| skills + config + SessionStart + PreToolUse + context compiler | ||
| skills-memory | ||
| skills-hooks + durable memory vault | ||
| orchestration | ||
| skills-memory + orchestrate skill + project-scoped custom agents | ||
| ``` | ||
| Metadata always uses `.codex/agent-arche.json`. Skills remain in `.agents/skills/`, the Codex-compatible shared skill location. | ||
| ## Skill selection profiles | ||
| The installer combines three inputs into checkbox defaults: | ||
| ```text | ||
| installation scope + project type + project stage -> recommended skills | ||
| ``` | ||
| - Skills-only and Skills + hooks start with the focused engineering core. | ||
| - Memory also checks `project-startup`, which the user can deselect for an established project. | ||
| - Full orchestration also checks planning and architecture skills used by complex delegated work. | ||
| - Project type selects the relevant frontend/backend domain workflows. | ||
| - Project stage adds `project-startup` only for greenfield projects. | ||
| - The user can still select or deselect every recommendation before installation. | ||
| Every checkbox remains editable. Explicit `--select-skills` input replaces the profile, while `--yes` accepts it. Full orchestration adds its non-selectable `orchestrate` routing skill after selection. | ||
| Each skill includes `agents/openai.yaml` for UI presentation. That optional file stays short and does not replace the `SKILL.md` name and description used for progressive-disclosure routing. | ||
| ## Context budgets | ||
| The compiler defaults to an estimated 1,200-token retrieval budget, at most six results, and a maximum configurable budget of 8,000 tokens. Ranking favors exact task phrases, file/path matches, titles, metadata, and explicit path hints. The agent may read primary source after retrieval when the task requires deeper evidence. | ||
| The compiler is a retrieval aid, not a truth oracle. Source code, tests, current configuration, and live behavior remain authoritative. | ||
| ## Token and quality controls | ||
| - Only installed skill names, descriptions, and paths enter initial skill discovery. | ||
| - Skill bodies stay below 500 lines; longer detail is routed into conditional references. | ||
| - Long references expose an early contents map so the agent can load the right section. | ||
| - Deterministic indexing, ranking, filtering, and reduction run outside model context. | ||
| - Session history, reviews, archives, and `_MOC.md` remain cold by default. | ||
| - Full orchestration is explicit, capped, and reserved for work that divides cleanly. | ||
| These controls follow the [official skill progressive-disclosure guidance](https://learn.chatgpt.com/docs/build-skills), [Codex subagent guidance](https://learn.chatgpt.com/docs/agent-configuration/subagents), and [OpenAI lean-prompt guidance](https://developers.openai.com/api/docs/guides/latest-model). |
| # Memory design | ||
| Memory is tiered by retrieval value: | ||
| | Tier | Content | Default behavior | | ||
| |---|---|---| | ||
| | L0 | `memory/manifest.md` | Small, fixed-size project overview | | ||
| | L1 | Cards, decisions, patterns, learnings, features | Indexed and ranked | | ||
| | L2 | Referenced source, ADR detail, active handoffs | Read when selected or followed | | ||
| | L3 | Sessions, reviews, archive, `_MOC.md` | Cold; excluded by default | | ||
| ## Durable write gate | ||
| Write memory only if at least one is true: | ||
| - A new architecture or product decision was made. | ||
| - A cross-cutting constraint is not obvious from code. | ||
| - A reusable project-specific pattern was verified. | ||
| - A recurring non-obvious failure mode was established. | ||
| - Another session needs unfinished state not captured elsewhere. | ||
| Do not write memory for routine fixes, formatting, ordinary dependency updates, facts visible in source, or completed work already captured by a commit, issue, plan, test, or diff. | ||
| ## Card metadata | ||
| ```yaml | ||
| id: DEC-014 | ||
| kind: decision | ||
| status: active | ||
| summary: Public sessions use opaque server-side tokens. | ||
| tags: [auth, session] | ||
| paths: [src/auth/**] | ||
| symbols: [validateSession] | ||
| last_verified_commit: abc1234 | ||
| supersedes: [] | ||
| ``` | ||
| Keep the body concise and evidence-backed. Use `supersedes` instead of retaining conflicting active truth. | ||
| ## Source breadcrumbs | ||
| Rare non-obvious implementation ceilings may use: | ||
| ```ts | ||
| // arche: limit=global-lock; revisit=measured-contention; memory=DEC-014 | ||
| ``` | ||
| These comments are not general documentation. `context.cjs harvest` collects them, and `query` can rank them against a task. Ordinary code should remain free of agent-only commentary. | ||
| The generated index lives in the OS temporary directory. Markdown memory changes refresh automatically; run `index --refresh` after adding or changing a source breadcrumb. | ||
| ## Human navigation | ||
| `_MOC.md` is deliberately fixed-size. It links memory areas, not every record. This preserves Obsidian navigation without forcing the model to scan a growing table of contents. |
| # Full orchestration | ||
| Full orchestration is a high-usage option for complex work that benefits from specialized, isolated agent threads. | ||
| > Every subagent performs separate model and tool work. This normally consumes more tokens than a comparable single-agent run. | ||
| ## Routing | ||
| ```mermaid | ||
| sequenceDiagram | ||
| participant U as User | ||
| participant O as Orchestrator | ||
| participant C as Context compiler | ||
| participant E as Explorer | ||
| participant W as Implementation worker | ||
| participant R as Reviewer | ||
| participant T as Test runner | ||
| U->>O: Complex task | ||
| O->>C: Query task and known paths | ||
| C-->>O: Ranked context pack | ||
| par Independent read-heavy work | ||
| O->>E: Map code and evidence | ||
| O->>T: Establish validation baseline | ||
| end | ||
| E-->>O: Paths, symbols, constraints | ||
| T-->>O: Reduced decisive results | ||
| O->>W: One bounded non-overlapping edit slice | ||
| W-->>O: Changes and focused checks | ||
| O->>R: Review integrated change | ||
| R-->>O: Findings or clean result | ||
| O-->>U: Verified synthesis | ||
| ``` | ||
| ## Delegation gate | ||
| Use subagents when: | ||
| - Two or more bounded workstreams can proceed independently. | ||
| - Exploration, test logs, or review evidence would pollute the primary thread. | ||
| - Specialized review materially improves quality. | ||
| - Parallelism provides useful wall-clock savings. | ||
| Stay single-agent when: | ||
| - The task is small or tightly sequential. | ||
| - Agents would edit overlapping files. | ||
| - One tool call or short investigation is enough. | ||
| - Coordination would cost more than the work. | ||
| ## Controls | ||
| - Maximum initial concurrency: three subagents. | ||
| - Parallelize read-heavy work first. | ||
| - One writer owns each overlapping path set. | ||
| - Subagent prompts contain only bounded objective, selected context, ownership, constraints, evidence, and stop condition. | ||
| - Subagents return summaries and decisive evidence, not raw logs. | ||
| - The orchestrator performs integration validation and never presents unverified worker claims as confirmed. | ||
| The custom agents inherit the parent session's live approval and sandbox controls unless an agent definition intentionally narrows them. |
Sorry, the diff of this file is not supported yet
| --- | ||
| id: project-manifest | ||
| title: Project Manifest | ||
| kind: manifest | ||
| status: active | ||
| summary: Fixed-size project facts used as the first retrieval layer. | ||
| tags: | ||
| - project | ||
| --- | ||
| # Project Manifest | ||
| ## Purpose | ||
| TODO: one sentence describing the product and primary users. | ||
| ## Stack | ||
| TODO: languages, frameworks, runtime, data stores, and deployment. | ||
| ## Canonical Commands | ||
| - Check: TODO | ||
| - Test: TODO | ||
| - Build: TODO | ||
| ## Active Area | ||
| TODO: current development focus, or `none`. | ||
| ## Durable Constraints | ||
| - TODO: only constraints that affect many tasks. | ||
| Keep this file below roughly 250 tokens. Move detail into durable cards. |
+16
| # Attribution notice | ||
| Agent Arche is an original integration that adapts ideas and, in some skill packages, workflow structure from the projects below. Upstream projects remain governed by their own licenses and attribution requirements. | ||
| | Area in Agent Arche | Credit | | ||
| |---|---| | ||
| | Engineering, planning, debugging, review, and deep-module workflow foundations | [mattpocock/skills](https://github.com/mattpocock/skills) | | ||
| | Fixed-height grouped skill selector and terminal redraw design | [vercel-labs/skills `search-multiselect.ts`](https://github.com/vercel-labs/skills/blob/main/src/prompts/search-multiselect.ts) | | ||
| | Sparse machine-searchable source breadcrumb and deterministic harvesting inspiration | [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail) | | ||
| | Design workflow inspiration | [cyxzdev/Uncodixfy](https://github.com/cyxzdev/Uncodixfy), [pbakaus/impeccable](https://github.com/pbakaus/impeccable), [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill) | | ||
| | Explicit compressed communication mode | [JuliusBrussee/caveman](https://github.com/JuliusBrussee/caveman) | | ||
| | Karpathy-style coding guardrails and PostgreSQL patterns | [Akindu23/my-agent-skills](https://github.com/Akindu23/my-agent-skills) | | ||
| | CLI prompt and color libraries | [Clack](https://github.com/bombshell-dev/clack), [picocolors](https://github.com/alexeyraspopov/picocolors) | | ||
| | Codex hook, custom-agent, and subagent configuration behavior | [Official OpenAI Codex documentation](https://learn.chatgpt.com/docs/agent-configuration/subagents) | | ||
| The installed versions are intentionally adapted for selective installation, bounded context retrieval, and opt-in orchestration; they should not be treated as verbatim upstream distributions. See each linked repository for its current license and original authorship. Agent Arche itself is distributed under the repository's MIT license. |
| name = "code_explorer" | ||
| description = "Read-only repository explorer that maps relevant paths, symbols, callers, and documented constraints." | ||
| sandbox_mode = "read-only" | ||
| developer_instructions = """ | ||
| Stay in exploration mode. Use targeted search and the context compiler before broad reads. Trace the real execution flow, identify owners and callers, and cite files and symbols. Do not edit files or propose speculative redesigns. Return only decisive evidence, remaining uncertainty, and the smallest useful map for the parent agent. | ||
| """ |
| name = "implementation_worker" | ||
| description = "Implements one bounded, non-overlapping engineering slice and verifies only the behavior it owns." | ||
| developer_instructions = """ | ||
| Own only the paths and outcome assigned by the orchestrator. Read applicable instructions and load only matching installed skills. Preserve unrelated changes, make the smallest defensible edit, and run focused validation. Do not expand scope, commit, push, publish, or modify files owned by another agent. Return changed files, checks run, decisive results, and remaining risk. | ||
| """ |
| name = "orchestrator" | ||
| description = "Coordinates complex engineering work across bounded specialized subagents and returns one verified result. Use only for explicit orchestration or parallel-agent requests." | ||
| developer_instructions = """ | ||
| Own task decomposition, routing, synchronization, and final synthesis. First use the installed context compiler to retrieve a compact task context pack. Keep requirements, decisions, and integration state in your thread; move noisy exploration, test logs, and independent reviews to subagents. | ||
| Do not spawn agents for a task one agent can complete cleanly. Start with at most three concurrent agents. Prefer parallel read-heavy work. Never assign overlapping files to multiple writing agents. Sequence dependent work and stop fan-out when enough evidence exists. | ||
| Route repository mapping to code_explorer, one bounded edit slice to implementation_worker, review to reviewer, and validation/log reduction to test_runner. Give each agent only its objective, relevant snippets, owned paths, constraints, evidence requirements, and stop condition. Wait for required results, resolve conflicts, run final integration validation, and return one concise evidence-backed answer. | ||
| """ |
| name = "reviewer" | ||
| description = "Read-only reviewer focused on correctness, security, regressions, and missing verification." | ||
| sandbox_mode = "read-only" | ||
| developer_instructions = """ | ||
| Review the assigned change like an owner. Prioritize real correctness defects, security issues, behavior regressions, and missing tests. Cite exact files and symbols and include a reproduction or failure path when possible. Skip style-only feedback unless it hides a defect. Do not edit files. Return findings in severity order and say explicitly when no material finding is confirmed. | ||
| """ |
| name = "test_runner" | ||
| description = "Runs bounded validation and returns reduced, decisive test or build evidence instead of raw logs." | ||
| developer_instructions = """ | ||
| Run only the validation commands assigned by the orchestrator or clearly documented by the repository. Do not edit source files. Keep full logs out of the parent context: return command, exit status, relevant counts, and the shortest decisive failure excerpt with file references. Distinguish product failures from environment, permission, network, and missing-tool failures. | ||
| """ |
| approval_policy = "on-request" | ||
| sandbox_mode = "workspace-write" | ||
| web_search = "live" | ||
| project_doc_fallback_filenames = ["AGENTS.md"] | ||
| [features] | ||
| hooks = true | ||
| [agents] | ||
| enabled = true | ||
| max_concurrent_threads_per_session = 3 | ||
| interrupt_message = true | ||
| # Optional MCP servers. Enable only when the installed project needs them. | ||
| [mcp_servers.context7] | ||
| command = "npx" | ||
| args = ["-y", "@upstash/context7-mcp"] | ||
| enabled = false | ||
| tool_timeout_sec = 60 | ||
| [mcp_servers.playwright] | ||
| command = "npx" | ||
| args = ["-y", "@playwright/mcp@latest"] | ||
| enabled = false | ||
| startup_timeout_sec = 20 | ||
| tool_timeout_sec = 60 | ||
| [mcp_servers.github] | ||
| url = "https://api.githubcopilot.com/mcp/" | ||
| bearer_token_env_var = "GITHUB_PAT_TOKEN" | ||
| enabled = false | ||
| [mcp_servers.openaiDeveloperDocs] | ||
| url = "https://developers.openai.com/mcp" | ||
| enabled = false |
| interface: | ||
| display_name: "Caveman Mode" | ||
| short_description: "Use an explicit ultra-concise response mode" | ||
| default_prompt: "Use $caveman to answer this in concise caveman mode." | ||
| policy: | ||
| allow_implicit_invocation: false |
| interface: | ||
| display_name: "Code Review" | ||
| short_description: "Review code against standards and requirements" | ||
| default_prompt: "Use $code-review to review this change against standards and requirements." |
| interface: | ||
| display_name: "Codebase Design" | ||
| short_description: "Design deep modules and maintainable seams" | ||
| default_prompt: "Use $codebase-design to explore a deeper module interface for this code." |
| interface: | ||
| display_name: "Product Design" | ||
| short_description: "Design, implement, and critique product interfaces" | ||
| default_prompt: "Use $design to improve this interface while preserving the product context." |
| interface: | ||
| display_name: "Diagnosing Bugs" | ||
| short_description: "Reproduce and isolate difficult software failures" | ||
| default_prompt: "Use $diagnosing-bugs to reproduce and isolate this failure before proposing a fix." |
| interface: | ||
| display_name: "Git Workflow" | ||
| short_description: "Create clean branches, commits, and pull requests" | ||
| default_prompt: "Use $git to prepare the requested Git change with repository conventions." |
| interface: | ||
| display_name: "Grill With Docs" | ||
| short_description: "Pressure-test plans against code and decisions" | ||
| default_prompt: "Use $grill-with-docs to pressure-test this plan against the project evidence." |
| interface: | ||
| display_name: "Agent Handoff" | ||
| short_description: "Create compact continuation context for another agent" | ||
| default_prompt: "Use $handoff to preserve only the unfinished context another agent needs." |
| interface: | ||
| display_name: "Implement Change" | ||
| short_description: "Implement and verify a requested code change" | ||
| default_prompt: "Use $implement to make and verify this requested change." |
| interface: | ||
| display_name: "Improve Codebase Architecture" | ||
| short_description: "Find and explain high-leverage architecture improvements" | ||
| default_prompt: "Use $improve-codebase-architecture to find the strongest deepening opportunities." |
| interface: | ||
| display_name: "Karpathy Guidelines" | ||
| short_description: "Keep coding changes simple, scoped, and verifiable" | ||
| default_prompt: "Use $karpathy-guidelines to keep this coding task scoped and verifiable." |
| interface: | ||
| display_name: "Full Orchestration" | ||
| short_description: "Route divisible work through specialized subagents" | ||
| default_prompt: "Use $orchestrate to delegate this divisible task to the smallest useful agent team." |
| --- | ||
| name: orchestrate | ||
| description: Route a complex, divisible engineering task through the project-scoped Codex orchestrator and specialized subagents. Use only when the user explicitly asks for orchestration, subagents, delegation, or parallel agent work. Full orchestration consumes substantially more tokens than a comparable single-agent run. | ||
| --- | ||
| # Orchestrate | ||
| Use the `orchestrator` custom agent to own decomposition, delegation, synchronization, and final synthesis. | ||
| Do not delegate a task that one agent can complete cleanly. Subagents are justified when at least two bounded workstreams can proceed independently, or when noisy exploration, tests, logs, or review evidence should stay outside the main thread. | ||
| Before spawning: | ||
| 1. Run the context compiler for the user request and known paths. | ||
| 2. Define the shared goal, hard constraints, approval boundary, and success checks. | ||
| 3. Split by ownership. Never give two writing agents overlapping files. | ||
| Route work to the narrowest agent: | ||
| - `code_explorer`: read-only repository mapping and evidence. | ||
| - `implementation_worker`: one bounded implementation slice. | ||
| - `reviewer`: correctness, security, regression, and test review. | ||
| - `test_runner`: focused validation and reduced failure evidence. | ||
| Start with no more than three concurrent subagents. Prefer parallel read-heavy work. Sequence exploration before implementation and implementation before final review when outputs depend on each other. | ||
| Each subagent prompt must include only its objective, relevant context-pack snippets, owned paths, constraints, expected evidence, output schema, and stop condition. Do not fork or repeat the full conversation. | ||
| Require each subagent to return: | ||
| ```text | ||
| status: complete | blocked | ||
| summary: concise result | ||
| evidence: paths, symbols, commands, and decisive output | ||
| changes: files changed, or none | ||
| risks: remaining uncertainty | ||
| ``` | ||
| Wait for required results, resolve conflicts once, run final integration checks in the main thread, then report one consolidated answer. Do not claim a subagent's result was independently verified unless the main thread checked it. |
| interface: | ||
| display_name: "PostgreSQL Patterns" | ||
| short_description: "Apply safe PostgreSQL schema and query patterns" | ||
| default_prompt: "Use $postgres-patterns to review this PostgreSQL change." |
| interface: | ||
| display_name: "SEO" | ||
| short_description: "Improve crawlability, metadata, and search performance" | ||
| default_prompt: "Use $seo to audit and improve this site search behavior." |
| # SEO growth and monitoring | ||
| ## Contents | ||
| - [Off-page basics](#off-page-basics) | ||
| - [Monitoring](#monitoring) | ||
| - [Common pitfalls](#common-pitfalls) | ||
| - [Validation tools](#validation-tools) | ||
| - [Portfolio-specific patterns](#portfolio-specific-patterns) | ||
| ## Off-page basics | ||
| ### Social signal standardization | ||
| Keep the same full name, role/headline, primary-domain link, and public identity across relevant profiles. Use `rel="me noopener noreferrer"` on outbound social links from the site. | ||
| ### Backlink strategy | ||
| Prefer a small number of relevant, credible links over bulk outreach. Useful linkable assets include detailed project case studies, original research or comparisons, and documented open-source tools. Check topical relevance and spam signals directly rather than treating third-party authority scores as truth. | ||
| ## Monitoring | ||
| ### Weekly checks | ||
| In Google Search Console, review impressions, clicks, CTR by page, average position, and new indexing errors. Investigate changes against deployments, crawl rules, content updates, and Core Web Vitals before choosing a fix. | ||
| ### Response guide | ||
| | Signal | Action | | ||
| |---|---| | ||
| | Impressions fall | Check `noindex`, robots rules, canonicals, server errors, and demand changes | | ||
| | Page CTR falls | Compare the query mix, then test a more specific title and description | | ||
| | Average position falls | Refresh stale content and inspect internal-link and competitor changes | | ||
| | Core Web Vitals regress | Measure the affected template and isolate the deployment regression | | ||
| Review longer-term trends monthly. Measure a material change long enough to account for crawl and reporting delay before attributing results. | ||
| ## Common pitfalls | ||
| | Pitfall | Cause | Fix | | ||
| |---|---|---| | ||
| | Template syntax in JSON-LD | Unsafe serialized markup | Escape `<` when embedding JSON in HTML | | ||
| | Duplicate canonicals | Shared or copied metadata | Give every indexable page the correct canonical | | ||
| | H1 and title diverge | Metadata changes without page review | Keep their subject and intent aligned | | ||
| | Empty schema fields | Unvalidated source data | Filter and validate before serialization | | ||
| | CLS from images | Missing dimensions | Provide intrinsic width and height | | ||
| | API routes indexed | Missing crawl controls | Prevent discovery and return appropriate indexing signals | | ||
| | Meta keywords | Obsolete implementation | Remove the tag | | ||
| ## Validation tools | ||
| | Tool | Use | | ||
| |---|---| | ||
| | [Google Rich Results Test](https://search.google.com/test/rich-results) | Validate supported structured data | | ||
| | [PageSpeed Insights](https://pagespeed.web.dev) | Inspect field and lab performance | | ||
| | [Google Search Console](https://search.google.com/search-console) | Review indexing and search performance | | ||
| | Lighthouse | Run local performance and SEO checks | | ||
| | `site:example.com` search | Perform a rough discovery check, not a complete index count | | ||
| ## Portfolio-specific patterns | ||
| - Target branded and role queries naturally across the appropriate pages. | ||
| - Make project pages useful case studies with evidence, relevant schema, source/demo links, and related internal links. | ||
| - Present certifications as verifiable trust signals without overstating what they prove. | ||
| - Link portfolio proof to related explanatory articles and back again when the relationship helps the reader. |
| interface: | ||
| display_name: "Test-Driven Development" | ||
| short_description: "Run a focused red-green development loop" | ||
| default_prompt: "Use $tdd to implement this behavior through a focused red-green loop." |
| interface: | ||
| display_name: "Spec to Tickets" | ||
| short_description: "Split planned work into verifiable vertical tickets" | ||
| default_prompt: "Use $to-tickets to split this approved plan into verifiable vertical tickets." |
| #!/usr/bin/env node | ||
| const additionalContext = [ | ||
| 'Load .agents/skills/caveman/SKILL.md at session start and use caveman ultra for user-visible conversational prose unless the user disables it. Do not compress requested artifacts, code, exact errors, or explanations that require detail.', | ||
| 'Before planning or editing, read applicable AGENTS.md files and only the relevant project docs or memory notes that exist.', | ||
| 'For implementation, refactoring, or code review, load .agents/skills/karpathy-guidelines/SKILL.md and follow it for that task.', | ||
| 'Load other skills only when their descriptions match the request; do not preload the full skill library.', | ||
| `Never hardcode secrets, API keys, or tokens. Use environment variables instead.`, | ||
| `Validate all external input before passing it to SQL, file paths, shell commands, or HTML output.`, | ||
| `Take small, reversible actions. Confirm destructive operations with the user first.`, | ||
| 'Maintain the custom Obsidian memory vault at memory/. Read memory/_MOC.md before work when it exists, update memory with what was done before finishing, and keep _MOC.md linked to new notes.', | ||
| 'Read applicable AGENTS.md files. Load only installed skills whose descriptions match the task.', | ||
| 'Before broad project or memory reads, run `node .codex/context/context.cjs query --task "<user request>" --paths "<known paths>"` and use the returned context pack. Never read memory/_MOC.md as model context.', | ||
| 'Write memory only for durable new decisions, constraints, reusable patterns, recurring gotchas, or an explicit handoff. Routine work needs no memory note.', | ||
| 'Keep changes scoped and confirm destructive or external actions.', | ||
| ].join(' '); | ||
@@ -13,0 +9,0 @@ |
+259
-305
@@ -1,323 +0,277 @@ | ||
| import React, { useState, useEffect, useMemo, useRef } from "react"; | ||
| import { Box, Text, useApp, useInput } from "ink"; | ||
| import fs from "fs"; | ||
| import { COPY } from "./lib/constants.js"; | ||
| import { readPackageJson, readMeta, detectInstalledPlatform, writeMeta, summarizePlan, fetchNpmHash, fetchNpmLatestVersion, isAllowedInstallPath, sleep, copyDir, copyFile, migrateLegacyMemoryVault, } from "./lib/utils.js"; | ||
| import path from "path"; | ||
| import { cancel, confirm, intro, isCancel, log, note, outro, select, spinner, } from "@clack/prompts"; | ||
| import pc from "picocolors"; | ||
| import { INSTALL_SCOPE_META, ORCHESTRATION_SKILL, PROJECT_STAGE_META, PROJECT_TYPE_META, SELECTABLE_SKILLS, SKILL_GROUPS, recommendedSkills, } from "./lib/constants.js"; | ||
| import { getCodexPlan } from "./lib/plans.js"; | ||
| import { getFooterHints } from "./lib/hints.js"; | ||
| import { Frame, Section } from "./components/Layout.js"; | ||
| import { Header } from "./components/Header.js"; | ||
| import { KeyHints, ScopeStep } from "./components/Options.js"; | ||
| import { InstallPreview, ExistingInstallView, UpToDateView, UpdateMissingView } from "./components/Preview.js"; | ||
| import { ProgressView } from "./components/Progress.js"; | ||
| import { SuccessView } from "./components/Success.js"; | ||
| import { Spinner } from "@inkjs/ui"; | ||
| const h = React.createElement; | ||
| function useCompactLayout() { | ||
| const read = () => ({ | ||
| width: process.stdout.columns ?? 120, | ||
| height: process.stdout.rows ?? 30, | ||
| import { skillMultiselect, skillSelectorCancel } from "./lib/skill-selector.js"; | ||
| import { copyDir, copyFile, detectInstalledPlatform, fetchNpmHash, fetchNpmLatestVersion, isAllowedInstallPath, migrateLegacyMemoryVault, readMeta, readPackageJson, summarizePlan, writeMeta, } from "./lib/utils.js"; | ||
| const platform = "codex"; | ||
| function stopOnCancel(value) { | ||
| if (!isCancel(value)) | ||
| return false; | ||
| cancel("No files were changed."); | ||
| return true; | ||
| } | ||
| function relativeTarget(cwd, target) { | ||
| const relative = path.relative(cwd, target).replace(/\\/g, "/"); | ||
| return relative || "."; | ||
| } | ||
| function formatPlan(plan) { | ||
| const preview = summarizePlan(plan); | ||
| if (!preview) | ||
| return "Nothing to install."; | ||
| const targets = preview.steps.map((step) => { | ||
| const unit = step.count === 1 ? "file" : "files"; | ||
| return `${pc.cyan("◆")} ${step.label} ${pc.dim(`(${step.count} ${unit})`)}`; | ||
| }); | ||
| const [dims, setDims] = useState(read); | ||
| useEffect(() => { | ||
| const update = () => setDims(read()); | ||
| process.stdout.on("resize", update); | ||
| return () => { | ||
| process.stdout.off("resize", update); | ||
| }; | ||
| }, []); | ||
| return { | ||
| width: dims.width, | ||
| height: dims.height, | ||
| compact: dims.height < 40, | ||
| }; | ||
| if (preview.missing.length > 0) { | ||
| targets.push(pc.yellow(`Missing: ${preview.missing.join(", ")}`)); | ||
| } | ||
| return targets.join("\n"); | ||
| } | ||
| function isInstallScope(value) { | ||
| return value === "skills-memory" || value === "skills"; | ||
| function formatResults(results) { | ||
| return results | ||
| .map((result) => `${pc.green("◆")} ${result.label} ${pc.dim(result.msg)}`) | ||
| .join("\n"); | ||
| } | ||
| export function App({ force = false }) { | ||
| const { exit } = useApp(); | ||
| const { compact } = useCompactLayout(); | ||
| const pkg = useMemo(() => readPackageJson(), []); | ||
| const cwd = process.cwd(); | ||
| const escapeLockUntil = useRef(0); | ||
| const [step, setStep] = useState("scope"); | ||
| const [scope, setScope] = useState("skills-memory"); | ||
| const platform = "codex"; | ||
| const [plan, setPlan] = useState(null); | ||
| const [existing, setExisting] = useState(null); | ||
| const [aborted, setAborted] = useState(false); | ||
| const [installSteps, setInstallSteps] = useState([]); | ||
| const [currentStepIdx, setCurrentStepIdx] = useState(-1); | ||
| const [totalFiles, setTotalFiles] = useState(0); | ||
| const [hash, setHash] = useState(null); | ||
| const [fetchingHash, setFetchingHash] = useState(false); | ||
| const [missing, setMissing] = useState([]); | ||
| const [preview, setPreview] = useState(null); | ||
| const [updateChecking, setUpdateChecking] = useState(force); | ||
| const [latestVersion, setLatestVersion] = useState(null); | ||
| const resetPreparedState = () => { | ||
| setPlan(null); | ||
| setPreview(null); | ||
| setExisting(null); | ||
| }; | ||
| const abortInstall = () => { | ||
| setAborted(true); | ||
| setTimeout(() => exit(), 100); | ||
| }; | ||
| const goBack = (currentStep) => { | ||
| if (currentStep === "confirm") { | ||
| resetPreparedState(); | ||
| setStep("scope"); | ||
| return true; | ||
| function showExisting(existing) { | ||
| log.warn(`Agent Arche v${existing.version} is already installed with this scope.`); | ||
| note("Run npx agent-arche update to replace the installed files.", "Existing installation"); | ||
| outro("No files were changed."); | ||
| } | ||
| async function chooseScope(initialScope) { | ||
| if (initialScope) | ||
| return initialScope; | ||
| const answer = await select({ | ||
| message: "What do you want to install?", | ||
| initialValue: "skills-memory", | ||
| options: Object.entries(INSTALL_SCOPE_META).map(([value, meta]) => ({ | ||
| value: value, | ||
| label: meta.label, | ||
| hint: meta.summary, | ||
| })), | ||
| }); | ||
| return stopOnCancel(answer) ? null : answer; | ||
| } | ||
| async function chooseProjectType(initialType) { | ||
| if (initialType) | ||
| return initialType; | ||
| const answer = await select({ | ||
| message: "What kind of project is this?", | ||
| initialValue: "full-stack", | ||
| options: Object.entries(PROJECT_TYPE_META).map(([value, meta]) => ({ | ||
| value: value, | ||
| label: meta.label, | ||
| hint: meta.summary, | ||
| })), | ||
| }); | ||
| return stopOnCancel(answer) ? null : answer; | ||
| } | ||
| async function chooseProjectStage(initialStage) { | ||
| if (initialStage) | ||
| return initialStage; | ||
| const answer = await select({ | ||
| message: "What stage is the project in?", | ||
| initialValue: "established", | ||
| options: Object.entries(PROJECT_STAGE_META).map(([value, meta]) => ({ | ||
| value: value, | ||
| label: meta.label, | ||
| hint: meta.summary, | ||
| })), | ||
| }); | ||
| return stopOnCancel(answer) ? null : answer; | ||
| } | ||
| function normalizeSkills(skills) { | ||
| if (skills.includes("*")) | ||
| return [...SELECTABLE_SKILLS]; | ||
| const allowed = new Set(SELECTABLE_SKILLS); | ||
| return [...new Set(skills.filter((skill) => allowed.has(skill)))]; | ||
| } | ||
| async function chooseSkills(scope, projectType, projectStage, initialSkills, yes = false) { | ||
| const defaults = recommendedSkills(scope, projectType, projectStage); | ||
| if (initialSkills) { | ||
| const normalized = normalizeSkills(initialSkills); | ||
| const unknown = initialSkills.filter((skill) => (skill !== "*" | ||
| && !(scope === "orchestration" && skill === ORCHESTRATION_SKILL) | ||
| && !normalized.includes(skill))); | ||
| if (unknown.length > 0) | ||
| throw new Error(`Unknown skill selection: ${unknown.join(", ")}`); | ||
| return normalized; | ||
| } | ||
| if (yes) | ||
| return [...defaults]; | ||
| const answer = await skillMultiselect({ | ||
| message: `Select skills for ${INSTALL_SCOPE_META[scope].label}`, | ||
| items: Object.entries(SKILL_GROUPS).flatMap(([group, skills]) => (skills.map((skill) => ({ ...skill, group })))), | ||
| initialSelected: defaults, | ||
| required: true, | ||
| maxVisible: 14, | ||
| }); | ||
| if (answer === skillSelectorCancel) { | ||
| cancel("No files were changed."); | ||
| return null; | ||
| } | ||
| return normalizeSkills(answer); | ||
| } | ||
| function sameSkills(left, right) { | ||
| return [...left].sort().join("\n") === [...right].sort().join("\n"); | ||
| } | ||
| async function resolveUpdate(cwd) { | ||
| const status = spinner(); | ||
| status.start("Checking the installed version"); | ||
| const detected = detectInstalledPlatform(cwd); | ||
| if (!detected) { | ||
| status.stop("No installation found"); | ||
| log.info("Run npx agent-arche install first."); | ||
| outro("No files were changed."); | ||
| return null; | ||
| } | ||
| const latestVersion = await fetchNpmLatestVersion(); | ||
| if (latestVersion && detected.meta.version === latestVersion) { | ||
| status.stop(`Already up to date (v${latestVersion})`); | ||
| outro("Nothing to update."); | ||
| return null; | ||
| } | ||
| status.stop(latestVersion | ||
| ? `Updating v${detected.meta.version} → v${latestVersion}` | ||
| : `Updating v${detected.meta.version}`); | ||
| return { scope: detected.meta.scope, existing: detected.meta }; | ||
| } | ||
| async function installPlan(cwd, plan, scope, version) { | ||
| const results = []; | ||
| const missing = []; | ||
| let totalFiles = 0; | ||
| const progress = spinner(); | ||
| progress.start("Installing Agent Arche"); | ||
| for (const step of plan.steps) { | ||
| progress.message(`Installing ${step.label}`); | ||
| if (!step.src || !fs.existsSync(step.src)) { | ||
| missing.push(step.label); | ||
| continue; | ||
| } | ||
| return false; | ||
| }; | ||
| const lockEscape = () => { | ||
| escapeLockUntil.current = Date.now() + 250; | ||
| }; | ||
| useInput((input, key) => { | ||
| const isEscape = key.escape || input === "\u001B"; | ||
| if (isEscape && Date.now() < escapeLockUntil.current) { | ||
| return; | ||
| } | ||
| // Block all input while the update check async is in flight | ||
| if (force && updateChecking) { | ||
| if (input === "q" || input === "Q" || isEscape) { | ||
| exit(); | ||
| if ("destDir" in step && step.destDir) { | ||
| if (!isAllowedInstallPath(cwd, step.destDir)) { | ||
| results.push({ label: step.label, msg: "blocked (unsafe path)" }); | ||
| continue; | ||
| } | ||
| return; | ||
| } | ||
| if (input === "q" || input === "Q") { | ||
| if (step === "done" || step === "existing" || step === "up-to-date" || step === "update-missing") { | ||
| exit(); | ||
| return; | ||
| const migrated = step.legacyMemoryDirs | ||
| ? migrateLegacyMemoryVault(cwd, step.destDir, step.legacyMemoryDirs) | ||
| : null; | ||
| if (migrated) { | ||
| totalFiles += migrated.count; | ||
| results.push({ label: step.label, msg: `migrated from ${migrated.from}` }); | ||
| } | ||
| abortInstall(); | ||
| return; | ||
| } | ||
| if (step === "confirm") { | ||
| if (isEscape) { | ||
| const movedBack = goBack(step); | ||
| if (movedBack) { | ||
| lockEscape(); | ||
| } | ||
| return; | ||
| else if (step.skipIfExists && fs.existsSync(step.destDir)) { | ||
| results.push({ label: step.label, msg: "kept existing files" }); | ||
| } | ||
| if (key.return || input === "y" || input === "Y") { | ||
| setStep("install"); | ||
| return; | ||
| else { | ||
| const count = copyDir(step.src, step.destDir, step.transform); | ||
| totalFiles += count; | ||
| results.push({ label: step.label, msg: `${count} file${count === 1 ? "" : "s"}` }); | ||
| } | ||
| if (input === "n" || input === "N") { | ||
| abortInstall(); | ||
| return; | ||
| } | ||
| return; | ||
| continue; | ||
| } | ||
| if (isEscape) { | ||
| if (step === "install") { | ||
| abortInstall(); | ||
| lockEscape(); | ||
| return; | ||
| if ("destFile" in step && step.destFile) { | ||
| if (!isAllowedInstallPath(cwd, step.destFile)) { | ||
| results.push({ label: step.label, msg: "blocked (unsafe path)" }); | ||
| continue; | ||
| } | ||
| if (step === "scope") { | ||
| abortInstall(); | ||
| lockEscape(); | ||
| return; | ||
| if (step.skipIfExists && fs.existsSync(step.destFile)) { | ||
| results.push({ label: step.label, msg: "kept existing file" }); | ||
| } | ||
| const movedBack = goBack(step); | ||
| if (movedBack) { | ||
| lockEscape(); | ||
| return; | ||
| else { | ||
| copyFile(step.src, step.destFile); | ||
| totalFiles += 1; | ||
| results.push({ label: step.label, msg: "1 file" }); | ||
| } | ||
| if (!movedBack && step !== "done" && step !== "existing" && step !== "up-to-date" && step !== "update-missing") { | ||
| abortInstall(); | ||
| lockEscape(); | ||
| } | ||
| } | ||
| }); | ||
| const preparePlan = (nextScope) => { | ||
| const nextPlan = getCodexPlan(cwd, nextScope); | ||
| setScope(nextScope); | ||
| setPlan(nextPlan); | ||
| setPreview(summarizePlan(nextPlan, nextScope)); | ||
| const detected = readMeta(nextPlan.metaDir); | ||
| setExisting(detected); | ||
| if (detected && detected.platform === platform && detected.scope === nextScope && !force) { | ||
| setStep("existing"); | ||
| setTimeout(() => exit(), 100); | ||
| return; | ||
| } | ||
| setStep("confirm"); | ||
| }; | ||
| useEffect(() => { | ||
| if (!force) { | ||
| return; | ||
| } | ||
| let cancelled = false; | ||
| const resolveUpdateFlow = async () => { | ||
| const detected = detectInstalledPlatform(cwd); | ||
| if (!detected) { | ||
| setUpdateChecking(false); | ||
| setStep("update-missing"); | ||
| setTimeout(() => exit(), 1200); | ||
| return; | ||
| } | ||
| setExisting(detected.meta); | ||
| setScope(detected.meta.scope); | ||
| const npmLatest = await fetchNpmLatestVersion(); | ||
| if (cancelled) { | ||
| return; | ||
| } | ||
| if (npmLatest) { | ||
| setLatestVersion(npmLatest); | ||
| if (detected.meta.version === npmLatest) { | ||
| setUpdateChecking(false); | ||
| setStep("up-to-date"); | ||
| return; | ||
| } | ||
| } | ||
| setUpdateChecking(false); | ||
| preparePlan(detected.meta.scope); | ||
| }; | ||
| resolveUpdateFlow(); | ||
| return () => { | ||
| cancelled = true; | ||
| }; | ||
| }, [cwd, force, exit]); | ||
| useEffect(() => { | ||
| if (step !== "install" || !plan) | ||
| return; | ||
| let cancelled = false; | ||
| const runInstall = async () => { | ||
| const results = []; | ||
| let total = 0; | ||
| const missingItems = []; | ||
| for (let i = 0; i < plan.steps.length; i++) { | ||
| if (cancelled) | ||
| return; | ||
| setCurrentStepIdx(i); | ||
| const s = plan.steps[i]; | ||
| if (!s.src || !fs.existsSync(s.src)) { | ||
| missingItems.push(s.label); | ||
| continue; | ||
| } | ||
| await sleep(250); | ||
| let countMsg = ""; | ||
| if ("destDir" in s && s.destDir) { | ||
| if (!isAllowedInstallPath(cwd, s.destDir)) { | ||
| countMsg = "blocked (unsafe path)"; | ||
| results.push({ label: s.label, msg: countMsg }); | ||
| setInstallSteps([...results]); | ||
| continue; | ||
| } | ||
| const migratedMemory = s.legacyMemoryDirs | ||
| ? migrateLegacyMemoryVault(cwd, s.destDir, s.legacyMemoryDirs) | ||
| : null; | ||
| if (migratedMemory) { | ||
| total += migratedMemory.count; | ||
| countMsg = `migrated from ${migratedMemory.from}`; | ||
| } | ||
| else if (s.skipIfExists && fs.existsSync(s.destDir)) { | ||
| countMsg = "skipped (already exists)"; | ||
| } | ||
| else { | ||
| const count = copyDir(s.src, s.destDir, s.transform); | ||
| total += count; | ||
| countMsg = `${count} file${count === 1 ? "" : "s"}`; | ||
| } | ||
| } | ||
| else if ("destFile" in s && s.destFile) { | ||
| if (!isAllowedInstallPath(cwd, s.destFile)) { | ||
| countMsg = "blocked (unsafe path)"; | ||
| results.push({ label: s.label, msg: countMsg }); | ||
| setInstallSteps([...results]); | ||
| continue; | ||
| } | ||
| if (s.skipIfExists && fs.existsSync(s.destFile)) { | ||
| countMsg = "skipped (already exists)"; | ||
| } | ||
| else { | ||
| copyFile(s.src, s.destFile); | ||
| total++; | ||
| countMsg = "1 file"; | ||
| } | ||
| } | ||
| results.push({ label: s.label, msg: countMsg }); | ||
| setInstallSteps([...results]); | ||
| setTotalFiles(total); | ||
| await sleep(50); | ||
| } | ||
| setMissing(missingItems); | ||
| setCurrentStepIdx(-1); | ||
| setFetchingHash(true); | ||
| const nextHash = await fetchNpmHash(pkg.version); | ||
| if (cancelled) | ||
| return; | ||
| setHash(nextHash); | ||
| setFetchingHash(false); | ||
| if (!isAllowedInstallPath(cwd, plan.metaDir)) { | ||
| setStep("done"); | ||
| setTimeout(() => exit(), 100); | ||
| return; | ||
| } | ||
| writeMeta(plan.metaDir, { | ||
| version: pkg.version, | ||
| installedAt: new Date().toISOString(), | ||
| source: "AshenDulsanka/agent-arche", | ||
| sourceType: "npm", | ||
| scope, | ||
| platform, | ||
| hash: nextHash ?? null, | ||
| }); | ||
| setStep("done"); | ||
| setTimeout(() => exit(), 100); | ||
| }; | ||
| runInstall(); | ||
| return () => { cancelled = true; }; | ||
| }, [pkg.version, plan, platform, step, exit, scope]); | ||
| return h(Box, { paddingX: 1, paddingY: compact ? 0 : 1, flexDirection: "column" }, h(Frame, null, h(Header, { version: pkg.version, force, cwd, step, compact, showSteps: !force }), aborted | ||
| ? h(Section, { eyebrow: COPY.cancelled.eyebrow, title: COPY.cancelled.title }, h(Text, { color: "gray" }, COPY.cancelled.message)) | ||
| : null, !aborted && force && updateChecking | ||
| ? h(Section, { eyebrow: "UPDATE", title: COPY.install.updateChecking }, h(Box, { marginTop: 0 }, h(Spinner, { type: "dots" }), h(Text, { color: "gray" }, " npm registry"))) | ||
| : null, !aborted && !force && step === "scope" | ||
| ? h(ScopeStep, { | ||
| value: scope, | ||
| compact, | ||
| onChange: setScope, | ||
| onSubmit: (value) => { | ||
| if (!isInstallScope(value)) { | ||
| return; | ||
| } | ||
| setScope(value); | ||
| preparePlan(value); | ||
| }, | ||
| }) | ||
| : null, !aborted && step === "existing" | ||
| ? h(ExistingInstallView, { platform, existing, compact }) | ||
| : null, !aborted && step === "up-to-date" && latestVersion | ||
| ? h(UpToDateView, { platform, existing, latestVersion, compact }) | ||
| : null, !aborted && step === "update-missing" | ||
| ? h(UpdateMissingView, { compact }) | ||
| : null, !aborted && step === "confirm" && preview && plan | ||
| ? h(InstallPreview, { | ||
| } | ||
| progress.message("Verifying package integrity"); | ||
| const hash = await fetchNpmHash(version); | ||
| if (isAllowedInstallPath(cwd, plan.metaDir)) { | ||
| writeMeta(plan.metaDir, { | ||
| version, | ||
| installedAt: new Date().toISOString(), | ||
| source: "AshenDulsanka/agent-arche", | ||
| sourceType: "npm", | ||
| scope, | ||
| platform, | ||
| plan, | ||
| preview, | ||
| force, | ||
| compact, | ||
| onConfirm: (value) => { | ||
| if (value) { | ||
| setStep("install"); | ||
| return; | ||
| } | ||
| abortInstall(); | ||
| }, | ||
| }) | ||
| : null, !aborted && step === "install" && plan | ||
| ? h(ProgressView, { plan, installSteps, currentStepIdx, fetchingHash, totalFiles, compact }) | ||
| : null, !aborted && step === "done" && plan | ||
| ? h(SuccessView, { platform, plan, totalFiles, hash, missing, force, compact }) | ||
| : null, h(KeyHints, { hints: getFooterHints(step) }))); | ||
| hash, | ||
| selectedSkills: [...plan.selectedSkills], | ||
| }); | ||
| } | ||
| progress.stop(`${totalFiles} file${totalFiles === 1 ? "" : "s"} installed`); | ||
| return { results, totalFiles, hash, missing }; | ||
| } | ||
| export async function runApp({ force = false, yes = false, scope: requestedScope, projectType: requestedProjectType, projectStage: requestedProjectStage, selectedSkills: requestedSkills, } = {}) { | ||
| const cwd = process.cwd(); | ||
| const pkg = readPackageJson(); | ||
| intro(`${pc.bgCyan(pc.black(" agent-arche "))} ${pc.dim(`v${pkg.version}`)}`); | ||
| let scope = requestedScope; | ||
| let projectType = requestedProjectType; | ||
| let projectStage = requestedProjectStage; | ||
| let selectedSkills = requestedSkills ? [...requestedSkills] : undefined; | ||
| if (force) { | ||
| const update = await resolveUpdate(cwd); | ||
| if (!update) | ||
| return 0; | ||
| scope = update.scope; | ||
| selectedSkills = update.existing.selectedSkills; | ||
| } | ||
| else { | ||
| scope = await chooseScope(scope) ?? undefined; | ||
| if (!scope) | ||
| return 0; | ||
| if (!selectedSkills) { | ||
| projectType = await chooseProjectType(projectType) ?? undefined; | ||
| if (!projectType) | ||
| return 0; | ||
| projectStage = await chooseProjectStage(projectStage) ?? undefined; | ||
| if (!projectStage) | ||
| return 0; | ||
| } | ||
| } | ||
| projectType ??= "general"; | ||
| projectStage ??= "established"; | ||
| selectedSkills = await chooseSkills(scope, projectType, projectStage, selectedSkills, yes) ?? undefined; | ||
| if (!selectedSkills || selectedSkills.length === 0) | ||
| return 0; | ||
| if (scope === "orchestration") { | ||
| log.warn("Full orchestration uses substantially more tokens because every subagent performs separate model and tool work."); | ||
| } | ||
| const plan = getCodexPlan(cwd, scope, selectedSkills); | ||
| const existing = readMeta(plan.metaDir); | ||
| if (existing | ||
| && existing.platform === platform | ||
| && existing.scope === scope | ||
| && sameSkills(existing.selectedSkills, plan.selectedSkills) | ||
| && !force) { | ||
| showExisting(existing); | ||
| return 0; | ||
| } | ||
| note(formatPlan(plan), `Install in ${relativeTarget(path.dirname(cwd), cwd)}`); | ||
| note(plan.selectedSkills.join(", "), "Selected skills"); | ||
| if (!yes) { | ||
| const approved = await confirm({ | ||
| message: force ? "Update these files?" : "Install these files?", | ||
| initialValue: true, | ||
| }); | ||
| if (stopOnCancel(approved) || !approved) { | ||
| if (!isCancel(approved)) | ||
| cancel("No files were changed."); | ||
| return 0; | ||
| } | ||
| } | ||
| const result = await installPlan(cwd, plan, scope, pkg.version); | ||
| if (result.results.length > 0) | ||
| note(formatResults(result.results), "Installed"); | ||
| if (result.missing.length > 0) | ||
| log.warn(`Skipped missing sources: ${result.missing.join(", ")}`); | ||
| if (!result.hash) | ||
| log.warn("Package integrity could not be fetched (offline or unpublished)."); | ||
| const nextSteps = plan.nextSteps.map((step, index) => `${index + 1}. ${step}`).join("\n"); | ||
| note(nextSteps, "Next steps"); | ||
| outro(force ? "Agent Arche updated." : "Agent Arche is ready."); | ||
| return 0; | ||
| } |
+112
-54
| #!/usr/bin/env node | ||
| import React from "react"; | ||
| import { render } from "ink"; | ||
| import readline from "readline"; | ||
| import { App } from "./App.js"; | ||
| import { runApp } from "./App.js"; | ||
| import { readPackageJson } from "./lib/utils.js"; | ||
| const h = React.createElement; | ||
| // ─── Entry point ───────────────────────────────────────────────────────────── | ||
| // All logic lives in App.js and bin/lib / bin/components. | ||
| // Edit bin/lib/constants.js to change any visible UI text. | ||
| function showVersion() { | ||
| const pkg = readPackageJson(); | ||
| console.log(pkg.version); | ||
| } | ||
| function showHelp() { | ||
| console.log(` | ||
| ◆ agent-arche - Codex skills and project context | ||
| agent-arche - Selective Codex skills, context, memory, and orchestration | ||
| Usage | ||
| npx agent-arche Install files for your AI assistant | ||
| npx agent-arche install Same as above | ||
| npx agent-arche update Update to latest (overwrites existing files) | ||
| npx agent-arche --version Print version | ||
| npx agent-arche --help Show this message | ||
| Usage | ||
| npx agent-arche [install] [options] | ||
| npx agent-arche update [options] | ||
| Install destination | ||
| Codex -> .agents/skills/ with optional .codex/ hooks and memory/ | ||
| Options | ||
| --skills-only Selected skills only | ||
| --skills-hooks Selected skills plus hooks and context tools | ||
| --skills-memory Selected skills, hooks, context tools, and memory | ||
| --orchestration Full multi-agent orchestration (high usage) | ||
| --project-type=<type> full-stack, backend, frontend, or general | ||
| --project-stage=<type> established or greenfield | ||
| --select-skills=a,b Install an explicit comma-separated skill set | ||
| --all-skills Install every selectable skill | ||
| -y, --yes Accept the selected scope's recommended skills | ||
| -v, --version Print the installed CLI version | ||
| -h, --help Show this help | ||
| What gets installed | ||
| config.toml Codex runtime and MCP server configuration | ||
| skills/ Reusable task workflows and references | ||
| hooks/ Safety hooks (session-start, pre-tool, changelog) | ||
| Examples | ||
| npx agent-arche | ||
| npx agent-arche --skills-only | ||
| npx agent-arche --skills-hooks --project-type=backend --project-stage=established | ||
| npx agent-arche --skills-hooks --select-skills=implement,design,git | ||
| npx agent-arche --orchestration | ||
| npx agent-arche update | ||
| Install scopes | ||
| Skills + hooks + memory | ||
| Shared skills plus hooks, config, and root memory | ||
| Skills only Shared skills only, without hooks or memory | ||
| Verify integrity | ||
| npm view agent-arche dist.integrity | ||
| Compare with hash in agent-arche.json inside your install directory. | ||
| Source | ||
| https://github.com/AshenDulsanka/agent-arche | ||
| Source | ||
| https://github.com/AshenDulsanka/agent-arche | ||
| `); | ||
| } | ||
| const [, , command] = process.argv; | ||
| if (command === "--version" || command === "-v") { | ||
| showVersion(); | ||
| process.exit(0); | ||
| const args = process.argv.slice(2); | ||
| const command = args.find((arg) => !arg.startsWith("-")) ?? "install"; | ||
| const knownFlags = new Set([ | ||
| "-h", | ||
| "--help", | ||
| "-v", | ||
| "--version", | ||
| "-y", | ||
| "--yes", | ||
| "--skills-only", | ||
| "--skills-hooks", | ||
| "--skills-memory", | ||
| "--orchestration", | ||
| "--all-skills", | ||
| ]); | ||
| const unknownFlag = args.find((arg) => (arg.startsWith("-") | ||
| && !knownFlags.has(arg) | ||
| && !arg.startsWith("--select-skills=") | ||
| && !arg.startsWith("--project-type=") | ||
| && !arg.startsWith("--project-stage="))); | ||
| const scopeFlags = ["--skills-only", "--skills-hooks", "--skills-memory", "--orchestration"] | ||
| .filter((flag) => args.includes(flag)); | ||
| function requestedScope() { | ||
| if (args.includes("--skills-only")) | ||
| return "skills"; | ||
| if (args.includes("--skills-hooks")) | ||
| return "skills-hooks"; | ||
| if (args.includes("--skills-memory")) | ||
| return "skills-memory"; | ||
| if (args.includes("--orchestration")) | ||
| return "orchestration"; | ||
| return undefined; | ||
| } | ||
| else if (command === "--help" || command === "-h" || command === "help") { | ||
| function requestedSkills() { | ||
| const explicit = args.find((arg) => arg.startsWith("--select-skills=")); | ||
| if (!explicit) | ||
| return args.includes("--all-skills") ? ["*"] : undefined; | ||
| return explicit.slice("--select-skills=".length).split(",").map((skill) => skill.trim()).filter(Boolean); | ||
| } | ||
| function requestedProjectType() { | ||
| const value = args.find((arg) => arg.startsWith("--project-type="))?.slice("--project-type=".length); | ||
| if (!value) | ||
| return undefined; | ||
| if (["full-stack", "backend", "frontend", "general"].includes(value)) | ||
| return value; | ||
| throw new Error(`Unknown project type: ${value}`); | ||
| } | ||
| function requestedProjectStage() { | ||
| const value = args.find((arg) => arg.startsWith("--project-stage="))?.slice("--project-stage=".length); | ||
| if (!value) | ||
| return undefined; | ||
| if (["established", "greenfield"].includes(value)) | ||
| return value; | ||
| throw new Error(`Unknown project stage: ${value}`); | ||
| } | ||
| if (args.includes("--version") || args.includes("-v")) { | ||
| console.log(readPackageJson().version); | ||
| } | ||
| else if (args.includes("--help") || args.includes("-h") || command === "help") { | ||
| showHelp(); | ||
| process.exit(0); | ||
| } | ||
| else if (command === undefined || command === "install" || command === "update") { | ||
| const force = command === "update"; | ||
| readline.emitKeypressEvents(process.stdin); | ||
| if (process.stdin.isTTY) | ||
| process.stdin.setRawMode(true); | ||
| // waitUntilExit() ensures process.exit is called once ink unmounts, | ||
| // so the CLI exits automatically after install, cancel, or 'n'. | ||
| const { waitUntilExit } = render(h(App, { force })); | ||
| waitUntilExit().then(() => process.exit(0)); | ||
| else if (unknownFlag) { | ||
| console.error(`Unknown option: ${unknownFlag}`); | ||
| console.error("Run npx agent-arche --help for usage."); | ||
| process.exitCode = 1; | ||
| } | ||
| else if (scopeFlags.length > 1) { | ||
| console.error(`Choose one install scope, not: ${scopeFlags.join(", ")}`); | ||
| console.error("Run npx agent-arche --help for usage."); | ||
| process.exitCode = 1; | ||
| } | ||
| else if (command !== "install" && command !== "update") { | ||
| console.error(`Unknown command: ${command}`); | ||
| console.error("Run npx agent-arche --help for usage."); | ||
| process.exitCode = 1; | ||
| } | ||
| else { | ||
| console.error(`\n ✗ Unknown command: ${command}`); | ||
| console.error(" Run npx agent-arche --help for usage.\n"); | ||
| process.exit(1); | ||
| Promise.resolve().then(() => runApp({ | ||
| force: command === "update", | ||
| yes: args.includes("--yes") || args.includes("-y"), | ||
| scope: requestedScope(), | ||
| projectType: requestedProjectType(), | ||
| projectStage: requestedProjectStage(), | ||
| selectedSkills: requestedSkills(), | ||
| })).then((code) => { | ||
| process.exitCode = code; | ||
| }, (error) => { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| console.error(`Installation failed: ${message}`); | ||
| process.exitCode = 1; | ||
| }); | ||
| } |
+115
-87
| export const META_FILE = "agent-arche.json"; | ||
| export const PLATFORM_META = { | ||
| codex: { | ||
| name: "Codex", | ||
| short: "Codex", | ||
| accent: "cyan", | ||
| destination: ".agents/skills/ with optional .codex/ hooks and memory/", | ||
| capabilities: [ | ||
| "Shared Codex skills with progressive disclosure", | ||
| "Optional safety hooks, runtime config, and repo-local memory", | ||
| export const INSTALL_SCOPE_META = { | ||
| skills: { | ||
| label: "Skills only", | ||
| summary: "Selected skills only. Lowest runtime overhead.", | ||
| details: [ | ||
| "No hooks, context compiler, memory, or subagents", | ||
| "Best when the project already has its own agent setup", | ||
| ], | ||
| note: "Built for MCP-enabled Codex sessions.", | ||
| }, | ||
| }; | ||
| export const INSTALL_SCOPE_META = { | ||
| "skills-hooks": { | ||
| label: "Skills + hooks", | ||
| summary: "Selected skills, lean safety hooks, and deterministic context tools.", | ||
| details: [ | ||
| "No project memory and no automatic subagent orchestration", | ||
| "Good for established projects that want lightweight guardrails", | ||
| ], | ||
| }, | ||
| "skills-memory": { | ||
| label: "Skills + hooks + memory (Recommended)", | ||
| accent: "magenta", | ||
| summary: "Install shared skills, safety hooks, runtime config, and root memory.", | ||
| summary: "Adds indexed durable memory without reading the full vault.", | ||
| details: [ | ||
| "No custom-agent, instruction, rule, or root AGENTS.md templates", | ||
| "Hooks remind the assistant to update memory before finishing", | ||
| "Context compiler retrieves only task-relevant memory cards", | ||
| "Routine work does not create memory notes", | ||
| ], | ||
| }, | ||
| skills: { | ||
| label: "Skills only", | ||
| accent: "cyan", | ||
| summary: "Install only shared skills into .agents/skills/.", | ||
| orchestration: { | ||
| label: "Full orchestration (High usage)", | ||
| summary: "Orchestrator and specialized subagents. Costs substantially more usage.", | ||
| details: [ | ||
| "No hooks, runtime config, or memory", | ||
| "Great for lightweight skill updates", | ||
| "Includes selected skills, hooks, indexed memory, and custom Codex agents", | ||
| "Each subagent performs separate model and tool work, increasing token usage", | ||
| ], | ||
| }, | ||
| }; | ||
| export const STEP_ORDER = ["scope", "preview", "install", "done"]; | ||
| export const STEP_LABELS = { | ||
| scope: "Scope", | ||
| preview: "Preview", | ||
| install: "Install", | ||
| done: "Done", | ||
| export const SKILL_GROUPS = { | ||
| "Core engineering": [ | ||
| { value: "implement", label: "Implement", hint: "code changes end to end" }, | ||
| { value: "diagnosing-bugs", label: "Diagnosing bugs", hint: "root-cause workflow" }, | ||
| { value: "tdd", label: "TDD", hint: "behavior-first tests" }, | ||
| { value: "code-review", label: "Code review", hint: "correctness and spec gate" }, | ||
| { value: "karpathy-guidelines", label: "Karpathy guidelines", hint: "surgical coding guardrails" }, | ||
| ], | ||
| "Security and backend": [ | ||
| { value: "security-review", label: "Security review", hint: "trust-boundary changes" }, | ||
| { value: "postgres-patterns", label: "PostgreSQL patterns", hint: "Postgres-specific work" }, | ||
| ], | ||
| "Frontend and growth": [ | ||
| { value: "design", label: "Design", hint: "UI, UX, motion, and audits" }, | ||
| { value: "seo", label: "SEO", hint: "crawlability and search performance" }, | ||
| ], | ||
| "Planning and architecture": [ | ||
| { value: "project-startup", label: "Project startup", hint: "new or unconfigured projects only" }, | ||
| { value: "to-spec", label: "Conversation to spec", hint: "synthesize an approved spec" }, | ||
| { value: "to-tickets", label: "Spec to tickets", hint: "split work into vertical tickets" }, | ||
| { value: "grill-with-docs", label: "Grill with docs", hint: "pressure-test plans" }, | ||
| { value: "codebase-design", label: "Codebase design", hint: "deep modules and seams" }, | ||
| { value: "improve-codebase-architecture", label: "Architecture review", hint: "find high-leverage improvements" }, | ||
| ], | ||
| "Delivery and communication": [ | ||
| { value: "git", label: "Git", hint: "branches, commits, and PRs" }, | ||
| { value: "handoff", label: "Handoff", hint: "compact continuation context" }, | ||
| { value: "caveman", label: "Caveman", hint: "explicit terse response mode" }, | ||
| ], | ||
| }; | ||
| export const PRIMARY_COMMANDS = { | ||
| codex: "codex", | ||
| }; | ||
| export const COPY = { | ||
| app: { | ||
| name: "agent-arche", | ||
| tagline: "Codex skills, hooks, and project memory for terminal-first teams.", | ||
| export const PROJECT_TYPE_META = { | ||
| "full-stack": { | ||
| label: "Full-stack", | ||
| summary: "Frontend and backend work in the same project.", | ||
| }, | ||
| scope: { | ||
| eyebrow: "Install mode", | ||
| title: "Choose what you want to install.", | ||
| backend: { | ||
| label: "Backend", | ||
| summary: "APIs, services, data, or infrastructure without a frontend surface.", | ||
| }, | ||
| preview: { | ||
| eyebrow: "Preview", | ||
| includes: "What this setup includes", | ||
| targets: "Install targets", | ||
| missingWarning: "Missing sources will be skipped", | ||
| confirmHints: "Enter/Y confirm N cancel", | ||
| frontend: { | ||
| label: "Frontend", | ||
| summary: "UI-focused project without backend-specific workflows.", | ||
| }, | ||
| existing: { | ||
| eyebrow: "Detected", | ||
| nextAction: "npx agent-arche update", | ||
| noFilesChanged: "No files were changed.", | ||
| latestTitle: "agent-arche is already on the latest version for this project.", | ||
| latestMessage: "No update is required.", | ||
| missingTitle: "No existing agent-arche installation was detected.", | ||
| missingMessage: "Run npx agent-arche install to set up Codex first.", | ||
| general: { | ||
| label: "General / other", | ||
| summary: "CLI, library, tooling, documentation, or mixed non-web work.", | ||
| }, | ||
| install: { | ||
| eyebrow: "Installing", | ||
| title: "Applying the package set to your repository.", | ||
| completed: "Completed", | ||
| hashFetching: "Fetching integrity hash from npm...", | ||
| updateChecking: "Checking for a newer version on npm...", | ||
| }; | ||
| export const PROJECT_STAGE_META = { | ||
| established: { | ||
| label: "Established project", | ||
| summary: "Already has code, conventions, or an agent harness.", | ||
| }, | ||
| success: { | ||
| eyebrow: "Ready", | ||
| integrity: "Integrity", | ||
| integrityCommand: "npm view agent-arche dist.integrity", | ||
| integrityOnline: "Fetched from npm registry", | ||
| integrityOffline: "Unavailable offline / unpublished", | ||
| nextSteps: "Next steps", | ||
| skippedNote: "Skipped because source files were not found", | ||
| greenfield: { | ||
| label: "Greenfield project", | ||
| summary: "New or unconfigured project that benefits from startup context.", | ||
| }, | ||
| cancelled: { | ||
| eyebrow: "Cancelled", | ||
| title: "Installation cancelled before any files were changed.", | ||
| message: "You can run the command again whenever you're ready.", | ||
| }, | ||
| labels: { | ||
| installScope: "Install scope", | ||
| platform: "Platform", | ||
| destination: "Destination", | ||
| installUnits: "Install units", | ||
| metadata: "Metadata", | ||
| filesWritten: "Files written", | ||
| primaryCommand: "Primary next command", | ||
| integrity: "Integrity", | ||
| installedVersion: "Installed version", | ||
| npmLatestVersion: "NPM latest", | ||
| installedAt: "Installed at", | ||
| nextAction: "Next action", | ||
| completedTargets: "Completed targets", | ||
| filesCopied: "Files copied so far", | ||
| }, | ||
| }; | ||
| const CORE_RECOMMENDED_SKILLS = [ | ||
| "implement", | ||
| "diagnosing-bugs", | ||
| "tdd", | ||
| "code-review", | ||
| "karpathy-guidelines", | ||
| "security-review", | ||
| "git", | ||
| "handoff", | ||
| ]; | ||
| const PROJECT_TYPE_RECOMMENDATIONS = { | ||
| "full-stack": ["design", "seo", "postgres-patterns"], | ||
| backend: ["postgres-patterns"], | ||
| frontend: ["design", "seo"], | ||
| general: [], | ||
| }; | ||
| const PROJECT_STAGE_RECOMMENDATIONS = { | ||
| established: [], | ||
| greenfield: ["project-startup"], | ||
| }; | ||
| const SCOPE_RECOMMENDATIONS = { | ||
| skills: [], | ||
| "skills-hooks": [], | ||
| "skills-memory": [], | ||
| orchestration: [ | ||
| "to-spec", | ||
| "to-tickets", | ||
| "grill-with-docs", | ||
| "codebase-design", | ||
| "improve-codebase-architecture", | ||
| ], | ||
| }; | ||
| export function recommendedSkills(scope, projectType, projectStage) { | ||
| return [...new Set([ | ||
| ...CORE_RECOMMENDED_SKILLS, | ||
| ...PROJECT_TYPE_RECOMMENDATIONS[projectType], | ||
| ...PROJECT_STAGE_RECOMMENDATIONS[projectStage], | ||
| ...SCOPE_RECOMMENDATIONS[scope], | ||
| ])]; | ||
| } | ||
| export const ORCHESTRATION_SKILL = "orchestrate"; | ||
| export const SELECTABLE_SKILLS = Object.values(SKILL_GROUPS).flatMap((group) => group.map((skill) => skill.value)); |
+60
-24
| import path from "path"; | ||
| import { PACKAGE_ROOT } from "./utils.js"; | ||
| import { ORCHESTRATION_SKILL } from "./constants.js"; | ||
| function memoryStep(cwd) { | ||
@@ -12,32 +13,67 @@ return { | ||
| } | ||
| const SKILLS_ONLY_NEXT_STEPS = [ | ||
| "Commit .agents/skills/ to your repo", | ||
| "Ask Codex to use the relevant skill file for the task", | ||
| ]; | ||
| const SKILLS_MEMORY_NEXT_STEPS = [ | ||
| "Commit .codex/, .agents/skills/, and memory/ to your repo", | ||
| "Ask Codex to use the relevant skill file for the task", | ||
| "Before finishing work, update the memory vault with what changed using the project-startup skill", | ||
| ]; | ||
| function getNextSteps(scope) { | ||
| if (scope === "skills") | ||
| return SKILLS_ONLY_NEXT_STEPS; | ||
| return SKILLS_MEMORY_NEXT_STEPS; | ||
| function skillSteps(cwd, selectedSkills) { | ||
| return selectedSkills.map((skill) => ({ | ||
| label: `.agents/skills/${skill}/`, | ||
| src: path.join(PACKAGE_ROOT, "skills", skill), | ||
| destDir: path.join(cwd, ".agents", "skills", skill), | ||
| })); | ||
| } | ||
| export function getCodexPlan(cwd, scope) { | ||
| const NEXT_STEPS = { | ||
| skills: [ | ||
| "Commit .codex/agent-arche.json and .agents/skills/ to your repo", | ||
| "Ask Codex to use the relevant skill file for the task", | ||
| ], | ||
| "skills-hooks": [ | ||
| "Commit .codex/ and .agents/skills/ to your repo", | ||
| "Review and trust the installed hooks with /hooks", | ||
| "Ask Codex to use the relevant skill file for the task", | ||
| ], | ||
| "skills-memory": [ | ||
| "Commit .codex/, .agents/skills/, and memory/ to your repo", | ||
| "Review and trust the installed hooks with /hooks", | ||
| "Use the context query before broad memory reads", | ||
| ], | ||
| orchestration: [ | ||
| "Commit .codex/, .agents/skills/, and memory/ to your repo", | ||
| "Review and trust the installed hooks with /hooks", | ||
| "Ask Codex to use the orchestrator agent for a complex, divisible task", | ||
| ], | ||
| }; | ||
| export function getCodexPlan(cwd, scope, requestedSkills) { | ||
| const dest = path.join(cwd, ".codex"); | ||
| const metaDir = scope === "skills" ? path.join(cwd, ".agents") : dest; | ||
| const src = path.join(PACKAGE_ROOT, "codex"); | ||
| const steps = scope === "skills" | ||
| ? [ | ||
| { label: ".agents/skills/", src: path.join(PACKAGE_ROOT, "skills"), destDir: path.join(cwd, ".agents", "skills") }, | ||
| ] | ||
| : [ | ||
| { label: ".codex/config.toml", src: path.join(src, "config.toml"), destFile: path.join(dest, "config.toml") }, | ||
| const selectedSkills = scope === "orchestration" | ||
| ? [...new Set([...requestedSkills, ORCHESTRATION_SKILL])] | ||
| : [...requestedSkills]; | ||
| const selectedSkillSteps = skillSteps(cwd, selectedSkills); | ||
| let steps = selectedSkillSteps; | ||
| if (scope !== "skills") { | ||
| const configSource = scope === "orchestration" | ||
| ? path.join(PACKAGE_ROOT, "orchestration", "config.toml") | ||
| : path.join(src, "config.toml"); | ||
| steps = [ | ||
| { label: ".codex/config.toml", src: configSource, destFile: path.join(dest, "config.toml") }, | ||
| { label: ".codex/hooks/", src: path.join(src, "hooks"), destDir: path.join(dest, "hooks") }, | ||
| memoryStep(cwd), | ||
| { label: ".agents/skills/", src: path.join(PACKAGE_ROOT, "skills"), destDir: path.join(cwd, ".agents", "skills") }, | ||
| { label: ".codex/context/", src: path.join(src, "context"), destDir: path.join(dest, "context") }, | ||
| ...selectedSkillSteps, | ||
| { label: ".codex/hooks.json", src: path.join(src, "hooks.json"), destFile: path.join(dest, "hooks.json") }, | ||
| ]; | ||
| return { dest, metaDir, steps, nextSteps: getNextSteps(scope) }; | ||
| } | ||
| if (scope === "skills-memory" || scope === "orchestration") { | ||
| steps.splice(3, 0, memoryStep(cwd)); | ||
| } | ||
| if (scope === "orchestration") { | ||
| steps.splice(4, 0, { | ||
| label: ".codex/agents/", | ||
| src: path.join(PACKAGE_ROOT, "orchestration", "agents"), | ||
| destDir: path.join(dest, "agents"), | ||
| }); | ||
| } | ||
| return { | ||
| dest, | ||
| metaDir: dest, | ||
| steps, | ||
| nextSteps: NEXT_STEPS[scope], | ||
| selectedSkills, | ||
| }; | ||
| } |
+13
-29
@@ -5,3 +5,3 @@ import fs from "fs"; | ||
| import { fileURLToPath } from "url"; | ||
| import { INSTALL_SCOPE_META, META_FILE, STEP_ORDER, } from "./constants.js"; | ||
| import { INSTALL_SCOPE_META, META_FILE, SELECTABLE_SKILLS, } from "./constants.js"; | ||
| const __filename = fileURLToPath(import.meta.url); | ||
@@ -113,2 +113,9 @@ const __dirname = path.dirname(__filename); | ||
| } | ||
| const rawScope = data.scope; | ||
| const scope = rawScope && rawScope in INSTALL_SCOPE_META | ||
| ? rawScope | ||
| : "skills-memory"; | ||
| const selectedSkills = Array.isArray(data.selectedSkills) | ||
| ? data.selectedSkills.filter((skill) => typeof skill === "string") | ||
| : [...SELECTABLE_SKILLS]; | ||
| return { | ||
@@ -119,5 +126,6 @@ version: data.version, | ||
| sourceType: typeof data.sourceType === "string" ? data.sourceType : "Unknown", | ||
| scope: data.scope === "skills" ? "skills" : "skills-memory", | ||
| scope, | ||
| platform: "codex", | ||
| hash: typeof data.hash === "string" ? data.hash : null, | ||
| selectedSkills, | ||
| }; | ||
@@ -148,3 +156,3 @@ } | ||
| platform: candidate.platform, | ||
| scope: meta.scope ?? "skills-memory", | ||
| scope: meta.scope, | ||
| }, | ||
@@ -244,10 +252,4 @@ metaDir: candidate.metaDir, | ||
| // ─── Misc helpers ───────────────────────────────────────────────────────────── | ||
| export function sleep(ms) { | ||
| return new Promise((resolve) => setTimeout(resolve, ms)); | ||
| } | ||
| export function formatMode(force) { | ||
| return force ? "update" : "install"; | ||
| } | ||
| // ─── Plan helpers ───────────────────────────────────────────────────────────── | ||
| export function summarizePlan(plan, scope) { | ||
| export function summarizePlan(plan) { | ||
| if (!plan) | ||
@@ -273,21 +275,3 @@ return null; | ||
| } | ||
| const scopeMeta = INSTALL_SCOPE_META[scope]; | ||
| const notes = [scopeMeta.summary, ...scopeMeta.details]; | ||
| return { total, steps, missing, notes }; | ||
| return { total, steps, missing }; | ||
| } | ||
| export function getStepIndexForScope(step) { | ||
| const normalized = step === "existing" || step === "confirm" ? "preview" : step; | ||
| const normalizedStep = STEP_ORDER.includes(normalized) ? normalized : "scope"; | ||
| const visibleSteps = [...STEP_ORDER]; | ||
| const currentIndex = Math.max(visibleSteps.indexOf(normalizedStep), 0); | ||
| return { visibleSteps, currentIndex }; | ||
| } | ||
| export function getScopeOptions() { | ||
| return Object.entries(INSTALL_SCOPE_META).map(([value, meta]) => ({ | ||
| value, | ||
| label: meta.label, | ||
| accent: meta.accent, | ||
| description: meta.summary, | ||
| details: meta.details, | ||
| })); | ||
| } |
+10
-19
@@ -10,22 +10,13 @@ --- | ||
| > This vault is maintained by AI agents. Open it in Obsidian to explore the knowledge graph. | ||
| > Graph view: color-coded by type — blue=decisions, green=patterns, yellow=learnings, purple=sessions, red=reviews. | ||
| > For maintainers: see [[README]] for why this root folder exists. | ||
| Human and Obsidian navigation only. Agent retrieval uses the context compiler, not this file. | ||
| ## Sessions | ||
| <!-- Orchestrator appends here: - [[sessions/YYYY-MM-DD-slug]] — one-line summary --> | ||
| - [[manifest]] — fixed-size project overview | ||
| - `cards/` — compact durable knowledge | ||
| - `decisions/` — architecture and product decisions | ||
| - `patterns/` — verified reusable implementation patterns | ||
| - `learnings/` — recurring project-specific gotchas | ||
| - `features/` — lightweight feature indexes | ||
| - `handoff/` — active continuation state | ||
| - `sessions/`, `reviews/`, `archive/` — cold history | ||
| ## Decisions | ||
| <!-- Planner/Researcher append here: - [[decisions/ADR-NNN-slug]] — one-line summary --> | ||
| ## Active Patterns | ||
| <!-- Coder/Designer append here: - [[patterns/slug]] — one-line summary --> | ||
| ## Learnings | ||
| <!-- Any agent appends here: - [[learnings/slug]] — one-line summary --> | ||
| ## Reviews | ||
| <!-- Code-reviewer/Security-auditor/UX-reviewer append here: - [[reviews/YYYY-MM-DD-type-slug]] — one-line summary --> | ||
| ## Features | ||
| <!-- Add manually or via Orchestrator: - [[features/slug]] — one-line summary --> | ||
| Do not append per-note links here. This map stays fixed-size as the project grows. |
+13
-5
@@ -1,9 +0,17 @@ | ||
| # Agent Memory Vault | ||
| # Agent memory vault | ||
| This folder is for agentic development context. | ||
| This vault stores durable project knowledge, not a transcript of every coding session. | ||
| agent-arche treats `memory/` as an Obsidian-style Markdown vault for durable project knowledge: decisions, implementation patterns, learnings, reviews, feature notes, and session summaries. Agents read it before work and update it before finishing so future Codex sessions can share the same project memory. | ||
| - `manifest.md` is the fixed-size project overview. | ||
| - `cards/`, `decisions/`, `patterns/`, `learnings/`, and `features/` hold durable facts. | ||
| - `handoff/` may hold active continuation state. | ||
| - `sessions/`, `reviews/`, and `archive/` are cold history and are not retrieved by default. | ||
| - `_MOC.md` is a fixed-size human/Obsidian map. Agents must not load it as task context. | ||
| This is not application runtime data, user content, or generated build output. Keep it in version control when your team wants AI assistants to share project context. | ||
| Retrieve context with: | ||
| Start with `_MOC.md` as the map of contents. | ||
| ```bash | ||
| node .codex/context/context.cjs query --task "<request>" --paths "<known paths>" | ||
| ``` | ||
| Create or update memory only when work adds a durable decision, constraint, reusable pattern, recurring gotcha, or explicit handoff. Routine fixes and facts already obvious from code need no note. |
| --- | ||
| id: DEC-NNN | ||
| title: "{{title}}" | ||
| kind: decision | ||
| status: active | ||
| date: {{date}} | ||
| type: decision | ||
| status: active | ||
| agent: {{agent}} | ||
| task: "{{task}}" | ||
| summary: "{{one-sentence decision}}" | ||
| tags: | ||
| - decision | ||
| aliases: [] | ||
| paths: [] | ||
| symbols: [] | ||
| supersedes: [] | ||
| last_verified_commit: "" | ||
| --- | ||
@@ -15,22 +18,16 @@ | ||
| ## Context | ||
| What is the situation that requires a decision? What problem does this solve? | ||
| ## Decision | ||
| ## Options Considered | ||
| {{decision}} | ||
| ### Option A — {{name}} | ||
| - **Pros:** | ||
| - **Cons:** | ||
| ## Why | ||
| ### Option B — {{name}} | ||
| - **Pros:** | ||
| - **Cons:** | ||
| {{reason and rejected alternative}} | ||
| ## Decision | ||
| What was chosen and the rationale behind it. | ||
| ## Revisit When | ||
| ## Consequences | ||
| What changes as a result of this decision? What does it make easier or harder? | ||
| {{observable trigger}} | ||
| ## Related | ||
| - [[sessions/YYYY-MM-DD-slug]] — session where this was decided | ||
| ## Evidence | ||
| - `path/to/file` |
| --- | ||
| title: "{{learning}}" | ||
| id: LRN-NNN | ||
| title: "{{title}}" | ||
| kind: learning | ||
| status: active | ||
| date: {{date}} | ||
| type: learning | ||
| status: active | ||
| agent: {{agent}} | ||
| task: "{{task}}" | ||
| summary: "{{one-sentence recurring gotcha}}" | ||
| tags: | ||
| - learning | ||
| aliases: [] | ||
| paths: [] | ||
| symbols: [] | ||
| last_verified_commit: "" | ||
| --- | ||
| # {{learning}} | ||
| # {{title}} | ||
| ## What Happened | ||
| The situation or task where this was discovered. | ||
| ## Signal | ||
| ## Root Cause | ||
| Why it happened — the underlying reason. | ||
| {{observable failure or confusion}} | ||
| ## Fix / Workaround | ||
| What solved it or how to work around it. | ||
| ## Cause | ||
| ## Prevention | ||
| How to avoid this in the future. What to check before doing X. | ||
| {{verified cause}} | ||
| ## Related | ||
| - [[sessions/YYYY-MM-DD-slug]] — session where this was discovered | ||
| ## Response | ||
| {{smallest reliable fix or check}} |
| --- | ||
| title: "{{pattern-name}}" | ||
| id: PAT-NNN | ||
| title: "{{title}}" | ||
| kind: pattern | ||
| status: active | ||
| date: {{date}} | ||
| type: pattern | ||
| status: active | ||
| agent: {{agent}} | ||
| summary: "{{one-sentence reusable pattern}}" | ||
| tags: | ||
| - pattern | ||
| aliases: [] | ||
| paths: [] | ||
| symbols: [] | ||
| last_verified_commit: "" | ||
| --- | ||
| # {{pattern-name}} | ||
| # {{title}} | ||
| ## When to Use | ||
| Conditions and context where this pattern applies. | ||
| ## Pattern | ||
| ## Implementation | ||
| {{pattern and when it applies}} | ||
| ```typescript | ||
| // the pattern | ||
| ``` | ||
| ## Evidence | ||
| ## Example in Codebase | ||
| `path/to/example/file.ts` — describe what it demonstrates. | ||
| - `path/to/file:line` | ||
| ## Anti-Patterns | ||
| What NOT to do and why. | ||
| ## Avoid | ||
| ## Related | ||
| - [[decisions/ADR-NNN-slug]] — why this pattern was adopted | ||
| - [[sessions/YYYY-MM-DD-slug]] — session where this was established | ||
| {{project-specific counter-pattern}} |
| --- | ||
| title: "{{title}}" | ||
| id: REV-{{date}}-NNN | ||
| title: "{{review title}}" | ||
| kind: review | ||
| status: cold | ||
| date: {{date}} | ||
| type: review | ||
| status: active | ||
| agent: {{agent}} | ||
| task: "{{task}}" | ||
| summary: "{{one-sentence review result}}" | ||
| tags: | ||
| - review | ||
| aliases: [] | ||
| paths: [] | ||
| --- | ||
| # {{title}} | ||
| # {{review title}} | ||
| ## Scope | ||
| What was reviewed — files, feature area, change type. | ||
| {{reviewed range}} | ||
| ## Findings | ||
| ### Critical / High | ||
| <!-- List only findings worth remembering long-term --> | ||
| {{evidence-backed findings}} | ||
| ### Medium / Low | ||
| <!-- List only findings worth remembering long-term --> | ||
| ## Overall Status | ||
| **Approved** / **Changes Required** / **Rejected** | ||
| ## Patterns to Follow | ||
| Links to the correct approaches for the issues raised. | ||
| ## Related | ||
| - [[sessions/YYYY-MM-DD-slug]] — session this review belongs to | ||
| Reviews are cold history. Promote only durable decisions or recurring gotchas into active cards. |
| --- | ||
| title: "{{task-name}}" | ||
| id: SES-{{date}}-NNN | ||
| title: "{{short handoff title}}" | ||
| kind: handoff | ||
| status: active | ||
| date: {{date}} | ||
| type: session | ||
| status: active | ||
| agent: orchestrator | ||
| task: "{{task}}" | ||
| summary: "{{one-sentence continuation state}}" | ||
| tags: | ||
| - session | ||
| aliases: [] | ||
| - handoff | ||
| paths: [] | ||
| --- | ||
| # {{task-name}} | ||
| # {{short handoff title}} | ||
| ## Objective | ||
| What was the user's verbatim request? | ||
| Use only when another session needs state that is not already captured by an issue, plan, commit, diff, or durable card. | ||
| ## Pipeline | ||
| `Agent A → Agent B → Agent C` | ||
| ## Continue From | ||
| ## Decisions Made | ||
| <!-- Link every decision note created during this session --> | ||
| {{current state and exact next action}} | ||
| ## Changes Made | ||
| | File | Change | Agent | | ||
| |------|--------|-------| | ||
| | `path/to/file` | description | coder | | ||
| ## Evidence | ||
| ## Issues Found | ||
| | Severity | Finding | Agent | Resolution | | ||
| |----------|---------|-------|------------| | ||
| - Commit/diff: {{reference}} | ||
| - Checks: {{commands and results}} | ||
| ## Patterns Established | ||
| <!-- Link any new pattern notes --> | ||
| ## Learnings | ||
| <!-- Link any new learning notes --> | ||
| ## Reviews | ||
| <!-- Link any review notes created --> | ||
| ## Related | ||
| <!-- Link to the feature this session belongs to, if any --> | ||
| Archive or delete after the continuation is complete. |
+11
-9
| { | ||
| "name": "agent-arche", | ||
| "version": "1.3.8", | ||
| "description": "Codex skills with optional hooks and project memory", | ||
| "version": "1.3.9", | ||
| "description": "Selective Codex skills, lean hooks, indexed memory, and optional multi-agent orchestration", | ||
| "keywords": [ | ||
@@ -9,3 +9,5 @@ "codex", | ||
| "hooks", | ||
| "memory" | ||
| "memory", | ||
| "orchestration", | ||
| "subagents" | ||
| ], | ||
@@ -38,4 +40,6 @@ "homepage": "https://github.com/AshenDulsanka/agent-arche#readme", | ||
| "memory/", | ||
| "dist/", | ||
| "templates/" | ||
| "orchestration/", | ||
| "docs/", | ||
| "NOTICE.md", | ||
| "dist/" | ||
| ], | ||
@@ -46,9 +50,7 @@ "engines": { | ||
| "dependencies": { | ||
| "@inkjs/ui": "^2.0.0", | ||
| "ink": "^5.1.0", | ||
| "react": "^18.3.1" | ||
| "@clack/prompts": "^1.2.0", | ||
| "picocolors": "^1.1.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^22.15.21", | ||
| "@types/react": "^18.3.18", | ||
| "tsx": "^4.20.6", | ||
@@ -55,0 +57,0 @@ "typescript": "^5.8.3" |
+140
-82
@@ -1,4 +0,4 @@ | ||
| # agent-arche | ||
| # Agent Arche | ||
| A Codex skills harness with reusable engineering workflows, optional safety hooks, and project memory (obsidian memory). | ||
| A selective Codex engineering harness: install only the skills a project needs, optionally add lean hooks and indexed memory, or opt into explicit multi-agent orchestration. | ||
@@ -11,12 +11,43 @@ ## Install | ||
| Run it from the project root and choose a scope: | ||
| The interactive CLI first selects an installation scope, then asks for the project type and stage. Those answers determine which grouped skills are initially checked; the user can still select or deselect anything before installation. | ||
| | Scope | Installs | | ||
| | Scope | Installs | Runtime posture | | ||
| |---|---|---| | ||
| | Skills only | Selected `.agents/skills/` and `.codex/agent-arche.json` | Lowest overhead | | ||
| | Skills + hooks | Selected skills, config, safety hooks, context compiler | Lean established-project setup | | ||
| | Skills + hooks + memory | Above plus indexed durable `memory/` | Recommended | | ||
| | Full orchestration | Above plus orchestrator and specialized Codex agents | Highest usage | | ||
| > **Usage warning:** Full orchestration costs substantially more usage than a comparable single-agent run. Every subagent performs separate model and tool work. Choose it only when independent workstreams, noisy exploration, tests, or review benefit from delegation. | ||
| The installation metadata always lives at `.codex/agent-arche.json`, including Skills-only installs. | ||
| ### Non-interactive examples | ||
| ```bash | ||
| npx agent-arche --skills-only --select-skills=implement,git --yes | ||
| npx agent-arche --skills-hooks --project-type=backend --project-stage=established --yes | ||
| npx agent-arche --skills-hooks --select-skills=implement,design,code-review --yes | ||
| npx agent-arche --skills-memory --all-skills --yes | ||
| npx agent-arche --orchestration --select-skills=implement,security-review --yes | ||
| ``` | ||
| The skill step opens with recommendations computed from the installation scope, project type, and project stage. Users can deselect any recommendation or select additional frontend, backend, planning, architecture, and delivery skills before installation. `--yes` accepts the computed recommendations when no explicit skill selection is supplied. | ||
| The selector keeps a fixed-height, cursor-following viewport. Use `↑`/`↓` to move, `←`/`→` to collapse or expand groups, `Space` to toggle a skill or group, `Enter` to confirm, and `Esc` to cancel. | ||
| | Input | Recommendation effect | | ||
| |---|---| | ||
| | Skills + hooks + memory | Skills, hooks, activation/config files, and memory | | ||
| | Skills only | `.agents/skills/` plus `.agents/agent-arche.json` install metadata | | ||
| | Every project | Core implementation, diagnosis, testing, review, security, Git, and handoff skills | | ||
| | Full-stack | Adds frontend design, SEO, and PostgreSQL workflows | | ||
| | Backend | Adds PostgreSQL workflows; leaves frontend skills unchecked | | ||
| | Frontend | Adds design and SEO workflows; leaves backend-specific skills unchecked | | ||
| | General / other | Keeps only the applicable core set | | ||
| | Greenfield | Adds `project-startup` | | ||
| | Established | Leaves `project-startup` unchecked | | ||
| | Full orchestration | Adds planning and architecture workflows used by delegated work | | ||
| After installation, run `project-startup` once. In Skills-only workspaces it configures only available project context and does not create a memory vault. | ||
| These are checkbox defaults, not locked bundles. Use `--project-type` and `--project-stage` for non-interactive profile selection, or `--select-skills` to override recommendations with an exact set. | ||
| Update later with: | ||
| Update the files managed by the recorded scope and skill selection with: | ||
@@ -27,82 +58,111 @@ ```bash | ||
| ## Skill Model | ||
| Legacy metadata under `.agents/` is still detected; the next update writes canonical metadata under `.codex/`. | ||
| The skills use progressive disclosure: Codex sees concise names and descriptions first, then reads a full `SKILL.md` only when its trigger matches. The default engineering chain is: | ||
| ## How it works | ||
| ```text | ||
| request/spec/ticket | ||
| -> implement | ||
| -> tdd when a useful behavior seam exists | ||
| -> design for UI/UX work | ||
| -> postgres-patterns only for confirmed PostgreSQL work | ||
| -> seo only for search-specific work | ||
| -> security-review only for trust-boundary changes | ||
| -> code-review after non-trivial edits | ||
| -> git or handoff only when requested | ||
| ```mermaid | ||
| flowchart LR | ||
| A[User request] --> B{Installed scope} | ||
| B -->|Skills| C[Matching selected skill] | ||
| B -->|Hooks| D[Lean SessionStart policy] | ||
| B -->|Memory| E[Context compiler] | ||
| B -->|Full orchestration| F[Orchestrator] | ||
| D --> C | ||
| E --> G[Top ranked context snippets] | ||
| G --> C | ||
| F --> H[Bounded specialized subagents] | ||
| H --> I[Evidence summaries] | ||
| I --> F | ||
| C --> J[Scoped implementation and checks] | ||
| F --> J | ||
| ``` | ||
| Other entry paths: | ||
| The harness follows progressive disclosure: | ||
| ```text | ||
| existing plan -> grill-with-docs -> to-spec -> to-tickets -> implement | ||
| broken behavior -> diagnosing-bugs -> implement -> code-review | ||
| architecture friction -> improve-codebase-architecture -> codebase-design -> grill-with-docs | ||
| ``` | ||
| 1. Codex sees only installed skill names and descriptions. | ||
| 2. It loads a skill body only when its trigger matches. | ||
| 3. The context compiler ranks project evidence deterministically. | ||
| 4. The model receives at most a small task-specific context pack rather than a full memory map. | ||
| 5. Full orchestration delegates only tasks that divide cleanly. | ||
| Chaining is conditional. It never authorizes automatic commits, pushes, PRs, external issue creation, or sub-agent spawning. | ||
| ## Selectable skills | ||
| ## Skill Routing | ||
| | Skill | Trigger | | ||
| | Group | Skills | | ||
| |---|---| | ||
| | `project-startup` | First run or missing `docs/agents/` context; seeds memory only in memory-capable scopes | | ||
| | `implement` | Any requested code change; central router and completion workflow | | ||
| | `diagnosing-bugs` | Broken, failing, throwing, flaky, or slow behavior that needs root-cause diagnosis | | ||
| | `tdd` | Test-first work or behavior changes with a useful public seam | | ||
| | `code-review` | Branch, PR, commit-range, or working-tree review; post-implementation quality gate | | ||
| | `security-review` | Auth, authorization, secrets, untrusted input, routes, SQL, files/uploads, redirects, SSRF, serialization, payments, sensitive data, or explicit vulnerability audit | | ||
| | `grill-with-docs` | Pressure-test a plan against existing code and documented decisions | | ||
| | `to-spec` | Explicitly synthesize the current discussion into a spec; implicit invocation is disabled | | ||
| | `to-tickets` | Split approved work into independently verifiable vertical tickets | | ||
| | `codebase-design` | Design deep modules, interfaces, seams, and test surfaces | | ||
| | `improve-codebase-architecture` | Find and explore architecture-deepening opportunities | | ||
| | `design` | UI/UX direction, implementation, redesign, audit, motion, and performance | | ||
| | `postgres-patterns` | Confirmed PostgreSQL schemas, SQL, migrations, RLS, indexes, pooling, or query plans | | ||
| | `seo` | Crawlability, metadata, structured data, search performance, web vitals, and internal linking | | ||
| | `git` | Branch, commit, PR, squash, or release-ready Git work | | ||
| | `handoff` | Compact continuation context for another session or person | | ||
| | `karpathy-guidelines` | Default implementation and review guardrails for assumptions, scope, simplicity, and verification | | ||
| | `caveman` | Explicit compressed communication mode | | ||
| | Core engineering | `implement`, `diagnosing-bugs`, `tdd`, `code-review`, `karpathy-guidelines` | | ||
| | Security and backend | `security-review`, `postgres-patterns` | | ||
| | Frontend and growth | `design`, `seo` | | ||
| | Planning and architecture | `project-startup`, `to-spec`, `to-tickets`, `grill-with-docs`, `codebase-design`, `improve-codebase-architecture` | | ||
| | Delivery and communication | `git`, `handoff`, `caveman` | | ||
| `api-design` and `coding-standards` were removed. Their generic rules overlapped repo instructions, `implement`, `code-review`, and `security-review`, while loading extra context on common tasks. API shape should follow the project's actual conventions; trust-boundary checks belong in the security workflow. | ||
| `project-startup` is optional. Install it only for a new or unconfigured project. Established projects can omit it entirely. | ||
| ## Security | ||
| Full orchestration automatically installs the `orchestrate` routing skill in addition to the user selection. | ||
| `security-review` is a portable, evidence-first workflow. It scopes assets and trust boundaries, selects only applicable OWASP-style threats, runs safe local proofs, and separates confirmed vulnerabilities from defense-in-depth suggestions. | ||
| Every shipped skill includes `agents/openai.yaml` for consistent desktop UI labels and explicit invocation prompts. Codex routing still depends on the concise `name` and `description` in `SKILL.md`; the optional UI metadata is not a replacement for those trigger fields. | ||
| The skill does not claim an entire application is secure from a partial diff review and does not authorize testing production or third-party systems. | ||
| ## Lean hooks | ||
| ## Codex Files | ||
| The hook bundle deliberately contains only: | ||
| Codex automatically discovers durable instructions from: | ||
| - `SessionStart`: a short instruction to use matching installed skills and the context compiler. | ||
| - `PreToolUse`: a narrow blocklist for catastrophic shell operations. | ||
| - global `~/.codex/AGENTS.md` or `AGENTS.override.md`; | ||
| - project `AGENTS.md` or `AGENTS.override.md`, walking from repo root to the current directory; | ||
| - fallback filenames explicitly listed in `project_doc_fallback_filenames`. | ||
| It does not force Caveman, preload Karpathy, read `_MOC.md`, write memory after every task, or run per-prompt/post-tool instrumentation. | ||
| `.codex/instructions.md` is not a special auto-discovered filename. This package does not create a parallel instruction system. Put durable project rules in `AGENTS.md`; use nested `AGENTS.md` or `AGENTS.override.md` for subtree-specific rules. | ||
| Review and trust project hooks in Codex with `/hooks`. | ||
| Each skill must contain `SKILL.md` with `name` and `description`. `agents/openai.yaml` is optional. Use it when UI metadata, invocation policy, or tool dependencies add value; it is not required just because the skill runs in Codex. This repo uses it selectively, including disabling implicit invocation for `to-spec`. | ||
| ## Indexed memory | ||
| Project-scoped runtime settings, MCP servers, hooks, model defaults, and sandbox behavior belong in `.codex/config.toml`, not in skill prose. | ||
| ```mermaid | ||
| flowchart TD | ||
| Q[Task and known paths] --> R[context.cjs query] | ||
| M[Fixed-size manifest] --> R | ||
| C[Durable cards and ADRs] --> R | ||
| B[arche: breadcrumbs] --> R | ||
| X[Sessions, reviews, archive, MOC] -. cold by default .-> R | ||
| R --> K[Top 3-6 ranked snippets] | ||
| K --> A[Agent context] | ||
| ``` | ||
| ## Runtime Pieces | ||
| Use: | ||
| | Piece | Purpose | | ||
| ```bash | ||
| node .codex/context/context.cjs index --refresh | ||
| node .codex/context/context.cjs query --task "fix session expiry" --paths "src/auth/session.ts" | ||
| node .codex/context/context.cjs check | ||
| node .codex/context/context.cjs harvest | ||
| ``` | ||
| Memory is written only for durable novelty: decisions, cross-cutting constraints, reusable patterns, recurring gotchas, or an explicit unfinished handoff. `_MOC.md` stays fixed-size and exists for humans/Obsidian, not model retrieval. | ||
| See [Memory design](docs/MEMORY.md) and [Architecture](docs/ARCHITECTURE.md). | ||
| ## Full orchestration | ||
| Full orchestration installs project-scoped agents under `.codex/agents/`: | ||
| - `orchestrator` | ||
| - `code_explorer` | ||
| - `implementation_worker` | ||
| - `reviewer` | ||
| - `test_runner` | ||
| The orchestrator starts with at most three concurrent subagents, favors parallel read-heavy work, prevents overlapping write ownership, and requires evidence summaries rather than raw logs. Simple tasks remain single-agent. | ||
| See [Orchestration](docs/ORCHESTRATION.md). | ||
| ## Codex files | ||
| | Path | Purpose | | ||
| |---|---| | ||
| | Hooks | Compact session context and a narrow pre-command blocklist for catastrophic shell operations | | ||
| | Memory | Repo-local durable decisions and patterns for the Skills + hooks + memory scope | | ||
| | `.codex/config.toml` | Codex runtime and disabled MCP examples without committed secrets | | ||
| | `.agents/skills/` | Only selected skill packages | | ||
| | `.codex/agent-arche.json` | Canonical installed scope and skill selection | | ||
| | `.codex/config.toml` | Runtime settings for hook-enabled scopes | | ||
| | `.codex/hooks.json`, `.codex/hooks/` | Lean lifecycle hooks | | ||
| | `.codex/context/` | Deterministic context index/query compiler | | ||
| | `.codex/agents/` | Full-orchestration custom agents only | | ||
| | `memory/` | Indexed durable project knowledge | | ||
| The hook bundle uses `SessionStart` for low-cost durable context and `PreToolUse` only for destructive shell blocking. It deliberately avoids per-prompt and post-tool hooks. Project hooks run only in trusted projects and changed hook definitions must be reviewed in Codex with `/hooks`. | ||
| Durable project instructions belong in applicable `AGENTS.md` files. `.codex/instructions.md` is not used as a parallel instruction system. | ||
@@ -113,23 +173,21 @@ ## Validate | ||
| npm run check | ||
| npm run build:dist | ||
| npm pack --dry-run | ||
| ``` | ||
| The check typechecks the installer. Skill structure should be reviewed when skills are added or renamed. | ||
| ## Credits | ||
| ## Manual Setup | ||
| Agent Arche adapts and combines ideas from several open-source projects. The installed workflows may differ materially from their upstream versions: | ||
| For Skills only, copy `skills/` to `.agents/skills/`. For Skills + hooks + memory, also copy `codex/config.toml`, `codex/hooks.json`, `codex/hooks/`, and `memory/` to their matching project paths. | ||
| - [mattpocock/skills](https://github.com/mattpocock/skills) — engineering workflow and deep-module skill foundations. | ||
| - [vercel-labs/skills](https://github.com/vercel-labs/skills) — reference for the redesigned interactive skill-selection CLI experience. | ||
| - [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail) — inspiration for sparse, machine-searchable source breadcrumbs and deterministic harvesting. | ||
| - [cyxzdev/Uncodixfy](https://github.com/cyxzdev/Uncodixfy), [pbakaus/impeccable](https://github.com/pbakaus/impeccable), and [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill) — design workflow inspiration. | ||
| - [JuliusBrussee/caveman](https://github.com/JuliusBrussee/caveman) — explicit compressed communication mode. | ||
| - [Akindu23/my-agent-skills](https://github.com/Akindu23/my-agent-skills) — Karpathy-style coding guardrails and PostgreSQL patterns. | ||
| - [Clack](https://github.com/bombshell-dev/clack) and [picocolors](https://github.com/alexeyraspopov/picocolors) — CLI prompt and terminal-color libraries. | ||
| - [Official OpenAI Codex documentation](https://learn.chatgpt.com/docs/agent-configuration/subagents) — custom-agent configuration and subagent usage boundaries. | ||
| The CLI is preferred because it applies the correct workspace-flavor boundaries. | ||
| See upstream repositories and this package's dependency lockfile for their licenses. Agent Arche itself is MIT licensed. | ||
| Older Full or Small installations are migrated to the Skills + hooks + memory scope in package metadata. Updates stop managing custom-agent, instruction, rule, and generated `AGENTS.md` files; existing project copies are left untouched so local customizations are never deleted automatically. | ||
| ## Credits | ||
| Built on top of excellent open-source work: | ||
| Some of the referenced skills have been adapted, renamed, combined, or otherwise updated to fit this Codex harness. They may therefore differ from their upstream versions; the links below credit the original projects and inspiration rather than implying that every skill remains an unchanged copy. | ||
| - [mattpocock/skills](https://github.com/mattpocock/skills): engineering workflow and deep-module skills. | ||
| - [cyxzdev/Uncodixfy](https://github.com/cyxzdev/Uncodixfy), [pbakaus/impeccable](https://github.com/pbakaus/impeccable), and [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill): design workflows. | ||
| - [JuliusBrussee/caveman](https://github.com/JuliusBrussee/caveman): compressed communication. | ||
| - [Akindu23/my-agent-skills](https://github.com/Akindu23/my-agent-skills): Karpathy-style guardrails and PostgreSQL patterns. | ||
| The component-by-component attribution map is in [NOTICE.md](NOTICE.md). |
@@ -7,4 +7,4 @@ --- | ||
| wenyan-lite, wenyan-full, wenyan-ultra. | ||
| Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens", | ||
| "be brief", or invokes $caveman. Also auto-triggers when token efficiency is requested. | ||
| Use only when the user explicitly says "caveman mode", "talk like caveman", "use caveman", | ||
| or invokes $caveman. Do not trigger merely because the user asks about token efficiency. | ||
| --- | ||
@@ -11,0 +11,0 @@ |
@@ -30,7 +30,7 @@ --- | ||
| - Read `AGENTS.md`, applicable nested instructions, and relevant contribution or architecture docs. | ||
| - Load `karpathy-guidelines` for scope discipline, assumptions, simplicity, surgical changes, and verification quality. | ||
| - Read the originating issue, spec, PRD, acceptance criteria, or user request. If tracker context is required but `docs/agents/issue-tracker.md` is missing, use `project-startup`. | ||
| - If installed, load `karpathy-guidelines` for scope discipline, assumptions, simplicity, surgical changes, and verification quality. Otherwise apply those review checks directly. | ||
| - Read the originating issue, spec, PRD, acceptance criteria, or user request. If tracker context is required but `docs/agents/issue-tracker.md` is missing, use `project-startup` only when it is installed; otherwise ask only for the missing tracker fact needed by this review. | ||
| - Inspect affected callers, tests, schemas, and sibling paths when the same failure could recur. | ||
| - Load `postgres-patterns` only for confirmed PostgreSQL changes. | ||
| - Load `security-review` only for auth, authorization, secrets, untrusted input, routes, SQL, file I/O, uploads, redirects, external calls, serialization, payments, or sensitive data. | ||
| - Load `postgres-patterns` only when it is installed and the change is confirmed PostgreSQL work. | ||
| - Load `security-review` only when it is installed and the change touches auth, authorization, secrets, untrusted input, routes, SQL, file I/O, uploads, redirects, external calls, serialization, payments, or sensitive data. If it is not installed, keep security-sensitive observations bounded and recommend a dedicated security review rather than claiming one was performed. | ||
@@ -37,0 +37,0 @@ ## 3. Standards axis |
| # Design It Twice | ||
| When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best. | ||
| When the user wants to explore alternative interfaces for a chosen deepening candidate, use this variant-comparison pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best. | ||
@@ -11,3 +11,3 @@ Uses the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**, **leverage**. | ||
| Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate: | ||
| Before generating variants, write a user-facing explanation of the problem space for the chosen candidate: | ||
@@ -18,18 +18,18 @@ - The constraints any new interface would need to satisfy | ||
| Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel. | ||
| Show this to the user, then immediately proceed to Step 2. | ||
| ### 2. Spawn sub-agents | ||
| ### 2. Generate independent variants | ||
| Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module. | ||
| Produce three **radically different** interfaces. Default to generating them serially in the current agent. Spawn sub-agents only when the user explicitly requested delegation or parallel agents **and** `.codex/agent-arche.json` says the project uses the `orchestration` scope. This preserves the design comparison without silently multiplying usage. | ||
| Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint: | ||
| Use a separate technical brief for each variant (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). When sub-agents are authorized, send only that brief rather than the full conversation. Give each variant a different design constraint: | ||
| - Agent 1: "Minimize the interface — aim for 1–3 entry points max. Maximise leverage per entry point." | ||
| - Agent 2: "Maximise flexibility — support many use cases and extension." | ||
| - Agent 3: "Optimise for the most common caller — make the default case trivial." | ||
| - Agent 4 (if applicable): "Design around ports & adapters for cross-seam dependencies." | ||
| - Variant 1: "Minimize the interface — aim for 1–3 entry points max. Maximise leverage per entry point." | ||
| - Variant 2: "Maximise flexibility — support many use cases and extension." | ||
| - Variant 3: "Optimise for the most common caller — make the default case trivial." | ||
| - Variant 4 (if applicable): "Design around ports & adapters for cross-seam dependencies." | ||
| Include both [SKILL.md](SKILL.md) vocabulary and CONTEXT.md vocabulary in the brief so each sub-agent names things consistently with the architecture language and the project's domain language. | ||
| Include both [SKILL.md](SKILL.md) vocabulary and CONTEXT.md vocabulary in the brief so each variant uses the architecture and project domain language consistently. | ||
| Each sub-agent outputs: | ||
| Each variant outputs: | ||
@@ -46,2 +46,2 @@ 1. Interface (types, methods, params — plus invariants, ordering, error modes) | ||
| After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu. | ||
| After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu. |
| # Heuristics Scoring Reference | ||
| ## Contents | ||
| - [Scoring rubric](#nielsens-10-usability-heuristics--scoring-rubric) | ||
| - [Total score interpretation](#total-score-interpretation) | ||
| - [Issue severity scale](#issue-severity-scale) | ||
| ## Nielsen's 10 Usability Heuristics — Scoring Rubric | ||
@@ -4,0 +10,0 @@ |
| # Personas Reference | ||
| ## Contents | ||
| - [Five core testing personas](#five-core-testing-personas) | ||
| - [Persona selection guide](#persona-selection-guide) | ||
| - [Project-specific personas](#project-specific-personas) | ||
| ## Five Core Testing Personas | ||
@@ -4,0 +10,0 @@ |
| # Stitch Design Taste — Semantic Design System Skill | ||
| ## Contents | ||
| - [Overview and prerequisites](#overview) | ||
| - [Goal](#the-goal) | ||
| - [Analysis and synthesis](#analysis--synthesis-instructions) | ||
| - [Output format](#output-format-designmd-structure) | ||
| - [Best practices](#best-practices) | ||
| - [Common pitfalls](#common-pitfalls-to-avoid) | ||
| ## Overview | ||
@@ -4,0 +13,0 @@ This skill generates `DESIGN.md` files optimized for Google Stitch screen generation. It translates the battle-tested anti-slop frontend engineering directives into Stitch's native semantic design language — descriptive, natural-language rules paired with precise values that Stitch's AI agent can interpret to produce premium, non-generic interfaces. |
| Analyze a feature and strategically add animations and micro-interactions that enhance understanding, provide feedback, and create delight. | ||
| ## Contents | ||
| - [Motion scope](#motion-scope) | ||
| - [Assess opportunities](#assess-animation-opportunities) | ||
| - [Plan strategy](#plan-animation-strategy) | ||
| - [Implement](#implement-animations) | ||
| - [Technical implementation](#technical-implementation) | ||
| - [Verify quality](#verify-quality) | ||
| --- | ||
@@ -4,0 +13,0 @@ |
| Deliver expert-level design critique through structured heuristic evaluation and cognitive load analysis. | ||
| ## Contents | ||
| - [Preparation](#mandatory-preparation) | ||
| - [Gather context](#step-1-gather-context) | ||
| - [Dual assessment](#step-2-dual-assessment) | ||
| - [Combined report](#step-3-combined-report) | ||
| - [Questions and actions](#step-4-ask-targeted-questions) | ||
| ## Mandatory Preparation | ||
@@ -4,0 +12,0 @@ |
@@ -7,2 +7,15 @@ > **Framework note:** Examples default to React/Next.js. Adapt all framework-specific code to the project's actual stack (SvelteKit, Vue, Angular, etc.). | ||
| ## Contents | ||
| - [Direction](#0a-direction-first) | ||
| - [Design thinking](#2-design-thinking-before-any-code) | ||
| - [Architecture and conventions](#3-technical-architecture--conventions) | ||
| - [Color and theme](#4-color--theme-system) | ||
| - [Engineering directives](#5-design-engineering-directives) | ||
| - [Motion](#6-motion--animation) | ||
| - [Banned patterns](#7-hard-no--banned-patterns) | ||
| - [Performance](#8-performance-guardrails) | ||
| - [Creative patterns](#10-the-creative-arsenal) | ||
| - [Pre-flight checklist](#12-final-pre-flight-checklist) | ||
| --- | ||
@@ -9,0 +22,0 @@ |
@@ -5,2 +5,10 @@ > **Framework note:** This skill is framework-agnostic. All CSS and layout examples apply universally. Adapt any component syntax to your project's actual stack. | ||
| ## Contents | ||
| - [Workflow](#how-this-works) | ||
| - [Design audit](#design-audit) | ||
| - [Upgrade techniques](#upgrade-techniques) | ||
| - [Fix priority](#fix-priority) | ||
| - [Rules](#rules) | ||
| ## How This Works | ||
@@ -175,2 +183,2 @@ | ||
| - If the project has no framework, use vanilla CSS. | ||
| - Keep changes reviewable and focused. Small, targeted improvements over big rewrites. | ||
| - Keep changes reviewable and focused. Small, targeted improvements over big rewrites. |
| Systematically score and identify quality issues across five dimensions to create an actionable improvement plan. | ||
| ## Contents | ||
| - [Audit framework](#audit-framework) | ||
| - [Severity scale](#severity-scale) | ||
| - [Scoring table](#scoring-table) | ||
| - [Report structure](#audit-report-structure) | ||
| ## Audit Framework | ||
@@ -4,0 +11,0 @@ |
| Identify and fix performance issues to create faster, smoother user experiences. | ||
| ## Contents | ||
| - [Assess issues](#assess-performance-issues) | ||
| - [Optimization strategy](#optimization-strategy) | ||
| - [Core Web Vitals](#core-web-vitals-optimization) | ||
| - [Monitoring](#performance-monitoring) | ||
| - [Verification](#verify-improvements) | ||
| ## Assess Performance Issues | ||
@@ -4,0 +12,0 @@ |
@@ -134,4 +134,4 @@ --- | ||
| **Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling), hand off to `improve-codebase-architecture` with the specifics. Make the recommendation **after** the fix is in, not before — you have more information now than when you started. | ||
| **Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling), hand off to `improve-codebase-architecture` with the specifics when that skill is installed; otherwise record the bounded architectural recommendation in the result. Make the recommendation **after** the fix is in, not before — you have more information now than when you started. | ||
| For an authorized fix, follow the `implement` workflow from the regression-test step onward. Load `security-review` when the root cause or fix touches a trust boundary. Finish with `code-review` for non-trivial changes. Do not commit unless the user requested it. | ||
| For an authorized fix, follow the `implement` workflow from the regression-test step onward when it is installed; otherwise continue with the same scoped edit-and-verify loop directly. Load `security-review` when it is installed and the root cause or fix touches a trust boundary. Finish with `code-review` for non-trivial changes when it is installed, or perform a focused Standards and Spec pass directly. Do not commit unless the user requested it. |
@@ -8,2 +8,11 @@ --- | ||
| ## Contents | ||
| - [Format and types](#format) | ||
| - [Scopes](#scopes) | ||
| - [Subject rules](#subject-line-rules) | ||
| - [Breaking changes](#breaking-changes) | ||
| - [Body and footer](#body) | ||
| - [Examples](#examples) | ||
| All commits in this repository must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification. | ||
@@ -10,0 +19,0 @@ |
@@ -6,3 +6,3 @@ --- | ||
| Write a handoff document summarising the current conversation so a fresh agent can continue the work. Save to the `/memory/handoff` directory of the current workspace, if that folder is not there make sure to create it in `/docs/handoff` and delete it after the task is done. | ||
| Write a handoff document summarising the current conversation so a fresh agent can continue the work. Read `.codex/agent-arche.json`: use `memory/handoff/` only for `skills-memory` or `orchestration` scopes. For `skills` or `skills-hooks`, use the OS temporary directory unless the repository already has an explicit handoff convention. Delete temporary handoffs after continuation. | ||
@@ -13,4 +13,6 @@ Include a compact "Suggested skills" section containing only skills whose trigger conditions match the remaining work. Prefer the normal chain: `diagnosing-bugs` for unresolved bugs, `implement` for edits, `code-review` after non-trivial edits, and domain skills only when applicable. | ||
| Create a handoff only when unfinished state would otherwise be lost. Completed routine work needs no handoff or memory note. | ||
| Redact any sensitive information, such as API keys, passwords, or personally identifiable information. | ||
| If the user passed arguments, treat them as a description of what the next session will focus on and tailor the doc accordingly. |
@@ -12,5 +12,5 @@ --- | ||
| Load `karpathy-guidelines` and follow it throughout implementation. Use `tdd` where meaningful, at pre-agreed public seams. | ||
| If installed, load `karpathy-guidelines` and follow it throughout implementation. If installed, use `tdd` where meaningful, at pre-agreed public seams. Missing companion skills are not blockers; apply the same principles directly from the repository instructions and task evidence. | ||
| Load only the additional skill that matches the work: | ||
| Load only an installed additional skill that matches the work: | ||
@@ -22,4 +22,6 @@ - `design` for UI/UX changes. | ||
| Run typechecking and focused tests regularly, then the relevant broader checks once at the end. Use `code-review` after non-trivial work. | ||
| Run typechecking and focused tests regularly, then the relevant broader checks once at the end. Use `code-review` after non-trivial work when it is installed; otherwise perform the same focused Standards and Spec pass directly. | ||
| Do not commit, push, publish, or open a PR unless the user requested it. Load `git` or `handoff` only when that action is requested. | ||
| Leave an `arche:` source breadcrumb only when a deliberate, non-obvious constraint would otherwise look like a bug to a future agent and names, tests, or ordinary comments cannot express it. Keep it to one searchable line, link a durable memory ID when one exists, and run `node .codex/context/context.cjs index --refresh`. Do not add agent commentary to routine code. | ||
| Do not commit, push, publish, or open a PR unless the user requested it. Load `git` or `handoff` only when that action is requested and the skill is installed; otherwise follow the repository's documented workflow directly. |
| # HTML Report Format | ||
| ## Contents | ||
| - [Scaffold](#scaffold) | ||
| - [Header and candidate cards](#header) | ||
| - [Diagram patterns](#diagram-patterns) | ||
| - [Style guidance](#style-guidance) | ||
| - [Top recommendation](#top-recommendation-section) | ||
| - [Tone](#tone) | ||
| The architectural review is rendered as a single self-contained HTML file in the OS temp directory. Tailwind and Mermaid both come from CDNs. Mermaid handles graph-shaped diagrams reliably; hand-built divs and inline SVG handle the more editorial visuals (mass diagrams, cross-sections). Mix the two — don't lean on Mermaid for everything, it'll start to look generic. | ||
@@ -4,0 +13,0 @@ |
@@ -12,3 +12,3 @@ --- | ||
| - Load `codebase-design` for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use these terms exactly in every suggestion — don't drift into "component," "service," "API," or "boundary." | ||
| - If installed, load `codebase-design` for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Otherwise use the vocabulary and deletion-test definitions in this skill without blocking the review. Use these terms exactly in every suggestion — don't drift into "component," "service," "API," or "boundary." | ||
| - The domain language in `CONTEXT.md` gives names to good seams; ADRs in `docs/adr/` record decisions this command should not re-litigate. | ||
@@ -64,3 +64,3 @@ | ||
| Once the user picks a candidate, load `grill-with-docs` to walk the decision tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, and what tests survive. | ||
| Once the user picks a candidate, load `grill-with-docs` when it is installed to walk the decision tree with them. Otherwise ask the same minimum decision questions directly: constraints, dependencies, the shape of the deepened module, what sits behind the seam, and what tests survive. | ||
@@ -72,2 +72,2 @@ Keep domain documentation current as decisions crystallize, following `docs/agents/domain.md` when present: | ||
| - **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. | ||
| - **Want to explore alternative interfaces for the deepened module?** Use `codebase-design` and its design-it-twice pattern. Parallel variants are optional and depend on the active environment. | ||
| - **Want to explore alternative interfaces for the deepened module?** Use `codebase-design` and its design-it-twice pattern when installed; otherwise produce two distinct interface sketches directly. Parallel variants require both explicit user authorization and the full-orchestration scope. |
| interface: | ||
| display_name: "Project Startup" | ||
| short_description: "Configure project context for agent-arche skills" | ||
| default_prompt: "Set up agent-arche context for the current project without overwriting existing conventions." | ||
| short_description: "Configure context for a new Agent Arche project" | ||
| default_prompt: "Use $project-startup to configure this new project without replacing existing conventions." |
| --- | ||
| name: project-startup | ||
| description: First-run setup for agent-arche projects. Analyze a codebase, seed repo memory when the installed workspace includes it, and configure docs/agents issue-tracker, triage-label, and domain context used by chained engineering skills. Use after installation or when that context is missing; in Skills-only workspaces, skip memory and configure only the available project context. | ||
| description: Optional first-run setup for a new or unconfigured Agent Arche project. Build a fixed-size manifest, create only evidenced durable memory cards when memory is installed, and configure docs/agents context. Do not use for established projects whose agent context is already configured. | ||
| --- | ||
| # Project Startup Skill | ||
| # Project startup | ||
| ## Purpose | ||
| Use this once for a new or genuinely unconfigured project. Stop immediately when existing project instructions, `docs/agents/`, and the selected workspace features already provide sufficient context. | ||
| This is the single first-run entry point for a newly installed agent-arche project. | ||
| ## 1. Detect the installed scope | ||
| It does two jobs in one skill folder: | ||
| Read `.codex/agent-arche.json`. | ||
| 1. In memory-capable installs, analyze the codebase, interview the user only for missing context, and seed durable project memory with decisions, patterns, learnings, and a feature index. Skip this job in Skills-only installs. | ||
| 2. Configure the engineering skill context in `docs/agents/` so skills know this repo's issue tracker workflow, triage label vocabulary, and domain documentation layout. | ||
| - `skills`: no hooks or memory setup. | ||
| - `skills-hooks`: configure project docs only; no memory. | ||
| - `skills-memory` or `orchestration`: configure project docs and indexed memory. | ||
| Do not split this flow into separate public skills. Keep startup discoverable as one action. | ||
| Do not create features the selected scope did not install. | ||
| **Do not start writing memory notes until the investigation and interview phases are complete.** Premature notes will be wrong and mislead future agents. | ||
| ## 2. Investigate before asking | ||
| --- | ||
| Read only existing entry points relevant to configuration: | ||
| ## Phase 0 - Startup State Check | ||
| - Applicable `AGENTS.md` | ||
| - `README.md` and the package manifest | ||
| - Top-level application/source layout | ||
| - Existing architecture, domain, issue, and contribution docs | ||
| - Git remote and existing issue templates | ||
| Before doing setup work, inspect the repo and decide what is already done. | ||
| Determine purpose, users, stack, canonical checks, current focus, tracker, domain-doc layout, and durable constraints. Ask concise questions only for decisions the repository cannot answer. Never use quotas for decisions, patterns, features, or gotchas. | ||
| ### 0.1 Resolve memory vault | ||
| ## 3. Configure engineering context | ||
| First determine the workspace flavor from `.codex/agent-arche.json` when available, otherwise from the installed directories. Skills + hooks + memory is memory-capable. Skills only is not. Treat legacy Full or Small metadata as Skills + hooks + memory. | ||
| Create or update only what is missing: | ||
| For Skills only, mark memory bootstrap as not applicable and continue to the engineering-context check. Do not create `memory/`. | ||
| - `docs/agents/issue-tracker.md` | ||
| - `docs/agents/triage-labels.md` | ||
| - `docs/agents/domain.md` | ||
| - An `## Agent skills` block in an existing root `AGENTS.md` | ||
| For a memory-capable workspace, check in this order: | ||
| Use the reference templates in this skill folder. If no root instruction file exists, ask before creating one. Preserve existing conventions and never invent tracker commands, labels, or domain boundaries. | ||
| 1. `memory/` | ||
| 2. Legacy `.codex/memory/` | ||
| ## 4. Bootstrap memory when installed | ||
| Use `memory/` as the canonical vault. If a legacy platform-local vault exists and `memory/` does not, move the legacy vault to `memory/` before seeding. If multiple legacy vaults exist and no root vault exists, tell the user what you found and ask which vault should become `memory/`. | ||
| Keep `memory/manifest.md` below roughly 250 tokens. Record only: | ||
| Treat memory bootstrap as already done only when the selected `{memoryDir}/_MOC.md` exists and has real project-specific content, not just placeholders. | ||
| - Product purpose and primary users | ||
| - Stack and deployment | ||
| - Canonical check, test, and build commands | ||
| - Current active area | ||
| - A few cross-cutting durable constraints | ||
| ### 0.2 Check engineering context | ||
| Create a separate card only when evidence establishes durable knowledge that will affect future work: | ||
| Engineering context is complete only when all are true: | ||
| - Architecture or product decision | ||
| - Reusable project-specific pattern | ||
| - Recurring non-obvious gotcha | ||
| - Constraint not obvious from current code | ||
| - `docs/agents/issue-tracker.md` exists | ||
| - `docs/agents/triage-labels.md` exists | ||
| - `docs/agents/domain.md` exists | ||
| - The root `AGENTS.md` file has an `## Agent skills` block | ||
| Use the templates in `memory/templates/`. Give every card a stable `id`, `kind`, `status`, one-sentence `summary`, relevant `tags`, `paths`, and `symbols`. Cite repository evidence. Do not create session notes, feature catalogs, or duplicates of source code and existing docs. | ||
| If both memory and engineering context are complete, report that startup is already configured and stop. | ||
| Run: | ||
| --- | ||
| ## Phase 1 - Silent Codebase Investigation | ||
| Skip this phase only if memory bootstrap is already complete. | ||
| Explore the project autonomously before asking the user anything. This prevents asking questions the codebase already answers. | ||
| ### 1.1 Read the entry points | ||
| Read in this order, skipping files that do not exist: | ||
| - `AGENTS.md` - Codex project instructions, if present | ||
| - `README.md` - purpose, setup, feature overview | ||
| - `package.json` / `pyproject.toml` / `Cargo.toml` - dependencies, scripts | ||
| - `src/` or `app/` top-level - folder structure | ||
| - Any `docs/ARCHITECTURE.md` or `docs/` folder | ||
| ### 1.2 Map the codebase structure | ||
| Identify: | ||
| - **Tech stack:** framework, language, runtime, database, styling system | ||
| - **Folder layout:** where features, routes, components, utilities, types, tests live | ||
| - **API surface:** routes/endpoints and their rough purpose | ||
| - **Key patterns already in use:** data flow, error handling, auth, validation, tests | ||
| - **Dependencies worth noting:** major libraries and what they are used for | ||
| ### 1.3 Identify knowledge gaps | ||
| After exploring, note only what you could not determine from code or docs: | ||
| - Business context: what problem this solves and who uses it | ||
| - Why certain technical choices were made | ||
| - Which areas are actively changing versus stable | ||
| - Known issues or technical debt | ||
| - Planned features or upcoming work | ||
| --- | ||
| ## Phase 2 - Interview the User | ||
| Skip this phase only if memory bootstrap is already complete. | ||
| Ask only about things you could not determine in Phase 1. Do not ask things the codebase already answered. | ||
| Use the platform's question mechanism if available. Ask in batches of **3-5 questions maximum per round**. After each round, re-evaluate whether you need more information before proceeding. | ||
| ### Question bank - pick the relevant ones | ||
| **Project identity** | ||
| - What problem does this project solve? Who are the primary users? | ||
| - Is this internal tooling, a consumer product, a B2B SaaS, an API service, or something else? | ||
| - What is the current stage: prototype, MVP, production with active users? | ||
| **Technical decisions** | ||
| - Why was a key framework, database, or language chosen over alternatives? | ||
| - Are there architectural constraints the team must work within? | ||
| - Is this repo one service, one app, or part of a larger system? | ||
| **Current state** | ||
| - What are the most important features currently working? | ||
| - What is actively being built or changed right now? | ||
| - What are the biggest known technical problems or areas of debt? | ||
| **Conventions** | ||
| - Are there unwritten rules or team conventions not captured in docs? | ||
| - Are there naming or structural patterns the team cares deeply about? | ||
| **Memory preferences** | ||
| - Are there decisions already made that agents should always know? | ||
| - Are there past mistakes or anti-patterns agents should avoid? | ||
| ### Confidence threshold | ||
| Continue interviewing until you can answer all of the following: | ||
| - [ ] What this system does and who uses it | ||
| - [ ] The full tech stack | ||
| - [ ] Folder structure and where each type of code lives | ||
| - [ ] At least 10 key architectural or technical decisions and their rationale | ||
| - [ ] At least 5 known anti-patterns, gotchas, or past mistakes to avoid | ||
| - [ ] Current development focus | ||
| If the repo is too small or new to support those counts, record the gap explicitly instead of fabricating detail. | ||
| --- | ||
| ## Phase 3 - Write the Memory Vault | ||
| Skip this phase only if memory bootstrap is already complete. | ||
| Write all notes now, in this order. Use templates in `{memoryDir}/templates/` when present. | ||
| ### 3.1 Architecture decision records | ||
| Create one ADR per significant technical decision identified. Number them sequentially starting from `ADR-001`. | ||
| File path: `{memoryDir}/decisions/ADR-NNN-slug.md` | ||
| Good candidates: | ||
| - Framework choice | ||
| - Database choice | ||
| - Auth strategy | ||
| - State management approach | ||
| - Styling system choice | ||
| - Monorepo versus polyrepo | ||
| - Hosting, compliance, or team constraints | ||
| Each ADR must use the `decision.md` template when available and include complete frontmatter with `tags`. | ||
| ### 3.2 Established patterns | ||
| Create one pattern note per significant reusable pattern found in the codebase. | ||
| File path: `{memoryDir}/patterns/slug.md` | ||
| Good candidates: | ||
| - API route structure | ||
| - Component props and typing | ||
| - Error handling and user-facing errors | ||
| - Data loading and fetch patterns | ||
| - Form handling and validation | ||
| - Auth/session checks | ||
| Each pattern note must include a real code example from the codebase with a file path. | ||
| ### 3.3 Known learnings | ||
| Create one learning note per known issue, anti-pattern, or technical debt item mentioned by the user or visible in the code. | ||
| File path: `{memoryDir}/learnings/slug.md` | ||
| Good candidates: | ||
| - "We tried X and it caused Y - do not do it" | ||
| - Known performance bottlenecks | ||
| - Libraries that had breaking changes or were replaced | ||
| - Current stack footguns | ||
| ### 3.4 Feature index | ||
| Create one feature note per major existing feature as a lightweight index. | ||
| File path: `{memoryDir}/features/slug.md` | ||
| Use this minimal format: | ||
| ```markdown | ||
| --- | ||
| title: "{{feature-name}}" | ||
| date: {{date}} | ||
| type: feature | ||
| status: active | ||
| tags: | ||
| - feature | ||
| --- | ||
| # {{feature-name}} | ||
| ## What It Does | ||
| One paragraph describing the feature and its user value. | ||
| ## Key Files | ||
| | File | Role | | ||
| |------|------| | ||
| | `path/to/file` | description | | ||
| ## Related Decisions | ||
| - [[decisions/ADR-NNN-slug]] | ||
| ## Related Patterns | ||
| - [[patterns/slug]] | ||
| ```bash | ||
| node .codex/context/context.cjs index --refresh | ||
| node .codex/context/context.cjs check | ||
| node .codex/context/context.cjs query --task "project startup verification" | ||
| ``` | ||
| ### 3.5 Seed the MOC | ||
| Do not append links to `memory/_MOC.md`; it must remain fixed-size. | ||
| Replace placeholder sections in `{memoryDir}/_MOC.md` with links to all notes created: | ||
| ## 5. Report | ||
| ```markdown | ||
| ## Decisions | ||
| - [[decisions/ADR-001-slug]] - one-line summary | ||
| Report installed scope, files created or updated, unresolved gaps, and the context-query result. Do not claim memory is complete; it should grow only when durable novelty appears. | ||
| ## Active Patterns | ||
| - [[patterns/slug]] - one-line summary | ||
| ## Learnings | ||
| - [[learnings/slug]] - one-line summary | ||
| ## Features | ||
| - [[features/slug]] - one-line summary | ||
| ``` | ||
| --- | ||
| ## Phase 4 - Configure Engineering Skill Context | ||
| Skip this phase only if engineering context is already complete. | ||
| Scaffold the per-repo configuration that the engineering skills assume: | ||
| - **Issue tracker:** where issues live and which commands or files skills should use | ||
| - **Triage labels:** tracker labels or local state strings for the five canonical triage roles | ||
| - **Domain docs:** where `CONTEXT.md`, `CONTEXT-MAP.md`, and ADRs live | ||
| This is prompt-driven, not a deterministic script. Explore, present what you found, confirm unresolved choices with the user, then write. | ||
| Do not invent hidden workflow directories or conventions. If a repo does not already have a local markdown issue location, ask for the desired path and record it explicitly. | ||
| ### 4.1 Explore | ||
| Look at the current repo. Read whatever exists; do not assume: | ||
| - `git remote -v` and `.git/config` | ||
| - `AGENTS.md` at the repo root | ||
| - `CONTEXT.md` and `CONTEXT-MAP.md` at the repo root | ||
| - `docs/adr/` and any `src/*/docs/adr/` directories | ||
| - `docs/agents/` | ||
| - Existing issue or planning locations such as `.github/ISSUE_TEMPLATE/`, `docs/issues/`, `issues/`, `tasks/`, `specs/`, or `docs/` planning artifacts | ||
| - Existing labels in the tracker, if a CLI is configured and the user has asked for live verification | ||
| ### 4.2 Present findings and ask | ||
| Summarize what is present and missing. Then walk through only decisions that are not obvious from the repo. If multiple choices are unresolved, ask them one at a time. | ||
| **Section A - Issue tracker** | ||
| Explain that the issue tracker is where issues live for this repo. Skills such as `to-spec`, `to-tickets`, `code-review`, and `handoff` need to know whether to call a tracker CLI, write a repo-local markdown file, or follow another workflow. | ||
| Default posture: | ||
| - If `git remote` points at GitHub, propose GitHub Issues. | ||
| - If `git remote` points at GitLab, propose GitLab Issues. | ||
| - Otherwise offer GitHub, GitLab, local markdown, or another tracker described by the user. | ||
| For local markdown, always ask for the base directory and file naming pattern unless an existing repo convention is obvious. | ||
| **Section B - Triage label vocabulary** | ||
| Explain that spec and ticket workflows need the actual tracker label strings configured for this repo. | ||
| Canonical roles: | ||
| - `needs-triage` - maintainer needs to evaluate | ||
| - `needs-info` - waiting on reporter | ||
| - `ready-for-agent` - fully specified, AFK-ready | ||
| - `ready-for-human` - needs human implementation | ||
| - `wontfix` - will not be actioned | ||
| Default: each role's string equals its name. Ask whether the user wants overrides. | ||
| **Section C - Domain docs** | ||
| Explain that some engineering skills read domain context and ADRs before proposing changes. | ||
| Confirm the layout: | ||
| - **Single-context:** one `CONTEXT.md` plus `docs/adr/` at the repo root | ||
| - **Multi-context:** `CONTEXT-MAP.md` at the root pointing to per-context `CONTEXT.md` files | ||
| ### 4.3 Confirm and edit | ||
| Show the user a draft of: | ||
| - The `## Agent skills` block to add to each existing instruction file selected in step 4.4 | ||
| - The contents of `docs/agents/issue-tracker.md` | ||
| - The contents of `docs/agents/triage-labels.md` | ||
| - The contents of `docs/agents/domain.md` | ||
| Let them edit before writing. | ||
| ### 4.4 Write | ||
| Pick instruction files to edit: | ||
| - If `AGENTS.md` exists, edit it. | ||
| - If it does not exist, ask the user before creating it. | ||
| Never create a new instruction file when a repo already has one. Work with the file or files already there. | ||
| If an `## Agent skills` block already exists, update its contents in place rather than appending a duplicate. | ||
| The block: | ||
| ```markdown | ||
| ## Agent skills | ||
| ### Issue tracker | ||
| [one-line summary of where issues are tracked]. See `docs/agents/issue-tracker.md`. | ||
| ### Triage labels | ||
| [one-line summary of the label vocabulary]. See `docs/agents/triage-labels.md`. | ||
| ### Domain docs | ||
| [one-line summary of layout - "single-context" or "multi-context"]. See `docs/agents/domain.md`. | ||
| ``` | ||
| Write the three docs files using the seed templates in this folder as starting points: | ||
| - [issue-tracker-github.md](./issue-tracker-github.md) | ||
| - [issue-tracker-local.md](./issue-tracker-local.md) | ||
| - [triage-labels.md](./triage-labels.md) | ||
| - [domain.md](./domain.md) | ||
| For GitLab or another tracker, write `docs/agents/issue-tracker.md` from scratch using the repo evidence and the user's description. | ||
| --- | ||
| ## Phase 5 - Report | ||
| After startup, summarize: | ||
| ```markdown | ||
| ## Project Startup Complete | ||
| ### Memory Bootstrap | ||
| - Status: created / already present / not applicable for Skills only / skipped with reason | ||
| - Memory vault: {memoryDir} | ||
| - Notes created: X decisions, X patterns, X learnings, X features | ||
| ### Engineering Skill Context | ||
| - Status: created / already present / skipped with reason | ||
| - Files: docs/agents/issue-tracker.md, docs/agents/triage-labels.md, docs/agents/domain.md | ||
| - Instruction file updated: AGENTS.md | ||
| ### What Agents Now Know | ||
| Brief paragraph on what future agents will load before work. | ||
| ### Gaps Remaining | ||
| Anything unresolved or intentionally deferred. | ||
| ``` | ||
| ## Rules | ||
| - Never fabricate information. Ask or leave an explicit `TODO` placeholder. | ||
| - Do not overwrite an existing memory vault or `docs/agents/` setup unless the user explicitly asks for a reset. | ||
| - Do not create notes for things covered by installed skills; memory is project-specific. | ||
| - Every memory note must have complete YAML frontmatter and a `## Related` section with at least one `[[wiki-link]]`. | ||
| - File names: `lowercase-kebab-case.md`. ADRs: `ADR-NNN-slug.md`. | ||
| - Tags must reflect the domain: `#auth`, `#api`, `#ui`, `#database`, `#performance`, `#security`, `#testing`, etc. | ||
| This skill replaces the legacy external setup flow. Never tell users to run a missing setup skill. | ||
| - Never overwrite a configured vault or project convention without explicit permission. | ||
| - Never fabricate rationale, commands, labels, or historical mistakes. | ||
| - Facts already obvious from code do not need memory duplication. | ||
| - Routine work needs no memory note. | ||
| - This skill is optional and should not be installed for projects that do not need startup configuration. |
| interface: | ||
| display_name: "Security Review" | ||
| short_description: "Threat-model and test security-sensitive changes" | ||
| default_prompt: "Review this security-sensitive change and report evidence-backed findings." | ||
| short_description: "Threat-model and review security-sensitive changes" | ||
| default_prompt: "Use $security-review to inspect this security-sensitive change for concrete risks." |
+1
-115
@@ -478,116 +478,2 @@ --- | ||
| --- | ||
| ## Phase 7: Off-Page Basics | ||
| ### Social Signal Standardization | ||
| Consistent identity across platforms strengthens entity recognition in Google's Knowledge Graph. | ||
| **Every public profile must have:** | ||
| - Same full name (no nicknames) | ||
| - Same role/headline using your primary keywords | ||
| - Link back to your primary domain | ||
| - `rel="me noopener noreferrer"` on outbound social links from your site | ||
| Platforms: GitHub, LinkedIn, X/Twitter, YouTube, Dev.to, any publications. | ||
| ### Backlink Strategy | ||
| One high-quality backlink beats twenty weak ones. | ||
| **Backlink-ready assets (create one):** | ||
| - Detailed project case study with architecture, metrics, and lessons | ||
| - Original research or tool comparison | ||
| - Open-source tool with real documentation | ||
| **Outreach targets:** | ||
| - Relevant communities (Discord servers, Reddit, niche forums) | ||
| - Dev directories (Dev.to, HackerNews Show HN) | ||
| - Guest posts on domain authority > 20 niche sites (cybersecurity, dev, SaaS) | ||
| **Quality bar:** Relevant niche + DA > 10 + no spam indicators. | ||
| --- | ||
| ## Phase 8: Monitoring | ||
| ### Weekly Checklist (30 min) | ||
| In Google Search Console: | ||
| 1. Impressions ↑ — visibility growing? | ||
| 2. Clicks ↑ — CTR improvements landing? | ||
| 3. CTR by page — any page < 1.5%? → rewrite title/description | ||
| 4. Average position — any page at 11–15? → internal link boost candidate | ||
| 5. Coverage errors — new "Excluded" or "Error" pages? | ||
| ### Response Guide | ||
| | Signal | Action | | ||
| |--------|--------| | ||
| | Impressions ↓ | Check for noindex tags, robots.txt issues, server errors | | ||
| | CTR ↓ on a page | Rewrite title/description — try more specific variant | | ||
| | Avg position degraded | Content refresh + add internal links from higher-ranking pages | | ||
| | CWV regression | Run PageSpeed Insights → isolate regression → fix before next deploy | | ||
| ### Monthly Optimization Cycle | ||
| 1. Audit pages in GSC top 100 with CTR < 2% | ||
| 2. Rewrite titles/descriptions for underperformers | ||
| 3. Add internal links from high-ranking pages to underperformers | ||
| 4. Refresh content with stale dates or outdated data | ||
| 5. Measure after 2 weeks | ||
| --- | ||
| ## Common Pitfalls | ||
| | Pitfall | Cause | Fix | | ||
| |---------|-------|-----| | ||
| | Template syntax in JSON-LD | Unescaped `<` in rendered HTML | `.replace(/</g, '\\u003c')` | | ||
| | Duplicate canonicals | Copy-paste errors | Each page self-canonicals to its own URL | | ||
| | Description truncated on mobile | Key info after 120 chars | Front-load the message in first 120 chars | | ||
| | H1 mismatches title | Forgot to sync after title change | H1 must echo or closely match `<title>` | | ||
| | Empty schema fields | No filter before `.map()` | `filter(item => Boolean(item.name?.trim()))` | | ||
| | CLS from images | No explicit dimensions | Always add `width` + `height` to `<img>` | | ||
| | API routes getting indexed | Missing `Disallow` in robots.txt | `Disallow: /api/` | | ||
| | Meta keywords tag | Old habit | Remove — Google ignores it since 2009 | | ||
| --- | ||
| ## Validation Tools | ||
| | Tool | Use | | ||
| |------|-----| | ||
| | [Google Rich Results Test](https://search.google.com/test/rich-results) | Validate JSON-LD schema | | ||
| | [PageSpeed Insights](https://pagespeed.web.dev) | Core Web Vitals (field + lab) | | ||
| | [Google Search Console](https://search.google.com/search-console) | Indexing, clicks, impressions, CTR | | ||
| | [SERP Simulator — Sistrix](https://app.sistrix.com/serp-simulator) | Preview title/description in SERP | | ||
| | Lighthouse (Chrome DevTools) | Local CWV audit during development | | ||
| | `site:yourdomain.com` in Google | Quick index check | | ||
| --- | ||
| ## Portfolio-Specific Patterns | ||
| ### Dual keyword strategy | ||
| Target both branded queries (`[your name]`) and role queries (`security researcher portfolio`, `full-stack security engineer`). Spread naturally across Home, About, Projects, Experience — never concentrate all keywords on one page. | ||
| ### Project pages as conversion funnels | ||
| Each project detail page: | ||
| - Rich description + `ItemList` + `BreadcrumbList` schema | ||
| - Link to GitHub / live demo | ||
| - Related experience links (internal linking — share authority) | ||
| - CTA toward contact or LinkedIn | ||
| ### Certifications as trust signals | ||
| Create `/about/certifications/[provider]` detail pages with `CollectionPage` + `EducationalOccupationalCredential` schema. Reference certifications in experience descriptions for natural keyword reinforcement. | ||
| ### Blog as discovery gateway | ||
| - Link **from** portfolio detail pages **to** related blog posts | ||
| - Link **from** blog posts **back to** portfolio projects | ||
| - Portfolio = proof; blog = discovery and long-tail keyword capture | ||
| For off-page work, monitoring, common failure patterns, validation tools, or portfolio-specific guidance, read [references/growth-and-monitoring.md](references/growth-and-monitoring.md). Do not load that reference for ordinary crawl, metadata, schema, performance, or on-page tasks. |
| interface: | ||
| display_name: "Conversation to Spec" | ||
| short_description: "Synthesize the current discussion into a project spec" | ||
| default_prompt: "Turn this conversation into a spec, then show me the draft before publishing it." | ||
| short_description: "Synthesize an approved project specification" | ||
| default_prompt: "Use $to-spec to turn this conversation into a reviewable specification draft." | ||
| policy: | ||
| allow_implicit_invocation: false |
@@ -8,3 +8,3 @@ --- | ||
| The issue tracker and triage label vocabulary should be documented under `docs/agents/`. If that context is missing, load `project-startup` first. | ||
| The issue tracker and triage label vocabulary should be documented under `docs/agents/`. If that context is missing, load `project-startup` only when it is installed; otherwise draft locally and ask only for the tracker destination and label vocabulary before publishing. | ||
@@ -77,2 +77,2 @@ ## Process | ||
| After publication, offer `to-tickets` when the spec is large enough to benefit from independently verifiable vertical slices. Do not create tickets unless the user asks or approves that next step. | ||
| After publication, offer `to-tickets` when it is installed and the spec is large enough to benefit from independently verifiable vertical slices. Do not create tickets unless the user asks or approves that next step. |
@@ -10,3 +10,3 @@ --- | ||
| The issue tracker and triage label vocabulary should be documented under `docs/agents/`. If that context is missing, load `project-startup` first. | ||
| The issue tracker and triage label vocabulary should be documented under `docs/agents/`. If that context is missing, load `project-startup` only when it is installed; otherwise ask only for the tracker destination and label vocabulary needed to publish. | ||
@@ -107,2 +107,2 @@ ## Process | ||
| Work the frontier one ticket at a time with the `implement` skill. Use a fresh context or a `handoff` when a ticket approaches the context limit. | ||
| Work the frontier one ticket at a time with `implement` when it is installed, or with the same scoped edit-and-verify loop directly. Use a fresh context or `handoff` when that skill is installed and a ticket approaches the context limit; otherwise write a compact temporary continuation note outside the repository. |
| import React from "react"; | ||
| import { Box, Text } from "ink"; | ||
| import path from "path"; | ||
| import { COPY, STEP_LABELS } from "../lib/constants.js"; | ||
| import { getStepIndexForScope, formatMode } from "../lib/utils.js"; | ||
| const h = React.createElement; | ||
| function StepIndicator({ visibleSteps, currentIndex }) { | ||
| return h(Box, { marginTop: 1 }, visibleSteps.map((step, index) => h(Box, { key: step, marginRight: index === visibleSteps.length - 1 ? 0 : 1 }, h(Text, { color: index <= currentIndex ? "cyan" : "gray" }, index < currentIndex ? "●" : index === currentIndex ? "◆" : "○"), h(Text, { color: index <= currentIndex ? "white" : "gray" }, ` ${STEP_LABELS[step]}`), index === visibleSteps.length - 1 ? null : h(Text, { color: "gray" }, " ")))); | ||
| } | ||
| export function Header({ version, force, cwd, step, compact, showSteps }) { | ||
| const stepInfo = getStepIndexForScope(step); | ||
| return h(Box, { flexDirection: "column" }, h(Box, { justifyContent: "space-between" }, h(Box, null, h(Text, { color: "cyan" }, "◆ "), h(Text, { bold: true }, COPY.app.name), h(Text, { color: "gray" }, ` v${version}`)), h(Text, { color: force ? "yellow" : "green" }, formatMode(force).toUpperCase())), compact ? null : h(Text, { color: "gray" }, COPY.app.tagline), h(Box, { marginTop: compact ? 0 : 1, justifyContent: "space-between" }, h(Text, { color: "gray" }, `Project ${path.basename(cwd) || cwd}`), showSteps | ||
| ? h(Text, { color: "gray" }, `Step ${stepInfo.currentIndex + 1}/${stepInfo.visibleSteps.length}`) | ||
| : null), showSteps | ||
| ? h(StepIndicator, { visibleSteps: stepInfo.visibleSteps, currentIndex: stepInfo.currentIndex }) | ||
| : null); | ||
| } |
| import React from "react"; | ||
| import { Box, Text } from "ink"; | ||
| const h = React.createElement; | ||
| export function Frame({ children }) { | ||
| return h(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1 }, children); | ||
| } | ||
| export function Section({ title, eyebrow, children }) { | ||
| return h(Box, { flexDirection: "column", marginTop: 1 }, eyebrow ? h(Text, { color: "gray" }, eyebrow.toUpperCase()) : null, h(Text, { bold: true }, title), h(Box, { marginTop: 1, flexDirection: "column" }, children)); | ||
| } |
| import React, { useState, useEffect, useMemo } from "react"; | ||
| import { Box, Text, useInput } from "ink"; | ||
| import { COPY } from "../lib/constants.js"; | ||
| import { getScopeOptions } from "../lib/utils.js"; | ||
| import { Section } from "./Layout.js"; | ||
| const h = React.createElement; | ||
| function asScope(value) { | ||
| if (value === "skills-memory" || value === "skills") { | ||
| return value; | ||
| } | ||
| return null; | ||
| } | ||
| export function KeyHints({ hints }) { | ||
| return h(Box, { marginTop: 1 }, hints.map((hint, index) => h(Box, { key: `${hint.label}-${index}`, marginRight: index === hints.length - 1 ? 0 : 3 }, h(Text, { color: "gray" }, hint.label), h(Text, { color: "white" }, ` ${hint.value}`)))); | ||
| } | ||
| // ─── CompactOptionList ──────────────────────────────────────────────────────── | ||
| function CompactOptionList({ options, activeIndex }) { | ||
| return h(Box, { flexDirection: "column" }, ...options.map((option, index) => { | ||
| const selected = index === activeIndex; | ||
| return h(Box, { key: option.value, justifyContent: "space-between" }, h(Box, null, h(Text, { color: selected ? option.accent : "gray" }, selected ? "◆ " : "○ "), h(Text, { bold: selected }, option.label)), h(Text, { color: "gray" }, option.description)); | ||
| })); | ||
| } | ||
| // ─── OptionList ─────────────────────────────────────────────────────────────── | ||
| export function OptionList({ options, value, onChange, onSubmit, compact = false }) { | ||
| const initialIndex = Math.max(0, options.findIndex((o) => o.value === value)); | ||
| const [activeIndex, setActiveIndex] = useState(initialIndex); | ||
| useEffect(() => { | ||
| const next = options.findIndex((o) => o.value === value); | ||
| if (next >= 0) | ||
| setActiveIndex(next); | ||
| }, [options, value]); | ||
| const active = options[activeIndex] ?? options[0]; | ||
| useEffect(() => { | ||
| if (active && active.value !== value) | ||
| onChange(active.value); | ||
| }, [active, onChange, value]); | ||
| useInput((input, key) => { | ||
| if (key.upArrow) { | ||
| setActiveIndex((p) => (p - 1 + options.length) % options.length); | ||
| return; | ||
| } | ||
| if (key.downArrow) { | ||
| setActiveIndex((p) => (p + 1) % options.length); | ||
| return; | ||
| } | ||
| if (key.return && active) { | ||
| onSubmit(active.value); | ||
| } | ||
| if (input === "j") { | ||
| setActiveIndex((p) => (p + 1) % options.length); | ||
| } | ||
| if (input === "k") { | ||
| setActiveIndex((p) => (p - 1 + options.length) % options.length); | ||
| } | ||
| }); | ||
| if (compact) { | ||
| const current = options[activeIndex] ?? options[0]; | ||
| return h(Box, { flexDirection: "column" }, h(CompactOptionList, { options, activeIndex }), current | ||
| ? h(Box, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: current.accent, paddingX: 1, paddingY: 0 }, h(Text, { color: "gray" }, current.description), ...(current.details || []).map((detail, i) => h(Box, { key: `${current.value}-d-${i}` }, h(Text, { color: current.accent }, "• "), h(Text, null, detail))), current.note ? h(Text, { color: "gray" }, current.note) : null) | ||
| : null); | ||
| } | ||
| return h(Box, { flexDirection: "column" }, options.map((option, index) => { | ||
| const selected = index === activeIndex; | ||
| return h(Box, { | ||
| key: option.value, | ||
| flexDirection: "column", | ||
| borderStyle: "round", | ||
| borderColor: selected ? option.accent : "gray", | ||
| paddingX: 1, | ||
| paddingY: 0, | ||
| marginBottom: 1, | ||
| }, h(Box, { justifyContent: "space-between" }, h(Box, null, h(Text, { color: selected ? option.accent : "gray" }, selected ? "◆ " : "○ "), h(Text, { bold: selected }, option.label)), h(Text, { color: "gray" }, option.description)), ...(option.details || []).map((detail, di) => h(Box, { key: `${option.value}-${di}`, marginLeft: 2 }, h(Text, { color: "gray" }, "• "), h(Text, { color: selected ? "white" : "gray" }, detail))), option.note | ||
| ? h(Box, { marginLeft: 2, marginTop: 1 }, h(Text, { color: "gray" }, option.note)) | ||
| : null); | ||
| })); | ||
| } | ||
| // ─── ScopeStep ──────────────────────────────────────────────────────────────── | ||
| export function ScopeStep({ value, onSubmit, onChange, compact }) { | ||
| const [current, setCurrent] = useState(value || "skills-memory"); | ||
| const scopeOptions = useMemo(() => getScopeOptions(), []); | ||
| const handleChange = (next) => { | ||
| const parsed = asScope(next); | ||
| if (parsed) { | ||
| setCurrent(parsed); | ||
| onChange?.(parsed); | ||
| } | ||
| }; | ||
| return h(Section, { eyebrow: COPY.scope.eyebrow, title: COPY.scope.title }, h(OptionList, { options: scopeOptions, value: current, onChange: handleChange, onSubmit, compact })); | ||
| } |
| import React from "react"; | ||
| import { Box, Text } from "ink"; | ||
| import path from "path"; | ||
| import { INSTALL_SCOPE_META, PLATFORM_META, META_FILE, COPY } from "../lib/constants.js"; | ||
| import { Section } from "./Layout.js"; | ||
| const h = React.createElement; | ||
| export function InfoLine({ label, value, valueColor = "white" }) { | ||
| return h(Box, { justifyContent: "space-between" }, h(Text, { color: "gray" }, label), h(Text, { color: valueColor }, value)); | ||
| } | ||
| // ─── ConfirmBar ─────────────────────────────────────────────────────────────── | ||
| function ConfirmBar({ force }) { | ||
| return h(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, paddingY: 0, justifyContent: "space-between" }, h(Text, null, `${force ? "Update" : "Install"} now?`), h(Text, { color: "gray" }, COPY.preview.confirmHints)); | ||
| } | ||
| // ─── InstallPreview ─────────────────────────────────────────────────────────── | ||
| export function InstallPreview({ scope, platform, plan, preview, force, onConfirm, compact }) { | ||
| const meta = PLATFORM_META[platform]; | ||
| const scopeMeta = INSTALL_SCOPE_META[scope]; | ||
| return h(Box, { flexDirection: "column" }, h(Section, { eyebrow: COPY.preview.eyebrow, title: `${force ? "Update" : "Install"} package set before writing files.` }, h(Box, { borderStyle: "round", borderColor: meta.accent, paddingX: 1, paddingY: 0, flexDirection: "column" }, h(InfoLine, { label: COPY.labels.installScope, value: scopeMeta.label, valueColor: scopeMeta.accent }), h(InfoLine, { label: COPY.labels.platform, value: meta.name, valueColor: meta.accent }), h(InfoLine, { label: COPY.labels.destination, value: meta.destination }), h(InfoLine, { label: COPY.labels.installUnits, value: `${preview.total} files across ${preview.steps.length} targets` }), h(InfoLine, { label: COPY.labels.metadata, value: path.join(plan.metaDir, META_FILE) })), h(Box, { marginTop: 1, flexDirection: "column" }, h(Text, { bold: true }, COPY.preview.includes), ...preview.notes.slice(0, compact ? 2 : preview.notes.length).map((note, i) => h(Box, { key: `note-${i}`, marginLeft: 1 }, h(Text, { color: meta.accent }, "• "), h(Text, null, note)))), h(Box, { marginTop: 1, flexDirection: "column" }, h(Text, { bold: true }, COPY.preview.targets), ...preview.steps.slice(0, compact ? 4 : preview.steps.length).map((step, i) => h(Box, { key: `${step.label}-${i}`, justifyContent: "space-between" }, h(Text, { color: "gray" }, step.label), h(Text, null, step.kind === "file" ? "1 file" : `${step.count} files`))), compact && preview.steps.length > 4 | ||
| ? h(Text, { color: "gray" }, `+ ${preview.steps.length - 4} more targets`) | ||
| : null), preview.missing.length > 0 | ||
| ? h(Box, { marginTop: 1, flexDirection: "column" }, h(Text, { color: "yellow" }, COPY.preview.missingWarning), h(Text, { color: "gray" }, preview.missing.join(", "))) | ||
| : null, h(ConfirmBar, { force }))); | ||
| } | ||
| // ─── ExistingInstallView ────────────────────────────────────────────────────── | ||
| export function ExistingInstallView({ platform, existing, compact }) { | ||
| const meta = PLATFORM_META[platform]; | ||
| return h(Section, { eyebrow: COPY.existing.eyebrow, title: `${meta.name} is already installed in this project.` }, h(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 0, flexDirection: "column" }, h(InfoLine, { label: COPY.labels.installedVersion, value: existing?.version ?? "Unknown", valueColor: "yellow" }), h(InfoLine, { label: COPY.labels.platform, value: meta.name }), h(InfoLine, { label: COPY.labels.installedAt, value: existing?.installedAt ?? "Unknown" }), h(InfoLine, { label: COPY.labels.nextAction, value: COPY.existing.nextAction, valueColor: "cyan" })), compact ? null : h(Box, { marginTop: 1 }, h(Text, { color: "gray" }, COPY.existing.noFilesChanged))); | ||
| } | ||
| export function UpToDateView({ platform, existing, latestVersion, compact }) { | ||
| const meta = PLATFORM_META[platform]; | ||
| return h(Section, { eyebrow: COPY.existing.eyebrow, title: COPY.existing.latestTitle }, h(Box, { borderStyle: "round", borderColor: "green", paddingX: 1, paddingY: 0, flexDirection: "column" }, h(InfoLine, { label: COPY.labels.platform, value: meta.name }), h(InfoLine, { label: COPY.labels.installedVersion, value: existing?.version ?? "Unknown", valueColor: "green" }), h(InfoLine, { label: COPY.labels.npmLatestVersion, value: latestVersion, valueColor: "green" }), h(InfoLine, { label: COPY.labels.installedAt, value: existing?.installedAt ?? "Unknown" })), compact ? null : h(Box, { marginTop: 1 }, h(Text, { color: "gray" }, COPY.existing.latestMessage))); | ||
| } | ||
| export function UpdateMissingView({ compact }) { | ||
| return h(Section, { eyebrow: COPY.existing.eyebrow, title: COPY.existing.missingTitle }, h(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 0, flexDirection: "column" }, h(Text, null, COPY.existing.missingMessage), compact ? null : h(Text, { color: "cyan" }, COPY.existing.nextAction.replace("update", "install")))); | ||
| } |
| import React from "react"; | ||
| import { Box, Text } from "ink"; | ||
| import { Spinner } from "@inkjs/ui"; | ||
| import { COPY } from "../lib/constants.js"; | ||
| import { Section } from "./Layout.js"; | ||
| import { InfoLine } from "./Preview.js"; | ||
| const h = React.createElement; | ||
| export function ProgressView({ plan, installSteps, currentStepIdx, fetchingHash, totalFiles, compact }) { | ||
| const completed = installSteps.length; | ||
| const totalTargets = plan.steps.length; | ||
| return h(Section, { eyebrow: COPY.install.eyebrow, title: COPY.install.title }, h(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 0, flexDirection: "column" }, h(InfoLine, { label: COPY.labels.completedTargets, value: `${completed}/${totalTargets}`, valueColor: "cyan" }), h(InfoLine, { label: COPY.labels.filesCopied, value: `${totalFiles}` }), currentStepIdx >= 0 | ||
| ? h(Box, { marginTop: 1 }, h(Spinner, { type: "dots" }), h(Text, { color: "cyan" }, ` ${plan.steps[currentStepIdx]?.label}`)) | ||
| : null, fetchingHash | ||
| ? h(Box, { marginTop: 1 }, h(Spinner, { type: "dots" }), h(Text, null, ` ${COPY.install.hashFetching}`)) | ||
| : null), installSteps.length > 0 | ||
| ? h(Box, { marginTop: 1, flexDirection: "column" }, h(Text, { bold: true }, COPY.install.completed), ...installSteps.slice(-(compact ? 4 : 8)).map((item, index) => h(Box, { key: `${item.label}-${index}` }, h(Text, { color: "green" }, "✓ "), h(Text, { color: "white" }, item.label), item.msg ? h(Text, { color: "gray" }, ` ${item.msg}`) : null))) | ||
| : null); | ||
| } |
| import React from "react"; | ||
| import { Box, Text } from "ink"; | ||
| import path from "path"; | ||
| import { PLATFORM_META, COPY, PRIMARY_COMMANDS, META_FILE } from "../lib/constants.js"; | ||
| import { Section } from "./Layout.js"; | ||
| import { InfoLine } from "./Preview.js"; | ||
| const h = React.createElement; | ||
| export function SuccessView({ platform, plan, totalFiles, hash, missing, force, compact }) { | ||
| const meta = PLATFORM_META[platform]; | ||
| const primaryCommand = PRIMARY_COMMANDS[platform]; | ||
| return h(Box, { flexDirection: "column" }, h(Section, { eyebrow: COPY.success.eyebrow, title: `${meta.name} setup ${force ? "updated" : "installed"} successfully.` }, h(Box, { borderStyle: "round", borderColor: meta.accent, paddingX: 1, paddingY: 0, flexDirection: "column" }, h(InfoLine, { label: COPY.labels.filesWritten, value: `${totalFiles}`, valueColor: meta.accent }), h(InfoLine, { label: COPY.labels.platform, value: meta.name }), h(InfoLine, { label: COPY.labels.primaryCommand, value: primaryCommand, valueColor: "cyan" }), h(InfoLine, { label: COPY.labels.integrity, value: hash ? COPY.success.integrityOnline : COPY.success.integrityOffline, valueColor: hash ? "green" : "yellow" })), hash | ||
| ? h(Box, { marginTop: 1, flexDirection: "column" }, h(Text, { bold: true }, COPY.success.integrity), h(Text, { color: "gray" }, COPY.success.integrityCommand), compact ? null : h(Text, { color: "gray" }, `Compare with ${path.join(plan.metaDir, META_FILE)}`), h(Text, { color: "gray" }, `${hash.slice(0, compact ? 36 : 56)}...`)) | ||
| : null, h(Box, { marginTop: 1, flexDirection: "column" }, h(Text, { bold: true }, COPY.success.nextSteps), ...plan.nextSteps.slice(0, compact ? 2 : plan.nextSteps.length).map((step, i) => h(Box, { key: `next-${i}` }, h(Text, { color: "gray" }, `${i + 1}. `), h(Text, null, step))), compact && plan.nextSteps.length > 2 | ||
| ? h(Text, { color: "gray" }, `+ ${plan.nextSteps.length - 2} more next steps`) | ||
| : null), missing.length > 0 | ||
| ? h(Box, { marginTop: 1, flexDirection: "column" }, h(Text, { color: "yellow" }, COPY.success.skippedNote), h(Text, { color: "gray" }, missing.join(", "))) | ||
| : null)); | ||
| } |
| const NAVIGATION_HINTS = [ | ||
| { label: "↑↓", value: "move" }, | ||
| { label: "enter", value: "select" }, | ||
| { label: "j/k", value: "navigate" }, | ||
| ]; | ||
| const BACK_QUIT_HINTS = [ | ||
| { label: "esc", value: "back" }, | ||
| { label: "q", value: "quit" }, | ||
| ]; | ||
| const INSTALL_HINTS = [ | ||
| { label: "esc", value: "cancel" }, | ||
| { label: "q", value: "quit" }, | ||
| ]; | ||
| const CONFIRM_HINTS = [ | ||
| { label: "enter/y", value: "confirm" }, | ||
| { label: "n", value: "cancel" }, | ||
| { label: "esc", value: "back" }, | ||
| { label: "q", value: "quit" }, | ||
| ]; | ||
| export function getFooterHints(step) { | ||
| if (step === "install") { | ||
| return INSTALL_HINTS; | ||
| } | ||
| if (step === "scope") { | ||
| return [...NAVIGATION_HINTS, ...BACK_QUIT_HINTS]; | ||
| } | ||
| if (step === "confirm") { | ||
| return CONFIRM_HINTS; | ||
| } | ||
| return BACK_QUIT_HINTS; | ||
| } |
| # Contributing | ||
| <!-- TODO: Replace "[PROJECT NAME]" throughout this file with the actual project name. --> | ||
| ## Getting started | ||
| <!-- TODO: Fill in the actual setup commands for this project. --> | ||
| ```bash | ||
| # 1. Clone the repo | ||
| git clone https://github.com/TODO-USERNAME/TODO-REPO-NAME.git | ||
| cd TODO-REPO-NAME | ||
| # 2. Install dependencies | ||
| # TODO: Replace with your package manager (pnpm / npm / yarn) | ||
| pnpm install | ||
| # 3. Copy environment variables | ||
| cp .env.example .env | ||
| # TODO: Fill in the required values in .env — see README for what each one does | ||
| # 4. Start the dev server | ||
| # TODO: Replace with your actual dev command | ||
| pnpm dev | ||
| ``` | ||
| ## Branch naming | ||
| Branches must follow this format: `type/short-description` | ||
| | Type | When to use | | ||
| |------|-------------| | ||
| | `feat/` | New feature or capability | | ||
| | `fix/` | Bug fix | | ||
| | `docs/` | Documentation only | | ||
| | `refactor/` | Code change with no behaviour change | | ||
| | `chore/` | Tooling, config, dependencies | | ||
| | `ci/` | CI/CD pipeline changes | | ||
| Example: `feat/contact-form-validation`, `fix/mobile-nav-overflow` | ||
| Rules: | ||
| - All lowercase | ||
| - Hyphens only (no underscores, no slashes within the description) | ||
| - 2–5 words after the prefix | ||
| - No ticket numbers in branch names | ||
| ## Commit messages | ||
| Use [Conventional Commits](https://www.conventionalcommits.org/): | ||
| ``` | ||
| type(scope): short description in imperative mood | ||
| Optional longer explanation of what and why (not how). | ||
| ``` | ||
| Types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci` | ||
| Examples: | ||
| - `feat(auth): add magic link sign-in` | ||
| - `fix(contact): prevent double form submission on slow connection` | ||
| - `docs: update README with Docker setup steps` | ||
| Rules: | ||
| - Subject line max 72 characters | ||
| - Imperative mood ("add" not "added", "fix" not "fixes") | ||
| - No period at the end of the subject line | ||
| - Breaking changes: append `!` to the type → `feat!: redesign navigation` | ||
| ## Pull requests | ||
| 1. Branch from `main` (never commit directly to `main`) | ||
| 2. Keep PRs focused — one logical change per PR | ||
| 3. Fill in the PR template completely before requesting review | ||
| 4. PRs with UI changes must include before/after screenshots | ||
| 5. All checks must pass before merging | ||
| 6. Squash merge — one commit per PR on `main` | ||
| ## Running tests | ||
| ```bash | ||
| # Unit tests | ||
| # TODO: Replace with actual test command | ||
| pnpm test | ||
| # Unit tests with coverage | ||
| # TODO: Replace or delete if coverage not set up | ||
| pnpm test:coverage | ||
| # E2E tests (requires dev server running) | ||
| # TODO: Replace or delete if Playwright not set up | ||
| pnpm exec playwright test | ||
| ``` | ||
| <!-- TODO: Add any additional test setup notes here (e.g. database seeding, env vars needed for tests). --> | ||
| ## Code standards | ||
| Code standards are enforced automatically via `.github/instructions/`: | ||
| - **TypeScript** rules auto-load when editing `.ts` files | ||
| - **Svelte** rules auto-load when editing `.svelte` files | ||
| - **API route** rules auto-load when editing files in `routes/api/` or `server/` | ||
| - **Test** rules auto-load when editing `*.test.ts` or `*.spec.ts` files | ||
| Run the linter before committing: | ||
| ```bash | ||
| # TODO: Replace with actual lint command (eslint / biome / etc.) | ||
| pnpm lint | ||
| ``` | ||
| ## Changelog | ||
| Update `CHANGELOG.md` under `[Unreleased]` for every source change before opening a PR. Format: | ||
| ```markdown | ||
| ## [Unreleased] | ||
| ### Added | ||
| - New contact form with honeypot bot detection | ||
| ### Fixed | ||
| - Mobile navigation overflow on 320px viewports | ||
| ### Changed | ||
| - Migrated image assets to WebP format | ||
| ``` | ||
| ## Questions? | ||
| <!-- TODO: Replace with your actual contact method — GitHub Discussions, Discord, email, etc. --> | ||
| Open a GitHub Discussion or reach out at TODO-CONTACT. |
| --- | ||
| name: Bug report | ||
| about: Something is broken or behaving unexpectedly | ||
| title: "fix: [brief description]" | ||
| labels: bug | ||
| assignees: "" | ||
| --- | ||
| ## What happened | ||
| <!-- Describe the bug clearly. What did you see? --> | ||
| ## What you expected | ||
| <!-- What should have happened instead? --> | ||
| ## Steps to reproduce | ||
| <!-- Be as specific as possible so the root cause can be found. --> | ||
| 1. Go to ... | ||
| 2. Click / enter / submit ... | ||
| 3. See error: ... | ||
| ## Environment | ||
| <!-- TODO: Adjust these fields to match what makes sense for your project. | ||
| Delete fields that aren't relevant (e.g. delete Browser if it's a CLI tool). --> | ||
| - **Browser**: e.g. Chrome 124, Firefox 125, Safari 17 | ||
| - **OS**: e.g. macOS 14, Windows 11 | ||
| - **Device**: e.g. Desktop, iPhone 15 | ||
| - **Route / page**: e.g. `/contact` | ||
| ## Error output | ||
| <!-- Paste any error messages, console logs, or stack traces here. --> | ||
| ``` | ||
| TODO: paste error here or delete this block | ||
| ``` | ||
| ## Additional context | ||
| <!-- Screenshots, screen recordings, or anything else that helps. --> |
| --- | ||
| name: Feature request | ||
| about: Propose a new feature or improvement | ||
| title: "feat: [brief description]" | ||
| labels: enhancement | ||
| assignees: "" | ||
| --- | ||
| ## Summary | ||
| <!-- One sentence: what should be added or changed? --> | ||
| ## Motivation | ||
| <!-- Why does this need to exist? What problem does it solve or what value does it add? | ||
| Who benefits from this? --> | ||
| ## Proposed solution | ||
| <!-- How do you think this should work? Be as specific as you can. | ||
| This doesn't need to be a technical spec — describe the user experience. --> | ||
| ## Alternatives considered | ||
| <!-- Did you consider any other approaches? Why didn't they work? --> | ||
| ## Acceptance criteria | ||
| <!-- How will we know this feature is done and working correctly? | ||
| List the specific outcomes a reviewer should be able to verify. --> | ||
| - [ ] | ||
| - [ ] | ||
| ## Additional context | ||
| <!-- Mockups, references, related issues, or anything else that helps. --> | ||
| <!-- TODO: Link any related issues with "Related to #N" or "Depends on #N" --> |
| ## What changed | ||
| <!-- Describe what this PR does. Be specific — "Added email validation to contact form" not "fixed stuff". | ||
| Summarise the diff in bullet points. --> | ||
| - | ||
| ## Why | ||
| <!-- What problem does this solve? Link to a related issue if one exists. | ||
| Delete "Closes #" if there's no issue. --> | ||
| Closes #TODO | ||
| <!-- TODO: If this is not tied to an issue, replace the line above with a brief motivation sentence. --> | ||
| ## How to test | ||
| <!-- Step-by-step instructions for a reviewer to verify the changes work. | ||
| Write steps based on what was changed. --> | ||
| 1. | ||
| 2. | ||
| 3. Expected result: | ||
| ## Screenshots | ||
| <!-- TODO: Add screenshots or screen recordings for any UI changes. | ||
| Delete this section if there are no visual changes. --> | ||
| | Before | After | | ||
| |--------|-------| | ||
| | | | | ||
| ## Checklist | ||
| <!-- TODO: Check off items that apply. Delete irrelevant rows. --> | ||
| - [ ] Tests added or updated | ||
| - [ ] No hardcoded secrets or credentials | ||
| - [ ] Accessibility checked (keyboard nav, labels, contrast) | ||
| - [ ] Mobile tested (or not applicable) | ||
| - [ ] CHANGELOG.md updated under `[Unreleased]` |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
360504
4.51%2
-33.33%3
-25%114
23.91%1482
26.02%191
43.61%5
25%2
100%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed