chati-dev 4.1.5 → 4.2.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.
@@ -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
  }
@@ -0,0 +1,56 @@
1
+ # Team Build Tasks Template (Article XXI)
2
+ # Used by orchestrator when forming a Build Team (BUILD phase)
3
+ # QA reviews EACH task as Dev completes it (per-task review, not batch)
4
+
5
+ team_type: build
6
+ phase: build
7
+ mission: "Implement all tasks with per-task QA review and evidence-based quality gates"
8
+
9
+ # NOTE: Tasks are dynamically populated from tasks.md at team formation time.
10
+ # This template defines the PATTERN, not the actual tasks.
11
+ # The orchestrator reads tasks.md and generates TT-BLD-{NNN} entries per task.
12
+
13
+ task_pattern:
14
+ dev_task:
15
+ id_format: "TT-BLD-{NNN}-DEV"
16
+ assigned_to: dev
17
+ status: pending
18
+ score: null
19
+ blocker: null
20
+ threshold: 95
21
+ on_complete:
22
+ notify: qa-implementation
23
+ message_type: task_ready_for_review
24
+ include: [task_id, commit_hash, score, artifacts, self_critique_findings]
25
+
26
+ qa_review:
27
+ id_format: "TT-BLD-{NNN}-QA"
28
+ assigned_to: qa-implementation
29
+ status: pending
30
+ score: null
31
+ blocker: null
32
+ depends_on: ["TT-BLD-{NNN}-DEV"]
33
+ threshold: 95
34
+ protocol:
35
+ - "Run Evidence Gate (Phase 4c): lint, typecheck, test output required"
36
+ - "Classify fault origin (Phase 4d): INTENT | SPEC | CODE | DEFER"
37
+ - "Check Decision Trail for known-bad states before review"
38
+ - "Issue per-task verdict: pass | warn | block"
39
+ on_block:
40
+ action: notify_dev
41
+ message_type: task_review_findings
42
+ include: [task_id, verdict, findings, evidence, fault_origin]
43
+
44
+ # Example of dynamically generated tasks (orchestrator produces these):
45
+ #
46
+ # tasks:
47
+ # - id: TT-BLD-001-DEV
48
+ # title: "T1.1: Initialize Next.js + App Router"
49
+ # assigned_to: dev
50
+ # source_task: T1.1
51
+ # ...
52
+ # - id: TT-BLD-001-QA
53
+ # title: "T1.1: Review + approve"
54
+ # assigned_to: qa-implementation
55
+ # depends_on: [TT-BLD-001-DEV]
56
+ # ...
@@ -0,0 +1,73 @@
1
+ # Team Planning Tasks Template (Article XXI)
2
+ # Used by orchestrator when forming a Planning Team (post-Brief)
3
+ # Cross-review assignments form a circular review: Detail -> Architect -> UX -> Detail
4
+
5
+ team_type: planning
6
+ phase: plan
7
+ mission: "Co-design PRD, architecture, and UX specification with cross-validated alignment"
8
+
9
+ tasks:
10
+ - id: TT-PLN-001
11
+ title: "Expand Brief into PRD with acceptance criteria"
12
+ assigned_to: detail
13
+ status: pending
14
+ score: null
15
+ blocker: null
16
+ depends_on: []
17
+ cross_review:
18
+ target: architect
19
+ questions:
20
+ - "Do PRD entities align with the data architecture constraints you anticipate?"
21
+ - "Are there requirements that would create architectural contradictions?"
22
+ - "Are scope decisions in Section 4 likely to conflict with your design?"
23
+ artifacts_produced:
24
+ - prd.md
25
+ threshold: 90
26
+
27
+ - id: TT-PLN-002
28
+ title: "Design system architecture and data architecture"
29
+ assigned_to: architect
30
+ status: pending
31
+ score: null
32
+ blocker: null
33
+ depends_on: []
34
+ cross_review:
35
+ target: ux
36
+ questions:
37
+ - "Does the responsive strategy conflict with deployment architecture constraints?"
38
+ - "Do Design System tokens account for API response latency in loading states?"
39
+ - "Are there component complexity choices that contradict the scalability approach?"
40
+ artifacts_produced:
41
+ - architecture.md
42
+ threshold: 90
43
+
44
+ - id: TT-PLN-003
45
+ title: "Design UX specification with brandbook, user flows, and components"
46
+ assigned_to: ux
47
+ status: pending
48
+ score: null
49
+ blocker: null
50
+ depends_on: []
51
+ cross_review:
52
+ target: detail
53
+ questions:
54
+ - "Do any PRD requirements conflict with the selected visual direction?"
55
+ - "Are there user flows implying requirements not captured in the PRD?"
56
+ - "Is the target user profile consistent with UX research findings?"
57
+ artifacts_produced:
58
+ - ux-specification.md
59
+ - brandbook.md
60
+ - brandbook.html
61
+ threshold: 90
62
+
63
+ - id: TT-PLN-004
64
+ title: "Cross-validate alignment: PRD entities = DB tables = UI components"
65
+ assigned_to: null
66
+ status: pending
67
+ score: null
68
+ blocker: null
69
+ depends_on: [TT-PLN-001, TT-PLN-002, TT-PLN-003]
70
+ cross_review: null
71
+ artifacts_produced: []
72
+ threshold: 95
73
+ notes: "Assigned by Team Lead after individual tasks complete. All 3 members participate."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chati-dev",
3
- "version": "4.1.5",
3
+ "version": "4.2.0",
4
4
  "description": "AI-Powered Multi-Agent Orchestration System — Structured vibe coding for Full Stack Development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,7 @@ export const SAFETY_TRIGGERS = {
