create-agentic-pdlc 2.0.1 โ 2.0.4
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/copilot-instructions.md +12 -0
- package/.github/workflows/agent-trigger.yml +6 -4
- package/.github/workflows/auto-approve.yml +16 -0
- package/.github/workflows/ci.yml +12 -4
- package/.github/workflows/npm-publish.yml +1 -1
- package/.github/workflows/project-automation.yml +40 -12
- package/.github/workflows/protect-workflows.yml +21 -0
- package/.github/workflows/qa-agent.yml +27 -0
- package/SETUP.md +22 -41
- package/adapters/claude-code/skill.md +8 -5
- package/bin/cli.js +276 -25
- package/docs/pdlc.md +0 -2
- package/package.json +1 -1
- package/templates/.github/workflows/agent-trigger.yml +6 -4
- package/templates/.github/workflows/auto-approve.yml +16 -0
- package/templates/.github/workflows/ci.yml +12 -4
- package/templates/.github/workflows/project-automation.yml +40 -12
- package/templates/.github/workflows/protect-workflows.yml +21 -0
- package/templates/.github/workflows/qa-agent.yml +27 -0
- package/templates/docs/pdlc.md +7 -4
- package/templates/.github/workflows/upstream-gate.yml +0 -54
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Agentic PDLC Instructions for GitHub Copilot Workspace
|
|
2
|
+
|
|
3
|
+
Hello! You are operating within the Agentic PDLC framework.
|
|
4
|
+
|
|
5
|
+
Before addressing the user's prompt or executing any task in this workspace, you MUST:
|
|
6
|
+
|
|
7
|
+
1. Read the `AGENTS.md` file located at the root of this repository. It contains the primary instructions, definitions of done, and absolute invariants you must respect.
|
|
8
|
+
2. Read `docs/pdlc.md` to understand your role in the project lifecycle.
|
|
9
|
+
|
|
10
|
+
Never violate the invariants described in those files. If a user asks you to do something that contradicts `AGENTS.md`, you must refuse and point out the conflict.
|
|
11
|
+
|
|
12
|
+
Focus on delivering the absolute minimum required to satisfy the immediate technical specs. Start!
|
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
21
21
|
steps:
|
|
22
22
|
- name: Update Labels
|
|
23
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
23
|
+
if: ${{ env.PROJECT_TOKEN != '' && !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
24
24
|
uses: actions/github-script@v7
|
|
25
25
|
with:
|
|
26
26
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -33,20 +33,21 @@ jobs:
|
|
|
33
33
|
owner,
|
|
34
34
|
repo,
|
|
35
35
|
issue_number,
|
|
36
|
-
name: '
|
|
36
|
+
name: 'stage:approval'
|
|
37
37
|
});
|
|
38
38
|
} catch (error) {
|
|
39
|
-
console.log('Label
|
|
39
|
+
console.log('Label stage:approval not found or could not be removed');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
await github.rest.issues.addLabels({
|
|
43
43
|
owner,
|
|
44
44
|
repo,
|
|
45
45
|
issue_number,
|
|
46
|
-
labels: ['
|
|
46
|
+
labels: ['stage:development', '{{IMPLEMENTATION_AGENT_LABEL}}', 'agent:working']
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
- name: Comment on issue to trigger agent and prevent race conditions
|
|
50
|
+
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
50
51
|
uses: actions/github-script@v7
|
|
51
52
|
with:
|
|
52
53
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -89,6 +90,7 @@ jobs:
|
|
|
89
90
|
contents: read
|
|
90
91
|
steps:
|
|
91
92
|
- name: Comment on issue to trigger agent
|
|
93
|
+
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
92
94
|
uses: actions/github-script@v7
|
|
93
95
|
with:
|
|
94
96
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: Auto Approve PRs
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, labeled, synchronize]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
pull-requests: write
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
auto-approve:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
if: contains(github.event.pull_request.labels.*.name, 'auto-approve')
|
|
13
|
+
steps:
|
|
14
|
+
- uses: hmarr/auto-approve-action@v4
|
|
15
|
+
with:
|
|
16
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -20,13 +20,21 @@ jobs:
|
|
|
20
20
|
run: echo "Replace this with your package manager install command"
|
|
21
21
|
|
|
22
22
|
- name: Run Linters
|
|
23
|
-
|
|
23
|
+
if: ${{ !contains('{{LINT_COMMAND}}', '{{') }}
|
|
24
|
+
run: |
|
|
25
|
+
{{LINT_COMMAND}}
|
|
24
26
|
|
|
25
27
|
- name: Typecheck
|
|
26
|
-
|
|
28
|
+
if: ${{ !contains('{{TYPECHECK_COMMAND}}', '{{') }}
|
|
29
|
+
run: |
|
|
30
|
+
{{TYPECHECK_COMMAND}}
|
|
27
31
|
|
|
28
32
|
- name: Build
|
|
29
|
-
|
|
33
|
+
if: ${{ !contains('{{BUILD_COMMAND}}', '{{') }}
|
|
34
|
+
run: |
|
|
35
|
+
{{BUILD_COMMAND}}
|
|
30
36
|
|
|
31
37
|
- name: Run Tests
|
|
32
|
-
|
|
38
|
+
if: ${{ !contains('{{TEST_COMMAND}}', '{{') }}
|
|
39
|
+
run: |
|
|
40
|
+
{{TEST_COMMAND}}
|
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
# Strip 'v' from tag (e.g., v1.0.2 -> 1.0.2)
|
|
28
28
|
VERSION=${GITHUB_REF_NAME#v}
|
|
29
29
|
echo "Setting package version to $VERSION"
|
|
30
|
-
npm version $VERSION --no-git-tag-version
|
|
30
|
+
npm version $VERSION --no-git-tag-version --allow-same-version
|
|
31
31
|
|
|
32
32
|
- name: Install Dependencies
|
|
33
33
|
run: npm install
|
|
@@ -11,6 +11,7 @@ on:
|
|
|
11
11
|
env:
|
|
12
12
|
PROJECT_ID: "{{PROJECT_ID}}"
|
|
13
13
|
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
14
|
+
STATUS_IDEA: "{{ID_IDEA}}"
|
|
14
15
|
STATUS_EXPLORATION: "{{ID_EXPLORATION}}"
|
|
15
16
|
STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
|
|
16
17
|
STATUS_DETAILING: "{{ID_DETAILING}}"
|
|
@@ -30,7 +31,7 @@ jobs:
|
|
|
30
31
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
31
32
|
steps:
|
|
32
33
|
- name: Detect Label and Move Issue
|
|
33
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
34
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
34
35
|
uses: actions/github-script@v7
|
|
35
36
|
with:
|
|
36
37
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -39,28 +40,28 @@ jobs:
|
|
|
39
40
|
let targetStatusId = null;
|
|
40
41
|
let stageName = null;
|
|
41
42
|
|
|
42
|
-
if (labelName === '
|
|
43
|
+
if (labelName === 'stage:exploration') {
|
|
43
44
|
targetStatusId = process.env.STATUS_EXPLORATION;
|
|
44
45
|
stageName = 'Exploration';
|
|
45
|
-
} else if (labelName === '
|
|
46
|
+
} else if (labelName === 'stage:brainstorming') {
|
|
46
47
|
targetStatusId = process.env.STATUS_BRAINSTORMING;
|
|
47
48
|
stageName = 'Brainstorming';
|
|
48
|
-
} else if (labelName === '
|
|
49
|
+
} else if (labelName === 'stage:detailing') {
|
|
49
50
|
targetStatusId = process.env.STATUS_DETAILING;
|
|
50
51
|
stageName = 'Detailing';
|
|
51
|
-
} else if (labelName === '
|
|
52
|
+
} else if (labelName === 'stage:approval') {
|
|
52
53
|
targetStatusId = process.env.STATUS_APPROVAL;
|
|
53
54
|
stageName = 'Approval';
|
|
54
|
-
} else if (labelName === '
|
|
55
|
+
} else if (labelName === 'stage:development') {
|
|
55
56
|
targetStatusId = process.env.STATUS_DEVELOPMENT;
|
|
56
57
|
stageName = 'Development';
|
|
57
|
-
} else if (labelName === '
|
|
58
|
+
} else if (labelName === 'stage:testing') {
|
|
58
59
|
targetStatusId = process.env.STATUS_TESTING;
|
|
59
60
|
stageName = 'Testing';
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
if (!targetStatusId) {
|
|
63
|
-
console.log('No
|
|
64
|
+
console.log('No stage PDLC label found. Skipping.');
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -86,9 +87,36 @@ jobs:
|
|
|
86
87
|
await moveItem(node_id, targetStatusId);
|
|
87
88
|
console.log(`Issue #${number} moved to ${stageName}`);
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
# OPTIONAL: Uncomment to enable architecture-violation โ Idea
|
|
91
|
+
# move-violation-to-board:
|
|
92
|
+
# name: architecture-violation โ ๐ก Idea
|
|
93
|
+
# if: github.event_name == 'issues' && github.event.action == 'labeled' && github.event.label.name == 'architecture-violation'
|
|
94
|
+
# runs-on: ubuntu-latest
|
|
95
|
+
# steps:
|
|
96
|
+
# - name: Move issue to Idea
|
|
97
|
+
# if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
98
|
+
# uses: actions/github-script@v7
|
|
99
|
+
# with:
|
|
100
|
+
# github-token: ${{ env.PROJECT_TOKEN }}
|
|
101
|
+
# script: |
|
|
102
|
+
# const { issue: { number, node_id } } = context.payload;
|
|
103
|
+
# const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
104
|
+
# mutation($p: ID!, $c: ID!) {
|
|
105
|
+
# addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
106
|
+
# }`, { p: process.env.PROJECT_ID, c: node_id });
|
|
107
|
+
# await github.graphql(`
|
|
108
|
+
# mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
109
|
+
# updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
110
|
+
# projectV2Item { id }
|
|
111
|
+
# }
|
|
112
|
+
# }`, {
|
|
113
|
+
# p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
114
|
+
# v: { singleSelectOptionId: process.env.STATUS_IDEA }
|
|
115
|
+
# });
|
|
116
|
+
# console.log(`Issue #${number} moved to Idea`);
|
|
90
117
|
|
|
91
118
|
# PR Opened โ Move linked issue to Code Review / PR
|
|
119
|
+
# ๐ก VARIANT B (QA Agent): If using an AI QA agent, change `STATUS_CODE_REVIEW_PR` to `STATUS_TESTING` on line ~158
|
|
92
120
|
move-card-on-pr-open:
|
|
93
121
|
name: Open PR โ Code Review / PR
|
|
94
122
|
if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
|
@@ -97,7 +125,7 @@ jobs:
|
|
|
97
125
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
98
126
|
steps:
|
|
99
127
|
- name: Move linked issue to Code Review / PR
|
|
100
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
128
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
101
129
|
uses: actions/github-script@v7
|
|
102
130
|
with:
|
|
103
131
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -151,7 +179,7 @@ jobs:
|
|
|
151
179
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
152
180
|
steps:
|
|
153
181
|
- name: Swap PR labels
|
|
154
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
182
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
155
183
|
uses: actions/github-script@v7
|
|
156
184
|
with:
|
|
157
185
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -170,7 +198,7 @@ jobs:
|
|
|
170
198
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
171
199
|
steps:
|
|
172
200
|
- name: Move issue to Production
|
|
173
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
201
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
174
202
|
uses: actions/github-script@v7
|
|
175
203
|
with:
|
|
176
204
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Protect Workflows
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened, labeled]
|
|
6
|
+
paths:
|
|
7
|
+
- '.github/**'
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
block-unauthorized-edits:
|
|
11
|
+
name: Protect .github directory
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Check for Human Approval
|
|
15
|
+
if: ${{ !contains(github.event.pull_request.labels.*.name, 'human-approved') }}
|
|
16
|
+
run: |
|
|
17
|
+
echo "๐จ Modifications in the .github/ directory detected."
|
|
18
|
+
echo "As a security measure, this repository blocks workflow edits by default."
|
|
19
|
+
echo "If you are an AI Agent, do not add the approval label yourself."
|
|
20
|
+
echo "If you are the human owner, please add the 'human-approved' label to this PR to allow merging."
|
|
21
|
+
exit 1
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: AI QA Agent
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, synchronize, reopened]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
pull-requests: write
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
qa:
|
|
12
|
+
name: Run AI QA Agent
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- name: Execute QA Tests
|
|
17
|
+
run: |
|
|
18
|
+
echo "Run your QA Agent here."
|
|
19
|
+
echo "This could be QAWolf, a custom LLM script, or a secondary agent."
|
|
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"
|
|
25
|
+
env:
|
|
26
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
package/SETUP.md
CHANGED
|
@@ -114,57 +114,38 @@ git push
|
|
|
114
114
|
|
|
115
115
|
---
|
|
116
116
|
|
|
117
|
-
## Step 7 โ
|
|
117
|
+
## Step 7 โ Ignite the Backlog
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
Forget heavy templates and CLI commands. Just drop your raw idea and let the AI do the heavy lifting:
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
1. Open your **GitHub Project** board.
|
|
122
|
+
2. Under the **Idea** column, click **`+ Add item`**.
|
|
123
|
+
3. Type your feature title and hit **Enter**.
|
|
124
|
+
4. Select **Blank issue**.
|
|
125
|
+
5. In the right sidebar, add a brief description of what you want.
|
|
126
|
+
6. Ask your AI Assistant to read the issue and write the full specification!
|
|
127
|
+
|
|
128
|
+
Your AI will handle the refinement and automatically move it to the `Specification` stage.
|
|
128
129
|
|
|
129
130
|
---
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
## (Optional) Architecture Violation Integration
|
|
133
|
+
|
|
134
|
+
If your project uses an automated architecture auditing tool (e.g., a CI job that creates issues with an `architecture-violation` label), you can easily integrate it with your project board.
|
|
133
135
|
|
|
134
|
-
|
|
136
|
+
Simply open `.github/workflows/project-automation.yml` and uncomment the `move-violation-to-board` job in the `jobs` section. This will automatically move any issues labeled with `architecture-violation` to the `Idea` column on your board.
|
|
135
137
|
|
|
136
138
|
---
|
|
137
139
|
|
|
138
|
-
## (Optional)
|
|
140
|
+
## (Optional) AI QA Agent Integration (Variant B)
|
|
139
141
|
|
|
140
|
-
If
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if: github.event_name == 'issues' && github.event.label.name == 'architecture-violation'
|
|
148
|
-
runs-on: ubuntu-latest
|
|
149
|
-
steps:
|
|
150
|
-
- uses: actions/github-script@v7
|
|
151
|
-
with:
|
|
152
|
-
github-token: ${{ secrets.PROJECT_TOKEN }}
|
|
153
|
-
script: |
|
|
154
|
-
const issueNodeId = context.payload.issue.node_id;
|
|
155
|
-
const addResult = await github.graphql(`
|
|
156
|
-
mutation($p: ID!, $c: ID!) {
|
|
157
|
-
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
158
|
-
}`, { p: process.env.PROJECT_ID, c: issueNodeId });
|
|
159
|
-
const itemId = addResult.addProjectV2ItemById.item.id;
|
|
160
|
-
await github.graphql(`
|
|
161
|
-
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
162
|
-
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
163
|
-
projectV2Item { id }
|
|
164
|
-
}
|
|
165
|
-
}`, { p: process.env.PROJECT_ID, i: itemId, f: process.env.STATUS_FIELD_ID,
|
|
166
|
-
v: { singleSelectOptionId: process.env.STATUS_IDEA } });
|
|
167
|
-
```
|
|
142
|
+
If you use an AI QA Agent (e.g., QAWolf, a secondary AI script) to test your PRs before Code Review, you can switch to **Variant B**.
|
|
143
|
+
|
|
144
|
+
Simply open `.github/workflows/project-automation.yml`:
|
|
145
|
+
1. In the `move-card-on-pr-open` job, change the PR destination from `STATUS_CODE_REVIEW_PR` to `STATUS_TESTING`.
|
|
146
|
+
2. Uncomment the `move-card-on-qa-pass` job at the bottom of the file to move tested issues to `Code Review / PR` upon receiving a `qa:pass` label.
|
|
147
|
+
|
|
148
|
+
If you don't use this, you can safely delete `templates/.github/workflows/qa-agent.yml`.
|
|
168
149
|
|
|
169
150
|
---
|
|
170
151
|
|
|
@@ -19,18 +19,21 @@ Specifically, check for:
|
|
|
19
19
|
- `.github/workflows/pdlc-health-check.yml`
|
|
20
20
|
|
|
21
21
|
If any of these files are missing, you are in **Setup Mode**. Do not proceed with feature requests until setup is complete.
|
|
22
|
-
1.
|
|
23
|
-
2.
|
|
22
|
+
1. **Language Detection:** Analyze the user's previous prompts and preferred language. Conduct this entire Setup Mode and ask all your interactive questions in that same language.
|
|
23
|
+
2. Acknowledge that the framework is not yet set up.
|
|
24
|
+
3. Interactively ask the user for required values **one group at a time**:
|
|
24
25
|
- **Project basics:** Project Name, Description, Technical Stack (Structure), and GitHub Username (for CODEOWNERS security).
|
|
25
26
|
- **Commands:** Test command, Lint command, Build command.
|
|
26
27
|
- **Invariants:** Critical business rules agents must never violate (e.g. Human-in-the-loop).
|
|
27
|
-
- **Board IDs:** PROJECT_ID, STATUS_FIELD_ID, column option IDs (provide standard PDLC options: Idea, Exploration, Brainstorming, Detail Solution, Approval, Development, Testing, Code Review / PR,
|
|
28
|
-
- **Architecture Violation:** Ask "Does your project use an automated architecture auditing tool (e.g., a CI job that creates issues with an `architecture-violation` label)?". If yes,
|
|
28
|
+
- **Board IDs:** PROJECT_ID, STATUS_FIELD_ID, column option IDs (provide standard PDLC options: Idea, Exploration, Brainstorming, Detail Solution, Approval, Development, Testing, Code Review / PR, Production). Allow user to answer "skip", which means you leave the placeholders intact.
|
|
29
|
+
- **Architecture Violation:** Ask "Does your project use an automated architecture auditing tool (e.g., a CI job that creates issues with an `architecture-violation` label)?". If yes, uncomment the `move-violation-to-board` job inside `project-automation.yml`. If no, ask if they would like help implementing one, reminding them that it significantly improves their agentic development process. If they decline, you can leave the job commented out.
|
|
30
|
+
- **QA Agent (Variant B):** Ask "Do you plan to use an AI QA Agent (e.g. QAWolf or a secondary script) to verify your PRs before Code Review?". If yes, explain you will adopt Variant B: you will change `STATUS_CODE_REVIEW_PR` to `STATUS_TESTING` inside the `move-card-on-pr-open` job in `project-automation.yml` and uncomment the `move-card-on-qa-pass` job. If no, leave the workflow as Variant A (default) and delete the optional `.github/workflows/qa-agent.yml` template.
|
|
29
31
|
- **Implementation agent handle:** e.g., `@google-labs-jules`, or "none".
|
|
30
32
|
3. Generate and write the missing files replacing the `{{SCREAMING_SNAKE_CASE}}` placeholders using the templates logic you know (usually they reside in standard Agentic PDLC templates).
|
|
31
33
|
4. Offer to run the `gh` commands for labels (`spec:approved`, `pr:in-review`, `pr:approved`, `architecture-violation`).
|
|
32
34
|
5. Commit everything with the message: `chore: setup agentic-pdlc framework`.
|
|
33
|
-
6.
|
|
35
|
+
6. **IMPORTANT:** Delete this setup prompt file (e.g., `.agentic-setup.md`, `.agentic-setup-prompt.md`, or `.agentic-pdlc/SETUP_PROMPT.md`) from the root or `.agentic-pdlc/` directory to clean up the workspace.
|
|
36
|
+
7. Conclude Setup Mode.
|
|
34
37
|
|
|
35
38
|
---
|
|
36
39
|
|
package/bin/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const readline = require('readline');
|
|
6
|
+
const { execSync, execFileSync } = require('child_process');
|
|
6
7
|
|
|
7
8
|
// The directory where CLI is executed
|
|
8
9
|
const targetDir = process.cwd();
|
|
@@ -14,13 +15,72 @@ const rl = readline.createInterface({
|
|
|
14
15
|
output: process.stdout
|
|
15
16
|
});
|
|
16
17
|
|
|
18
|
+
function askQuestion(query) {
|
|
19
|
+
return new Promise(resolve => rl.question(query, resolve));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Detect language
|
|
23
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'en-US';
|
|
24
|
+
const isPt = locale.toLowerCase().startsWith('pt');
|
|
25
|
+
const isEs = locale.toLowerCase().startsWith('es');
|
|
26
|
+
|
|
27
|
+
function t(en, pt, es) {
|
|
28
|
+
if (isPt) return pt;
|
|
29
|
+
if (isEs) return es || en; // Fallback para inglรชs se faltar espanhol
|
|
30
|
+
return en;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const i18n = {
|
|
34
|
+
welcome: t('๐ค Welcome to Agentic PDLC Boilerplate Bootstrap! ๐ค', '๐ค Bem-vindo ao Agentic PDLC Boilerplate Bootstrap! ๐ค', '๐ค ยกBienvenido a Agentic PDLC Boilerplate Bootstrap! ๐ค'),
|
|
35
|
+
checking_gh: t('Checking GitHub CLI (gh) installation...', 'Verificando instalaรงรฃo do GitHub CLI (gh)...', 'Verificando la instalaciรณn de GitHub CLI (gh)...'),
|
|
36
|
+
gh_ok: t('โ
GitHub CLI is authenticated.', 'โ
GitHub CLI estรก autenticado.', 'โ
GitHub CLI estรก autenticado.'),
|
|
37
|
+
gh_error: t('โ GitHub CLI (gh) is not installed or not authenticated.', 'โ GitHub CLI (gh) nรฃo estรก instalado ou nรฃo autenticado.', 'โ GitHub CLI (gh) no estรก instalado o no estรก autenticado.'),
|
|
38
|
+
gh_install: t('Please install it from https://cli.github.com/ and run "gh auth login" before continuing.\n', 'Por favor, instale em https://cli.github.com/ e rode "gh auth login" antes de continuar.\n', 'Por favor, instรกlalo desde https://cli.github.com/ y ejecuta "gh auth login" antes de continuar.\n'),
|
|
39
|
+
ask_agent: t('Which AI Agent will you use for the setup? (e.g. claude, cursor, copilot, antigravity, or other): ', 'Qual Agente de IA vocรช usarรก para o setup? (ex: claude, cursor, copilot, antigravity, ou outro): ', 'ยฟQuรฉ Agente de IA usarรกs para la configuraciรณn? (ej: claude, cursor, copilot, antigravity, u otro): '),
|
|
40
|
+
ask_repo: t('What is your GitHub repository URL? (e.g., https://github.com/YOUR_USER/repo_name): ', 'Qual รฉ a URL do seu repositรณrio no GitHub? (ex: https://github.com/SEU_USUARIO/repo_name): ', 'ยฟCuรกl es la URL de tu repositorio en GitHub? (ej: https://github.com/TU_USUARIO/repo_name): '),
|
|
41
|
+
invalid_repo: t('โ Invalid repository URL. Expected format: https://github.com/OWNER/REPO', 'โ URL de repositรณrio invรกlida. Formato esperado: https://github.com/OWNER/REPO', 'โ URL de repositorio invรกlida. Formato esperado: https://github.com/OWNER/REPO'),
|
|
42
|
+
ask_org: t('Does this repository belong to a personal User account (e.g., github.com/rafaeltcosta86) or an Organization (e.g., github.com/google-labs)? (user/org): ', 'Esse repositรณrio pertence a um Usuรกrio pessoal (ex: github.com/rafaeltcosta86) ou a uma Organizaรงรฃo (ex: github.com/google-labs)? (user/org): ', 'ยฟEste repositorio pertenece a un Usuario personal (ej: github.com/rafaeltcosta86) o a una Organizaciรณn (ej: github.com/google-labs)? (user/org): '),
|
|
43
|
+
ask_branch: t('What is your main branch name? (default: main): ', 'Qual o nome da sua branch principal? (padrรฃo: main): ', 'ยฟCuรกl es el nombre de tu rama principal? (por defecto: main): '),
|
|
44
|
+
starting_setup: t('Starting automated repository setup...', 'Iniciando o setup automatizado do repositรณrio...', 'Iniciando la configuraciรณn automatizada del repositorio...'),
|
|
45
|
+
creating_labels: t('[1/2] Creating repository labels...', '[1/2] Criando labels no repositรณrio...', '[1/2] Creando etiquetas (labels) en el repositorio...'),
|
|
46
|
+
label_ok: t('โ
Label created: ', 'โ
Label criada: ', 'โ
Etiqueta creada: '),
|
|
47
|
+
label_warn: t('โ ๏ธ Failed to create label (might already exist): ', 'โ ๏ธ Falha ao criar label (talvez jรก exista): ', 'โ ๏ธ Fallo al crear etiqueta (quizรกs ya exista): '),
|
|
48
|
+
applying_protection: t('[2/3] Applying branch protection on ', '[2/3] Aplicando proteรงรฃo de branch em ', '[2/3] Aplicando protecciรณn de rama en '),
|
|
49
|
+
protection_ok: t('โ
Branch protection applied to ', 'โ
Proteรงรฃo de branch aplicada em ', 'โ
Protecciรณn de rama aplicada a '),
|
|
50
|
+
protection_warn: t('โ ๏ธ Failed to apply branch protection. (Do you have GitHub Pro or is the repo Public?)', 'โ ๏ธ Falha ao aplicar proteรงรฃo de branch. (Vocรช tem GitHub Pro ou o repo รฉ Pรบblico?)', 'โ ๏ธ Fallo al aplicar protecciรณn de rama. (ยฟTienes GitHub Pro o el repositorio es Pรบblico?)'),
|
|
51
|
+
creating_project: t('[2/2] Creating Project V2 Board...', '[2/2] Criando Project V2 Board...', '[2/2] Creando Project V2 Board...'),
|
|
52
|
+
project_ok: t('โ
Project created (ID: ', 'โ
Projeto criado (ID: ', 'โ
Proyecto creado (ID: '),
|
|
53
|
+
project_err: t('โ Failed to create project. Error: ', 'โ Falha ao criar o projeto. Erro: ', 'โ Fallo al crear el proyecto. Error: '),
|
|
54
|
+
config_columns: t('Configuring Project Columns...', 'Configurando colunas do Projeto...', 'Configurando columnas del Proyecto...'),
|
|
55
|
+
columns_ok: t('โ
Project columns configured successfully.', 'โ
Colunas do projeto configuradas com sucesso.', 'โ
Columnas del proyecto configuradas con รฉxito.'),
|
|
56
|
+
columns_warn: t('โ ๏ธ Failed to configure project columns. You may need to add them manually.', 'โ ๏ธ Falha ao configurar colunas. Vocรช pode precisar adicionรก-las manualmente.', 'โ ๏ธ Fallo al configurar columnas. Es posible que debas agregarlas manualmente.'),
|
|
57
|
+
scaffolding: t('Scaffolding Agentic PDLC into your project...', 'Injetando Agentic PDLC no seu projeto...', 'Inyectando Agentic PDLC en tu proyecto...'),
|
|
58
|
+
templates_copied: t('โ
Templates copied to .agentic-pdlc/templates/', 'โ
Templates copiados para .agentic-pdlc/templates/', 'โ
Plantillas copiadas a .agentic-pdlc/templates/'),
|
|
59
|
+
pdlc_prefilled: t('โ
Pre-filled pdlc.md with Project ID, Status Field ID, and Column Option IDs.', 'โ
pdlc.md preenchido com Project ID, Status Field ID, e Column Option IDs.', 'โ
pdlc.md completado con Project ID, Status Field ID y Column Option IDs.'),
|
|
60
|
+
setup_written: t('โ
Setup agent profile written to .agentic-setup.md\n', 'โ
Perfil de setup do agente salvo em .agentic-setup.md\n', 'โ
Perfil de configuraciรณn del agente guardado en .agentic-setup.md\n'),
|
|
61
|
+
framework_scaffolded: t('๐ Framework files scaffolded to .agentic-pdlc/templates/', '๐ Arquivos do framework injetados em .agentic-pdlc/templates/', '๐ Archivos del framework inyectados en .agentic-pdlc/templates/'),
|
|
62
|
+
next_steps: t('๐ NEXT STEPS:', '๐ PRรXIMOS PASSOS:', '๐ PRรXIMOS PASOS:'),
|
|
63
|
+
step_1: t('1. Open your AI Assistant (Claude, Cursor, Copilot, etc).', '1. Abra o seu Assistente de IA (Claude, Cursor, Copilot, etc).', '1. Abre tu Asistente de IA (Claude, Cursor, Copilot, etc).'),
|
|
64
|
+
step_2: t('2. Ask it to read the .agentic-setup.md and start Setup Mode in any language you prefer. Example ๐', '2. Peรงa para ele ler o .agentic-setup.md e iniciar o Setup Mode. Exemplo ๐', '2. Pรญdele que lea .agentic-setup.md e inicie el Setup Mode. Ejemplo ๐'),
|
|
65
|
+
note_cleanup: t('Note: The agent will clean up the .agentic-setup.md file automatically when finished.\n', 'Nota: O agente irรก limpar o arquivo .agentic-setup.md automaticamente quando terminar.\n', 'Nota: El agente limpiarรก el archivo .agentic-setup.md automรกticamente cuando termine.\n'),
|
|
66
|
+
missing_claude: t('โ Could not find instruction file at ', 'โ Nรฃo foi possรญvel encontrar o arquivo de instruรงรฃo em ', 'โ No se pudo encontrar el archivo de instrucciรณn en '),
|
|
67
|
+
cursor_rules_written: t('โ
Default cursor rules written to .cursorrules', 'โ
Regras padrรฃo do cursor salvas em .cursorrules', 'โ
Reglas por defecto de cursor guardadas en .cursorrules'),
|
|
68
|
+
cursor_setup_written: t('โ
Framework Setup Instructions written to .agentic-pdlc/SETUP_PROMPT.md', 'โ
Instruรงรตes de Setup do Framework salvas em .agentic-pdlc/SETUP_PROMPT.md', 'โ
Instrucciones de Setup del Framework guardadas en .agentic-pdlc/SETUP_PROMPT.md'),
|
|
69
|
+
cursor_done: t('๐ Done! To start the conversational setup:', '๐ Pronto! Para iniciar o setup conversacional:', '๐ ยกListo! Para iniciar la configuraciรณn conversacional:'),
|
|
70
|
+
cursor_step_1: t('\t1. Open Cursor', '\t1. Abra o Cursor', '\t1. Abre Cursor'),
|
|
71
|
+
cursor_step_2: t('\t2. Open Composer (Cmd+I or Cmd+L) and type: "@.agentic-pdlc/SETUP_PROMPT.md execute Setup Mode"\n', '\t2. Abra o Composer (Cmd+I ou Cmd+L) e digite: "@.agentic-pdlc/SETUP_PROMPT.md execute Setup Mode"\n', '\t2. Abre Composer (Cmd+I o Cmd+L) y escribe: "@.agentic-pdlc/SETUP_PROMPT.md execute Setup Mode"\n'),
|
|
72
|
+
generic_written: t('โ
Agent generic setup instructions written to .agentic-setup.md', 'โ
Instruรงรตes genรฉricas salvas em .agentic-setup.md', 'โ
Instrucciones genรฉricas guardadas en .agentic-setup.md'),
|
|
73
|
+
generic_done: t('Tell your AI agent to read and execute the .agentic-setup.md file!\n', 'Diga ao seu agente para ler e executar o arquivo .agentic-setup.md!\n', 'ยกDile a tu agente de IA que lea y ejecute el archivo .agentic-setup.md!\n')
|
|
74
|
+
};
|
|
75
|
+
|
|
17
76
|
const cyan = '\x1b[36m';
|
|
18
77
|
const reset = '\x1b[0m';
|
|
19
78
|
const green = '\x1b[32m';
|
|
20
79
|
const yellow = '\x1b[33m';
|
|
80
|
+
const red = '\x1b[31m';
|
|
21
81
|
|
|
22
82
|
console.log(`${cyan}================================================================${reset}`);
|
|
23
|
-
console.log(`${cyan}
|
|
83
|
+
console.log(`${cyan}${i18n.welcome}${reset}`);
|
|
24
84
|
console.log(`${cyan}================================================================${reset}\n`);
|
|
25
85
|
|
|
26
86
|
// Helper function to recursively copy directories
|
|
@@ -39,10 +99,169 @@ function copyDirSync(src, dest) {
|
|
|
39
99
|
}
|
|
40
100
|
}
|
|
41
101
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
102
|
+
async function runSetup() {
|
|
103
|
+
console.log(`${yellow}${i18n.checking_gh}${reset}`);
|
|
104
|
+
try {
|
|
105
|
+
execSync('gh auth status', { stdio: 'ignore' });
|
|
106
|
+
console.log(`${green}${i18n.gh_ok}${reset}\n`);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(`${red}${i18n.gh_error}${reset}`);
|
|
109
|
+
console.error(`${i18n.gh_install}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const agentAnswer = await askQuestion(i18n.ask_agent);
|
|
114
|
+
const agent = agentAnswer.trim().toLowerCase();
|
|
115
|
+
if (!['claude', 'cursor', 'copilot'].includes(agent)) {
|
|
116
|
+
console.log(t(`โน๏ธ Generating Universal Setup for '${agent}' (Compatible with any Markdown-reading agent).`, `โน๏ธ Gerando Setup Universal para '${agent}' (Compatรญvel com qualquer agente que leia Markdown).`, `โน๏ธ Generando Setup Universal para '${agent}' (Compatible con cualquier agente que lea Markdown).`));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let repoOwner, repoName, repo;
|
|
120
|
+
while (true) {
|
|
121
|
+
let repoUrl = (await askQuestion(i18n.ask_repo)).trim();
|
|
122
|
+
if (repoUrl.endsWith('/')) repoUrl = repoUrl.slice(0, -1);
|
|
123
|
+
if (repoUrl.endsWith('.git')) repoUrl = repoUrl.slice(0, -4);
|
|
124
|
+
const repoParts = repoUrl.split('/');
|
|
125
|
+
if (repoParts.length >= 2) {
|
|
126
|
+
repoOwner = repoParts[repoParts.length - 2];
|
|
127
|
+
repoName = repoParts[repoParts.length - 1];
|
|
128
|
+
repo = `${repoOwner}/${repoName}`;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
console.log(`${red}${i18n.invalid_repo}${reset}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const askProjectName = t(`What is the project name for the board? (default: ${repoName.toUpperCase()}): `, `Qual o nome do projeto em que o board serรก configurado? (padrรฃo: ${repoName.toUpperCase()}): `, `ยฟCuรกl es el nombre del proyecto en el que se configurarรก el board? (por defecto: ${repoName.toUpperCase()}): `);
|
|
135
|
+
const projectNameAnswer = await askQuestion(askProjectName);
|
|
136
|
+
const projectName = projectNameAnswer.trim() ? projectNameAnswer.trim().toUpperCase() : repoName.toUpperCase();
|
|
137
|
+
const boardName = `BOARD - ${projectName}`;
|
|
138
|
+
|
|
139
|
+
let isOrg = false;
|
|
140
|
+
try {
|
|
141
|
+
const ownerType = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.owner.type']).toString().trim();
|
|
142
|
+
isOrg = ownerType === 'Organization';
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const accountTypeAnswer = await askQuestion(i18n.ask_org);
|
|
145
|
+
isOrg = accountTypeAnswer.trim().toLowerCase() === 'org' || accountTypeAnswer.trim().toLowerCase() === 'organization';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let branchName = 'main';
|
|
149
|
+
try {
|
|
150
|
+
branchName = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.default_branch']).toString().trim() || 'main';
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const branchAnswer = await askQuestion(i18n.ask_branch);
|
|
153
|
+
if (branchAnswer.trim()) branchName = branchAnswer.trim();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
|
|
157
|
+
|
|
158
|
+
// Labels
|
|
159
|
+
const labels = [
|
|
160
|
+
{ name: 'stage:exploration', color: '9b59b6', description: 'Issue is being evaluated' },
|
|
161
|
+
{ name: 'stage:brainstorming', color: 'e84393', description: 'Proposed approaches awaiting PM gate' },
|
|
162
|
+
{ name: 'stage:detailing', color: '3498db', description: 'Technical spec is being written' },
|
|
163
|
+
{ name: 'stage:development', color: 'e67e22', description: 'Agent is implementing the spec' },
|
|
164
|
+
{ name: 'stage:testing', color: '8e44ad', description: 'Agent is testing the implementation' },
|
|
165
|
+
{ name: 'spec:approved', color: '0e8a16', description: 'Spec approved โ agent can implement' },
|
|
166
|
+
{ name: 'pr:in-review', color: 'e4e669', description: 'PR awaiting code review' },
|
|
167
|
+
{ name: 'pr:approved', color: '0e8a16', description: 'PR approved, ready for merge' },
|
|
168
|
+
{ name: 'architecture-violation', color: 'd93f0b', description: 'Invariant violation detected by CI' },
|
|
169
|
+
{ name: 'qa:approved', color: '0e8a16', description: 'QA Agent approved the implementation' },
|
|
170
|
+
{ name: 'qa:needs-work', color: 'd93f0b', description: 'QA Agent found issues' },
|
|
171
|
+
{ name: 'jules', color: '5319e7', description: 'Jules AI Agent' }
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
console.log(`\n${cyan}${i18n.creating_labels}${reset}`);
|
|
175
|
+
for (const label of labels) {
|
|
176
|
+
try {
|
|
177
|
+
execFileSync('gh', ['label', 'create', label.name, '--color', label.color, '--description', label.description, '--repo', repo, '--force'], { stdio: 'ignore' });
|
|
178
|
+
console.log(` ${i18n.label_ok}${label.name}`);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.log(` ${i18n.label_warn}${label.name}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Project V2
|
|
185
|
+
console.log(`\n${cyan}${i18n.creating_project}${reset}`);
|
|
186
|
+
let ownerId, projectId;
|
|
187
|
+
try {
|
|
188
|
+
if (isOrg) {
|
|
189
|
+
const orgOutput = execFileSync('gh', ['api', 'graphql', '-f', 'query=query($login: String!) { organization(login: $login) { id } }', '-f', `login=${repoOwner}`, '--jq', '.data.organization.id']).toString().trim();
|
|
190
|
+
ownerId = orgOutput;
|
|
191
|
+
} else {
|
|
192
|
+
const userOutput = execFileSync('gh', ['api', 'graphql', '-f', 'query={ viewer { id } }', '--jq', '.data.viewer.id']).toString().trim();
|
|
193
|
+
ownerId = userOutput;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const projectCreateOutput = execFileSync('gh', ['api', 'graphql', '-f', 'query=mutation($owner: ID!, $title: String!) { createProjectV2(input: {ownerId: $owner, title: $title}) { projectV2 { id } } }', '-f', `owner=${ownerId}`, '-f', `title=${boardName}`, '--jq', '.data.createProjectV2.projectV2.id']).toString().trim();
|
|
197
|
+
projectId = projectCreateOutput;
|
|
198
|
+
|
|
199
|
+
console.log(` ${i18n.project_ok}${projectId})`);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.log(` ${i18n.project_err}${err.message}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let statusFieldId;
|
|
205
|
+
let optionMap = {};
|
|
206
|
+
|
|
207
|
+
if (projectId) {
|
|
208
|
+
console.log(` ${cyan}${i18n.config_columns}${reset}`);
|
|
209
|
+
try {
|
|
210
|
+
const fieldsOutput = execFileSync('gh', ['api', 'graphql', '-f', 'query=query($projectId: ID!) { node(id: $projectId) { ... on ProjectV2 { fields(first: 20) { nodes { ... on ProjectV2SingleSelectField { id name } } } } } }', '-f', `projectId=${projectId}`, '--jq', '.data.node.fields.nodes[] | select(.name == "Status") | .id']).toString().trim();
|
|
211
|
+
statusFieldId = fieldsOutput;
|
|
212
|
+
|
|
213
|
+
if (statusFieldId) {
|
|
214
|
+
const columns = [
|
|
215
|
+
{ name: "๐ก Idea", description: "Backlog", color: "GRAY" },
|
|
216
|
+
{ name: "๐ Exploration", description: "Claude is analyzing", color: "PURPLE" },
|
|
217
|
+
{ name: "๐ง Brainstorming", description: "Awaiting PM gate", color: "PINK" },
|
|
218
|
+
{ name: "๐ Detail Solution", description: "Technical spec", color: "BLUE" },
|
|
219
|
+
{ name: "โ
Approval", description: "Spec ready", color: "GREEN" },
|
|
220
|
+
{ name: "โ๏ธ Development", description: "Agent implementing", color: "ORANGE" },
|
|
221
|
+
{ name: "๐งช Testing", description: "QA testing", color: "RED" },
|
|
222
|
+
{ name: "๐ Code Review / PR", description: "PR opened", color: "YELLOW" },
|
|
223
|
+
{ name: "๐ Production", description: "Merged", color: "GREEN" }
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
const updateFieldQuery = `mutation($projectId: ID!, $fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]) {
|
|
227
|
+
updateProjectV2Field(input: {
|
|
228
|
+
projectId: $projectId,
|
|
229
|
+
fieldId: $fieldId,
|
|
230
|
+
singleSelectOptions: $options
|
|
231
|
+
}) {
|
|
232
|
+
projectV2Field {
|
|
233
|
+
... on ProjectV2SingleSelectField {
|
|
234
|
+
options { id name }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}`;
|
|
239
|
+
|
|
240
|
+
const queryPayload = JSON.stringify({
|
|
241
|
+
query: updateFieldQuery,
|
|
242
|
+
variables: {
|
|
243
|
+
projectId: projectId,
|
|
244
|
+
fieldId: statusFieldId,
|
|
245
|
+
options: columns
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const updateOutput = execFileSync('gh', ['api', 'graphql', '--input', '-'], { input: queryPayload }).toString().trim();
|
|
250
|
+
const jsonResponse = updateOutput ? JSON.parse(updateOutput) : null;
|
|
251
|
+
const returnedOptions = jsonResponse?.data?.updateProjectV2Field?.projectV2Field?.options || [];
|
|
252
|
+
|
|
253
|
+
for (const opt of returnedOptions) {
|
|
254
|
+
optionMap[opt.name] = opt.id;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log(` ${i18n.columns_ok}`);
|
|
258
|
+
}
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.log(` ${i18n.columns_warn}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
|
|
46
265
|
|
|
47
266
|
// We copy the templates folder so the agent has the real text logic to replace and rename
|
|
48
267
|
const sourceTemplates = path.join(sourceDir, 'templates');
|
|
@@ -50,7 +269,33 @@ rl.question('Which AI Agent will you use for the setup? (e.g. claude, cursor, co
|
|
|
50
269
|
|
|
51
270
|
if (fs.existsSync(sourceTemplates)) {
|
|
52
271
|
copyDirSync(sourceTemplates, targetTemplates);
|
|
53
|
-
console.log(
|
|
272
|
+
console.log(`${i18n.templates_copied}`);
|
|
273
|
+
|
|
274
|
+
// Substitute values in docs/pdlc.md automatically
|
|
275
|
+
const pdlcDest = path.join(targetTemplates, 'docs', 'pdlc.md');
|
|
276
|
+
if (fs.existsSync(pdlcDest)) {
|
|
277
|
+
let pdlcContent = fs.readFileSync(pdlcDest, 'utf8');
|
|
278
|
+
|
|
279
|
+
if (projectId) pdlcContent = pdlcContent.replace(/\\{\\{PROJECT_ID\\}\\}/g, () => projectId);
|
|
280
|
+
if (statusFieldId) pdlcContent = pdlcContent.replace(/\\{\\{STATUS_FIELD_ID\\}\\}/g, () => statusFieldId);
|
|
281
|
+
pdlcContent = pdlcContent.replace(/\\{\\{REPO_OWNER\\}\\}/g, () => repoOwner);
|
|
282
|
+
pdlcContent = pdlcContent.replace(/\\{\\{REPO_NAME\\}\\}/g, () => repoName);
|
|
283
|
+
|
|
284
|
+
if (Object.keys(optionMap).length > 0) {
|
|
285
|
+
pdlcContent = pdlcContent.replace(/\{\{ID_IDEA\}\}/g, optionMap["๐ก Idea"] || 'MISSING_ID');
|
|
286
|
+
pdlcContent = pdlcContent.replace(/\{\{ID_EXPLORATION\}\}/g, optionMap["๐ Exploration"] || 'MISSING_ID');
|
|
287
|
+
pdlcContent = pdlcContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, optionMap["๐ง Brainstorming"] || 'MISSING_ID');
|
|
288
|
+
pdlcContent = pdlcContent.replace(/\{\{ID_DETAIL\}\}/g, optionMap["๐ Detail Solution"] || 'MISSING_ID');
|
|
289
|
+
pdlcContent = pdlcContent.replace(/\{\{ID_APPROVAL\}\}/g, optionMap["โ
Approval"] || 'MISSING_ID');
|
|
290
|
+
pdlcContent = pdlcContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, optionMap["โ๏ธ Development"] || 'MISSING_ID');
|
|
291
|
+
pdlcContent = pdlcContent.replace(/\{\{ID_TESTING\}\}/g, optionMap["๐งช Testing"] || 'MISSING_ID');
|
|
292
|
+
pdlcContent = pdlcContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, optionMap["๐ Code Review / PR"] || 'MISSING_ID');
|
|
293
|
+
pdlcContent = pdlcContent.replace(/\{\{ID_PRODUCTION\}\}/g, optionMap["๐ Production"] || 'MISSING_ID');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fs.writeFileSync(pdlcDest, pdlcContent);
|
|
297
|
+
console.log(`${i18n.pdlc_prefilled}`);
|
|
298
|
+
}
|
|
54
299
|
}
|
|
55
300
|
|
|
56
301
|
// Handle the specific setup instructions target
|
|
@@ -61,15 +306,19 @@ rl.question('Which AI Agent will you use for the setup? (e.g. claude, cursor, co
|
|
|
61
306
|
if (fs.existsSync(claudeSetupSrc)) {
|
|
62
307
|
const dest = path.join(targetDir, '.agentic-setup.md');
|
|
63
308
|
fs.copyFileSync(claudeSetupSrc, dest);
|
|
64
|
-
console.log(
|
|
65
|
-
console.log(
|
|
66
|
-
console.log(`${
|
|
67
|
-
console.log(`${
|
|
68
|
-
console.log(`${
|
|
69
|
-
console.log(`${
|
|
70
|
-
console.log(
|
|
309
|
+
console.log(`${i18n.setup_written}`);
|
|
310
|
+
console.log(`${green}============================================================${reset}`);
|
|
311
|
+
console.log(`${green}${i18n.framework_scaffolded}${reset}`);
|
|
312
|
+
console.log(`${green}============================================================${reset}\n`);
|
|
313
|
+
console.log(`${yellow}${i18n.next_steps}${reset}`);
|
|
314
|
+
console.log(`${cyan}${i18n.step_1}${reset}`);
|
|
315
|
+
console.log(`${cyan}${i18n.step_2}${reset}`);
|
|
316
|
+
console.log(`${cyan}>>> English: "Read .agentic-setup.md and guide me through the setup."${reset}`);
|
|
317
|
+
console.log(`${cyan}>>> Espaรฑol: "Lea el archivo .agentic-setup.md e inicie el Setup Mode"${reset}`);
|
|
318
|
+
console.log(`${cyan}>>> Portuguรชs: "Leia o arquivo .agentic-setup.md e inicie o Setup Mode."${reset}\n`);
|
|
319
|
+
console.log(`${i18n.note_cleanup}`);
|
|
71
320
|
} else {
|
|
72
|
-
console.error(
|
|
321
|
+
console.error(`${i18n.missing_claude}${claudeSetupSrc}`);
|
|
73
322
|
}
|
|
74
323
|
} else if (agent === 'cursor') {
|
|
75
324
|
if (fs.existsSync(cursorSetupSrc)) {
|
|
@@ -81,22 +330,24 @@ rl.question('Which AI Agent will you use for the setup? (e.g. claude, cursor, co
|
|
|
81
330
|
const setupPromptDest = path.join(targetDir, '.agentic-pdlc', 'SETUP_PROMPT.md');
|
|
82
331
|
if (fs.existsSync(claudeSetupSrc)) fs.copyFileSync(claudeSetupSrc, setupPromptDest);
|
|
83
332
|
|
|
84
|
-
console.log(
|
|
85
|
-
console.log(
|
|
86
|
-
console.log(`\n${green}
|
|
87
|
-
console.log(`${cyan}
|
|
88
|
-
console.log(`${cyan}
|
|
333
|
+
console.log(`${i18n.cursor_rules_written}`);
|
|
334
|
+
console.log(`${i18n.cursor_setup_written}`);
|
|
335
|
+
console.log(`\n${green}${i18n.cursor_done}${reset}`);
|
|
336
|
+
console.log(`${cyan}${i18n.cursor_step_1}${reset}`);
|
|
337
|
+
console.log(`${cyan}${i18n.cursor_step_2}${reset}`);
|
|
89
338
|
} else {
|
|
90
|
-
console.error(
|
|
339
|
+
console.error(`${i18n.missing_claude}${cursorSetupSrc}`);
|
|
91
340
|
}
|
|
92
341
|
} else {
|
|
93
342
|
// Generic fallback mapping
|
|
94
|
-
const dest = path.join(targetDir, '.agentic-setup
|
|
343
|
+
const dest = path.join(targetDir, '.agentic-setup.md');
|
|
95
344
|
fs.copyFileSync(claudeSetupSrc, dest);
|
|
96
|
-
console.log(
|
|
97
|
-
console.log(`\n${green}
|
|
98
|
-
console.log(`${cyan}
|
|
345
|
+
console.log(`${i18n.generic_written}`);
|
|
346
|
+
console.log(`\n${green}${i18n.cursor_done}${reset}`);
|
|
347
|
+
console.log(`${cyan}>>> ${i18n.generic_done}${reset}`);
|
|
99
348
|
}
|
|
100
349
|
|
|
101
350
|
rl.close();
|
|
102
|
-
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
runSetup();
|
package/docs/pdlc.md
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
| โ๏ธ Development | Agent implementing the spec | Label `stage:development` |
|
|
13
13
|
| ๐งช Testing | CI pipeline running | GitHub Actions |
|
|
14
14
|
| ๐ Code Review / PR | PR opened, awaiting human review | GitHub Actions |
|
|
15
|
-
| ๐ Merge | PR approved, awaiting merge | GitHub Actions |
|
|
16
15
|
| ๐ Production | Merged | GitHub Actions |
|
|
17
16
|
|
|
18
17
|
<!--
|
|
@@ -40,7 +39,6 @@ REPO = {{REPO_OWNER}}/{{REPO_NAME}}
|
|
|
40
39
|
| โ๏ธ Development | `{{ID_DEVELOPMENT}}` |
|
|
41
40
|
| ๐งช Testing | `{{ID_TESTING}}` |
|
|
42
41
|
| ๐ Code Review / PR | `{{ID_CODE_REVIEW_PR}}` |
|
|
43
|
-
| ๐ Merge | `{{ID_MERGE}}` |
|
|
44
42
|
| ๐ Production | `{{ID_PRODUCTION}}` |
|
|
45
43
|
|
|
46
44
|
## Agent ร Phase Mapping
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
21
21
|
steps:
|
|
22
22
|
- name: Update Labels
|
|
23
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
23
|
+
if: ${{ env.PROJECT_TOKEN != '' && !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
24
24
|
uses: actions/github-script@v7
|
|
25
25
|
with:
|
|
26
26
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -33,20 +33,21 @@ jobs:
|
|
|
33
33
|
owner,
|
|
34
34
|
repo,
|
|
35
35
|
issue_number,
|
|
36
|
-
name: '
|
|
36
|
+
name: 'stage:approval'
|
|
37
37
|
});
|
|
38
38
|
} catch (error) {
|
|
39
|
-
console.log('Label
|
|
39
|
+
console.log('Label stage:approval not found or could not be removed');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
await github.rest.issues.addLabels({
|
|
43
43
|
owner,
|
|
44
44
|
repo,
|
|
45
45
|
issue_number,
|
|
46
|
-
labels: ['
|
|
46
|
+
labels: ['stage:development', '{{IMPLEMENTATION_AGENT_LABEL}}', 'agent:working']
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
- name: Comment on issue to trigger agent and prevent race conditions
|
|
50
|
+
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
50
51
|
uses: actions/github-script@v7
|
|
51
52
|
with:
|
|
52
53
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -89,6 +90,7 @@ jobs:
|
|
|
89
90
|
contents: read
|
|
90
91
|
steps:
|
|
91
92
|
- name: Comment on issue to trigger agent
|
|
93
|
+
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
92
94
|
uses: actions/github-script@v7
|
|
93
95
|
with:
|
|
94
96
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: Auto Approve PRs
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, labeled, synchronize]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
pull-requests: write
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
auto-approve:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
if: contains(github.event.pull_request.labels.*.name, 'auto-approve')
|
|
13
|
+
steps:
|
|
14
|
+
- uses: hmarr/auto-approve-action@v4
|
|
15
|
+
with:
|
|
16
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -20,13 +20,21 @@ jobs:
|
|
|
20
20
|
run: echo "Replace this with your package manager install command"
|
|
21
21
|
|
|
22
22
|
- name: Run Linters
|
|
23
|
-
|
|
23
|
+
if: ${{ !contains('{{LINT_COMMAND}}', '{{') }}
|
|
24
|
+
run: |
|
|
25
|
+
{{LINT_COMMAND}}
|
|
24
26
|
|
|
25
27
|
- name: Typecheck
|
|
26
|
-
|
|
28
|
+
if: ${{ !contains('{{TYPECHECK_COMMAND}}', '{{') }}
|
|
29
|
+
run: |
|
|
30
|
+
{{TYPECHECK_COMMAND}}
|
|
27
31
|
|
|
28
32
|
- name: Build
|
|
29
|
-
|
|
33
|
+
if: ${{ !contains('{{BUILD_COMMAND}}', '{{') }}
|
|
34
|
+
run: |
|
|
35
|
+
{{BUILD_COMMAND}}
|
|
30
36
|
|
|
31
37
|
- name: Run Tests
|
|
32
|
-
|
|
38
|
+
if: ${{ !contains('{{TEST_COMMAND}}', '{{') }}
|
|
39
|
+
run: |
|
|
40
|
+
{{TEST_COMMAND}}
|
|
@@ -11,6 +11,7 @@ on:
|
|
|
11
11
|
env:
|
|
12
12
|
PROJECT_ID: "{{PROJECT_ID}}"
|
|
13
13
|
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
14
|
+
STATUS_IDEA: "{{ID_IDEA}}"
|
|
14
15
|
STATUS_EXPLORATION: "{{ID_EXPLORATION}}"
|
|
15
16
|
STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
|
|
16
17
|
STATUS_DETAILING: "{{ID_DETAILING}}"
|
|
@@ -30,7 +31,7 @@ jobs:
|
|
|
30
31
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
31
32
|
steps:
|
|
32
33
|
- name: Detect Label and Move Issue
|
|
33
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
34
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
34
35
|
uses: actions/github-script@v7
|
|
35
36
|
with:
|
|
36
37
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -39,28 +40,28 @@ jobs:
|
|
|
39
40
|
let targetStatusId = null;
|
|
40
41
|
let stageName = null;
|
|
41
42
|
|
|
42
|
-
if (labelName === '
|
|
43
|
+
if (labelName === 'stage:exploration') {
|
|
43
44
|
targetStatusId = process.env.STATUS_EXPLORATION;
|
|
44
45
|
stageName = 'Exploration';
|
|
45
|
-
} else if (labelName === '
|
|
46
|
+
} else if (labelName === 'stage:brainstorming') {
|
|
46
47
|
targetStatusId = process.env.STATUS_BRAINSTORMING;
|
|
47
48
|
stageName = 'Brainstorming';
|
|
48
|
-
} else if (labelName === '
|
|
49
|
+
} else if (labelName === 'stage:detailing') {
|
|
49
50
|
targetStatusId = process.env.STATUS_DETAILING;
|
|
50
51
|
stageName = 'Detailing';
|
|
51
|
-
} else if (labelName === '
|
|
52
|
+
} else if (labelName === 'stage:approval') {
|
|
52
53
|
targetStatusId = process.env.STATUS_APPROVAL;
|
|
53
54
|
stageName = 'Approval';
|
|
54
|
-
} else if (labelName === '
|
|
55
|
+
} else if (labelName === 'stage:development') {
|
|
55
56
|
targetStatusId = process.env.STATUS_DEVELOPMENT;
|
|
56
57
|
stageName = 'Development';
|
|
57
|
-
} else if (labelName === '
|
|
58
|
+
} else if (labelName === 'stage:testing') {
|
|
58
59
|
targetStatusId = process.env.STATUS_TESTING;
|
|
59
60
|
stageName = 'Testing';
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
if (!targetStatusId) {
|
|
63
|
-
console.log('No
|
|
64
|
+
console.log('No stage PDLC label found. Skipping.');
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -86,9 +87,36 @@ jobs:
|
|
|
86
87
|
await moveItem(node_id, targetStatusId);
|
|
87
88
|
console.log(`Issue #${number} moved to ${stageName}`);
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
# OPTIONAL: Uncomment to enable architecture-violation โ Idea
|
|
91
|
+
# move-violation-to-board:
|
|
92
|
+
# name: architecture-violation โ ๐ก Idea
|
|
93
|
+
# if: github.event_name == 'issues' && github.event.action == 'labeled' && github.event.label.name == 'architecture-violation'
|
|
94
|
+
# runs-on: ubuntu-latest
|
|
95
|
+
# steps:
|
|
96
|
+
# - name: Move issue to Idea
|
|
97
|
+
# if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
98
|
+
# uses: actions/github-script@v7
|
|
99
|
+
# with:
|
|
100
|
+
# github-token: ${{ env.PROJECT_TOKEN }}
|
|
101
|
+
# script: |
|
|
102
|
+
# const { issue: { number, node_id } } = context.payload;
|
|
103
|
+
# const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
104
|
+
# mutation($p: ID!, $c: ID!) {
|
|
105
|
+
# addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
106
|
+
# }`, { p: process.env.PROJECT_ID, c: node_id });
|
|
107
|
+
# await github.graphql(`
|
|
108
|
+
# mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
109
|
+
# updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
110
|
+
# projectV2Item { id }
|
|
111
|
+
# }
|
|
112
|
+
# }`, {
|
|
113
|
+
# p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
114
|
+
# v: { singleSelectOptionId: process.env.STATUS_IDEA }
|
|
115
|
+
# });
|
|
116
|
+
# console.log(`Issue #${number} moved to Idea`);
|
|
90
117
|
|
|
91
118
|
# PR Opened โ Move linked issue to Code Review / PR
|
|
119
|
+
# ๐ก VARIANT B (QA Agent): If using an AI QA agent, change `STATUS_CODE_REVIEW_PR` to `STATUS_TESTING` on line ~158
|
|
92
120
|
move-card-on-pr-open:
|
|
93
121
|
name: Open PR โ Code Review / PR
|
|
94
122
|
if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
|
@@ -97,7 +125,7 @@ jobs:
|
|
|
97
125
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
98
126
|
steps:
|
|
99
127
|
- name: Move linked issue to Code Review / PR
|
|
100
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
128
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
101
129
|
uses: actions/github-script@v7
|
|
102
130
|
with:
|
|
103
131
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -151,7 +179,7 @@ jobs:
|
|
|
151
179
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
152
180
|
steps:
|
|
153
181
|
- name: Swap PR labels
|
|
154
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
182
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
155
183
|
uses: actions/github-script@v7
|
|
156
184
|
with:
|
|
157
185
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -170,7 +198,7 @@ jobs:
|
|
|
170
198
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
171
199
|
steps:
|
|
172
200
|
- name: Move issue to Production
|
|
173
|
-
if: ${{ env.PROJECT_TOKEN != '' }}
|
|
201
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
174
202
|
uses: actions/github-script@v7
|
|
175
203
|
with:
|
|
176
204
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Protect Workflows
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened, labeled]
|
|
6
|
+
paths:
|
|
7
|
+
- '.github/**'
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
block-unauthorized-edits:
|
|
11
|
+
name: Protect .github directory
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Check for Human Approval
|
|
15
|
+
if: ${{ !contains(github.event.pull_request.labels.*.name, 'human-approved') }}
|
|
16
|
+
run: |
|
|
17
|
+
echo "๐จ Modifications in the .github/ directory detected."
|
|
18
|
+
echo "As a security measure, this repository blocks workflow edits by default."
|
|
19
|
+
echo "If you are an AI Agent, do not add the approval label yourself."
|
|
20
|
+
echo "If you are the human owner, please add the 'human-approved' label to this PR to allow merging."
|
|
21
|
+
exit 1
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: AI QA Agent
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, synchronize, reopened]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
pull-requests: write
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
qa:
|
|
12
|
+
name: Run AI QA Agent
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- name: Execute QA Tests
|
|
17
|
+
run: |
|
|
18
|
+
echo "Run your QA Agent here."
|
|
19
|
+
echo "This could be QAWolf, a custom LLM script, or a secondary agent."
|
|
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"
|
|
25
|
+
env:
|
|
26
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
package/templates/docs/pdlc.md
CHANGED
|
@@ -10,9 +10,8 @@
|
|
|
10
10
|
| ๐ Detail Solution | Claude is writing the technical spec | Label `stage:detailing` |
|
|
11
11
|
| โ
Approval | Spec ready, awaiting `spec:approved` label | Label `spec:approved` |
|
|
12
12
|
| โ๏ธ Development | Agent implementing the spec | Label `stage:development` |
|
|
13
|
-
| ๐งช Testing | CI pipeline running | GitHub Actions |
|
|
14
|
-
| ๐ Code Review / PR | PR opened
|
|
15
|
-
| ๐ Merge | PR approved, awaiting merge | GitHub Actions |
|
|
13
|
+
| ๐งช Testing | CI pipeline or AI QA Agent running (Variant B) | GitHub Actions / QA Agent |
|
|
14
|
+
| ๐ Code Review / PR | PR opened (Variant A) or QA passed (Variant B) | GitHub Actions |
|
|
16
15
|
| ๐ Production | Merged | GitHub Actions |
|
|
17
16
|
|
|
18
17
|
<!--
|
|
@@ -20,6 +19,11 @@ Adapt columns as needed. The functional baseline is:
|
|
|
20
19
|
๐ก Idea โ โ๏ธ Development โ ๐ Code Review / PR โ ๐ Production
|
|
21
20
|
-->
|
|
22
21
|
|
|
22
|
+
## Workflow Variants (QA Agent)
|
|
23
|
+
|
|
24
|
+
- **Variant A (Default):** PRs bypass the `Testing` column and land directly in `Code Review / PR`.
|
|
25
|
+
- **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`.
|
|
26
|
+
|
|
23
27
|
## Board Identifiers (GitHub Projects)
|
|
24
28
|
|
|
25
29
|
```
|
|
@@ -40,7 +44,6 @@ REPO = {{REPO_OWNER}}/{{REPO_NAME}}
|
|
|
40
44
|
| โ๏ธ Development | `{{ID_DEVELOPMENT}}` |
|
|
41
45
|
| ๐งช Testing | `{{ID_TESTING}}` |
|
|
42
46
|
| ๐ Code Review / PR | `{{ID_CODE_REVIEW_PR}}` |
|
|
43
|
-
| ๐ Merge | `{{ID_MERGE}}` |
|
|
44
47
|
| ๐ Production | `{{ID_PRODUCTION}}` |
|
|
45
48
|
|
|
46
49
|
## Agent ร Phase Mapping
|
|
@@ -1,54 +0,0 @@
|
|
|
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
|
-
}
|