teamcast 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,7 +69,7 @@ TeamCast now uses a canonical manifest shape with target-specific blocks:
69
69
  - `claude.agents.<name>` - native Claude Code runtime fields and doc outputs
70
70
  - `codex.agents.<name>` - native Codex runtime fields and TOML outputs
71
71
  - `<target>.agents.<name>.forge` - TeamCast-only metadata such as delegation graph
72
- - `project.environments` - active project environments such as `node`, `python` — auto-detected or explicit
72
+ - `project.environments` - active project environments (`node`, `python`, `go`, `rust`, `java`, `ruby`, `docker`, `terraform`) — auto-detected or explicit
73
73
 
74
74
  TeamCast includes a built-in registry of capabilities, traits, instruction fragments, policy fragments, models, and skills. These are not serialized into `teamcast.yaml`.
75
75
 
@@ -526,6 +526,50 @@ This means a **reviewer** (read + execute, no write) gets code patterns but NOT
526
526
 
527
527
  Custom agents work the same way — a `react-dev` with `write_files` + `execute` automatically gets the right fragments without any role-name matching.
528
528
 
529
+ #### Built-in environments
530
+
531
+ | Environment | Auto-detected by | Policy allows |
532
+ |---|---|---|
533
+ | `node` | `package.json` | `npm`, `npx`, `node` |
534
+ | `python` | `pyproject.toml`, `requirements.txt`, `setup.py` | `pytest`, `python`, `uv`, `poetry` |
535
+ | `go` | `go.mod` | `go build/test/run/vet/mod` |
536
+ | `rust` | `Cargo.toml` | `cargo`, `rustfmt`, `clippy` |
537
+ | `java` | `pom.xml`, `build.gradle` | `mvn`, `gradle`, `./gradlew` |
538
+ | `ruby` | `Gemfile` | `bundle`, `rake`, `rspec` |
539
+ | `docker` | `Dockerfile`, `docker-compose.yml` | `docker`, `docker compose` |
540
+ | `terraform` | `main.tf`, `terraform.tf` | `terraform init/plan/validate/fmt` |
541
+
542
+ #### Custom environments
543
+
544
+ Drop a YAML file into `.agentforge/environments/` to add a new environment or override a builtin:
545
+
546
+ ```yaml
547
+ # .agentforge/environments/bun.yaml
548
+ id: bun
549
+ description: "Bun runtime environment"
550
+ detect_files:
551
+ - bun.lockb
552
+ policy_rules:
553
+ sandbox:
554
+ enabled: true
555
+ allow:
556
+ - "Bash(bun *)"
557
+ instruction_fragments:
558
+ bun_patterns:
559
+ content: |
560
+ This project uses Bun.
561
+ Use `bun install`, `bun run`, and `bun test`.
562
+ requires_capabilities:
563
+ - read_files
564
+ ```
565
+
566
+ Reference it in `teamcast.yaml` by id:
567
+
568
+ ```yaml
569
+ project:
570
+ environments: [node, bun]
571
+ ```
572
+
529
573
  ### Instruction Layers
530
574
 
531
575
  Agent prompts are composed from three layers:
@@ -534,7 +578,7 @@ Agent prompts are composed from three layers:
534
578
  |-------|--------|-------|
535
579
  | **instruction_blocks** | `teamcast.yaml` or preset | Project-specific behavior, workflow rules |
536
580
  | **instruction_fragments** | Built-in registry | Reusable role patterns (e.g. `feature-developer-workflow`) |
537
- | **environment instructions** | Built-in environments | Toolchain best practices, injected by capability |
581
+ | **environment instructions** | Built-in + custom environments | Toolchain best practices, injected by capability |
538
582
 
539
583
  Presets provide sensible defaults for `instruction_blocks` and `instruction_fragments`. For deeper customization, edit `teamcast.yaml` and run `teamcast generate`.
540
584
 
@@ -7,6 +7,7 @@ import { hasErrors, printManifestValidationSummary, } from '../validator/reporte
7
7
  import { getTarget, getRegisteredTargetNames } from '../renderers/registry.js';
8
8
  import { applyEnvironmentInstructions, resolveEnvironmentIds, resolveEnvironmentPolicies, } from '../core/environment-resolver.js';
9
9
  import { checkManifestRegistry } from '../validator/checks/manifest-registry.js';
