kodelyth-ecc 1.5.10 → 1.7.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 (123) hide show
  1. package/.github/workflows/publish.yml +19 -0
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +447 -0
  4. package/CLAUDE.md +48 -16
  5. package/README.md +265 -81
  6. package/VERSION +1 -1
  7. package/actions/ecc-review/README.md +243 -0
  8. package/actions/ecc-review/action.yml +158 -0
  9. package/actions/ecc-review/post-comment.js +171 -0
  10. package/actions/ecc-review/run-review.js +285 -0
  11. package/agents/backdoor-hunter.md +260 -0
  12. package/agents/chaos-engineer.md +251 -0
  13. package/agents/code-stealer-detector.md +228 -0
  14. package/agents/jailbreak-tester.md +222 -0
  15. package/agents/license-violation-finder.md +212 -0
  16. package/agents/prompt-injection-hunter.md +126 -0
  17. package/agents/secret-hunter.md +209 -0
  18. package/agents/supply-chain-auditor.md +195 -0
  19. package/bin/kodelyth-ecc.js +875 -1
  20. package/bundles/enterprise.md +172 -0
  21. package/bundles/indie-hacker.md +106 -0
  22. package/bundles/red-team.md +138 -0
  23. package/cat +0 -0
  24. package/commands/dashboard.md +67 -0
  25. package/commands/devil-mode.md +121 -0
  26. package/commands/memory-evolve.md +71 -0
  27. package/commands/replay.md +61 -0
  28. package/commands/route-model.md +48 -0
  29. package/commands/swarm.md +68 -0
  30. package/commands/verify-supply-chain.md +59 -0
  31. package/docs/dashboard.md +211 -0
  32. package/docs/evolve.md +303 -0
  33. package/docs/mcp-clients.md +167 -0
  34. package/docs/mcp.md +178 -0
  35. package/docs/replay.md +244 -0
  36. package/docs/supply-chain.md +207 -0
  37. package/docs/swarm.md +243 -0
  38. package/hooks/hooks.json +52 -0
  39. package/hooks/memory/auto-recall.js +29 -1
  40. package/hooks/safety/README.md +124 -0
  41. package/hooks/safety/lib/patterns.js +179 -0
  42. package/hooks/safety/prompt-injection-guard.js +179 -0
  43. package/hooks/safety/token-budget.js +229 -0
  44. package/install.ps1 +199 -2
  45. package/install.sh +285 -3
  46. package/package.json +30 -4
  47. package/rules/common/agent-intent-routing.md +182 -0
  48. package/rules/common/cost-aware-model-routing.md +152 -0
  49. package/scripts/dashboard/data.js +353 -0
  50. package/scripts/dashboard/server.js +324 -0
  51. package/scripts/dashboard/static/index.html +645 -0
  52. package/scripts/evolve/analyze.js +303 -0
  53. package/scripts/evolve/proposals.js +162 -0
  54. package/scripts/evolve/stats.js +219 -0
  55. package/scripts/mcp/catalog.js +244 -0
  56. package/scripts/mcp/client.js +181 -0
  57. package/scripts/mcp/prompts.js +133 -0
  58. package/scripts/mcp/resources.js +94 -0
  59. package/scripts/mcp/server.js +153 -0
  60. package/scripts/mcp/tools.js +465 -0
  61. package/scripts/replay/bundle.js +191 -0
  62. package/scripts/replay/replay.js +107 -0
  63. package/scripts/router/classify.js +232 -0
  64. package/scripts/supply-chain/manifest.js +154 -0
  65. package/scripts/supply-chain/sbom.js +202 -0
  66. package/scripts/supply-chain/verify.js +102 -0
  67. package/scripts/swarm/build-plan.js +193 -0
  68. package/skills/cost-aware-model-routing/SKILL.md +153 -0
  69. package/skills/kodelyth-quickstart/SKILL.md +7 -0
  70. package/skills/observability-dashboard/SKILL.md +119 -0
  71. package/skills/self-evolving-memory/SKILL.md +175 -0
  72. package/skills/session-replay/SKILL.md +199 -0
  73. package/skills/supply-chain-verification/SKILL.md +201 -0
  74. package/skills/swarm-orchestrator/SKILL.md +177 -0
  75. package/social/card-install.svg +1 -1
  76. package/social/facebook-group/POST.md +121 -0
  77. package/social/facebook-group/fb-1-3am-debug.png +0 -0
  78. package/social/facebook-group/fb-1-3am-debug.svg +97 -0
  79. package/social/facebook-group/fb-2-cpu-upgrade.png +0 -0
  80. package/social/facebook-group/fb-2-cpu-upgrade.svg +132 -0
  81. package/social/facebook-group/fb-3-before-after.png +0 -0
  82. package/social/facebook-group/fb-3-before-after.svg +94 -0
  83. package/social/facebook-v150.svg +5 -5
  84. package/social/github-social-preview.svg +119 -100
  85. package/social/readme-hero.svg +11 -11
  86. package/social/section-agents.svg +57 -0
  87. package/social/section-author.svg +54 -0
  88. package/social/section-dashboard.svg +59 -0
  89. package/social/section-devil.svg +54 -0
  90. package/social/section-hooks.svg +51 -0
  91. package/social/section-install.svg +42 -0
  92. package/social/section-learning.svg +52 -0
  93. package/social/section-mcp.svg +46 -0
  94. package/social/section-memory.svg +57 -0
  95. package/social/section-parallel.svg +72 -0
  96. package/social/section-routing.svg +51 -0
  97. package/social/x-card-agents-grid.svg +6 -6
  98. package/tests/dashboard/data.test.js +235 -0
  99. package/tests/dashboard/server.test.js +240 -0
  100. package/tests/evolve/analyze.test.js +169 -0
  101. package/tests/evolve/proposals.test.js +173 -0
  102. package/tests/evolve/stats.test.js +159 -0
  103. package/tests/mcp/catalog.test.js +98 -0
  104. package/tests/mcp/client.test.js +109 -0
  105. package/tests/mcp/resources-prompts.test.js +70 -0
  106. package/tests/mcp/tools.test.js +159 -0
  107. package/tests/replay/bundle.test.js +181 -0
  108. package/tests/replay/replay.test.js +131 -0
  109. package/tests/router/classify.test.js +164 -0
  110. package/tests/safety/patterns.test.js +94 -0
  111. package/tests/safety/prompt-injection-guard.test.js +111 -0
  112. package/tests/safety/token-budget.test.js +119 -0
  113. package/tests/supply-chain/manifest.test.js +147 -0
  114. package/tests/supply-chain/sbom.test.js +170 -0
  115. package/tests/supply-chain/verify.test.js +146 -0
  116. package/tests/swarm/build-plan.test.js +188 -0
  117. package/wiki/Agent-Reference.md +58 -7
  118. package/wiki/FAQ.md +204 -7
  119. package/wiki/Home.md +104 -28
  120. package/wiki/Hook-Reference.md +1 -1
  121. package/wiki/Installation-Guide.md +109 -6
  122. package/wiki/Platform-Support.md +239 -25
  123. package/wiki/Skill-Reference.md +101 -6
