@jetrabbits/agentic 0.4.0 → 0.5.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 (93) hide show
  1. package/AGENTS.md +8 -0
  2. package/CHANGELOG.md +3 -0
  3. package/Makefile +21 -5
  4. package/README.md +17 -4
  5. package/agentic +78 -7
  6. package/areas/devops/ci-cd/workflows/onboard-repo.md +29 -0
  7. package/areas/devops/ci-cd/workflows/pipeline-debug.md +26 -0
  8. package/areas/devops/ci-cd/workflows/release-pipeline.md +53 -0
  9. package/areas/devops/database-ops/workflows/backup-verify.md +27 -0
  10. package/areas/devops/database-ops/workflows/db-incident.md +30 -0
  11. package/areas/devops/devsecops/workflows/policy-onboard.md +34 -0
  12. package/areas/devops/devsecops/workflows/security-scan-pipeline.md +33 -0
  13. package/areas/devops/infrastructure/workflows/destroy-environment.md +31 -0
  14. package/areas/devops/infrastructure/workflows/drift-remediation.md +29 -0
  15. package/areas/devops/infrastructure/workflows/module-development.md +32 -0
  16. package/areas/devops/infrastructure/workflows/provision-environment.md +29 -0
  17. package/areas/devops/kubernetes/workflows/cluster-bootstrap.md +36 -0
  18. package/areas/devops/kubernetes/workflows/debug-workload.md +29 -0
  19. package/areas/devops/kubernetes/workflows/onboard-service.md +35 -0
  20. package/areas/devops/kubernetes/workflows/upgrade-cluster.md +30 -0
  21. package/areas/devops/networking/workflows/onboard-ingress.md +27 -0
  22. package/areas/devops/networking/workflows/service-mesh-onboard.md +27 -0
  23. package/areas/devops/observability/workflows/alert-investigation.md +29 -0
  24. package/areas/devops/observability/workflows/observability-stack-setup.md +33 -0
  25. package/areas/devops/observability/workflows/onboard-service-monitoring.md +31 -0
  26. package/areas/devops/sre/workflows/incident-response.md +48 -0
  27. package/areas/devops/sre/workflows/postmortem.md +32 -0
  28. package/areas/devops/sre/workflows/slo-review.md +35 -1
  29. package/areas/software/backend/workflows/add-migration.md +33 -0
  30. package/areas/software/backend/workflows/create-endpoint.md +40 -0
  31. package/areas/software/backend/workflows/debug-issue.md +31 -0
  32. package/areas/software/backend/workflows/develop-epic.md +37 -0
  33. package/areas/software/backend/workflows/develop-feature.md +44 -0
  34. package/areas/software/backend/workflows/refactor-module.md +35 -0
  35. package/areas/software/backend/workflows/test-feature.md +30 -0
  36. package/areas/software/data-engineering/workflows/backfill-data.md +25 -0
  37. package/areas/software/data-engineering/workflows/data-quality-incident.md +31 -0
  38. package/areas/software/data-engineering/workflows/lineage-trace.md +25 -0
  39. package/areas/software/data-engineering/workflows/new-model.md +30 -0
  40. package/areas/software/data-engineering/workflows/schema-migration.md +29 -0
  41. package/areas/software/frontend/workflows/a11y-fix.md +30 -0
  42. package/areas/software/frontend/workflows/bundle-analyze.md +28 -0
  43. package/areas/software/frontend/workflows/release-prep.md +33 -0
  44. package/areas/software/frontend/workflows/scaffold-component.md +32 -0
  45. package/areas/software/frontend/workflows/visual-regression.md +32 -0
  46. package/areas/software/full-stack/workflows/backend-project-full-cycle.md +47 -2
  47. package/areas/software/full-stack/workflows/debug-issue.md +29 -0
  48. package/areas/software/full-stack/workflows/develop-feature.md +38 -0
  49. package/areas/software/full-stack/workflows/feature-implementation-flow.md +38 -0
  50. package/areas/software/full-stack/workflows/testing-ci-pipeline.md +30 -0
  51. package/areas/software/general/workflows/code-review-workflow.md +31 -0
  52. package/areas/software/general/workflows/development-cycle-workflow.md +38 -0
  53. package/areas/software/general/workflows/project-setup-workflow.md +38 -0
  54. package/areas/software/mlops/workflows/champion-challenger.md +29 -0
  55. package/areas/software/mlops/workflows/deploy-endpoint.md +30 -0
  56. package/areas/software/mlops/workflows/evaluate-model.md +28 -0
  57. package/areas/software/mlops/workflows/model-incident.md +29 -0
  58. package/areas/software/mlops/workflows/train-experiment.md +25 -0
  59. package/areas/software/mobile/workflows/crash-triage.md +28 -0
  60. package/areas/software/mobile/workflows/device-testing.md +27 -0
  61. package/areas/software/mobile/workflows/ota-update.md +25 -0
  62. package/areas/software/mobile/workflows/release-build.md +30 -0
  63. package/areas/software/mobile/workflows/store-submission.md +29 -0
  64. package/areas/software/platform/workflows/cost-audit.md +28 -0
  65. package/areas/software/platform/workflows/deploy-production.md +30 -0
  66. package/areas/software/platform/workflows/drift-check.md +29 -0
  67. package/areas/software/platform/workflows/incident-response.md +33 -0
  68. package/areas/software/platform/workflows/provision-env.md +36 -0
  69. package/areas/software/qa/workflows/flakiness-investigation.md +30 -0
  70. package/areas/software/qa/workflows/performance-audit.md +29 -0
  71. package/areas/software/qa/workflows/regression-suite.md +28 -0
  72. package/areas/software/qa/workflows/smoke-test.md +31 -0
  73. package/areas/software/qa/workflows/test-coverage-report.md +28 -0
  74. package/areas/software/security/workflows/compliance-report.md +27 -0
  75. package/areas/software/security/workflows/pen-test-sim.md +28 -0
  76. package/areas/software/security/workflows/secret-rotation.md +33 -2
  77. package/areas/software/security/workflows/security-scan.md +29 -0
  78. package/areas/software/security/workflows/threat-model-review.md +30 -0
  79. package/docs/agentic-usage.md +1 -1
  80. package/docs/catalog.schema.json +5 -1
  81. package/docs/opencode_setup.md +10 -0
  82. package/docs/site/README.md +15 -1
  83. package/docs/site/app.js +68 -0
  84. package/docs/site/catalog.json +74 -1
  85. package/docs/site/index.html +5 -1
  86. package/docs/site/styles.css +52 -4
  87. package/extensions/opencode/opencode.json +0 -1
  88. package/extensions/opencode/profiles/githubcopilot/opencode.json +1 -2
  89. package/extensions/opencode/profiles/openai/opencode.json +20 -20
  90. package/package.json +1 -1
  91. package/scripts/build_docs_catalog.py +13 -1
  92. package/scripts/sync_workflow_diagrams.py +199 -0
  93. package/extensions/opencode/plugins/sound-notification.ts +0 -13
