create-agentic-pdlc 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/assets/agentic-pdlc-flow.svg +255 -0
- package/.github/workflows/agent-trigger.yml +40 -1
- package/.github/workflows/ci.yml +3 -0
- package/.github/workflows/pdlc-health-check.yml +123 -0
- package/.github/workflows/project-automation.yml +36 -55
- package/AGENTS.md +3 -1
- package/README.md +12 -8
- package/SETUP.md +41 -2
- package/adapters/claude-code/skill.md +24 -3
- package/docs/flow.md +160 -0
- package/docs/pdlc.md +17 -13
- package/docs/spikes/04-upstream-flow-gap-analysis.md +39 -0
- package/package.json +1 -1
- package/templates/.github/CODEOWNERS +5 -0
- package/templates/.github/workflows/agent-trigger.yml +40 -1
- package/templates/.github/workflows/ci.yml +3 -0
- package/templates/.github/workflows/pdlc-health-check.yml +123 -0
- package/templates/.github/workflows/project-automation.yml +36 -55
- package/templates/.github/workflows/upstream-gate.yml +54 -0
- package/templates/AGENTS.md +28 -1
- package/templates/docs/pdlc.md +17 -13
|
@@ -5,8 +5,8 @@ on:
|
|
|
5
5
|
types: [opened, reopened, closed]
|
|
6
6
|
pull_request_review:
|
|
7
7
|
types: [submitted]
|
|
8
|
-
|
|
9
|
-
types: [
|
|
8
|
+
issues:
|
|
9
|
+
types: [labeled]
|
|
10
10
|
|
|
11
11
|
env:
|
|
12
12
|
PROJECT_ID: "{{PROJECT_ID}}"
|
|
@@ -15,45 +15,52 @@ env:
|
|
|
15
15
|
STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
|
|
16
16
|
STATUS_DETAILING: "{{ID_DETAILING}}"
|
|
17
17
|
STATUS_APPROVAL: "{{ID_APPROVAL}}"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
STATUS_DEVELOPMENT: "{{ID_DEVELOPMENT}}"
|
|
19
|
+
STATUS_TESTING: "{{ID_TESTING}}"
|
|
20
|
+
STATUS_CODE_REVIEW_PR: "{{ID_CODE_REVIEW_PR}}"
|
|
21
|
+
STATUS_PRODUCTION: "{{ID_PRODUCTION}}"
|
|
21
22
|
|
|
22
23
|
jobs:
|
|
23
|
-
# Issue
|
|
24
|
-
move-card-on-
|
|
25
|
-
name: Upstream
|
|
26
|
-
if: github.event_name == '
|
|
24
|
+
# Issue Labeled → Move Upstream
|
|
25
|
+
move-card-on-label:
|
|
26
|
+
name: Upstream Label → Move Card
|
|
27
|
+
if: github.event_name == 'issues' && github.event.action == 'labeled'
|
|
27
28
|
runs-on: ubuntu-latest
|
|
28
29
|
env:
|
|
29
30
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
30
31
|
steps:
|
|
31
|
-
- name: Detect
|
|
32
|
+
- name: Detect Label and Move Issue
|
|
32
33
|
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
33
34
|
uses: actions/github-script@v7
|
|
34
35
|
with:
|
|
35
36
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
36
37
|
script: |
|
|
37
|
-
const
|
|
38
|
+
const labelName = context.payload.label.name;
|
|
38
39
|
let targetStatusId = null;
|
|
39
40
|
let stageName = null;
|
|
40
41
|
|
|
41
|
-
if (
|
|
42
|
+
if (labelName === 'upstream:exploration') {
|
|
42
43
|
targetStatusId = process.env.STATUS_EXPLORATION;
|
|
43
44
|
stageName = 'Exploration';
|
|
44
|
-
} else if (
|
|
45
|
+
} else if (labelName === 'upstream:brainstorming') {
|
|
45
46
|
targetStatusId = process.env.STATUS_BRAINSTORMING;
|
|
46
47
|
stageName = 'Brainstorming';
|
|
47
|
-
} else if (
|
|
48
|
+
} else if (labelName === 'upstream:detailing') {
|
|
48
49
|
targetStatusId = process.env.STATUS_DETAILING;
|
|
49
50
|
stageName = 'Detailing';
|
|
50
|
-
} else if (
|
|
51
|
+
} else if (labelName === 'upstream:approval') {
|
|
51
52
|
targetStatusId = process.env.STATUS_APPROVAL;
|
|
52
53
|
stageName = 'Approval';
|
|
54
|
+
} else if (labelName === 'upstream:development') {
|
|
55
|
+
targetStatusId = process.env.STATUS_DEVELOPMENT;
|
|
56
|
+
stageName = 'Development';
|
|
57
|
+
} else if (labelName === 'upstream:testing') {
|
|
58
|
+
targetStatusId = process.env.STATUS_TESTING;
|
|
59
|
+
stageName = 'Testing';
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
if (!targetStatusId) {
|
|
56
|
-
console.log('No upstream PDLC
|
|
63
|
+
console.log('No upstream PDLC label found. Skipping.');
|
|
57
64
|
return;
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -79,16 +86,17 @@ jobs:
|
|
|
79
86
|
await moveItem(node_id, targetStatusId);
|
|
80
87
|
console.log(`Issue #${number} moved to ${stageName}`);
|
|
81
88
|
|
|
89
|
+
{{OPTIONAL_ARCHITECTURE_VIOLATION_JOB}}
|
|
82
90
|
|
|
83
|
-
# PR Opened → Move linked issue to Code Review
|
|
91
|
+
# PR Opened → Move linked issue to Code Review / PR
|
|
84
92
|
move-card-on-pr-open:
|
|
85
|
-
name: Open PR → Code Review
|
|
93
|
+
name: Open PR → Code Review / PR
|
|
86
94
|
if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
|
87
95
|
runs-on: ubuntu-latest
|
|
88
96
|
env:
|
|
89
97
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
90
98
|
steps:
|
|
91
|
-
- name: Move linked issue to Code Review
|
|
99
|
+
- name: Move linked issue to Code Review / PR
|
|
92
100
|
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
93
101
|
uses: actions/github-script@v7
|
|
94
102
|
with:
|
|
@@ -117,7 +125,7 @@ jobs:
|
|
|
117
125
|
}
|
|
118
126
|
}`, {
|
|
119
127
|
p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
120
|
-
v: { singleSelectOptionId: process.env.
|
|
128
|
+
v: { singleSelectOptionId: process.env.STATUS_CODE_REVIEW_PR }
|
|
121
129
|
});
|
|
122
130
|
};
|
|
123
131
|
|
|
@@ -125,24 +133,24 @@ jobs:
|
|
|
125
133
|
for (const n of linkedIssues) {
|
|
126
134
|
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
127
135
|
await moveItem(issue.node_id);
|
|
128
|
-
console.log(`Issue #${n} → Code Review`);
|
|
136
|
+
console.log(`Issue #${n} → Code Review / PR`);
|
|
129
137
|
}
|
|
130
138
|
} else {
|
|
131
139
|
await moveItem(pr.node_id);
|
|
132
|
-
console.log(`PR #${prNumber} → Code Review (no linked issue)`);
|
|
140
|
+
console.log(`PR #${prNumber} → Code Review / PR (no linked issue)`);
|
|
133
141
|
}
|
|
134
142
|
|
|
135
|
-
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:review'] }).catch(() => {});
|
|
143
|
+
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:in-review'] }).catch(() => {});
|
|
136
144
|
|
|
137
|
-
# Review Approved →
|
|
145
|
+
# Review Approved → Add Label
|
|
138
146
|
move-card-on-review-approved:
|
|
139
|
-
name: Approved PR →
|
|
147
|
+
name: Approved PR → Add Label
|
|
140
148
|
if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved'
|
|
141
149
|
runs-on: ubuntu-latest
|
|
142
150
|
env:
|
|
143
151
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
144
152
|
steps:
|
|
145
|
-
- name:
|
|
153
|
+
- name: Swap PR labels
|
|
146
154
|
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
147
155
|
uses: actions/github-script@v7
|
|
148
156
|
with:
|
|
@@ -150,34 +158,7 @@ jobs:
|
|
|
150
158
|
script: |
|
|
151
159
|
const prNumber = context.payload.pull_request.number;
|
|
152
160
|
const { owner, repo } = context.repo;
|
|
153
|
-
|
|
154
|
-
const body = pr.body ?? '';
|
|
155
|
-
const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)].map(m => parseInt(m[1]));
|
|
156
|
-
|
|
157
|
-
const moveItem = async (nodeId) => {
|
|
158
|
-
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
159
|
-
mutation($p: ID!, $c: ID!) {
|
|
160
|
-
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
161
|
-
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
162
|
-
await github.graphql(`
|
|
163
|
-
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
164
|
-
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
165
|
-
projectV2Item { id }
|
|
166
|
-
}
|
|
167
|
-
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
168
|
-
v: { singleSelectOptionId: process.env.STATUS_PULL_REQUEST } });
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
if (linkedIssues.length > 0) {
|
|
172
|
-
for (const n of linkedIssues) {
|
|
173
|
-
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
174
|
-
await moveItem(issue.node_id);
|
|
175
|
-
}
|
|
176
|
-
} else {
|
|
177
|
-
await moveItem(pr.node_id);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
try { await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'pr:review' }); } catch {}
|
|
161
|
+
try { await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'pr:in-review' }); } catch {}
|
|
181
162
|
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:approved'] }).catch(() => {});
|
|
182
163
|
|
|
183
164
|
# PR Merged → Production
|
|
@@ -211,7 +192,7 @@ jobs:
|
|
|
211
192
|
projectV2Item { id }
|
|
212
193
|
}
|
|
213
194
|
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
214
|
-
v: { singleSelectOptionId: process.env.
|
|
195
|
+
v: { singleSelectOptionId: process.env.STATUS_PRODUCTION } });
|
|
215
196
|
};
|
|
216
197
|
|
|
217
198
|
if (linkedIssues.length > 0) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Upstream Gate 1
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
evaluate-gate:
|
|
9
|
+
name: Evaluate Brainstorming Approval
|
|
10
|
+
if: ${{ !github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'upstream:brainstorming') }}
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
env:
|
|
13
|
+
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check for approval and swap label
|
|
16
|
+
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
17
|
+
uses: actions/github-script@v7
|
|
18
|
+
with:
|
|
19
|
+
github-token: ${{ env.PROJECT_TOKEN }}
|
|
20
|
+
script: |
|
|
21
|
+
const comment = context.payload.comment.body.toLowerCase();
|
|
22
|
+
const isApproved =
|
|
23
|
+
comment.includes('approved') ||
|
|
24
|
+
comment.includes('lgtm') ||
|
|
25
|
+
/option\s+[a-z0-9]/i.test(comment) ||
|
|
26
|
+
/go\s+with\s+[a-z0-9]/i.test(comment) ||
|
|
27
|
+
/proceed/i.test(comment);
|
|
28
|
+
|
|
29
|
+
if (isApproved) {
|
|
30
|
+
const { owner, repo } = context.repo;
|
|
31
|
+
const issue_number = context.payload.issue.number;
|
|
32
|
+
|
|
33
|
+
console.log('Approval detected, swapping labels...');
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await github.rest.issues.removeLabel({
|
|
37
|
+
owner,
|
|
38
|
+
repo,
|
|
39
|
+
issue_number,
|
|
40
|
+
name: 'upstream:brainstorming'
|
|
41
|
+
});
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.log('Could not remove upstream:brainstorming label');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await github.rest.issues.addLabels({
|
|
47
|
+
owner,
|
|
48
|
+
repo,
|
|
49
|
+
issue_number,
|
|
50
|
+
labels: ['upstream:detailing']
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
console.log('No approval pattern detected in the comment.');
|
|
54
|
+
}
|
package/templates/AGENTS.md
CHANGED
|
@@ -35,7 +35,33 @@ Always start from the current `main` HEAD. Never work over stale snapshots.
|
|
|
35
35
|
3. Read all files mentioned in the issue's technical context.
|
|
36
36
|
4. Implement the **minimum viable change** that satisfies the ACs — do not refactor beyond scope.
|
|
37
37
|
5. Run tests: `{{TEST_COMMAND}}`
|
|
38
|
-
6.
|
|
38
|
+
6. Run typecheck (if applicable): `{{TYPECHECK_COMMAND}}`
|
|
39
|
+
7. Create a Pull Request with `Closes #N` in the body — automation moves the board.
|
|
40
|
+
|
|
41
|
+
### Spec format (Upstream Agents)
|
|
42
|
+
|
|
43
|
+
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:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
**As** [user],
|
|
47
|
+
**I want** [action],
|
|
48
|
+
**so that** [benefit].
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Acceptance Criteria
|
|
53
|
+
|
|
54
|
+
**AC1 — ...**
|
|
55
|
+
- Given ...
|
|
56
|
+
- When ...
|
|
57
|
+
- Then ...
|
|
58
|
+
|
|
59
|
+
**AC2 — ...**
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
## Files to modify
|
|
63
|
+
- `path/to/file.ts` — what changes
|
|
64
|
+
```
|
|
39
65
|
|
|
40
66
|
## What NOT to do
|
|
41
67
|
|
|
@@ -49,5 +75,6 @@ Always start from the current `main` HEAD. Never work over stale snapshots.
|
|
|
49
75
|
|
|
50
76
|
- **Tests:** `{{TEST_COMMAND}}`
|
|
51
77
|
- **Lint/Types:** `{{LINT_COMMAND}}`
|
|
78
|
+
- **Typecheck:** `{{TYPECHECK_COMMAND}}`
|
|
52
79
|
- **Build:** `{{BUILD_COMMAND}}`
|
|
53
80
|
{{EXTRA_PATTERNS}}
|
package/templates/docs/pdlc.md
CHANGED
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
| Column | Meaning | Who moves the card |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| 💡 Idea | Backlog — every new issue lands here |
|
|
8
|
-
| 🔍 Exploration | Claude is analyzing code and context |
|
|
9
|
-
| 🧠 Brainstorming | Claude proposed approaches, awaiting PM gate |
|
|
10
|
-
| 📐 Detail Solution | Claude is writing the technical spec |
|
|
11
|
-
| ✅ Approval | Spec ready, awaiting `spec:approved` label |
|
|
12
|
-
| ⚙️ Development | Agent implementing the spec |
|
|
7
|
+
| 💡 Idea | Backlog — every new issue lands here | Manual |
|
|
8
|
+
| 🔍 Exploration | Claude is analyzing code and context | Label `stage:exploration` |
|
|
9
|
+
| 🧠 Brainstorming | Claude proposed approaches, awaiting PM gate | Label `stage:brainstorming` |
|
|
10
|
+
| 📐 Detail Solution | Claude is writing the technical spec | Label `stage:detailing` |
|
|
11
|
+
| ✅ Approval | Spec ready, awaiting `spec:approved` label | Label `spec:approved` |
|
|
12
|
+
| ⚙️ Development | Agent implementing the spec | Label `stage:development` |
|
|
13
13
|
| 🧪 Testing | CI pipeline running | GitHub Actions |
|
|
14
|
-
| 👁 Code Review | PR opened, awaiting human review | GitHub Actions |
|
|
15
|
-
| 🔀
|
|
14
|
+
| 👁 Code Review / PR | PR opened, awaiting human review | GitHub Actions |
|
|
15
|
+
| 🔀 Merge | PR approved, awaiting merge | GitHub Actions |
|
|
16
16
|
| 🚀 Production | Merged | GitHub Actions |
|
|
17
17
|
|
|
18
18
|
<!--
|
|
19
19
|
Adapt columns as needed. The functional baseline is:
|
|
20
|
-
💡 Idea → ⚙️ Development → 👁 Code Review → 🚀 Production
|
|
20
|
+
💡 Idea → ⚙️ Development → 👁 Code Review / PR → 🚀 Production
|
|
21
21
|
-->
|
|
22
22
|
|
|
23
23
|
## Board Identifiers (GitHub Projects)
|
|
@@ -39,8 +39,8 @@ REPO = {{REPO_OWNER}}/{{REPO_NAME}}
|
|
|
39
39
|
| ✅ Approval | `{{ID_APPROVAL}}` |
|
|
40
40
|
| ⚙️ Development | `{{ID_DEVELOPMENT}}` |
|
|
41
41
|
| 🧪 Testing | `{{ID_TESTING}}` |
|
|
42
|
-
| 👁 Code Review | `{{
|
|
43
|
-
| 🔀
|
|
42
|
+
| 👁 Code Review / PR | `{{ID_CODE_REVIEW_PR}}` |
|
|
43
|
+
| 🔀 Merge | `{{ID_MERGE}}` |
|
|
44
44
|
| 🚀 Production | `{{ID_PRODUCTION}}` |
|
|
45
45
|
|
|
46
46
|
## Agent × Phase Mapping
|
|
@@ -49,7 +49,7 @@ REPO = {{REPO_OWNER}}/{{REPO_NAME}}
|
|
|
49
49
|
|---|---|
|
|
50
50
|
| 💡 → 📐 (upstream) | Claude (or ideation agent) in conversational session |
|
|
51
51
|
| ⚙️ → 🔀 (downstream) | {{IMPLEMENTATION_AGENT}} (e.g. Jules `@google-labs-jules`) |
|
|
52
|
-
| 👁 Code Review | Human (you) |
|
|
52
|
+
| 👁 Code Review / PR | Human (you) |
|
|
53
53
|
| Automatic transitions | GitHub Actions |
|
|
54
54
|
|
|
55
55
|
## Issue Title Conventions
|
|
@@ -67,8 +67,12 @@ REPO = {{REPO_OWNER}}/{{REPO_NAME}}
|
|
|
67
67
|
|
|
68
68
|
| Label | Entity | Color | Meaning |
|
|
69
69
|
|---|---|---|---|
|
|
70
|
+
| `stage:exploration` | Issue | Purple | Issue is being evaluated |
|
|
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 |
|
|
70
74
|
| `spec:approved` | Issue | Green | Gate 2 — agent is cleared to implement |
|
|
71
|
-
| `pr:review` | PR | Yellow | Awaiting code review |
|
|
75
|
+
| `pr:in-review` | PR | Yellow | Awaiting code review |
|
|
72
76
|
| `pr:approved` | PR | Green | Code review approved |
|
|
73
77
|
|
|
74
78
|
## Approval Gates
|