Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ load("@npm//:defs.bzl", "npm_link_all_packages")

npm_link_all_packages(name = "node_modules")

js_library(
name = "config_yml",
srcs = ["config.yml"],
visibility = ["//__tests__:__pkg__"],
)

js_library(
name = "jest_config",
srcs = [
Expand Down
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,15 @@ repository in your GitHub organization.

## Dashboard

Temper ships with a real-time operations dashboard powered by **thrum** — a Rust/Axum
backend with HTMX and SSE for live agent monitoring. The dashboard provides:
Temper includes a built-in operations dashboard for monitoring compliance and
activity across your organization. The dashboard provides:

- Task queue with approve/reject/retry actions
- Live agent activity grid with streaming stdout/stderr
- Per-task review pages with syntax-colored diffs and gate reports
- Budget tracking, memory inspection, and agent-to-agent messaging
- Organization-wide compliance score with per-repo breakdown
- Repository health cards — branch protection, signed commits, CI status, merge settings, labels
- Active pull request tracker with check status, labels, and age
- Signal feed for real-time webhook events and configuration drift

Run `thrum` alongside the Probot app, or access the dashboard at `/dashboard` when
deployed with the standalone HTTP handler.
Access the dashboard at `/dashboard` when running with the standalone HTTP handler.

## Quick Start

Expand Down
2 changes: 2 additions & 0 deletions __tests__/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jest_bin.jest_test(
":test_sources",
"//src:lib",
"//:jest_config",
"//:config_yml",
"//:node_modules/jest",
"//:node_modules/@babel/core",
"//:node_modules/@babel/preset-env",
Expand All @@ -52,6 +53,7 @@ jest_bin.jest_test(
":test_sources",
"//src:lib",
"//:jest_config",
"//:config_yml",
"//:node_modules/jest",
"//:node_modules/@babel/core",
"//:node_modules/@babel/preset-env",
Expand Down
18 changes: 17 additions & 1 deletion __tests__/integration/ai-review.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
updateReviewStatus,
_reviewTimestamps,
_resetReviews,
AI_REVIEW_SIGNATURE
} from '../../src/ai-review.js';
import { _setConfigForTesting } from '../../src/config.js';
import { getLogger } from '../../src/logger.js';
Expand Down Expand Up @@ -398,6 +397,23 @@ describe('ai-review', () => {
expect(result).toContain('`aaaaaaa`');
expect(result).not.toContain('a'.repeat(40));
});

it('includes repo and branch metadata when provided', () => {
const result = formatReviewComment('review', 42, 'abc1234', {
baseRepo: 'pulseengine/temper',
baseBranch: 'main',
headRepo: 'contributor/temper',
headBranch: 'fix/bug',
});
expect(result).toContain('contributor/temper:`fix/bug`');
expect(result).toContain('pulseengine/temper:`main`');
expect(result).toContain('→');
});

it('omits branch line when meta is not provided', () => {
const result = formatReviewComment('review', 1);
expect(result).not.toContain('→');
});
});

// =========================================================================
Expand Down
2 changes: 1 addition & 1 deletion __tests__/integration/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ describe('app', () => {

it('works without getRouter option', () => {
const { app } = setupApp({ skipRouter: true });
expect(app.on).toHaveBeenCalledTimes(4);
expect(app.on).toHaveBeenCalledTimes(5);
expect(app.onError).toHaveBeenCalledTimes(1);
});

Expand Down
29 changes: 29 additions & 0 deletions __tests__/unit/schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,33 @@ describe('validateConfig', () => {
const result = validateConfig({ ai_review: { enabled: true, temperature: 0.3 } });
expect(result.valid).toBe(true);
});

it('accepts valid self_update config', () => {
const result = validateConfig({ self_update: { enabled: true, repo: 'temper', branch: 'main' } });
expect(result.valid).toBe(true);
});

it('rejects non-boolean self_update.enabled', () => {
const result = validateConfig({ self_update: { enabled: 'yes' } });
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('self_update.enabled');
});

it('rejects empty self_update.repo', () => {
const result = validateConfig({ self_update: { repo: '' } });
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('self_update.repo');
});

it('rejects non-string self_update.branch', () => {
const result = validateConfig({ self_update: { branch: 123 } });
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('self_update.branch');
});

it('rejects non-object self_update', () => {
const result = validateConfig({ self_update: 'yes' });
expect(result.valid).toBe(false);
expect(result.errors[0]).toContain('self_update');
});
});
50 changes: 50 additions & 0 deletions __tests__/unit/self-update.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { spawn } from 'node:child_process';
import { triggerSelfUpdate } from '../../src/self-update.js';

jest.mock('node:child_process', () => ({
spawn: jest.fn(() => ({ unref: jest.fn() }))
}));

describe('triggerSelfUpdate', () => {
const mockLogger = { info: jest.fn(), warn: jest.fn(), error: jest.fn() };

beforeEach(() => {
jest.clearAllMocks();
});

it('spawns the self-update binary as a detached process', () => {
triggerSelfUpdate(mockLogger);

expect(spawn).toHaveBeenCalledTimes(1);
const [binary, args, opts] = spawn.mock.calls[0];
expect(binary).toContain('temper-self-update');
expect(args).toEqual([]);
expect(opts.detached).toBe(true);
expect(opts.stdio).toBe('ignore');
});

it('passes TEMPER_PID and TEMPER_REPO_DIR as env vars', () => {
triggerSelfUpdate(mockLogger);

const opts = spawn.mock.calls[0][2];
expect(opts.env.TEMPER_PID).toBe(String(process.pid));
expect(opts.env.TEMPER_REPO_DIR).toBeDefined();
});

it('calls unref() on the child process', () => {
const mockUnref = jest.fn();
spawn.mockReturnValue({ unref: mockUnref });

triggerSelfUpdate(mockLogger);

expect(mockUnref).toHaveBeenCalledTimes(1);
});

it('logs that the update is being triggered', () => {
triggerSelfUpdate(mockLogger);

expect(mockLogger.info).toHaveBeenCalledWith(
expect.stringContaining('self-update')
);
});
});
12 changes: 10 additions & 2 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ branch_protection:
required_status_checks:
strict: true
contexts: []
enforce_admins: false
required_pull_request_reviews: null
enforce_admins: true
required_pull_request_reviews:
required_approving_review_count: 1
dismiss_stale_reviews: true
require_code_owner_reviews: false
required_linear_history: false
required_conversation_resolution: false
restrictions: null
Expand All @@ -47,6 +50,11 @@ auto_merge:
- thrum
merge_method: squash

self_update:
enabled: true
repo: temper
branch: main

issue_labels:
- name: "bug"
color: "d73a4a"
Expand Down
3 changes: 2 additions & 1 deletion jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = {
'^.+\\.js$': ['babel-jest', { configFile: './babel.config.cjs' }]
},
transformIgnorePatterns: [
'/node_modules/(?!probot|@probot|@octokit|before-after-hook|universal-user-agent|universal-github-app-jwt|octokit-auth-probot)'
'/node_modules/(?!\\.pnpm|\\.aspect_rules_js|probot|@probot|@octokit|before-after-hook|universal-user-agent|universal-github-app-jwt|octokit-auth-probot)'
],
testMatch: [
'**/__tests__/unit/**/*.test.js',
Expand All @@ -15,6 +15,7 @@ module.exports = {
],
collectCoverageFrom: [
'src/**/*.js',
'!src/dashboard.js',
'index.js',
'!**/node_modules/**'
],
Expand Down
Loading