@@ -27,13 +27,16 @@
27
27
  </div>
28
28
  </div>
29
29
 
30
- <div class="toolbar" aria-label="Search docs">
30
+ <div class="toolbar" aria-label="Docs controls">
31
31
  <input id="search" type="search" placeholder="Search trigger, workflow, examples..." />
32
32
  <select id="language" aria-label="Language">
33
33
  <option value="both">EN + RU</option>
34
34
  <option value="en">EN only</option>
35
35
  <option value="ru">RU only</option>
36
36
  </select>
37
+ <button id="theme-toggle" class="theme-toggle" type="button" aria-pressed="false">
38
+ Dark
39
+ </button>
37
40
  </div>
38
41
  </div>
39
42
  </header>
@@ -47,6 +50,7 @@
47
50
 
48
51
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
49
52
  <script src="https://cdn.jsdelivr.net/npm/lunr/lunr.min.js"></script>
53
+ <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
50
54
  <script src="./app.js" type="module"></script>
51
55
  </body>
52
56
  </html>
@@ -1,5 +1,18 @@
1
1
  :root {
2
- color-scheme: light dark;
2
+ color-scheme: light;
3
+ --bg: #f8fafc;
4
+ --fg: #0f172a;
5
+ --muted: #64748b;
6
+ --accent: #15803d;
7
+ --panel: rgba(255, 255, 255, 0.86);
8
+ --panel-strong: #ffffff;
9
+ --border: #cbd5e1;
10
+ --code-bg: #e2e8f0;
11
+ --code-fg: #0f172a;
12
+ --mermaid-bg: rgba(226, 232, 240, 0.45);
13
+ }
14
+ html[data-theme="dark"] {
15
+ color-scheme: dark;
3
16
  --bg: #0f172a;
4
17
  --fg: #e2e8f0;
5
18
  --muted: #94a3b8;
@@ -8,6 +21,8 @@
8
21
  --panel-strong: #1e293b;
9
22
  --border: #334155;
10
23
  --code-bg: #020617;
24
+ --code-fg: #e2e8f0;
25
+ --mermaid-bg: rgba(2, 6, 23, 0.35);
11
26
  }
