chati-dev 4.1.6 → 4.2.1

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.
@@ -19,7 +19,7 @@ import { homedir } from 'os';
19
19
 
20
20
  const API_BASE = 'https://chati.dev/api';
21
21
  const TELEMETRY_ENDPOINT = 'https://chati.dev/api/telemetry';
22
- const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
22
+ const CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours — shorter window to catch expirations faster
23
23
  const PING_THROTTLE_MS = 5 * 60 * 1000; // 5 minutes
24
24
 
25
25
  const GLOBAL_DIR = join(homedir(), '.chati-dev');
@@ -31,9 +31,9 @@ async function main() {
31
31
  for await (const chunk of process.stdin) input += chunk;
32
32
 
33
33
  try {
34
- // Read global license file
34
+ // Read global license file — REQUIRED for operation
35
35
  if (!existsSync(LICENSE_PATH)) {
36
- allow(); // No license configured orchestrator handles inline activation
36
+ block('No license found. Run: npx chati-dev activate --key=YOUR-KEY\nGet a license at https://chati.dev/pricing');
37
37
  return;
38
38
  }
39
39
 
@@ -41,7 +41,7 @@ async function main() {
41
41
  const licenseKey = readYamlField(licenseRaw, 'key');
42
42
 
43
43
  if (!licenseKey || licenseKey === 'null') {
44
- allow();
44
+ block('No license key configured. Run: npx chati-dev activate --key=YOUR-KEY\nGet a license at https://chati.dev/pricing');
45
45
  return;
46
46
  }
47
47
 
@@ -89,8 +89,15 @@ async function main() {
89
89
  return;
90
90
  }
91
91
  block(buildMessage(data.status, data.reason));
92
- } catch { /* expected: API may be unreachable — fail open */
93
- allow();
92
+ } catch {
93
+ // API unreachable — check if we have a recent VALID cache to fall back on
94
+ // If cache is less than 24h old AND was VALID, allow (grace period for network issues)
95
+ // Otherwise block — we cannot verify the license
96
+ if (age < 24 * 60 * 60 * 1000 && status === 'VALID') {
97
+ allow(); // grace: last check was recent and valid, allow despite network issue
98
+ } else {
99
+ block('Unable to verify license (network issue). If this persists, check your connection.\nRun: npx chati-dev activate --key=YOUR-KEY');
100
+ }
94
101
  }
95
102
  } catch { /* expected: stdin parse may fail — fail open */
96
103
  allow();
@@ -26,6 +26,17 @@ function readSessionState(projectDir) {
26
26
  return match ? match[1].trim().replace(/^["']|["']$/g, '') : null;
27
27
  };
28
28
 
29
+ // Detect active team (Article XXI)
30
+ let activeTeamId = null;
31
+ let activeTeamPhase = null;
32
+ const teamStatusMatch = raw.match(/teams:[\s\S]*?status:\s*(active|forming)/m);
33
+ if (teamStatusMatch) {
34
+ const teamIdMatch = raw.match(/team_id:\s*(TM-\d{8}-[a-z]{3})/m);
35
+ activeTeamId = teamIdMatch ? teamIdMatch[1] : null;
36
+ const teamPhaseMatch = raw.match(/phase:\s*(planning|build)/m);
37
+ activeTeamPhase = teamPhaseMatch ? teamPhaseMatch[1] : null;
38
+ }
39
+
29
40
  return {
30
41
  mode: extract('mode') || 'discover',
31
42
  currentAgent: extract('current_agent') || null,
@@ -33,6 +44,8 @@ function readSessionState(projectDir) {
33
44
  pipelinePosition: extract('pipeline_position') || null,
34
45
  turnCount: parseInt(extract('turn_count') || '0', 10),
35
46
  provider: extract('provider') || 'claude',
47
+ activeTeamId,
48
+ activeTeamPhase,
36
49
  };
37
50
  }
38
51
 
@@ -81,12 +94,12 @@ async function main() {
81
94
 
82
95
  // Primary: turn-count tracks cumulative context consumption across the session.
83
96
  // Fallback: prompt-length for first turn or missing session data.
97
+ const promptText = event.prompt || '';
84
98
  let remainingPercent;
85
99
  const maxTurns = 40;
86
100
  if (session.turnCount > 0) {
87
101
  remainingPercent = Math.max(0, Math.round((1 - session.turnCount / maxTurns) * 100));
88
102
  } else {
89
- const promptText = event.prompt || '';
90
103
  const estimatedTokens = Math.ceil(promptText.length / 4);
91
104
  const contextLimit = hookResolveLimit(inferredModel, session.provider);
92
105
  remainingPercent = Math.max(0, Math.round((1 - estimatedTokens / contextLimit) * 100));
@@ -111,6 +124,22 @@ async function main() {
111
124
  }
112
125
  }
113
126
 
127
+ // L6 Team Roster Layer (Article XII, Article XXI) — injected when a team is active
128
+ // Max 500 tokens, flat budget. Contains team metadata + shared task status only.
129
+ let teamContextBlock = '';
130
+ if (session.activeTeamId && bracket !== 'CRITICAL') {
131
+ const taskListPath = join(projectDir, '.chati', 'teams', session.activeTeamId, 'tasks.yaml');
132
+ if (existsSync(taskListPath)) {
133
+ // Read and cap at 800 chars (~200 tokens) to stay within 500-token budget
134
+ const rawTasks = readFileSync(taskListPath, 'utf-8').trim().slice(0, 800);
135
+ teamContextBlock = [
136
+ ` <team-context team-id="${session.activeTeamId}" phase="${session.activeTeamPhase}">`,
137
+ ` ${rawTasks.split('\n').join('\n ')}`,
138
+ ' </team-context>',
139
+ ].join('\n');
140
+ }
141
+ }
142
+
114
143
  // Frustration detection — adapt response style when user is frustrated
115
144
  const frustrationDetected = detectFrustration(promptText);
116
145
 
@@ -127,6 +156,7 @@ async function main() {
127
156
  session.currentAgent ? ` <agent>${session.currentAgent}</agent>` : '',
128
157
  session.pipelinePosition ? ` <pipeline-position>${session.pipelinePosition}</pipeline-position>` : '',
129
158
  memoryBlock,
159
+ teamContextBlock,
130
160
  bracket === 'CRITICAL' ? ' <advisory>Context running low. Consider handoff or summary.</advisory>' : '',
131
161
  microcompactAdvisory,
132
162
  frustrationDetected ? ' <advisory priority="high">User shows signs of frustration. Be more direct, acknowledge the issue explicitly, focus on the solution, avoid repeating previous suggestions.</advisory>' : '',
@@ -5,6 +5,10 @@
5
5
  {
6
6
  "matcher": ".*",
7
7
  "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "node chati.dev/hooks/license-guard.js"
11
+ },
8
12
  {
9
13
  "type": "command",
10
14
  "command": "node chati.dev/hooks/prism-engine.js"
@@ -55,6 +59,10 @@
55
59
  {
56
60
  "type": "command",
57
61
  "command": "node chati.dev/hooks/style-guard.js"
62
+ },
63
+ {
64
+ "type": "command",
65
+ "command": "node chati.dev/hooks/team-quality-gate.js"
58
66
  }
59
67
  ]
60
68
  },
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Team Quality Gate Hook — PreToolUse (Write|Edit)
4
+ *
5
+ * Prevents cross-member artifact contamination in team mode.
6
+ * When a team is active, each teammate can only write to paths
7
+ * associated with their assigned tasks.
8
+ *
9
+ * If no team is active (CHATI_TEAM_ID absent, or no active team
10
+ * in session.yaml), this hook allows immediately — zero overhead.
11
+ *
12
+ * Article XXI: Agent Teams Governance
13
+ * Enforcement: BLOCK — writes to another member's artifact scope are rejected.
14
+ */
15
+
16
+ import { existsSync, readFileSync } from 'fs';
17
+ import { join } from 'path';
18
+
19
+ // Artifact scope per agent — defines which output paths each agent owns
20
+ const AGENT_ARTIFACT_SCOPES = {
21
+ detail: [
22
+ 'chati.dev/artifacts/2-PRD/',
23
+ 'chati.dev/artifacts/handoffs/detail-',
24
+ 'prd.md',
25
+ ],
26
+ architect: [
27
+ 'chati.dev/artifacts/3-Architecture/',
28
+ 'chati.dev/artifacts/handoffs/architect-',
29
+ 'architecture.md',
30
+ ],
31
+ ux: [
32
+ 'chati.dev/artifacts/4-UX/',
33
+ 'chati.dev/artifacts/handoffs/ux-',
34
+ 'ux-specification.md',
35
+ 'brandbook.md',
36
+ 'brandbook.html',
37
+ 'component-discovery-log.md',
38
+ 'reference-analysis.md',
39
+ ],
40
+ dev: [
41
+ 'src/',
42
+ 'lib/',
43
+ 'app/',
44
+ 'pages/',
45
+ 'components/',
46
+ 'utils/',
47
+ 'hooks/',
48
+ 'styles/',
49
+ 'public/',
50
+ 'test/',
51
+ 'tests/',
52
+ '__tests__/',
53
+ 'chati.dev/artifacts/8-Implementation/',
54
+ 'chati.dev/artifacts/handoffs/dev-',
55
+ ],
56
+ 'qa-implementation': [
57
+ 'chati.dev/artifacts/9-QA-Implementation/',
58
+ 'chati.dev/artifacts/handoffs/qa-implementation-',
59
+ ],
60
+ };
61
+
62
+ // Shared paths any teammate can write to
63
+ const SHARED_PATHS = [
64
+ '.chati/',
65
+ 'chati.dev/artifacts/handoffs/teams/',
66
+ 'chati.dev/artifacts/decisions/',
67
+ ];
68
+
69
+ async function main() {
70
+ let input = '';
71
+ for await (const chunk of process.stdin) {
72
+ input += chunk;
73
+ }
74
+
75
+ try {
76
+ const event = JSON.parse(input);
77
+ const projectDir = event.cwd || process.cwd();
78
+
79
+ // Quick exit: no team active → allow everything
80
+ const teamId = process.env.CHATI_TEAM_ID;
81
+ if (!teamId) {
82
+ process.stdout.write(JSON.stringify({ result: 'allow' }));
83
+ return;
84
+ }
85
+
86
+ // Detect current agent from env or session
87
+ const currentAgent = process.env.CHATI_TEAM_MEMBER || detectAgentFromSession(projectDir);
88
+ if (!currentAgent) {
89
+ // Cannot determine agent → allow (fail-open for safety)
90
+ process.stdout.write(JSON.stringify({ result: 'allow' }));
91
+ return;
92
+ }
93
+
94
+ // Get the file path being written
95
+ const filePath = event.tool_input?.file_path || '';
96
+ if (!filePath) {
97
+ process.stdout.write(JSON.stringify({ result: 'allow' }));
98
+ return;
99
+ }
100
+
101
+ // Normalize path relative to project
102
+ const relativePath = filePath.replace(projectDir + '/', '').replace(projectDir, '');
103
+
104
+ // Check shared paths first — always allowed
105
+ if (SHARED_PATHS.some(sp => relativePath.startsWith(sp))) {
106
+ process.stdout.write(JSON.stringify({ result: 'allow' }));
107
+ return;
108
+ }
109
+
110
+ // Check if another agent owns this path
111
+ for (const [agent, scopes] of Object.entries(AGENT_ARTIFACT_SCOPES)) {
112
+ if (agent === currentAgent) continue;
113
+ for (const scope of scopes) {
114
+ if (relativePath.startsWith(scope) || (!scope.includes('/') && relativePath.endsWith(scope))) {
115
+ process.stdout.write(JSON.stringify({
116
+ result: 'block',
117
+ reason: `[Team Quality Gate] ${currentAgent} cannot write to ${relativePath} — this path belongs to ${agent}. Use the team mailbox to coordinate changes (Article XXI).`,
118
+ }));
119
+ return;
120
+ }
121
+ }
122
+ }
123
+
124
+ // Path not owned by anyone specific → allow
125
+ process.stdout.write(JSON.stringify({ result: 'allow' }));
126
+ } catch (err) {
127
+ // Fail-open: hook errors should not block execution
128
+ process.stderr.write(`[chati] team-quality-gate: ${err.message}\n`);
129
+ process.stdout.write(JSON.stringify({ result: 'allow' }));
130
+ }
131
+ }
132
+
133
+ function detectAgentFromSession(projectDir) {
134
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
135
+ if (!existsSync(sessionPath)) return null;
136
+ const raw = readFileSync(sessionPath, 'utf-8');
137
+ const match = raw.match(/current_agent:\s*(.+)/m);
138
+ return match ? match[1].trim().replace(/^["']|["']$/g, '') : null;
139
+ }
140
+
141
+ // Only run main when executed directly
142
+ import { fileURLToPath } from 'url';
143
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
144
+ main();
145
+ }
@@ -77,6 +77,7 @@ Parse the JSON output. The `action` field tells you what to do:
77
77
  | `activate_interactive` | Action: Interactive Agent |
78
78
  | `spawn_autonomous` | Action: Autonomous Agent |
79
79
  | `spawn_parallel` | Action: Parallel Spawn |
80
+ | `spawn_team` | Action: Spawn Team (Article XXI) |
80
81
  | `resume` | Action: Resume |
81
82
  | `user_preview` | Action: User Preview |
82
83
  | `complete` | Action: Complete |
@@ -237,6 +238,8 @@ If terminal spawning fails (CLI not found, system error), fall back **silently**
237
238
 
238
239
  Planning phase agents (detail, architect, ux) run simultaneously.
239
240
 
241
+ **NOTE (Article XXI):** When `features.agent_teams: true` in config.yaml, the orchestrator MUST attempt `spawn_team` FIRST for GROUP 1 agents (detail, architect, ux) and BUILD pair (dev, qa-implementation). Only fall back to `spawn_parallel` if team spawning is unavailable or fails. The CLI `orchestrate next` command handles this automatically — when teams are enabled, it returns `spawn_team` instead of `spawn_parallel`.
242
+
240
243
  1. Display: "Spawning parallel group: {agents}"
241
244
  2. Execute `parallel_spawn_command` via Bash tool
242
245
  3. Parse consolidated JSON output
@@ -256,6 +259,156 @@ Planning phase agents (detail, architect, ux) run simultaneously.
256
259
 
257
260
  ---
258
261
 
262
+ ## Action: Spawn Team (Article XXI)
263
+
264
+ Agent Teams enable real-time peer communication between agents. The orchestrator acts as Team Lead, spawning teammates as native Claude Code sub-agents (Agent tool), and coordinating their work through a shared task list and filesystem-based mailbox.
265
+
266
+ ### Step 1: Register Team in Session
267
+
268
+ Run via Bash to create team directories, task list, and session entry:
269
+ ```
270
+ node packages/chati-dev/bin/chati.js orchestrate spawn-team --team-type {planning|build} --previous-agent {last_completed_agent}
271
+ ```
272
+ Parse JSON. If `fallback_required: true` → fall back silently to `spawn_parallel`. Do NOT display error messages.
273
+
274
+ Display: "Forming {team_type} team: {members}"
275
+
276
+ ### Step 2: Spawn Teammates via Agent Tool
277
+
278
+ Use the Claude Code **Agent tool** to spawn each teammate as a sub-agent. This is the same mechanism used by `chati-update.md` for SCAN/COMPARE phases.
279
+
280
+ **For Planning Team** (post-Brief): spawn 3 agents IN PARALLEL by making all Agent tool calls in a SINGLE message:
281
+
282
+ ```
283
+ Agent tool call 1:
284
+ description: "Detail agent — expand PRD"
285
+ prompt: [Read and follow chati.dev/agents/plan/detail.md.
286
+ Team mode active: team_id={team_id}.
287
+ Read shared task list at {task_list_path} — your task is TT-PLN-001.
288
+ Mailbox directory: {mailbox_path}
289
+ Cross-review target: architect (send cross_review_request after completing PRD).
290
+ Previous agent handoff: chati.dev/artifacts/handoffs/brief-handoff.md
291
+ CRITICAL: Produce your PRD INDEPENDENTLY first (sealed-bid). Only read teammates' outputs during cross-review.
292
+ Write your handoff to chati.dev/artifacts/handoffs/detail-handoff.md when done.]
293
+
294
+ Agent tool call 2:
295
+ description: "Architect agent — design architecture"
296
+ prompt: [Read and follow chati.dev/agents/plan/architect.md.
297
+ Team mode active: team_id={team_id}.
298
+ Read shared task list at {task_list_path} — your task is TT-PLN-002.
299
+ Mailbox directory: {mailbox_path}
300
+ Cross-review target: ux (send cross_review_request after completing architecture).
301
+ Previous agent handoff: chati.dev/artifacts/handoffs/brief-handoff.md
302
+ CRITICAL: Produce your architecture INDEPENDENTLY first (sealed-bid).
303
+ Write your handoff to chati.dev/artifacts/handoffs/architect-handoff.md when done.]
304
+
305
+ Agent tool call 3:
306
+ description: "UX agent — design UX specification"
307
+ prompt: [Read and follow chati.dev/agents/plan/ux.md.
308
+ Team mode active: team_id={team_id}.
309
+ Read shared task list at {task_list_path} — your task is TT-PLN-003.
310
+ Mailbox directory: {mailbox_path}
311
+ Cross-review target: detail (send cross_review_request after completing UX spec).
312
+ Previous agent handoff: chati.dev/artifacts/handoffs/brief-handoff.md
313
+ CRITICAL: Produce your UX specification INDEPENDENTLY first (sealed-bid).
314
+ Write your handoff to chati.dev/artifacts/handoffs/ux-handoff.md when done.]
315
+ ```
316
+
317
+ **For Build Team** (BUILD phase): spawn 2 agents IN PARALLEL:
318
+
319
+ ```
320
+ Agent tool call 1:
321
+ description: "Dev agent — implement tasks"
322
+ prompt: [Read and follow chati.dev/agents/build/dev.md.
323
+ Team mode active: team_id={team_id}.
324
+ Read shared task list at {task_list_path}.
325
+ Mailbox directory: {mailbox_path}
326
+ Per-task QA mode: after each task, write task_ready_for_review to mailbox.
327
+ Continue to next task immediately — do NOT wait for QA response.
328
+ Poll mailbox at task boundaries for QA findings.
329
+ Previous agent handoff: chati.dev/artifacts/handoffs/qa-planning-handoff.md
330
+ Write your handoff to chati.dev/artifacts/handoffs/dev-handoff.md when done.]
331
+
332
+ Agent tool call 2:
333
+ description: "QA-Implementation agent — per-task review"
334
+ prompt: [Read and follow chati.dev/agents/quality/qa-implementation.md.
335
+ Team mode active: team_id={team_id}.
336
+ Read shared task list at {task_list_path}.
337
+ Mailbox directory: {mailbox_path}
338
+ Per-task review mode: poll mailbox for task_ready_for_review from Dev.
339
+ Run Evidence Gate (Phase 4c) and Root Layer Classification (Phase 4d) per task.
340
+ Write task_review_findings to mailbox for each reviewed task.
341
+ Run full Triple Review Protocol (Phase 5) only after the FINAL task.
342
+ Previous context: chati.dev/artifacts/handoffs/qa-planning-handoff.md (plan overview)
343
+ Write your handoff to chati.dev/artifacts/handoffs/qa-implementation-handoff.md when done.]
344
+ ```
345
+
346
+ ### Step 3: Collect Results
347
+
348
+ When all Agent tool calls return:
349
+
350
+ 1. Read each agent's handoff from `chati.dev/artifacts/handoffs/`
351
+ 2. Read the shared task list for final scores:
352
+ ```
353
+ node packages/chati-dev/bin/chati.js orchestrate team-status --team-id {team_id}
354
+ ```
355
+ 3. If any agent failed: note which one and its error
356
+ 4. Dissolve the team:
357
+ ```
358
+ node packages/chati-dev/bin/chati.js orchestrate team-dissolve --team-id {team_id}
359
+ ```
360
+ 5. Parse dissolution JSON — check `quality_gate.passed`:
361
+ - If `true`: present Completion Options (Team variant)
362
+ - If `false`: display `quality_gate.failures` and offer correction options
363
+
364
+ 6. Present **Completion Options** (Team variant):
365
+ ```
366
+ {team_type} team completed. Team score: {team_score}%.
367
+ Members: {member1}: {score1}%, {member2}: {score2}%, ...
368
+ Quality Gate: {PASSED | FAILED: {failures}}
369
+
370
+ 1. Approve and continue to {next_agent} (Recommended)
371
+ 2. Review artifacts from a specific team member
372
+ 3. Re-run a specific team member for improvements
373
+ ```
374
+
375
+ ### Echo Escalation
376
+
377
+ When reviewing the team-dissolve JSON, if `echo_events` is non-empty and count >= 3 for any member:
378
+ ```
379
+ Team member {agent} appears stuck in a repetition loop (Article XXII Echo Detection).
380
+
381
+ 1. Intervene directly — provide specific guidance (Recommended)
382
+ 2. Remove {agent} from team — complete its work sequentially
383
+ 3. Dissolve entire team — fall back to sequential pipeline
384
+ ```
385
+
386
+ ### Team Transition Protocol
387
+
388
+ After Planning Team (detail + architect + ux) completes and user approves:
389
+ 1. Dissolve Planning Team: `team-dissolve --team-id {planning-team-id}`
390
+ 2. Continue pipeline normally through Phases, Tasks, QA-Planning (these are NOT parallelizable and run sequentially as interactive or autonomous agents)
391
+ 3. When BUILD phase activates: attempt to form Build Team (dev + qa-implementation) following the same Steps 1-3 above
392
+ 4. Build Team uses per-task QA mode: QA reviews each Dev task as it completes, not as a batch
393
+
394
+ **Team cleanup order:** Dissolve current team BEFORE creating next team. Never two teams active simultaneously.
395
+
396
+ ### Team Fallback
397
+
398
+ If the Agent tool is unavailable or any sub-agent spawn fails:
399
+ ```
400
+ 1. Fall back silently to spawn_parallel (GROUP 1) or spawn_autonomous (BUILD)
401
+ Use the existing Action: Parallel Spawn or Action: Autonomous Agent flow
402
+ 2. Do NOT display any error or fallback message to the user
403
+ 3. Run: node packages/chati-dev/bin/chati.js orchestrate team-dissolve --team-id {team_id}
404
+ to mark the team as degraded in session.yaml
405
+ 4. All quality gates and thresholds remain unchanged
406
+ 5. Alternatively, if spawn_parallel also fails, use Sequential Fallback:
407
+ read each agent .md file and execute sequentially in-conversation
408
+ ```
409
+
410
+ ---
411
+
259
412
  ## Action: Resume
260
413
 
261
414
  The user is returning to an active session.
@@ -315,6 +468,10 @@ DEPLOY:
315
468
  Current Agent: {current_agent}
316
469
  Backlog: {count} items ({high_priority} high priority)
317
470
  Context: {bracket} ({remaining}%)
471
+
472
+ TEAMS (if active):
473
+ {team_id}: {status} — members: {roster} — score: {team_score}%
474
+ Decision Trail: {decision_trail_count} entries ({unresolved} unresolved)
318
475
  ```
319
476
 
320
477
  ---
@@ -454,8 +611,9 @@ The orchestrator enforces `chati.dev/constitution.md`:
454
611
 
455
612
  | Level | Action | Articles |
456
613
  |-------|--------|----------|
457
- | **BLOCK** | Halt agent on violation | I, II, III, IV, VII, VIII, X, XI, XV |
458
- | **GUIDE** | Correct without halting | V, IX |
614
+ | **BLOCK** | Halt agent on violation | I, II, III, IV, VII, VIII, X, XI, XII, XIII, XV, XVIII, XX, XXI, XXII |
615
+ | **STRICT** | Must not bypass quality gates | XVII |
616
+ | **GUIDE** | Correct without halting | V, IX, XIV, XVI, XIX |
459
617
  | **WARN** | Generate warning in QA | VI |
460
618
 
461
619
  ---
@@ -483,6 +641,14 @@ If QA finds spec or architecture issues:
483
641
  - issue_type "architecture" → route to architect agent
484
642
  - issue_type "code" → fix in build mode (no backward transition)
485
643
 
644
+ ### Root Layer Routing (Article XXII)
645
+
646
+ When QA-Implementation classifies a fault via the Fault Vector Protocol:
647
+ - `INTENT` → escalate to user via deviation protocol (requirement is flawed)
648
+ - `SPEC` → backward transition to Architect, UX, or Tasks agent
649
+ - `CODE` → Dev agent silent correction loop (existing behavior)
650
+ - `DEFER` → add to session backlog, do not block approval
651
+
486
652
  ---
487
653
 
488
654
  ## Authority Boundaries
@@ -496,6 +662,10 @@ If QA finds spec or architecture issues:
496
662
  - Manage backlog items
497
663
  - Spawn parallel terminals
498
664
  - Decide execution mode (interactive vs autonomous)
665
+ - Form and dissolve Agent Teams (Team Lead role, Article XXI)
666
+ - Monitor shared task list and mailbox during team execution
667
+ - Detect and respond to echo events within teams (Article XXII)
668
+ - Route corrections to correct layer via Root Layer Routing (Article XXII)
499
669
 
500
670
  ### ALLOWED
501
671
  - Read any file in the project (for state detection)
@@ -194,6 +194,166 @@
194
194
  "reason": { "type": "string", "description": "User reason (override only) or QA finding (backward only)" }
195
195
  }
196
196
  }
197
+ },
198
+ "teams": {
199
+ "type": "array",
200
+ "default": [],
201
+ "description": "Agent Teams state tracking (Article XXI). Empty array when teams are not active.",
202
+ "items": {
203
+ "type": "object",
204
+ "required": ["team_id", "phase", "roster", "status"],
205
+ "properties": {
206
+ "team_id": {
207
+ "type": "string",
208
+ "pattern": "^TM-\\d{8}-[a-z]{3}$",
209
+ "description": "Unique team identifier (e.g., TM-20260411-pln)"
210
+ },
211
+ "phase": {
212
+ "enum": ["planning", "build"],
213
+ "description": "Pipeline phase this team serves"
214
+ },
215
+ "mission": {
216
+ "type": "string",
217
+ "description": "Team Mission Statement (one sentence, measurable outcome)"
218
+ },
219
+ "lead": {
220
+ "type": "string",
221
+ "default": "orchestrator",
222
+ "description": "Team Lead agent name"
223
+ },
224
+ "roster": {
225
+ "type": "array",
226
+ "items": { "type": "string" },
227
+ "description": "Agent names enrolled in this team (excluding lead)"
228
+ },
229
+ "status": {
230
+ "enum": ["forming", "active", "completing", "dissolved", "degraded"],
231
+ "description": "Team lifecycle state"
232
+ },
233
+ "task_list_path": {
234
+ "type": ["string", "null"],
235
+ "description": "Path to shared task list YAML"
236
+ },
237
+ "mailbox_path": {
238
+ "type": ["string", "null"],
239
+ "description": "Path to team mailbox directory"
240
+ },
241
+ "formed_at": {
242
+ "type": ["string", "null"],
243
+ "format": "date-time"
244
+ },
245
+ "dissolved_at": {
246
+ "type": ["string", "null"],
247
+ "format": "date-time"
248
+ },
249
+ "team_score": {
250
+ "type": ["number", "null"],
251
+ "minimum": 0,
252
+ "maximum": 100,
253
+ "description": "Aggregate team score (average of member task scores)"
254
+ },
255
+ "member_scores": {
256
+ "type": "object",
257
+ "description": "Per-member score at dissolution",
258
+ "patternProperties": { ".*": { "type": "number" } }
259
+ },
260
+ "correction_cycles": {
261
+ "type": "integer",
262
+ "default": 0,
263
+ "description": "Number of Team Correction Cycles consumed (max 2)"
264
+ },
265
+ "echo_events": {
266
+ "type": "array",
267
+ "default": [],
268
+ "description": "Echo Detection events within this team (Article XXII)",
269
+ "items": {
270
+ "type": "object",
271
+ "properties": {
272
+ "detected_at": { "type": "string", "format": "date-time" },
273
+ "member": { "type": "string" },
274
+ "similarity": { "type": "number" },
275
+ "action_taken": { "type": "string" }
276
+ }
277
+ }
278
+ },
279
+ "fallback_used": {
280
+ "type": "boolean",
281
+ "default": false,
282
+ "description": "True if team spawning failed and sequential fallback was used"
283
+ }
284
+ }
285
+ }
286
+ },
287
+ "team_events": {
288
+ "type": "array",
289
+ "default": [],
290
+ "description": "Audit trail of team lifecycle events (Article XXI)",
291
+ "items": {
292
+ "type": "object",
293
+ "required": ["timestamp", "event", "team_id"],
294
+ "properties": {
295
+ "timestamp": { "type": "string", "format": "date-time" },
296
+ "event": { "enum": ["formed", "dissolved", "degraded", "echo_detected", "fallback_activated", "correction_cycle"] },
297
+ "team_id": { "type": "string" },
298
+ "trigger": { "type": "string", "description": "What caused this event" },
299
+ "state": { "enum": ["clean", "degraded"], "description": "Dissolution state (for dissolved/degraded events)" }
300
+ }
301
+ }
302
+ },
303
+ "decision_trail": {
304
+ "type": "array",
305
+ "default": [],
306
+ "description": "Known-bad state log to prevent cyclical re-introduction of defects (Article XXII). Append-only.",
307
+ "items": {
308
+ "type": "object",
309
+ "required": ["id", "trigger", "fault_origin", "what_was_wrong", "avoid", "logged_at"],
310
+ "properties": {
311
+ "id": {
312
+ "type": "string",
313
+ "pattern": "^DT-\\d{4}$",
314
+ "description": "Decision Trail entry ID (e.g., DT-0001)"
315
+ },
316
+ "trigger": {
317
+ "type": "string",
318
+ "description": "What caused this revision (QA finding, user rejection, echo event)"
319
+ },
320
+ "fault_origin": {
321
+ "enum": ["INTENT", "SPEC", "CODE", "DEFER"],
322
+ "description": "Root Layer classification (Article XXII)"
323
+ },
324
+ "routed_to": {
325
+ "type": "string",
326
+ "description": "Agent the correction was routed to"
327
+ },
328
+ "what_was_wrong": {
329
+ "type": "string",
330
+ "description": "Precise description of the defect or known-bad state"
331
+ },
332
+ "avoid": {
333
+ "type": "string",
334
+ "description": "What NOT to do on the next iteration"
335
+ },
336
+ "evidence_hash": {
337
+ "type": ["string", "null"],
338
+ "description": "SHA-1 of the evidence excerpt for Echo Detection matching"
339
+ },
340
+ "correction_loop": {
341
+ "type": "integer",
342
+ "minimum": 1,
343
+ "maximum": 3,
344
+ "description": "Which correction loop iteration produced this entry"
345
+ },
346
+ "logged_at": {
347
+ "type": "string",
348
+ "format": "date-time"
349
+ },
350
+ "resolved": {
351
+ "type": "boolean",
352
+ "default": false,
353
+ "description": "True when the correcting agent has addressed this entry"
354
+ }
355
+ }
356
+ }
197
357
  }
198
358
  }
199
359
  }