create-agentic-pdlc 2.3.0 → 3.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.
Files changed (64) hide show
  1. package/.agentic-pdlc/hooks/pdlc-stage-gate.sh +37 -10
  2. package/.agentic-pdlc/metrics/raw/2026-W22.jsonl +114 -0
  3. package/.claude/settings.json +18 -0
  4. package/.coderabbit.yaml +35 -0
  5. package/.github/ISSUE_TEMPLATE/bug.md +53 -0
  6. package/.github/ISSUE_TEMPLATE/feature.md +54 -0
  7. package/.github/ISSUE_TEMPLATE/task.md +33 -0
  8. package/.github/workflows/add-to-board.yml +1 -1
  9. package/.github/workflows/agent-trigger.yml +4 -4
  10. package/.github/workflows/ci.yml +1 -1
  11. package/.github/workflows/npm-publish.yml +2 -2
  12. package/.github/workflows/pdlc-health-check.yml +1 -1
  13. package/.github/workflows/pdlc-stage-gate.yml +2 -2
  14. package/.github/workflows/project-automation.yml +25 -40
  15. package/AGENTS.md +50 -8
  16. package/CLAUDE.md +3 -1
  17. package/README.md +33 -32
  18. package/SETUP.md +2 -1
  19. package/adapters/claude-code/skill.md +39 -14
  20. package/adapters/hooks/pdlc-stage-gate.sh +3 -8
  21. package/bin/cli.js +555 -194
  22. package/docs/pdlc.md +5 -5
  23. package/docs/superpowers/plans/2026-05-28-jules-label-pat-split.md +240 -0
  24. package/docs/superpowers/plans/2026-05-29-agentic-pulse-rework-taxonomy.md +474 -0
  25. package/docs/superpowers/plans/2026-05-29-qa-gate-enforcement.md +354 -0
  26. package/docs/superpowers/plans/2026-06-04-spec-format-issue-template.md +160 -0
  27. package/docs/superpowers/plans/2026-06-04-two-tier-installer.md +1056 -0
  28. package/docs/superpowers/specs/2026-05-29-agentic-pulse-rework-taxonomy-design.md +122 -0
  29. package/docs/superpowers/specs/2026-06-04-spec-format-issue-template-design.md +46 -0
  30. package/package.json +2 -2
  31. package/templates/.github/ISSUE_TEMPLATE/bug.md +53 -0
  32. package/templates/.github/ISSUE_TEMPLATE/feature.md +54 -0
  33. package/templates/.github/ISSUE_TEMPLATE/task.md +33 -0
  34. package/templates/.github/workflows/add-to-board.yml +4 -4
  35. package/templates/.github/workflows/agent-trigger.yml +22 -13
  36. package/{.agentic-pdlc/templates → templates}/.github/workflows/agentic-metrics.yml +150 -27
  37. package/templates/.github/workflows/ci.yml +1 -1
  38. package/templates/.github/workflows/pdlc-health-check.yml +1 -1
  39. package/templates/.github/workflows/pdlc-stage-gate.yml +2 -2
  40. package/templates/.github/workflows/project-automation.yml +71 -32
  41. package/templates/.github/workflows/qa-agent.yml +32 -18
  42. package/templates/.github/workflows/qa-gate.yml +51 -0
  43. package/templates/full/AGENTS.md +143 -0
  44. package/templates/full/CLAUDE.md +30 -0
  45. package/templates/{docs → full/docs}/pdlc.md +4 -4
  46. package/templates/lite/AGENTS.md +121 -0
  47. package/templates/lite/CLAUDE.md +44 -0
  48. package/tests/cli.test.js +32 -0
  49. package/.agentic-pdlc/templates/.github/CODEOWNERS +0 -5
  50. package/.agentic-pdlc/templates/.github/copilot-instructions.md +0 -12
  51. package/.agentic-pdlc/templates/.github/workflows/add-to-board.yml +0 -38
  52. package/.agentic-pdlc/templates/.github/workflows/agent-trigger.yml +0 -146
  53. package/.agentic-pdlc/templates/.github/workflows/auto-approve.yml +0 -16
  54. package/.agentic-pdlc/templates/.github/workflows/ci.yml +0 -54
  55. package/.agentic-pdlc/templates/.github/workflows/pdlc-health-check.yml +0 -121
  56. package/.agentic-pdlc/templates/.github/workflows/pdlc-stage-gate.yml +0 -51
  57. package/.agentic-pdlc/templates/.github/workflows/project-automation.yml +0 -274
  58. package/.agentic-pdlc/templates/.github/workflows/protect-workflows.yml +0 -21
  59. package/.agentic-pdlc/templates/.github/workflows/qa-agent.yml +0 -128
  60. package/.agentic-pdlc/templates/AGENTS.md +0 -104
  61. package/.agentic-pdlc/templates/docs/pdlc.md +0 -123
  62. package/.github/workflows/agentic-metrics.yml +0 -422
  63. package/.github/workflows/qa-agent.yml +0 -128
  64. package/templates/AGENTS.md +0 -115