12
27
  body {
13
28
  margin: 0;
@@ -90,7 +105,8 @@ body {
90
105
  padding: 0.45rem 0.65rem;
91
106
  border: 1px solid var(--border);
92
107
  border-radius: 10px;
93
- background: rgba(2, 6, 23, 0.75);
108
+ background: var(--code-bg);
109
+ color: var(--code-fg);
94
110
  font-size: 0.75rem;
95
111
  line-height: 1.35;
96
112
  overflow-wrap: anywhere;
@@ -131,12 +147,43 @@ input, select {
131
147
  border-radius: 10px;
132
148
  padding: 0.5rem 0.75rem;
133
149
  }
150
+ .theme-toggle {
151
+ flex: 0 0 auto;
152
+ min-width: 5rem;
153
+ background: var(--panel-strong);
154
+ color: var(--fg);
155
+ border: 1px solid var(--border);
156
+ border-radius: 10px;
157
+ padding: 0.5rem 0.75rem;
158
+ cursor: pointer;
159
+ }
160
+ .theme-toggle:hover {
161
+ border-color: var(--accent);
162
+ }
163
+ .theme-toggle[aria-pressed="true"] {
164
+ background: var(--accent);
165
+ border-color: var(--accent);
166
+ color: #ffffff;
167
+ }
134
168
  .area-title { font-size: .85rem; text-transform: uppercase; color: var(--muted); margin: 1rem 0 .4rem; }
135
169
  .wf-btn { width: 100%; text-align: left; margin-bottom: .4rem; background: var(--panel-strong); color: var(--fg); border: 1px solid var(--border); border-radius: 8px; padding: .5rem; cursor: pointer; }
136
170
  .wf-btn:hover { border-color: var(--accent); }
137
- code, pre { background: var(--code-bg); border-radius: 8px; }
171
+ code, pre { background: var(--code-bg); color: var(--code-fg); border-radius: 8px; }
138
172
  .chip { display:inline-block; border:1px solid var(--border); border-radius:999px; padding:.15rem .5rem; margin:.15rem; color:var(--muted); font-size:.82rem; }
139
173
  .meta { color: var(--muted); }
174
+ .mermaid {
175
+ max-width: 100%;
176
+ margin: 1rem 0;
177
+ padding: 1rem;
178
+ border: 1px solid var(--border);
179
+ border-radius: 8px;
180
+ background: var(--mermaid-bg);
181
+ overflow-x: auto;
182
+ }
183
+ .mermaid svg {
184
+ display: block;
185
+ max-width: none;
186
+ }
140
187
 
141
188
  @media (max-width: 980px) {
142
189
  .site-header__inner {
@@ -162,7 +209,8 @@ code, pre { background: var(--code-bg); border-radius: 8px; }
162
209
  flex-wrap: wrap;
163
210
  }
164
211
  .toolbar input,
165
- .toolbar select {
212
+ .toolbar select,
213
+ .theme-toggle {
166
214
  width: 100%;
167
215
  flex-basis: 100%;
168
216
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://opencode.ai/config.json",
3
3
  "plugin": [
4
- "sound-notification",
5
4
  "telegram-notification"
6
5
  ],
7
6
  "agent": {
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://opencode.ai/config.json",
3
3
  "plugin": [
4
- "sound-notification",
5
4
  "telegram-notification"
6
5
  ],
7
6
  "agent": {
@@ -39,7 +38,7 @@
39
38
  "team-lead": {
40
39
  "description": "Team Lead - planning and code review",
41
40
  "mode": "all",
42
- "model": "github-copilot/claude-opus-4.7",
41
+ "model": "github-copilot/claude-opus-4.8",
43
42
  "fallback": [],
44
43
  "permission": {
45
44
  "task": {
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "$schema": "https://opencode.ai/config.json",
3
3
  "plugin": [
4
- "sound-notification",
5
4
  "telegram-notification"
6
5
  ],
6
+ "small_model": "openai/gpt-5-nano",
7
7
  "agent": {
8
8
  "product-owner": {
9
9
  "description": "Main coordinator for feature development workflow",
10
10
  "mode": "primary",
11
11
  "model": "openai/gpt-5.5",
12
- "fallback": [],
12
+ "fallback": ["openai/gpt-5.4"],
13
13
  "permission": {
14
14
  "task": {
15
15
  "pm": "allow",
@@ -25,8 +25,8 @@
25
25
  "pm": {
26
26
  "description": "Project Manager - coordinates workflow",
27
27
  "mode": "subagent",
28
- "model": "openai/gpt-5.5",
29
- "fallback": [],
28
+ "model": "openai/gpt-5.4-mini",
29
+ "fallback": ["openai/gpt-5.4"],
30
30
  "permission": {
31
31
  "task": {
32
32
  "team-lead": "allow",
@@ -40,7 +40,7 @@
40
40
  "description": "Team Lead - planning and code review",
41
41
  "mode": "all",
42
42
  "model": "openai/gpt-5.5",
43
- "fallback": [],
43
+ "fallback": ["openai/gpt-5.4"],
44
44
  "permission": {
45
45
  "task": {
46
46
  "developer": "allow",
@@ -51,50 +51,50 @@
51
51
  "developer": {
52
52
  "description": "Developer - implements code",
53
53
  "mode": "all",
54
- "model": "openai/gpt-5.5",
55
- "fallback": []
54
+ "model": "openai/gpt-5.4-mini",
55
+ "fallback": ["openai/gpt-5.4"]
56
56
  },
57
57
  "devops-engineer": {
58
58
  "description": "DevOps Engineer - infrastructure, CI/CD, and platform reliability",
59
59
  "mode": "all",
60
- "model": "openai/gpt-5.5",
61
- "fallback": []
60
+ "model": "openai/gpt-5.4-mini",
61
+ "fallback": ["openai/gpt-5.4"]
62
62
  },
63
63
  "instruction_reviewer": {
64
64
  "description": "Instruction Reviewer - post-task review of instruction effectiveness and tool discipline",
65
65
  "mode": "all",
66
- "model": "openai/gpt-5.5",
67
- "fallback": []
66
+ "model": "openai/gpt-5-nano",
67
+ "fallback": ["openai/gpt-5.4-nano"]
68
68
  },
69
69
  "memory_curator": {
70
70
  "description": "Memory Curator - post-task memory hygiene recommendations without automatic writes",
71
71
  "mode": "all",
72
- "model": "openai/gpt-5.5",
73
- "fallback": []
72
+ "model": "openai/gpt-5-nano",
73
+ "fallback": ["openai/gpt-5.4-nano"]
74
74
  },
75
75
  "qa": {
76
76
  "description": "QA Engineer - runs tests",
77
77
  "mode": "subagent",
78
- "model": "openai/gpt-5.5",
79
- "fallback": []
78
+ "model": "openai/gpt-5.4-nano",
79
+ "fallback": ["openai/gpt-5.4-mini"]
80
80
  },
81
81
  "designer": {
82
82
  "description": "Designer - UI/UX validation",
83
83
  "mode": "subagent",
84
- "model": "openai/gpt-5.5",
85
- "fallback": []
84
+ "model": "openai/gpt-5.4-nano",
85
+ "fallback": ["openai/gpt-5.4-mini"]
86
86
  },
87
87
  "plan": {
88
88
  "description": "Planning mode",
89
89
  "mode": "primary",
90
90
  "model": "openai/gpt-5.5",
91
- "fallback": []
91
+ "fallback": ["openai/gpt-5.4"]
92
92
  },
93
93
  "build": {
94
94
  "description": "Build mode",
95
95
  "mode": "primary",
96
- "model": "openai/gpt-5.5",
97
- "fallback": []
96
+ "model": "openai/gpt-5.4-mini",
97
+ "fallback": ["openai/gpt-5.4"]
98
98
  }
99
99
  }
100
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetrabbits/agentic",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Agent Intelligence Configuration CLI",
5
5
  "bin": {
6
6
  "agentic": "bin/agentic.js"
@@ -31,6 +31,10 @@ PLACEHOLDER_STRINGS = (
31
31
  "Goal: execute workflow steps end-to-end",
32
32
  "Use when: run workflow",
33
33
  )
34
+ WORKFLOW_DIAGRAM_RE = re.compile(
35
+ r"<!-- agent-diagram:start -->\n```mermaid\n(.*?)\n```\n<!-- agent-diagram:end -->",
36
+ re.DOTALL,
37
+ )
34
38
 
35
39
 
36
40
  def _parse_yaml_list(frontmatter: str, key: str) -> List[str]:
@@ -76,6 +80,7 @@ class Workflow:
76
80
  uses_skills: List[str]
77
81
  quality_gates: List[str]
78
82
  skill_refs: List[dict]
83
+ workflow_diagram: str
79
84
 
80
85
 
81
86
  @dataclass
@@ -126,9 +131,15 @@ def parse_workflow(path: Path) -> Workflow:
126
131
  uses_skills=uses_skills,
127
132
  quality_gates=_parse_yaml_list(front, "quality-gates"),
128
133
  skill_refs=_resolve_skill_paths(area, uses_skills),
134
+ workflow_diagram=_extract_workflow_diagram(text),
129
135
  )
130
136
 
131
137
 
138
+ def _extract_workflow_diagram(text: str) -> str:
139
+ match = WORKFLOW_DIAGRAM_RE.search(text)
140
+ return match.group(1).strip() if match else ""
141
+
142
+
132
143
  def parse_prompt(path: Path) -> Prompt:
133
144
  text = path.read_text(encoding="utf-8")
134
145
  frontmatter = PROMPT_FRONTMATTER_RE.match(text)
@@ -242,12 +253,13 @@ def build_catalog(validate: bool = False) -> Tuple[dict, List[str]]:
242
253
  "uses_skills": wf.uses_skills,
243
254
  "skill_refs": wf.skill_refs,
244
255
  "quality_gates": wf.quality_gates,
256
+ "workflow_diagram": wf.workflow_diagram,
245
257
  "examples": {"both": [asdict(e) for e in (pr.examples if pr else [])]},
246
258
  }
247
259
  )
248
260
 
249
261
  catalog = {
250
- "version": "1.1.0",
262
+ "version": "1.2.0",
251
263
  "generated_from": "areas/**/{workflows,prompts}",
252
264
  "areas": list(areas_out.values()),
253
265
  "stats": {
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import re
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Iterable
9
+
10
+ ROOT = Path(__file__).resolve().parents[1]
11
+ AREAS_DIR = ROOT / "areas"
12
+
13
+ FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL)
14
+ TRIGGER_RE = re.compile(r"^trigger:\s*(.+)$", re.MULTILINE)
15
+ INITIATOR_RE = re.compile(r"^\s{2}initiator:\s*(.+)$", re.MULTILINE)
16
+ H3_RE = re.compile(r"^###\s+(.+)$", re.MULTILINE)
17
+ H2_RE = re.compile(r"^##\s+(.+)$", re.MULTILINE)
18
+ ROLE_MENTION_RE = re.compile(r"`@([^`]+)`")
19
+ GENERATED_SECTION_RE = re.compile(
20
+ r"\n?## Agent Interaction Diagram\n\n"
21
+ r"<!-- agent-diagram:start -->\n"
22
+ r"```mermaid\n.*?\n```\n"
23
+ r"<!-- agent-diagram:end -->\n?",
24
+ re.DOTALL,
25
+ )
26
+
27
+
28
+ @dataclass
29
+ class Step:
30
+ label: str
31
+ roles: list[str]
32
+
33
+
34
+ def _parse_yaml_list(frontmatter: str, key: str) -> list[str]:
35
+ m = re.search(rf"^{re.escape(key)}:\s*\n((?:\s*-\s.*\n)+)", frontmatter, re.MULTILINE)
36
+ if not m:
37
+ inline = re.search(rf"^{re.escape(key)}:\s*\[(.*?)\]\s*$", frontmatter, re.MULTILINE)
38
+ if not inline:
39
+ return []
40
+ return [x.strip().strip("'\"") for x in inline.group(1).split(",") if x.strip()]
41
+ return [line.strip()[1:].strip().strip("'\"") for line in m.group(1).splitlines() if line.strip().startswith("-")]
42
+
43
+
44
+ def _clean_role(role: str) -> str:
45
+ return role.strip().strip("'\"").removeprefix("@").strip()
46
+
47
+
48
+ def _clean_step_label(raw: str) -> str:
49
+ label = re.sub(r"\s+[—-]\s+`@[^`]+`(?:\s*\+\s*`@[^`]+`)*.*$", "", raw).strip()
50
+ label = re.sub(r"`@[^`]+`", "", label).strip()
51
+ label = re.sub(r"^Step\s+", "", label, flags=re.IGNORECASE)
52
+ return label
53
+
54
+
55
+ def _extract_steps(text: str, fallback_roles: list[str]) -> list[Step]:
56
+ steps: list[Step] = []
57
+ for match in H3_RE.finditer(text):
58
+ heading = match.group(1).strip()
59
+ roles = [_clean_role(role) for role in ROLE_MENTION_RE.findall(heading)]
60
+ roles = [role for role in roles if role]
61
+ steps.append(Step(label=_clean_step_label(heading), roles=roles or fallback_roles))
62
+ return steps
63
+
64
+
65
+ def _extract_exit(text: str) -> str:
66
+ match = re.search(r"^## Exit\n\n?(.+?)(?:\n## |\Z)", text, re.MULTILINE | re.DOTALL)
67
+ if not match:
68
+ return "Workflow complete"
69
+ first_line = next((line.strip() for line in match.group(1).splitlines() if line.strip()), "")
70
+ return first_line or "Workflow complete"
71
+
72
+
73
+ def _has_loop(text: str) -> bool:
74
+ return bool(re.search(r"^##\s+(Iteration Loop|Rollback|Rollbacks?)\b", text, re.MULTILINE))
75
+
76
+
77
+ def _node_id(prefix: str, index: int) -> str:
78
+ return f"{prefix}_{index}"
79
+
80
+
81
+ def _mermaid_label(value: str, max_len: int = 78) -> str:
82
+ compact = re.sub(r"\s+", " ", value).strip()
83
+ compact = compact.replace("`", "").replace("**", "")
84
+ if len(compact) > max_len:
85
+ compact = compact[: max_len - 3].rstrip() + "..."
86
+ return compact.replace("\\", "\\\\").replace('"', '\\"')
87
+
88
+
89
+ def _unique(values: Iterable[str]) -> list[str]:
90
+ seen: set[str] = set()
91
+ out: list[str] = []
92
+ for value in values:
93
+ if value and value not in seen:
94
+ seen.add(value)
95
+ out.append(value)
96
+ return out
97
+
98
+
99
+ def build_diagram(text: str) -> str:
100
+ front = FRONTMATTER_RE.search(text)
101
+ frontmatter = front.group(1) if front else ""
102
+ trigger = TRIGGER_RE.search(frontmatter)
103
+ initiator = INITIATOR_RE.search(frontmatter)
104
+
105
+ trigger_label = trigger.group(1).strip() if trigger else "/workflow"
106
+ roles = [_clean_role(role) for role in _parse_yaml_list(frontmatter, "roles")]
107
+ if initiator:
108
+ roles = _unique([_clean_role(initiator.group(1)), *roles])
109
+ steps = _extract_steps(text, roles)
110
+
111
+ lines = ["flowchart TD", f' start(["Start { _mermaid_label(trigger_label) }"])']
112
+ role_ids: dict[str, str] = {}
113
+ for index, role in enumerate(_unique(role for step in steps for role in step.roles), 1):
114
+ role_id = _node_id("role", index)
115
+ role_ids[role] = role_id
116
+ lines.append(f' {role_id}["{_mermaid_label(role)}"]')
117
+
118
+ if steps:
119
+ for index, step in enumerate(steps, 1):
120
+ lines.append(f' step_{index}["{_mermaid_label(step.label)}"]')
121
+ lines.append(f' exit(["{_mermaid_label(_extract_exit(text))}"])')
122
+ lines.append(" start --> step_1")
123
+ for index in range(1, len(steps)):
124
+ lines.append(f" step_{index} --> step_{index + 1}")
125
+ lines.append(f" step_{len(steps)} --> exit")
126
+ for index, step in enumerate(steps, 1):
127
+ for role in step.roles:
128
+ role_id = role_ids.get(role)
129
+ if role_id:
130
+ lines.append(f" {role_id} -. owns .-> step_{index}")
131
+ if _has_loop(text) and len(steps) > 1:
132
+ lines.append(f" step_{len(steps)} -. iterate if blocked .-> step_1")
133
+ else:
134
+ lines.append(f' exit(["{_mermaid_label(_extract_exit(text))}"])')
135
+ lines.append(" start --> exit")
136
+ for role, role_id in role_ids.items():
137
+ lines.append(f" {role_id} -. owns .-> exit")
138
+
139
+ return "\n".join(lines)
140
+
141
+
142
+ def _section(diagram: str) -> str:
143
+ return (
144
+ "## Agent Interaction Diagram\n\n"
145
+ "<!-- agent-diagram:start -->\n"
146
+ "```mermaid\n"
147
+ f"{diagram}\n"
148
+ "```\n"
149
+ "<!-- agent-diagram:end -->\n"
150
+ )
151
+
152
+
153
+ def _insert_index(text: str) -> int:
154
+ text_without_existing = GENERATED_SECTION_RE.sub("\n", text)
155
+ candidates = []
156
+ for match in H2_RE.finditer(text_without_existing):
157
+ heading = match.group(1).strip().lower()
158
+ if heading.startswith(("iteration loop", "rollback", "exit", "mandatory role delegation")):
159
+ candidates.append(match.start())
160
+ if candidates:
161
+ return candidates[0]
162
+ return len(text_without_existing.rstrip()) + 1
163
+
164
+
165
+ def sync_text(text: str) -> str:
166
+ diagram = build_diagram(text)
167
+ text = GENERATED_SECTION_RE.sub("\n", text).rstrip() + "\n"
168
+ section = _section(diagram)
169
+ idx = _insert_index(text)
170
+ before = text[:idx].rstrip()
171
+ after = text[idx:].lstrip("\n")
172
+ if after:
173
+ return f"{before}\n\n{section}\n{after}"
174
+ return f"{before}\n\n{section}"
175
+
176
+
177
+ def main() -> int:
178
+ parser = argparse.ArgumentParser()
179
+ parser.add_argument("--check", action="store_true", help="Fail if any workflow diagram is out of date.")
180
+ args = parser.parse_args()
181
+
182
+ changed: list[Path] = []
183
+ for path in sorted(AREAS_DIR.glob("**/workflows/*.md")):
184
+ original = path.read_text(encoding="utf-8")
185
+ updated = sync_text(original)
186
+ if updated != original:
187
+ changed.append(path)
188
+ if not args.check:
189
+ path.write_text(updated, encoding="utf-8")
190
+
191
+ if changed:
192
+ for path in changed:
193
+ print(path.relative_to(ROOT))
194
+ return 1 if args.check else 0
195
+ return 0
196
+
197
+
198
+ if __name__ == "__main__":
199
+ raise SystemExit(main())
@@ -1,13 +0,0 @@
1
- import type { Plugin } from "@opencode-ai/plugin"
2
-
3
- export const SoundNotificationPlugin: Plugin = async ({ $ }) => {
4
- return {
5
- event: async ({ event }) => {
6
- if (event.type === "session.idle") {
7
- try {
8
- await $`afplay /System/Library/Sounds/Glass.aiff`
9
- } catch {}
10
- }
11
- },
12
- }
13
- }