create-agentic-pdlc 1.1.1 → 2.0.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/.github/assets/agentic-pdlc-flow.svg +255 -0
- package/.github/workflows/agent-trigger.yml +40 -1
- package/.github/workflows/ci.yml +3 -0
- package/.github/workflows/pdlc-health-check.yml +123 -0
- package/.github/workflows/project-automation.yml +36 -55
- package/AGENTS.md +3 -1
- package/README.md +12 -8
- package/SETUP.md +41 -2
- package/adapters/claude-code/skill.md +24 -3
- package/docs/flow.md +160 -0
- package/docs/pdlc.md +17 -13
- package/docs/spikes/04-upstream-flow-gap-analysis.md +39 -0
- package/package.json +1 -1
- package/templates/.github/CODEOWNERS +5 -0
- package/templates/.github/workflows/agent-trigger.yml +40 -1
- package/templates/.github/workflows/ci.yml +3 -0
- package/templates/.github/workflows/pdlc-health-check.yml +123 -0
- package/templates/.github/workflows/project-automation.yml +36 -55
- package/templates/.github/workflows/upstream-gate.yml +54 -0
- package/templates/AGENTS.md +28 -1
- package/templates/docs/pdlc.md +17 -13
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 450" width="100%" height="100%">
|
|
2
|
+
<defs>
|
|
3
|
+
<style>
|
|
4
|
+
:root {
|
|
5
|
+
--bg-color: transparent;
|
|
6
|
+
--card-bg: #ffffff;
|
|
7
|
+
--card-border: #e2e8f0;
|
|
8
|
+
--text-main: #0f172a;
|
|
9
|
+
--text-muted: #64748b;
|
|
10
|
+
--accent-blue: #3b82f6;
|
|
11
|
+
--accent-purple: #8b5cf6;
|
|
12
|
+
--accent-green: #10b981;
|
|
13
|
+
--glow-blue: rgba(59, 130, 246, 0.15);
|
|
14
|
+
--glow-purple: rgba(139, 92, 246, 0.15);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@media (prefers-color-scheme: dark) {
|
|
18
|
+
:root {
|
|
19
|
+
--card-bg: #0d1117;
|
|
20
|
+
--card-border: #30363d;
|
|
21
|
+
--text-main: #e6edf3;
|
|
22
|
+
--text-muted: #848d97;
|
|
23
|
+
--accent-blue: #58a6ff;
|
|
24
|
+
--accent-purple: #bc8cff;
|
|
25
|
+
--accent-green: #238636;
|
|
26
|
+
--glow-blue: rgba(88, 166, 255, 0.2);
|
|
27
|
+
--glow-purple: rgba(188, 140, 255, 0.2);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
body { background-color: var(--bg-color); }
|
|
32
|
+
|
|
33
|
+
.card { fill: var(--card-bg); stroke: var(--card-border); stroke-width: 2; rx: 16; }
|
|
34
|
+
.card-glow-left { fill: var(--glow-blue); filter: blur(40px); rx: 16; }
|
|
35
|
+
.card-glow-right { fill: var(--glow-purple); filter: blur(40px); rx: 16; }
|
|
36
|
+
|
|
37
|
+
.text-title { fill: var(--text-main); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 26px; font-weight: 800; letter-spacing: -0.5px; }
|
|
38
|
+
.text-subtitle { fill: var(--text-muted); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 16px; font-weight: 500; }
|
|
39
|
+
.text-label { fill: var(--text-main); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px; font-weight: 600; }
|
|
40
|
+
|
|
41
|
+
.icon-container { fill: var(--card-bg); stroke: var(--card-border); stroke-width: 2; rx: 12; }
|
|
42
|
+
|
|
43
|
+
.path-line { stroke: url(#gradientLine); stroke-width: 4; stroke-linecap: round; stroke-linejoin: round; fill: none; stroke-dasharray: 8 8; animation: dashMove 15s linear infinite; }
|
|
44
|
+
.path-glow { stroke: url(#gradientLine); stroke-width: 12; stroke-linecap: round; stroke-linejoin: round; fill: none; opacity: 0.3; filter: blur(8px); }
|
|
45
|
+
.path-interaction { stroke: var(--text-muted); stroke-width: 2; stroke-linecap: round; fill: none; stroke-dasharray: 4 4; opacity: 0.5; animation: dashMove 20s linear infinite reverse; }
|
|
46
|
+
|
|
47
|
+
.badge-bg { fill: var(--card-bg); stroke: var(--accent-blue); stroke-width: 2; rx: 17; }
|
|
48
|
+
.badge-text { fill: var(--accent-blue); font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 15px; font-weight: 700; }
|
|
49
|
+
|
|
50
|
+
.pr-badge-bg { fill: var(--accent-green); rx: 8; }
|
|
51
|
+
.pr-badge-text { fill: #ffffff; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 15px; font-weight: 700; }
|
|
52
|
+
|
|
53
|
+
.pulse { animation: pulse 2s infinite; }
|
|
54
|
+
.pulse-slow { animation: pulse 4s infinite; }
|
|
55
|
+
|
|
56
|
+
@keyframes dashMove {
|
|
57
|
+
from { stroke-dashoffset: 1000; }
|
|
58
|
+
to { stroke-dashoffset: 0; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@keyframes pulse {
|
|
62
|
+
0% { filter: drop-shadow(0 0 4px var(--accent-green)); }
|
|
63
|
+
50% { filter: drop-shadow(0 0 12px var(--accent-green)); }
|
|
64
|
+
100% { filter: drop-shadow(0 0 4px var(--accent-green)); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Gear Animation */
|
|
68
|
+
.gear { transform-origin: 0px 0px; animation: spin 8s linear infinite; }
|
|
69
|
+
.gear-reverse { transform-origin: 0px 0px; animation: spin-reverse 8s linear infinite; }
|
|
70
|
+
@keyframes spin { 100% { transform: rotate(360deg); } }
|
|
71
|
+
@keyframes spin-reverse { 100% { transform: rotate(-360deg); } }
|
|
72
|
+
|
|
73
|
+
/* Blinking Cursor and Typing */
|
|
74
|
+
@keyframes blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
|
|
75
|
+
.blinking-cursor { animation: blink 1s infinite; }
|
|
76
|
+
|
|
77
|
+
@keyframes typeEffect {
|
|
78
|
+
0%, 10% { opacity: 0; transform: translateY(5px); }
|
|
79
|
+
20%, 80% { opacity: 1; transform: translateY(0); }
|
|
80
|
+
90%, 100% { opacity: 0; transform: translateY(-5px); }
|
|
81
|
+
}
|
|
82
|
+
.code-line-1 { animation: typeEffect 6s infinite; }
|
|
83
|
+
.code-line-2 { animation: typeEffect 6s infinite 0.5s; }
|
|
84
|
+
.code-line-3 { animation: typeEffect 6s infinite 1s; }
|
|
85
|
+
|
|
86
|
+
/* Conceiving Spec:Approved Badge Animation */
|
|
87
|
+
@keyframes conceive {
|
|
88
|
+
0% { transform: translate(-300px, 0px) scale(0.5); opacity: 0; }
|
|
89
|
+
15% { transform: translate(-100px, 10px) scale(0.8); opacity: 1; }
|
|
90
|
+
30%, 70% { transform: translate(0px, 0px) scale(1); opacity: 1; filter: drop-shadow(0 0 15px var(--accent-blue)); }
|
|
91
|
+
85% { transform: translate(150px, -10px) scale(0.8); opacity: 1; }
|
|
92
|
+
100% { transform: translate(300px, -20px) scale(0.5); opacity: 0; }
|
|
93
|
+
}
|
|
94
|
+
.conceive-badge { animation: conceive 8s ease-in-out infinite; }
|
|
95
|
+
|
|
96
|
+
/* Orb traversing from human to AIs */
|
|
97
|
+
@keyframes dataFlow {
|
|
98
|
+
0% { transform: translate(0,0); opacity: 0; }
|
|
99
|
+
10% { opacity: 1; }
|
|
100
|
+
90% { opacity: 1; }
|
|
101
|
+
100% { transform: translate(120px, 0); opacity: 0; }
|
|
102
|
+
}
|
|
103
|
+
</style>
|
|
104
|
+
|
|
105
|
+
<linearGradient id="gradientLine" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
106
|
+
<stop offset="0%" stop-color="var(--accent-blue)" />
|
|
107
|
+
<stop offset="100%" stop-color="var(--accent-purple)" />
|
|
108
|
+
</linearGradient>
|
|
109
|
+
|
|
110
|
+
<!-- 3D Human Gradients -->
|
|
111
|
+
<radialGradient id="headGrad" cx="35%" cy="35%" r="65%">
|
|
112
|
+
<stop offset="0%" stop-color="#e2e8f0" />
|
|
113
|
+
<stop offset="100%" stop-color="#475569" />
|
|
114
|
+
</radialGradient>
|
|
115
|
+
<linearGradient id="bodyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
116
|
+
<stop offset="0%" stop-color="#cbd5e1" />
|
|
117
|
+
<stop offset="100%" stop-color="#334155" />
|
|
118
|
+
</linearGradient>
|
|
119
|
+
</defs>
|
|
120
|
+
|
|
121
|
+
<!-- Left Block: Upstream -->
|
|
122
|
+
<g transform="translate(50, 70)">
|
|
123
|
+
<rect class="card-glow-left" x="0" y="0" width="360" height="320" />
|
|
124
|
+
<rect class="card" x="0" y="0" width="360" height="320" />
|
|
125
|
+
|
|
126
|
+
<text class="text-title" x="180" y="50" text-anchor="middle">🌊 Upstream</text>
|
|
127
|
+
<text class="text-subtitle" x="180" y="75" text-anchor="middle">Idea → Specification</text>
|
|
128
|
+
|
|
129
|
+
<!-- Human Icon (3D Style) -->
|
|
130
|
+
<g transform="translate(60, 110)">
|
|
131
|
+
<rect class="icon-container" x="0" y="0" width="80" height="80" />
|
|
132
|
+
|
|
133
|
+
<!-- 3D Human Silhouette -->
|
|
134
|
+
<circle cx="40" cy="28" r="14" fill="url(#headGrad)" />
|
|
135
|
+
<path d="M40 48 C18 48, 12 70, 12 80 L68 80 C68 70, 62 48, 40 48 Z" fill="url(#bodyGrad)" />
|
|
136
|
+
|
|
137
|
+
<text class="text-label" x="40" y="110" text-anchor="middle">Human / PM</text>
|
|
138
|
+
</g>
|
|
139
|
+
|
|
140
|
+
<!-- Interaction Animations -->
|
|
141
|
+
<!-- Human to AGENTS.md -->
|
|
142
|
+
<path class="path-interaction" d="M 100 190 Q 140 220 180 240" />
|
|
143
|
+
<!-- AIs to AGENTS.md -->
|
|
144
|
+
<path class="path-interaction" d="M 260 190 Q 220 220 180 240" />
|
|
145
|
+
<!-- Human to AIs (Collaboration) -->
|
|
146
|
+
<path class="path-interaction" d="M 140 150 L 220 150" />
|
|
147
|
+
|
|
148
|
+
<!-- Floating Data Orb between Human and AIs -->
|
|
149
|
+
<circle cx="140" cy="150" r="4" fill="var(--accent-blue)" filter="blur(1px)">
|
|
150
|
+
<animate attributeName="cx" values="140; 220; 140" dur="4s" repeatCount="indefinite" />
|
|
151
|
+
</circle>
|
|
152
|
+
|
|
153
|
+
<!-- AI Assistants Group -->
|
|
154
|
+
<g transform="translate(220, 110)">
|
|
155
|
+
<rect class="icon-container" x="0" y="0" width="80" height="80" />
|
|
156
|
+
|
|
157
|
+
<!-- Claude-like Asterisk/Brain -->
|
|
158
|
+
<g transform="translate(20, 20) scale(0.6)">
|
|
159
|
+
<path d="M20 5 L20 35 M5 20 L35 20 M10 10 L30 30 M10 30 L30 10" style="stroke: #d97757; stroke-width: 6; stroke-linecap: round;" />
|
|
160
|
+
</g>
|
|
161
|
+
|
|
162
|
+
<!-- Cursor-like Pointer -->
|
|
163
|
+
<g transform="translate(45, 20) scale(0.7)">
|
|
164
|
+
<path d="M5 5 L15 35 L20 20 L35 15 Z" style="fill: var(--accent-blue); stroke: var(--accent-blue); stroke-width: 2; stroke-linejoin: round;" />
|
|
165
|
+
</g>
|
|
166
|
+
|
|
167
|
+
<!-- Copilot-like Hexagon/Wings -->
|
|
168
|
+
<g transform="translate(32, 45) scale(0.6)">
|
|
169
|
+
<path d="M10 25 C 10 10, 30 10, 30 25 C 30 35, 20 40, 20 40 C 20 40, 10 35, 10 25 Z M 5 20 L 10 25 M 35 20 L 30 25" style="fill: none; stroke: var(--text-main); stroke-width: 4; stroke-linecap: round; stroke-linejoin: round;" />
|
|
170
|
+
</g>
|
|
171
|
+
|
|
172
|
+
<text class="text-label" x="40" y="110" text-anchor="middle">Agnostic AIs</text>
|
|
173
|
+
</g>
|
|
174
|
+
|
|
175
|
+
<!-- The Resulting Spec -->
|
|
176
|
+
<g transform="translate(100, 240)">
|
|
177
|
+
<rect x="0" y="0" width="160" height="46" rx="8" style="fill: var(--card-bg); stroke: var(--card-border); stroke-width: 2;" />
|
|
178
|
+
<path d="M25 15h15M25 23h25M25 31h20M15 15v16" style="stroke: var(--accent-blue); stroke-width: 2; stroke-linecap: round;" />
|
|
179
|
+
<text class="text-label" x="50" y="28">AGENTS.md</text>
|
|
180
|
+
</g>
|
|
181
|
+
</g>
|
|
182
|
+
|
|
183
|
+
<!-- The Bridge / Connection -->
|
|
184
|
+
<g transform="translate(410, 230)">
|
|
185
|
+
<!-- Animated Path connecting boxes -->
|
|
186
|
+
<path class="path-glow" d="M 0 0 C 60 0, 120 0, 180 0" />
|
|
187
|
+
<path class="path-line" d="M 0 0 C 60 0, 120 0, 180 0" />
|
|
188
|
+
|
|
189
|
+
<!-- Floating Spec:Approved Badge that travels from Human to Agent -->
|
|
190
|
+
<g class="conceive-badge" transform="translate(0, -35)">
|
|
191
|
+
<rect class="badge-bg" x="0" y="0" width="160" height="34" />
|
|
192
|
+
<!-- Removed the bullet, centered text perfectly -->
|
|
193
|
+
<text class="badge-text" x="80" y="22" text-anchor="middle">spec:approved</text>
|
|
194
|
+
</g>
|
|
195
|
+
</g>
|
|
196
|
+
|
|
197
|
+
<!-- Right Block: Downstream -->
|
|
198
|
+
<g transform="translate(600, 70)">
|
|
199
|
+
<rect class="card-glow-right" x="0" y="0" width="360" height="320" />
|
|
200
|
+
<rect class="card" x="0" y="0" width="360" height="320" />
|
|
201
|
+
|
|
202
|
+
<text class="text-title" x="180" y="50" text-anchor="middle">🏭 Downstream</text>
|
|
203
|
+
<text class="text-subtitle" x="180" y="75" text-anchor="middle">Spec → Production Code</text>
|
|
204
|
+
|
|
205
|
+
<!-- Central Auto Agent Factory Graphic -->
|
|
206
|
+
<g transform="translate(80, 110)">
|
|
207
|
+
<!-- Server / Terminal Window representing the AI Workspace -->
|
|
208
|
+
<rect x="0" y="0" width="200" height="110" rx="6" style="fill: var(--card-bg); stroke: var(--card-border); stroke-width: 3;" />
|
|
209
|
+
<!-- Terminal Header -->
|
|
210
|
+
<path d="M0 25 L200 25" style="stroke: var(--card-border); stroke-width: 3;" />
|
|
211
|
+
<circle cx="15" cy="12.5" r="4" style="fill: #ff5f56;" />
|
|
212
|
+
<circle cx="30" cy="12.5" r="4" style="fill: #ffbd2e;" />
|
|
213
|
+
<circle cx="45" cy="12.5" r="4" style="fill: #27c93f;" />
|
|
214
|
+
|
|
215
|
+
<!-- Terminal Code Content with Animation -->
|
|
216
|
+
<path d="M20 45 L35 60 L20 75" style="stroke: var(--accent-purple); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; fill: none;" />
|
|
217
|
+
|
|
218
|
+
<!-- Blinking Cursor -->
|
|
219
|
+
<line x1="45" y1="75" x2="55" y2="75" class="blinking-cursor" style="stroke: var(--text-main); stroke-width: 3; stroke-linecap: round;" />
|
|
220
|
+
|
|
221
|
+
<!-- Animated Code Lines Typing -->
|
|
222
|
+
<rect class="code-line-1" x="20" y="90" width="60" height="6" rx="3" style="fill: var(--text-muted); opacity: 0.5;" />
|
|
223
|
+
<rect class="code-line-2" x="85" y="90" width="40" height="6" rx="3" style="fill: var(--accent-blue); opacity: 0.5;" />
|
|
224
|
+
<rect class="code-line-3" x="20" y="105" width="90" height="6" rx="3" style="fill: var(--text-muted); opacity: 0.5;" />
|
|
225
|
+
|
|
226
|
+
<!-- Gears processing the code -->
|
|
227
|
+
<g transform="translate(150, 65)">
|
|
228
|
+
<g class="gear">
|
|
229
|
+
<circle cx="0" cy="0" r="16" style="fill: none; stroke: var(--text-muted); stroke-width: 4; stroke-dasharray: 6 4;" />
|
|
230
|
+
<circle cx="0" cy="0" r="6" style="fill: var(--text-muted);" />
|
|
231
|
+
</g>
|
|
232
|
+
<g class="gear-reverse" transform="translate(-18, 22) scale(0.7)">
|
|
233
|
+
<circle cx="0" cy="0" r="16" style="fill: none; stroke: var(--accent-purple); stroke-width: 4; stroke-dasharray: 6 4;" />
|
|
234
|
+
<circle cx="0" cy="0" r="6" style="fill: var(--accent-purple);" />
|
|
235
|
+
</g>
|
|
236
|
+
</g>
|
|
237
|
+
</g>
|
|
238
|
+
|
|
239
|
+
<!-- Result: Pull Request Badge -->
|
|
240
|
+
<g transform="translate(100, 240)" class="pulse">
|
|
241
|
+
<rect class="pr-badge-bg" x="0" y="0" width="160" height="46" />
|
|
242
|
+
|
|
243
|
+
<!-- PR Icon (GitHub style) -->
|
|
244
|
+
<g transform="translate(15, 13) scale(1.2)" style="fill: none; stroke: #ffffff; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;">
|
|
245
|
+
<circle cx="4" cy="4" r="2" />
|
|
246
|
+
<circle cx="4" cy="14" r="2" />
|
|
247
|
+
<circle cx="14" cy="4" r="2" />
|
|
248
|
+
<path d="M4 6 v6" />
|
|
249
|
+
<path d="M14 6 v2 c0 2 -2 2 -2 2 h-4 c0 0 -2 0 -2 2 v2" />
|
|
250
|
+
</g>
|
|
251
|
+
|
|
252
|
+
<text class="pr-badge-text" x="45" y="28">Pull Request</text>
|
|
253
|
+
</g>
|
|
254
|
+
</g>
|
|
255
|
+
</svg>
|
|
@@ -12,8 +12,41 @@ jobs:
|
|
|
12
12
|
# Runs only when spec:approved is added
|
|
13
13
|
if: github.event.label.name == 'spec:approved'
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
issues: write
|
|
17
|
+
pull-requests: write
|
|
18
|
+
contents: read
|
|
19
|
+
env:
|
|
20
|
+
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
15
21
|
steps:
|
|
16
|
-
- name:
|
|
22
|
+
- name: Update Labels
|
|
23
|
+
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
24
|
+
uses: actions/github-script@v7
|
|
25
|
+
with:
|
|
26
|
+
github-token: ${{ env.PROJECT_TOKEN }}
|
|
27
|
+
script: |
|
|
28
|
+
const { owner, repo } = context.repo;
|
|
29
|
+
const issue_number = context.payload.issue.number;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await github.rest.issues.removeLabel({
|
|
33
|
+
owner,
|
|
34
|
+
repo,
|
|
35
|
+
issue_number,
|
|
36
|
+
name: 'upstream:approval'
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.log('Label upstream:approval not found or could not be removed');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await github.rest.issues.addLabels({
|
|
43
|
+
owner,
|
|
44
|
+
repo,
|
|
45
|
+
issue_number,
|
|
46
|
+
labels: ['upstream:development', '{{IMPLEMENTATION_AGENT_LABEL}}', 'agent:working']
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
- name: Comment on issue to trigger agent and prevent race conditions
|
|
17
50
|
uses: actions/github-script@v7
|
|
18
51
|
with:
|
|
19
52
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -22,6 +55,8 @@ jobs:
|
|
|
22
55
|
const issueTitle = context.payload.issue.title;
|
|
23
56
|
|
|
24
57
|
const body = [
|
|
58
|
+
`🤖 **Agentic PDLC Orchestrator:** I have dispatched the implementation agent. Please wait for the Pull Request and avoid making concurrent commits on this task to prevent race conditions.`,
|
|
59
|
+
'',
|
|
25
60
|
`{{AGENT_HANDLE}} The spec for this issue has been approved. Please implement it exactly as described in the body above.`,
|
|
26
61
|
'',
|
|
27
62
|
'**Mandatory steps before you begin:**',
|
|
@@ -48,6 +83,10 @@ jobs:
|
|
|
48
83
|
# Runs when architecture-violation is added (Sentinel flow)
|
|
49
84
|
if: github.event.label.name == 'architecture-violation'
|
|
50
85
|
runs-on: ubuntu-latest
|
|
86
|
+
permissions:
|
|
87
|
+
issues: write
|
|
88
|
+
pull-requests: write
|
|
89
|
+
contents: read
|
|
51
90
|
steps:
|
|
52
91
|
- name: Comment on issue to trigger agent
|
|
53
92
|
uses: actions/github-script@v7
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
name: PDLC Health Check (Drift Detection)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: '0 8 * * 1' # Every Monday at 8am
|
|
7
|
+
|
|
8
|
+
env:
|
|
9
|
+
PROJECT_ID: "{{PROJECT_ID}}"
|
|
10
|
+
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
11
|
+
STATUS_EXPLORATION: "{{ID_EXPLORATION}}"
|
|
12
|
+
STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
|
|
13
|
+
STATUS_DETAILING: "{{ID_DETAILING}}"
|
|
14
|
+
STATUS_APPROVAL: "{{ID_APPROVAL}}"
|
|
15
|
+
STATUS_DEVELOPMENT: "{{ID_DEVELOPMENT}}"
|
|
16
|
+
STATUS_TESTING: "{{ID_TESTING}}"
|
|
17
|
+
STATUS_CODE_REVIEW_PR: "{{ID_CODE_REVIEW_PR}}"
|
|
18
|
+
STATUS_PRODUCTION: "{{ID_PRODUCTION}}"
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
check-drift:
|
|
22
|
+
name: Detect Project Board Drift
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
env:
|
|
25
|
+
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
26
|
+
permissions:
|
|
27
|
+
issues: write
|
|
28
|
+
steps:
|
|
29
|
+
- name: Validate Board Configuration
|
|
30
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
31
|
+
uses: actions/github-script@v7
|
|
32
|
+
with:
|
|
33
|
+
github-token: ${{ env.PROJECT_TOKEN }}
|
|
34
|
+
script: |
|
|
35
|
+
const projectId = process.env.PROJECT_ID;
|
|
36
|
+
const statusFieldId = process.env.STATUS_FIELD_ID;
|
|
37
|
+
const envVars = {
|
|
38
|
+
'STATUS_EXPLORATION': process.env.STATUS_EXPLORATION,
|
|
39
|
+
'STATUS_BRAINSTORMING': process.env.STATUS_BRAINSTORMING,
|
|
40
|
+
'STATUS_DETAILING': process.env.STATUS_DETAILING,
|
|
41
|
+
'STATUS_APPROVAL': process.env.STATUS_APPROVAL,
|
|
42
|
+
'STATUS_DEVELOPMENT': process.env.STATUS_DEVELOPMENT,
|
|
43
|
+
'STATUS_TESTING': process.env.STATUS_TESTING,
|
|
44
|
+
'STATUS_CODE_REVIEW_PR': process.env.STATUS_CODE_REVIEW_PR,
|
|
45
|
+
'STATUS_PRODUCTION': process.env.STATUS_PRODUCTION
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const query = `
|
|
49
|
+
query($projectId: ID!) {
|
|
50
|
+
node(id: $projectId) {
|
|
51
|
+
... on ProjectV2 {
|
|
52
|
+
title
|
|
53
|
+
fields(first: 20) {
|
|
54
|
+
nodes {
|
|
55
|
+
... on ProjectV2SingleSelectField {
|
|
56
|
+
id
|
|
57
|
+
name
|
|
58
|
+
options {
|
|
59
|
+
id
|
|
60
|
+
name
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
let result;
|
|
71
|
+
try {
|
|
72
|
+
result = await github.graphql(query, { projectId });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.log("❌ Error fetching project. Verify your PROJECT_ID.");
|
|
75
|
+
console.log(error);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const project = result.node;
|
|
80
|
+
if (!project) {
|
|
81
|
+
console.log("❌ Project not found.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const statusField = project.fields.nodes.find(f => f.id === statusFieldId);
|
|
86
|
+
if (!statusField) {
|
|
87
|
+
console.log("❌ Status field not found.");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const validOptions = statusField.options;
|
|
92
|
+
const validOptionIds = validOptions.map(o => o.id);
|
|
93
|
+
|
|
94
|
+
let hasDrift = false;
|
|
95
|
+
let missingVars = [];
|
|
96
|
+
|
|
97
|
+
for (const [varName, id] of Object.entries(envVars)) {
|
|
98
|
+
if (id && !id.startsWith('{{') && !validOptionIds.includes(id)) {
|
|
99
|
+
hasDrift = true;
|
|
100
|
+
missingVars.push(varName);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (hasDrift) {
|
|
105
|
+
console.log("🚨 Drift detected! The following mapped columns no longer exist: " + missingVars.join(", "));
|
|
106
|
+
|
|
107
|
+
let table = "| Column Name | New ID |\n|---|---|\n";
|
|
108
|
+
validOptions.forEach(opt => {
|
|
109
|
+
table += `| ${opt.name} | \`${opt.id}\` |\n`;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const body = `🚨 **Agentic PDLC Drift Detected**\n\nThe following columns mapped in your \`.github/workflows/project-automation.yml\` no longer exist in your project board:\n\n**${missingVars.join(", ")}**\n\n### How to fix it:\nHere is the list of current columns in your board with their valid IDs. Please update the \`env\` block in your \`.github/workflows/project-automation.yml\` and \`.github/workflows/pdlc-health-check.yml\`.\n\n${table}`;
|
|
113
|
+
|
|
114
|
+
await github.rest.issues.create({
|
|
115
|
+
owner: context.repo.owner,
|
|
116
|
+
repo: context.repo.repo,
|
|
117
|
+
title: '🚨 Agentic PDLC Drift Detected in Project Board',
|
|
118
|
+
body: body,
|
|
119
|
+
labels: ['bug']
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
console.log("✅ No drift detected. Board configuration is healthy.");
|
|
123
|
+
}
|
|
@@ -5,8 +5,8 @@ on:
|
|
|
5
5
|
types: [opened, reopened, closed]
|
|
6
6
|
pull_request_review:
|
|
7
7
|
types: [submitted]
|
|
8
|
-
|
|
9
|
-
types: [
|
|
8
|
+
issues:
|
|
9
|
+
types: [labeled]
|
|
10
10
|
|
|
11
11
|
env:
|
|
12
12
|
PROJECT_ID: "{{PROJECT_ID}}"
|
|
@@ -15,45 +15,52 @@ env:
|
|
|
15
15
|
STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
|
|
16
16
|
STATUS_DETAILING: "{{ID_DETAILING}}"
|
|
17
17
|
STATUS_APPROVAL: "{{ID_APPROVAL}}"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
STATUS_DEVELOPMENT: "{{ID_DEVELOPMENT}}"
|
|
19
|
+
STATUS_TESTING: "{{ID_TESTING}}"
|
|
20
|
+
STATUS_CODE_REVIEW_PR: "{{ID_CODE_REVIEW_PR}}"
|
|
21
|
+
STATUS_PRODUCTION: "{{ID_PRODUCTION}}"
|
|
21
22
|
|
|
22
23
|
jobs:
|
|
23
|
-
# Issue
|
|
24
|
-
move-card-on-
|
|
25
|
-
name: Upstream
|
|
26
|
-
if: github.event_name == '
|
|
24
|
+
# Issue Labeled → Move Upstream
|
|
25
|
+
move-card-on-label:
|
|
26
|
+
name: Upstream Label → Move Card
|
|
27
|
+
if: github.event_name == 'issues' && github.event.action == 'labeled'
|
|
27
28
|
runs-on: ubuntu-latest
|
|
28
29
|
env:
|
|
29
30
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
30
31
|
steps:
|
|
31
|
-
- name: Detect
|
|
32
|
+
- name: Detect Label and Move Issue
|
|
32
33
|
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
33
34
|
uses: actions/github-script@v7
|
|
34
35
|
with:
|
|
35
36
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
36
37
|
script: |
|
|
37
|
-
const
|
|
38
|
+
const labelName = context.payload.label.name;
|
|
38
39
|
let targetStatusId = null;
|
|
39
40
|
let stageName = null;
|
|
40
41
|
|
|
41
|
-
if (
|
|
42
|
+
if (labelName === 'upstream:exploration') {
|
|
42
43
|
targetStatusId = process.env.STATUS_EXPLORATION;
|
|
43
44
|
stageName = 'Exploration';
|
|
44
|
-
} else if (
|
|
45
|
+
} else if (labelName === 'upstream:brainstorming') {
|
|
45
46
|
targetStatusId = process.env.STATUS_BRAINSTORMING;
|
|
46
47
|
stageName = 'Brainstorming';
|
|
47
|
-
} else if (
|
|
48
|
+
} else if (labelName === 'upstream:detailing') {
|
|
48
49
|
targetStatusId = process.env.STATUS_DETAILING;
|
|
49
50
|
stageName = 'Detailing';
|
|
50
|
-
} else if (
|
|
51
|
+
} else if (labelName === 'upstream:approval') {
|
|
51
52
|
targetStatusId = process.env.STATUS_APPROVAL;
|
|
52
53
|
stageName = 'Approval';
|
|
54
|
+
} else if (labelName === 'upstream:development') {
|
|
55
|
+
targetStatusId = process.env.STATUS_DEVELOPMENT;
|
|
56
|
+
stageName = 'Development';
|
|
57
|
+
} else if (labelName === 'upstream:testing') {
|
|
58
|
+
targetStatusId = process.env.STATUS_TESTING;
|
|
59
|
+
stageName = 'Testing';
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
if (!targetStatusId) {
|
|
56
|
-
console.log('No upstream PDLC
|
|
63
|
+
console.log('No upstream PDLC label found. Skipping.');
|
|
57
64
|
return;
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -79,16 +86,17 @@ jobs:
|
|
|
79
86
|
await moveItem(node_id, targetStatusId);
|
|
80
87
|
console.log(`Issue #${number} moved to ${stageName}`);
|
|
81
88
|
|
|
89
|
+
{{OPTIONAL_ARCHITECTURE_VIOLATION_JOB}}
|
|
82
90
|
|
|
83
|
-
# PR Opened → Move linked issue to Code Review
|
|
91
|
+
# PR Opened → Move linked issue to Code Review / PR
|
|
84
92
|
move-card-on-pr-open:
|
|
85
|
-
name: Open PR → Code Review
|
|
93
|
+
name: Open PR → Code Review / PR
|
|
86
94
|
if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
|
87
95
|
runs-on: ubuntu-latest
|
|
88
96
|
env:
|
|
89
97
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
90
98
|
steps:
|
|
91
|
-
- name: Move linked issue to Code Review
|
|
99
|
+
- name: Move linked issue to Code Review / PR
|
|
92
100
|
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
93
101
|
uses: actions/github-script@v7
|
|
94
102
|
with:
|
|
@@ -117,7 +125,7 @@ jobs:
|
|
|
117
125
|
}
|
|
118
126
|
}`, {
|
|
119
127
|
p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
120
|
-
v: { singleSelectOptionId: process.env.
|
|
128
|
+
v: { singleSelectOptionId: process.env.STATUS_CODE_REVIEW_PR }
|
|
121
129
|
});
|
|
122
130
|
};
|
|
123
131
|
|
|
@@ -125,24 +133,24 @@ jobs:
|
|
|
125
133
|
for (const n of linkedIssues) {
|
|
126
134
|
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
127
135
|
await moveItem(issue.node_id);
|
|
128
|
-
console.log(`Issue #${n} → Code Review`);
|
|
136
|
+
console.log(`Issue #${n} → Code Review / PR`);
|
|
129
137
|
}
|
|
130
138
|
} else {
|
|
131
139
|
await moveItem(pr.node_id);
|
|
132
|
-
console.log(`PR #${prNumber} → Code Review (no linked issue)`);
|
|
140
|
+
console.log(`PR #${prNumber} → Code Review / PR (no linked issue)`);
|
|
133
141
|
}
|
|
134
142
|
|
|
135
|
-
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:review'] }).catch(() => {});
|
|
143
|
+
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:in-review'] }).catch(() => {});
|
|
136
144
|
|
|
137
|
-
# Review Approved →
|
|
145
|
+
# Review Approved → Add Label
|
|
138
146
|
move-card-on-review-approved:
|
|
139
|
-
name: Approved PR →
|
|
147
|
+
name: Approved PR → Add Label
|
|
140
148
|
if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved'
|
|
141
149
|
runs-on: ubuntu-latest
|
|
142
150
|
env:
|
|
143
151
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
144
152
|
steps:
|
|
145
|
-
- name:
|
|
153
|
+
- name: Swap PR labels
|
|
146
154
|
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
147
155
|
uses: actions/github-script@v7
|
|
148
156
|
with:
|
|
@@ -150,34 +158,7 @@ jobs:
|
|
|
150
158
|
script: |
|
|
151
159
|
const prNumber = context.payload.pull_request.number;
|
|
152
160
|
const { owner, repo } = context.repo;
|
|
153
|
-
|
|
154
|
-
const body = pr.body ?? '';
|
|
155
|
-
const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)].map(m => parseInt(m[1]));
|
|
156
|
-
|
|
157
|
-
const moveItem = async (nodeId) => {
|
|
158
|
-
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
159
|
-
mutation($p: ID!, $c: ID!) {
|
|
160
|
-
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
161
|
-
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
162
|
-
await github.graphql(`
|
|
163
|
-
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
164
|
-
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
165
|
-
projectV2Item { id }
|
|
166
|
-
}
|
|
167
|
-
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
168
|
-
v: { singleSelectOptionId: process.env.STATUS_PULL_REQUEST } });
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
if (linkedIssues.length > 0) {
|
|
172
|
-
for (const n of linkedIssues) {
|
|
173
|
-
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
174
|
-
await moveItem(issue.node_id);
|
|
175
|
-
}
|
|
176
|
-
} else {
|
|
177
|
-
await moveItem(pr.node_id);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
try { await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'pr:review' }); } catch {}
|
|
161
|
+
try { await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'pr:in-review' }); } catch {}
|
|
181
162
|
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:approved'] }).catch(() => {});
|
|
182
163
|
|
|
183
164
|
# PR Merged → Production
|
|
@@ -211,7 +192,7 @@ jobs:
|
|
|
211
192
|
projectV2Item { id }
|
|
212
193
|
}
|
|
213
194
|
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
214
|
-
v: { singleSelectOptionId: process.env.
|
|
195
|
+
v: { singleSelectOptionId: process.env.STATUS_PRODUCTION } });
|
|
215
196
|
};
|
|
216
197
|
|
|
217
198
|
if (linkedIssues.length > 0) {
|
package/AGENTS.md
CHANGED
|
@@ -32,7 +32,8 @@ Always start from the current `main` HEAD. Never work over stale snapshots.
|
|
|
32
32
|
3. Read all files mentioned in the issue's technical context.
|
|
33
33
|
4. Implement the **minimum viable change** that satisfies the ACs — do not refactor beyond scope.
|
|
34
34
|
5. Run tests: `echo "No tests/build needed."`
|
|
35
|
-
6.
|
|
35
|
+
6. Run typecheck: `echo "No typecheck needed."`
|
|
36
|
+
7. Create a Pull Request with `Closes #N` in the body — automation moves the board.
|
|
36
37
|
|
|
37
38
|
## What NOT to do
|
|
38
39
|
|
|
@@ -46,4 +47,5 @@ Always start from the current `main` HEAD. Never work over stale snapshots.
|
|
|
46
47
|
|
|
47
48
|
- **Tests:** `echo "No tests/build needed."`
|
|
48
49
|
- **Lint/Types:** `echo "No tests/build needed."`
|
|
50
|
+
- **Typecheck:** `echo "No typecheck needed."`
|
|
49
51
|
- **Build:** `echo "No tests/build needed."`
|