Skip to content

Commit 700d14c

Browse files
hyperpolymathclaude
andcommitted
feat: add Elixir actuation scripts + sync learning outcomes and finishingbot updates
- scripts/fix-add-elixir-ci.sh: auto-generate .github/workflows/elixir.yml for repos with mix.exs but no CI; detects OTP/Elixir versions from .tool-versions; adds optional credo/dialyzer steps if deps found; SHA-pinned erlef/setup-beam - scripts/fix-elixir-ssl-verify.sh: replace verify: :verify_none with verify: :verify_peer + CAStore reference; flags files for manual review of CA source - shared-context/learning/fix-outcomes.jsonl: +1123 new fix outcome records - bots/finishingbot/src/analyzers/license.rs: license analyzer updates - bots/finishingbot/src/config.rs: config additions - dashboard/src/groove.rs: groove integration updates - panicbot-sweep.yml: workflow update Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bc0cd02 commit 700d14c

8 files changed

Lines changed: 1375 additions & 8 deletions

File tree

.github/workflows/panicbot-sweep.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
run: |
6262
if ! command -v panic-attack &>/dev/null; then
6363
echo "::warning::panic-attack not found, attempting cargo install"
64-
cargo install panic-attacker || echo "::error::Failed to install panic-attack"
64+
cargo install --git https://github.com/hyperpolymath/panic-attacker.git || echo "::error::Failed to install panic-attack"
6565
fi
6666
6767
- name: Run sweep

TEST-NEEDS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Test & Benchmark Requirements
22

3+
## CRG Grade: C — ACHIEVED 2026-04-04
4+
35
## Current State (updated 2026-04-04)
46

57
### What Was Added (this session — CRG C blitz)

bots/finishingbot/src/analyzers/license.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ use std::path::Path;
1010
use tracing::debug;
1111
use walkdir::WalkDir;
1212

13+
/// Repositories that deliberately use AGPL-3.0-or-later instead of PMPL.
14+
/// These are co-developed projects with the user's son and use AGPL by deliberate choice.
15+
const AGPL_EXCEPTION_REPOS: &[&str] = &[
16+
"game-server-admin",
17+
"idaptik",
18+
"airborne-submarine-squadron",
19+
];
20+
21+
/// Check whether a repository path corresponds to an AGPL exception repo.
22+
fn is_agpl_exception(path: &Path) -> bool {
23+
path.file_name()
24+
.and_then(|name| name.to_str())
25+
.map(|name| AGPL_EXCEPTION_REPOS.contains(&name))
26+
.unwrap_or(false)
27+
}
28+
1329
/// License validation analyzer
1430
pub struct LicenseAnalyzer;
1531

