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
15 changes: 10 additions & 5 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@ about: Report something that isn't working
labels: bug
---

**Describe the bug**
### Describe the bug

A clear description of what's going wrong.

**Steps to reproduce**
### Steps to reproduce

1. ...
2. ...

**Expected behavior**
### Expected behavior

What you expected to happen.

**Environment**
### Environment

- Node version:
- openclaw-linear version:
- OS:

**Additional context**
### Additional context

Logs, screenshots, or anything else that might help.
157 changes: 157 additions & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
name: Code Quality

on:
pull_request:
branches: [main]
types: [opened, reopened, synchronize]
paths:
- 'src/**'
- 'test/**'
- 'package.json'
- 'tsconfig*'
- 'vitest*'
- '.eslint*'
- 'eslint*'
- '.prettier*'
- '.markdownlint*'
- '.github/workflows/code-quality.yml'
workflow_dispatch:

jobs:
quality:
name: Lint and test
# The linting and testing pipeline should never take more than 15 minutes.
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: false

- name: Get changed code quality workflow
id: changed-code-quality
uses: tj-actions/changed-files@v41
with:
files: |
.github/workflows/code-quality.yml

- name: Get changed markdown files
id: changed-markdown
uses: tj-actions/changed-files@v41
with:
files: |
.markdownlint*
**/*.md

- name: Get changed JavaScript project files
id: changed-js-project
uses: tj-actions/changed-files@v41
with:
files: |
.eslint*
eslint*
.prettier*
package.json
package-lock.json
tsconfig*
vitest*
src/**
test/**

- name: Should lint documentation
id: lint-docs
run: |
run=false
if [ "${{ steps.changed-markdown.outputs.any_modified }}" == 'true' ] || [ "${{ steps.changed-code-quality.outputs.any_modified }}" == 'true' ] || [ "${{ github.event.action }}" == 'workflow_dispatch' ]; then
run=true
fi
echo "run=${run}" >> $GITHUB_OUTPUT
shell: bash

- name: Should run unit tests
id: check-js
run: |
run=false
if [ "${{ steps.changed-js-project.outputs.any_modified }}" == 'true' ] || [ "${{ steps.changed-code-quality.outputs.any_modified }}" == 'true' ] || [ "${{ github.event.action }}" == 'workflow_dispatch' ]; then
run=true
fi
echo "run=${run}" >> $GITHUB_OUTPUT
shell: bash

- name: Lint all documentation
if: steps.lint-docs.outputs.run == 'true'
uses: DavidAnson/markdownlint-cli2-action@v22
with:
globs: |
**/*.md

- name: Setup Node JS
if: steps.check-js.outputs.run == 'true'
uses: actions/setup-node@v6
with:
node-version-file: .tool-versions
cache: 'npm'
cache-dependency-path: package-lock.json

- name: Install dependencies
if: steps.check-js.outputs.run == 'true'
run: npm ci
shell: bash

- name: Build project
if: steps.check-js.outputs.run == 'true'
run: npm run build
shell: bash

- name: Check formatting
if: steps.check-js.outputs.run == 'true'
run: npm run format:check
shell: bash

- name: Lint project
if: steps.check-js.outputs.run == 'true'
run: npm run lint:project
shell: bash

- name: Run unit tests
if: steps.check-js.outputs.run == 'true'
id: run-unit-tests
run: npm run test:coverage
continue-on-error: true
shell: bash

- name: Get the coverage file
if: steps.check-js.outputs.run == 'true'
run: |
branch="${{ github.head_ref }}"
coverage_branch="${branch//[\":<>|*?\\\/]/-}"
coverage_dir="coverage-${coverage_branch}"
mkdir -p "${coverage_dir}" && sudo cp -r coverage "${coverage_dir}"

echo "coverage_branch=${coverage_branch}" >> $GITHUB_OUTPUT
echo "coverage_dir=${coverage_dir}" >> $GITHUB_OUTPUT
shell: bash
id: coverage

- name: Upload the coverage as an artifact
if: steps.check-js.outputs.run == 'true'
uses: actions/upload-artifact@v4
with:
name: ${{ steps.coverage.outputs.coverage_branch }}-test-coverage
path: ${{ steps.coverage.outputs.coverage_dir }}
retention-days: 30

- name: Check if unit tests failed
if: steps.check-js.outputs.run == 'true'
run: |
if [ "${{ steps.run-unit-tests.outcome }}" == "failure" ]; then
echo "Unit tests failed."
exit 1
fi
shell: bash

- name: Default job success
if: steps.lint-docs.outputs.run == 'false' && steps.check-js.outputs.run == 'false'
run: exit 0
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
node_modules/
dist/
.test-tmp*

