@jetrabbits/agentic 0.3.3 → 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.
- package/AGENTS.md +8 -0
- package/CHANGELOG.md +18 -0
- package/Makefile +26 -5
- package/README.md +25 -6
- package/agentic +801 -66
- package/areas/devops/ci-cd/workflows/onboard-repo.md +29 -0
- package/areas/devops/ci-cd/workflows/pipeline-debug.md +26 -0
- package/areas/devops/ci-cd/workflows/release-pipeline.md +53 -0
- package/areas/devops/database-ops/workflows/backup-verify.md +27 -0
- package/areas/devops/database-ops/workflows/db-incident.md +30 -0
- package/areas/devops/devsecops/workflows/policy-onboard.md +34 -0
- package/areas/devops/devsecops/workflows/security-scan-pipeline.md +33 -0
- package/areas/devops/infrastructure/workflows/destroy-environment.md +31 -0
- package/areas/devops/infrastructure/workflows/drift-remediation.md +29 -0
- package/areas/devops/infrastructure/workflows/module-development.md +32 -0
- package/areas/devops/infrastructure/workflows/provision-environment.md +29 -0
- package/areas/devops/kubernetes/workflows/cluster-bootstrap.md +36 -0
- package/areas/devops/kubernetes/workflows/debug-workload.md +29 -0
- package/areas/devops/kubernetes/workflows/onboard-service.md +35 -0
- package/areas/devops/kubernetes/workflows/upgrade-cluster.md +30 -0
- package/areas/devops/networking/workflows/onboard-ingress.md +27 -0
- package/areas/devops/networking/workflows/service-mesh-onboard.md +27 -0
- package/areas/devops/observability/workflows/alert-investigation.md +29 -0
- package/areas/devops/observability/workflows/observability-stack-setup.md +33 -0
- package/areas/devops/observability/workflows/onboard-service-monitoring.md +31 -0
- package/areas/devops/sre/workflows/incident-response.md +48 -0
- package/areas/devops/sre/workflows/postmortem.md +32 -0
- package/areas/devops/sre/workflows/slo-review.md +35 -1
- package/areas/software/backend/workflows/add-migration.md +33 -0
- package/areas/software/backend/workflows/create-endpoint.md +40 -0
- package/areas/software/backend/workflows/debug-issue.md +31 -0
- package/areas/software/backend/workflows/develop-epic.md +37 -0
- package/areas/software/backend/workflows/develop-feature.md +44 -0
- package/areas/software/backend/workflows/refactor-module.md +35 -0
- package/areas/software/backend/workflows/test-feature.md +30 -0
- package/areas/software/data-engineering/workflows/backfill-data.md +25 -0
- package/areas/software/data-engineering/workflows/data-quality-incident.md +31 -0
- package/areas/software/data-engineering/workflows/lineage-trace.md +25 -0
- package/areas/software/data-engineering/workflows/new-model.md +30 -0
- package/areas/software/data-engineering/workflows/schema-migration.md +29 -0
- package/areas/software/frontend/workflows/a11y-fix.md +30 -0
- package/areas/software/frontend/workflows/bundle-analyze.md +28 -0
- package/areas/software/frontend/workflows/release-prep.md +33 -0
- package/areas/software/frontend/workflows/scaffold-component.md +32 -0
- package/areas/software/frontend/workflows/visual-regression.md +32 -0
- package/areas/software/full-stack/workflows/backend-project-full-cycle.md +47 -2
- package/areas/software/full-stack/workflows/debug-issue.md +29 -0
- package/areas/software/full-stack/workflows/develop-feature.md +38 -0
- package/areas/software/full-stack/workflows/feature-implementation-flow.md +38 -0
- package/areas/software/full-stack/workflows/testing-ci-pipeline.md +30 -0
- package/areas/software/general/workflows/code-review-workflow.md +31 -0
- package/areas/software/general/workflows/development-cycle-workflow.md +38 -0
- package/areas/software/general/workflows/project-setup-workflow.md +38 -0
- package/areas/software/mlops/workflows/champion-challenger.md +29 -0
- package/areas/software/mlops/workflows/deploy-endpoint.md +30 -0
- package/areas/software/mlops/workflows/evaluate-model.md +28 -0
- package/areas/software/mlops/workflows/model-incident.md +29 -0
- package/areas/software/mlops/workflows/train-experiment.md +25 -0
- package/areas/software/mobile/workflows/crash-triage.md +28 -0
- package/areas/software/mobile/workflows/device-testing.md +27 -0
- package/areas/software/mobile/workflows/ota-update.md +25 -0
- package/areas/software/mobile/workflows/release-build.md +30 -0
- package/areas/software/mobile/workflows/store-submission.md +29 -0
- package/areas/software/platform/workflows/cost-audit.md +28 -0
- package/areas/software/platform/workflows/deploy-production.md +30 -0
- package/areas/software/platform/workflows/drift-check.md +29 -0
- package/areas/software/platform/workflows/incident-response.md +33 -0
- package/areas/software/platform/workflows/provision-env.md +36 -0
- package/areas/software/qa/workflows/flakiness-investigation.md +30 -0
- package/areas/software/qa/workflows/performance-audit.md +29 -0
- package/areas/software/qa/workflows/regression-suite.md +28 -0
- package/areas/software/qa/workflows/smoke-test.md +31 -0
- package/areas/software/qa/workflows/test-coverage-report.md +28 -0
- package/areas/software/security/workflows/compliance-report.md +27 -0
- package/areas/software/security/workflows/pen-test-sim.md +28 -0
- package/areas/software/security/workflows/secret-rotation.md +33 -2
- package/areas/software/security/workflows/security-scan.md +29 -0
- package/areas/software/security/workflows/threat-model-review.md +30 -0
- package/docs/agentic-usage.md +19 -2
- package/docs/catalog.schema.json +5 -1
- package/docs/mcp/README.md +28 -0
- package/docs/opencode_setup.md +21 -1
- package/docs/site/README.md +15 -1
- package/docs/site/app.js +68 -0
- package/docs/site/catalog.json +74 -1
- package/docs/site/index.html +5 -1
- package/docs/site/styles.css +52 -4
- package/extensions/opencode/opencode.json +0 -1
- package/extensions/opencode/profiles/githubcopilot/opencode.json +87 -0
- package/extensions/opencode/profiles/openai/opencode.json +100 -0
- package/package.json +1 -1
- package/scripts/build_docs_catalog.py +13 -1
- package/scripts/sync_workflow_diagrams.py +199 -0
- package/extensions/opencode/plugins/sound-notification.ts +0 -13
package/docs/site/index.html
CHANGED
|
@@ -27,13 +27,16 @@
|
|
|
27
27
|
</div>
|
|
28
28
|
</div>
|
|
29
29
|
|
|
30
|
-
<div class="toolbar" aria-label="
|
|
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>
|
package/docs/site/styles.css
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
color-scheme: light
|
|
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:
|
|
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
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"plugin": [
|
|
4
|
+
"telegram-notification"
|
|
5
|
+
],
|
|
6
|
+
"agent": {
|
|
7
|
+
"product-owner": {
|
|
8
|
+
"description": "Main coordinator for feature development workflow",
|
|
9
|
+
"mode": "primary",
|
|
10
|
+
"model": "github-copilot/gpt-5.5",
|
|
11
|
+
"fallback": [],
|
|
12
|
+
"permission": {
|
|
13
|
+
"task": {
|
|
14
|
+
"pm": "allow",
|
|
15
|
+
"team-lead": "allow",
|
|
16
|
+
"designer": "allow",
|
|
17
|
+
"developer": "allow",
|
|
18
|
+
"qa": "allow",
|
|
19
|
+
"instruction_reviewer": "allow",
|
|
20
|
+
"memory_curator": "allow"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"pm": {
|
|
25
|
+
"description": "Project Manager - coordinates workflow",
|
|
26
|
+
"mode": "subagent",
|
|
27
|
+
"model": "github-copilot/gpt-5.3-codex",
|
|
28
|
+
"fallback": [],
|
|
29
|
+
"permission": {
|
|
30
|
+
"task": {
|
|
31
|
+
"team-lead": "allow",
|
|
32
|
+
"designer": "allow",
|
|
33
|
+
"developer": "allow",
|
|
34
|
+
"qa": "allow"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"team-lead": {
|
|
39
|
+
"description": "Team Lead - planning and code review",
|
|
40
|
+
"mode": "all",
|
|
41
|
+
"model": "github-copilot/claude-opus-4.8",
|
|
42
|
+
"fallback": [],
|
|
43
|
+
"permission": {
|
|
44
|
+
"task": {
|
|
45
|
+
"developer": "allow",
|
|
46
|
+
"qa": "allow"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"developer": {
|
|
51
|
+
"description": "Developer - implements code",
|
|
52
|
+
"mode": "all",
|
|
53
|
+
"model": "github-copilot/gpt-5.3-codex",
|
|
54
|
+
"fallback": []
|
|
55
|
+
},
|
|
56
|
+
"devops-engineer": {
|
|
57
|
+
"description": "DevOps Engineer - infrastructure, CI/CD, and platform reliability",
|
|
58
|
+
"mode": "all",
|
|
59
|
+
"model": "github-copilot/claude-sonnet-4.6",
|
|
60
|
+
"fallback": []
|
|
61
|
+
},
|
|
62
|
+
"instruction_reviewer": {
|
|
63
|
+
"description": "Instruction Reviewer - post-task review of instruction effectiveness and tool discipline",
|
|
64
|
+
"mode": "all",
|
|
65
|
+
"model": "github-copilot/claude-sonnet-4.6",
|
|
66
|
+
"fallback": []
|
|
67
|
+
},
|
|
68
|
+
"memory_curator": {
|
|
69
|
+
"description": "Memory Curator - post-task memory hygiene recommendations without automatic writes",
|
|
70
|
+
"mode": "all",
|
|
71
|
+
"model": "github-copilot/gpt-5-mini",
|
|
72
|
+
"fallback": []
|
|
73
|
+
},
|
|
74
|
+
"qa": {
|
|
75
|
+
"description": "QA Engineer - runs tests",
|
|
76
|
+
"mode": "subagent",
|
|
77
|
+
"model": "github-copilot/gpt-5.3-codex",
|
|
78
|
+
"fallback": []
|
|
79
|
+
},
|
|
80
|
+
"designer": {
|
|
81
|
+
"description": "Designer - UI/UX validation",
|
|
82
|
+
"mode": "subagent",
|
|
83
|
+
"model": "github-copilot/gemini-3.5-flash",
|
|
84
|
+
"fallback": []
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"plugin": [
|
|
4
|
+
"telegram-notification"
|
|
5
|
+
],
|
|
6
|
+
"small_model": "openai/gpt-5-nano",
|
|
7
|
+
"agent": {
|
|
8
|
+
"product-owner": {
|
|
9
|
+
"description": "Main coordinator for feature development workflow",
|
|
10
|
+
"mode": "primary",
|
|
11
|
+
"model": "openai/gpt-5.5",
|
|
12
|
+
"fallback": ["openai/gpt-5.4"],
|
|
13
|
+
"permission": {
|
|
14
|
+
"task": {
|
|
15
|
+
"pm": "allow",
|
|
16
|
+
"team-lead": "allow",
|
|
17
|
+
"designer": "allow",
|
|
18
|
+
"developer": "allow",
|
|
19
|
+
"qa": "allow",
|
|
20
|
+
"instruction_reviewer": "allow",
|
|
21
|
+
"memory_curator": "allow"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"pm": {
|
|
26
|
+
"description": "Project Manager - coordinates workflow",
|
|
27
|
+
"mode": "subagent",
|
|
28
|
+
"model": "openai/gpt-5.4-mini",
|
|
29
|
+
"fallback": ["openai/gpt-5.4"],
|
|
30
|
+
"permission": {
|
|
31
|
+
"task": {
|
|
32
|
+
"team-lead": "allow",
|
|
33
|
+
"designer": "allow",
|
|
34
|
+
"developer": "allow",
|
|
35
|
+
"qa": "allow"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"team-lead": {
|
|
40
|
+
"description": "Team Lead - planning and code review",
|
|
41
|
+
"mode": "all",
|
|
42
|
+
"model": "openai/gpt-5.5",
|
|
43
|
+
"fallback": ["openai/gpt-5.4"],
|
|
44
|
+
"permission": {
|
|
45
|
+
"task": {
|
|
46
|
+
"developer": "allow",
|
|
47
|
+
"qa": "allow"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"developer": {
|
|
52
|
+
"description": "Developer - implements code",
|
|
53
|
+
"mode": "all",
|
|
54
|
+
"model": "openai/gpt-5.4-mini",
|
|
55
|
+
"fallback": ["openai/gpt-5.4"]
|
|
56
|
+
},
|
|
57
|
+
"devops-engineer": {
|
|
58
|
+
"description": "DevOps Engineer - infrastructure, CI/CD, and platform reliability",
|
|
59
|
+
"mode": "all",
|
|
60
|
+
"model": "openai/gpt-5.4-mini",
|
|
61
|
+
"fallback": ["openai/gpt-5.4"]
|
|
62
|
+
},
|
|
63
|
+
"instruction_reviewer": {
|
|
64
|
+
"description": "Instruction Reviewer - post-task review of instruction effectiveness and tool discipline",
|
|
65
|
+
"mode": "all",
|
|
66
|
+
"model": "openai/gpt-5-nano",
|
|
67
|
+
"fallback": ["openai/gpt-5.4-nano"]
|
|
68
|
+
},
|
|
69
|
+
"memory_curator": {
|
|
70
|
+
"description": "Memory Curator - post-task memory hygiene recommendations without automatic writes",
|
|
71
|
+
"mode": "all",
|
|
72
|
+
"model": "openai/gpt-5-nano",
|
|
73
|
+
"fallback": ["openai/gpt-5.4-nano"]
|
|
74
|
+
},
|
|
75
|
+
"qa": {
|
|
76
|
+
"description": "QA Engineer - runs tests",
|
|
77
|
+
"mode": "subagent",
|
|
78
|
+
"model": "openai/gpt-5.4-nano",
|
|
79
|
+
"fallback": ["openai/gpt-5.4-mini"]
|
|
80
|
+
},
|
|
81
|
+
"designer": {
|
|
82
|
+
"description": "Designer - UI/UX validation",
|
|
83
|
+
"mode": "subagent",
|
|
84
|
+
"model": "openai/gpt-5.4-nano",
|
|
85
|
+
"fallback": ["openai/gpt-5.4-mini"]
|
|
86
|
+
},
|
|
87
|
+
"plan": {
|
|
88
|
+
"description": "Planning mode",
|
|
89
|
+
"mode": "primary",
|
|
90
|
+
"model": "openai/gpt-5.5",
|
|
91
|
+
"fallback": ["openai/gpt-5.4"]
|
|
92
|
+
},
|
|
93
|
+
"build": {
|
|
94
|
+
"description": "Build mode",
|
|
95
|
+
"mode": "primary",
|
|
96
|
+
"model": "openai/gpt-5.4-mini",
|
|
97
|
+
"fallback": ["openai/gpt-5.4"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
package/package.json
CHANGED
|
@@ -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.
|
|
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
|
-
}
|