@@ -114,9 +130,20 @@ impl LicenseAnalyzer {
114130
) {
115131
let detected = self.detect_license(content);
116132

133+
// Determine the repository root from the LICENSE file path
134+
let repo_root = path.parent().unwrap_or(path);
135+
117136
match detected {
118137
Some(license) => {
119-
if config.licenses.strict && !config.licenses.allowed.contains(&license) {
138+
// AGPL-3.0-or-later is always valid for exception repos
139+
if is_agpl_exception(repo_root)
140+
&& (license == "AGPL-3.0-or-later" || license == "AGPL-3.0")
141+
{
142+
debug!(
143+
"Detected AGPL-3.0-or-later in exception repo: {}",
144+
repo_root.display()
145+
);
146+
} else if config.licenses.strict && !config.licenses.allowed.contains(&license) {
120147
result.add(
121148
Finding::new(
122149
"LIC-003",
@@ -165,7 +192,7 @@ impl LicenseAnalyzer {
165192
if content_lower.contains("gnu affero general public license")
166193
|| content_lower.contains("agpl-3.0")
167194
{
168-
return Some("PMPL-1.0-or-later".to_string());
195+
return Some("AGPL-3.0-or-later".to_string());
169196
}
170197

171198
if content_lower.contains("gnu general public license")
@@ -231,6 +258,11 @@ impl LicenseAnalyzer {
231258
let first_lines: String = content.lines().take(10).collect::<Vec<_>>().join("\n");
232259

233260
if !spdx_pattern.is_match(&first_lines) {
261+
let suggested_license = if is_agpl_exception(path) {
262+
"AGPL-3.0-or-later".to_string()
263+
} else {
264+
config.licenses.allowed.first().unwrap_or(&"<LICENSE>".to_string()).clone()
265+
};
234266
result.add(
235267
Finding::new(
236268
"LIC-002",
@@ -241,7 +273,7 @@ impl LicenseAnalyzer {
241273
.with_file(entry_path.to_path_buf())
242274
.with_suggestion(&format!(
243275
"Add '// SPDX-License-Identifier: {}' at the start",
244-
config.licenses.allowed.first().unwrap_or(&"<LICENSE>".to_string())
276+
suggested_license
245277
))
246278
.fixable(),
247279
);
@@ -253,7 +285,16 @@ impl LicenseAnalyzer {
253285
/// Add SPDX header to a file
254286
fn add_spdx_header(&self, path: &Path, allowed_licenses: &[String]) -> Result<()> {
255287
let content = std::fs::read_to_string(path)?;
256-
let default_license = "PMPL-1.0-or-later".to_string();
288+
289+
// Use AGPL-3.0-or-later for exception repos, PMPL-1.0-or-later otherwise
290+
let default_license = if path
291+
.ancestors()
292+
.any(|ancestor| is_agpl_exception(ancestor))
293+
{
294+
"AGPL-3.0-or-later".to_string()
295+
} else {
296+
"PMPL-1.0-or-later".to_string()
297+
};
257298
let license = allowed_licenses.first().unwrap_or(&default_license);
258299

259300
// Determine comment style based on extension

bots/finishingbot/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ fn default_allowed_licenses() -> Vec<String> {
102102
"Apache-2.0".to_string(),
103103
"BSD-3-Clause".to_string(),
104104
"PMPL-1.0".to_string(),
105+
// AGPL-3.0-or-later is used by co-developed repos (idaptik,
106+
// airborne-submarine-squadron, game-server-admin)
107+
"AGPL-3.0-or-later".to_string(),
105108
]
106109
}
107110

dashboard/src/groove.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use axum::response::Json;
1515
///
1616
/// Advertises the fleet's bot-orchestration capability in the standard Groove
1717
/// format. Any groove-aware client can probe `GET /.well-known/groove` on
18-
/// port 7500 to discover this service.
18+
/// port 8080 to discover this service.
1919
///
2020
/// ## Capabilities offered
2121
///
@@ -46,8 +46,8 @@ pub async fn groove_manifest() -> Json<serde_json::Value> {
4646
},
4747
"consumes": ["octad-storage", "static-analysis"],
4848
"endpoints": {
49-
"api": "http://localhost:7500/api",
50-
"health": "http://localhost:7500/health"
49+
"api": "http://localhost:8080/api",
50+
"health": "http://localhost:8080/health"
5151
},
5252
"health": "/health",
5353
"applicability": ["individual", "team"]

scripts/fix-add-elixir-ci.sh

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: PMPL-1.0-or-later
3+
#
4+
# fix-add-elixir-ci.sh — Add GitHub Actions CI workflow for Elixir/Phoenix projects
5+
#
6+
# Triggered when: mix.exs present, no CI workflow found running mix test.
7+
#
8+
# Generated workflow runs on ubuntu-latest with Erlang/OTP + Elixir via
9+
# erlef/setup-beam, SHA-pinned. Includes:
10+
# - mix deps.get
11+
# - mix compile --warnings-as-errors
12+
# - mix test
13+
# - mix credo (if credo dep detected)
14+
# - mix dialyzer (if dialyxir dep detected)
15+
#
16+
# Idempotent: skips if elixir.yml / mix.yml already exists.
17+
# Does NOT commit — dispatch-runner handles that.
18+
#
19+
# Usage: fix-add-elixir-ci.sh <repo-path> <finding-json>
20+
21+
set -euo pipefail
22+
23+
REPO_PATH="${1:?Usage: $0 <repo-path> <finding-json>}"
24+
FINDING_JSON="${2:?Missing finding JSON}"
25+
26+
WORKFLOWS_DIR="${REPO_PATH}/.github/workflows"
27+
28+
# Idempotency check
29+
for wf in elixir.yml mix.yml beam.yml elixir-ci.yml; do
30+
if [[ -f "${WORKFLOWS_DIR}/${wf}" ]]; then
31+
echo "[fix-add-elixir-ci] ${wf} already exists — skipping."
32+
exit 0
33+
fi
34+
done
35+
# Also check for any workflow containing mix test
36+
if [[ -d "${WORKFLOWS_DIR}" ]] && grep -rl 'mix test' "${WORKFLOWS_DIR}/" >/dev/null 2>&1; then
37+
echo "[fix-add-elixir-ci] Workflow with 'mix test' already exists — skipping."
38+
exit 0
39+
fi
40+
41+
mkdir -p "${WORKFLOWS_DIR}"
42+
43+
# Detect optional tooling from mix.exs
44+
MIX_EXS="${REPO_PATH}/mix.exs"
45+
HAS_CREDO=false
46+
HAS_DIALYXIR=false
47+
if [[ -f "${MIX_EXS}" ]]; then
48+
grep -q 'credo' "${MIX_EXS}" && HAS_CREDO=true || true
49+
grep -q 'dialyxir\|dialyzir' "${MIX_EXS}" && HAS_DIALYXIR=true || true
50+
fi
51+
52+
# Detect OTP/Elixir version hints from .tool-versions or mix.exs
53+
OTP_VERSION="27"
54+
ELIXIR_VERSION="1.17"
55+
if [[ -f "${REPO_PATH}/.tool-versions" ]]; then
56+
otp_line=$(grep '^erlang ' "${REPO_PATH}/.tool-versions" | awk '{print $2}' | cut -d. -f1 || true)
57+
elixir_line=$(grep '^elixir ' "${REPO_PATH}/.tool-versions" | awk '{print $2}' | cut -d- -f1 || true)
58+
[[ -n "${otp_line}" ]] && OTP_VERSION="${otp_line}"
59+
[[ -n "${elixir_line}" ]] && ELIXIR_VERSION="${elixir_line}"
60+
fi
61+
62+
# Build optional steps
63+
OPTIONAL_STEPS=""
64+
if [[ "${HAS_CREDO}" == "true" ]]; then
65+
OPTIONAL_STEPS="${OPTIONAL_STEPS}
66+
- name: Credo static analysis
67+
run: mix credo --strict
68+
"
69+
fi
70+
if [[ "${HAS_DIALYXIR}" == "true" ]]; then
71+
OPTIONAL_STEPS="${OPTIONAL_STEPS}
72+
- name: Dialyzer type checking
73+
run: mix dialyzer
74+
"
75+
fi
76+
77+
cat > "${WORKFLOWS_DIR}/elixir.yml" << WORKFLOW
78+
# SPDX-License-Identifier: PMPL-1.0-or-later
79+
name: Elixir CI
80+
81+
on:
82+
push:
83+
branches: [main, master]
84+
pull_request:
85+
branches: [main, master]
86+
87+
permissions:
88+
contents: read
89+
90+
jobs:
91+
test:
92+
name: Build and test
93+
runs-on: ubuntu-latest
94+
95+
strategy:
96+
matrix:
97+
otp: ['${OTP_VERSION}']
98+
elixir: ['${ELIXIR_VERSION}']
99+
100+
steps:
101+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
102+
103+
- name: Set up Elixir
104+
uses: erlef/setup-beam@5304e04ea2b355f03681464e683d92e3b2f18451 # v1
105+
with:
106+
otp-version: \${{ matrix.otp }}
107+
elixir-version: \${{ matrix.elixir }}
108+
109+
- name: Restore dependencies cache
110+
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
111+
with:
112+
path: deps
113+
key: \${{ runner.os }}-mix-\${{ hashFiles('**/mix.lock') }}
114+
restore-keys: \${{ runner.os }}-mix-
115+
116+
- name: Install dependencies
117+
run: mix deps.get
118+
119+
- name: Compile (warnings as errors)
120+
run: mix compile --warnings-as-errors
121+
${OPTIONAL_STEPS}
122+
- name: Run tests
123+
run: mix test
124+
WORKFLOW
125+
126+
echo "[fix-add-elixir-ci] Created ${WORKFLOWS_DIR}/elixir.yml"
127+
echo " OTP ${OTP_VERSION} / Elixir ${ELIXIR_VERSION}"
128+
[[ "${HAS_CREDO}" == "true" ]] && echo " + credo step" || true
129+
[[ "${HAS_DIALYXIR}" == "true" ]] && echo " + dialyzer step" || true

scripts/fix-elixir-ssl-verify.sh

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: PMPL-1.0-or-later
3+
#
4+
# fix-elixir-ssl-verify.sh — Replace verify: :verify_none with :verify_peer
5+
#
6+
# SSL verify_none disables TLS certificate validation entirely, allowing
7+
# MITM attacks. This script adds :verify_peer with either ca_store (CAStore
8+
# hex package) or the system-provided cacerts fallback.
9+
#
10+
# NOTE: This fix requires manual review — the correct cacertfile depends on
11+
# whether the app uses CAStore, :certifi, or the OTP built-in :public_key
12+
# cacerts. The script flags occurrences and substitutes a safe template;
13+
# the developer must verify the CA source is appropriate for their use case.
14+
#
15+
# Usage: fix-elixir-ssl-verify.sh <repo-path> <finding-json>
16+
17+
set -euo pipefail
18+
19+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
20+
source "$SCRIPT_DIR/lib/third-party-excludes.sh" 2>/dev/null || true
21+
22+
REPO_PATH="${1:?Usage: $0 <repo-path> <finding-json>}"
23+
FINDING_JSON="${2:?Missing finding JSON}"
24+
25+
FIND_EXCLUDES=(-not -path "*/.git/*" "${FIND_THIRD_PARTY_EXCLUDES[@]:-}")
26+
27+
EX_FILES=()
28+
while IFS= read -r -d '' f; do
29+
EX_FILES+=("$f")
30+
done < <(find "$REPO_PATH" -type f \( -name "*.ex" -o -name "*.exs" \) "${FIND_EXCLUDES[@]}" -print0 2>/dev/null)
31+
32+
if [[ ${#EX_FILES[@]} -eq 0 ]]; then
33+
echo "[fix-elixir-ssl-verify] No Elixir files found."
34+
exit 0
35+
fi
36+
37+
FLAGGED=0
38+
39+
for file in "${EX_FILES[@]}"; do
40+
if ! grep -qP 'verify:\s*:verify_none|ssl:\s*\[verify:\s*:verify_none\]' "$file" 2>/dev/null; then
41+
continue
42+
fi
43+
44+
rel="${file#"$REPO_PATH"/}"
45+
echo ""
46+
echo " [WARNING] SSL verify_none found in: $rel"
47+
echo " Lines:"
48+
grep -n 'verify:\s*:verify_none' "$file" | head -5 | sed 's/^/ /'
49+
50+
tmpfile=$(mktemp)
51+
# Replace standalone verify: :verify_none
52+
sed 's/verify: :verify_none/verify: :verify_peer, cacertfile: CAStore.file_path() # REVIEW: set appropriate CA source/g' "$file" > "$tmpfile"
53+
54+
if ! diff -q "$file" "$tmpfile" >/dev/null 2>&1; then
55+
cp "$tmpfile" "$file"
56+
echo " -> Substituted verify: :verify_peer + CAStore reference in $rel"
57+
echo " REVIEW REQUIRED: confirm CA source (CAStore, :certifi, or :public_key.cacerts_get/0)"
58+
((FLAGGED++)) || true
59+
fi
60+
rm -f "$tmpfile"
61+
done
62+
63+
echo ""
64+
if [[ "$FLAGGED" -gt 0 ]]; then
65+
echo "Modified $FLAGGED file(s). Manual review required before merging."
66+
echo "Recommended: add {:ca_store, \"~> 0.1\"} to mix.exs deps if not present."
67+
else
68+
echo "No verify: :verify_none patterns found."
69+
fi

0 commit comments

Comments
 (0)