12
12
  CRITICAL_RISK: 'critical_risk', // Security/data risk detected
13
13
  TIMEOUT: 'timeout', // Agent exceeded time limit
14
14
  GOTCHA_SPIKE: 'gotcha_spike', // 5+ new gotchas in current session
15
+ EDIT_LOOP: 'edit_loop', // Same file edited 3+ times in current task (Article XX)
15
16
  };
16
17
 
17
18
  // Critical risk keywords
@@ -98,6 +99,9 @@ export function evaluateTrigger(trigger, state) {
98
99
  case SAFETY_TRIGGERS.GOTCHA_SPIKE:
99
100
  return evaluateGotchaSpike(state);
100
101
 
102
+ case SAFETY_TRIGGERS.EDIT_LOOP:
103
+ return evaluateEditLoop(state);
104
+
101
105
  default:
102
106
  return {
103
107
  triggered: false,
@@ -291,6 +295,34 @@ function evaluateGotchaSpike(state) {
291
295
  };
292
296
  }
293
297
 
298
+ /**
299
+ * Article XX — Detect edit loops (same file edited 3+ times in current task).
300
+ * state.fileEdits should be an object: { 'path/to/file.tsx': 3, ... }
301
+ */
302
+ function evaluateEditLoop(state) {
303
+ const threshold = 3;
304
+ const fileEdits = state.fileEdits || {};
305
+ const loopedFiles = Object.entries(fileEdits)
306
+ .filter(([, count]) => count >= threshold);
307
+
308
+ if (loopedFiles.length > 0) {
309
+ const details = loopedFiles
310
+ .map(([path, count]) => `${path} (${count} edits)`)
311
+ .join(', ');
312
+ return {
313
+ triggered: true,
314
+ severity: 'critical',
315
+ details: `Edit loop detected (Article XX): ${details}. Stop and diagnose root cause before continuing.`,
316
+ };
317
+ }
318
+
319
+ return {
320
+ triggered: false,
321
+ severity: 'warning',
322
+ details: 'No edit loops detected',
323
+ };
324
+ }
325
+
294
326
  /**
295
327
  * Get critical risk keywords list.
296
328
  * @returns {string[]}
@@ -533,14 +533,14 @@ main();
533
533
 
534
534
  /**
535
535
  * Generate the style guard hook for Gemini CLI.
536
- * BeforeTool event -- advisory: enforces em-dash and emoji standards.
536
+ * BeforeTool event -- blocking: rejects em-dashes and emojis in generated content.
537
537
  */
538
538
  function generateStyleGuard() {
539
539
  return `${HOOK_HEADER}
540
540
  /**
541
541
  * Style Guard -- BeforeTool
542
- * Advisory: enforces Constitution Article V writing standards
543
- * (no em-dashes, no emojis in generated content).
542
+ * Blocking: rejects em-dashes and emojis in generated content.
543
+ * Constitution Article V enforcement.
544
544
  */
545
545
  async function main() {
546
546
  let input = '';
@@ -584,8 +584,8 @@ async function main() {
584
584
 
585
585
  if (result.violations && result.violations.length > 0) {
586
586
  console.log(JSON.stringify({
587
- decision: 'allow',
588
- reason: \`[Style Guard] \${result.violations.join(' ')}\`,
587
+ decision: 'block',
588
+ reason: \`[Style Guard] \${result.violations.join(' ')} Rewrite without em-dashes and emojis.\`,
589
589
  }));
590
590
  return;
591
591
  }