@team-agent/installer 0.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.
Files changed (36) hide show
  1. package/README.md +201 -0
  2. package/crates/team-agent-core/Cargo.toml +12 -0
  3. package/crates/team-agent-core/src/lib.rs +287 -0
  4. package/crates/team-agent-core/src/main.rs +152 -0
  5. package/examples/team.spec.yaml +206 -0
  6. package/examples/team_state.md +35 -0
  7. package/npm/install.mjs +266 -0
  8. package/package.json +28 -0
  9. package/pyproject.toml +18 -0
  10. package/schemas/result-envelope.schema.json +76 -0
  11. package/schemas/team.schema.json +241 -0
  12. package/scripts/install.py +88 -0
  13. package/scripts/run_regression_tests.py +79 -0
  14. package/skills/team-agent/SKILL.md +173 -0
  15. package/src/team_agent/__init__.py +3 -0
  16. package/src/team_agent/__main__.py +5 -0
  17. package/src/team_agent/cli.py +857 -0
  18. package/src/team_agent/compiler.py +269 -0
  19. package/src/team_agent/coordinator.py +62 -0
  20. package/src/team_agent/errors.py +10 -0
  21. package/src/team_agent/events.py +37 -0
  22. package/src/team_agent/fake_worker.py +80 -0
  23. package/src/team_agent/mcp_server.py +579 -0
  24. package/src/team_agent/message_store.py +497 -0
  25. package/src/team_agent/paths.py +45 -0
  26. package/src/team_agent/permissions.py +123 -0
  27. package/src/team_agent/profiles.py +882 -0
  28. package/src/team_agent/providers.py +1045 -0
  29. package/src/team_agent/routing.py +84 -0
  30. package/src/team_agent/runtime.py +5213 -0
  31. package/src/team_agent/rust_core.py +156 -0
  32. package/src/team_agent/simple_yaml.py +236 -0
  33. package/src/team_agent/spec.py +308 -0
  34. package/src/team_agent/state.py +112 -0
  35. package/src/team_agent/task_graph.py +80 -0
  36. package/templates/team_state.md +32 -0
