@kody-ade/engine 0.1.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/LICENSE +21 -0
- package/README.md +322 -0
- package/dist/agent-runner.d.ts +4 -0
- package/dist/agent-runner.js +122 -0
- package/dist/bin/cli.js +11276 -0
- package/dist/ci/parse-inputs.d.ts +6 -0
- package/dist/ci/parse-inputs.js +76 -0
- package/dist/ci/parse-safety.d.ts +6 -0
- package/dist/ci/parse-safety.js +22 -0
- package/dist/cli/args.d.ts +13 -0
- package/dist/cli/args.js +42 -0
- package/dist/cli/litellm.d.ts +2 -0
- package/dist/cli/litellm.js +85 -0
- package/dist/cli/task-resolution.d.ts +2 -0
- package/dist/cli/task-resolution.js +41 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +72 -0
- package/dist/context.d.ts +4 -0
- package/dist/context.js +83 -0
- package/dist/definitions.d.ts +3 -0
- package/dist/definitions.js +59 -0
- package/dist/entry.d.ts +1 -0
- package/dist/entry.js +236 -0
- package/dist/git-utils.d.ts +13 -0
- package/dist/git-utils.js +174 -0
- package/dist/github-api.d.ts +14 -0
- package/dist/github-api.js +114 -0
- package/dist/kody-utils.d.ts +1 -0
- package/dist/kody-utils.js +9 -0
- package/dist/learning/auto-learn.d.ts +2 -0
- package/dist/learning/auto-learn.js +169 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.js +51 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.js +20 -0
- package/dist/observer.d.ts +9 -0
- package/dist/observer.js +80 -0
- package/dist/pipeline/complexity.d.ts +3 -0
- package/dist/pipeline/complexity.js +12 -0
- package/dist/pipeline/executor-registry.d.ts +3 -0
- package/dist/pipeline/executor-registry.js +20 -0
- package/dist/pipeline/hooks.d.ts +17 -0
- package/dist/pipeline/hooks.js +110 -0
- package/dist/pipeline/questions.d.ts +2 -0
- package/dist/pipeline/questions.js +44 -0
- package/dist/pipeline/runner-selection.d.ts +2 -0
- package/dist/pipeline/runner-selection.js +13 -0
- package/dist/pipeline/state.d.ts +4 -0
- package/dist/pipeline/state.js +37 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.js +213 -0
- package/dist/preflight.d.ts +1 -0
- package/dist/preflight.js +69 -0
- package/dist/retrospective.d.ts +26 -0
- package/dist/retrospective.js +211 -0
- package/dist/stages/agent.d.ts +2 -0
- package/dist/stages/agent.js +94 -0
- package/dist/stages/gate.d.ts +2 -0
- package/dist/stages/gate.js +32 -0
- package/dist/stages/review.d.ts +2 -0
- package/dist/stages/review.js +32 -0
- package/dist/stages/ship.d.ts +3 -0
- package/dist/stages/ship.js +154 -0
- package/dist/stages/verify.d.ts +2 -0
- package/dist/stages/verify.js +94 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +1 -0
- package/dist/validators.d.ts +8 -0
- package/dist/validators.js +42 -0
- package/dist/verify-runner.d.ts +11 -0
- package/dist/verify-runner.js +110 -0
- package/kody.config.schema.json +299 -0
- package/package.json +39 -0
- package/prompts/autofix.md +52 -0
- package/prompts/build.md +26 -0
- package/prompts/decompose.md +77 -0
- package/prompts/plan.md +65 -0
- package/prompts/review-fix.md +27 -0
- package/prompts/review.md +115 -0
- package/prompts/taskify-ticket.md +122 -0
- package/prompts/taskify.md +70 -0
- package/templates/kody-watch.yml +57 -0
- package/templates/kody.yml +450 -0
- package/templates/watch-agents/branch-cleanup/agent.json +7 -0
- package/templates/watch-agents/branch-cleanup/agent.md +13 -0
- package/templates/watch-agents/dependency-checker/agent.json +7 -0
- package/templates/watch-agents/dependency-checker/agent.md +14 -0
- package/templates/watch-agents/readme-health/agent.json +7 -0
- package/templates/watch-agents/readme-health/agent.md +17 -0
- package/templates/watch-agents/stale-pr-reviewer/agent.json +7 -0
- package/templates/watch-agents/stale-pr-reviewer/agent.md +13 -0
- package/templates/watch-agents/todo-scanner/agent.json +7 -0
- package/templates/watch-agents/todo-scanner/agent.md +10 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
name: kody
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
task_id:
|
|
7
|
+
required: true
|
|
8
|
+
type: string
|
|
9
|
+
mode:
|
|
10
|
+
type: string
|
|
11
|
+
default: "full"
|
|
12
|
+
from_stage:
|
|
13
|
+
type: string
|
|
14
|
+
default: ""
|
|
15
|
+
issue_number:
|
|
16
|
+
type: string
|
|
17
|
+
default: ""
|
|
18
|
+
feedback:
|
|
19
|
+
type: string
|
|
20
|
+
default: ""
|
|
21
|
+
dry_run:
|
|
22
|
+
type: boolean
|
|
23
|
+
default: false
|
|
24
|
+
|
|
25
|
+
issue_comment:
|
|
26
|
+
types: [created]
|
|
27
|
+
|
|
28
|
+
pull_request:
|
|
29
|
+
types: [closed]
|
|
30
|
+
|
|
31
|
+
pull_request_review:
|
|
32
|
+
types: [submitted]
|
|
33
|
+
|
|
34
|
+
workflow_run:
|
|
35
|
+
workflows: ["CI"]
|
|
36
|
+
types: [completed]
|
|
37
|
+
|
|
38
|
+
push:
|
|
39
|
+
branches: [main, dev]
|
|
40
|
+
paths: ["src/**", "kody.config.json", "package.json"]
|
|
41
|
+
|
|
42
|
+
concurrency:
|
|
43
|
+
group: kody-${{ github.event.inputs.task_id || github.event.issue.number || github.event.pull_request.number || github.event.workflow_run.id || github.sha }}
|
|
44
|
+
cancel-in-progress: false
|
|
45
|
+
|
|
46
|
+
permissions:
|
|
47
|
+
issues: write
|
|
48
|
+
pull-requests: write
|
|
49
|
+
contents: write
|
|
50
|
+
|
|
51
|
+
jobs:
|
|
52
|
+
# ─── Parse (issue_comment trigger only) ──────────────────────────────────────
|
|
53
|
+
parse:
|
|
54
|
+
if: >-
|
|
55
|
+
github.event_name == 'issue_comment' &&
|
|
56
|
+
(contains(github.event.comment.body, '@kody') || contains(github.event.comment.body, '/kody'))
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
timeout-minutes: 5
|
|
59
|
+
outputs:
|
|
60
|
+
task_id: ${{ steps.parse.outputs.task_id }}
|
|
61
|
+
mode: ${{ steps.parse.outputs.mode }}
|
|
62
|
+
from_stage: ${{ steps.parse.outputs.from_stage }}
|
|
63
|
+
issue_number: ${{ steps.parse.outputs.issue_number }}
|
|
64
|
+
pr_number: ${{ steps.parse.outputs.pr_number }}
|
|
65
|
+
feedback: ${{ steps.parse.outputs.feedback }}
|
|
66
|
+
ci_run_id: ${{ steps.parse.outputs.ci_run_id }}
|
|
67
|
+
ticket_id: ${{ steps.parse.outputs.ticket_id }}
|
|
68
|
+
prd_file: ${{ steps.parse.outputs.prd_file }}
|
|
69
|
+
dry_run: ${{ steps.parse.outputs.dry_run }}
|
|
70
|
+
valid: ${{ steps.parse.outputs.valid }}
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/setup-node@v4
|
|
73
|
+
with:
|
|
74
|
+
node-version: 22
|
|
75
|
+
|
|
76
|
+
- name: Install Kody Engine
|
|
77
|
+
run: npm install -g @kody-ade/kody-engine-lite
|
|
78
|
+
|
|
79
|
+
- name: Validate author
|
|
80
|
+
id: safety
|
|
81
|
+
run: |
|
|
82
|
+
ALLOWED="COLLABORATOR MEMBER OWNER"
|
|
83
|
+
ASSOC="${{ github.event.comment.author_association }}"
|
|
84
|
+
if echo "$ALLOWED" | grep -qw "$ASSOC"; then
|
|
85
|
+
echo "valid=true" >> $GITHUB_OUTPUT
|
|
86
|
+
else
|
|
87
|
+
echo "valid=false" >> $GITHUB_OUTPUT
|
|
88
|
+
echo "reason=Author association '$ASSOC' not allowed" >> $GITHUB_OUTPUT
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
- name: Add reaction
|
|
92
|
+
if: steps.safety.outputs.valid == 'true'
|
|
93
|
+
uses: actions/github-script@v7
|
|
94
|
+
continue-on-error: true
|
|
95
|
+
with:
|
|
96
|
+
script: |
|
|
97
|
+
await github.rest.reactions.createForIssueComment({
|
|
98
|
+
owner: context.repo.owner,
|
|
99
|
+
repo: context.repo.repo,
|
|
100
|
+
comment_id: context.payload.comment.id,
|
|
101
|
+
content: 'eyes'
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
- name: Parse inputs
|
|
105
|
+
if: steps.safety.outputs.valid == 'true'
|
|
106
|
+
id: parse
|
|
107
|
+
env:
|
|
108
|
+
COMMENT_BODY: ${{ github.event.comment.body }}
|
|
109
|
+
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
110
|
+
ISSUE_IS_PR: ${{ github.event.issue.pull_request && 'true' || '' }}
|
|
111
|
+
TRIGGER_TYPE: comment
|
|
112
|
+
run: kody-engine-lite ci-parse
|
|
113
|
+
|
|
114
|
+
# ─── Orchestrate ─────────────────────────────────────────────────────────────
|
|
115
|
+
orchestrate:
|
|
116
|
+
if: >-
|
|
117
|
+
github.event_name == 'workflow_dispatch' ||
|
|
118
|
+
(needs.parse.result == 'success' && needs.parse.outputs.valid == 'true') ||
|
|
119
|
+
(github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested')
|
|
120
|
+
needs: [parse]
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
timeout-minutes: 120
|
|
123
|
+
steps:
|
|
124
|
+
- name: Generate App token
|
|
125
|
+
id: app-token
|
|
126
|
+
if: vars.KODY_APP_ID != ''
|
|
127
|
+
uses: actions/create-github-app-token@v1
|
|
128
|
+
with:
|
|
129
|
+
app-id: ${{ vars.KODY_APP_ID }}
|
|
130
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
131
|
+
repositories: ${{ github.event.repository.name }}
|
|
132
|
+
|
|
133
|
+
- uses: actions/checkout@v4
|
|
134
|
+
with:
|
|
135
|
+
fetch-depth: 0
|
|
136
|
+
persist-credentials: true
|
|
137
|
+
token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
138
|
+
|
|
139
|
+
- name: Checkout PR branch (for fix/rerun/review on PRs)
|
|
140
|
+
if: github.event.issue.pull_request && (needs.parse.outputs.mode == 'fix' || needs.parse.outputs.mode == 'fix-ci' || needs.parse.outputs.mode == 'rerun' || needs.parse.outputs.mode == 'review' || needs.parse.outputs.mode == 'resolve')
|
|
141
|
+
env:
|
|
142
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
143
|
+
run: |
|
|
144
|
+
PR_NUM="${{ github.event.issue.number }}"
|
|
145
|
+
PR_BRANCH=$(gh pr view "$PR_NUM" --json headRefName -q '.headRefName')
|
|
146
|
+
echo "Checking out PR branch: $PR_BRANCH"
|
|
147
|
+
git checkout "$PR_BRANCH"
|
|
148
|
+
git pull origin "$PR_BRANCH"
|
|
149
|
+
|
|
150
|
+
- uses: pnpm/action-setup@v4
|
|
151
|
+
with:
|
|
152
|
+
version: latest
|
|
153
|
+
- uses: actions/setup-node@v4
|
|
154
|
+
with:
|
|
155
|
+
node-version: 22
|
|
156
|
+
cache: pnpm
|
|
157
|
+
|
|
158
|
+
- run: pnpm install --frozen-lockfile
|
|
159
|
+
|
|
160
|
+
- name: Install Claude Code CLI
|
|
161
|
+
run: npm install -g @anthropic-ai/claude-code
|
|
162
|
+
|
|
163
|
+
- name: Install LiteLLM proxy
|
|
164
|
+
run: pip install 'litellm[proxy]' prisma && python -m prisma py fetch
|
|
165
|
+
|
|
166
|
+
- name: Configure git
|
|
167
|
+
run: |
|
|
168
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
169
|
+
git config user.name "github-actions[bot]"
|
|
170
|
+
|
|
171
|
+
- name: Export project secrets
|
|
172
|
+
env:
|
|
173
|
+
ALL_SECRETS: ${{ toJSON(secrets) }}
|
|
174
|
+
run: |
|
|
175
|
+
echo "$ALL_SECRETS" | jq -r 'to_entries[] | select(.key | test("^(GITHUB_TOKEN)$") | not) | @json' | while IFS= read -r entry; do
|
|
176
|
+
KEY=$(echo "$entry" | jq -r '.key')
|
|
177
|
+
VALUE=$(echo "$entry" | jq -r '.value')
|
|
178
|
+
DELIM="KODY_EOF_${KEY}"
|
|
179
|
+
echo "${KEY}<<${DELIM}" >> $GITHUB_ENV
|
|
180
|
+
echo "${VALUE}" >> $GITHUB_ENV
|
|
181
|
+
echo "${DELIM}" >> $GITHUB_ENV
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
- name: Warm up Next.js build cache
|
|
185
|
+
continue-on-error: true
|
|
186
|
+
run: npx next build 2>/dev/null || true
|
|
187
|
+
timeout-minutes: 15
|
|
188
|
+
|
|
189
|
+
- name: Run Kody pipeline
|
|
190
|
+
env:
|
|
191
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
192
|
+
TASK_ID: ${{ github.event.inputs.task_id || needs.parse.outputs.task_id }}
|
|
193
|
+
MODE: ${{ github.event.inputs.mode || needs.parse.outputs.mode || 'full' }}
|
|
194
|
+
FROM_STAGE: ${{ github.event.inputs.from_stage || needs.parse.outputs.from_stage }}
|
|
195
|
+
ISSUE_NUMBER: ${{ github.event.inputs.issue_number || needs.parse.outputs.issue_number }}
|
|
196
|
+
PR_NUMBER: ${{ needs.parse.outputs.pr_number }}
|
|
197
|
+
FEEDBACK: ${{ github.event.inputs.feedback || needs.parse.outputs.feedback }}
|
|
198
|
+
COMPLEXITY: ${{ needs.parse.outputs.complexity }}
|
|
199
|
+
CI_RUN_ID: ${{ needs.parse.outputs.ci_run_id }}
|
|
200
|
+
TICKET_ID: ${{ needs.parse.outputs.ticket_id }}
|
|
201
|
+
PRD_FILE: ${{ needs.parse.outputs.prd_file }}
|
|
202
|
+
DRY_RUN: ${{ github.event.inputs.dry_run || needs.parse.outputs.dry_run || 'false' }}
|
|
203
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
204
|
+
run: |
|
|
205
|
+
if [ "$MODE" = "bootstrap" ]; then
|
|
206
|
+
echo "Running bootstrap..."
|
|
207
|
+
npx kody-engine-lite bootstrap
|
|
208
|
+
elif [ "$MODE" = "taskify" ]; then
|
|
209
|
+
echo "Running taskify..."
|
|
210
|
+
ARGS=""
|
|
211
|
+
[ -n "$TICKET_ID" ] && ARGS="$ARGS --ticket $TICKET_ID"
|
|
212
|
+
[ -n "$PRD_FILE" ] && ARGS="$ARGS --file $PRD_FILE"
|
|
213
|
+
[ -n "$ISSUE_NUMBER" ] && ARGS="$ARGS --issue-number $ISSUE_NUMBER"
|
|
214
|
+
[ -n "$TASK_ID" ] && ARGS="$ARGS --task-id $TASK_ID"
|
|
215
|
+
npx kody-engine-lite taskify $ARGS
|
|
216
|
+
else
|
|
217
|
+
CMD="run"
|
|
218
|
+
[ "$MODE" = "rerun" ] && CMD="rerun"
|
|
219
|
+
[ "$MODE" = "fix" ] && CMD="fix"
|
|
220
|
+
[ "$MODE" = "fix-ci" ] && CMD="fix-ci"
|
|
221
|
+
[ "$MODE" = "review" ] && CMD="review"
|
|
222
|
+
[ "$MODE" = "resolve" ] && CMD="resolve"
|
|
223
|
+
[ "$MODE" = "status" ] && CMD="status"
|
|
224
|
+
ARGS="--issue-number $ISSUE_NUMBER"
|
|
225
|
+
[ -n "$TASK_ID" ] && ARGS="$ARGS --task-id $TASK_ID"
|
|
226
|
+
[ -n "$PR_NUMBER" ] && ARGS="$ARGS --pr-number $PR_NUMBER"
|
|
227
|
+
[ -n "$FROM_STAGE" ] && ARGS="$ARGS --from $FROM_STAGE"
|
|
228
|
+
[ -n "$COMPLEXITY" ] && ARGS="$ARGS --complexity $COMPLEXITY"
|
|
229
|
+
# FEEDBACK is also passed via env var (avoids shell escaping issues)
|
|
230
|
+
[ -n "$FEEDBACK" ] && ARGS="$ARGS --feedback \"$FEEDBACK\""
|
|
231
|
+
[ "$DRY_RUN" = "true" ] && ARGS="$ARGS --dry-run"
|
|
232
|
+
npx kody-engine-lite $CMD $ARGS
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
- name: Pipeline summary
|
|
236
|
+
if: always()
|
|
237
|
+
run: |
|
|
238
|
+
TASK_ID="${{ github.event.inputs.task_id || needs.parse.outputs.task_id }}"
|
|
239
|
+
STATUS_FILE=".kody/tasks/${TASK_ID}/status.json"
|
|
240
|
+
if [ -f "$STATUS_FILE" ]; then
|
|
241
|
+
STATE=$(jq -r '.state' "$STATUS_FILE")
|
|
242
|
+
ICON="❌"
|
|
243
|
+
[ "$STATE" = "completed" ] && ICON="✅"
|
|
244
|
+
echo "## ${ICON} Kody Pipeline: \`${TASK_ID}\`" >> $GITHUB_STEP_SUMMARY
|
|
245
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
246
|
+
echo "**Status:** ${STATE}" >> $GITHUB_STEP_SUMMARY
|
|
247
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
248
|
+
echo "| Stage | State |" >> $GITHUB_STEP_SUMMARY
|
|
249
|
+
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
|
250
|
+
for stage in taskify plan build verify review review-fix ship; do
|
|
251
|
+
SSTATE=$(jq -r ".stages[\"$stage\"].state // \"—\"" "$STATUS_FILE")
|
|
252
|
+
case "$SSTATE" in
|
|
253
|
+
completed) SICON="✅" ;;
|
|
254
|
+
failed) SICON="❌" ;;
|
|
255
|
+
timeout) SICON="⏱" ;;
|
|
256
|
+
running) SICON="▶️" ;;
|
|
257
|
+
*) SICON="○" ;;
|
|
258
|
+
esac
|
|
259
|
+
echo "| ${stage} | ${SICON} ${SSTATE} |" >> $GITHUB_STEP_SUMMARY
|
|
260
|
+
done
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
- name: Upload artifacts
|
|
264
|
+
if: always()
|
|
265
|
+
uses: actions/upload-artifact@v4
|
|
266
|
+
continue-on-error: true
|
|
267
|
+
with:
|
|
268
|
+
name: kody-tasks-${{ github.event.inputs.task_id || needs.parse.outputs.task_id }}
|
|
269
|
+
path: .kody/tasks/
|
|
270
|
+
retention-days: 3
|
|
271
|
+
|
|
272
|
+
# ─── Close Issue on PR Merge ────────────────────────────────────────────────
|
|
273
|
+
close-issue-on-merge:
|
|
274
|
+
if: >-
|
|
275
|
+
github.event_name == 'pull_request' &&
|
|
276
|
+
github.event.pull_request.merged == true
|
|
277
|
+
runs-on: ubuntu-latest
|
|
278
|
+
permissions:
|
|
279
|
+
issues: write
|
|
280
|
+
steps:
|
|
281
|
+
- name: Generate App token
|
|
282
|
+
id: app-token
|
|
283
|
+
if: vars.KODY_APP_ID != ''
|
|
284
|
+
uses: actions/create-github-app-token@v1
|
|
285
|
+
with:
|
|
286
|
+
app-id: ${{ vars.KODY_APP_ID }}
|
|
287
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
288
|
+
repositories: ${{ github.event.repository.name }}
|
|
289
|
+
|
|
290
|
+
- name: Close linked issue
|
|
291
|
+
uses: actions/github-script@v7
|
|
292
|
+
with:
|
|
293
|
+
github-token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
294
|
+
script: |
|
|
295
|
+
// Extract issue number from branch name (e.g. "42--feature-name")
|
|
296
|
+
const branch = context.payload.pull_request.head.ref;
|
|
297
|
+
const match = branch.match(/^(\d+)--/);
|
|
298
|
+
if (!match) return;
|
|
299
|
+
const issueNumber = parseInt(match[1], 10);
|
|
300
|
+
|
|
301
|
+
// Verify the issue exists and is open
|
|
302
|
+
try {
|
|
303
|
+
const { data: issue } = await github.rest.issues.get({
|
|
304
|
+
owner: context.repo.owner,
|
|
305
|
+
repo: context.repo.repo,
|
|
306
|
+
issue_number: issueNumber,
|
|
307
|
+
});
|
|
308
|
+
if (issue.state === 'closed') return;
|
|
309
|
+
if (issue.pull_request) return; // Skip if it's a PR, not an issue
|
|
310
|
+
|
|
311
|
+
await github.rest.issues.update({
|
|
312
|
+
owner: context.repo.owner,
|
|
313
|
+
repo: context.repo.repo,
|
|
314
|
+
issue_number: issueNumber,
|
|
315
|
+
state: 'closed',
|
|
316
|
+
state_reason: 'completed',
|
|
317
|
+
});
|
|
318
|
+
core.info(`Closed issue #${issueNumber} after PR #${context.payload.pull_request.number} was merged`);
|
|
319
|
+
} catch (e) {
|
|
320
|
+
core.warning(`Could not close issue #${issueNumber}: ${e.message}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
# ─── Error Notifications ─────────────────────────────────────────────────────
|
|
324
|
+
notify-parse-error:
|
|
325
|
+
if: >-
|
|
326
|
+
github.event_name == 'issue_comment' &&
|
|
327
|
+
needs.parse.outputs.valid != 'true' &&
|
|
328
|
+
(contains(github.event.comment.body, '@kody') || contains(github.event.comment.body, '/kody'))
|
|
329
|
+
needs: [parse]
|
|
330
|
+
runs-on: ubuntu-latest
|
|
331
|
+
steps:
|
|
332
|
+
- uses: actions/github-script@v7
|
|
333
|
+
with:
|
|
334
|
+
script: |
|
|
335
|
+
await github.rest.issues.createComment({
|
|
336
|
+
owner: context.repo.owner,
|
|
337
|
+
repo: context.repo.repo,
|
|
338
|
+
issue_number: context.issue.number,
|
|
339
|
+
body: '❌ Failed to parse command.\n\nUsage: `@kody [full|rerun|status] <task-id> [--from <stage>] [--feedback "<text>"]`'
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
notify-orchestrate-error:
|
|
343
|
+
if: failure() && needs.orchestrate.result == 'failure'
|
|
344
|
+
needs: [orchestrate]
|
|
345
|
+
runs-on: ubuntu-latest
|
|
346
|
+
steps:
|
|
347
|
+
- uses: actions/github-script@v7
|
|
348
|
+
with:
|
|
349
|
+
script: |
|
|
350
|
+
const issueNumber = Number('${{ github.event.inputs.issue_number || needs.parse.outputs.issue_number || 0 }}');
|
|
351
|
+
if (issueNumber > 0) {
|
|
352
|
+
await github.rest.issues.createComment({
|
|
353
|
+
owner: context.repo.owner,
|
|
354
|
+
repo: context.repo.repo,
|
|
355
|
+
issue_number: issueNumber,
|
|
356
|
+
body: `❌ Pipeline failed. [View logs](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
# ─── Fix-CI Auto-trigger (workflow_run trigger) ─────────────────────────────
|
|
361
|
+
fix-ci-trigger:
|
|
362
|
+
if: >-
|
|
363
|
+
github.event_name == 'workflow_run' &&
|
|
364
|
+
github.event.workflow_run.conclusion == 'failure' &&
|
|
365
|
+
github.event.workflow_run.event == 'pull_request' &&
|
|
366
|
+
github.event.workflow_run.pull_requests[0]
|
|
367
|
+
runs-on: ubuntu-latest
|
|
368
|
+
timeout-minutes: 5
|
|
369
|
+
permissions:
|
|
370
|
+
issues: write
|
|
371
|
+
pull-requests: write
|
|
372
|
+
steps:
|
|
373
|
+
- name: Check loop guard and post fix-ci comment
|
|
374
|
+
uses: actions/github-script@v7
|
|
375
|
+
with:
|
|
376
|
+
script: |
|
|
377
|
+
const pr = context.payload.workflow_run.pull_requests[0];
|
|
378
|
+
const prNumber = pr.number;
|
|
379
|
+
|
|
380
|
+
// Check recent comments for existing fix-ci attempt (last 24h)
|
|
381
|
+
const comments = await github.rest.issues.listComments({
|
|
382
|
+
owner: context.repo.owner,
|
|
383
|
+
repo: context.repo.repo,
|
|
384
|
+
issue_number: prNumber,
|
|
385
|
+
per_page: 30,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
389
|
+
const recentFixCi = comments.data.filter(
|
|
390
|
+
(c) => c.body.includes('@kody fix-ci') && new Date(c.created_at) > oneDayAgo
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (recentFixCi.length >= 1) {
|
|
394
|
+
core.info('Loop guard: @kody fix-ci already commented in last 24h, skipping');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Check if last commit was from a bot (kody's previous fix attempt)
|
|
399
|
+
const commits = await github.rest.pulls.listCommits({
|
|
400
|
+
owner: context.repo.owner,
|
|
401
|
+
repo: context.repo.repo,
|
|
402
|
+
pull_number: prNumber,
|
|
403
|
+
per_page: 1,
|
|
404
|
+
});
|
|
405
|
+
const lastAuthor = commits.data[commits.data.length - 1]?.commit?.author?.name;
|
|
406
|
+
if (lastAuthor === 'github-actions[bot]' || lastAuthor === 'kody[bot]') {
|
|
407
|
+
core.info('Loop guard: last commit from bot, skipping');
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Post fix-ci comment
|
|
412
|
+
const runId = context.payload.workflow_run.id;
|
|
413
|
+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
|
|
414
|
+
await github.rest.issues.createComment({
|
|
415
|
+
owner: context.repo.owner,
|
|
416
|
+
repo: context.repo.repo,
|
|
417
|
+
issue_number: prNumber,
|
|
418
|
+
body: `@kody fix-ci\nCI failed: [View logs](${runUrl})\nRun ID: ${runId}`,
|
|
419
|
+
});
|
|
420
|
+
core.info(`Posted @kody fix-ci on PR #${prNumber} for run ${runId}`);
|
|
421
|
+
|
|
422
|
+
# ─── Smoke Test (push trigger) ──────────────────────────────────────────────
|
|
423
|
+
smoke-test:
|
|
424
|
+
if: github.event_name == 'push'
|
|
425
|
+
runs-on: ubuntu-latest
|
|
426
|
+
timeout-minutes: 10
|
|
427
|
+
steps:
|
|
428
|
+
- uses: actions/checkout@v4
|
|
429
|
+
- uses: pnpm/action-setup@v4
|
|
430
|
+
with:
|
|
431
|
+
version: latest
|
|
432
|
+
- uses: actions/setup-node@v4
|
|
433
|
+
with:
|
|
434
|
+
node-version: 22
|
|
435
|
+
cache: pnpm
|
|
436
|
+
- run: pnpm install --frozen-lockfile
|
|
437
|
+
- name: Typecheck
|
|
438
|
+
run: pnpm tsc --noEmit
|
|
439
|
+
- name: CLI loads
|
|
440
|
+
run: npx kody-engine-lite --help
|
|
441
|
+
- name: Dry run
|
|
442
|
+
run: |
|
|
443
|
+
mkdir -p .kody/tasks/smoke-test
|
|
444
|
+
echo "Smoke test task" > .kody/tasks/smoke-test/task.md
|
|
445
|
+
npx kody-engine-lite run --task-id smoke-test --dry-run || true
|
|
446
|
+
if [ -f ".kody/tasks/smoke-test/status.json" ]; then
|
|
447
|
+
echo "✓ status.json created"
|
|
448
|
+
cat .kody/tasks/smoke-test/status.json
|
|
449
|
+
fi
|
|
450
|
+
rm -rf .kody/tasks/smoke-test
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
List remote branches and identify stale ones that should be cleaned up.
|
|
2
|
+
|
|
3
|
+
1. Run `gh api repos/{{repo}}/branches --paginate --jq '.[] | {name: .name, protected: .protected}'` to get all branches.
|
|
4
|
+
2. For each non-protected branch, check its last commit date using `gh api repos/{{repo}}/commits?sha=BRANCH_NAME&per_page=1 --jq '.[0].commit.committer.date'`.
|
|
5
|
+
3. Flag any branch whose last commit is **older than 30 days**.
|
|
6
|
+
4. Ignore these branches: `main`, `dev`, `staging`, `production`, and any branch starting with `release/`.
|
|
7
|
+
5. Check if there is already an open issue with label `kody:watch:stale-branches` — if so, do NOT create a new one.
|
|
8
|
+
6. If stale branches are found, create **one** GitHub issue:
|
|
9
|
+
- Title: `Branch cleanup: N stale branches older than 30 days`
|
|
10
|
+
- Label: `kody:watch:stale-branches`
|
|
11
|
+
- Body: a markdown table with columns: Branch Name, Last Commit Date, Days Stale. End with the command to delete them: `git push origin --delete branch-name`.
|
|
12
|
+
|
|
13
|
+
If no stale branches are found, do not create an issue.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Audit the repository's dependencies for known vulnerabilities.
|
|
2
|
+
|
|
3
|
+
1. Read `package.json` to understand the project's dependency stack.
|
|
4
|
+
2. Run `npm audit --json` (or `pnpm audit --json` if pnpm-lock.yaml exists) to check for known vulnerabilities.
|
|
5
|
+
3. Focus only on **high** and **critical** severity vulnerabilities.
|
|
6
|
+
|
|
7
|
+
For each finding:
|
|
8
|
+
1. Check if there is already an open issue with label `kody:watch:vulnerability` mentioning the package name. If so, skip it.
|
|
9
|
+
2. Create a GitHub issue with:
|
|
10
|
+
- Title: `Vulnerability: <package-name> (<severity>)`
|
|
11
|
+
- Label: `kody:watch:vulnerability`
|
|
12
|
+
- Body containing: package name, current version, severity, advisory URL (if available), and suggested fix (upgrade command)
|
|
13
|
+
|
|
14
|
+
If no high/critical vulnerabilities are found, do not create any issues.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Check the repository's README.md for completeness and essential documentation.
|
|
2
|
+
|
|
3
|
+
1. Read `README.md` from the repository root. If it does not exist, that is a critical finding.
|
|
4
|
+
2. Check for the presence of these essential sections (look for headings or keywords):
|
|
5
|
+
- **Project description** — what the project does (first paragraph or a "## About" section)
|
|
6
|
+
- **Getting started / Installation** — how to install and set up
|
|
7
|
+
- **Usage / Running** — how to run the project (e.g. dev server, scripts)
|
|
8
|
+
- **Environment variables** — required env vars or reference to .env.example
|
|
9
|
+
- **Tech stack** — frameworks, languages, databases used
|
|
10
|
+
3. Also read `package.json` to understand what scripts are available and cross-reference with the README.
|
|
11
|
+
4. Check if there is already an open issue with label `kody:watch:readme-health` — if so, do NOT create a new one.
|
|
12
|
+
5. If any essential sections are missing, create **one** GitHub issue:
|
|
13
|
+
- Title: `README health: N missing sections`
|
|
14
|
+
- Label: `kody:watch:readme-health`
|
|
15
|
+
- Body: list which sections are present (with a checkmark) and which are missing, with a brief suggestion for each missing section.
|
|
16
|
+
|
|
17
|
+
If README is complete with all essential sections, do not create an issue.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
List all open pull requests. For each PR, check the last activity date (most recent of: last commit, last comment, last review).
|
|
2
|
+
|
|
3
|
+
Flag any PR that has had **no activity in the last 7 days** as stale.
|
|
4
|
+
|
|
5
|
+
For each stale PR:
|
|
6
|
+
1. First, check if there is already an open issue with the label `kody:watch:stale-pr` that mentions this PR number. If so, skip it.
|
|
7
|
+
2. Create a GitHub issue with:
|
|
8
|
+
- Title: `Stale PR #<number>: <pr-title>`
|
|
9
|
+
- Label: `kody:watch:stale-pr`
|
|
10
|
+
- Body containing: PR number, author, days since last activity, a link to the PR, and a suggested action (ping author, close, or merge)
|
|
11
|
+
|
|
12
|
+
Ignore PRs that have any of these labels: `on-hold`, `wip`, `draft`.
|
|
13
|
+
Do not flag draft PRs.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Scan the repository source code for TODO, FIXME, and HACK comments that indicate unfinished work or known issues.
|
|
2
|
+
|
|
3
|
+
1. Use `grep -rn "TODO\|FIXME\|HACK\|XXX" src/ --include="*.ts" --include="*.tsx"` to find all markers.
|
|
4
|
+
2. Group findings by type (TODO, FIXME, HACK).
|
|
5
|
+
3. Check if there is already an open issue with label `kody:watch:todo-scan` — if so, do NOT create a new one.
|
|
6
|
+
4. If no existing issue, create **one** GitHub issue summarizing all findings:
|
|
7
|
+
- Title: `Code health: N unresolved TODO/FIXME/HACK markers found`
|
|
8
|
+
- Label: `kody:watch:todo-scan`
|
|
9
|
+
- Body: a markdown table with columns: Type, File, Line, Comment text
|
|
10
|
+
- End with a brief recommendation to address high-priority items (FIXME, HACK) first.
|