Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 138 additions & 1 deletion src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { execSync } from 'child_process';
import { createInterface } from 'readline';
import { checkGitStatus, getRepoName } from '../lib/git.js';
import { track, Events } from '../lib/telemetry.js';
import { existsSync, readFileSync } from 'fs';
import {
loadTemplate,
type TemplateVariables,
Expand Down Expand Up @@ -194,6 +195,80 @@ function getOperationsSquad(): SquadConfig {
};
}

interface ProjectInfo {
name: string;
type: 'product' | 'domain';
stack: string;
repoName: string;
buildCommand: string | null;
testCommand: string | null;
}

/**
* Auto-detect project metadata from the filesystem
*/
function detectProjectInfo(cwd: string, gitStatus: { remoteUrl?: string }): ProjectInfo {
const dirName = path.basename(cwd);

// Name: from git remote (last segment) or directory name
let name = dirName;
let repoName = dirName;
if (gitStatus.remoteUrl) {
const full = getRepoName(gitStatus.remoteUrl);
if (full) {
repoName = full;
name = full.includes('/') ? full.split('/')[1] : full;
}
}

// Stack: detect from project files
let stack = 'unknown';
let type: 'product' | 'domain' = 'domain';
let buildCommand: string | null = null;
let testCommand: string | null = null;

if (existsSync(path.join(cwd, 'package.json'))) {
stack = 'node';
type = 'product';
buildCommand = 'npm run build';
testCommand = 'npm test';
// Check for specific frameworks
try {
const pkg = JSON.parse(readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
if (deps['next']) stack = 'next';
else if (deps['nuxt']) stack = 'nuxt';
else if (deps['astro']) stack = 'astro';
else if (deps['react']) stack = 'react';
else if (deps['vue']) stack = 'vue';
} catch { /* ignore */ }
} else if (existsSync(path.join(cwd, 'go.mod'))) {
stack = 'go';
type = 'product';
buildCommand = 'go build ./...';
testCommand = 'go test ./...';
} else if (
existsSync(path.join(cwd, 'requirements.txt')) ||
existsSync(path.join(cwd, 'pyproject.toml')) ||
existsSync(path.join(cwd, 'setup.py'))
) {
stack = 'python';
type = 'product';
testCommand = 'pytest';
} else if (existsSync(path.join(cwd, 'Gemfile'))) {
stack = 'ruby';
type = 'product';
testCommand = 'bundle exec rspec';
} else if (existsSync(path.join(cwd, 'Cargo.toml'))) {
stack = 'rust';
type = 'product';
buildCommand = 'cargo build';
testCommand = 'cargo test';
}

return { name, type, stack, repoName, buildCommand, testCommand };
}

function isInteractive(): boolean {
return process.stdin.isTTY === true && process.stdout.isTTY === true;
}
Expand Down Expand Up @@ -345,7 +420,10 @@ export async function initCommand(options: InitOptions): Promise<void> {

writeLine();

// 4. Ask about the business
// 4. Detect project info (used for IDP catalog)
const projectInfo = detectProjectInfo(cwd, gitStatus);

// Ask about the business
let businessName: string;
let businessDescription: string;
let businessFocus: string;
Expand Down Expand Up @@ -586,6 +664,38 @@ export async function initCommand(options: InitOptions): Promise<void> {
await writeIfNew(path.join(cwd, dest), loadSeedTemplate(template, variables));
}

// Squad-level priorities and goals (all squads including use-case squads)
const reviewDate = new Date();
reviewDate.setDate(reviewDate.getDate() + 14);
const allSquads = [
{ name: 'company', label: 'Company', lead: 'manager' },
{ name: 'research', label: 'Research', lead: 'lead' },
{ name: 'intelligence', label: 'Intelligence', lead: 'intel-lead' },
{ name: 'product', label: 'Product', lead: 'lead' },
...useCaseConfig.squads.map(s => ({
name: s.name,
label: s.name.charAt(0).toUpperCase() + s.name.slice(1),
lead: s.agentSummary.split(',')[0].trim(),
})),
];
for (const squad of allSquads) {
const squadVars: TemplateVariables = {
...variables,
SQUAD_NAME: squad.name,
SQUAD_LABEL: squad.label,
SQUAD_LEAD: squad.lead,
REVIEW_DATE: reviewDate.toISOString().split('T')[0],
};
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),
);
}