@@ -0,0 +1,206 @@
1
+ version: 1
2
+ team:
3
+ name: teamspec-full-example
4
+ mode: supervisor_worker
5
+ objective: Build, research, review, and document a code change with Codex CLI workers.
6
+ workspace: .
7
+ leader:
8
+ id: leader
9
+ role: leader
10
+ provider: codex
11
+ model: null
12
+ tools:
13
+ - fs_read
14
+ - fs_list
15
+ - mcp_team
16
+ - provider_builtin
17
+ context_policy:
18
+ keep_user_thread: true
19
+ receive_worker_outputs: structured_only
20
+ max_worker_result_tokens: 4000
21
+ agents:
22
+ - id: codex_implementer
23
+ role: implementation_engineer
24
+ provider: codex
25
+ model: null
26
+ working_directory: .
27
+ system_prompt:
28
+ inline: |
29
+ You are the implementation worker. Make focused code changes, run relevant tests,
30
+ and report a result_envelope_v1 with changed files, tests, risks, artifacts, and next actions.
31
+ file: null
32
+ tools:
33
+ - fs_read
34
+ - fs_write
35
+ - fs_list
36
+ - execute_bash
37
+ - git_diff
38
+ - mcp_team
39
+ - provider_builtin
40
+ permission_mode: restricted
41
+ preferred_for:
42
+ - implementation
43
+ - bug_fix
44
+ - test
45
+ avoid_for:
46
+ - final_risk_signoff
47
+ output_contract:
48
+ format: result_envelope_v1
49
+ required_fields:
50
+ - task_id
51
+ - status
52
+ - summary
53
+ - artifacts
54
+ - id: codex_researcher
55
+ role: researcher
56
+ provider: codex
57
+ model: null
58
+ working_directory: .
59
+ system_prompt:
60
+ inline: |
61
+ You are the research worker. Prefer read-only analysis and summarize findings
62
+ as result_envelope_v1. Do not edit files.
63
+ file: null
64
+ tools:
65
+ - fs_read
66
+ - fs_list
67
+ - network
68
+ - mcp_team
69
+ - provider_builtin
70
+ permission_mode: restricted
71
+ preferred_for:
72
+ - research
73
+ - architecture
74
+ - docs
75
+ avoid_for:
76
+ - implementation
77
+ output_contract:
78
+ format: result_envelope_v1
79
+ required_fields:
80
+ - task_id
81
+ - status
82
+ - summary
83
+ - artifacts
84
+ - id: codex_reviewer
85
+ role: code_reviewer
86
+ provider: codex
87
+ model: null
88
+ working_directory: .
89
+ system_prompt:
90
+ inline: |
91
+ You are the reviewer. Find correctness, regression, security, and missing-test risks.
92
+ Stay read-only unless the leader explicitly changes your permissions.
93
+ file: null
94
+ tools:
95
+ - fs_read
96
+ - fs_list
97
+ - git_diff
98
+ - mcp_team
99
+ - provider_builtin
100
+ permission_mode: restricted
101
+ preferred_for:
102
+ - review
103
+ - risk_check
104
+ avoid_for:
105
+ - implementation
106
+ output_contract:
107
+ format: result_envelope_v1
108
+ required_fields:
109
+ - task_id
110
+ - status
111
+ - summary
112
+ - artifacts
113
+ routing:
114
+ default_assignee: leader
115
+ rules:
116
+ - id: implementation-to-codex
117
+ when: task.type in ["implementation", "bug_fix", "test"]
118
+ assign_to: codex_implementer
119
+ priority: 100
120
+ - id: research-to-codex
121
+ when: task.type in ["research", "architecture", "docs"]
122
+ assign_to: codex_researcher
123
+ priority: 90
124
+ - id: review-to-codex
125
+ when: task.type in ["review", "risk_check"]
126
+ assign_to: codex_reviewer
127
+ priority: 90
128
+ communication:
129
+ protocol: mcp_inbox
130
+ topology: leader_centered
131
+ worker_to_worker: false
132
+ ack_timeout_sec: 60
133
+ result_format: result_envelope_v1
134
+ message_store:
135
+ sqlite: .team/runtime/team.db
136
+ mirror_files: .team/messages
137
+ runtime:
138
+ backend: tmux
139
+ display_backend: none
140
+ session_name: teamspec-full-example
141
+ auto_launch: true
142
+ require_user_approval_before_launch: true
143
+ dangerous_auto_approve: false
144
+ max_active_agents: 3
145
+ startup_order:
146
+ - codex_implementer
147
+ - codex_researcher
148
+ - codex_reviewer
149
+ context:
150
+ state_file: team_state.md
151
+ artifact_dir: .team/artifacts
152
+ log_dir: .team/logs
153
+ summarization:
154
+ worker_full_logs: retain_outside_leader_context
155
+ state_update: after_each_result
156
+ tasks:
157
+ - id: task_research
158
+ title: Read the task context and identify design risks.
159
+ type: research
160
+ assignee: null
161
+ deps: []
162
+ acceptance:
163
+ - Result envelope includes summary and risks.
164
+ status: pending
165
+ requires_tools:
166
+ - fs_read
167
+ files:
168
+ - "**/*"
169
+ risk: medium
170
+ retry_limit: 1
171
+ human_confirmation: false
172
+ - id: task_impl
173
+ title: Implement the requested code change and run tests.
174
+ type: implementation
175
+ assignee: null
176
+ deps:
177
+ - task_research
178
+ acceptance:
179
+ - Changed files and tests are reported.
180
+ status: pending
181
+ requires_tools:
182
+ - fs_write
183
+ - execute_bash
184
+ files:
185
+ - "src/**"
186
+ - "tests/**"
187
+ risk: medium
188
+ retry_limit: 1
189
+ human_confirmation: false
190
+ - id: task_review
191
+ title: Review implementation output and identify regressions.
192
+ type: review
193
+ assignee: null
194
+ deps:
195
+ - task_impl
196
+ acceptance:
197
+ - Findings are structured with risk and artifacts.
198
+ status: pending
199
+ requires_tools:
200
+ - fs_read
201
+ - git_diff
202
+ files:
203
+ - "**/*"
204
+ risk: medium
205
+ retry_limit: 0
206
+ human_confirmation: false
@@ -0,0 +1,35 @@
1
+ # Team State
2
+
3
+ Updated: 2026-05-12T14:16:08.963244+00:00
4
+
5
+ ## Objective
6
+
7
+ Build, research, review, and document a code change with Codex CLI workers.
8
+
9
+ ## Team
10
+
11
+ - Name: teamspec-full-example
12
+ - Runtime session: teamspec-full-example
13
+
14
+ ## Agents
15
+
16
+ - codex_implementer: implementation_engineer on codex (running)
17
+ - codex_researcher: researcher on codex (running)
18
+ - codex_reviewer: code_reviewer on codex (running)
19
+
20
+ ## Task Graph
21
+
22
+ - task_research [pending], assignee=codex_researcher, deps=none: Read the task context and identify design risks.
23
+ - task_impl [pending], assignee=codex_implementer, deps=task_research: Implement the requested code change and run tests.
24
+ - task_review [pending], assignee=codex_reviewer, deps=task_impl: Review implementation output and identify regressions.
25
+
26
+ ## Latest Results
27
+
28
+
29
+ ## Blockers
30
+
31
+ - None
32
+
33
+ ## Next Step
34
+
35
+ - Continue routing ready tasks and collect result envelopes.
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const packageRoot = path.resolve(__dirname, "..");
10
+ const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
11
+
12
+ const command = process.argv[2] || "install";
13
+ const args = process.argv.slice(3);
14
+
15
+ if (["-h", "--help", "help"].includes(command)) {
16
+ printHelp();
17
+ process.exit(0);
18
+ }
19
+
20
+ if (command === "install" || command === "update") {
21
+ install(args);
22
+ } else if (command === "doctor") {
23
+ runDoctor(args);
24
+ } else if (command === "uninstall") {
25
+ uninstall(args);
26
+ } else {
27
+ console.error(`unknown command: ${command}`);
28
+ printHelp();
29
+ process.exit(2);
30
+ }
31
+
32
+ function printHelp() {
33
+ console.log(`Team Agent installer
34
+
35
+ Usage:
36
+ npx @team-agent/installer@latest install
37
+ npx @team-agent/installer@latest doctor
38
+ npx @team-agent/installer@latest uninstall
39
+
40
+ Options:
41
+ --prefix <dir> wrapper install prefix, default ~/.local
42
+ --runtime-dir <dir> stable runtime root, default ~/.team-agent/runtime
43
+ --python <path> Python executable, otherwise TEAM_AGENT_PYTHON, python3, python
44
+ --purge-runtime uninstall also removes the runtime root
45
+ `);
46
+ }
47
+
48
+ function install(argv) {
49
+ const opts = parseOptions(argv);
50
+ const python = resolvePython(opts.python);
51
+ const prefix = path.resolve(expandHome(opts.prefix || path.join(os.homedir(), ".local")));
52
+ const binDir = path.join(prefix, "bin");
53
+ const runtimeRoot = path.resolve(expandHome(opts.runtimeDir || path.join(os.homedir(), ".team-agent", "runtime")));
54
+ const version = packageJson.version || "dev";
55
+ const dest = path.join(runtimeRoot, version);
56
+ const tmp = path.join(runtimeRoot, `.${version}.${process.pid}.tmp`);
57
+ const backup = path.join(runtimeRoot, `.${version}.previous`);
58
+
59
+ fs.mkdirSync(runtimeRoot, { recursive: true });
60
+ fs.rmSync(tmp, { recursive: true, force: true });
61
+ copyTree(packageRoot, tmp);
62
+ fs.rmSync(backup, { recursive: true, force: true });
63
+ if (fs.existsSync(dest)) {
64
+ fs.renameSync(dest, backup);
65
+ }
66
+ fs.renameSync(tmp, dest);
67
+
68
+ fs.mkdirSync(binDir, { recursive: true });
69
+ writeWrapper(path.join(binDir, "team-agent"), dest, "team_agent", python);
70
+ writeWrapper(path.join(binDir, "team_orchestrator"), dest, "team_agent.mcp_server", python);
71
+ writeWrapper(path.join(binDir, "team-agent-coordinator"), dest, "team_agent.coordinator", python);
72
+
73
+ const teamAgent = path.join(binDir, "team-agent");
74
+ const skill = spawnSync(teamAgent, ["install-skill", "--target", "all"], {
75
+ text: true,
76
+ encoding: "utf8",
77
+ });
78
+ if (skill.status !== 0) {
79
+ process.stderr.write(skill.stderr || skill.stdout || "team-agent install-skill failed\n");
80
+ process.exit(skill.status || 1);
81
+ }
82
+
83
+ console.log(`installed: ${teamAgent}`);
84
+ console.log(`runtime: ${dest}`);
85
+ console.log(`python: ${python}`);
86
+ console.log(`skill: installed for Codex and Claude`);
87
+ console.log(`PATH: ensure ${binDir} is on PATH`);
88
+
89
+ const doctor = spawnSync(teamAgent, ["doctor", "--json"], { text: true, encoding: "utf8" });
90
+ if (doctor.status === 0) {
91
+ console.log("doctor: ok");
92
+ } else {
93
+ console.log("doctor: has blockers; run `team-agent doctor` after updating PATH");
94
+ }
95
+ }
96
+
97
+ function runDoctor(argv) {
98
+ const opts = parseOptions(argv);
99
+ const prefix = path.resolve(expandHome(opts.prefix || path.join(os.homedir(), ".local")));
100
+ const teamAgent = path.join(prefix, "bin", "team-agent");
101
+ if (!fs.existsSync(teamAgent)) {
102
+ console.error(`team-agent wrapper not found: ${teamAgent}`);
103
+ process.exit(1);
104
+ }
105
+ const proc = spawnSync(teamAgent, ["doctor"], { stdio: "inherit" });
106
+ process.exit(proc.status || 0);
107
+ }
108
+
109
+ function uninstall(argv) {
110
+ const opts = parseOptions(argv);
111
+ const prefix = path.resolve(expandHome(opts.prefix || path.join(os.homedir(), ".local")));
112
+ for (const name of ["team-agent", "team_orchestrator", "team-agent-coordinator"]) {
113
+ fs.rmSync(path.join(prefix, "bin", name), { force: true });
114
+ }
115
+ for (const skillDir of [
116
+ path.join(os.homedir(), ".codex", "skills", "team-agent"),
117
+ path.join(os.homedir(), ".claude", "skills", "team-agent"),
118
+ ]) {
119
+ fs.rmSync(skillDir, { recursive: true, force: true });
120
+ }
121
+ console.log(`removed wrappers from ${path.join(prefix, "bin")}`);
122
+ console.log("removed skills from ~/.codex/skills/team-agent and ~/.claude/skills/team-agent");
123
+ if (opts.purgeRuntime) {
124
+ const runtimeRoot = path.resolve(expandHome(opts.runtimeDir || path.join(os.homedir(), ".team-agent", "runtime")));
125
+ fs.rmSync(runtimeRoot, { recursive: true, force: true });
126
+ console.log(`removed runtime root ${runtimeRoot}`);
127
+ } else {
128
+ console.log("runtime directories are left under ~/.team-agent/runtime for rollback; pass --purge-runtime only when no teams are running.");
129
+ }
130
+ }
131
+
132
+ function parseOptions(argv) {
133
+ const opts = {};
134
+ for (let i = 0; i < argv.length; i += 1) {
135
+ const item = argv[i];
136
+ if (item === "--prefix") {
137
+ opts.prefix = argv[++i];
138
+ } else if (item.startsWith("--prefix=")) {
139
+ opts.prefix = item.slice("--prefix=".length);
140
+ } else if (item === "--runtime-dir") {
141
+ opts.runtimeDir = argv[++i];
142
+ } else if (item.startsWith("--runtime-dir=")) {
143
+ opts.runtimeDir = item.slice("--runtime-dir=".length);
144
+ } else if (item === "--python") {
145
+ opts.python = argv[++i];
146
+ } else if (item.startsWith("--python=")) {
147
+ opts.python = item.slice("--python=".length);
148
+ } else if (item === "--purge-runtime") {
149
+ opts.purgeRuntime = true;
150
+ } else {
151
+ throw new Error(`unknown option: ${item}`);
152
+ }
153
+ }
154
+ return opts;
155
+ }
156
+
157
+ function resolvePython(explicit) {
158
+ const candidates = pythonCandidates(explicit);
159
+ for (const candidate of candidates) {
160
+ const resolved = path.isAbsolute(candidate) ? candidate : which(candidate) || candidate;
161
+ if (!resolved) {
162
+ continue;
163
+ }
164
+ const proc = spawnSync(resolved, ["-c", "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)"], {
165
+ text: true,
166
+ encoding: "utf8",
167
+ });
168
+ if (proc.status === 0) {
169
+ return resolved;
170
+ }
171
+ }
172
+ console.error("No usable Python >= 3.10 found. Set TEAM_AGENT_PYTHON or pass --python.");
173
+ process.exit(1);
174
+ }
175
+
176
+ function pythonCandidates(explicit) {
177
+ const candidates = [explicit, process.env.TEAM_AGENT_PYTHON, "python3", "python"];
178
+ const commonPaths = [
179
+ "/opt/homebrew/bin/python3",
180
+ "/usr/local/bin/python3",
181
+ "/usr/bin/python3",
182
+ "/opt/homebrew/opt/python@3/bin/python3",
183
+ "/usr/local/opt/python@3/bin/python3",
184
+ ];
185
+ candidates.push(...commonPaths);
186
+ for (const root of ["/opt/homebrew/opt", "/usr/local/opt"]) {
187
+ try {
188
+ for (const entry of fs.readdirSync(root)) {
189
+ if (!entry.startsWith("python@")) {
190
+ continue;
191
+ }
192
+ const bin = path.join(root, entry, "bin");
193
+ for (const name of fs.readdirSync(bin)) {
194
+ if (/^python3(\.\d+)?$/.test(name)) {
195
+ candidates.push(path.join(bin, name));
196
+ }
197
+ }
198
+ }
199
+ } catch {
200
+ continue;
201
+ }
202
+ }
203
+ return [...new Set(candidates.filter(Boolean))];
204
+ }
205
+
206
+ function which(commandName) {
207
+ for (const directory of (process.env.PATH || "").split(path.delimiter)) {
208
+ if (!directory) {
209
+ continue;
210
+ }
211
+ const candidate = path.join(directory, commandName);
212
+ try {
213
+ fs.accessSync(candidate, fs.constants.X_OK);
214
+ return candidate;
215
+ } catch {
216
+ continue;
217
+ }
218
+ }
219
+ return null;
220
+ }
221
+
222
+ function writeWrapper(file, runtimeDir, moduleName, python) {
223
+ const content = `#!/usr/bin/env sh
224
+ PYTHON_BIN="\${TEAM_AGENT_PYTHON:-${doubleQuoteValue(python)}}"
225
+ PYTHONPATH="${doubleQuoteValue(path.join(runtimeDir, "src"))}" exec "$PYTHON_BIN" -m ${moduleName} "$@"
226
+ `;
227
+ fs.writeFileSync(file, content, { mode: 0o755 });
228
+ fs.chmodSync(file, 0o755);
229
+ }
230
+
231
+ function copyTree(src, dest) {
232
+ const ignored = new Set([".git", ".team", "node_modules", "__pycache__", ".pytest_cache", ".venv"]);
233
+ const stat = fs.lstatSync(src);
234
+ if (stat.isDirectory()) {
235
+ const name = path.basename(src);
236
+ if (ignored.has(name) || src.endsWith(path.join("team-agent-core", "target"))) {
237
+ return;
238
+ }
239
+ fs.mkdirSync(dest, { recursive: true, mode: stat.mode });
240
+ for (const entry of fs.readdirSync(src)) {
241
+ if (entry === ".DS_Store") {
242
+ continue;
243
+ }
244
+ copyTree(path.join(src, entry), path.join(dest, entry));
245
+ }
246
+ return;
247
+ }
248
+ if (stat.isFile()) {
249
+ fs.copyFileSync(src, dest);
250
+ fs.chmodSync(dest, stat.mode);
251
+ }
252
+ }
253
+
254
+ function expandHome(value) {
255
+ if (value === "~") {
256
+ return os.homedir();
257
+ }
258
+ if (value.startsWith("~/")) {
259
+ return path.join(os.homedir(), value.slice(2));
260
+ }
261
+ return value;
262
+ }
263
+
264
+ function doubleQuoteValue(value) {
265
+ return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
266
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@team-agent/installer",
3
+ "version": "0.1.0",
4
+ "description": "npx installer for Team Agent",
5
+ "bin": {
6
+ "team-agent-installer": "npm/install.mjs"
7
+ },
8
+ "files": [
9
+ "npm",
10
+ "scripts",
11
+ "src",
12
+ "skills",
13
+ "templates",
14
+ "examples",
15
+ "schemas",
16
+ "crates/team-agent-core/Cargo.toml",
17
+ "crates/team-agent-core/src",
18
+ "pyproject.toml",
19
+ "README.md",
20
+ "!**/__pycache__/**",
21
+ "!**/*.pyc",
22
+ "!**/target/**"
23
+ ],
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "license": "UNLICENSED"
28
+ }
package/pyproject.toml ADDED
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "teamspec-agent-mode"
3
+ version = "0.1.0"
4
+ description = "Spec-first CLI-native team agent runtime for Claude Code, Codex CLI, and Gemini CLI workers."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = []
8
+
9
+ [project.optional-dependencies]
10
+ test = []
11
+
12
+ [project.scripts]
13
+ team-agent = "team_agent.cli:main"
14
+ team_orchestrator = "team_agent.mcp_server:main"
15
+ team-agent-coordinator = "team_agent.coordinator:main"
16
+
17
+ [tool.team-agent]
18
+ run = "PYTHONPATH=src python3 -m team_agent"
@@ -0,0 +1,76 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://teamspec.local/schemas/result-envelope.schema.json",
4
+ "title": "TeamSpec result_envelope_v1",
5
+ "type": "object",
6
+ "required": ["schema_version", "task_id", "agent_id", "status", "summary", "changes", "tests", "risks", "artifacts", "next_actions"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "schema_version": { "const": "result_envelope_v1" },
10
+ "task_id": { "type": "string", "minLength": 1 },
11
+ "agent_id": { "type": "string", "minLength": 1 },
12
+ "status": { "enum": ["success", "blocked", "failed", "partial"] },
13
+ "summary": { "type": "string" },
14
+ "changes": {
15
+ "type": "array",
16
+ "items": {
17
+ "type": "object",
18
+ "required": ["path", "kind", "description"],
19
+ "additionalProperties": false,
20
+ "properties": {
21
+ "path": { "type": "string" },
22
+ "kind": { "enum": ["created", "modified", "deleted", "observed"] },
23
+ "description": { "type": "string" }
24
+ }
25
+ }
26
+ },
27
+ "tests": {
28
+ "type": "array",
29
+ "items": {
30
+ "type": "object",
31
+ "required": ["command", "status"],
32
+ "additionalProperties": false,
33
+ "properties": {
34
+ "command": { "type": "string" },
35
+ "status": { "enum": ["passed", "failed", "not_run", "skipped"] },
36
+ "detail": { "type": "string" }
37
+ }
38
+ }
39
+ },
40
+ "risks": {
41
+ "type": "array",
42
+ "items": {
43
+ "type": "object",
44
+ "required": ["severity", "description"],
45
+ "additionalProperties": false,
46
+ "properties": {
47
+ "severity": { "enum": ["low", "medium", "high"] },
48
+ "description": { "type": "string" }
49
+ }
50
+ }
51
+ },
52
+ "artifacts": {
53
+ "type": "array",
54
+ "items": {
55
+ "type": "object",
56
+ "required": ["path", "description"],
57
+ "additionalProperties": false,
58
+ "properties": {
59
+ "path": { "type": "string" },
60
+ "description": { "type": "string" }
61
+ }
62
+ }
63
+ },
64
+ "next_actions": {
65
+ "type": "array",
66
+ "items": {
67
+ "type": "object",
68
+ "required": ["description"],
69
+ "additionalProperties": false,
70
+ "properties": {
71
+ "description": { "type": "string" }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }