@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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/dist/agent-runner.d.ts +4 -0
  4. package/dist/agent-runner.js +122 -0
  5. package/dist/bin/cli.js +11276 -0
  6. package/dist/ci/parse-inputs.d.ts +6 -0
  7. package/dist/ci/parse-inputs.js +76 -0
  8. package/dist/ci/parse-safety.d.ts +6 -0
  9. package/dist/ci/parse-safety.js +22 -0
  10. package/dist/cli/args.d.ts +13 -0
  11. package/dist/cli/args.js +42 -0
  12. package/dist/cli/litellm.d.ts +2 -0
  13. package/dist/cli/litellm.js +85 -0
  14. package/dist/cli/task-resolution.d.ts +2 -0
  15. package/dist/cli/task-resolution.js +41 -0
  16. package/dist/config.d.ts +49 -0
  17. package/dist/config.js +72 -0
  18. package/dist/context.d.ts +4 -0
  19. package/dist/context.js +83 -0
  20. package/dist/definitions.d.ts +3 -0
  21. package/dist/definitions.js +59 -0
  22. package/dist/entry.d.ts +1 -0
  23. package/dist/entry.js +236 -0
  24. package/dist/git-utils.d.ts +13 -0
  25. package/dist/git-utils.js +174 -0
  26. package/dist/github-api.d.ts +14 -0
  27. package/dist/github-api.js +114 -0
  28. package/dist/kody-utils.d.ts +1 -0
  29. package/dist/kody-utils.js +9 -0
  30. package/dist/learning/auto-learn.d.ts +2 -0
  31. package/dist/learning/auto-learn.js +169 -0
  32. package/dist/logger.d.ts +14 -0
  33. package/dist/logger.js +51 -0
  34. package/dist/memory.d.ts +1 -0
  35. package/dist/memory.js +20 -0
  36. package/dist/observer.d.ts +9 -0
  37. package/dist/observer.js +80 -0
  38. package/dist/pipeline/complexity.d.ts +3 -0
  39. package/dist/pipeline/complexity.js +12 -0
  40. package/dist/pipeline/executor-registry.d.ts +3 -0
  41. package/dist/pipeline/executor-registry.js +20 -0
  42. package/dist/pipeline/hooks.d.ts +17 -0
  43. package/dist/pipeline/hooks.js +110 -0
  44. package/dist/pipeline/questions.d.ts +2 -0
  45. package/dist/pipeline/questions.js +44 -0
  46. package/dist/pipeline/runner-selection.d.ts +2 -0
  47. package/dist/pipeline/runner-selection.js +13 -0
  48. package/dist/pipeline/state.d.ts +4 -0
  49. package/dist/pipeline/state.js +37 -0
  50. package/dist/pipeline.d.ts +3 -0
  51. package/dist/pipeline.js +213 -0
  52. package/dist/preflight.d.ts +1 -0
  53. package/dist/preflight.js +69 -0
  54. package/dist/retrospective.d.ts +26 -0
  55. package/dist/retrospective.js +211 -0
  56. package/dist/stages/agent.d.ts +2 -0
  57. package/dist/stages/agent.js +94 -0
  58. package/dist/stages/gate.d.ts +2 -0
  59. package/dist/stages/gate.js +32 -0
  60. package/dist/stages/review.d.ts +2 -0
  61. package/dist/stages/review.js +32 -0
  62. package/dist/stages/ship.d.ts +3 -0
  63. package/dist/stages/ship.js +154 -0
  64. package/dist/stages/verify.d.ts +2 -0
  65. package/dist/stages/verify.js +94 -0
  66. package/dist/types.d.ts +61 -0
  67. package/dist/types.js +1 -0
  68. package/dist/validators.d.ts +8 -0
  69. package/dist/validators.js +42 -0
  70. package/dist/verify-runner.d.ts +11 -0
  71. package/dist/verify-runner.js +110 -0
  72. package/kody.config.schema.json +299 -0
  73. package/package.json +39 -0
  74. package/prompts/autofix.md +52 -0
  75. package/prompts/build.md +26 -0
  76. package/prompts/decompose.md +77 -0
  77. package/prompts/plan.md +65 -0
  78. package/prompts/review-fix.md +27 -0
  79. package/prompts/review.md +115 -0
  80. package/prompts/taskify-ticket.md +122 -0
  81. package/prompts/taskify.md +70 -0
  82. package/templates/kody-watch.yml +57 -0
  83. package/templates/kody.yml +450 -0
  84. package/templates/watch-agents/branch-cleanup/agent.json +7 -0
  85. package/templates/watch-agents/branch-cleanup/agent.md +13 -0
  86. package/templates/watch-agents/dependency-checker/agent.json +7 -0
  87. package/templates/watch-agents/dependency-checker/agent.md +14 -0
  88. package/templates/watch-agents/readme-health/agent.json +7 -0
  89. package/templates/watch-agents/readme-health/agent.md +17 -0
  90. package/templates/watch-agents/stale-pr-reviewer/agent.json +7 -0
  91. package/templates/watch-agents/stale-pr-reviewer/agent.md +13 -0
  92. package/templates/watch-agents/todo-scanner/agent.json +7 -0
  93. 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,7 @@
1
+ {
2
+ "name": "branch-cleanup",
3
+ "description": "Finds stale remote branches with no recent activity and suggests cleanup",
4
+ "schedule": {
5
+ "every": 96
6
+ }
7
+ }
@@ -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,7 @@
1
+ {
2
+ "name": "dependency-checker",
3
+ "description": "Checks for outdated or vulnerable dependencies and creates tracking issues",
4
+ "schedule": {
5
+ "every": 96
6
+ }
7
+ }
@@ -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,7 @@
1
+ {
2
+ "name": "readme-health",
3
+ "description": "Checks if README.md exists and documents essential sections",
4
+ "schedule": {
5
+ "every": 96
6
+ }
7
+ }
@@ -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,7 @@
1
+ {
2
+ "name": "stale-pr-reviewer",
3
+ "description": "Finds pull requests with no recent activity and creates tracking issues",
4
+ "schedule": {
5
+ "every": 48
6
+ }
7
+ }
@@ -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,7 @@
1
+ {
2
+ "name": "todo-scanner",
3
+ "description": "Scans source code for TODO, FIXME, and HACK comments and creates tracking issues",
4
+ "schedule": {
5
+ "every": 48
6
+ }
7
+ }
@@ -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.