create-agentic-pdlc 2.2.1 β 2.4.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/.agentic-pdlc/SETUP_PROMPT.md +3 -4
- package/.agentic-pdlc/metrics/raw/2026-W18.jsonl +2 -0
- package/.agentic-pdlc/metrics/raw/2026-W21.jsonl +68 -0
- package/.agentic-pdlc/metrics/raw/2026-W22.jsonl +114 -0
- package/.agentic-setup-prompt.md +3 -4
- package/.agentic-setup.md +3 -4
- package/.github/ISSUE_TEMPLATE/bug.md +53 -0
- package/.github/ISSUE_TEMPLATE/feature.md +54 -0
- package/.github/ISSUE_TEMPLATE/task.md +33 -0
- package/.github/workflows/add-to-board.yml +1 -1
- package/.github/workflows/agent-trigger.yml +4 -4
- package/.github/workflows/agentic-metrics.yml +171 -38
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/npm-publish.yml +2 -2
- package/.github/workflows/pdlc-health-check.yml +1 -3
- package/.github/workflows/pdlc-stage-gate.yml +2 -2
- package/.github/workflows/project-automation.yml +79 -16
- package/.github/workflows/qa-agent.yml +26 -15
- package/.github/workflows/qa-gate.yml +51 -0
- package/AGENTS.md +50 -8
- package/CLAUDE.md +53 -3
- package/SETUP.md +4 -1
- package/adapters/claude-code/skill.md +44 -20
- package/adapters/hooks/pdlc-stage-gate.sh +2 -7
- package/bin/cli.js +41 -9
- package/docs/flow.md +8 -21
- package/docs/pdlc.md +24 -16
- package/docs/superpowers/plans/2026-05-28-jules-label-pat-split.md +240 -0
- package/docs/superpowers/plans/2026-05-29-agentic-pulse-rework-taxonomy.md +474 -0
- package/docs/superpowers/plans/2026-05-29-qa-gate-enforcement.md +354 -0
- package/docs/superpowers/specs/2026-05-29-agentic-pulse-rework-taxonomy-design.md +122 -0
- package/package.json +1 -1
- package/templates/.github/ISSUE_TEMPLATE/bug.md +53 -0
- package/templates/.github/ISSUE_TEMPLATE/feature.md +54 -0
- package/templates/.github/ISSUE_TEMPLATE/task.md +33 -0
- package/templates/.github/workflows/add-to-board.yml +4 -4
- package/templates/.github/workflows/agent-trigger.yml +24 -15
- package/{.agentic-pdlc/templates β templates}/.github/workflows/agentic-metrics.yml +166 -36
- package/templates/.github/workflows/ci.yml +15 -1
- package/templates/.github/workflows/pdlc-health-check.yml +1 -3
- package/templates/.github/workflows/pdlc-stage-gate.yml +2 -2
- package/templates/.github/workflows/project-automation.yml +93 -36
- package/templates/.github/workflows/qa-agent.yml +33 -17
- package/templates/.github/workflows/qa-gate.yml +51 -0
- package/templates/AGENTS.md +74 -23
- package/templates/docs/pdlc.md +24 -16
- package/.agentic-pdlc/templates/.github/CODEOWNERS +0 -5
- package/.agentic-pdlc/templates/.github/copilot-instructions.md +0 -12
- package/.agentic-pdlc/templates/.github/workflows/add-to-board.yml +0 -38
- package/.agentic-pdlc/templates/.github/workflows/agent-trigger.yml +0 -146
- package/.agentic-pdlc/templates/.github/workflows/auto-approve.yml +0 -16
- package/.agentic-pdlc/templates/.github/workflows/ci.yml +0 -40
- package/.agentic-pdlc/templates/.github/workflows/pdlc-health-check.yml +0 -123
- package/.agentic-pdlc/templates/.github/workflows/pdlc-stage-gate.yml +0 -51
- package/.agentic-pdlc/templates/.github/workflows/project-automation.yml +0 -278
- package/.agentic-pdlc/templates/.github/workflows/protect-workflows.yml +0 -21
- package/.agentic-pdlc/templates/.github/workflows/qa-agent.yml +0 -128
- package/.agentic-pdlc/templates/AGENTS.md +0 -81
- package/.agentic-pdlc/templates/docs/pdlc.md +0 -115
|
@@ -17,13 +17,13 @@ jobs:
|
|
|
17
17
|
pull-requests: write
|
|
18
18
|
contents: read
|
|
19
19
|
env:
|
|
20
|
-
|
|
20
|
+
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
21
21
|
PROJECT_ID: "{{PROJECT_ID}}"
|
|
22
22
|
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
23
23
|
STATUS_DEVELOPMENT: "{{ID_DEVELOPMENT}}"
|
|
24
24
|
steps:
|
|
25
|
-
- name: Update
|
|
26
|
-
uses: actions/github-script@
|
|
25
|
+
- name: Update stage labels
|
|
26
|
+
uses: actions/github-script@v8
|
|
27
27
|
with:
|
|
28
28
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
29
29
|
script: |
|
|
@@ -41,23 +41,32 @@ jobs:
|
|
|
41
41
|
console.log('Label stage:approval not found or could not be removed');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const agentLabel = '{{IMPLEMENTATION_AGENT_LABEL}}';
|
|
45
|
-
const labelsToAdd = ['stage:development'];
|
|
46
|
-
if (!agentLabel.includes('{{')) labelsToAdd.push(agentLabel, 'agent:working');
|
|
47
|
-
|
|
48
44
|
await github.rest.issues.addLabels({
|
|
49
45
|
owner,
|
|
50
46
|
repo,
|
|
51
47
|
issue_number,
|
|
52
|
-
labels:
|
|
48
|
+
labels: ['stage:development']
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
- name: Add agent label via PAT
|
|
52
|
+
if: ${{ env.PROJECT_TOKEN != '' && !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
53
|
+
uses: actions/github-script@v8
|
|
54
|
+
with:
|
|
55
|
+
github-token: ${{ env.PROJECT_TOKEN }}
|
|
56
|
+
script: |
|
|
57
|
+
await github.rest.issues.addLabels({
|
|
58
|
+
owner: context.repo.owner,
|
|
59
|
+
repo: context.repo.repo,
|
|
60
|
+
issue_number: context.payload.issue.number,
|
|
61
|
+
labels: ['{{IMPLEMENTATION_AGENT_LABEL}}']
|
|
53
62
|
});
|
|
54
63
|
|
|
55
64
|
- name: Move board card to Development
|
|
56
|
-
if: ${{ env.
|
|
65
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
57
66
|
continue-on-error: true
|
|
58
|
-
uses: actions/github-script@
|
|
67
|
+
uses: actions/github-script@v8
|
|
59
68
|
with:
|
|
60
|
-
github-token: ${{ env.
|
|
69
|
+
github-token: ${{ env.PROJECT_TOKEN }}
|
|
61
70
|
script: |
|
|
62
71
|
const nodeId = context.payload.issue.node_id;
|
|
63
72
|
const number = context.payload.issue.number;
|
|
@@ -75,8 +84,8 @@ jobs:
|
|
|
75
84
|
console.log(`Issue #${number} β Development`);
|
|
76
85
|
|
|
77
86
|
- name: Comment on issue to trigger agent and prevent race conditions
|
|
78
|
-
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
79
|
-
uses: actions/github-script@
|
|
87
|
+
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') && vars.JULES_ENABLED == 'true' }}
|
|
88
|
+
uses: actions/github-script@v8
|
|
80
89
|
with:
|
|
81
90
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
82
91
|
script: |
|
|
@@ -118,8 +127,8 @@ jobs:
|
|
|
118
127
|
contents: read
|
|
119
128
|
steps:
|
|
120
129
|
- name: Comment on issue to trigger agent
|
|
121
|
-
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
122
|
-
uses: actions/github-script@
|
|
130
|
+
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') && vars.JULES_ENABLED == 'true' }}
|
|
131
|
+
uses: actions/github-script@v8
|
|
123
132
|
with:
|
|
124
133
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
125
134
|
script: |
|
|
@@ -9,15 +9,20 @@ permissions:
|
|
|
9
9
|
contents: write
|
|
10
10
|
issues: write
|
|
11
11
|
|
|
12
|
+
env:
|
|
13
|
+
AGENTIC_PULSE_REVIEWERS: |
|
|
14
|
+
code_reviewer=gemini-code-assist[bot]
|
|
15
|
+
qa_agent=github-actions[bot]
|
|
16
|
+
|
|
12
17
|
jobs:
|
|
13
18
|
generate-pulse:
|
|
14
19
|
name: Generate Weekly Agentic Pulse
|
|
15
20
|
runs-on: ubuntu-latest
|
|
16
21
|
steps:
|
|
17
|
-
- uses: actions/checkout@
|
|
22
|
+
- uses: actions/checkout@v5.0.1
|
|
18
23
|
|
|
19
24
|
- name: Collect Stage Residence Time
|
|
20
|
-
uses: actions/github-script@
|
|
25
|
+
uses: actions/github-script@v8
|
|
21
26
|
with:
|
|
22
27
|
script: |
|
|
23
28
|
const fs = require('fs');
|
|
@@ -25,7 +30,7 @@ jobs:
|
|
|
25
30
|
const { owner, repo } = context.repo;
|
|
26
31
|
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
27
32
|
const STAGE_LABELS = new Set([
|
|
28
|
-
'stage:
|
|
33
|
+
'stage:brainstorming', 'stage:detailing',
|
|
29
34
|
'stage:approval', 'stage:development', 'stage:testing'
|
|
30
35
|
]);
|
|
31
36
|
|
|
@@ -104,13 +109,26 @@ jobs:
|
|
|
104
109
|
git push
|
|
105
110
|
|
|
106
111
|
- name: Collect PR and Issue Insights
|
|
107
|
-
uses: actions/github-script@
|
|
112
|
+
uses: actions/github-script@v8
|
|
108
113
|
with:
|
|
109
114
|
script: |
|
|
110
115
|
const fs = require('fs');
|
|
111
116
|
const { owner, repo } = context.repo;
|
|
112
117
|
const weekKey = process.env.WEEK_KEY;
|
|
113
118
|
|
|
119
|
+
// ββ Preload stage:detailing times for stage correlation ββββββββββ
|
|
120
|
+
const detailingByIssue = {};
|
|
121
|
+
const jsonlPath = `.agentic-pdlc/metrics/raw/${weekKey}.jsonl`;
|
|
122
|
+
if (fs.existsSync(jsonlPath)) {
|
|
123
|
+
const rawLines = fs.readFileSync(jsonlPath, 'utf8').trim().split('\n').filter(Boolean);
|
|
124
|
+
for (const line of rawLines) {
|
|
125
|
+
const r = JSON.parse(line);
|
|
126
|
+
if (r.stage === 'stage:detailing') {
|
|
127
|
+
detailingByIssue[r.issueNumber] = round1((detailingByIssue[r.issueNumber] || 0) + r.durationDays);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
114
132
|
// ββ Helper ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
115
133
|
function daysSince(isoStr) {
|
|
116
134
|
return (Date.now() - new Date(isoStr).getTime()) / 864e5;
|
|
@@ -138,6 +156,22 @@ jobs:
|
|
|
138
156
|
// ββ Signal collection βββββββββββββββββββββββββββββββββββββββββββ
|
|
139
157
|
const signals = [];
|
|
140
158
|
|
|
159
|
+
// ββ Review actor map (from AGENTIC_PULSE_REVIEWERS env var) βββββ
|
|
160
|
+
const actorMap = {}; // login β role
|
|
161
|
+
const reviewersEnv = (process.env.AGENTIC_PULSE_REVIEWERS || '').trim();
|
|
162
|
+
if (reviewersEnv) {
|
|
163
|
+
for (const line of reviewersEnv.split('\n')) {
|
|
164
|
+
const eq = line.indexOf('=');
|
|
165
|
+
if (eq < 0) continue;
|
|
166
|
+
const role = line.slice(0, eq).trim();
|
|
167
|
+
const logins = line.slice(eq + 1).trim();
|
|
168
|
+
for (const login of logins.split(',').map(l => l.trim()).filter(Boolean)) {
|
|
169
|
+
actorMap[login] = role;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const taxonomyEnabled = Object.keys(actorMap).length > 0;
|
|
174
|
+
|
|
141
175
|
// 1. Orphan issues: open >14 days with no linked PR
|
|
142
176
|
const closeRe = /(?:closes?|fixes?|resolves?)\s+#(\d+)/gi;
|
|
143
177
|
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
|
|
@@ -223,29 +257,68 @@ jobs:
|
|
|
223
257
|
}
|
|
224
258
|
}
|
|
225
259
|
|
|
226
|
-
// 3. Rework rate
|
|
260
|
+
// 3. Rework rate with actor taxonomy (if AGENTIC_PULSE_REVIEWERS configured)
|
|
227
261
|
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
228
262
|
const weekMerged = recentPRs.filter(pr => pr.merged_at && new Date(pr.merged_at) > weekAgo);
|
|
229
263
|
|
|
230
264
|
if (weekMerged.length > 0) {
|
|
231
265
|
let firstShots = 0;
|
|
266
|
+
const reworkByRole = {};
|
|
267
|
+
const reworkDetails = []; // { pr_number, issue_number } for stage correlation
|
|
268
|
+
const issueRe = /(?:closes?|fixes?|resolves?)\s+#(\d+)/i;
|
|
269
|
+
|
|
232
270
|
for (const pr of weekMerged.slice(0, 10)) {
|
|
233
271
|
try {
|
|
234
272
|
const commits = await github.rest.pulls.listCommits({
|
|
235
273
|
owner, repo, pull_number: pr.number, per_page: 100
|
|
236
274
|
});
|
|
237
|
-
const times = commits.data
|
|
275
|
+
const times = commits.data
|
|
276
|
+
.map(c => new Date(c.commit.committer.date).getTime())
|
|
277
|
+
.sort((a, b) => a - b);
|
|
278
|
+
|
|
238
279
|
let sessions = 1;
|
|
239
280
|
for (let i = 1; i < times.length; i++) {
|
|
240
281
|
if (times[i] - times[i-1] > 10 * 60 * 1000) sessions++;
|
|
241
282
|
}
|
|
242
|
-
|
|
283
|
+
|
|
284
|
+
if (sessions === 1) { firstShots++; continue; }
|
|
285
|
+
|
|
286
|
+
if (!taxonomyEnabled) continue;
|
|
287
|
+
|
|
288
|
+
let reviewTriggered = false;
|
|
289
|
+
try {
|
|
290
|
+
const reviews = await github.rest.pulls.listReviews({
|
|
291
|
+
owner, repo, pull_number: pr.number, per_page: 100
|
|
292
|
+
});
|
|
293
|
+
const attributedRoles = new Set();
|
|
294
|
+
for (const review of reviews.data) {
|
|
295
|
+
if (!review.user) continue;
|
|
296
|
+
const role = actorMap[review.user.login];
|
|
297
|
+
if (!role || review.state === 'APPROVED' || attributedRoles.has(role)) continue;
|
|
298
|
+
const reviewTime = new Date(review.submitted_at).getTime();
|
|
299
|
+
if (times.some(t => t > reviewTime)) {
|
|
300
|
+
reworkByRole[role] = (reworkByRole[role] || 0) + 1;
|
|
301
|
+
reviewTriggered = true;
|
|
302
|
+
attributedRoles.add(role);
|
|
303
|
+
if (!reworkDetails.some(d => d.pr_number === pr.number)) {
|
|
304
|
+
const m = issueRe.exec(pr.body || '');
|
|
305
|
+
if (m) reworkDetails.push({ pr_number: pr.number, issue_number: parseInt(m[1]) });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} catch(e) { /* reviews not accessible β skip taxonomy for this PR */ }
|
|
310
|
+
|
|
311
|
+
if (!reviewTriggered) {
|
|
312
|
+
reworkByRole.self_correction = (reworkByRole.self_correction || 0) + 1;
|
|
313
|
+
}
|
|
314
|
+
|
|
243
315
|
} catch (e) { /* skip if commits not accessible */ }
|
|
244
316
|
}
|
|
317
|
+
|
|
245
318
|
const total = Math.min(weekMerged.length, 10);
|
|
246
319
|
const pct = Math.round(firstShots / total * 100);
|
|
320
|
+
const reworkCount = total - firstShots;
|
|
247
321
|
|
|
248
|
-
// Detect if repo uses an agent label (jules, sweep, codex, etc.)
|
|
249
322
|
const agentLabels = new Set(['jules', 'sweep', 'codex', 'copilot']);
|
|
250
323
|
const usesAgent = weekMerged.some(pr =>
|
|
251
324
|
(pr.labels || []).some(l => agentLabels.has(l.name.toLowerCase()))
|
|
@@ -253,27 +326,78 @@ jobs:
|
|
|
253
326
|
const subject = usesAgent ? 'Agent first-shot rate' : 'PRs sem rework';
|
|
254
327
|
const verb = usesAgent ? 'acertaram de primeira' : 'foram mergeados sem rework';
|
|
255
328
|
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
329
|
+
if (taxonomyEnabled && reworkCount > 0) {
|
|
330
|
+
const lines = [];
|
|
331
|
+
for (const [role, count] of Object.entries(reworkByRole).sort((a, b) => b[1] - a[1])) {
|
|
332
|
+
const s = count > 1 ? 's' : '';
|
|
333
|
+
if (role === 'code_reviewer') lines.push(` β³ Code reviewer: **${count} PR${s}** β revisar DoD em stage:development`);
|
|
334
|
+
else if (role === 'qa_agent') lines.push(` β³ QA Agent: **${count} PR${s}** β spec com lacunas funcionais em stage:detailing`);
|
|
335
|
+
else if (role === 'self_correction') lines.push(` β³ Self-correction: **${count} PR${s}** (causa nΓ£o determinada automaticamente)`);
|
|
336
|
+
else lines.push(` β³ ${role}: **${count} PR${s}**`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const reviewerRework = reworkByRole.code_reviewer || 0;
|
|
340
|
+
const level = reviewerRework >= Math.ceil(reworkCount * 0.8) ? 'red'
|
|
341
|
+
: (reviewerRework >= Math.ceil(reworkCount * 0.5) || (reworkByRole.qa_agent || 0) > 0) ? 'yellow'
|
|
342
|
+
: 'neutral';
|
|
343
|
+
const emoji = level === 'red' ? 'π΄' : level === 'yellow' ? 'π‘' : 'π΅';
|
|
344
|
+
|
|
264
345
|
signals.push({
|
|
265
|
-
level
|
|
266
|
-
emoji
|
|
267
|
-
title:
|
|
268
|
-
body:
|
|
346
|
+
level,
|
|
347
|
+
emoji,
|
|
348
|
+
title: `**Rework: ${100 - pct}%** β ${reworkCount} de ${total} PRs tiveram commits extras`,
|
|
349
|
+
body: lines.join('\n')
|
|
269
350
|
});
|
|
351
|
+
|
|
352
|
+
// ββ Stage correlation ββββββββββββββββββββββββββββββββββββββββ
|
|
353
|
+
if (reworkDetails.length > 0 && Object.keys(detailingByIssue).length > 0) {
|
|
354
|
+
const reworkIssueNums = new Set(reworkDetails.map(d => d.issue_number));
|
|
355
|
+
|
|
356
|
+
const reworkGroup = reworkDetails
|
|
357
|
+
.map(d => detailingByIssue[d.issue_number])
|
|
358
|
+
.filter(t => t !== undefined);
|
|
359
|
+
|
|
360
|
+
const cleanGroup = weekMerged.slice(0, 10)
|
|
361
|
+
.map(pr => { const m = issueRe.exec(pr.body || ''); return m ? parseInt(m[1]) : null; })
|
|
362
|
+
.filter(n => n !== null && !reworkIssueNums.has(n))
|
|
363
|
+
.map(n => detailingByIssue[n])
|
|
364
|
+
.filter(t => t !== undefined);
|
|
365
|
+
|
|
366
|
+
if (reworkGroup.length >= 3 && cleanGroup.length >= 3) {
|
|
367
|
+
const avgRework = round1(reworkGroup.reduce((a, b) => a + b, 0) / reworkGroup.length);
|
|
368
|
+
const avgClean = round1(cleanGroup.reduce((a, b) => a + b, 0) / cleanGroup.length);
|
|
369
|
+
if (avgRework < avgClean * 0.75) {
|
|
370
|
+
signals.push({
|
|
371
|
+
level: 'neutral',
|
|
372
|
+
emoji: 'π‘',
|
|
373
|
+
title: `**Stage correlation:** PRs com reviewer rework tiveram Detailing mΓ©dio de ${avgRework}d vs ${avgClean}d (N=${reworkGroup.length} vs ${cleanGroup.length})`,
|
|
374
|
+
body: 'β Specs rΓ‘pidas correlacionam com mais rework de review'
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
270
380
|
} else {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
381
|
+
// Taxonomy disabled or no rework β existing signal unchanged
|
|
382
|
+
if (pct >= 80) {
|
|
383
|
+
signals.push({
|
|
384
|
+
level: 'green', emoji: 'π’',
|
|
385
|
+
title: `**${subject}: ${pct}%**`,
|
|
386
|
+
body: `${firstShots} de ${total} PRs ${verb} esta semana. β
`
|
|
387
|
+
});
|
|
388
|
+
} else if (pct < 50) {
|
|
389
|
+
signals.push({
|
|
390
|
+
level: 'yellow', emoji: 'π‘',
|
|
391
|
+
title: `**${subject}: ${pct}% β rework alto**`,
|
|
392
|
+
body: `Apenas ${firstShots} de ${total} PRs sem commits extras.\nβ Specs incompletas ou mudanΓ§as de requisito durante implementaΓ§Γ£o.`
|
|
393
|
+
});
|
|
394
|
+
} else {
|
|
395
|
+
signals.push({
|
|
396
|
+
level: 'neutral', emoji: 'π΅',
|
|
397
|
+
title: `**${subject}: ${pct}%**`,
|
|
398
|
+
body: `${firstShots} de ${total} PRs sem rework commits.`
|
|
399
|
+
});
|
|
400
|
+
}
|
|
277
401
|
}
|
|
278
402
|
}
|
|
279
403
|
|
|
@@ -292,12 +416,10 @@ jobs:
|
|
|
292
416
|
|
|
293
417
|
// ββ Stage Residence Time section (conditional) ββββββββββββββββββ
|
|
294
418
|
let stageSection = '';
|
|
295
|
-
const jsonlPath = `.agentic-pdlc/metrics/raw/${weekKey}.jsonl`;
|
|
296
419
|
if (fs.existsSync(jsonlPath)) {
|
|
297
420
|
const records = fs.readFileSync(jsonlPath, 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse);
|
|
298
421
|
if (records.length > 0) {
|
|
299
422
|
const STAGES = [
|
|
300
|
-
['stage:exploration', 'Exploration'],
|
|
301
423
|
['stage:brainstorming', 'Brainstorming'],
|
|
302
424
|
['stage:detailing', 'Detailing'],
|
|
303
425
|
['stage:approval', 'Approval'],
|
|
@@ -374,7 +496,7 @@ jobs:
|
|
|
374
496
|
console.log(`Built pulse issue for ${weekKey} β ${signals.length} signals, ${reds} red, ${yellows} yellow`);
|
|
375
497
|
|
|
376
498
|
- name: Create Weekly Pulse Issue
|
|
377
|
-
uses: actions/github-script@
|
|
499
|
+
uses: actions/github-script@v8
|
|
378
500
|
with:
|
|
379
501
|
script: |
|
|
380
502
|
const { owner, repo } = context.repo;
|
|
@@ -394,19 +516,27 @@ jobs:
|
|
|
394
516
|
console.log(`Created label ${LABEL}`);
|
|
395
517
|
}
|
|
396
518
|
|
|
397
|
-
// Close previous pulse issues
|
|
519
|
+
// Close previous pulse issues; upsert if same week already exists
|
|
398
520
|
const prev = await github.rest.issues.listForRepo({
|
|
399
521
|
owner, repo, labels: LABEL, state: 'open', per_page: 20
|
|
400
522
|
});
|
|
523
|
+
let existingIssue = null;
|
|
401
524
|
for (const issue of prev.data) {
|
|
402
|
-
if (issue.title
|
|
525
|
+
if (issue.title === title) {
|
|
526
|
+
existingIssue = issue;
|
|
527
|
+
console.log(`Found existing pulse for ${title}: #${issue.number} β will update body`);
|
|
528
|
+
} else {
|
|
403
529
|
await github.rest.issues.update({ owner, repo, issue_number: issue.number, state: 'closed' });
|
|
404
530
|
console.log(`Closed previous pulse: #${issue.number}`);
|
|
405
531
|
}
|
|
406
532
|
}
|
|
407
533
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
|
|
534
|
+
if (existingIssue) {
|
|
535
|
+
await github.rest.issues.update({ owner, repo, issue_number: existingIssue.number, body });
|
|
536
|
+
console.log(`β
Updated pulse issue: #${existingIssue.number} β ${title}`);
|
|
537
|
+
} else {
|
|
538
|
+
const created = await github.rest.issues.create({
|
|
539
|
+
owner, repo, title, body, labels: [LABEL]
|
|
540
|
+
});
|
|
541
|
+
console.log(`β
Created pulse issue: #${created.data.number} β ${title}`);
|
|
542
|
+
}
|
|
@@ -11,7 +11,7 @@ jobs:
|
|
|
11
11
|
name: Run tests and linters
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
steps:
|
|
14
|
-
- uses: actions/checkout@
|
|
14
|
+
- uses: actions/checkout@v5.0.1
|
|
15
15
|
|
|
16
16
|
- name: Setup environment
|
|
17
17
|
run: echo "Replace this with your language/toolchain setup (e.g., actions/setup-node)"
|
|
@@ -38,3 +38,17 @@ jobs:
|
|
|
38
38
|
if: ${{ !contains('{{TEST_COMMAND}}', '{{') }}
|
|
39
39
|
run: |
|
|
40
40
|
{{TEST_COMMAND}}
|
|
41
|
+
|
|
42
|
+
ci-status:
|
|
43
|
+
name: Sentinel / CI
|
|
44
|
+
needs: [validate]
|
|
45
|
+
if: always()
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- name: Check validate result
|
|
49
|
+
run: |
|
|
50
|
+
result="${{ needs.validate.result }}"
|
|
51
|
+
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
|
|
52
|
+
echo "CI failed: validate=$result"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
@@ -8,7 +8,6 @@ on:
|
|
|
8
8
|
env:
|
|
9
9
|
PROJECT_ID: "{{PROJECT_ID}}"
|
|
10
10
|
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
11
|
-
STATUS_EXPLORATION: "{{ID_EXPLORATION}}"
|
|
12
11
|
STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
|
|
13
12
|
STATUS_DETAILING: "{{ID_DETAILING}}"
|
|
14
13
|
STATUS_APPROVAL: "{{ID_APPROVAL}}"
|
|
@@ -28,14 +27,13 @@ jobs:
|
|
|
28
27
|
steps:
|
|
29
28
|
- name: Validate Board Configuration
|
|
30
29
|
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
31
|
-
uses: actions/github-script@
|
|
30
|
+
uses: actions/github-script@v8
|
|
32
31
|
with:
|
|
33
32
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
34
33
|
script: |
|
|
35
34
|
const projectId = process.env.PROJECT_ID;
|
|
36
35
|
const statusFieldId = process.env.STATUS_FIELD_ID;
|
|
37
36
|
const envVars = {
|
|
38
|
-
'STATUS_EXPLORATION': process.env.STATUS_EXPLORATION,
|
|
39
37
|
'STATUS_BRAINSTORMING': process.env.STATUS_BRAINSTORMING,
|
|
40
38
|
'STATUS_DETAILING': process.env.STATUS_DETAILING,
|
|
41
39
|
'STATUS_APPROVAL': process.env.STATUS_APPROVAL,
|
|
@@ -37,12 +37,12 @@ jobs:
|
|
|
37
37
|
|
|
38
38
|
for NUM in $ISSUE_NUMS; do
|
|
39
39
|
LABELS=$(gh issue view "$NUM" --repo "$REPO" --json labels --jq '[.labels[].name] | join(" ")' 2>/dev/null || echo "")
|
|
40
|
-
if echo "$LABELS" | grep -qw "stage:approval" || echo "$LABELS" | grep -qw "spec:approved" || echo "$LABELS" | grep -qw "stage:development"; then
|
|
40
|
+
if echo "$LABELS" | grep -qw "stage:approval" || echo "$LABELS" | grep -qw "spec:approved" || echo "$LABELS" | grep -qw "stage:development" || echo "$LABELS" | grep -qw "stage:testing" || echo "$LABELS" | grep -qw "human-approved"; then
|
|
41
41
|
echo "β
Issue #$NUM approved"
|
|
42
42
|
else
|
|
43
43
|
STAGE=$(echo "$LABELS" | tr ' ' '\n' | grep "^stage:" | head -1 || echo "none")
|
|
44
44
|
echo "β Issue #$NUM missing approval (current: $STAGE)"
|
|
45
|
-
echo " Required: stage:approval OR spec:approved OR stage:development label on the issue."
|
|
45
|
+
echo " Required: stage:approval OR spec:approved OR stage:development OR stage:testing OR human-approved label on the issue."
|
|
46
46
|
echo " Emergency bypass: add 'hotfix' label to this PR."
|
|
47
47
|
exit 1
|
|
48
48
|
fi
|