coverage/
.eslintcache
14 changes: 14 additions & 0 deletions .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"config": {
"default": true,
"first-line-h1": false,
"line-length": {
"line_length": 280,
},
"no-inline-html": {
"allowed_elements": ["a", "br", "div", "h1", "img", "p"],
},
"table-column-style": false,
},
"ignores": ["**/node_modules/**"],
}
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
coverage
**/*.md
**/*.yml
**/*.yaml
**/*.code-workspace
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"semi": false,
"printWidth": 120
}
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 20
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
# openclaw-linear (GenUI Fork)

A GenUI-maintained fork of [stepandel/openclaw-linear](https://github.com/stepandel/openclaw-linear), with bug fixes and additional capabilities including Linear view management.

---

# openclaw-linear

> GenUI-maintained fork of [stepandel/openclaw-linear](https://github.com/stepandel/openclaw-linear),
> with bug fixes and additional capabilities including Linear view management.

Linear integration for [OpenClaw](https://github.com/nichochar/openclaw). Receives Linear webhook events, routes them through a persistent work queue, and gives agents tools to manage issues, comments, projects, teams, and relations via the Linear GraphQL API.

## Install
Expand Down Expand Up @@ -52,6 +49,7 @@ plugins:
## Webhook Setup

1. **Make your endpoint publicly accessible.** The plugin registers at `/hooks/linear`:

```bash
# Example with Tailscale Funnel
tailscale funnel --bg 3000
Expand Down Expand Up @@ -126,7 +124,14 @@ plugins:
(new session)
```

Events flow through four stages. The **webhook handler** verifies signatures and deduplicates deliveries. The **event router** filters by team, type, and user, then classifies each event as `wake` (needs the agent's attention now) or `notify` (queue silently). Wake actions pass through a **debouncer** that batches events within a configurable window. Both paths write to the **work queue** — a persistent, priority-sorted JSONL file. The agent is only woken when new items are actually added (deduplication may suppress a dispatch). After the agent completes an item, **auto-wake** checks for remaining work and starts a fresh session if needed.
Events flow through four stages.
The **webhook handler** verifies signatures and deduplicates deliveries.
The **event router** filters by team, type, and user, then classifies each event as `wake`
(needs the agent's attention now) or `notify` (queue silently).
Wake actions pass through a **debouncer** that batches events within a configurable window.
Both paths write to the **work queue** — a persistent, priority-sorted JSONL file.
The agent is only woken when new items are actually added (deduplication may suppress a dispatch).
After the agent completes an item, **auto-wake** checks for remaining work and starts a fresh session if needed.

## Work Queue

Expand Down Expand Up @@ -319,4 +324,4 @@ src/
npm install
npm run build
npm test
```
```
72 changes: 72 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// @ts-check
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import prettierConfig from 'eslint-config-prettier'

export default tseslint.config(
{
ignores: ['dist/*', 'coverage/*', 'node_modules/*'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
prettierConfig,
{
files: ['**/*.ts'],
linterOptions: {
reportUnusedDisableDirectives: 'error',
},
languageOptions: {
parserOptions: {
project: './tsconfig.eslint.json',
},
},
rules: {
complexity: 'error',
'default-case-last': 'error',
'default-param-last': 'off',
'dot-notation': 'off',
eqeqeq: 'error',
'guard-for-in': 'error',
'max-depth': 'error',
'no-await-in-loop': 'error',
'no-duplicate-imports': 'error',
'no-new-native-nonconstructor': 'error',
'no-promise-executor-return': 'error',
'no-self-compare': 'error',
'no-template-curly-in-string': 'error',
'no-unmodified-loop-condition': 'error',
'no-unreachable-loop': 'error',
'no-unused-private-class-members': 'error',
'no-unused-vars': 'off',
'no-use-before-define': 'off',
'no-useless-rename': 'error',
'no-sequences': 'error',
'no-var': 'error',
'object-shorthand': 'error',
'require-atomic-updates': 'error',
'require-await': 'off',
'@typescript-eslint/default-param-last': 'error',
'@typescript-eslint/dot-notation': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-use-before-define': ['error', { functions: false, typedefs: false }],
'@typescript-eslint/restrict-template-expressions': ['error', { allowNumber: true, allowBoolean: true }],
},
},
{
files: ['test/**/*.ts'],
rules: {
'@typescript-eslint/unbound-method': 'off',
// Vitest mocks (vi.fn()) and JSON.parse return `any` — suppress unsafe/any rules in tests
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
},
},
{
files: ['*.config.{js,mjs,ts}', 'vitest.*.ts'],
...tseslint.configs.disableTypeChecked,
},
)
Loading
Loading