create-agentic-pdlc 2.1.5 → 2.2.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 +73 -0
- package/.agentic-pdlc/hooks/pdlc-stage-gate.sh +39 -0
- package/.agentic-pdlc/metrics/.gitkeep +0 -0
- package/.agentic-pdlc/metrics/2026-W19.md +21 -0
- package/.agentic-pdlc/metrics/raw/2026-W19.jsonl +6 -0
- package/.agentic-pdlc/metrics/raw/2026-W20.jsonl +1 -0
- package/.agentic-pdlc/templates/.github/CODEOWNERS +5 -0
- package/.agentic-pdlc/templates/.github/copilot-instructions.md +12 -0
- package/.agentic-pdlc/templates/.github/workflows/add-to-board.yml +38 -0
- package/.agentic-pdlc/templates/.github/workflows/agent-trigger.yml +146 -0
- package/.agentic-pdlc/templates/.github/workflows/agentic-metrics.yml +412 -0
- package/.agentic-pdlc/templates/.github/workflows/auto-approve.yml +16 -0
- package/.agentic-pdlc/templates/.github/workflows/ci.yml +40 -0
- package/.agentic-pdlc/templates/.github/workflows/pdlc-health-check.yml +123 -0
- package/.agentic-pdlc/templates/.github/workflows/pdlc-stage-gate.yml +51 -0
- package/.agentic-pdlc/templates/.github/workflows/project-automation.yml +278 -0
- package/.agentic-pdlc/templates/.github/workflows/protect-workflows.yml +21 -0
- package/.agentic-pdlc/templates/.github/workflows/qa-agent.yml +128 -0
- package/.agentic-pdlc/templates/AGENTS.md +81 -0
- package/.agentic-pdlc/templates/docs/pdlc.md +15 -5
- package/.agentic-setup-prompt.md +73 -0
- package/.agentic-setup.md +73 -0
- package/.claude/settings.json +15 -0
- package/.cursorrules +9 -0
- package/.github/ISSUE_TEMPLATE/pulse-feedback.md +11 -0
- package/.github/workflows/add-to-board.yml +38 -0
- package/.github/workflows/agent-trigger.yml +30 -43
- package/.github/workflows/agentic-metrics.yml +412 -0
- package/.github/workflows/pdlc-health-check.yml +10 -10
- package/.github/workflows/pdlc-stage-gate.yml +51 -0
- package/.github/workflows/project-automation.yml +68 -18
- package/.github/workflows/qa-agent.yml +112 -11
- package/CLAUDE.md +9 -0
- package/README.md +8 -0
- package/SETUP.md +28 -0
- package/adapters/claude-code/skill.md +41 -3
- package/adapters/hooks/pdlc-stage-gate.sh +44 -0
- package/bin/cli.js +28 -5
- package/docs/pdlc.md +15 -5
- package/package.json +1 -1
- package/pr_comments.json +20 -0
- package/templates/.github/workflows/add-to-board.yml +38 -0
- package/templates/.github/workflows/agent-trigger.yml +34 -4
- package/templates/.github/workflows/pdlc-stage-gate.yml +51 -0
- package/templates/.github/workflows/project-automation.yml +78 -54
- package/templates/.github/workflows/qa-agent.yml +14 -13
- package/templates/AGENTS.md +10 -0
- package/templates/docs/pdlc.md +15 -5
|
@@ -6,22 +6,123 @@ on:
|
|
|
6
6
|
permissions:
|
|
7
7
|
pull-requests: write
|
|
8
8
|
contents: read
|
|
9
|
+
issues: read
|
|
10
|
+
models: read
|
|
9
11
|
|
|
10
12
|
jobs:
|
|
11
13
|
qa:
|
|
12
|
-
name:
|
|
14
|
+
name: AC Coverage Verification (GitHub Models)
|
|
13
15
|
runs-on: ubuntu-latest
|
|
16
|
+
env:
|
|
17
|
+
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
18
|
+
PROJECT_ID: "PVT_kwHODpFFL84BXg7h"
|
|
19
|
+
STATUS_FIELD_ID: "PVTSSF_lAHODpFFL84BXg7hzhStRHI"
|
|
20
|
+
STATUS_CODE_REVIEW_PR: "86ca9720"
|
|
14
21
|
steps:
|
|
15
22
|
- uses: actions/checkout@v4
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
echo "If tests pass: gh pr edit $PR_URL --add-label 'qa:pass'"
|
|
21
|
-
echo "If tests fail: gh pr edit $PR_URL --add-label 'qa:fail'"
|
|
22
|
-
|
|
23
|
-
# Example success:
|
|
24
|
-
# gh pr edit ${{ github.event.pull_request.html_url }} --add-label "qa:pass"
|
|
23
|
+
with:
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
|
|
26
|
+
- name: Verify AC Coverage via GitHub Models
|
|
25
27
|
env:
|
|
26
28
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
-
|
|
29
|
+
run: |
|
|
30
|
+
set -e
|
|
31
|
+
|
|
32
|
+
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
33
|
+
BASE="${{ github.event.pull_request.base.sha }}"
|
|
34
|
+
HEAD="${{ github.event.pull_request.head.sha }}"
|
|
35
|
+
|
|
36
|
+
# Get PR diff (truncated to 8000 chars to stay within context limits)
|
|
37
|
+
DIFF=$(git diff "$BASE" "$HEAD" | head -c 64000)
|
|
38
|
+
|
|
39
|
+
# Extract linked issues from PR body
|
|
40
|
+
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body // ""')
|
|
41
|
+
ISSUE_NUMS=$(echo "$PR_BODY" | grep -oiE '(Closes?|Fixes?|Resolves?)\s+#([0-9]+)' | grep -oE '[0-9]+' || true)
|
|
42
|
+
|
|
43
|
+
# Build acceptance criteria context
|
|
44
|
+
AC_CONTEXT=""
|
|
45
|
+
if [ -n "$ISSUE_NUMS" ]; then
|
|
46
|
+
for NUM in $ISSUE_NUMS; do
|
|
47
|
+
ISSUE_BODY=$(gh issue view "$NUM" --json body --jq '.body // ""' 2>/dev/null || echo "")
|
|
48
|
+
AC_CONTEXT="${AC_CONTEXT}\\n\\n--- Issue #${NUM} ---\\n${ISSUE_BODY}"
|
|
49
|
+
done
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [ -z "$AC_CONTEXT" ]; then
|
|
53
|
+
AC_CONTEXT="No linked issue found. Evaluate if the PR description is self-contained."
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Serialize prompt as JSON string and call GitHub Models API (30s timeout)
|
|
57
|
+
PROMPT_JSON=$(printf '%s' "You are a senior QA engineer. Review whether this PR diff satisfies the Acceptance Criteria below.\n\nACCEPTANCE CRITERIA:\n${AC_CONTEXT}\n\nPR DIFF:\n${DIFF}\n\nFirst line of your response must be exactly one word: PASS or FAIL. Second line: brief explanation (max 3 sentences)." | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')
|
|
58
|
+
|
|
59
|
+
RESPONSE=$(curl -sf -X POST \
|
|
60
|
+
"https://models.github.ai/inference/chat/completions" \
|
|
61
|
+
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
|
62
|
+
-H "Content-Type: application/json" \
|
|
63
|
+
-d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"user\",\"content\":${PROMPT_JSON}}]}" \
|
|
64
|
+
--max-time 30 || echo "API_ERROR")
|
|
65
|
+
|
|
66
|
+
if [ "$RESPONSE" = "API_ERROR" ]; then
|
|
67
|
+
gh pr edit "$PR_NUMBER" --add-label "infra:qa-broken"
|
|
68
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not reach GitHub Models API. Manual review required."
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
VERDICT=$(echo "$RESPONSE" | python3 -c 'import json,sys,re; d=json.load(sys.stdin); t=d.get("choices",[{}])[0].get("message",{}).get("content","").strip(); first=t.split("\n")[0].upper() if t else ""; print("FAIL" if re.search(r"\bFAIL\b",first) else "PASS" if re.search(r"\bPASS\b",first) else "API_ERROR")')
|
|
73
|
+
EXPLANATION=$(echo "$RESPONSE" | python3 -c 'import json,sys; d=json.load(sys.stdin); t=d.get("choices",[{}])[0].get("message",{}).get("content","").strip(); lines=t.split("\n",1); print(lines[1].strip() if len(lines)>1 else "")')
|
|
74
|
+
|
|
75
|
+
if echo "$VERDICT" | grep -q "^PASS"; then
|
|
76
|
+
gh pr edit "$PR_NUMBER" --add-label "qa:approved"
|
|
77
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** AC coverage verified. ${EXPLANATION}"
|
|
78
|
+
elif echo "$VERDICT" | grep -q "^FAIL"; then
|
|
79
|
+
gh pr edit "$PR_NUMBER" --add-label "qa:needs-work"
|
|
80
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** AC coverage insufficient. ${EXPLANATION}"
|
|
81
|
+
else
|
|
82
|
+
gh pr edit "$PR_NUMBER" --add-label "infra:qa-broken"
|
|
83
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not parse GitHub Models response. Manual review required."
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
- name: Move board card to Code Review on qa:approved
|
|
87
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
88
|
+
uses: actions/github-script@v7
|
|
89
|
+
with:
|
|
90
|
+
github-token: ${{ env.PROJECT_TOKEN }}
|
|
91
|
+
script: |
|
|
92
|
+
const prNumber = context.payload.pull_request.number;
|
|
93
|
+
const { owner, repo } = context.repo;
|
|
94
|
+
|
|
95
|
+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
96
|
+
if (!pr.labels.some(l => l.name === 'qa:approved')) {
|
|
97
|
+
console.log('qa:approved not on PR — skipping board move');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const body = pr.body ?? '';
|
|
102
|
+
const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)]
|
|
103
|
+
.map(m => parseInt(m[1]));
|
|
104
|
+
|
|
105
|
+
const moveItem = async (nodeId) => {
|
|
106
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
107
|
+
mutation($p: ID!, $c: ID!) {
|
|
108
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
109
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
110
|
+
await github.graphql(`
|
|
111
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
112
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
113
|
+
projectV2Item { id }
|
|
114
|
+
}
|
|
115
|
+
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
116
|
+
v: { singleSelectOptionId: process.env.STATUS_CODE_REVIEW_PR } });
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (linkedIssues.length > 0) {
|
|
120
|
+
for (const n of linkedIssues) {
|
|
121
|
+
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
122
|
+
await moveItem(issue.node_id);
|
|
123
|
+
console.log(`Issue #${n} → Code Review / PR`);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
await moveItem(pr.node_id);
|
|
127
|
+
console.log(`PR #${prNumber} → Code Review / PR (no linked issue)`);
|
|
128
|
+
}
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# PDLC Stage Gate
|
|
2
|
+
|
|
3
|
+
NEVER run `gh pr create` unless one of these is true:
|
|
4
|
+
- The linked issue has label `stage:approval`
|
|
5
|
+
- The branch name starts with `hotfix/`
|
|
6
|
+
|
|
7
|
+
Advance stages first: `exploration` → `brainstorming` → `detailing` → `approval`
|
|
8
|
+
|
|
9
|
+
The PreToolUse hook will block the action automatically if this rule is violated.
|
package/README.md
CHANGED
|
@@ -33,6 +33,14 @@ npx create-agentic-pdlc
|
|
|
33
33
|
|
|
34
34
|
The CLI sets up your GitHub board, labels, and workflows, then hands over to your AI assistant to finish the configuration interactively. No YAML editing. No manual config.
|
|
35
35
|
|
|
36
|
+
**Already set up? Add or reconfigure optional agents at any time:**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx create-agentic-pdlc --update
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Detects what is already configured (Jules, QA Agent, Sentinel) and interactively sets up what is missing. Your existing board IDs and customizations are never touched.
|
|
43
|
+
|
|
36
44
|
---
|
|
37
45
|
|
|
38
46
|
## 🏗️ How It Works
|
package/SETUP.md
CHANGED
|
@@ -149,6 +149,34 @@ If you don't use this, you can safely delete `templates/.github/workflows/qa-age
|
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
152
|
+
## (Optional) Agentic Metrics — Weekly Report
|
|
153
|
+
|
|
154
|
+
Every Sunday a `📊 Agentic Pulse` issue is created automatically in your repository with agentic-specific insights no off-the-shelf tool provides:
|
|
155
|
+
|
|
156
|
+
- **Orphan issues** — open 14+ days with no linked PR (forgotten or blocked work)
|
|
157
|
+
- **PR merge time trend** — week-over-week comparison (flow acceleration or slowdown)
|
|
158
|
+
- **Rework rate** — PRs merged without extra commit sessions (agent or human first-shot rate)
|
|
159
|
+
- **Unlinked PRs** — merged without `Closes #N` (traceability gaps)
|
|
160
|
+
- **Stage Residence Time** — included automatically if your team uses `stage:*` labels _(Milestone 2)_
|
|
161
|
+
|
|
162
|
+
The issue appears in your GitHub notifications automatically — zero extra setup, zero extra secrets.
|
|
163
|
+
|
|
164
|
+
**To activate:**
|
|
165
|
+
|
|
166
|
+
1. Copy the workflow to your repository:
|
|
167
|
+
```bash
|
|
168
|
+
cp .agentic-pdlc/templates/.github/workflows/agentic-metrics.yml .github/workflows/agentic-metrics.yml
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
2. Commit and push. The first pulse runs next Sunday, or trigger it manually:
|
|
172
|
+
```bash
|
|
173
|
+
gh workflow run agentic-metrics.yml
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
> **No additional secrets needed.** The workflow uses the standard `GITHUB_TOKEN` and creates a `metrics:weekly` label automatically on first run.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
152
180
|
## Final Verification Checklist
|
|
153
181
|
|
|
154
182
|
- [ ] Board has 10 columns fully configured
|
|
@@ -42,7 +42,7 @@ If any of these files are missing, you are in **Setup Mode**. Do not proceed wit
|
|
|
42
42
|
- c) **Yes, activate** — *Uncomment the `move-violation-to-board` job in `project-automation.yml`.* → Activate immediately.
|
|
43
43
|
- **QA Agent:** Ask: *"Do you want to use a QA agent to verify PRs automatically before Code Review?"* Present the options:
|
|
44
44
|
- a) **No (Variant A)** — *PRs go straight to Code Review. Standard and simpler.*
|
|
45
|
-
- b) **Yes (Variant B), but I need help configuring it** — *PRs pass through a QA Agent before being reviewed.
|
|
45
|
+
- b) **Yes (Variant B), but I need help configuring it** — *PRs pass through a QA Agent before being reviewed. Uses `GITHUB_TOKEN` — zero additional secrets.* → Guide the user through configuration.
|
|
46
46
|
- c) **Yes (Variant B), I already have it configured** — *PRs pass through a QA Agent before being reviewed.* → Activate Variant B immediately: change `STATUS_CODE_REVIEW_PR` to `STATUS_TESTING` in the `move-card-on-pr-open` job and uncomment the `move-card-on-qa-pass` job in `project-automation.yml`.
|
|
47
47
|
- **Implementation Agent:** Ask: *"Do you use an autonomous implementation agent? (It implements the features you approve for development)"* Present the options:
|
|
48
48
|
- a) **No** — *No autonomous implementation agent.*
|
|
@@ -86,7 +86,32 @@ This detects which optional agents (Jules, QA Agent, Sentinel) are already confi
|
|
|
86
86
|
|
|
87
87
|
If `AGENTS.md` and `docs/pdlc.md` are present, you are in **Execution Mode**.
|
|
88
88
|
|
|
89
|
-
### 0.
|
|
89
|
+
### 0. [FIRST] Issue Type Identification
|
|
90
|
+
|
|
91
|
+
**Run before anything else — before `stage:exploration`, before reading code.**
|
|
92
|
+
|
|
93
|
+
Reading the issue title and body for type inference is exempt from the `stage:exploration` requirement: it is metadata already present in the request, not code reading or skill invocation.
|
|
94
|
+
|
|
95
|
+
1. Check if issue already has a `type:*` label (`type:us`, `type:task`, `type:bug`, `type:spike`) → if yes, skip to Section 0.1.
|
|
96
|
+
2. Read issue title + body (metadata only — no code reading at this step).
|
|
97
|
+
3. Classify using these rules:
|
|
98
|
+
- `type:task` — operational change, config, rename, docs update, non-functional (no user-facing behavior change)
|
|
99
|
+
- `type:bug` — something broken that should work
|
|
100
|
+
- `type:spike` — research/evaluation spike, never reaches Development
|
|
101
|
+
- `type:us` — new feature, behavioral change, anything product-facing
|
|
102
|
+
4. Confidence ≥ 85% → add inferred label: `gh issue edit <N> --add-label "type:<inferred>"`
|
|
103
|
+
5. Confidence < 85% → default to `type:us`: `gh issue edit <N> --add-label "type:us"`
|
|
104
|
+
|
|
105
|
+
**Type drives the PDLC flow:**
|
|
106
|
+
|
|
107
|
+
| Type | Flow |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `type:us` | Full flow: exploration → brainstorming → Gate 1 → detailing → approval |
|
|
110
|
+
| `type:task` | Skip brainstorming: exploration → detailing → approval |
|
|
111
|
+
| `type:bug` | Skip brainstorming: exploration → detailing → approval |
|
|
112
|
+
| `type:spike` | Skip brainstorming: exploration → detailing → conclusion comment (never reaches Development) |
|
|
113
|
+
|
|
114
|
+
### 0.1 Board Labels — Mandatory at Every State Transition
|
|
90
115
|
|
|
91
116
|
These label commands are non-negotiable. They run **before** the activity they announce — before reading code, before invoking any skill, before any other action.
|
|
92
117
|
|
|
@@ -98,8 +123,21 @@ These label commands are non-negotiable. They run **before** the activity they a
|
|
|
98
123
|
|
|
99
124
|
No investigation, no skill invocation, no code reading happens before `stage:exploration` is applied. No architecture presentation starts before `stage:brainstorming` is set (and `stage:exploration` removed). No spec writing starts before `stage:detailing` is set (and `stage:brainstorming` removed).
|
|
100
125
|
|
|
126
|
+
### 0.1 PR Stage Gate — Non-Negotiable
|
|
127
|
+
|
|
128
|
+
**NEVER run `gh pr create` unless the linked issue has label `stage:approval`.**
|
|
129
|
+
|
|
130
|
+
The PreToolUse hook enforces this automatically and will block the command. The only bypass is a branch prefixed with `hotfix/` — which requires explicit PM instruction, never agent self-authorization.
|
|
131
|
+
|
|
132
|
+
Hotfix flow (only when PM explicitly requests it):
|
|
133
|
+
```bash
|
|
134
|
+
gh issue edit <N> --add-label "hotfix"
|
|
135
|
+
git checkout -b hotfix/<N>-<description>
|
|
136
|
+
# implement → gh pr create --label hotfix
|
|
137
|
+
```
|
|
138
|
+
|
|
101
139
|
### 1. Daily Upstream Loop
|
|
102
|
-
Your job is to move issues from "Idea" to "Detail Solution".
|
|
140
|
+
Your job is to move issues from "💡 Idea - don't move manually to Exploration" to "📐 Detail Solution".
|
|
103
141
|
When asked to work on a feature, you will:
|
|
104
142
|
- Explore the code context.
|
|
105
143
|
- Present architectural approaches (Brainstorming).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PDLC Stage Gate — blocks gh pr create without stage:approval on linked issue.
|
|
3
|
+
# Bypass: branch prefix hotfix/ skips all checks.
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
COMMAND=$(echo "$INPUT" | node -e "const d=JSON.parse(require('fs').readFileSync(0)); console.log(d.command || '')" 2>/dev/null || echo "")
|
|
7
|
+
|
|
8
|
+
if ! echo "$COMMAND" | grep -q "gh pr create"; then
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
|
13
|
+
|
|
14
|
+
if echo "$BRANCH" | grep -qE "^hotfix/"; then
|
|
15
|
+
echo "✅ PDLC: Hotfix branch — stage gate bypassed."
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
ISSUE_NUM=$(echo "$BRANCH" | grep -oE '[0-9]+' | head -1)
|
|
20
|
+
|
|
21
|
+
if [ -z "$ISSUE_NUM" ]; then
|
|
22
|
+
echo "❌ PDLC Stage Gate: Cannot determine issue from branch '$BRANCH'."
|
|
23
|
+
echo " Use: feat/<issue-number>-<description> or hotfix/<issue-number>-<description>"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
LABELS=$(gh issue view "$ISSUE_NUM" --json labels --jq '[.labels[].name] | join(" ")' 2>/dev/null || echo "")
|
|
28
|
+
|
|
29
|
+
if echo "$LABELS" | grep -qw "stage:approval"; then
|
|
30
|
+
echo "✅ PDLC: Issue #$ISSUE_NUM approved — gate passed."
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
if echo "$LABELS" | grep -qw "stage:development"; then
|
|
35
|
+
echo "✅ PDLC: Issue #$ISSUE_NUM in development (spec already approved) — gate passed."
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
STAGE=$(echo "$LABELS" | tr ' ' '\n' | grep "^stage:" | head -1 || echo "none")
|
|
40
|
+
echo "❌ PDLC Stage Gate: Issue #$ISSUE_NUM is not approved."
|
|
41
|
+
echo " Current stage: $STAGE"
|
|
42
|
+
echo " Required: stage:approval or stage:development label on the issue."
|
|
43
|
+
echo " Emergency bypass: rename branch to hotfix/<issue-number>-<description>."
|
|
44
|
+
exit 1
|
package/bin/cli.js
CHANGED
|
@@ -68,8 +68,8 @@ const i18n = {
|
|
|
68
68
|
update_jules_header: t('— Jules (autonomous implementation agent) —', '— Jules (agente de implementação autônomo) —', '— Jules (agente de implementación autónomo) —'),
|
|
69
69
|
update_jules_ask: t(' Which agent? (a) @google-labs-jules (b) Other (c) Skip: ', ' Qual agente? (a) @google-labs-jules (b) Outro (c) Pular: ', ' ¿Qué agente? (a) @google-labs-jules (b) Otro (c) Omitir: '),
|
|
70
70
|
update_jules_ask_handle: t(' Agent handle (e.g. @my-agent): ', ' Handle do agente (ex: @meu-agente): ', ' Handle del agente (ej: @mi-agente): '),
|
|
71
|
-
update_qa_header: t('— QA Agent (AC verification via
|
|
72
|
-
update_qa_ask: t(' Activate?
|
|
71
|
+
update_qa_header: t('— QA Agent (AC verification via GitHub Models — zero secrets) —', '— QA Agent (verificação de ACs via GitHub Models — zero secrets) —', '— QA Agent (verificación de ACs via GitHub Models — zero secrets) —'),
|
|
72
|
+
update_qa_ask: t(' Activate? Uses GITHUB_TOKEN — no extra secrets needed. (Y/n): ', ' Ativar? Usa GITHUB_TOKEN — nenhum secret extra necessário. (S/n): ', ' ¿Activar? Usa GITHUB_TOKEN — sin secrets adicionales. (S/n): '),
|
|
73
73
|
update_sentinel_header: t('— Sentinel (architecture audit via Gemini Code Assist) —', '— Sentinel (auditoria de arquitetura via Gemini Code Assist) —', '— Sentinel (auditoría de arquitectura via Gemini Code Assist) —'),
|
|
74
74
|
update_sentinel_ask: t(' Activate? Requires Gemini Code Assist CI job. (Y/n): ', ' Ativar? Requer CI job do Gemini Code Assist. (S/n): ', ' ¿Activar? Requiere CI job de Gemini Code Assist. (S/n): '),
|
|
75
75
|
};
|
|
@@ -343,6 +343,29 @@ async function runSetup() {
|
|
|
343
343
|
// Non-fatal — agent will ask for the values instead
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
+
// Install PDLC stage gate hook (all agents)
|
|
347
|
+
const hookSrc = path.join(sourceDir, 'adapters', 'hooks', 'pdlc-stage-gate.sh');
|
|
348
|
+
const hookDir = path.join(targetDir, '.agentic-pdlc', 'hooks');
|
|
349
|
+
const hookDest = path.join(hookDir, 'pdlc-stage-gate.sh');
|
|
350
|
+
if (fs.existsSync(hookSrc)) {
|
|
351
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
352
|
+
fs.copyFileSync(hookSrc, hookDest);
|
|
353
|
+
fs.chmodSync(hookDest, '755');
|
|
354
|
+
}
|
|
355
|
+
const claudeSettingsDir = path.join(targetDir, '.claude');
|
|
356
|
+
const claudeSettingsPath = path.join(claudeSettingsDir, 'settings.json');
|
|
357
|
+
if (!fs.existsSync(claudeSettingsPath)) {
|
|
358
|
+
fs.mkdirSync(claudeSettingsDir, { recursive: true });
|
|
359
|
+
fs.writeFileSync(claudeSettingsPath, JSON.stringify({
|
|
360
|
+
hooks: {
|
|
361
|
+
PreToolUse: [{
|
|
362
|
+
matcher: 'Bash',
|
|
363
|
+
hooks: [{ type: 'command', command: 'bash .agentic-pdlc/hooks/pdlc-stage-gate.sh' }]
|
|
364
|
+
}]
|
|
365
|
+
}
|
|
366
|
+
}, null, 2) + '\n');
|
|
367
|
+
}
|
|
368
|
+
|
|
346
369
|
// Handle the specific setup instructions target
|
|
347
370
|
const claudeSetupSrc = path.join(sourceDir, 'adapters', 'claude-code', 'skill.md');
|
|
348
371
|
const cursorSetupSrc = path.join(sourceDir, 'adapters', 'cursor', 'rules.md');
|
|
@@ -536,9 +559,9 @@ async function runUpdate() {
|
|
|
536
559
|
if (!['n', 'no', 'não', 'nao'].includes(answer)) {
|
|
537
560
|
activateQaAgent(paPath);
|
|
538
561
|
results.push(t(
|
|
539
|
-
'✅ QA Agent configured — Variant B activated
|
|
540
|
-
'✅ QA Agent configurado — Variant B ativado
|
|
541
|
-
'✅ QA Agent configurado — Variant B activado
|
|
562
|
+
'✅ QA Agent configured — Variant B activated (uses GITHUB_TOKEN, no extra secrets needed)',
|
|
563
|
+
'✅ QA Agent configurado — Variant B ativado (usa GITHUB_TOKEN, nenhum secret extra necessário)',
|
|
564
|
+
'✅ QA Agent configurado — Variant B activado (usa GITHUB_TOKEN, sin secrets adicionales)'
|
|
542
565
|
));
|
|
543
566
|
} else {
|
|
544
567
|
results.push(t('⏭ QA Agent — skipped', '⏭ QA Agent — pulado', '⏭ QA Agent — omitido'));
|
package/docs/pdlc.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| Column | Meaning | Who moves the card |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| 💡 Idea | Backlog —
|
|
7
|
+
| 💡 Idea — don't move manually to Exploration | Backlog — tell agent: "work on issue #XX" | Don't move manually |
|
|
8
8
|
| 🔍 Exploration | AI is analyzing code and context | Label `stage:exploration` |
|
|
9
9
|
| 🧠 Brainstorming | AI proposed approaches and trade-offs | Label `stage:brainstorming` |
|
|
10
10
|
| 📐 Detail Solution | AI is writing the technical spec | Label `stage:detailing` |
|
|
@@ -72,6 +72,10 @@ REPO = {{REPO_OWNER}}/{{REPO_NAME}}
|
|
|
72
72
|
| `spec:approved` | Issue | Green | Gate 2 — agent is cleared to implement |
|
|
73
73
|
| `pr:in-review` | PR | Yellow | Awaiting code review |
|
|
74
74
|
| `pr:approved` | PR | Green | Code review approved |
|
|
75
|
+
| `type:us` | Issue | Blue | New feature or behavioral change — full flow |
|
|
76
|
+
| `type:task` | Issue | Yellow | Operational/non-functional change — skips brainstorming |
|
|
77
|
+
| `type:bug` | Issue | Red | Something broken — skips brainstorming |
|
|
78
|
+
| `type:spike` | Issue | Gray | Research/evaluation — never reaches Development |
|
|
75
79
|
|
|
76
80
|
## Approval Gates
|
|
77
81
|
|
|
@@ -85,10 +89,16 @@ This triggers the implementation agent via `agent-trigger.yml`.
|
|
|
85
89
|
|
|
86
90
|
## Shortcuts by Type
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
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.
|
|
93
|
+
|
|
94
|
+
| Label | Flow |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `type:us` | Full flow — exploration → brainstorming → Gate 1 → detailing → approval |
|
|
97
|
+
| `type:task` | Skips brainstorming — exploration → detailing → approval |
|
|
98
|
+
| `type:bug` | Skips brainstorming — exploration → detailing → approval |
|
|
99
|
+
| `type:spike` | Skips brainstorming — exploration → detailing → conclusion comment (never reaches Development) |
|
|
100
|
+
|
|
101
|
+
If no `type:*` label present and agent confidence < 85%, defaults to `type:us` (safe fallback — never skips gates by omission).
|
|
92
102
|
|
|
93
103
|
## Definition of Done
|
|
94
104
|
|
package/package.json
CHANGED
package/pr_comments.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"body": " \n\nThe use of `execSync` with template literals containing user-provided variables (such as `${repo}`, `${repoOwner}`, `${repoName}`, and `${branchName}`) is vulnerable to command injection. If a user provides a malicious repository URL or branch name containing shell metacharacters (e.g., `; rm -rf /`), they could execute arbitrary shell commands. It is highly recommended to use `spawnSync` with an arguments array to avoid shell interpretation, or at least sanitize and quote the inputs strictly.",
|
|
3
|
+
"path": "bin/cli.js",
|
|
4
|
+
"line": 155
|
|
5
|
+
}
|
|
6
|
+
{
|
|
7
|
+
"body": "\n\nThe required status check context `\"Sentinel / CI\"` does not match any of the workflow or job names defined in the provided templates (e.g., `PDLC Board Automation` or `Detect Project Board Drift`). This will cause pull requests to be permanently blocked as the required check will never be reported. Based on the provided templates, `\"Detect Project Board Drift\"` seems to be the intended health check job name.\n\n```suggestion\n required_status_checks: { strict: true, checks: [{ context: \"Detect Project Board Drift\" }] },\n```",
|
|
8
|
+
"path": "bin/cli.js",
|
|
9
|
+
"line": 165
|
|
10
|
+
}
|
|
11
|
+
{
|
|
12
|
+
"body": "\n\nParsing `updateOutput` directly can throw a `SyntaxError` if the command fails or returns an empty/invalid response. Although it's inside a `try...catch` block, checking if the output is truthy before parsing and using optional chaining provides better resilience and prevents the script from crashing on unexpected API responses.\n\n```suggestion\n const jsonResponse = updateOutput ? JSON.parse(updateOutput) : null;\n const returnedOptions = jsonResponse?.data?.updateProjectV2SingleSelectField?.projectV2SingleSelectField?.options || [];\n```",
|
|
13
|
+
"path": "bin/cli.js",
|
|
14
|
+
"line": 248
|
|
15
|
+
}
|
|
16
|
+
{
|
|
17
|
+
"body": "\n\nTypo in Spanish translation: \"arquivo\" is Portuguese. In Spanish, it should be \"archivo\".\n\n```suggestion\n console.log(`${cyan}>>> Español: \"Lea el archivo .agentic-setup.md e inicie el Setup Mode\"${reset}`);\n```",
|
|
18
|
+
"path": "bin/cli.js",
|
|
19
|
+
"line": 314
|
|
20
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Add to Board on Open
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types: [opened]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
PROJECT_ID: "{{PROJECT_ID}}"
|
|
9
|
+
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
10
|
+
STATUS_IDEA: "{{ID_IDEA}}"
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
add-to-board:
|
|
14
|
+
name: Auto-add new issue to board
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
env:
|
|
17
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
18
|
+
steps:
|
|
19
|
+
- name: Add issue to project board
|
|
20
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
21
|
+
uses: actions/github-script@v7
|
|
22
|
+
with:
|
|
23
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
24
|
+
script: |
|
|
25
|
+
const nodeId = context.payload.issue.node_id;
|
|
26
|
+
const number = context.payload.issue.number;
|
|
27
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
28
|
+
mutation($p: ID!, $c: ID!) {
|
|
29
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
30
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
31
|
+
await github.graphql(`
|
|
32
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
33
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
34
|
+
projectV2Item { id }
|
|
35
|
+
}
|
|
36
|
+
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
37
|
+
v: { singleSelectOptionId: process.env.STATUS_IDEA } });
|
|
38
|
+
console.log(`Issue #${number} added to board → Idea`);
|
|
@@ -16,16 +16,20 @@ jobs:
|
|
|
16
16
|
issues: write
|
|
17
17
|
pull-requests: write
|
|
18
18
|
contents: read
|
|
19
|
+
env:
|
|
20
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
21
|
+
PROJECT_ID: "{{PROJECT_ID}}"
|
|
22
|
+
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
23
|
+
STATUS_DEVELOPMENT: "{{ID_DEVELOPMENT}}"
|
|
19
24
|
steps:
|
|
20
25
|
- name: Update Labels
|
|
21
|
-
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
22
26
|
uses: actions/github-script@v7
|
|
23
27
|
with:
|
|
24
28
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
25
29
|
script: |
|
|
26
30
|
const { owner, repo } = context.repo;
|
|
27
31
|
const issue_number = context.payload.issue.number;
|
|
28
|
-
|
|
32
|
+
|
|
29
33
|
try {
|
|
30
34
|
await github.rest.issues.removeLabel({
|
|
31
35
|
owner,
|
|
@@ -36,14 +40,40 @@ jobs:
|
|
|
36
40
|
} catch (error) {
|
|
37
41
|
console.log('Label stage:approval not found or could not be removed');
|
|
38
42
|
}
|
|
39
|
-
|
|
43
|
+
|
|
44
|
+
const agentLabel = '{{IMPLEMENTATION_AGENT_LABEL}}';
|
|
45
|
+
const labelsToAdd = ['stage:development'];
|
|
46
|
+
if (!agentLabel.includes('{{')) labelsToAdd.push(agentLabel, 'agent:working');
|
|
47
|
+
|
|
40
48
|
await github.rest.issues.addLabels({
|
|
41
49
|
owner,
|
|
42
50
|
repo,
|
|
43
51
|
issue_number,
|
|
44
|
-
labels:
|
|
52
|
+
labels: labelsToAdd
|
|
45
53
|
});
|
|
46
54
|
|
|
55
|
+
- name: Move board card to Development
|
|
56
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
57
|
+
continue-on-error: true
|
|
58
|
+
uses: actions/github-script@v7
|
|
59
|
+
with:
|
|
60
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
61
|
+
script: |
|
|
62
|
+
const nodeId = context.payload.issue.node_id;
|
|
63
|
+
const number = context.payload.issue.number;
|
|
64
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
65
|
+
mutation($p: ID!, $c: ID!) {
|
|
66
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
67
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
68
|
+
await github.graphql(`
|
|
69
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
70
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
71
|
+
projectV2Item { id }
|
|
72
|
+
}
|
|
73
|
+
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
74
|
+
v: { singleSelectOptionId: process.env.STATUS_DEVELOPMENT } });
|
|
75
|
+
console.log(`Issue #${number} → Development`);
|
|
76
|
+
|
|
47
77
|
- name: Comment on issue to trigger agent and prevent race conditions
|
|
48
78
|
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
49
79
|
uses: actions/github-script@v7
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: PDLC Stage Gate
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
pull-requests: read
|
|
8
|
+
issues: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
stage-gate:
|
|
12
|
+
name: PDLC Stage Gate
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check stage:approval
|
|
16
|
+
env:
|
|
17
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
18
|
+
run: |
|
|
19
|
+
set -e
|
|
20
|
+
REPO="${{ github.repository }}"
|
|
21
|
+
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
22
|
+
|
|
23
|
+
PR_LABELS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '[.labels[].name] | join(" ")')
|
|
24
|
+
if echo "$PR_LABELS" | grep -qw "hotfix"; then
|
|
25
|
+
echo "✅ Hotfix label — stage gate bypassed."
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
PR_BODY=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json body --jq '.body // ""')
|
|
30
|
+
ISSUE_NUMS=$(echo "$PR_BODY" | grep -oiE '(Closes?|Fixes?|Resolves?)\s+#([0-9]+)' | grep -oE '[0-9]+' || true)
|
|
31
|
+
|
|
32
|
+
if [ -z "$ISSUE_NUMS" ]; then
|
|
33
|
+
echo "❌ No linked issues in PR body."
|
|
34
|
+
echo " Add 'Closes #N' to PR body, or add 'hotfix' label to PR for emergencies."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
for NUM in $ISSUE_NUMS; do
|
|
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
|
|
41
|
+
echo "✅ Issue #$NUM approved"
|
|
42
|
+
else
|
|
43
|
+
STAGE=$(echo "$LABELS" | tr ' ' '\n' | grep "^stage:" | head -1 || echo "none")
|
|
44
|
+
echo "❌ Issue #$NUM missing approval (current: $STAGE)"
|
|
45
|
+
echo " Required: stage:approval OR spec:approved OR stage:development label on the issue."
|
|
46
|
+
echo " Emergency bypass: add 'hotfix' label to this PR."
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
echo "✅ All linked issues approved."
|