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.
Files changed (59) hide show
  1. package/.agentic-pdlc/SETUP_PROMPT.md +3 -4
  2. package/.agentic-pdlc/metrics/raw/2026-W18.jsonl +2 -0
  3. package/.agentic-pdlc/metrics/raw/2026-W21.jsonl +68 -0
  4. package/.agentic-pdlc/metrics/raw/2026-W22.jsonl +114 -0
  5. package/.agentic-setup-prompt.md +3 -4
  6. package/.agentic-setup.md +3 -4
  7. package/.github/ISSUE_TEMPLATE/bug.md +53 -0
  8. package/.github/ISSUE_TEMPLATE/feature.md +54 -0
  9. package/.github/ISSUE_TEMPLATE/task.md +33 -0
  10. package/.github/workflows/add-to-board.yml +1 -1
  11. package/.github/workflows/agent-trigger.yml +4 -4
  12. package/.github/workflows/agentic-metrics.yml +171 -38
  13. package/.github/workflows/ci.yml +1 -1
  14. package/.github/workflows/npm-publish.yml +2 -2
  15. package/.github/workflows/pdlc-health-check.yml +1 -3
  16. package/.github/workflows/pdlc-stage-gate.yml +2 -2
  17. package/.github/workflows/project-automation.yml +79 -16
  18. package/.github/workflows/qa-agent.yml +26 -15
  19. package/.github/workflows/qa-gate.yml +51 -0
  20. package/AGENTS.md +50 -8
  21. package/CLAUDE.md +53 -3
  22. package/SETUP.md +4 -1
  23. package/adapters/claude-code/skill.md +44 -20
  24. package/adapters/hooks/pdlc-stage-gate.sh +2 -7
  25. package/bin/cli.js +41 -9
  26. package/docs/flow.md +8 -21
  27. package/docs/pdlc.md +24 -16
  28. package/docs/superpowers/plans/2026-05-28-jules-label-pat-split.md +240 -0
  29. package/docs/superpowers/plans/2026-05-29-agentic-pulse-rework-taxonomy.md +474 -0
  30. package/docs/superpowers/plans/2026-05-29-qa-gate-enforcement.md +354 -0
  31. package/docs/superpowers/specs/2026-05-29-agentic-pulse-rework-taxonomy-design.md +122 -0
  32. package/package.json +1 -1
  33. package/templates/.github/ISSUE_TEMPLATE/bug.md +53 -0
  34. package/templates/.github/ISSUE_TEMPLATE/feature.md +54 -0
  35. package/templates/.github/ISSUE_TEMPLATE/task.md +33 -0
  36. package/templates/.github/workflows/add-to-board.yml +4 -4
  37. package/templates/.github/workflows/agent-trigger.yml +24 -15
  38. package/{.agentic-pdlc/templates β†’ templates}/.github/workflows/agentic-metrics.yml +166 -36
  39. package/templates/.github/workflows/ci.yml +15 -1
  40. package/templates/.github/workflows/pdlc-health-check.yml +1 -3
  41. package/templates/.github/workflows/pdlc-stage-gate.yml +2 -2
  42. package/templates/.github/workflows/project-automation.yml +93 -36
  43. package/templates/.github/workflows/qa-agent.yml +33 -17
  44. package/templates/.github/workflows/qa-gate.yml +51 -0
  45. package/templates/AGENTS.md +74 -23
  46. package/templates/docs/pdlc.md +24 -16
  47. package/.agentic-pdlc/templates/.github/CODEOWNERS +0 -5
  48. package/.agentic-pdlc/templates/.github/copilot-instructions.md +0 -12
  49. package/.agentic-pdlc/templates/.github/workflows/add-to-board.yml +0 -38
  50. package/.agentic-pdlc/templates/.github/workflows/agent-trigger.yml +0 -146
  51. package/.agentic-pdlc/templates/.github/workflows/auto-approve.yml +0 -16
  52. package/.agentic-pdlc/templates/.github/workflows/ci.yml +0 -40
  53. package/.agentic-pdlc/templates/.github/workflows/pdlc-health-check.yml +0 -123
  54. package/.agentic-pdlc/templates/.github/workflows/pdlc-stage-gate.yml +0 -51
  55. package/.agentic-pdlc/templates/.github/workflows/project-automation.yml +0 -278
  56. package/.agentic-pdlc/templates/.github/workflows/protect-workflows.yml +0 -21
  57. package/.agentic-pdlc/templates/.github/workflows/qa-agent.yml +0 -128
  58. package/.agentic-pdlc/templates/AGENTS.md +0 -81
  59. 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
- PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
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 Labels
26
- uses: actions/github-script@v7
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: labelsToAdd
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.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
65
+ if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
57
66
  continue-on-error: true
58
- uses: actions/github-script@v7
67
+ uses: actions/github-script@v8
59
68
  with:
60
- github-token: ${{ env.PROJECT_PAT }}
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@v7
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@v7
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@v4
22
+ - uses: actions/checkout@v5.0.1
18
23
 
19
24
  - name: Collect Stage Residence Time
20
- uses: actions/github-script@v7
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:exploration', 'stage:brainstorming', 'stage:detailing',
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@v7
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: commits per PR β€” single push session = first-shot
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.map(c => new Date(c.commit.committer.date).getTime()).sort();
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
- if (sessions === 1) firstShots++;
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 (pct >= 80) {
257
- signals.push({
258
- level: 'green',
259
- emoji: '🟒',
260
- title: `**${subject}: ${pct}%**`,
261
- body: `${firstShots} de ${total} PRs ${verb} esta semana. βœ…`
262
- });
263
- } else if (pct < 50) {
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: 'yellow',
266
- emoji: '🟑',
267
- title: `**${subject}: ${pct}% β€” rework alto**`,
268
- body: `Apenas ${firstShots} de ${total} PRs sem commits extras.\n→ Specs incompletas ou mudanças de requisito durante implementação.`
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
- signals.push({
272
- level: 'neutral',
273
- emoji: 'πŸ”΅',
274
- title: `**${subject}: ${pct}%**`,
275
- body: `${firstShots} de ${total} PRs sem rework commits.`
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@v7
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 !== 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
- // Create new pulse issue
409
- const created = await github.rest.issues.create({
410
- owner, repo, title, body, labels: [LABEL]
411
- });
412
- console.log(`βœ… Created pulse issue: #${created.data.number} β€” ${title}`);
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@v4
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@v7
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