@@ -1,104 +0,0 @@
1
- # {{PROJECT_NAME}} — AI Agent Instructions
2
-
3
- This template is the contract between the project and any external AI agent
4
- (Claude Code, Cursor, Copilot, Jules, Codex, Sweep, etc.). Read this before committing any change.
5
-
6
- ## Project Overview
7
-
8
- {{PROJECT_DESCRIPTION}}
9
-
10
- **Structure:**
11
- {{PROJECT_STRUCTURE}}
12
-
13
- ## Before Any Change
14
-
15
- ```bash
16
- git fetch origin && git checkout main && git pull
17
- ```
18
-
19
- Always start from the current `main` HEAD. Never work over stale snapshots.
20
-
21
- ## Invariants / Non-negotiable business rules
22
-
23
- {{INVARIANTS}}
24
- <!-- Examples:
25
- 1. **Human-in-the-Loop** — No external side-effect actions without explicit human approval.
26
- 2. **Immutable Audit-Log** — It's strictly forbidden to UPDATE/DELETE on audit_log; INSERT only.
27
- 3. **Credential Isolation** — Decryption occurs only in a specific service.
28
- -->
29
-
30
- ## Mandatory Workflow
31
-
32
- 0. **Identity**: Always prefix your GitHub comments with `🤖 **Agent:** ` to distinguish yourself.
33
- 1. **Initial State**: When beginning work on a new issue, your very first action must be to apply the `stage:brainstorming` label using the GitHub CLI (`gh issue edit <N> --add-label "stage:brainstorming"`).
34
- 2. Read the issue entirely — understand its type (US/BUG/TASK/SPIKE) and the Acceptance Criteria.
35
- 3. Read `docs/pdlc.md` — understand the PDLC and the Definition of Done in this project.
36
- 4. Read all files mentioned in the issue's technical context.
37
- 5. Implement the **minimum viable change** that satisfies the ACs — do not refactor beyond scope.
38
- 6. Run tests: `{{TEST_COMMAND}}`
39
- 7. Run typecheck (if applicable): `{{TYPECHECK_COMMAND}}`
40
- 8. Create a Pull Request with `Closes #N` in the body — automation moves the board.
41
-
42
- ### Spec format (Upstream Agents)
43
-
44
- When detailing a solution in an issue body, you must **always** include both the user story and the acceptance criteria. Never append only the ACs to an existing text; rewrite the full issue body in this standard format:
45
-
46
- ```
47
- **As** [user],
48
- **I want** [action],
49
- **so that** [benefit].
50
-
51
- ---
52
-
53
- ## Acceptance Criteria
54
-
55
- **AC1 — ...**
56
- - Given ...
57
- - When ...
58
- - Then ...
59
-
60
- **AC2 — ...**
61
- ...
62
-
63
- ## Files to modify
64
- - `path/to/file.ts` — what changes
65
- ```
66
-
67
- ## Stage Transition Rules (non-negotiable)
68
-
69
- MUST apply `stage:brainstorming` label immediately on starting work — before reading
70
- any code, before invoking any skill. Then read context and present problem summary
71
- + 2–3 solution options in a single message.
72
-
73
- MUST NOT add `stage:detailing` label until the user has explicitly selected
74
- an approach in the current conversation turn. Work done in a prior
75
- planning session does NOT count as confirmation.
76
-
77
- MUST NOT add `spec:approved`, `stage:development`, or manually add
78
- `stage:approval` — these represent final human approval or the result of it.
79
- `stage:approval` is only set by system automation after you provide a complete
80
- spec for human review. Adding them manually triggers irreversible automation
81
- (Jules dispatch, board move).
82
-
83
- Each stage transition requires a fresh explicit signal from the user in the same
84
- session where the transition happens. These rules have no exceptions.
85
-
86
- ## What NOT to do
87
-
88
- - Never commit directly to `main`.
89
- - Never open a PR without passing the tests.
90
- - Never implement beyond the immediate scope of the issue.
91
- - Never create future-proofing abstractions for hypothetical features.
92
- - The agent MUST NOT apply these labels under any circumstances (PM only):
93
- - `spec:approved`: triggers Jules dispatch + board move to Development.
94
- - `qa:approved`: triggers board move to Code Review.
95
- - `qa:needs-work`: signals the PR requires changes and halts the flow.
96
- {{EXTRA_DONT}}
97
-
98
- ## Project Standards
99
-
100
- - **Tests:** `{{TEST_COMMAND}}`
101
- - **Lint/Types:** `{{LINT_COMMAND}}`
102
- - **Typecheck:** `{{TYPECHECK_COMMAND}}`
103
- - **Build:** `{{BUILD_COMMAND}}`
104
- {{EXTRA_PATTERNS}}
@@ -1,123 +0,0 @@
1
- # PDLC — {{PROJECT_NAME}}
2
-
3
- ## Board Columns
4
-
5
- | Column | Meaning | Who moves the card |
6
- |---|---|---|
7
- | 💡 Idea | Backlog — tell agent: "work on issue #XX" | Don't move manually |
8
- | 🧠 Brainstorming | AI reading context, proposing approaches and trade-offs | Label `stage:brainstorming` |
9
- | 📐 Detail Solution | Claude is writing the technical spec | Label `stage:detailing` |
10
- | ✅ Approval | Spec ready, awaiting `spec:approved` label | Label `spec:approved` |
11
- | ⚙️ Development | Agent implementing the spec | Label `stage:development` |
12
- | 🧪 Testing | CI pipeline or AI QA Agent running (Variant B) | GitHub Actions / QA Agent |
13
- | 👁 Code Review / PR | PR opened (Variant A) or QA passed (Variant B) | GitHub Actions |
14
- | 🚀 Ready for Production | Merged | GitHub Actions |
15
-
16
- <!--
17
- Adapt columns as needed. The functional baseline is:
18
- 💡 Idea → ⚙️ Development → 👁 Code Review / PR → 🚀 Ready for Production
19
- -->
20
-
21
- ## Workflow Variants (QA Agent)
22
-
23
- - **Variant A (Default):** PRs bypass the `Testing` column and land directly in `Code Review / PR`.
24
- - **Variant B (QA Agent Enabled):** PRs land in the `Testing` column first. An AI QA agent verifies the PR, adding `qa:pass` or `qa:fail`. Only after a `qa:pass` is the issue moved to `Code Review / PR`.
25
-
26
- ## Board Identifiers (GitHub Projects)
27
-
28
- ```
29
- PROJECT_ID = {{PROJECT_ID}}
30
- STATUS_FIELD = {{STATUS_FIELD_ID}}
31
- REPO = {{REPO_OWNER}}/{{REPO_NAME}}
32
- ```
33
-
34
- ## Column Option IDs
35
-
36
- | Column | Option ID |
37
- |---|---|
38
- | 💡 Idea | `{{ID_IDEA}}` |
39
- | 🧠 Brainstorming | `{{ID_BRAINSTORMING}}` |
40
- | 📐 Detail Solution | `{{ID_DETAIL}}` |
41
- | ✅ Approval | `{{ID_APPROVAL}}` |
42
- | ⚙️ Development | `{{ID_DEVELOPMENT}}` |
43
- | 🧪 Testing | `{{ID_TESTING}}` |
44
- | 👁 Code Review / PR | `{{ID_CODE_REVIEW_PR}}` |
45
- | 🚀 Ready for Production | `{{ID_READY_FOR_PRODUCTION}}` |
46
-
47
- ## Agent × Phase Mapping
48
-
49
- | Phase | Responsible |
50
- |---|---|
51
- | 💡 → 📐 (upstream) | Claude (or ideation agent) in conversational session |
52
- | ⚙️ → 🔀 (downstream) | {{IMPLEMENTATION_AGENT}} (e.g. Jules `@google-labs-jules`) |
53
- | 👁 Code Review / PR | Human (you) |
54
- | Automatic transitions | GitHub Actions |
55
-
56
- ## Issue Title Conventions
57
-
58
- ```
59
- [icon] [PREFIX]: [short description, imperative tense]
60
-
61
- 👤 US: user story
62
- 🐛 BUG: bug
63
- 🔧 TASK: operational task
64
- 🔬 SPIKE: exploration/evaluation spike
65
- ```
66
-
67
- ## Labels
68
-
69
- | Label | Entity | Color | Meaning |
70
- |---|---|---|---|
71
- | `stage:brainstorming` | Issue | Pink | Proposed approaches awaiting PM gate |
72
- | `stage:detailing` | Issue | Blue | Technical spec is being written |
73
- | `stage:development` | Issue | Orange | Agent is implementing the spec |
74
- | `spec:approved` | Issue | Green | Gate 2 — agent is cleared to implement |
75
- | `pr:in-review` | PR | Yellow | Awaiting code review |
76
- | `pr:approved` | PR | Green | Code review approved |
77
- | `type:us` | Issue | Blue | New feature or behavioral change — full flow |
78
- | `type:task` | Issue | Yellow | Operational/non-functional change — full flow |
79
- | `type:bug` | Issue | Red | Something broken — full flow |
80
- | `type:spike` | Issue | Gray | Research/evaluation — never reaches Development |
81
-
82
- ## Approval Gates
83
-
84
- **Gate 1 — PM/Ideation (Brainstorming):**
85
- Agent presents problem summary + 2–3 solution options in a single message. You select an approach.
86
- Format: *"Option X"* or *"Go with B"* or *"Approved — proceed with option X."*
87
-
88
- **Gate 2 — Tech Lead (Spec):**
89
- You add the `spec:approved` label to the issue after reviewing the technical spec in the body.
90
- This triggers the implementation agent via `agent-trigger.yml`.
91
-
92
- ## Shortcuts by Type
93
-
94
- The `type:*` label is the authoritative signal — set automatically by the agent via type inference (see `adapters/claude-code/skill.md`). Title prefixes (`🔧 TASK:`, `👤 US:`) are hints for humans; the label drives the flow.
95
-
96
- | Label | Flow |
97
- |---|---|
98
- | `type:us` | brainstorming → Gate 1 → detailing → approval |
99
- | `type:task` | brainstorming → Gate 1 → detailing → approval |
100
- | `type:bug` | brainstorming → Gate 1 → detailing → approval |
101
- | `type:spike` | brainstorming → Gate 1 → detailing → conclusion comment (never reaches Development) |
102
-
103
- If no `type:*` label present and agent confidence < 85%, defaults to `type:us` (safe fallback — never skips gates by omission).
104
-
105
- ## Bypass Mechanism
106
-
107
- Agents MUST NOT skip any stage. The ONLY authorized bypasses are:
108
-
109
- | Mechanism | Who authorizes | What it bypasses |
110
- |---|---|---|
111
- | `human-approved` label on issue | PM (human) only | All stage gates |
112
- | Branch prefix `hotfix/` | PM (human) only | PR gate only |
113
-
114
- Agents MUST NOT self-authorize a bypass. Stop and ask the PM explicitly.
115
-
116
- ## Definition of Done
117
-
118
- An issue is truly done when:
119
- - [ ] All Acceptance Criteria described in the body are implemented
120
- - [ ] Tests passing: `{{TEST_COMMAND}}`
121
- - [ ] No invariant violations (CI green)
122
- - [ ] Associated PR explicitly contains `Closes #N`
123
- - [ ] Basic manual smoke test executed after deploy (when applicable)
@@ -1,422 +0,0 @@
1
- name: Agentic Metrics — Weekly Pulse
2
-
3
- on:
4
- schedule:
5
- - cron: '0 8 * * 0' # Sunday 8am UTC
6
- workflow_dispatch:
7
-
8
- permissions:
9
- contents: write
10
- issues: write
11
-
12
- jobs:
13
- generate-pulse:
14
- name: Generate Weekly Agentic Pulse
15
- runs-on: ubuntu-latest
16
- steps:
17
- - uses: actions/checkout@v4
18
-
19
- - name: Collect Stage Residence Time
20
- uses: actions/github-script@v7
21
- with:
22
- script: |
23
- const fs = require('fs');
24
-
25
- const { owner, repo } = context.repo;
26
- const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
27
- const STAGE_LABELS = new Set([
28
- 'stage:brainstorming', 'stage:detailing',
29
- 'stage:approval', 'stage:development', 'stage:testing'
30
- ]);
31
-
32
- const QUERY = `
33
- query($owner: String!, $repo: String!, $since: DateTime!, $cursor: String) {
34
- repository(owner: $owner, name: $repo) {
35
- issues(first: 50, after: $cursor, filterBy: { since: $since }) {
36
- pageInfo { hasNextPage endCursor }
37
- nodes {
38
- number
39
- timelineItems(first: 100, itemTypes: [LABELED_EVENT, UNLABELED_EVENT]) {
40
- nodes {
41
- ... on LabeledEvent { __typename createdAt label { name } }
42
- ... on UnlabeledEvent { __typename createdAt label { name } }
43
- }
44
- }
45
- }
46
- }
47
- }
48
- }
49
- `;
50
-
51
- const records = [];
52
- let cursor = null;
53
- do {
54
- const result = await github.graphql(QUERY, { owner, repo, since, cursor });
55
- const { nodes, pageInfo } = result.repository.issues;
56
-
57
- for (const issue of nodes) {
58
- const events = issue.timelineItems.nodes
59
- .filter(e => e.label && STAGE_LABELS.has(e.label.name))
60
- .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
61
-
62
- const labeledAt = {};
63
- for (const ev of events) {
64
- const stage = ev.label.name;
65
- if (ev.__typename === 'LabeledEvent') {
66
- labeledAt[stage] = new Date(ev.createdAt);
67
- } else if (ev.__typename === 'UnlabeledEvent' && labeledAt[stage]) {
68
- const durationDays = Math.round((new Date(ev.createdAt) - labeledAt[stage]) / 864e5 * 10) / 10;
69
- records.push({ issueNumber: issue.number, stage, durationDays });
70
- delete labeledAt[stage];
71
- }
72
- }
73
- }
74
-
75
- cursor = pageInfo.hasNextPage ? pageInfo.endCursor : null;
76
- } while (cursor);
77
-
78
- function isoWeek(d) {
79
- const dt = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
80
- dt.setUTCDate(dt.getUTCDate() + 4 - (dt.getUTCDay() || 7));
81
- const y = new Date(Date.UTC(dt.getUTCFullYear(), 0, 1));
82
- return { year: dt.getUTCFullYear(), week: Math.ceil((((dt - y) / 86400000) + 1) / 7) };
83
- }
84
-
85
- const { year, week } = isoWeek(new Date());
86
- const weekKey = `${year}-W${String(week).padStart(2, '0')}`;
87
-
88
- fs.mkdirSync('.agentic-pdlc/metrics/raw', { recursive: true });
89
- fs.writeFileSync(
90
- `.agentic-pdlc/metrics/raw/${weekKey}.jsonl`,
91
- records.map(r => JSON.stringify(r)).join('\n')
92
- );
93
-
94
- core.exportVariable('WEEK_KEY', weekKey);
95
- console.log(`Collected ${records.length} stage transitions → week ${weekKey}`);
96
-
97
- - name: Commit JSONL raw data
98
- run: |
99
- git config user.name "github-actions[bot]"
100
- git config user.email "github-actions[bot]@users.noreply.github.com"
101
- git add .agentic-pdlc/metrics/raw/
102
- git diff --staged --quiet && echo "No new stage data" && exit 0
103
- git commit -m "metrics: raw stage data ${WEEK_KEY} [skip ci]"
104
- git push
105
-
106
- - name: Collect PR and Issue Insights
107
- uses: actions/github-script@v7
108
- with:
109
- script: |
110
- const fs = require('fs');
111
- const { owner, repo } = context.repo;
112
- const weekKey = process.env.WEEK_KEY;
113
-
114
- // ── Helper ──────────────────────────────────────────────────────
115
- function daysSince(isoStr) {
116
- return (Date.now() - new Date(isoStr).getTime()) / 864e5;
117
- }
118
- function round1(n) { return Math.round(n * 10) / 10; }
119
-
120
- // Week date range for issue title (ISO week, Mon–Sun)
121
- function weekDateRange(key) {
122
- const [yr, wStr] = key.split('-W');
123
- const week = parseInt(wStr, 10);
124
- // Jan 4 is always in week 1
125
- const jan4 = new Date(Date.UTC(parseInt(yr, 10), 0, 4));
126
- const monday = new Date(jan4);
127
- monday.setUTCDate(jan4.getUTCDate() - ((jan4.getUTCDay() + 6) % 7) + (week - 1) * 7);
128
- const sunday = new Date(monday);
129
- sunday.setUTCDate(monday.getUTCDate() + 6);
130
- const months = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];
131
- const pad = n => String(n).padStart(2, '0');
132
- const startMonth = months[monday.getUTCMonth()];
133
- const endMonth = months[sunday.getUTCMonth()];
134
- const monthLabel = startMonth === endMonth ? startMonth : startMonth + '/' + endMonth;
135
- return pad(monday.getUTCDate()) + '-' + pad(sunday.getUTCDate()) + '/' + monthLabel;
136
- }
137
-
138
- // ── Signal collection ───────────────────────────────────────────
139
- const signals = [];
140
-
141
- // 1. Orphan issues: open >14 days with no linked PR
142
- const closeRe = /(?:closes?|fixes?|resolves?)\s+#(\d+)/gi;
143
- const openIssues = await github.paginate(github.rest.issues.listForRepo, {
144
- owner, repo, state: 'open', per_page: 100
145
- });
146
-
147
- const recentPRs = await github.paginate(github.rest.pulls.list, {
148
- owner, repo, state: 'closed', per_page: 50,
149
- sort: 'updated', direction: 'desc'
150
- });
151
-
152
- const linkedIssueNums = new Set();
153
- for (const pr of recentPRs) {
154
- const body = pr.body || '';
155
- let m;
156
- while ((m = closeRe.exec(body)) !== null) linkedIssueNums.add(parseInt(m[1]));
157
- closeRe.lastIndex = 0;
158
- }
159
-
160
- const orphans = openIssues.filter(i =>
161
- !i.pull_request &&
162
- daysSince(i.created_at) > 14 &&
163
- !linkedIssueNums.has(i.number)
164
- );
165
-
166
- if (orphans.length > 0) {
167
- const names = orphans.slice(0, 5).map(i => `#${i.number} "${i.title}"`).join(', ');
168
- const extra = orphans.length > 5 ? ` e mais ${orphans.length - 5}` : '';
169
- signals.push({
170
- level: 'red',
171
- emoji: '🔴',
172
- title: `**${orphans.length} issue${orphans.length > 1 ? 's abertas' : ' aberta'} há 14+ dias sem PR linkado**`,
173
- body: `${names}${extra}\n→ Atribua, planeje ou feche se não for mais relevante.`
174
- });
175
- } else {
176
- signals.push({
177
- level: 'green',
178
- emoji: '🟢',
179
- title: '**Nenhuma issue esquecida**',
180
- body: 'Todas as issues abertas têm menos de 14 dias ou têm PR linkado.'
181
- });
182
- }
183
-
184
- // 2. PR merge time trend: last 10 merged PRs split 5+5
185
- const mergedPRs = recentPRs
186
- .filter(pr => pr.merged_at)
187
- .slice(0, 10);
188
-
189
- if (mergedPRs.length >= 4) {
190
- const half = Math.floor(mergedPRs.length / 2);
191
- const recent = mergedPRs.slice(0, half);
192
- const older = mergedPRs.slice(half);
193
- const avgDays = prs => {
194
- const total = prs.reduce((s, pr) =>
195
- s + (new Date(pr.merged_at) - new Date(pr.created_at)) / 864e5, 0);
196
- return round1(total / prs.length);
197
- };
198
- const recentAvg = avgDays(recent);
199
- const olderAvg = avgDays(older);
200
- const ratio = recentAvg / olderAvg;
201
-
202
- if (ratio >= 1.5) {
203
- signals.push({
204
- level: 'yellow',
205
- emoji: '🟡',
206
- title: `**Tempo de merge aumentou ${round1(ratio)}x esta semana**`,
207
- body: `Últimos ${half} PRs: **${recentAvg} dias** · ${half} anteriores: **${olderAvg} dias**\n→ Algo desacelerou o review. Verifique PRs pendentes de aprovação.`
208
- });
209
- } else if (ratio <= 0.67) {
210
- signals.push({
211
- level: 'green',
212
- emoji: '🟢',
213
- title: `**Tempo de merge melhorou ${round1(1/ratio)}x esta semana**`,
214
- body: `Últimos ${half} PRs: **${recentAvg} dias** · ${half} anteriores: **${olderAvg} dias** ✅`
215
- });
216
- } else {
217
- signals.push({
218
- level: 'neutral',
219
- emoji: '🔵',
220
- title: `**Tempo de merge estável: ${recentAvg} dias** (semana passada: ${olderAvg}d)`,
221
- body: ''
222
- });
223
- }
224
- }
225
-
226
- // 3. Rework rate: commits per PR — single push session = first-shot
227
- const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
228
- const weekMerged = recentPRs.filter(pr => pr.merged_at && new Date(pr.merged_at) > weekAgo);
229
-
230
- if (weekMerged.length > 0) {
231
- let firstShots = 0;
232
- for (const pr of weekMerged.slice(0, 10)) {
233
- try {
234
- const commits = await github.rest.pulls.listCommits({
235
- owner, repo, pull_number: pr.number, per_page: 100
236
- });
237
- const times = commits.data.map(c => new Date(c.commit.committer.date).getTime()).sort();
238
- let sessions = 1;
239
- for (let i = 1; i < times.length; i++) {
240
- if (times[i] - times[i-1] > 10 * 60 * 1000) sessions++;
241
- }
242
- if (sessions === 1) firstShots++;
243
- } catch (e) { /* skip if commits not accessible */ }
244
- }
245
- const total = Math.min(weekMerged.length, 10);
246
- const pct = Math.round(firstShots / total * 100);
247
-
248
- // Detect if repo uses an agent label (jules, sweep, codex, etc.)
249
- const agentLabels = new Set(['jules', 'sweep', 'codex', 'copilot']);
250
- const usesAgent = weekMerged.some(pr =>
251
- (pr.labels || []).some(l => agentLabels.has(l.name.toLowerCase()))
252
- );
253
- const subject = usesAgent ? 'Agent first-shot rate' : 'PRs sem rework';
254
- const verb = usesAgent ? 'acertaram de primeira' : 'foram mergeados sem rework';
255
-
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) {
264
- 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.`
269
- });
270
- } else {
271
- signals.push({
272
- level: 'neutral',
273
- emoji: '🔵',
274
- title: `**${subject}: ${pct}%**`,
275
- body: `${firstShots} de ${total} PRs sem rework commits.`
276
- });
277
- }
278
- }
279
-
280
- // 4. Unlinked PRs: merged without Closes/Fixes #N
281
- const unlinkRe = /(?:closes?|fixes?|resolves?)\s+#\d+/i;
282
- const unlinked = weekMerged.filter(pr => !unlinkRe.test(pr.body || ''));
283
- if (unlinked.length > 0) {
284
- const nums = unlinked.map(pr => `#${pr.number}`).join(', ');
285
- signals.push({
286
- level: 'yellow',
287
- emoji: '🔵',
288
- title: `**${unlinked.length} PR${unlinked.length > 1 ? 's mergeados' : ' mergeado'} sem link de issue**`,
289
- body: `${nums} não ${unlinked.length > 1 ? 'têm' : 'tem'} \`Closes #N\` no body. Difícil rastrear o que resolveram.\n→ Edite o body do PR ou feche as issues relacionadas manualmente.`
290
- });
291
- }
292
-
293
- // ── Stage Residence Time section (conditional) ──────────────────
294
- let stageSection = '';
295
- const jsonlPath = `.agentic-pdlc/metrics/raw/${weekKey}.jsonl`;
296
- if (fs.existsSync(jsonlPath)) {
297
- const records = fs.readFileSync(jsonlPath, 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse);
298
- if (records.length > 0) {
299
- const STAGES = [
300
- ['stage:brainstorming', 'Brainstorming'],
301
- ['stage:detailing', 'Detailing'],
302
- ['stage:approval', 'Approval'],
303
- ['stage:development', 'Development'],
304
- ['stage:testing', 'Testing'],
305
- ];
306
- const byStage = {};
307
- for (const r of records) {
308
- if (!byStage[r.stage]) byStage[r.stage] = [];
309
- byStage[r.stage].push(r.durationDays);
310
- }
311
- const avg = arr => arr.reduce((a, b) => a + b, 0) / arr.length;
312
-
313
- let maxStage = null, maxDays = -1;
314
- const rows = [];
315
- for (const [stage, label] of STAGES) {
316
- const days = byStage[stage];
317
- if (!days) {
318
- rows.push(`| **${label}** | — | — |`);
319
- continue;
320
- }
321
- const a = round1(avg(days));
322
- rows.push(`| **${label}** | ${a}d | ${days.length} |`);
323
- if (a > maxDays) { maxStage = label; maxDays = a; }
324
- }
325
-
326
- if (rows.length > 0) {
327
- stageSection = `\n---\n\n### 🔄 Stage Residence Time *(board ativo)*\n\n| Stage | Média | Issues |\n|---|---|---|\n${rows.join('\n')}\n\n> \`${maxStage}\` é o maior gargalo (${maxDays}d avg). Considere quebrar specs ou aumentar cadência de revisão nessa fase.\n`;
328
- }
329
- }
330
- }
331
-
332
- // ── Narrative ───────────────────────────────────────────────────
333
- const reds = signals.filter(s => s.level === 'red').length;
334
- const yellows = signals.filter(s => s.level === 'yellow').length;
335
-
336
- let narrative;
337
- if (reds >= 2) narrative = '⚠️ Fluxo sob pressão esta semana. Múltiplos bloqueios identificados. Priorize limpar issues orphans e revisar PRs pendentes antes de iniciar novas features.';
338
- else if (reds === 1 && yellows >= 1) narrative = 'Semana mista. Um ponto crítico e sinais de desaceleração. Verifique os itens marcados em 🔴 antes de avançar.';
339
- else if (reds === 0 && yellows === 0) narrative = '🟢 Fluxo saudável. Nenhum bloqueio identificado esta semana. Bom momento para avançar em novas features.';
340
- else narrative = 'Fluxo normal. Alguns ajustes de processo podem melhorar rastreabilidade e velocidade.';
341
-
342
- // ── Build issue body ────────────────────────────────────────────
343
- const signalLines = signals.map(s =>
344
- `${s.emoji} ${s.title}${s.body ? '\n' + s.body : ''}`
345
- ).join('\n\n');
346
-
347
- const nextWeekNum = (() => {
348
- const d = new Date(); d.setDate(d.getDate() + 7);
349
- const dt = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
350
- dt.setUTCDate(dt.getUTCDate() + 4 - (dt.getUTCDay() || 7));
351
- const y = new Date(Date.UTC(dt.getUTCFullYear(), 0, 1));
352
- const w = Math.ceil((((dt - y) / 86400000) + 1) / 7);
353
- return `${dt.getUTCFullYear()}-W${String(w).padStart(2, '0')}`;
354
- })();
355
-
356
- const body = [
357
- '> Auto-generated by [agentic-pdlc](https://github.com/rafaeltcosta86/agentic-pdlc). Appears every Sunday.',
358
- '',
359
- narrative,
360
- '',
361
- '---',
362
- '',
363
- '### Sinais desta semana',
364
- '',
365
- signalLines,
366
- stageSection,
367
- '---',
368
- '*Próximo pulse: ' + nextWeekNum + '*',
369
- '',
370
- '💬 Este pulse foi útil? [Conta pra gente](https://github.com/rafaeltcosta86/agentic-pdlc/issues/new?template=pulse-feedback.md&title=' + encodeURIComponent('[Pulse] ' + weekKey) + ') — 1 clique, sem formulário.'
371
- ].join('\n');
372
-
373
- const dateRange = weekDateRange(weekKey);
374
- core.exportVariable('PULSE_TITLE', '📊 Agentic Pulse — ' + weekKey + ' (' + dateRange + ')');
375
- core.exportVariable('PULSE_BODY', body);
376
- console.log(`Built pulse issue for ${weekKey} — ${signals.length} signals, ${reds} red, ${yellows} yellow`);
377
-
378
- - name: Create Weekly Pulse Issue
379
- uses: actions/github-script@v7
380
- with:
381
- script: |
382
- const { owner, repo } = context.repo;
383
- const title = process.env.PULSE_TITLE;
384
- const body = process.env.PULSE_BODY;
385
- const LABEL = 'metrics:weekly';
386
-
387
- // Ensure label exists
388
- try {
389
- await github.rest.issues.getLabel({ owner, repo, name: LABEL });
390
- } catch (e) {
391
- await github.rest.issues.createLabel({
392
- owner, repo, name: LABEL,
393
- color: '0075ca',
394
- description: 'Auto-generated weekly metrics pulse'
395
- });
396
- console.log(`Created label ${LABEL}`);
397
- }
398
-
399
- // Close previous pulse issues; upsert if same week already exists
400
- const prev = await github.rest.issues.listForRepo({
401
- owner, repo, labels: LABEL, state: 'open', per_page: 20
402
- });
403
- let existingIssue = null;
404
- for (const issue of prev.data) {
405
- if (issue.title === title) {
406
- existingIssue = issue;
407
- console.log(`Found existing pulse for ${title}: #${issue.number} — will update body`);
408
- } else {
409
- await github.rest.issues.update({ owner, repo, issue_number: issue.number, state: 'closed' });
410
- console.log(`Closed previous pulse: #${issue.number}`);
411
- }
412
- }
413
-
414
- if (existingIssue) {
415
- await github.rest.issues.update({ owner, repo, issue_number: existingIssue.number, body });
416
- console.log(`✅ Updated pulse issue: #${existingIssue.number} — ${title}`);
417
- } else {
418
- const created = await github.rest.issues.create({
419
- owner, repo, title, body, labels: [LABEL]
420
- });
421
- console.log(`✅ Created pulse issue: #${created.data.number} — ${title}`);
422
- }