Compare commits
No commits in common. "master" and "feat/incident-response-timeline" have entirely different histories.
master
...
feat/incid
@ -43,13 +43,13 @@ jobs:
|
|||||||
git diff origin/${{ github.base_ref }}..HEAD > /tmp/pr_diff.txt
|
git diff origin/${{ github.base_ref }}..HEAD > /tmp/pr_diff.txt
|
||||||
echo "diff_size=$(wc -l < /tmp/pr_diff.txt | tr -d ' ')" >> $GITHUB_OUTPUT
|
echo "diff_size=$(wc -l < /tmp/pr_diff.txt | tr -d ' ')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Analyze with LLM
|
- name: Analyze with Ollama
|
||||||
id: analyze
|
id: analyze
|
||||||
if: steps.diff.outputs.diff_size != '0'
|
if: steps.diff.outputs.diff_size != '0'
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
LITELLM_URL: http://172.0.0.29:11434/v1
|
OLLAMA_URL: https://ollama-ui.tftsr.com/ollama/v1
|
||||||
LITELLM_API_KEY: ${{ secrets.OLLAMA_API_KEY }}
|
OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }}
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
run: |
|
run: |
|
||||||
@ -62,32 +62,32 @@ jobs:
|
|||||||
| grep -v -E '^[+-].*[A-Za-z0-9+/]{40,}={0,2}([^A-Za-z0-9+/=]|$)')
|
| grep -v -E '^[+-].*[A-Za-z0-9+/]{40,}={0,2}([^A-Za-z0-9+/=]|$)')
|
||||||
PROMPT="Analyze the following code changes for correctness, security issues, and best practices. PR Title: ${PR_TITLE}\n\nDiff:\n${DIFF_CONTENT}\n\nProvide a review with: 1) Summary, 2) Bugs/errors, 3) Security issues, 4) Best practices. Give specific comments with suggested fixes."
|
PROMPT="Analyze the following code changes for correctness, security issues, and best practices. PR Title: ${PR_TITLE}\n\nDiff:\n${DIFF_CONTENT}\n\nProvide a review with: 1) Summary, 2) Bugs/errors, 3) Security issues, 4) Best practices. Give specific comments with suggested fixes."
|
||||||
BODY=$(jq -cn \
|
BODY=$(jq -cn \
|
||||||
--arg model "qwen2.5-72b" \
|
--arg model "qwen3-coder-next:latest" \
|
||||||
--arg content "$PROMPT" \
|
--arg content "$PROMPT" \
|
||||||
'{model: $model, messages: [{role: "user", content: $content}], stream: false}')
|
'{model: $model, messages: [{role: "user", content: $content}], stream: false}')
|
||||||
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] PR #${PR_NUMBER} - Calling liteLLM API (${#BODY} bytes)..."
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] PR #${PR_NUMBER} - Calling Ollama API (${#BODY} bytes)..."
|
||||||
HTTP_CODE=$(curl -s --max-time 300 --connect-timeout 30 \
|
HTTP_CODE=$(curl -s --max-time 120 --connect-timeout 30 \
|
||||||
--retry 3 --retry-delay 10 --retry-connrefused --retry-max-time 300 \
|
--retry 3 --retry-delay 5 --retry-connrefused --retry-max-time 120 \
|
||||||
-o /tmp/llm_response.json -w "%{http_code}" \
|
-o /tmp/ollama_response.json -w "%{http_code}" \
|
||||||
-X POST "$LITELLM_URL/chat/completions" \
|
-X POST "$OLLAMA_URL/chat/completions" \
|
||||||
-H "Authorization: Bearer $LITELLM_API_KEY" \
|
-H "Authorization: Bearer $OLLAMA_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$BODY")
|
-d "$BODY")
|
||||||
echo "HTTP status: $HTTP_CODE"
|
echo "HTTP status: $HTTP_CODE"
|
||||||
echo "Response file size: $(wc -c < /tmp/llm_response.json) bytes"
|
echo "Response file size: $(wc -c < /tmp/ollama_response.json) bytes"
|
||||||
if [ "$HTTP_CODE" != "200" ]; then
|
if [ "$HTTP_CODE" != "200" ]; then
|
||||||
echo "ERROR: liteLLM returned HTTP $HTTP_CODE"
|
echo "ERROR: Ollama returned HTTP $HTTP_CODE"
|
||||||
cat /tmp/llm_response.json
|
cat /tmp/ollama_response.json
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if ! jq empty /tmp/llm_response.json 2>/dev/null; then
|
if ! jq empty /tmp/ollama_response.json 2>/dev/null; then
|
||||||
echo "ERROR: Invalid JSON response from liteLLM"
|
echo "ERROR: Invalid JSON response from Ollama"
|
||||||
cat /tmp/llm_response.json
|
cat /tmp/ollama_response.json
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
REVIEW=$(jq -r '.choices[0].message.content // empty' /tmp/llm_response.json)
|
REVIEW=$(jq -r '.choices[0].message.content // empty' /tmp/ollama_response.json)
|
||||||
if [ -z "$REVIEW" ]; then
|
if [ -z "$REVIEW" ]; then
|
||||||
echo "ERROR: No content in liteLLM response"
|
echo "ERROR: No content in Ollama response"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "Review length: ${#REVIEW} chars"
|
echo "Review length: ${#REVIEW} chars"
|
||||||
@ -109,11 +109,11 @@ jobs:
|
|||||||
if [ -f "/tmp/pr_review.txt" ] && [ -s "/tmp/pr_review.txt" ]; then
|
if [ -f "/tmp/pr_review.txt" ] && [ -s "/tmp/pr_review.txt" ]; then
|
||||||
REVIEW_BODY=$(head -c 65536 /tmp/pr_review.txt)
|
REVIEW_BODY=$(head -c 65536 /tmp/pr_review.txt)
|
||||||
BODY=$(jq -n \
|
BODY=$(jq -n \
|
||||||
--arg body "Automated PR Review (qwen2.5-72b via liteLLM):\n\n${REVIEW_BODY}\n\n---\n*automated code review*" \
|
--arg body "🤖 Automated PR Review:\n\n${REVIEW_BODY}\n\n---\n*this is an automated review from Ollama*" \
|
||||||
'{body: $body, event: "COMMENT"}')
|
'{body: $body, event: "COMMENT"}')
|
||||||
else
|
else
|
||||||
BODY=$(jq -n \
|
BODY=$(jq -n \
|
||||||
'{body: "Automated PR Review could not be completed - LLM analysis failed or produced no output.", event: "COMMENT"}')
|
'{body: "⚠️ Automated PR Review could not be completed — Ollama analysis failed or produced no output.", event: "COMMENT"}')
|
||||||
fi
|
fi
|
||||||
HTTP_CODE=$(curl -s --max-time 30 --connect-timeout 10 \
|
HTTP_CODE=$(curl -s --max-time 30 --connect-timeout 10 \
|
||||||
-o /tmp/review_post_response.json -w "%{http_code}" \
|
-o /tmp/review_post_response.json -w "%{http_code}" \
|
||||||
@ -131,4 +131,4 @@ jobs:
|
|||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
if: always()
|
if: always()
|
||||||
shell: bash
|
shell: bash
|
||||||
run: rm -f /tmp/pr_diff.txt /tmp/llm_response.json /tmp/pr_review.txt /tmp/review_post_response.json
|
run: rm -f /tmp/pr_diff.txt /tmp/ollama_response.json /tmp/pr_review.txt /tmp/review_post_response.json
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
13
CHANGELOG.md
13
CHANGELOG.md
@ -4,19 +4,6 @@ All notable changes to TFTSR are documented here.
|
|||||||
Commit types shown: feat, fix, perf, docs, refactor.
|
Commit types shown: feat, fix, perf, docs, refactor.
|
||||||
CI, chore, and build changes are excluded.
|
CI, chore, and build changes are excluded.
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
- Harden timeline event input validation and atomic writes
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
- Update wiki for timeline events and incident response methodology
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- Add timeline_events table, model, and CRUD commands
|
|
||||||
- Populate RCA and postmortem docs with real timeline data
|
|
||||||
- Wire incident response methodology into AI and record triage events
|
|
||||||
|
|
||||||
## [0.2.65] — 2026-04-15
|
## [0.2.65] — 2026-04-15
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@ -42,8 +42,11 @@ describe("Audit Log", () => {
|
|||||||
it("displays audit entries", async () => {
|
it("displays audit entries", async () => {
|
||||||
render(<Security />);
|
render(<Security />);
|
||||||
|
|
||||||
// Wait for table to appear after async audit data loads
|
// Wait for audit log to load
|
||||||
const table = await screen.findByRole("table");
|
await screen.findByText("Audit Log");
|
||||||
|
|
||||||
|
// Check that the table has rows (header + data rows)
|
||||||
|
const table = screen.getByRole("table");
|
||||||
expect(table).toBeInTheDocument();
|
expect(table).toBeInTheDocument();
|
||||||
|
|
||||||
const rows = screen.getAllByRole("row");
|
const rows = screen.getAllByRole("row");
|
||||||
@ -53,7 +56,9 @@ describe("Audit Log", () => {
|
|||||||
it("provides way to view transmitted data details", async () => {
|
it("provides way to view transmitted data details", async () => {
|
||||||
render(<Security />);
|
render(<Security />);
|
||||||
|
|
||||||
// Wait for async data to load and render the table
|
await screen.findByText("Audit Log");
|
||||||
|
|
||||||
|
// Should have View/Hide buttons for expanding details
|
||||||
const viewButtons = await screen.findAllByRole("button", { name: /View/i });
|
const viewButtons = await screen.findAllByRole("button", { name: /View/i });
|
||||||
expect(viewButtons.length).toBeGreaterThan(0);
|
expect(viewButtons.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
@ -61,13 +66,14 @@ describe("Audit Log", () => {
|
|||||||
it("details column or button exists for viewing data", async () => {
|
it("details column or button exists for viewing data", async () => {
|
||||||
render(<Security />);
|
render(<Security />);
|
||||||
|
|
||||||
// Wait for async data to load and render the table
|
await screen.findByText("Audit Log");
|
||||||
await screen.findByRole("table");
|
|
||||||
|
|
||||||
|
// The audit log should have a Details column header
|
||||||
const detailsHeader = screen.getByText("Details");
|
const detailsHeader = screen.getByText("Details");
|
||||||
expect(detailsHeader).toBeInTheDocument();
|
expect(detailsHeader).toBeInTheDocument();
|
||||||
|
|
||||||
const viewButtons = screen.getAllByRole("button", { name: /View/i });
|
// Should have view buttons
|
||||||
|
const viewButtons = await screen.findAllByRole("button", { name: /View/i });
|
||||||
expect(viewButtons.length).toBe(2); // One for each mock entry
|
expect(viewButtons.length).toBe(2); // One for each mock entry
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user