diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..b0efc7ce --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +node_modules +dist +.worktrees +*.tgz diff --git a/src/cli.ts b/src/cli.ts index 628a562b..cc07bfdc 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -327,8 +327,11 @@ Examples: return runCommand(target || null, { ...options, timeout: parseInt(options.timeout, 10) }); }); -// List command (removed — use status instead) -program.command('list', { hidden: true }).description('[removed]').action(removedCommand('list', 'Use: squads status')); +// List command — alias for status +program.command('list').description('List squads (alias for: squads status)').action(async () => { + const { statusCommand } = await import('./commands/status.js'); + return statusCommand(); +}); // Orchestrate command - lead-coordinated squad execution registerOrchestrateCommand(program); diff --git a/src/commands/init.ts b/src/commands/init.ts index b0372a11..a34e1382 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -262,6 +262,44 @@ async function fileExists(filePath: string): Promise { } } +/** + * Detect the primary technology stack from project files. + */ +async function detectStack(cwd: string): Promise { + const checks: Array<[string, string]> = [ + ['package.json', 'node'], + ['requirements.txt', 'python'], + ['pyproject.toml', 'python'], + ['setup.py', 'python'], + ['go.mod', 'go'], + ['Cargo.toml', 'rust'], + ['pom.xml', 'java'], + ['build.gradle', 'java'], + ['Gemfile', 'ruby'], + ['composer.json', 'php'], + ]; + + for (const [file, stack] of checks) { + try { + await fs.access(path.join(cwd, file)); + return stack; + } catch { + // not present + } + } + return 'unknown'; +} + +/** + * Slugify a name for use as a file/service identifier. + */ +function toServiceSlug(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-|-$/g, '') || 'service'; +} + /** * Load a seed template from templates/seed/ * Falls back to bundled templates in dist/templates/seed/ @@ -471,10 +509,13 @@ export async function initCommand(options: InitOptions): Promise { // 5. Create the seed const spinner = ora('Planting the seed...').start(); + const serviceSlug = toServiceSlug(businessName); try { // Only show PLACEHOLDER sentinel when user skipped the description in interactive mode const isPlaceholder = businessDescription.includes('add your business description'); + const detectedStack = await detectStack(cwd); + const repoUrl = gitStatus.remoteUrl || ''; const variables: TemplateVariables = { BUSINESS_NAME: businessName, BUSINESS_DESCRIPTION: businessDescription || `${businessName} — details to be added by the manager agent.`, @@ -488,6 +529,9 @@ export async function initCommand(options: InitOptions): Promise { PROVIDER: selectedProvider, PROVIDER_NAME: provider?.name || 'Unknown', CURRENT_DATE: new Date().toISOString().split('T')[0], + SERVICE_NAME: serviceSlug, + SERVICE_STACK: detectedStack, + REPO_URL: repoUrl, }; // Core directories (always created) @@ -511,6 +555,7 @@ export async function initCommand(options: InitOptions): Promise { '.agents/skills/squads-cli', '.agents/skills/gh', '.agents/config', + '.agents/idp/catalog', ]; // Add use-case specific directories @@ -612,6 +657,51 @@ export async function initCommand(options: InitOptions): Promise { const businessBrief = loadSeedTemplate('BUSINESS_BRIEF.md.template', variables); await writeFile(path.join(cwd, '.agents/BUSINESS_BRIEF.md'), businessBrief); + // company.md (L1 context layer — "Why" for all agents) + const companyMd = loadSeedTemplate('company.md', variables); + await writeIfNew(path.join(cwd, '.agents/company.md'), companyMd); + + // IDP service catalog entry (only if .agents/idp/ doesn't already exist) + const idpCatalogFile = path.join(cwd, `.agents/idp/catalog/${serviceSlug}.yaml`); + const idpAlreadyExists = await fileExists(path.join(cwd, '.agents/idp/catalog')); + // Check if ANY yaml already exists — skip catalog generation if so + let existingCatalog = false; + if (idpAlreadyExists) { + try { + const entries = await fs.readdir(path.join(cwd, '.agents/idp/catalog')); + existingCatalog = entries.some(f => f.endsWith('.yaml')); + } catch { + // ignore + } + } + if (!existingCatalog) { + const catalogContent = loadSeedTemplate('idp/catalog/service.yaml', variables); + await writeFile(idpCatalogFile, catalogContent); + } + + // priorities.md + goals.md — one per squad (writeIfNew so re-runs don't clobber) + const coreSquadNames = [ + { name: 'company', title: 'Company' }, + { name: 'research', title: 'Research' }, + { name: 'intelligence', title: 'Intelligence' }, + { name: 'product', title: 'Product' }, + ]; + const useCaseSquadNames = useCaseConfig.squads.map(s => ({ + name: s.name, + title: s.name.charAt(0).toUpperCase() + s.name.slice(1), + })); + for (const squad of [...coreSquadNames, ...useCaseSquadNames]) { + const squadVars = { ...variables, SQUAD_NAME: squad.name, SQUAD_NAME_TITLE: squad.title }; + await writeIfNew( + path.join(cwd, `.agents/memory/${squad.name}/priorities.md`), + loadSeedTemplate('memory/squad-priorities.md', squadVars) + ); + await writeIfNew( + path.join(cwd, `.agents/memory/${squad.name}/goals.md`), + loadSeedTemplate('memory/squad-goals.md', squadVars) + ); + } + // AGENTS.md (repo root — vendor-neutral agent instructions) const agentsMd = loadTemplate('core/AGENTS.md.template', variables); await writeIfNew(path.join(cwd, 'AGENTS.md'), agentsMd); @@ -703,7 +793,9 @@ export async function initCommand(options: InitOptions): Promise { } writeLine(chalk.dim(' • .agents/skills/ CLI + GitHub workflow skills')); - writeLine(chalk.dim(' • .agents/memory/ Persistent state')); + writeLine(chalk.dim(' • .agents/memory/ Persistent state (goals, priorities per squad)')); + writeLine(chalk.dim(' • .agents/company.md Company context (L1 — the "why")')); + writeLine(chalk.dim(` • .agents/idp/catalog/ Service catalog — ${serviceSlug}.yaml`)); writeLine(chalk.dim(' • .agents/BUSINESS_BRIEF.md')); writeLine(chalk.dim(' • AGENTS.md Agent instructions (vendor-neutral)')); if (selectedProvider === 'claude') { diff --git a/src/lib/setup-checks.ts b/src/lib/setup-checks.ts index 3b76b669..6e7b5ba6 100644 --- a/src/lib/setup-checks.ts +++ b/src/lib/setup-checks.ts @@ -260,12 +260,13 @@ export function checkProviderAuth(providerId: string): CheckResult { return { name: provider.name, status: 'ok' }; } - // Check CLI if required + // Check CLI if required — missing CLI is a warning (not an error) during init + // Users can scaffold first and install the provider CLI later if (provider.cliCheck) { if (!commandExists(provider.cliCheck)) { return { name: provider.name, - status: 'missing', + status: 'warning', message: `CLI not installed`, hint: provider.installCmd ? `Install: ${provider.installCmd}` : undefined, fixCommand: provider.installCmd, diff --git a/templates/seed/company.md b/templates/seed/company.md new file mode 100644 index 00000000..eae0401b --- /dev/null +++ b/templates/seed/company.md @@ -0,0 +1,26 @@ +--- +scope: all-agents +authority: company +updated: {{CURRENT_DATE}} +--- + +# Company Context + +## Mission + +{{BUSINESS_DESCRIPTION}} + +## What We Build + +{{BUSINESS_NAME}} — see `.agents/BUSINESS_BRIEF.md` for details. + +## Research Focus + +{{BUSINESS_FOCUS}} + +## How We Work + +- Agents commit all output — if it's not in git, it didn't happen +- Every claim needs a source; outputs must be actionable +- Read state before acting; write state after +- Escalate to human when spend > $50 or scope is unclear diff --git a/templates/seed/config/SYSTEM.md b/templates/seed/config/SYSTEM.md index ffa9a11d..0095ffd5 100644 --- a/templates/seed/config/SYSTEM.md +++ b/templates/seed/config/SYSTEM.md @@ -1,3 +1,9 @@ +--- +scope: all-agents +authority: immutable +version: "1.0" +--- + # System Protocol Immutable rules for all agent executions. Every agent reads this before starting work. diff --git a/templates/seed/idp/catalog/service.yaml b/templates/seed/idp/catalog/service.yaml new file mode 100644 index 00000000..8b9f7d48 --- /dev/null +++ b/templates/seed/idp/catalog/service.yaml @@ -0,0 +1,29 @@ +# Service catalog entry — auto-generated by squads init +# Edit this file to describe your service accurately. +# Docs: https://agents-squads.com/docs/idp/catalog + +apiVersion: squads/v1 +kind: Service +metadata: + name: {{SERVICE_NAME}} + description: {{BUSINESS_DESCRIPTION}} + owner: {{BUSINESS_NAME}} + repo: {{REPO_URL}} + tags: + - {{SERVICE_STACK}} +spec: + type: product + stack: {{SERVICE_STACK}} + branches: + default: main + development: develop + workflow: pr-to-develop + ci: + template: null + required_checks: [] + test_command: null + build_command: null + health: [] + dependencies: + runtime: [] + scorecard: default diff --git a/templates/seed/memory/squad-goals.md b/templates/seed/memory/squad-goals.md new file mode 100644 index 00000000..532af935 --- /dev/null +++ b/templates/seed/memory/squad-goals.md @@ -0,0 +1,18 @@ +--- +squad: {{SQUAD_NAME}} +updated: {{CURRENT_DATE}} +--- + +# {{SQUAD_NAME_TITLE}} Goals + +## Active + +(No active goals yet — add them as your squad starts working) + +## Achieved + +(none yet) + +## Abandoned + +(none yet) diff --git a/templates/seed/memory/squad-priorities.md b/templates/seed/memory/squad-priorities.md new file mode 100644 index 00000000..20c8683f --- /dev/null +++ b/templates/seed/memory/squad-priorities.md @@ -0,0 +1,22 @@ +--- +squad: {{SQUAD_NAME}} +updated: {{CURRENT_DATE}} +--- + +# {{SQUAD_NAME_TITLE}} Priorities + +## Focus + +1. **Deliver visible output on every run** — no run should end with nothing committed or noted +2. **Build on context** — read state.md before starting; don't repeat work already done + +## Not Now + +- Work outside squad scope +- Speculative improvements without clear business value + +## Standing Rules + +- Read `.agents/memory/{{SQUAD_NAME}}/*/state.md` before starting +- Write state.md after every run, even if nothing changed +- Every output must answer: "So what? What should we do next?" diff --git a/templates/seed/squads/company/company-critic.md b/templates/seed/squads/company/company-critic.md index cedb9c46..7e459fe6 100644 --- a/templates/seed/squads/company/company-critic.md +++ b/templates/seed/squads/company/company-critic.md @@ -1,6 +1,9 @@ --- name: Company Critic role: critic +squad: company +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: medium tools: diff --git a/templates/seed/squads/company/company-eval.md b/templates/seed/squads/company/company-eval.md index 6a581dec..f8e34e52 100644 --- a/templates/seed/squads/company/company-eval.md +++ b/templates/seed/squads/company/company-eval.md @@ -1,6 +1,9 @@ --- name: Company Evaluator role: evaluator +squad: company +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: medium tools: diff --git a/templates/seed/squads/company/event-dispatcher.md b/templates/seed/squads/company/event-dispatcher.md index ea8bee18..d59853cb 100644 --- a/templates/seed/squads/company/event-dispatcher.md +++ b/templates/seed/squads/company/event-dispatcher.md @@ -1,6 +1,9 @@ --- name: Event Dispatcher role: doer +squad: company +provider: {{PROVIDER}} +trigger: manual model: haiku effort: medium tools: diff --git a/templates/seed/squads/company/goal-tracker.md b/templates/seed/squads/company/goal-tracker.md index 37874620..7dc5cf4b 100644 --- a/templates/seed/squads/company/goal-tracker.md +++ b/templates/seed/squads/company/goal-tracker.md @@ -1,6 +1,9 @@ --- name: Goal Tracker role: doer +squad: company +provider: {{PROVIDER}} +trigger: manual model: haiku effort: medium tools: diff --git a/templates/seed/squads/company/manager.md b/templates/seed/squads/company/manager.md index 0ffd2ddf..288c0ba5 100644 --- a/templates/seed/squads/company/manager.md +++ b/templates/seed/squads/company/manager.md @@ -1,6 +1,9 @@ --- name: Manager role: lead +squad: company +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: high skills: diff --git a/templates/seed/squads/engineering/code-reviewer.md b/templates/seed/squads/engineering/code-reviewer.md index 8d087881..ba1abd3c 100644 --- a/templates/seed/squads/engineering/code-reviewer.md +++ b/templates/seed/squads/engineering/code-reviewer.md @@ -1,6 +1,9 @@ --- name: Code Reviewer role: evaluator +squad: engineering +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: medium --- diff --git a/templates/seed/squads/engineering/issue-solver.md b/templates/seed/squads/engineering/issue-solver.md index 14b78bd9..d12412dd 100644 --- a/templates/seed/squads/engineering/issue-solver.md +++ b/templates/seed/squads/engineering/issue-solver.md @@ -1,6 +1,9 @@ --- name: Issue Solver role: lead +squad: engineering +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: high skills: diff --git a/templates/seed/squads/engineering/test-writer.md b/templates/seed/squads/engineering/test-writer.md index aebed122..b7039307 100644 --- a/templates/seed/squads/engineering/test-writer.md +++ b/templates/seed/squads/engineering/test-writer.md @@ -1,6 +1,9 @@ --- name: Test Writer role: doer +squad: engineering +provider: {{PROVIDER}} +trigger: manual model: haiku effort: medium --- diff --git a/templates/seed/squads/intelligence/intel-critic.md b/templates/seed/squads/intelligence/intel-critic.md index 7fb2cd44..68134dff 100644 --- a/templates/seed/squads/intelligence/intel-critic.md +++ b/templates/seed/squads/intelligence/intel-critic.md @@ -1,6 +1,9 @@ --- name: Intel Critic role: critic +squad: intelligence +provider: {{PROVIDER}} +trigger: manual model: haiku effort: medium tools: diff --git a/templates/seed/squads/intelligence/intel-eval.md b/templates/seed/squads/intelligence/intel-eval.md index 89a0c8e5..1c8e0201 100644 --- a/templates/seed/squads/intelligence/intel-eval.md +++ b/templates/seed/squads/intelligence/intel-eval.md @@ -1,6 +1,9 @@ --- name: Intel Eval role: evaluator +squad: intelligence +provider: {{PROVIDER}} +trigger: manual model: haiku effort: medium tools: diff --git a/templates/seed/squads/intelligence/intel-lead.md b/templates/seed/squads/intelligence/intel-lead.md index 2279e154..fbbfd4a8 100644 --- a/templates/seed/squads/intelligence/intel-lead.md +++ b/templates/seed/squads/intelligence/intel-lead.md @@ -1,6 +1,9 @@ --- name: Intel Lead role: lead +squad: intelligence +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: high tools: diff --git a/templates/seed/squads/marketing/content-drafter.md b/templates/seed/squads/marketing/content-drafter.md index a296d6e2..c952ba59 100644 --- a/templates/seed/squads/marketing/content-drafter.md +++ b/templates/seed/squads/marketing/content-drafter.md @@ -1,6 +1,9 @@ --- name: Content Drafter role: lead +squad: marketing +provider: {{PROVIDER}} +trigger: manual model: haiku effort: medium skills: diff --git a/templates/seed/squads/marketing/growth-analyst.md b/templates/seed/squads/marketing/growth-analyst.md index a6235a42..c75ad4a3 100644 --- a/templates/seed/squads/marketing/growth-analyst.md +++ b/templates/seed/squads/marketing/growth-analyst.md @@ -1,6 +1,9 @@ --- name: Growth Analyst role: evaluator +squad: marketing +provider: {{PROVIDER}} +trigger: manual model: haiku effort: low --- diff --git a/templates/seed/squads/marketing/social-poster.md b/templates/seed/squads/marketing/social-poster.md index cd2f0edc..756c2d56 100644 --- a/templates/seed/squads/marketing/social-poster.md +++ b/templates/seed/squads/marketing/social-poster.md @@ -1,6 +1,9 @@ --- name: Social Poster role: doer +squad: marketing +provider: {{PROVIDER}} +trigger: manual model: haiku effort: low --- diff --git a/templates/seed/squads/operations/finance-tracker.md b/templates/seed/squads/operations/finance-tracker.md index 2b0e29a5..fbebc8d4 100644 --- a/templates/seed/squads/operations/finance-tracker.md +++ b/templates/seed/squads/operations/finance-tracker.md @@ -1,6 +1,9 @@ --- name: Finance Tracker role: doer +squad: operations +provider: {{PROVIDER}} +trigger: manual model: haiku effort: low --- diff --git a/templates/seed/squads/operations/goal-tracker.md b/templates/seed/squads/operations/goal-tracker.md index d91c71cf..b62d3615 100644 --- a/templates/seed/squads/operations/goal-tracker.md +++ b/templates/seed/squads/operations/goal-tracker.md @@ -1,6 +1,9 @@ --- name: Goal Tracker role: evaluator +squad: operations +provider: {{PROVIDER}} +trigger: manual model: haiku effort: low --- diff --git a/templates/seed/squads/operations/ops-lead.md b/templates/seed/squads/operations/ops-lead.md index 72b396e5..968344eb 100644 --- a/templates/seed/squads/operations/ops-lead.md +++ b/templates/seed/squads/operations/ops-lead.md @@ -1,6 +1,9 @@ --- name: Ops Lead role: lead +squad: operations +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: high skills: diff --git a/templates/seed/squads/product/lead.md b/templates/seed/squads/product/lead.md index a75560b7..81965669 100644 --- a/templates/seed/squads/product/lead.md +++ b/templates/seed/squads/product/lead.md @@ -1,6 +1,9 @@ --- name: Product Lead role: lead +squad: product +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: high tools: diff --git a/templates/seed/squads/product/scanner.md b/templates/seed/squads/product/scanner.md index 45506930..6369b250 100644 --- a/templates/seed/squads/product/scanner.md +++ b/templates/seed/squads/product/scanner.md @@ -1,6 +1,9 @@ --- name: Product Scanner role: doer +squad: product +provider: {{PROVIDER}} +trigger: manual model: haiku effort: medium tools: diff --git a/templates/seed/squads/product/worker.md b/templates/seed/squads/product/worker.md index dd9be5cf..b7bb90a1 100644 --- a/templates/seed/squads/product/worker.md +++ b/templates/seed/squads/product/worker.md @@ -1,6 +1,9 @@ --- name: Product Worker role: doer +squad: product +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: high tools: diff --git a/templates/seed/squads/research/analyst.md b/templates/seed/squads/research/analyst.md index da4b537b..3e2fc263 100644 --- a/templates/seed/squads/research/analyst.md +++ b/templates/seed/squads/research/analyst.md @@ -1,6 +1,9 @@ --- name: Analyst role: doer +squad: research +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: high tools: diff --git a/templates/seed/squads/research/lead.md b/templates/seed/squads/research/lead.md index b1a7fca1..2ca15d6f 100644 --- a/templates/seed/squads/research/lead.md +++ b/templates/seed/squads/research/lead.md @@ -1,6 +1,9 @@ --- name: Research Lead role: lead +squad: research +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: high tools: diff --git a/templates/seed/squads/research/synthesizer.md b/templates/seed/squads/research/synthesizer.md index 5afc2897..5ed618a8 100644 --- a/templates/seed/squads/research/synthesizer.md +++ b/templates/seed/squads/research/synthesizer.md @@ -1,6 +1,9 @@ --- name: Synthesizer role: doer +squad: research +provider: {{PROVIDER}} +trigger: manual model: sonnet effort: high tools: diff --git a/test/docker/Dockerfile.fresh-user b/test/docker/Dockerfile.fresh-user index e3a717c2..6859495d 100644 --- a/test/docker/Dockerfile.fresh-user +++ b/test/docker/Dockerfile.fresh-user @@ -1,12 +1,16 @@ # Simulates a brand new user installing squads-cli for the first time. # No config, no .agents dir, no history — completely clean environment. +# Builds from local source so tests exercise the current codebase. FROM node:22-slim RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/* -# Install squads-cli globally (as root, like sudo npm install -g) -RUN npm install -g squads-cli 2>&1 || echo "INSTALL FAILED" +# Build squads-cli from local source and install globally +WORKDIR /app +COPY . . +RUN npm ci && npm run build && npm pack +RUN npm install -g squads-cli-*.tgz # Verify it's installed RUN which squads && squads --version || echo "squads not found after install" diff --git a/test/setup-checks.test.ts b/test/setup-checks.test.ts index c5b0d1d5..6b4977b4 100644 --- a/test/setup-checks.test.ts +++ b/test/setup-checks.test.ts @@ -296,12 +296,12 @@ describe('setup-checks', () => { expect(result.message).toContain('Unknown provider'); }); - it('returns missing when provider CLI not installed', () => { - // commandExists('claude') -> false + it('returns warning when provider CLI not installed', () => { + // commandExists('claude') -> false — treated as warning, not error mockedExecSync.mockImplementationOnce(() => { throw new Error(); }); const result = checkProviderAuth('claude'); - expect(result.status).toBe('missing'); + expect(result.status).toBe('warning'); expect(result.fixCommand).toBeDefined(); }); @@ -338,11 +338,11 @@ describe('setup-checks', () => { } }); - it('returns missing when ollama CLI not installed', () => { + it('returns warning when ollama CLI not installed', () => { mockedExecSync.mockImplementationOnce(() => { throw new Error(); }); const result = checkProviderAuth('ollama'); - expect(result.status).toBe('missing'); + expect(result.status).toBe('warning'); }); });