@@ -0,0 +1,243 @@
1
+ # Kodelyth ECC — PR Review GitHub Action
2
+
3
+ A one-line GitHub Action that runs the Kodelyth ECC adversarial agent crew against your pull-request diff, posts findings as a PR comment, and optionally fails CI on critical issues.
4
+
5
+ > **Phase 1.6 of the Devil Roadmap.** Bigger reach lever than the VS Code extension — every GitHub repo is a potential install target.
6
+
7
+ ---
8
+
9
+ ## Quick Start
10
+
11
+ Drop this into `.github/workflows/ecc-review.yml` in any repo:
12
+
13
+ ```yaml
14
+ name: Kodelyth ECC Review
15
+
16
+ on:
17
+ pull_request:
18
+ branches: [main, master]
19
+
20
+ permissions:
21
+ contents: read
22
+ pull-requests: write # required to post the review comment
23
+
24
+ jobs:
25
+ review:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ with:
30
+ fetch-depth: 0
31
+
32
+ - uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.6.0
33
+ with:
34
+ bundle: red-team
35
+ fail-on: critical
36
+ api-key: ${{ secrets.ANTHROPIC_API_KEY }}
37
+ ```
38
+
39
+ That's it. Every PR now gets reviewed by ECC's adversarial crew and the findings appear as a sticky PR comment.
40
+
41
+ ---
42
+
43
+ ## Inputs
44
+
45
+ | Input | Default | Description |
46
+ |---|---|---|
47
+ | `bundle` | `red-team` | Which ECC bundle: `indie-hacker` / `red-team` / `enterprise` |
48
+ | `agents` | (none) | Comma-separated agent names. Overrides `bundle` if set. Example: `prompt-injection-hunter,secret-hunter,backdoor-hunter` |
49
+ | `fail-on` | `critical` | Severity threshold that fails the build: `critical` / `high` / `medium` / `none` |
50
+ | `api-key` | (none) | Anthropic API key. Pass via `${{ secrets.ANTHROPIC_API_KEY }}`. **Required** for live model review. |
51
+ | `model` | `claude-sonnet-4-5-20250929` | Claude model to use |
52
+ | `comment` | `true` | Post findings as a PR comment |
53
+ | `max-files` | `20` | Max files reviewed per run (cost guard) |
54
+ | `ecc-version` | `latest` | Pin a specific Kodelyth ECC version (e.g., `1.6.0`) |
55
+
56
+ ## Outputs
57
+
58
+ | Output | Description |
59
+ |---|---|
60
+ | `critical-count` | Number of CRITICAL findings |
61
+ | `high-count` | Number of HIGH findings |
62
+ | `medium-count` | Number of MEDIUM findings |
63
+ | `report-path` | Path to the JSON report on the runner |
64
+
65
+ ---
66
+
67
+ ## Bundle → Agents Mapping
68
+
69
+ | Bundle | Agents Run on Each PR |
70
+ |---|---|
71
+ | `indie-hacker` | `security-reviewer` · `code-reviewer` · `ux-reviewer` · `dependency-doctor` |
72
+ | `red-team` (default) | `prompt-injection-hunter` · `supply-chain-auditor` · `secret-hunter` · `backdoor-hunter` |
73
+ | `enterprise` | `code-reviewer` · `security-reviewer` · `license-violation-finder` · `supply-chain-auditor` · `api-guardian` |
74
+
75
+ To run a custom set, use `agents:` instead:
76
+
77
+ ```yaml
78
+ - uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.6.0
79
+ with:
80
+ agents: secret-hunter,license-violation-finder,jailbreak-tester
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Real-World Example Workflows
86
+
87
+ ### 1. Indie Hacker — fast feedback, never block
88
+
89
+ ```yaml
90
+ - uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.6.0
91
+ with:
92
+ bundle: indie-hacker
93
+ fail-on: none
94
+ api-key: ${{ secrets.ANTHROPIC_API_KEY }}
95
+ ```
96
+
97
+ PR gets a comment, but CI never fails. You decide what to address.
98
+
99
+ ### 2. Red Team — full adversarial sweep, block on critical
100
+
101
+ ```yaml
102
+ - uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.6.0
103
+ with:
104
+ bundle: red-team
105
+ fail-on: critical
106
+ api-key: ${{ secrets.ANTHROPIC_API_KEY }}
107
+ ```
108
+
109
+ Default. Recommended for any user-facing product.
110
+
111
+ ### 3. Enterprise — strict, block on high
112
+
113
+ ```yaml
114
+ - uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.6.0
115
+ with:
116
+ bundle: enterprise
117
+ fail-on: high
118
+ api-key: ${{ secrets.ANTHROPIC_API_KEY }}
119
+ ```
120
+
121
+ ### 4. Pre-public-release — secret + license + AI safety only
122
+
123
+ ```yaml
124
+ - uses: sifxprime/kodelyth-ecc/actions/ecc-review@v1.6.0
125
+ with:
126
+ agents: secret-hunter,license-violation-finder,prompt-injection-hunter
127
+ fail-on: critical
128
+ api-key: ${{ secrets.ANTHROPIC_API_KEY }}
129
+ ```
130
+
131
+ ### 5. Cost-controlled — only review on changes to security-sensitive paths
132
+
133
+ ```yaml
134
+ on:
135
+ pull_request:
136
+ paths:
137
+ - 'src/auth/**'
138
+ - 'src/api/**'
139
+ - 'src/payments/**'
140
+ - 'package*.json'
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Cost & Rate-Limit Guards
146
+
147
+ The action ships with sensible defaults to keep your Anthropic spend under control:
148
+
149
+ - `max-files: 20` — caps how many changed files get sent per run
150
+ - Each file capped at 20KB per send (truncated with marker)
151
+ - Default `red-team` bundle = 4 agents = 4 API calls per PR
152
+ - Each call uses `max_tokens: 4096`
153
+ - A typical PR review: 4 calls × ~6k input + 4k output tokens ≈ ~$0.06-0.15 with Sonnet
154
+
155
+ For larger codebases or stricter cost control:
156
+
157
+ ```yaml
158
+ with:
159
+ max-files: 10
160
+ agents: secret-hunter # single agent only
161
+ ecc-version: 1.6.0 # pin version for reproducibility
162
+ ```
163
+
164
+ ---
165
+
166
+ ## How It Works
167
+
168
+ 1. **Setup** — installs the latest (or pinned) Kodelyth ECC into the runner via `npx kodelyth-ecc --target claude-home --bundle <bundle>`
169
+ 2. **Diff** — resolves the changed files between PR base and head SHAs
170
+ 3. **Filter** — drops binary, vendored, and oversized files; caps at `max-files`
171
+ 4. **Review loop** — for each agent in the bundle:
172
+ - Loads the agent's playbook from `~/.claude/agents/<name>.md`
173
+ - Sends `[playbook + changed files + JSON output schema]` to Claude
174
+ - Parses the structured findings response
175
+ 5. **Aggregate** — combines all findings, tallies severities
176
+ 6. **Comment** — posts a single PR comment (upserts on subsequent pushes — never spammy)
177
+ 7. **Gate** — exits non-zero if findings exceed `fail-on` threshold
178
+
179
+ ---
180
+
181
+ ## What This Action Does NOT Do
182
+
183
+ - ❌ Auto-fix issues (advisory only)
184
+ - ❌ Send any data anywhere except Anthropic API + GitHub PR comments
185
+ - ❌ Cache anything between runs (each PR = clean slate)
186
+ - ❌ Run on commits that don't touch reviewable file types
187
+
188
+ ---
189
+
190
+ ## Privacy & Security
191
+
192
+ - **Code never leaves your control:** PR diff is sent to Anthropic API (using your key) and GitHub PR comments (using `github.token`). Nothing else.
193
+ - **No telemetry:** This action sends no analytics anywhere.
194
+ - **No persistence:** No data stored beyond the runtime of the workflow.
195
+ - **API key:** Pass via `secrets.*` only. Never hardcode.
196
+
197
+ If your repo has compliance requirements that prohibit sending code to Anthropic, **don't use this action**. Run ECC locally instead.
198
+
199
+ ---
200
+
201
+ ## Troubleshooting
202
+
203
+ ### "No ANTHROPIC_API_KEY set — review skipped"
204
+
205
+ You forgot to pass the secret. Add it under repo Settings → Secrets and variables → Actions, then reference it in the workflow:
206
+
207
+ ```yaml
208
+ api-key: ${{ secrets.ANTHROPIC_API_KEY }}
209
+ ```
210
+
211
+ ### "::warning::Agent file not found"
212
+
213
+ The bundle resolved an agent name that doesn't exist in the installed ECC. Pin a known-good version:
214
+
215
+ ```yaml
216
+ ecc-version: 1.6.0
217
+ ```
218
+
219
+ ### "Failed to post PR comment: 403"
220
+
221
+ Your workflow needs `pull-requests: write` permission. Add to top of workflow:
222
+
223
+ ```yaml
224
+ permissions:
225
+ contents: read
226
+ pull-requests: write
227
+ ```
228
+
229
+ ### Comment is too long (GitHub 65k limit)
230
+
231
+ Reduce findings by tightening `agents:` or `max-files:`. The action caps to first 30 findings in the comment but writes the full report to the runner artifacts (downloadable from Actions tab).
232
+
233
+ ---
234
+
235
+ ## Related
236
+
237
+ - [Kodelyth ECC repo](https://github.com/sifxprime/kodelyth-ecc) — the toolkit this action runs
238
+ - [`/devil-mode` command](../../commands/devil-mode.md) — same agents, run locally
239
+ - [Devil Roadmap](../../) — the broader v1.6.0 → v2.0.0 plan
240
+
241
+ ---
242
+
243
+ **Powered by Kodelyth ECC** · MIT license · No telemetry · No cloud
@@ -0,0 +1,158 @@
1
+ name: 'Kodelyth ECC PR Review'
2
+ description: 'Run Kodelyth ECC adversarial agents against a PR diff. Posts findings as a PR comment, optionally fails CI on critical issues.'
3
+ author: 'Kodelyth <github.com/sifxprime>'
4
+ branding:
5
+ icon: 'shield'
6
+ color: 'red'
7
+
8
+ inputs:
9
+ bundle:
10
+ description: 'Which ECC bundle to use: indie-hacker, red-team, enterprise'
11
+ required: false
12
+ default: 'red-team'
13
+ agents:
14
+ description: 'Comma-separated agent names to run. Overrides bundle if set. Examples: prompt-injection-hunter,supply-chain-auditor,secret-hunter'
15
+ required: false
16
+ default: ''
17
+ fail-on:
18
+ description: 'Severity threshold that fails CI: critical | high | medium | none'
19
+ required: false
20
+ default: 'critical'
21
+ api-key:
22
+ description: 'Anthropic API key (recommended). Falls back to OPENAI_API_KEY if not set.'
23
+ required: false
24
+ default: ''
25
+ model:
26
+ description: 'Claude model to use'
27
+ required: false
28
+ default: 'claude-sonnet-4-5-20250929'
29
+ comment:
30
+ description: 'Post findings as a PR comment'
31
+ required: false
32
+ default: 'true'
33
+ max-files:
34
+ description: 'Max number of changed files to review per run (cost guard)'
35
+ required: false
36
+ default: '20'
37
+ ecc-version:
38
+ description: 'Pin to a specific ECC version (default: latest from npm)'
39
+ required: false
40
+ default: 'latest'
41
+
42
+ outputs:
43
+ critical-count:
44
+ description: 'Number of CRITICAL findings'
45
+ value: ${{ steps.review.outputs.critical-count }}
46
+ high-count:
47
+ description: 'Number of HIGH findings'
48
+ value: ${{ steps.review.outputs.high-count }}
49
+ medium-count:
50
+ description: 'Number of MEDIUM findings'
51
+ value: ${{ steps.review.outputs.medium-count }}
52
+ report-path:
53
+ description: 'Filesystem path of the JSON findings report'
54
+ value: ${{ steps.review.outputs.report-path }}
55
+
56
+ runs:
57
+ using: 'composite'
58
+ steps:
59
+ - name: Setup Node 20
60
+ uses: actions/setup-node@v4
61
+ with:
62
+ node-version: '20'
63
+
64
+ - name: Install Kodelyth ECC
65
+ shell: bash
66
+ run: |
67
+ echo "::group::Install Kodelyth ECC"
68
+ npx --yes kodelyth-ecc@${{ inputs.ecc-version }} \
69
+ --target claude-home \
70
+ --bundle ${{ inputs.bundle }}
71
+ echo "::endgroup::"
72
+
73
+ - name: Resolve PR diff
74
+ id: diff
75
+ shell: bash
76
+ run: |
77
+ echo "::group::Resolve PR diff"
78
+ if [[ "${{ github.event_name }}" == "pull_request" ]]; then
79
+ BASE_SHA="${{ github.event.pull_request.base.sha }}"
80
+ HEAD_SHA="${{ github.event.pull_request.head.sha }}"
81
+ else
82
+ # push event — diff against previous commit
83
+ BASE_SHA="${{ github.event.before }}"
84
+ HEAD_SHA="${{ github.sha }}"
85
+ fi
86
+ echo "base-sha=$BASE_SHA" >> "$GITHUB_OUTPUT"
87
+ echo "head-sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
88
+ git fetch origin "$BASE_SHA" --depth=1 2>/dev/null || true
89
+ git diff --name-only "$BASE_SHA" "$HEAD_SHA" > /tmp/ecc-changed-files.txt || \
90
+ git diff --name-only "$HEAD_SHA~1" "$HEAD_SHA" > /tmp/ecc-changed-files.txt
91
+ echo "Changed files:"
92
+ cat /tmp/ecc-changed-files.txt
93
+ echo "::endgroup::"
94
+
95
+ - name: Run ECC review
96
+ id: review
97
+ shell: bash
98
+ env:
99
+ ANTHROPIC_API_KEY: ${{ inputs.api-key }}
100
+ ECC_BUNDLE: ${{ inputs.bundle }}
101
+ ECC_AGENTS: ${{ inputs.agents }}
102
+ ECC_MODEL: ${{ inputs.model }}
103
+ ECC_FAIL_ON: ${{ inputs.fail-on }}
104
+ ECC_MAX_FILES: ${{ inputs.max-files }}
105
+ ECC_BASE_SHA: ${{ steps.diff.outputs.base-sha }}
106
+ ECC_HEAD_SHA: ${{ steps.diff.outputs.head-sha }}
107
+ GITHUB_REPOSITORY: ${{ github.repository }}
108
+ GITHUB_TOKEN: ${{ github.token }}
109
+ PR_NUMBER: ${{ github.event.pull_request.number }}
110
+ run: |
111
+ node ${{ github.action_path }}/run-review.js
112
+
113
+ - name: Post PR comment
114
+ if: ${{ inputs.comment == 'true' && github.event_name == 'pull_request' && steps.review.outputs.report-path != '' }}
115
+ shell: bash
116
+ env:
117
+ GITHUB_TOKEN: ${{ github.token }}
118
+ REPORT_PATH: ${{ steps.review.outputs.report-path }}
119
+ PR_NUMBER: ${{ github.event.pull_request.number }}
120
+ REPO: ${{ github.repository }}
121
+ run: |
122
+ node ${{ github.action_path }}/post-comment.js
123
+
124
+ - name: Enforce fail-on threshold
125
+ shell: bash
126
+ env:
127
+ FAIL_ON: ${{ inputs.fail-on }}
128
+ CRITICAL: ${{ steps.review.outputs.critical-count }}
129
+ HIGH: ${{ steps.review.outputs.high-count }}
130
+ MEDIUM: ${{ steps.review.outputs.medium-count }}
131
+ run: |
132
+ critical=${CRITICAL:-0}
133
+ high=${HIGH:-0}
134
+ medium=${MEDIUM:-0}
135
+ case "$FAIL_ON" in
136
+ critical)
137
+ if [[ "$critical" -gt 0 ]]; then
138
+ echo "::error::Found $critical CRITICAL issues. Failing build (fail-on=critical)."
139
+ exit 1
140
+ fi ;;
141
+ high)
142
+ if [[ "$critical" -gt 0 ]] || [[ "$high" -gt 0 ]]; then
143
+ echo "::error::Found $critical CRITICAL + $high HIGH issues. Failing build (fail-on=high)."
144
+ exit 1
145
+ fi ;;
146
+ medium)
147
+ if [[ "$critical" -gt 0 ]] || [[ "$high" -gt 0 ]] || [[ "$medium" -gt 0 ]]; then
148
+ echo "::error::Found $critical CRITICAL + $high HIGH + $medium MEDIUM issues. Failing build (fail-on=medium)."
149
+ exit 1
150
+ fi ;;
151
+ none)
152
+ echo "::notice::Review complete. fail-on=none — never blocking."
153
+ ;;
154
+ *)
155
+ echo "::warning::Unknown fail-on value: $FAIL_ON. Treating as 'none'."
156
+ ;;
157
+ esac
158
+ echo "::notice::ECC review complete: $critical CRITICAL, $high HIGH, $medium MEDIUM."
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ // Kodelyth ECC — GitHub Action PR comment poster
3
+ // Reads the JSON report and posts a formatted PR comment via GitHub API.
4
+ //
5
+ // Env contract:
6
+ // GITHUB_TOKEN Provided by github.token
7
+ // REPORT_PATH JSON report file
8
+ // PR_NUMBER PR number
9
+ // REPO owner/repo
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const https = require('https');
15
+
16
+ const reportPath = process.env.REPORT_PATH;
17
+ const prNumber = process.env.PR_NUMBER;
18
+ const repo = process.env.REPO;
19
+ const token = process.env.GITHUB_TOKEN;
20
+
21
+ if (!reportPath || !fs.existsSync(reportPath)) {
22
+ console.log('::warning::No report found, skipping PR comment.');
23
+ process.exit(0);
24
+ }
25
+ if (!prNumber || !repo || !token) {
26
+ console.log('::warning::Missing PR_NUMBER, REPO, or GITHUB_TOKEN. Skipping comment.');
27
+ process.exit(0);
28
+ }
29
+
30
+ const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
31
+
32
+ // ── Format PR comment ───────────────────────────────────────────────────────
33
+ function severityEmoji(sev) {
34
+ const s = (sev || '').toLowerCase();
35
+ if (s === 'critical') return '🔴';
36
+ if (s === 'high') return '🟠';
37
+ if (s === 'medium') return '🟡';
38
+ if (s === 'low') return '🔵';
39
+ return '⚪';
40
+ }
41
+
42
+ function buildComment() {
43
+ const c = report.counts || { critical: 0, high: 0, medium: 0, low: 0 };
44
+ const total = (c.critical || 0) + (c.high || 0) + (c.medium || 0) + (c.low || 0);
45
+ const verdict = total === 0
46
+ ? '✅ **Clean.** No issues found by the selected agents.'
47
+ : `**${total}** finding${total === 1 ? '' : 's'} across ${(report.agents || []).length} agents.`;
48
+
49
+ const summaryTable = `
50
+ | Severity | Count |
51
+ |---|---|
52
+ | 🔴 Critical | ${c.critical || 0} |
53
+ | 🟠 High | ${c.high || 0} |
54
+ | 🟡 Medium | ${c.medium || 0} |
55
+ | 🔵 Low | ${c.low || 0} |
56
+ `.trim();
57
+
58
+ const findings = (report.findings || []).slice(0, 30); // cap to first 30 to fit comment limits
59
+ const findingsBlock = findings.length === 0 ? '' : `
60
+
61
+ ## Findings
62
+
63
+ ${findings.map((f, i) => {
64
+ const emoji = severityEmoji(f.severity);
65
+ const loc = f.line ? `\`${f.file}:${f.line}\`` : `\`${f.file || 'unknown'}\``;
66
+ return `### ${i + 1}. ${emoji} ${(f.severity || 'unknown').toUpperCase()} — ${f.title || '(no title)'}
67
+ **Agent:** \`${f.agent || 'unknown'}\` &nbsp;·&nbsp; **Location:** ${loc} &nbsp;·&nbsp; **Category:** ${f.category || 'general'}
68
+
69
+ ${f.description || ''}
70
+
71
+ ${f.evidence ? `<details><summary>Evidence</summary>\n\n\`\`\`\n${(f.evidence || '').slice(0, 1000)}\n\`\`\`\n\n</details>` : ''}
72
+
73
+ **Recommended fix:** ${f.fix || '(none provided)'}
74
+ `;
75
+ }).join('\n---\n')}
76
+ ${(report.findings || []).length > 30 ? `\n_…and ${(report.findings || []).length - 30} more findings (see full report artifact)._\n` : ''}`;
77
+
78
+ return `## 🦞 Kodelyth ECC — Devil Mode Review
79
+
80
+ ${verdict}
81
+
82
+ **Bundle:** \`${report.bundle || 'red-team'}\` &nbsp;·&nbsp; **Agents:** ${(report.agents || []).map(a => `\`${a}\``).join(', ')} &nbsp;·&nbsp; **Files reviewed:** ${(report.files_reviewed || []).length}
83
+
84
+ ${summaryTable}
85
+ ${findingsBlock}
86
+
87
+ ---
88
+
89
+ <sub>Powered by [Kodelyth ECC](https://github.com/sifxprime/kodelyth-ecc) · [Configure this action](https://github.com/sifxprime/kodelyth-ecc/tree/main/actions/ecc-review)</sub>`;
90
+ }
91
+
92
+ // ── Find existing comment from this action (for upsert) ─────────────────────
93
+ function listComments() {
94
+ return new Promise((resolve, reject) => {
95
+ const req = https.request({
96
+ hostname: 'api.github.com',
97
+ path: `/repos/${repo}/issues/${prNumber}/comments?per_page=100`,
98
+ method: 'GET',
99
+ headers: {
100
+ 'Authorization': `token ${token}`,
101
+ 'User-Agent': 'kodelyth-ecc-action',
102
+ 'Accept': 'application/vnd.github+json',
103
+ },
104
+ }, (res) => {
105
+ let data = '';
106
+ res.on('data', c => data += c);
107
+ res.on('end', () => {
108
+ try { resolve(JSON.parse(data)); }
109
+ catch (e) { reject(e); }
110
+ });
111
+ });
112
+ req.on('error', reject);
113
+ req.end();
114
+ });
115
+ }
116
+
117
+ function upsertComment(body, existingId) {
118
+ return new Promise((resolve, reject) => {
119
+ const payload = JSON.stringify({ body });
120
+ const opts = existingId ? {
121
+ hostname: 'api.github.com',
122
+ path: `/repos/${repo}/issues/comments/${existingId}`,
123
+ method: 'PATCH',
124
+ } : {
125
+ hostname: 'api.github.com',
126
+ path: `/repos/${repo}/issues/${prNumber}/comments`,
127
+ method: 'POST',
128
+ };
129
+ opts.headers = {
130
+ 'Authorization': `token ${token}`,
131
+ 'User-Agent': 'kodelyth-ecc-action',
132
+ 'Accept': 'application/vnd.github+json',
133
+ 'Content-Type': 'application/json',
134
+ 'Content-Length': Buffer.byteLength(payload),
135
+ };
136
+ const req = https.request(opts, (res) => {
137
+ let data = '';
138
+ res.on('data', c => data += c);
139
+ res.on('end', () => {
140
+ if (res.statusCode >= 200 && res.statusCode < 300) {
141
+ resolve(JSON.parse(data));
142
+ } else {
143
+ reject(new Error(`GitHub API ${res.statusCode}: ${data}`));
144
+ }
145
+ });
146
+ });
147
+ req.on('error', reject);
148
+ req.write(payload);
149
+ req.end();
150
+ });
151
+ }
152
+
153
+ (async () => {
154
+ try {
155
+ const body = buildComment();
156
+ let comments = [];
157
+ try {
158
+ comments = await listComments();
159
+ } catch (err) {
160
+ console.log(`::warning::Could not list existing comments (${err.message}); will create new.`);
161
+ }
162
+ const existing = Array.isArray(comments)
163
+ ? comments.find(c => c.body && c.body.includes('Kodelyth ECC — Devil Mode Review'))
164
+ : null;
165
+ const result = await upsertComment(body, existing ? existing.id : null);
166
+ console.log(`::notice::PR comment ${existing ? 'updated' : 'created'}: ${result.html_url || ''}`);
167
+ } catch (err) {
168
+ console.log(`::error::Failed to post PR comment: ${err.message}`);
169
+ process.exit(1);
170
+ }
171
+ })();