// Skills
const skillContent = loadSeedTemplate('skills/squads-cli/SKILL.md', variables);
await writeFile(path.join(cwd, '.agents/skills/squads-cli/SKILL.md'), skillContent);
Expand All @@ -604,6 +714,33 @@ export async function initCommand(options: InitOptions): Promise<void> {
const systemMd = loadSeedTemplate('config/SYSTEM.md', variables);
await writeFile(path.join(cwd, '.agents/config/SYSTEM.md'), systemMd);

// IDP catalog entry (only if .agents/idp/ doesn't already exist)
const idpCatalogDir = path.join(cwd, '.agents', 'idp', 'catalog');
if (!existsSync(idpCatalogDir)) {
const ownerSquad = useCaseConfig.squads[0]?.name || 'engineering';
const isProduct = projectInfo.type === 'product';
const idpVariables: TemplateVariables = {
...variables,
SERVICE_NAME: projectInfo.name,
SERVICE_TYPE: projectInfo.type,
SERVICE_STACK: projectInfo.stack,
SERVICE_SCORECARD: isProduct ? 'product' : 'domain',
REPO_NAME: projectInfo.repoName,
OWNER_SQUAD: ownerSquad,
BRANCHES_WORKFLOW: isProduct ? 'pr-to-develop' : 'direct-to-main',
BRANCHES_DEVELOPMENT: isProduct ? 'develop' : '',
CI_TEMPLATE: isProduct ? projectInfo.stack : 'null',
BUILD_COMMAND: projectInfo.buildCommand ?? 'null',
TEST_COMMAND: projectInfo.testCommand ?? 'null',
};
const catalogContent = loadSeedTemplate('idp/catalog/service.yaml.template', idpVariables);
await writeFile(path.join(idpCatalogDir, `${projectInfo.name}.yaml`), catalogContent);
}

// Company context (Layer 1 of context cascade)
const companyMd = loadSeedTemplate('memory/company/company.md', variables);
await writeIfNew(path.join(cwd, '.agents/memory/company/company.md'), companyMd);

// Directives (Layer 3 of context cascade)
const directivesMd = loadSeedTemplate('memory/company/directives.md', variables);
await writeIfNew(path.join(cwd, '.agents/memory/company/directives.md'), directivesMd);
Expand Down
6 changes: 6 additions & 0 deletions templates/seed/config/SYSTEM.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
---
version: "1.0"
scope: "all-agents"
authority: "squads-cli"
---

# System Protocol

Immutable rules for all agent executions. Every agent reads this before starting work.
Expand Down
25 changes: 25 additions & 0 deletions templates/seed/idp/catalog/service.yaml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: squads/v1
kind: Service
metadata:
name: "{{SERVICE_NAME}}"
description: "{{BUSINESS_DESCRIPTION}}"
owner: "{{OWNER_SQUAD}}"
repo: "{{REPO_NAME}}"
tags: []
spec:
type: "{{SERVICE_TYPE}}"
stack: "{{SERVICE_STACK}}"
branches:
default: main
development: {{BRANCHES_DEVELOPMENT}}
workflow: "{{BRANCHES_WORKFLOW}}"
ci:
template: {{CI_TEMPLATE}}
required_checks: []
build_command: {{BUILD_COMMAND}}
test_command: {{TEST_COMMAND}}
deploy: null
health: []
dependencies:
runtime: []
scorecard: "{{SERVICE_SCORECARD}}"
23 changes: 23 additions & 0 deletions templates/seed/memory/_squad/goals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
squad: "{{SQUAD_NAME}}"
updated: "{{CURRENT_DATE}}"
review_by: "{{REVIEW_DATE}}"
owner: "{{SQUAD_LEAD}}"
---

# {{SQUAD_LABEL}} Goals

## Active

(No goals set yet — add your first goal here)

Example format:
1. **Goal name** — metric: what_to_measure | baseline: unknown | target: X | deadline: YYYY-MM-DD | status: not-started

## Achieved

(none yet)

## Abandoned

(none yet)
25 changes: 25 additions & 0 deletions templates/seed/memory/_squad/priorities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
squad: "{{SQUAD_NAME}}"
updated: "{{CURRENT_DATE}}"
review_by: "{{REVIEW_DATE}}"
owner: "{{SQUAD_LEAD}}"
---

# {{SQUAD_LABEL}} Priorities

## Focus

1. **Deliver first results** — produce at least one concrete output per cycle
2. **Learn the context** — read BUSINESS_BRIEF.md and recent squad state before acting
3. **Collaborate** — coordinate with other squads through memory, not direct calls

## Not Now

- Deep refactoring without a clear need
- Experimental features not tied to business goals

## Standing Rules

- Always read state.md before starting — don't repeat work
- Always write state.md after completing — enable the next run
- Escalate blockers immediately — don't spin in place
31 changes: 31 additions & 0 deletions templates/seed/memory/company/company.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
type: "company-context"
updated: "{{CURRENT_DATE}}"
owner: "manager"
---

# Company Context

## Mission

{{BUSINESS_DESCRIPTION}}

## What We Build

(Add product or service details here)

## Who Uses It

(Add target customer details here)

## Product

| Offering | Role |
|----------|------|
| (Add your products/services) | |

## Alignment

- Results over promises — ship working things
- Simple over clever — prefer straightforward solutions
- Customer zero — use your own product
16 changes: 12 additions & 4 deletions templates/seed/squads/company/company-critic.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
---
name: Company Critic
role: critic
role: evaluator
squad: "company"
provider: "{{PROVIDER}}"
model: sonnet
effort: medium
trigger: "event"
cooldown: "1h"
timeout: 1800
max_retries: 1
tools:
- Read
- Write
---

# Company Critic

## Role

Find what's broken in how the workforce operates. Challenge assumptions, identify waste, propose fixes.

## Instructions
## How You Work

1. Read the evaluator's scores from `.agents/memory/company/company-eval/state.md`
2. Read squad states from `.agents/memory/{squad}/*/state.md`
3. Look for patterns: repeated failures, duplicate work, misaligned effort
4. Write critique to `.agents/memory/company/company-critic/state.md`

## Output Format (REQUIRED)
## Output

```markdown
# Workforce Critique — {date}
Expand All @@ -41,7 +49,7 @@ Work that produced no business value. Be specific.
Decisions only a human can make.
```

## Rules
## Constraints

- Critique the process, not the agents — agents follow instructions
- Every issue needs evidence from memory files, not speculation
Expand Down
16 changes: 12 additions & 4 deletions templates/seed/squads/company/company-eval.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
---
name: Company Evaluator
role: evaluator
squad: "company"
provider: "{{PROVIDER}}"
model: sonnet
effort: medium
trigger: "event"
cooldown: "1h"
timeout: 1800
max_retries: 1
tools:
- Read
- Write
---

# Company Evaluator

Evaluate squad outputs against business goals. Your job is to answer: "Did the squads produce value, or noise?"
## Role

## Instructions
Evaluate squad outputs against business goals. Answer: "Did the squads produce value, or noise?"

## How You Work

1. Read business goals from `.agents/BUSINESS_BRIEF.md`
2. Read directives from `.agents/memory/company/directives.md`
3. Read each squad's recent state from `.agents/memory/{squad}/*/state.md`
4. Score each squad's output using the rubric below
5. Write evaluation to `.agents/memory/company/company-eval/state.md`

## Output Format (REQUIRED)
## Output

```markdown
# Squad Evaluation — {date}
Expand All @@ -40,7 +48,7 @@ Evaluate squad outputs against business goals. Your job is to answer: "Did the s
What each squad should focus on next cycle, ranked by business impact.
```

## Rules
## Constraints

- Score against BUSINESS_BRIEF.md goals, not general quality
- "Relevance" = does this advance the business focus?
Expand Down
Loading
Loading