10
+ import { builtinResourceLoader } from '../registry/resource-loader.js';
10
11
  export function evaluateTeam(manifest, options) {
11
12
  const schemaResult = validateSchema(manifest);
12
13
  if (!schemaResult.valid) {
@@ -15,6 +16,8 @@ export function evaluateTeam(manifest, options) {
15
16
  validationResults: [],
16
17
  };
17
18
  }
19
+ if (options?.cwd)
20
+ builtinResourceLoader.loadUserResources(options.cwd);
18
21
  const rawManifest = applyDefaults(schemaResult.data);
19
22
  const resolvedManifest = options?.cwd ? resolveEnvironmentPolicies(rawManifest, options.cwd) : rawManifest;
20
23
  const manifestRegistryResults = checkManifestRegistry(resolvedManifest);
@@ -6,7 +6,7 @@ import { writeManifest } from '../manifest/writer.js';
6
6
  import { expandCapabilities } from '../core/capability-resolver.js';
7
7
  import { generate } from '../generator/index.js';
8
8
  import { defaultRegistry } from '../registry/index.js';
9
- import { printSuccess, printError, printHeader, printCommandSuccess, } from '../utils/chalk-helpers.js';
9
+ import { printSuccess, printError, printHeader, printCommandSuccess, printNextSteps, } from '../utils/chalk-helpers.js';
10
10
  import { getTarget, getRegisteredTargetNames } from '../renderers/registry.js';
11
11
  import { evaluateTeam, teamHasBlockingIssues, printManifestValidation, } from '../application/validate-team.js';
12
12
  import { promptConfirm, promptInput, promptList, promptCheckbox, } from '../utils/prompts.js';
@@ -248,6 +248,10 @@ export async function runAddAgentCommand(name, options) {
248
248
  const nextTeam = addAgentToTeam(team, name, agent);
249
249
  applyManifestChanges(cwd, manifest, targetName, nextTeam);
250
250
  printCommandSuccess(`Agent "${name}" added and configuration regenerated`);
251
+ printNextSteps([
252
+ `Open ${chalk.bold('teamcast.yaml')} and fill in agent instructions based on ${chalk.yellow('// TODO')} comments`,
253
+ `Run ${chalk.bold('teamcast generate')} to apply your changes`,
254
+ ]);
251
255
  }
252
256
  export async function runCreateSkillCommand(name, options) {
253
257
  const cwd = process.cwd();
@@ -407,7 +411,11 @@ async function promptAgentConfig(name, targetContext) {
407
411
  instructions: [
408
412
  {
409
413
  kind: 'behavior',
410
- content: `You are ${name}. Focus on the responsibilities described in your role and use your allowed tools appropriately.`,
414
+ content: `You are ${name}.\n// TODO: Describe the agent's core personality, rules, and constraints here.\n// Example: "You are a strict security auditor. Never trust user input."`,
415
+ },
416
+ {
417
+ kind: 'workflow',
418
+ content: `// TODO: Define the step-by-step process the agent should follow.\n// 1. Read the provided context.\n// 2. Perform analysis.\n// 3. Output the result.`,
411
419
  },
412
420
  ],
413
421
  };
@@ -1,6 +1,6 @@
1
1
  import { TARGET_NAMES, getManifestTargetConfig, setManifestTargetConfig } from '../manifest/targets.js';
2
2
  import { getEnvironment, detectEnvironments } from '../registry/environments.js';
3
- import { isEnvironmentId } from '../registry/types.js';
3
+ import { isEnvironmentId } from '../registry/environments.js';
4
4
  import { agentHasCapability } from './capability-resolver.js';
5
5
  /**
6
6
  * Resolves environment IDs from the manifest, combining:
@@ -80,31 +80,30 @@ function mergePoliciesSimple(base, extra) {
80
80
  }
81
81
  : undefined,
82
82
  hooks: base.hooks || extra.hooks
83
- ? {
84
- pre_tool_use: [...(base.hooks?.pre_tool_use ?? []), ...(extra.hooks?.pre_tool_use ?? [])].length > 0
85
- ? [...(base.hooks?.pre_tool_use ?? []), ...(extra.hooks?.pre_tool_use ?? [])]
86
- : undefined,
87
- post_tool_use: [...(base.hooks?.post_tool_use ?? []), ...(extra.hooks?.post_tool_use ?? [])].length > 0
88
- ? [...(base.hooks?.post_tool_use ?? []), ...(extra.hooks?.post_tool_use ?? [])]
89
- : undefined,
90
- notification: [...(base.hooks?.notification ?? []), ...(extra.hooks?.notification ?? [])].length > 0
91
- ? [...(base.hooks?.notification ?? []), ...(extra.hooks?.notification ?? [])]
92
- : undefined,
93
- }
83
+ ? (() => {
84
+ const pre = [...(base.hooks?.pre_tool_use ?? []), ...(extra.hooks?.pre_tool_use ?? [])];
85
+ const post = [...(base.hooks?.post_tool_use ?? []), ...(extra.hooks?.post_tool_use ?? [])];
86
+ const notif = [...(base.hooks?.notification ?? []), ...(extra.hooks?.notification ?? [])];
87
+ return {
88
+ pre_tool_use: pre.length > 0 ? pre : undefined,
89
+ post_tool_use: post.length > 0 ? post : undefined,
90
+ notification: notif.length > 0 ? notif : undefined,
91
+ };
92
+ })()
94
93
  : undefined,
95
94
  network: base.network || extra.network
96
- ? {
97
- allowed_domains: [...new Set([
95
+ ? (() => {
96
+ const domains = [...new Set([
98
97
  ...(base.network?.allowed_domains ?? []),
99
98
  ...(extra.network?.allowed_domains ?? []),
100
- ])].length > 0
101
- ? [...new Set([...(base.network?.allowed_domains ?? []), ...(extra.network?.allowed_domains ?? [])])]
102
- : undefined,
103
- }
104
- : undefined,
105
- assertions: [...(base.assertions ?? []), ...(extra.assertions ?? [])].length > 0
106
- ? [...(base.assertions ?? []), ...(extra.assertions ?? [])]
99
+ ])];
100
+ return { allowed_domains: domains.length > 0 ? domains : undefined };
101
+ })()
107
102
  : undefined,
103
+ assertions: (() => {
104
+ const merged = [...(base.assertions ?? []), ...(extra.assertions ?? [])];
105
+ return merged.length > 0 ? merged : undefined;
106
+ })(),
108
107
  };
109
108
  }
110
109
  function resolveTargetPolicies(envPolicies, targetConfig) {
@@ -3,7 +3,9 @@ import { buildGeneratedOutputs } from '../application/build-generated-files.js';
3
3
  import { getRegisteredTargetNames, getTarget } from '../renderers/registry.js';
4
4
  import { normalizeManifest } from '../manifest/normalize.js';
5
5
  import { applyEnvironmentInstructions, resolveEnvironmentIds, resolveEnvironmentPolicies, } from '../core/environment-resolver.js';
6
+ import { builtinResourceLoader } from '../registry/resource-loader.js';
6
7
  export function generate(manifest, options) {
8
+ builtinResourceLoader.loadUserResources(options.cwd);
7
9
  const rawManifest = resolveEnvironmentPolicies(applyDefaults(manifest), options.cwd);
8
10
  const envIds = resolveEnvironmentIds(rawManifest, options.cwd);
9
11
  const rawManifestRecord = rawManifest;
@@ -0,0 +1,65 @@
1
+ // Environment YAML schema — parse and convert YAML environment definitions
2
+ // to runtime EnvironmentDef objects.
3
+ import { existsSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { isCapability } from './types.js';
6
+ // --- Validation ---
7
+ export function parseEnvironmentYaml(raw) {
8
+ if (!raw || typeof raw !== 'object') {
9
+ throw new Error('Environment definition must be an object');
10
+ }
11
+ const obj = raw;
12
+ if (typeof obj.id !== 'string' || !obj.id) {
13
+ throw new Error('Environment definition requires a non-empty "id" field');
14
+ }
15
+ if (typeof obj.description !== 'string') {
16
+ throw new Error(`Environment "${obj.id}": "description" must be a string`);
17
+ }
18
+ if (obj.detect_files !== undefined) {
19
+ if (!Array.isArray(obj.detect_files) || !obj.detect_files.every((f) => typeof f === 'string')) {
20
+ throw new Error(`Environment "${obj.id}": "detect_files" must be a string array`);
21
+ }
22
+ }
23
+ if (!obj.policy_rules || typeof obj.policy_rules !== 'object') {
24
+ throw new Error(`Environment "${obj.id}": "policy_rules" must be an object`);
25
+ }
26
+ const policies = obj.policy_rules;
27
+ if (policies.allow !== undefined) {
28
+ if (!Array.isArray(policies.allow) || !policies.allow.every((a) => typeof a === 'string')) {
29
+ throw new Error(`Environment "${obj.id}": "policy_rules.allow" must be a string array`);
30
+ }
31
+ }
32
+ if (!obj.instruction_fragments || typeof obj.instruction_fragments !== 'object') {
33
+ throw new Error(`Environment "${obj.id}": "instruction_fragments" must be an object`);
34
+ }
35
+ return obj;
36
+ }
37
+ // --- Conversion to runtime EnvironmentDef ---
38
+ function toEnvironmentInstruction(value) {
39
+ if (typeof value === 'string')
40
+ return value;
41
+ return {
42
+ content: value.content,
43
+ requires_capabilities: value.requires_capabilities.filter(isCapability),
44
+ };
45
+ }
46
+ export function environmentYamlToDef(yaml) {
47
+ const fragments = {};
48
+ for (const [key, value] of Object.entries(yaml.instruction_fragments)) {
49
+ fragments[key] = toEnvironmentInstruction(value);
50
+ }
51
+ const def = {
52
+ id: yaml.id,
53
+ description: yaml.description,
54
+ policyRules: {
55
+ sandbox: yaml.policy_rules.sandbox,
56
+ allow: yaml.policy_rules.allow,
57
+ },
58
+ instructionFragments: fragments,
59
+ };
60
+ if (yaml.detect_files?.length) {
61
+ const files = yaml.detect_files;
62
+ def.detect = (cwd) => files.some((file) => existsSync(join(cwd, file)));
63
+ }
64
+ return def;
65
+ }
@@ -1,105 +1,17 @@
1
- import { existsSync } from 'fs';
2
- import { join } from 'path';
3
- const ENVIRONMENTS = {
4
- node: {
5
- id: 'node',
6
- description: 'Node.js environment, auto-detected via package.json',
7
- detect: (cwd) => existsSync(join(cwd, 'package.json')),
8
- policyRules: {
9
- sandbox: { enabled: true },
10
- allow: [
11
- 'Bash(npm run *)',
12
- 'Bash(npm test *)',
13
- 'Bash(npx *)',
14
- 'Bash(npm install)',
15
- 'Bash(node *)',
16
- ],
17
- },
18
- instructionFragments: {
19
- node_code_patterns: {
20
- content: [
21
- 'This is a Node.js project.',
22
- 'Use ESM module syntax (import/export). All relative imports must use .js extensions.',
23
- 'Prefer named exports over default exports.',
24
- 'Use TypeScript strict mode when tsconfig.json is present.',
25
- ].join('\n'),
26
- requires_capabilities: ['read_files'],
27
- },
28
- node_development: {
29
- content: [
30
- 'Install dependencies with `npm install`.',
31
- 'Use `npm run <script>` to execute package.json scripts.',
32
- 'Prefer async/await over raw Promises or callbacks.',
33
- 'Handle errors at system boundaries. Use typed error classes where the project defines them.',
34
- ].join('\n'),
35
- requires_capabilities: ['write_files'],
36
- },
37
- node_testing: {
38
- content: [
39
- 'Run tests with `npm test`.',
40
- 'Run a specific test file with `npx vitest run <path>` (vitest) or `npx jest <path>` (jest).',
41
- 'Always run tests after making changes to verify nothing broke.',
42
- 'Follow existing test patterns: check the tests/ directory for conventions before writing new tests.',
43
- ].join('\n'),
44
- requires_capabilities: ['execute', 'write_files'],
45
- },
46
- },
47
- },
48
- python: {
49
- id: 'python',
50
- description: 'Python environment, auto-detected via pyproject.toml or requirements.txt',
51
- detect: (cwd) => existsSync(join(cwd, 'pyproject.toml')) ||
52
- existsSync(join(cwd, 'requirements.txt')) ||
53
- existsSync(join(cwd, 'setup.py')),
54
- policyRules: {
55
- sandbox: { enabled: true },
56
- allow: [
57
- 'Bash(pytest *)',
58
- 'Bash(python -m pytest *)',
59
- 'Bash(uv run *)',
60
- 'Bash(poetry run *)',
61
- 'Bash(python *)',
62
- ],
63
- },
64
- instructionFragments: {
65
- python_code_patterns: {
66
- content: [
67
- 'This is a Python project.',
68
- 'Follow PEP 8 style conventions.',
69
- 'Use type hints for function signatures and class attributes.',
70
- 'Prefer pathlib.Path over os.path for file operations.',
71
- ].join('\n'),
72
- requires_capabilities: ['read_files'],
73
- },
74
- python_development: {
75
- content: [
76
- 'If using poetry: `poetry install` and `poetry run <cmd>`. If using uv: `uv sync` and `uv run <cmd>`.',
77
- 'Otherwise use pip and virtualenv.',
78
- 'Use structured logging (logging module) instead of print statements.',
79
- 'Handle exceptions with specific types, not bare except clauses.',
80
- ].join('\n'),
81
- requires_capabilities: ['write_files'],
82
- },
83
- python_testing: {
84
- content: [
85
- 'Run tests with `pytest`. If using poetry or uv, prefix with `poetry run` or `uv run`.',
86
- 'Run a specific test: `pytest <path>::<test_name>`.',
87
- 'Always run tests after changes. Follow existing test patterns in the tests/ directory.',
88
- 'Use fixtures for shared setup. Prefer parametrize for similar test cases.',
89
- ].join('\n'),
90
- requires_capabilities: ['execute', 'write_files'],
91
- },
92
- },
93
- },
94
- };
1
+ // Environment registry delegates to ResourceLoader (YAML is the sole source).
2
+ import { builtinResourceLoader } from './resource-loader.js';
3
+ export function isEnvironmentId(value) {
4
+ return builtinResourceLoader.hasEnvironment(value);
5
+ }
95
6
  export function getEnvironment(id) {
96
- return ENVIRONMENTS[id];
7
+ const env = builtinResourceLoader.getEnvironment(id);
8
+ if (!env)
9
+ throw new Error(`Unknown environment "${id}"`);
10
+ return env;
97
11
  }
98
12
  export function listEnvironments() {
99
- return Object.values(ENVIRONMENTS);
13
+ return builtinResourceLoader.listEnvironments();
100
14
  }
101
15
  export function detectEnvironments(cwd) {
102
- return Object.values(ENVIRONMENTS)
103
- .filter((env) => env.detect(cwd))
104
- .map((env) => env.id);
16
+ return builtinResourceLoader.detectEnvironments(cwd);
105
17
  }
@@ -0,0 +1,78 @@
1
+ // ResourceLoader — scans directories for YAML resource files and registers them.
2
+ import { readFileSync, readdirSync } from 'fs';
3
+ import { dirname, join } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { parse } from 'yaml';
6
+ import { parseEnvironmentYaml, environmentYamlToDef } from './environment-schema.js';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const BUILTIN_ENVIRONMENTS_DIR = join(__dirname, '../../templates/environments');
10
+ /** Check whether an environment matches the given cwd. */
11
+ function matchesEnv(env, cwd) {
12
+ return env.detect ? env.detect(cwd) : false;
13
+ }
14
+ export class ResourceLoader {
15
+ environments = new Map();
16
+ loadedDirs = new Set();
17
+ /** Load all *.yaml files from a directory as environment definitions.
18
+ * Each directory is only loaded once — adding files after the first call has no effect. */
19
+ loadEnvironmentsFromDir(dir, allowOverride = false) {
20
+ if (this.loadedDirs.has(dir))
21
+ return;
22
+ this.loadedDirs.add(dir);
23
+ let files;
24
+ try {
25
+ files = readdirSync(dir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'));
26
+ }
27
+ catch {
28
+ return; // Directory does not exist or is inaccessible
29
+ }
30
+ for (const file of files) {
31
+ const filePath = join(dir, file);
32
+ try {
33
+ const raw = parse(readFileSync(filePath, 'utf-8'));
34
+ const yaml = parseEnvironmentYaml(raw);
35
+ const def = environmentYamlToDef(yaml);
36
+ if (this.environments.has(def.id) && !allowOverride)
37
+ continue;
38
+ this.environments.set(def.id, def);
39
+ }
40
+ catch (err) {
41
+ if (allowOverride) {
42
+ // User-defined file — surface the error so the user can fix it
43
+ process.stderr.write(`[agentforge] Warning: skipping "${filePath}": ${err instanceof Error ? err.message : String(err)}\n`);
44
+ }
45
+ // Builtin files should never fail — skip silently
46
+ }
47
+ }
48
+ }
49
+ /** Load user-defined resources from a project's .agentforge/ directory. */
50
+ loadUserResources(projectDir) {
51
+ const envDir = join(projectDir, '.agentforge', 'environments');
52
+ this.loadEnvironmentsFromDir(envDir, true);
53
+ }
54
+ hasEnvironment(id) {
55
+ return this.environments.has(id);
56
+ }
57
+ getEnvironment(id) {
58
+ return this.environments.get(id);
59
+ }
60
+ listEnvironments() {
61
+ return [...this.environments.values()];
62
+ }
63
+ listEnvironmentIds() {
64
+ return [...this.environments.keys()];
65
+ }
66
+ detectEnvironments(cwd) {
67
+ return this.listEnvironments()
68
+ .filter((env) => matchesEnv(env, cwd))
69
+ .map((env) => env.id);
70
+ }
71
+ }
72
+ // Singleton — loads builtin environments from templates/environments/
73
+ function createBuiltinLoader() {
74
+ const loader = new ResourceLoader();
75
+ loader.loadEnvironmentsFromDir(BUILTIN_ENVIRONMENTS_DIR);
76
+ return loader;
77
+ }
78
+ export const builtinResourceLoader = createBuiltinLoader();
@@ -14,7 +14,74 @@ export const CAPABILITY_IDS = [
14
14
  export function isCapability(value) {
15
15
  return CAPABILITY_IDS.includes(value);
16
16
  }
17
- export const ENVIRONMENT_IDS = ['node', 'python'];
18
- export function isEnvironmentId(value) {
19
- return ENVIRONMENT_IDS.includes(value);
20
- }
17
+ // --- Capability Trait (named bundle of capabilities) ---
18
+ export const BUILTIN_CAPABILITY_TRAIT_IDS = [
19
+ 'base-read',
20
+ 'file-authoring',
21
+ 'command-execution',
22
+ 'web-research',
23
+ 'delegation',
24
+ 'interaction',
25
+ 'notebook-editing',
26
+ 'no-file-edits',
27
+ 'no-commands',
28
+ 'no-web',
29
+ 'full-access',
30
+ ];
31
+ // --- Policy Fragment ---
32
+ export const BUILTIN_POLICY_FRAGMENT_IDS = [
33
+ 'allow-git-read',
34
+ 'allow-git-write',
35
+ 'ask-git-push',
36
+ 'deny-destructive-shell',
37
+ 'deny-network-downloads',
38
+ 'deny-dynamic-exec',
39
+ 'deny-env-files',
40
+ 'sandbox-default',
41
+ ];
42
+ export const BUILTIN_INSTRUCTION_FRAGMENT_IDS = [
43
+ 'coordination-core',
44
+ 'delegate-first',
45
+ 'planning-core',
46
+ 'planning-read-only',
47
+ 'research-core',
48
+ 'research-citation',
49
+ 'research-no-file-edits',
50
+ 'development-core',
51
+ 'development-workflow',
52
+ 'tester-core',
53
+ 'tester-read-only',
54
+ 'review-core',
55
+ 'review-feedback',
56
+ 'security-audit-core',
57
+ 'security-audit-severity',
58
+ 'research-handoff',
59
+ 'secure-planning',
60
+ 'secure-development',
61
+ 'secure-development-tests',
62
+ 'security-review-gate',
63
+ 'post-audit-review',
64
+ 'solo-dev-core',
65
+ 'solo-dev-workflow',
66
+ 'solo-dev-style',
67
+ 'feature-orchestrator-workflow',
68
+ 'feature-orchestrator-output',
69
+ 'feature-planner-workflow',
70
+ 'feature-planner-read-only',
71
+ 'feature-developer-core',
72
+ 'feature-developer-workflow',
73
+ 'feature-developer-summary',
74
+ 'feature-reviewer-checklist',
75
+ 'feature-reviewer-style',
76
+ 'research-orchestrator-core',
77
+ 'research-orchestrator-workflow',
78
+ 'research-orchestrator-output',
79
+ 'research-planner-core',
80
+ 'research-planner-constraints',
81
+ 'research-developer-core',
82
+ 'research-developer-tests',
83
+ 'secure-orchestrator-core',
84
+ 'secure-orchestrator-workflow',
85
+ 'secure-orchestrator-gate',
86
+ 'post-audit-review-core',
87
+ ];
@@ -5,7 +5,7 @@ function getSkillBasePath(skillId) {
5
5
  }
6
6
  // --- Frontmatter (Codex: name + description only) ---
7
7
  function buildFrontmatter(name, description) {
8
- return ['---', `name: ${name}`, `description: ${description}`, '---'].join('\n');
8
+ return ['---', `name: ${JSON.stringify(name)}`, `description: ${JSON.stringify(description)}`, '---'].join('\n');
9
9
  }
10
10
  // --- Stub for unknown skills ---
11
11
  function generateSkillStub(skillName) {
@@ -1,7 +1,7 @@
1
1
  import { isCapabilityTraitName } from '../../registry/traits.js';
2
2
  import { isPolicyFragmentId } from '../../registry/policy-fragments.js';
3
3
  import { isInstructionFragmentId } from '../../registry/instruction-fragments.js';
4
- import { isEnvironmentId } from '../../registry/types.js';
4
+ import { isEnvironmentId } from '../../registry/environments.js';
5
5
  import { getManifestTargetEntries } from '../../manifest/targets.js';
6
6
  /**
7
7
  * Pre-normalization manifest-level registry checks.
@@ -66,5 +66,24 @@ export function checkTeamGraphEnhanced(team) {
66
66
  message: `Multiple root agents detected: ${roots.join(', ')} — consider a single orchestrator entry point`,
67
67
  });
68
68
  }
69
+ // HANDOFF_CAPABILITY_MISMATCH — delegating to an agent with no tools
70
+ for (const [agentId, agent] of agentEntries) {
71
+ for (const target of agent.metadata?.handoffs ?? []) {
72
+ const targetAgent = team.agents[target];
73
+ if (!targetAgent)
74
+ continue; // already caught by checkHandoffGraph
75
+ const targetTools = targetAgent.runtime.tools ?? [];
76
+ if (targetTools.length === 0) {
77
+ results.push({
78
+ severity: 'warning',
79
+ category: 'Team graph',
80
+ code: 'HANDOFF_CAPABILITY_MISMATCH',
81
+ phase: 'team-graph',
82
+ message: `Agent "${agentId}" hands off to "${target}" but "${target}" has no capabilities — delegation may be ineffective`,
83
+ agent: agentId,
84
+ });
85
+ }
86
+ }
87
+ }
69
88
  return results;
70
89
  }
@@ -15,8 +15,8 @@ import { checkSkillRequirements } from './checks/skill-requirements.js';
15
15
  import { checkMcpServers } from './checks/mcp.js';
16
16
  import { checkTeamGraphEnhanced } from './checks/team-graph-enhanced.js';
17
17
  const CHECKERS = (skillMap, targetName) => [
18
- checkRegistryReferences,
19
- checkEnvironments,
18
+ checkRegistryReferences, // Phase 1
19
+ checkEnvironments, // Phase 9
20
20
  checkTraitCapabilities, // Phase 2
21
21
  (team) => checkCapabilityTools(team, skillMap), // Phase 3
22
22
  (team) => checkHandoffGraph(team, skillMap),
@@ -76,7 +76,8 @@ export async function runWizard(options) {
76
76
  printCommandSuccess(`Agent team initialized for project "${rawManifest.project.name}"`);
77
77
  printManifestValidation(validation);
78
78
  printNextSteps([
79
+ `Open ${chalk.bold('teamcast.yaml')} and fill in agent instructions based on ${chalk.yellow('// TODO')} comments`,
79
80
  `${chalk.bold('teamcast explain')} - view the team structure`,
80
- `Edit ${chalk.bold('teamcast.yaml')} and run ${chalk.bold('teamcast generate')} to apply changes`,
81
+ `Run ${chalk.bold('teamcast generate')} to apply your changes`,
81
82
  ]);
82
83
  }
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { detectEnvironments, listEnvironments } from '../../registry/environments.js';
3
- import { isEnvironmentId } from '../../registry/types.js';
3
+ import { isEnvironmentId } from '../../registry/environments.js';
4
4
  import { promptCheckbox } from '../../utils/prompts.js';
5
5
  function mergeEnvironmentIds(...lists) {
6
6
  return [...new Set(lists.flatMap((list) => list ?? []))].filter(isEnvironmentId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamcast",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "YAML-driven CLI to design, validate, and generate multi-target agent teams for Claude Code and Codex",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,36 @@
1
+ id: docker
2
+ description: "Docker environment, auto-detected via Dockerfile"
3
+ detect_files:
4
+ - Dockerfile
5
+ - docker-compose.yml
6
+ - docker-compose.yaml
7
+ - compose.yml
8
+ - compose.yaml
9
+ policy_rules:
10
+ sandbox:
11
+ enabled: true
12
+ allow:
13
+ - "Bash(docker build *)"
14
+ - "Bash(docker run *)"
15
+ - "Bash(docker compose *)"
16
+ - "Bash(docker-compose *)"
17
+ - "Bash(docker ps *)"
18
+ - "Bash(docker logs *)"
19
+ - "Bash(docker images *)"
20
+ instruction_fragments:
21
+ docker_patterns:
22
+ content: |
23
+ This project uses Docker.
24
+ Use multi-stage builds to minimize image size.
25
+ Prefer alpine or slim base images where practical.
26
+ Use .dockerignore to exclude unnecessary files from build context.
27
+ requires_capabilities:
28
+ - read_files
29
+ docker_development:
30
+ content: |
31
+ Build images with `docker build -t <tag> .`.
32
+ Use `docker compose up` for multi-container setups.
33
+ Pin base image versions for reproducible builds.
34
+ Order Dockerfile instructions to maximize layer caching (dependencies before source code).
35
+ requires_capabilities:
36
+ - write_files
@@ -0,0 +1,40 @@
1
+ id: go
2
+ description: "Go environment, auto-detected via go.mod"
3
+ detect_files:
4
+ - go.mod
5
+ policy_rules:
6
+ sandbox:
7
+ enabled: true
8
+ allow:
9
+ - "Bash(go build *)"
10
+ - "Bash(go test *)"
11
+ - "Bash(go run *)"
12
+ - "Bash(go vet *)"
13
+ - "Bash(go mod *)"
14
+ - "Bash(go generate *)"
15
+ instruction_fragments:
16
+ go_code_patterns:
17
+ content: |
18
+ This is a Go project.
19
+ Follow standard Go conventions and idiomatic patterns.
20
+ Use gofmt/goimports for formatting.
21
+ Prefer short variable names in small scopes, descriptive names in larger scopes.
22
+ requires_capabilities:
23
+ - read_files
24
+ go_development:
25
+ content: |
26
+ Use `go build ./...` to compile all packages.
27
+ Use `go mod tidy` to clean up dependencies.
28
+ Handle errors explicitly — do not ignore returned errors.
29
+ Prefer returning errors over panicking.
30
+ requires_capabilities:
31
+ - write_files
32
+ go_testing:
33
+ content: |
34
+ Run tests with `go test ./...`.
35
+ Run a specific test: `go test -run TestName ./path/to/package`.
36
+ Use table-driven tests for multiple cases.
37
+ Always run tests after changes to verify nothing broke.
38
+ requires_capabilities:
39
+ - execute
40
+ - write_files
@@ -0,0 +1,41 @@
1
+ id: java
2
+ description: "Java environment, auto-detected via pom.xml or build.gradle"
3
+ detect_files:
4
+ - pom.xml
5
+ - build.gradle
6
+ - build.gradle.kts
7
+ policy_rules:
8
+ sandbox:
9
+ enabled: true
10
+ allow:
11
+ - "Bash(mvn *)"
12
+ - "Bash(gradle *)"
13
+ - "Bash(./gradlew *)"
14
+ - "Bash(java *)"
15
+ - "Bash(javac *)"
16
+ instruction_fragments:
17
+ java_code_patterns:
18
+ content: |
19
+ This is a Java project.
20
+ Follow standard Java naming conventions (camelCase for methods, PascalCase for classes).
21
+ Use appropriate access modifiers.
22
+ Prefer composition over inheritance where practical.
23
+ requires_capabilities:
24
+ - read_files
25
+ java_development:
26
+ content: |
27
+ If using Maven: `mvn compile` to build, `mvn package` to create artifacts.
28
+ If using Gradle: `./gradlew build` or `gradle build`.
29
+ Handle exceptions with specific types, not bare catch blocks.
30
+ Use try-with-resources for AutoCloseable resources.
31
+ requires_capabilities:
32
+ - write_files
33
+ java_testing:
34
+ content: |
35
+ Run tests with `mvn test` (Maven) or `./gradlew test` (Gradle).
36
+ Run a specific test: `mvn -Dtest=TestClassName test` or `./gradlew test --tests TestClassName`.
37
+ Use JUnit 5 annotations. Follow existing test patterns in the project.
38
+ Always run tests after changes to verify nothing broke.
39
+ requires_capabilities:
40
+ - execute
41
+ - write_files
@@ -0,0 +1,39 @@
1
+ id: node
2
+ description: "Node.js environment, auto-detected via package.json"
3
+ detect_files:
4
+ - package.json
5
+ policy_rules:
6
+ sandbox:
7
+ enabled: true
8
+ allow:
9
+ - "Bash(npm run *)"
10
+ - "Bash(npm test *)"
11
+ - "Bash(npx *)"
12
+ - "Bash(npm install)"
13
+ - "Bash(node *)"
14
+ instruction_fragments:
15
+ node_code_patterns:
16
+ content: |
17
+ This is a Node.js project.
18
+ Use ESM module syntax (import/export). All relative imports must use .js extensions.
19
+ Prefer named exports over default exports.
20
+ Use TypeScript strict mode when tsconfig.json is present.
21
+ requires_capabilities:
22
+ - read_files
23
+ node_development:
24
+ content: |
25
+ Install dependencies with `npm install`.
26
+ Use `npm run <script>` to execute package.json scripts.
27
+ Prefer async/await over raw Promises or callbacks.
28
+ Handle errors at system boundaries. Use typed error classes where the project defines them.
29
+ requires_capabilities:
30
+ - write_files
31
+ node_testing:
32
+ content: |
33
+ Run tests with `npm test`.
34
+ Run a specific test file with `npx vitest run <path>` (vitest) or `npx jest <path>` (jest).
35
+ Always run tests after making changes to verify nothing broke.
36
+ Follow existing test patterns: check the tests/ directory for conventions before writing new tests.
37
+ requires_capabilities:
38
+ - execute
39
+ - write_files
@@ -0,0 +1,41 @@
1
+ id: python
2
+ description: "Python environment, auto-detected via pyproject.toml or requirements.txt"
3
+ detect_files:
4
+ - pyproject.toml
5
+ - requirements.txt
6
+ - setup.py
7
+ policy_rules:
8
+ sandbox:
9
+ enabled: true
10
+ allow:
11
+ - "Bash(pytest *)"
12
+ - "Bash(python -m pytest *)"
13
+ - "Bash(uv run *)"
14
+ - "Bash(poetry run *)"
15
+ - "Bash(python *)"
16
+ instruction_fragments:
17
+ python_code_patterns:
18
+ content: |
19
+ This is a Python project.
20
+ Follow PEP 8 style conventions.
21
+ Use type hints for function signatures and class attributes.
22
+ Prefer pathlib.Path over os.path for file operations.
23
+ requires_capabilities:
24
+ - read_files
25
+ python_development:
26
+ content: |
27
+ If using poetry: `poetry install` and `poetry run <cmd>`. If using uv: `uv sync` and `uv run <cmd>`.
28
+ Otherwise use pip and virtualenv.
29
+ Use structured logging (logging module) instead of print statements.
30
+ Handle exceptions with specific types, not bare except clauses.
31
+ requires_capabilities:
32
+ - write_files
33
+ python_testing:
34
+ content: |
35
+ Run tests with `pytest`. If using poetry or uv, prefix with `poetry run` or `uv run`.
36
+ Run a specific test: `pytest <path>::<test_name>`.
37
+ Always run tests after changes. Follow existing test patterns in the tests/ directory.
38
+ Use fixtures for shared setup. Prefer parametrize for similar test cases.
39
+ requires_capabilities:
40
+ - execute
41
+ - write_files
@@ -0,0 +1,39 @@
1
+ id: ruby
2
+ description: "Ruby environment, auto-detected via Gemfile"
3
+ detect_files:
4
+ - Gemfile
5
+ policy_rules:
6
+ sandbox:
7
+ enabled: true
8
+ allow:
9
+ - "Bash(bundle *)"
10
+ - "Bash(rake *)"
11
+ - "Bash(rspec *)"
12
+ - "Bash(ruby *)"
13
+ - "Bash(rails *)"
14
+ instruction_fragments:
15
+ ruby_code_patterns:
16
+ content: |
17
+ This is a Ruby project.
18
+ Follow Ruby style conventions (snake_case for methods/variables, PascalCase for classes).
19
+ Use frozen string literal comments where the project follows that convention.
20
+ Prefer blocks and enumerators over manual loops.
21
+ requires_capabilities:
22
+ - read_files
23
+ ruby_development:
24
+ content: |
25
+ Install dependencies with `bundle install`.
26
+ Use `bundle exec` to run commands in the context of the bundle.
27
+ Prefer keyword arguments for methods with multiple optional parameters.
28
+ Handle errors with specific exception classes.
29
+ requires_capabilities:
30
+ - write_files
31
+ ruby_testing:
32
+ content: |
33
+ Run tests with `bundle exec rspec` (RSpec) or `bundle exec rake test` (Minitest).
34
+ Run a specific test: `bundle exec rspec path/to/spec.rb:LINE`.
35
+ Follow existing test patterns. Use shared examples for reusable test behaviors.
36
+ Always run tests after changes to verify nothing broke.
37
+ requires_capabilities:
38
+ - execute
39
+ - write_files
@@ -0,0 +1,41 @@
1
+ id: rust
2
+ description: "Rust environment, auto-detected via Cargo.toml"
3
+ detect_files:
4
+ - Cargo.toml
5
+ policy_rules:
6
+ sandbox:
7
+ enabled: true
8
+ allow:
9
+ - "Bash(cargo build *)"
10
+ - "Bash(cargo test *)"
11
+ - "Bash(cargo run *)"
12
+ - "Bash(cargo clippy *)"
13
+ - "Bash(cargo fmt *)"
14
+ - "Bash(cargo check *)"
15
+ - "Bash(rustfmt *)"
16
+ instruction_fragments:
17
+ rust_code_patterns:
18
+ content: |
19
+ This is a Rust project.
20
+ Follow Rust idioms: prefer ownership over borrowing when practical.
21
+ Use clippy lints to catch common mistakes.
22
+ Prefer Result/Option over panicking.
23
+ requires_capabilities:
24
+ - read_files
25
+ rust_development:
26
+ content: |
27
+ Use `cargo build` to compile the project.
28
+ Use `cargo check` for fast feedback without full compilation.
29
+ Run `cargo clippy` before committing to catch lint issues.
30
+ Use `cargo fmt` to format code consistently.
31
+ requires_capabilities:
32
+ - write_files
33
+ rust_testing:
34
+ content: |
35
+ Run tests with `cargo test`.
36
+ Run a specific test: `cargo test test_name`.
37
+ Use `#[cfg(test)]` modules for unit tests within source files.
38
+ Always run tests after changes to verify nothing broke.
39
+ requires_capabilities:
40
+ - execute
41
+ - write_files
@@ -0,0 +1,31 @@
1
+ id: terraform
2
+ description: "Terraform environment, auto-detected via main.tf"
3
+ detect_files:
4
+ - main.tf
5
+ - terraform.tf
6
+ policy_rules:
7
+ sandbox:
8
+ enabled: true
9
+ allow:
10
+ - "Bash(terraform init *)"
11
+ - "Bash(terraform plan *)"
12
+ - "Bash(terraform validate *)"
13
+ - "Bash(terraform fmt *)"
14
+ - "Bash(terraform state *)"
15
+ instruction_fragments:
16
+ terraform_patterns:
17
+ content: |
18
+ This project uses Terraform for infrastructure as code.
19
+ Follow HCL conventions: use snake_case for resource names and variables.
20
+ Organize configuration into logical files (main.tf, variables.tf, outputs.tf).
21
+ Use modules for reusable infrastructure components.
22
+ requires_capabilities:
23
+ - read_files
24
+ terraform_development:
25
+ content: |
26
+ Run `terraform init` to initialize providers and modules.
27
+ Run `terraform plan` to preview changes before applying.
28
+ Run `terraform validate` and `terraform fmt` before committing.
29
+ Never apply changes without reviewing the plan first.
30
+ requires_capabilities:
31
+ - write_files