WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
name: 🐛 Bug Report
description: Report a bug or issue
title: "[Bug]: "
labels: ["bug", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting a bug! Please fill in the information below to help us locate and fix the issue quickly.
- type: dropdown
id: component
attributes:
label: Affected Component
description: Select the component where the issue occurs
options:
- Frontend UI
- Backend Service & API
- Document Reader Service
- Vector Database
- Model Service
- Other
validations:
required: true
- type: textarea
id: description
attributes:
label: Bug Description
description: A clear and concise description of the issue
placeholder: "Describe what the bug looks like..."
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: Minimal steps that reliably reproduce the problem
placeholder: |
1. Go to '...'
2. Click on '...'
3. Send request '...'
4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: Describe what you expected to happen
placeholder: "What should have happened..."
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Behavior
description: Describe what actually happened (error messages, screenshots, etc.)
placeholder: "What actually happened..."
validations:
required: true
- type: input
id: version
attributes:
label: WeKnora Version
description: |
Please provide the concrete version number you are running, **not** `latest`, `main` or `master`.
You can find it on the **Settings → System Info** page (both "App Version" and "UI Version" — if they differ, please report both).
If you built from source, include the commit SHA instead.
placeholder: "e.g., App Version: v0.6.0, UI Version: v0.6.0 (or commit abc1234)"
validations:
required: true
- type: dropdown
id: deployment
attributes:
label: Deployment Method
description: How are you running WeKnora?
options:
- Docker Compose
- Build from source
- Lite (single binary)
- Desktop app
- Kubernetes (Helm)
- Other
validations:
required: true
- type: input
id: os
attributes:
label: Operating System
description: The operating system you are using
placeholder: "e.g., macOS 13.0, Ubuntu 20.04, Windows 11"
validations:
required: true
- type: markdown
attributes:
value: |
## 📋 Log Collection Guide
Please collect logs and paste them into the field below. Pick the command matching your deployment:
- Docker Compose:
```bash
docker compose logs -f --tail=1000 app docreader postgres
```
- Lite / Desktop / build from source: include the console output or log files (e.g., `logs/*.log`).
- type: textarea
id: logs
attributes:
label: Relevant Logs
description: Paste relevant logs or error messages here
placeholder: "Paste logs or error messages..."
render: shell
- type: checkboxes
id: terms
attributes:
label: Confirmation
description: Please confirm the following
options:
- label: I have searched existing issues and confirmed this is a new one
required: true
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: WeKnora Documentation
url: https://github.com/Tencent/WeKnora/blob/main/docs/QA.md
about: Check WeKnora FAQ
- name: Discussions
url: https://github.com/Tencent/WeKnora/discussions
about: Ask questions or share ideas in Discussions
@@ -0,0 +1,88 @@
name: ✨ Feature Request
description: Suggest a new feature or improvement
title: "[Feature]: "
labels: ["enhancement", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for the feature request! Please describe your idea in detail so we can better understand your needs.
- type: dropdown
id: component
attributes:
label: Affected Component
description: Select the component related to this request
options:
- Frontend UI
- Backend Service & API
- Document Reader Service
- Vector Database
- Model Service
- Configuration Management
- Other
validations:
required: true
- type: textarea
id: problem
attributes:
label: Problem Description
description: Describe the problem or limitation of the current feature
placeholder: "What is wrong or missing today?"
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: Describe the feature or improvement you would like to see
placeholder: "How would you like to solve it?"
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: Describe alternative solutions you have considered
placeholder: "Any alternatives you considered?"
- type: dropdown
id: impact
attributes:
label: Impact
description: Who is affected by the absence of this feature?
options:
- Just me / a personal preference
- My team or a small group
- Many users would benefit
- Blocking adoption of WeKnora
validations:
required: true
- type: textarea
id: usecase
attributes:
label: Use Case
description: Describe the specific use case for this feature
placeholder: "When would you use this feature?"
- type: textarea
id: additional
attributes:
label: Additional Information
description: Any other context, screenshots, or links
placeholder: "Add any other relevant information..."
- type: checkboxes
id: terms
attributes:
label: Confirmation
description: Please confirm the following
options:
- label: I have searched existing issues and confirmed this is a new request
required: true
- label: I understand this request may need discussion and evaluation
required: true
+103
View File
@@ -0,0 +1,103 @@
name: ❓ Question
description: Ask a question about WeKnora or seek help
title: "[Question]: "
labels: ["question", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Welcome! Please provide enough information so we can help you effectively.
- type: dropdown
id: category
attributes:
label: Question Category
description: Select the category of your question
options:
- Installation & Deployment
- Configuration
- Usage
- Performance
- Integration
- Other
validations:
required: true
- type: textarea
id: question
attributes:
label: Question Description
description: Please describe your question in detail
placeholder: "What problem did you encounter? What help do you need?"
validations:
required: true
- type: textarea
id: context
attributes:
label: Context
description: Please provide relevant context
placeholder: |
- What are you trying to do?
- What outcome do you expect?
- What have you tried so far?
- type: input
id: os
attributes:
label: Operating System
description: The operating system you are using
placeholder: "e.g., macOS 13.0, Ubuntu 20.04, Windows 11"
validations:
required: true
- type: textarea
id: environment
attributes:
label: Other Environment Info
description: Please provide other relevant environment information
placeholder: |
- WeKnora App Version: [concrete version from Settings → System Info, e.g., v0.6.0 — NOT "latest"]
- WeKnora UI Version: [concrete version from Settings → System Info, e.g., v0.6.0]
- Deployment: [e.g., Docker, build from source]
- Other relevant info...
- type: markdown
attributes:
value: |
## 📋 Log Collection Guide (if needed)
If the issue involves errors or requires debugging, please collect logs:
```bash
docker compose logs -f --tail=1000 app docreader postgres
```
- type: textarea
id: logs
attributes:
label: Relevant Logs
description: Paste relevant logs or error messages here if available
placeholder: "Paste logs or error messages..."
render: shell
- type: textarea
id: research
attributes:
label: Resources Already Checked
description: List the docs, issues or other resources you've already checked
placeholder: |
- Docs checked: [e.g., README.md, API.md]
- Issues checked: [e.g., #123, #456]
- Other resources: [e.g., official docs, Stack Overflow]
- type: checkboxes
id: terms
attributes:
label: Confirmation
description: Please confirm the following
options:
- label: I have searched existing issues and documentation
required: true
- label: I have provided enough information to help understand the question
required: true
+144
View File
@@ -0,0 +1,144 @@
# Dependabot configuration for the WeKnora repository.
#
# Policy:
# * `/cli` is the only ecosystem that receives routine version updates
# (monthly, grouped into one minor/patch PR and one major PR). It's
# a standalone Go binary with a small, well-scoped dependency surface
# where staying current is cheap.
# * Every other ecosystem (server gomod, /client, frontend npm,
# miniprogram npm, docreader pip, github-actions) is configured as
# "security-only" — no scheduled version PRs, but CVE security
# updates still open PRs automatically.
#
# Security-only pattern (mirrors fluent/fluentd-kubernetes-daemonset and
# the dependabot docs for "Disabling Dependabot version updates"):
#
# open-pull-requests-limit: 0 # primary kill-switch for version PRs
# ignore: # belt-and-suspenders: drop every
# - dependency-name: "*" # non-security update at the source.
# update-types: ["version-update"]
# groups: # collapse simultaneous CVE PRs into
# security-updates: # ONE PR per ecosystem so a noisy
# applies-to: security-updates
# patterns: ["*"]
#
# Why both `open-pull-requests-limit: 0` AND `ignore`:
# The official docs treat `limit: 0` as the canonical way to disable
# version updates, but dependabot-core#7353 shows it has historically
# been ignored for some ecosystems. Adding the wildcard `ignore` rule
# guarantees zero scheduled version PRs even if the limit gets
# regressed upstream. Security updates are unaffected by either knob.
#
# Prerequisite: "Dependabot security updates" must be enabled in
# Settings → Code security and analysis. Without it, no CVE PRs are
# opened regardless of this file.
#
# Conventions applied to `/cli`:
# * Monthly schedule (one batch / month).
# * Two groups so per-dep PRs never flood:
# - `cli-deps` bundles minor + patch → ONE PR / month.
# - `cli-deps-major` bundles semver-major bumps the same way so
# breaking changes surface as a single
# review-required PR rather than silently
# accumulating.
# * `open-pull-requests-limit: 3` as a safety net against stalled queues.
# * `commit-message.prefix: "chore(deps)"` to match the repo's
# Conventional Commits convention.
version: 2
updates:
# ─── Go modules ───
- package-ecosystem: gomod
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]
groups:
server-security:
applies-to: security-updates
patterns: ["*"]
- package-ecosystem: gomod
directory: "/cli"
schedule:
interval: monthly
open-pull-requests-limit: 3
commit-message:
prefix: "chore(deps)"
groups:
cli-deps:
patterns: ["*"]
update-types: ["minor", "patch"]
cli-deps-major:
patterns: ["*"]
update-types: ["major"]
- package-ecosystem: gomod
directory: "/client"
schedule:
interval: monthly
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]
groups:
client-security:
applies-to: security-updates
patterns: ["*"]
# ─── npm ───
- package-ecosystem: npm
directory: "/frontend"
schedule:
interval: monthly
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]
groups:
frontend-security:
applies-to: security-updates
patterns: ["*"]
- package-ecosystem: npm
directory: "/miniprogram"
schedule:
interval: monthly
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]
groups:
miniprogram-security:
applies-to: security-updates
patterns: ["*"]
# ─── Python ───
- package-ecosystem: pip
directory: "/docreader"
schedule:
interval: monthly
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]
groups:
docreader-security:
applies-to: security-updates
patterns: ["*"]
# ─── GitHub Actions ───
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor", "version-update:semver-patch"]
groups:
gha-security:
applies-to: security-updates
patterns: ["*"]
+42
View File
@@ -0,0 +1,42 @@
<!-- Title should follow Conventional Commits, e.g. `feat: ...`, `fix: ...`, `docs: ...` -->
## Description
<!-- Briefly describe the purpose and changes of this PR -->
## Type of Change
<!-- Check applicable items -->
- [ ] 🐛 Bug fix
- [ ] ✨ New feature
- [ ] 💥 Breaking change
- [ ] 📚 Documentation update
- [ ] 🎨 Refactor
- [ ] ⚡ Performance improvement
- [ ] 🧪 Test
- [ ] 🔧 Configuration / Build / CI
## Related Issue
<!-- If this PR resolves an issue, use "Fixes #123" or "Closes #123" -->
Fixes #
## Testing
<!-- Describe how these changes were tested. Include reproduction or verification steps. -->
<!--
For a focused change, run checks scoped to the files/packages you changed.
See the Contributing section in README.md for examples. If a full-repository
check is blocked by unrelated baseline or environment failures, record the
exact command and failure here.
-->
## Checklist
- [ ] `git diff --check origin/main...HEAD` passes
- [ ] Changed source files are formatted
- [ ] Targeted tests for the changed packages/components pass
- [ ] Diff-scoped lint passes where applicable (for Go: `golangci-lint run --new-from-rev=origin/main ./...`)
- [ ] Full-repository checks were run, or any unrelated/environment-dependent failures are documented above
- [ ] Self-reviewed the code
- [ ] Added/updated tests covering the change
- [ ] Updated related documentation (README, `docs/`, Swagger annotations, etc.)
- [ ] Breaking changes are clearly called out in the description above
## Screenshots / Recordings
<!-- Required for user-visible UI changes -->
+81
View File
@@ -0,0 +1,81 @@
name: Anydoc
# Compiles the vendored Rust archive, audits it, and exercises the `anydoc`
# build tag. That is minutes of rustc + cargo-audit, so this workflow stays
# off PRs that only touch migrations, frontend, CLI, or unrelated Go packages.
on:
push:
branches: [main]
paths:
- "third_party/anydoc-go/**"
- "scripts/build-anydoc-lib.sh"
- "internal/infrastructure/docparser/**"
- ".github/workflows/anydoc.yml"
pull_request:
paths:
- "third_party/anydoc-go/**"
- "scripts/build-anydoc-lib.sh"
- "internal/infrastructure/docparser/**"
- ".github/workflows/anydoc.yml"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
GO_VERSION: "1.26"
GOPROXY: https://proxy.golang.org,direct
jobs:
anydoc:
name: anydoc parser engine
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- uses: dtolnay/rust-toolchain@stable
- name: Cache cargo-audit
id: cargo-audit-cache
uses: actions/cache@v4
with:
path: ~/.cargo/bin/cargo-audit
key: cargo-audit-${{ runner.os }}
- name: Install cargo-audit
if: steps.cargo-audit-cache.outputs.cache-hit != 'true'
run: cargo install cargo-audit --locked
# The archive is a build artifact, not a committed blob, so CI is what
# keeps the vendored bindings in third_party/anydoc-go compiling.
# This also materializes patched-anydoc/ (gitignored); cargo audit below
# cannot load the crates-io patch until that tree exists.
- name: Build the anydoc static archive
run: ./scripts/build-anydoc-lib.sh
# The vendored bindings pull anydoc and its PDF stack from crates.io, and
# WeKnora parses untrusted uploads in-process. A Rust advisory there (as
# in RUSTSEC-2026-0187, an uncatchable abort on a hostile PDF) has to
# break the build, not wait for someone to notice. Vulnerabilities fail
# the job; unmaintained-crate warnings only report, since a transitive
# crate going unmaintained is not something this repo can act on.
- name: Audit the vendored dependency tree
working-directory: third_party/anydoc-go
run: cargo audit
- name: Vet, test, and build with the engine linked in
run: |
go vet -tags anydoc ./internal/infrastructure/docparser/...
go test -tags anydoc -count=1 ./internal/infrastructure/docparser/...
go build -tags anydoc ./cmd/server
+107
View File
@@ -0,0 +1,107 @@
name: App
# Anydoc (Rust archive + `anydoc` build tag) lives in anydoc.yml so a
# migrations-only or unrelated Go PR does not compile the parser engine.
on:
push:
branches: [main]
paths:
- "**/*.go"
- "!cli/**"
- "!client/**"
- "!docreader/**"
- "!third_party/anydoc-go/**"
- "go.mod"
- "go.sum"
- "Makefile"
- "config/**"
- "migrations/**"
- "skills/preloaded/**"
- "testdata/**"
- "docker/Dockerfile.app"
- ".github/workflows/app.yml"
pull_request:
paths:
- "**/*.go"
- "!cli/**"
- "!client/**"
- "!docreader/**"
- "!third_party/anydoc-go/**"
- "go.mod"
- "go.sum"
- "Makefile"
- "config/**"
- "migrations/**"
- "skills/preloaded/**"
- "testdata/**"
- "docker/Dockerfile.app"
- ".github/workflows/app.yml"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
GO_VERSION: "1.26"
GOPROXY: https://proxy.golang.org,direct
# Keep tests independent of a developer machine's custom log template.
LOG_FORMAT: ""
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
jobs:
verify:
name: Format, vet, test, and build
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: true
# v6 defaults to go.mod for the cache key (more stable than go.sum).
- name: Download modules
run: go mod download
- name: Check formatting
shell: bash
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
# Compare base..head, not the PR merge commit, so files changed only
# on main after the branch point are not attributed to this PR.
diff_range="${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}"
else
base="$BASE_SHA"
if [ -z "$base" ] || [[ "$base" =~ ^0+$ ]]; then
base=$(git rev-parse HEAD^)
fi
diff_range="$base..$GITHUB_SHA"
fi
unformatted=$(
git diff --name-only --diff-filter=ACMR -z "$diff_range" -- '*.go' ':!cli/**' |
xargs -0 -r gofmt -l
)
if [ -n "$unformatted" ]; then
echo "The following Go files are not gofmt-formatted:"
echo "$unformatted"
exit 1
fi
- name: Vet and test
shell: bash
run: |
mapfile -t pkgs < <(go list ./... | grep -v '/docreader/')
go vet "${pkgs[@]}"
go test "${pkgs[@]}"
- name: Build server
run: go build ./cmd/server
+66
View File
@@ -0,0 +1,66 @@
name: CLI E2E
# Runs the cli/acceptance/e2e/ suite against a real WeKnora server.
# Cost-gated so it doesn't fire on every PR:
# - workflow_dispatch: maintainer can trigger manually
# - pull_request labeled "acceptance-e2e": adding the label kicks a run
#
# Required repo secrets (only set on Tencent/WeKnora; cross-fork PRs have no
# access and the job exits early via the secrets-present guard):
# WEKNORA_E2E_HOST - https URL of the test server
# WEKNORA_E2E_TOKEN - bearer token / API key for that host
#
# Mirrors gh acceptance/ trigger pattern: env-gated, opt-in, single matrix.
on:
workflow_dispatch:
pull_request:
types: [labeled, synchronize]
paths:
- 'cli/**'
- '.github/workflows/cli-e2e.yml'
defaults:
run:
working-directory: cli
shell: bash
permissions:
contents: read
jobs:
e2e:
name: rag full loop
# Skip auto-trigger on label-add unless the label is exactly "acceptance-e2e".
# Manual workflow_dispatch always runs.
if: |
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'acceptance-e2e'))
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: '1.26'
cache-dependency-path: cli/go.sum
- name: secrets present
id: secrets
env:
E2E_HOST: ${{ secrets.WEKNORA_E2E_HOST }}
E2E_TOKEN: ${{ secrets.WEKNORA_E2E_TOKEN }}
run: |
if [ -z "$E2E_HOST" ] || [ -z "$E2E_TOKEN" ]; then
echo "::warning::WEKNORA_E2E_HOST or WEKNORA_E2E_TOKEN not set; skipping e2e."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: go test -tags=acceptance_e2e
if: steps.secrets.outputs.skip == 'false'
env:
WEKNORA_E2E_HOST: ${{ secrets.WEKNORA_E2E_HOST }}
WEKNORA_E2E_TOKEN: ${{ secrets.WEKNORA_E2E_TOKEN }}
run: go test -tags=acceptance_e2e -v -timeout=8m ./acceptance/e2e/...
+58
View File
@@ -0,0 +1,58 @@
name: CLI
# Builds and tests the cli/ Go module on every push / PR that touches it.
# Foundation PR (PR-1) gates: 3-platform `go build` + `go test ./...` green.
on:
push:
branches: [main]
paths:
- 'cli/**'
- '.github/workflows/cli.yml'
pull_request:
paths:
- 'cli/**'
- '.github/workflows/cli.yml'
defaults:
run:
working-directory: cli
# Windows default is PowerShell, which tokenizes `go test -coverprofile=coverage.out ./...`
# in a way that detaches `.out` as a separate positional package arg.
# Force bash everywhere so the run lines parse identically across platforms
# (Git Bash is preinstalled on windows-latest runners).
shell: bash
permissions:
contents: read
jobs:
test:
name: test (${{ matrix.os }} / go ${{ matrix.go }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: ['1.26']
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
cache-dependency-path: cli/go.sum
- name: go build
run: go build ./...
- name: go test
run: go test -race -coverprofile=coverage.out ./...
- name: go vet
run: go vet ./...
- name: Check agent-facing doc wire vocabulary
if: runner.os == 'Linux'
run: scripts/check-skill-wire-vocab.sh
- name: Check for committed credentials in docs
if: runner.os == 'Linux'
run: scripts/check-secret-tokens.sh
- name: coverage report
if: matrix.os == 'ubuntu-latest'
run: go tool cover -func=coverage.out
+293
View File
@@ -0,0 +1,293 @@
name: Build and Push Docker Image
on:
push:
tags:
- "v*"
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
NODE_VERSION: "24"
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
build-ui:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Prepare version info
id: version
run: |
eval "$(./scripts/get_version.sh env)"
echo "commit_id=$COMMIT_ID" >> $GITHUB_OUTPUT
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Build frontend
env:
VITE_IS_DOCKER: true
VITE_FRONTEND_COMMIT: ${{ steps.version.outputs.commit_id }}
run: ./scripts/build_frontend_dist.sh
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-ui
- name: Build ui Image
uses: docker/build-push-action@v7
with:
push: true
platforms: linux/amd64,linux/arm64
file: frontend/Dockerfile
context: ./frontend
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha,scope=build-ui
cache-to: type=gha,scope=build-ui,mode=max
build-docreader:
runs-on: ubuntu-latest
steps:
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-docreader
- name: Build docreader Image
uses: docker/build-push-action@v7
with:
push: true
platforms: linux/amd64,linux/arm64
file: docker/Dockerfile.docreader
context: .
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha,scope=build-docreader
cache-to: type=gha,scope=build-docreader,mode=max
build-sandbox:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-sandbox
- name: Build sandbox Image
uses: docker/build-push-action@v7
with:
push: true
platforms: linux/amd64,linux/arm64
file: docker/Dockerfile.sandbox
target: sandbox
context: .
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha,scope=build-sandbox
cache-to: type=gha,scope=build-sandbox,mode=max
# Cube builds templates straight from the image and gates the build on
# GET :49983/health, so it needs the variant with envd baked in.
- name: Docker meta (Cube variant)
id: meta-cube
uses: docker/metadata-action@v6
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-sandbox
flavor: |
suffix=-cube,onlatest=true
# amd64 only: cubesandbox-base, the source of the envd binary, publishes
# no arm64 image.
- name: Build sandbox Cube Image
uses: docker/build-push-action@v7
with:
push: true
platforms: linux/amd64
file: docker/Dockerfile.sandbox
target: cube
context: .
labels: ${{ steps.meta-cube.outputs.labels }}
tags: ${{ steps.meta-cube.outputs.tags }}
cache-from: type=gha,scope=build-sandbox-cube
cache-to: type=gha,scope=build-sandbox-cube,mode=max
build-app:
strategy:
matrix:
include:
- arch: amd64
platform: linux/amd64
runs: ubuntu-latest
- arch: arm64
platform: linux/arm64
runs: ubuntu-24.04-arm
runs-on: ${{ matrix.runs }}
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-app
- name: Prepare version info
id: version
run: |
# 使用统一的版本管理脚本
eval "$(./scripts/get_version.sh env)"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "commit_id=$COMMIT_ID" >> $GITHUB_OUTPUT
echo "build_time=$BUILD_TIME" >> $GITHUB_OUTPUT
echo "go_version=$GO_VERSION" >> $GITHUB_OUTPUT
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
# 显示版本信息
./scripts/get_version.sh info
- name: Build app Image
id: build
uses: docker/build-push-action@v7
with:
push: true
platforms: ${{ matrix.platform }}
file: docker/Dockerfile.app
context: .
build-args: |
${{ format('VERSION_ARG={0}', steps.version.outputs.version) }}
${{ format('COMMIT_ID_ARG={0}', steps.version.outputs.commit_id) }}
${{ format('BUILD_TIME_ARG={0}', steps.version.outputs.build_time) }}
${{ format('GO_VERSION_ARG={0}', steps.version.outputs.go_version) }}
WITH_ANYDOC=1
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-app
cache-from: type=gha,scope=build-app-${{ env.PLATFORM_PAIR }}
cache-to: type=gha,scope=build-app-${{ env.PLATFORM_PAIR }},mode=max
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- build-app
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-app
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ secrets.DOCKERHUB_USERNAME }}/weknora-app@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ secrets.DOCKERHUB_USERNAME }}/weknora-app:${{ steps.meta.outputs.version }}
+107
View File
@@ -0,0 +1,107 @@
name: DocReader
on:
push:
branches: [main]
paths:
- "docreader/**"
- "go.mod"
- "go.sum"
- "testdata/**"
- "docker/Dockerfile.docreader"
- "docker/Dockerfile.odl-hybrid"
- ".github/workflows/docreader.yml"
pull_request:
paths:
- "docreader/**"
- "go.mod"
- "go.sum"
- "testdata/**"
- "docker/Dockerfile.docreader"
- "docker/Dockerfile.odl-hybrid"
- ".github/workflows/docreader.yml"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
PYTHON_VERSION: "3.10.18"
GO_VERSION: "1.26"
GOPROXY: https://proxy.golang.org,direct
jobs:
verify:
name: Python tests and Go client tests
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
cache-dependency-glob: docreader/uv.lock
- name: Install Python dependencies
run: uv sync --project docreader --locked --no-dev --python ${{ env.PYTHON_VERSION }}
- name: Compile Python sources
run: uv run --project docreader --python ${{ env.PYTHON_VERSION }} python -m compileall -q docreader
- name: Run Python tests
run: uv run --project docreader --python ${{ env.PYTHON_VERSION }} python -m unittest discover -s docreader/tests -p "test_*.py" -v
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Download Go modules
run: go mod download
# TestReadURL drives the live WebParser (Playwright WebKit). Without the
# browser binary, scrape used to return "Error parsing web page: …" as
# markdown and the Go client treated that as success. After #2883 the
# parser raises, so CI must install WebKit the same way the image does.
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-webkit-${{ hashFiles('docreader/uv.lock') }}
- name: Install Playwright WebKit
run: uv run --project docreader --python ${{ env.PYTHON_VERSION }} python -m playwright install --with-deps webkit
- name: Start DocReader gRPC server
run: |
uv run --project docreader --python ${{ env.PYTHON_VERSION }} python -m docreader.main &
echo $! > /tmp/docreader.pid
for i in $(seq 1 30); do
if nc -z 127.0.0.1 50051 2>/dev/null; then
echo "DocReader gRPC server is ready"
exit 0
fi
sleep 1
done
echo "DocReader gRPC server failed to start"
exit 1
- name: Run Go client tests
run: go test ./docreader/client ./docreader/proto
- name: Stop DocReader gRPC server
if: always()
run: |
if [ -f /tmp/docreader.pid ]; then
kill "$(cat /tmp/docreader.pid)" 2>/dev/null || true
fi
+268
View File
@@ -0,0 +1,268 @@
name: Build & Publish (dsh-weknora)
# Build, test, and publish the DeepSeek Harness plugin (packages/dsh-weknora/) to npm.
#
# Three jobs:
# test typecheck, unit tests, both halves of the contract, and a
# packaging check
# e2e install the package into a throwaway dsh profile and drive real
# tool calls through the harness agent loop against a mock backend
# publish on a `dsh-weknora-v*` tag only, publish to npm with provenance
#
# The e2e job pins the harness version so a plugin change is the only thing that
# can turn it red. The weekly drift job runs the same check against dsh@latest and
# is allowed to fail: it exists to notice a harness breaking change early, since
# dsh is in developer preview.
on:
push:
branches: [main]
tags:
- "dsh-weknora-v*"
paths:
- "packages/dsh-weknora/**"
- ".github/workflows/dsh-plugin.yml"
pull_request:
paths:
- "packages/dsh-weknora/**"
- ".github/workflows/dsh-plugin.yml"
schedule:
# Monday 03:00 UTC: catch a harness release that breaks the plugin.
- cron: "0 3 * * 1"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
# The harness release this plugin is verified against. Bump it together with
# the compatibility note in packages/dsh-weknora/README.md.
DSH_PINNED_SPEC: "@deepseek-ai/dsh@0.1.0-rc.8"
# The contract's Go half lives in this repository's module. app.yml runs it
# only for a Go diff, so a change to the fixture or to the plugin alone would
# otherwise skip the side that checks WeKnora still serves those calls.
GO_VERSION: "1.26"
# `dsh plugin add` shells out to pnpm to install into the profile, and the
# runner image ships only npm and yarn. The profile it writes carries its
# settings in pnpm-workspace.yaml, which pnpm 10 is the first to read.
PNPM_VERSION: "10"
jobs:
test:
name: Typecheck, test, pack
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: packages/dsh-weknora
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm
cache-dependency-path: packages/dsh-weknora/package-lock.json
- name: Install dependencies
run: npm ci || npm install
- name: Typecheck
run: npm run typecheck
- name: Build
run: npm run build
- name: Unit and contract tests
run: node --test "test/*.test.mjs"
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Contract tests against WeKnora's Go types
working-directory: ${{ github.workspace }}
run: go test ./packages/dsh-weknora/contract/...
- name: Pack and verify the published file list
run: |
set -e
npm pack --dry-run --json > pack.json
node -e "
const files = require('./pack.json')[0].files.map(f => f.path);
const required = ['dist/index.js', 'dist/index.d.ts', 'cordis.patch.yml', 'README.md', 'package.json'];
const missing = required.filter(name => !files.includes(name));
if (missing.length > 0) {
console.error('missing from the tarball: ' + missing.join(', '));
process.exit(1);
}
const leaked = files.filter(name => name.startsWith('test/') || name.startsWith('src/'));
if (leaked.length > 0) {
console.error('unexpected files in the tarball: ' + leaked.join(', '));
process.exit(1);
}
console.log('tarball contents verified: ' + files.length + ' files');
"
rm pack.json
e2e:
name: End-to-end inside dsh (pinned)
runs-on: ubuntu-latest
# Cold cache: npm install @deepseek-ai/dsh takes ~15 min; keep headroom.
timeout-minutes: 25
defaults:
run:
working-directory: packages/dsh-weknora
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm
cache-dependency-path: packages/dsh-weknora/package-lock.json
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
# The e2e script installs @deepseek-ai/dsh on first run (~15 min uncached).
# Reuse the install across workflow runs; bump DSH_PINNED_SPEC to refresh.
- name: Cache pinned dsh install
uses: actions/cache@v4
with:
path: .cache/dsh-e2e-install
key: dsh-e2e-${{ env.DSH_PINNED_SPEC }}
- name: Install dependencies
run: npm ci || npm install
- name: Build
run: npm run build
- name: Run the plugin inside a real dsh profile
env:
DSH_PACKAGE_SPEC: ${{ env.DSH_PINNED_SPEC }}
DSH_INSTALL_DIR: ${{ github.workspace }}/.cache/dsh-e2e-install
DSH_E2E_LOG: ${{ runner.temp }}/dsh-weknora-e2e.log
run: node test/e2e/run-in-dsh.mjs
- name: Upload the transcript
if: always()
uses: actions/upload-artifact@v4
with:
name: dsh-weknora-e2e-transcript
path: ${{ runner.temp }}/dsh-weknora-e2e.log
if-no-files-found: ignore
drift:
name: End-to-end inside dsh (latest, advisory)
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 25
continue-on-error: true
defaults:
run:
working-directory: packages/dsh-weknora
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm
cache-dependency-path: packages/dsh-weknora/package-lock.json
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Week id for dsh@latest cache
id: week
run: echo "id=$(date -u +%G-W%V)" >> "$GITHUB_OUTPUT"
# dsh@latest moves; refresh the cache weekly.
- name: Cache latest dsh install
uses: actions/cache@v4
with:
path: .cache/dsh-e2e-install-latest
key: dsh-e2e-latest-${{ steps.week.outputs.id }}
- name: Install dependencies
run: npm ci || npm install
- name: Build
run: npm run build
- name: Run against the latest harness release
env:
DSH_PACKAGE_SPEC: "@deepseek-ai/dsh@latest"
DSH_INSTALL_DIR: ${{ github.workspace }}/.cache/dsh-e2e-install-latest
run: node test/e2e/run-in-dsh.mjs
publish:
name: Publish to npm
needs: [test, e2e]
if: startsWith(github.ref, 'refs/tags/dsh-weknora-v')
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
environment:
name: npm-publish
url: https://www.npmjs.com/package/@wxg-prc-cpg/dsh-weknora
defaults:
run:
working-directory: packages/dsh-weknora
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
cache: npm
cache-dependency-path: packages/dsh-weknora/package-lock.json
- name: Install dependencies
run: npm ci || npm install
- name: Build
run: npm run build
- name: Check the tag names the packaged version
run: |
set -e
version="$(node -p "require('./package.json').version")"
expected="dsh-weknora-v${version}"
if [ "$GITHUB_REF_NAME" != "$expected" ]; then
echo "::error::tag $GITHUB_REF_NAME does not name the packaged version $version (expected $expected)."
echo "::error::Bump packages/dsh-weknora/package.json, or retag — publishing here would ship a version the tag does not name."
exit 1
fi
- name: Check whether this version is already published
id: check_npm
run: |
set -e
name="$(node -p "require('./package.json').name")"
version="$(node -p "require('./package.json').version")"
echo "packaged: $name@$version"
if npm view "$name@$version" version >/dev/null 2>&1; then
echo "should_publish=false" >> "$GITHUB_OUTPUT"
echo "::warning::$name $version is already on npm; skipping publish. Bump package.json to release."
else
echo "should_publish=true" >> "$GITHUB_OUTPUT"
fi
- name: Publish
if: steps.check_npm.outputs.should_publish == 'true'
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+55
View File
@@ -0,0 +1,55 @@
name: Frontend
on:
push:
branches: [main]
paths:
- "frontend/**"
- "scripts/build_frontend_dist.sh"
- ".github/workflows/frontend.yml"
pull_request:
paths:
- "frontend/**"
- "scripts/build_frontend_dist.sh"
- ".github/workflows/frontend.yml"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
NODE_VERSION: "24"
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
verify:
name: Test, type-check, and build
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci --no-audit --no-fund
- name: Run tests
run: npm test
- name: Type-check
run: npm run type-check
- name: Build
run: npm run build
+59
View File
@@ -0,0 +1,59 @@
name: Go Lint Cache
# Action caches written by a pull request run are scoped to that pull request,
# so only runs on the default branch produce caches every pull request can
# restore. Without this workflow each pull request would recompile the whole
# package graph from scratch (~6 minutes for the root module, versus ~40s warm).
#
# This lives apart from go-lint.yml rather than as a conditional job inside it:
# a job skipped by `if` still surfaces as a check on every pull request, and
# GitHub renders its name with the matrix expression unexpanded.
on:
push:
branches: [main]
paths:
- "**/*.go"
- "**/go.mod"
- "**/go.sum"
- ".golangci.yml"
- ".github/workflows/go-lint-cache.yml"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
GO_VERSION: "1.26"
GOLANGCI_LINT_VERSION: "v2.12.2"
GOPROXY: https://proxy.golang.org,direct
jobs:
warm-cache:
name: warm lint cache (${{ matrix.module }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
module: [".", "cli", "client"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: ${{ matrix.module }}/go.sum
- uses: golangci/golangci-lint-action@v9
with:
version: ${{ env.GOLANGCI_LINT_VERSION }}
working-directory: ${{ matrix.module }}
# This workflow exists for its cache side effect only; the backlog of
# existing findings must not turn main red. The run still prints the
# full report, which makes a manual dispatch a useful backlog view.
args: --issues-exit-code=0
+104
View File
@@ -0,0 +1,104 @@
name: Go Lint
# The repository carries a large backlog of pre-existing golangci-lint findings,
# so pull requests are only judged on the code they introduce (`only-new-issues`).
# That filter needs a pull request diff, which is why this workflow has no other
# trigger; the companion go-lint-cache.yml keeps the caches this job restores
# from warm on main.
on:
pull_request:
paths:
- "**/*.go"
- "**/go.mod"
- "**/go.sum"
- ".golangci.yml"
- ".github/workflows/go-lint.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
GO_VERSION: "1.26"
GOLANGCI_LINT_VERSION: "v2.12.2"
GOPROXY: https://proxy.golang.org,direct
jobs:
# Path filters above start this workflow for any Go change; the matrix still
# used to lint all three modules. Detect which modules the PR actually
# touches so a root-only change (migrations test, handler, …) does not queue
# cli/ and client/ runners. Changing .golangci.yml or this workflow still
# lints every module, since they share the root config.
changes:
name: detect lint modules
runs-on: ubuntu-latest
outputs:
modules: ${{ steps.detect.outputs.modules }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Detect changed modules
id: detect
shell: bash
run: |
diff_range="${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}"
modules=()
if git diff --name-only --diff-filter=ACMR "$diff_range" -- \
.golangci.yml .github/workflows/go-lint.yml | grep -q .; then
modules=("." "cli" "client")
else
if git diff --name-only --diff-filter=ACMR "$diff_range" -- \
'*.go' ':!cli/**' ':!client/**' go.mod go.sum | grep -q .; then
modules+=(".")
fi
if git diff --name-only --diff-filter=ACMR "$diff_range" -- 'cli/**' | grep -q .; then
modules+=("cli")
fi
if git diff --name-only --diff-filter=ACMR "$diff_range" -- 'client/**' | grep -q .; then
modules+=("client")
fi
fi
json="["
first=1
for m in "${modules[@]}"; do
if [ "$first" -eq 1 ]; then
first=0
else
json+=","
fi
json+="\"${m}\""
done
json+="]"
echo "modules=${json}" >> "$GITHUB_OUTPUT"
echo "Lint modules: ${json}"
golangci:
name: golangci-lint (${{ matrix.module }})
needs: changes
if: needs.changes.outputs.modules != '[]'
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
module: ${{ fromJSON(needs.changes.outputs.modules) }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: ${{ matrix.module }}/go.sum
- uses: golangci/golangci-lint-action@v9
with:
version: ${{ env.GOLANGCI_LINT_VERSION }}
working-directory: ${{ matrix.module }}
only-new-issues: true
+149
View File
@@ -0,0 +1,149 @@
name: Build & Publish (mcp-server)
# Build, test, and publish the Python MCP server (mcp-server/) to PyPI.
# Publishing uses PyPI Trusted Publishing (OIDC) — no API token required,
# only the GitHub→PyPI OIDC trust configured on the repo.
#
# Release policy: publishing does NOT depend on tags. Bump the version in
# mcp-server/pyproject.toml (and the synced version strings in setup.py,
# __init__.py, weknora_mcp_server.py server_version, main.py --version),
# merge to main, and the pipeline builds + publishes automatically.
# A pre-publish check queries PyPI and skips upload if that version is
# already published, so re-running on an unchanged version stays green.
# Releases also still trigger on mcp-server-v* tags (back-compat).
on:
push:
branches: [main]
tags:
- "mcp-server-v*"
paths:
- "mcp-server/**"
- ".github/workflows/mcp-server.yml"
pull_request:
paths:
- "mcp-server/**"
- ".github/workflows/mcp-server.yml"
workflow_dispatch:
permissions:
contents: read
jobs:
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
defaults:
run:
working-directory: mcp-server
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies (test extras)
run: uv sync --python ${{ matrix.python-version }} --extra test
- name: Run tests
# unittest avoids pytest importing __init__.py as a broken package root.
# Manual diagnostics live in check_imports.py (not matched by test_*.py).
run: uv run --python ${{ matrix.python-version }} python -m unittest discover -s . -p "test_*.py" -v
build:
name: Build sdist + wheel
needs: test
runs-on: ubuntu-latest
# Build on push to main, manual runs, and release tags — but never on PRs.
if: github.event_name != 'pull_request'
defaults:
run:
working-directory: mcp-server
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Set up Python
run: uv python install 3.12
- name: Build package
run: uv build
- name: Verify wheel contains upload_paths.py (regression guard)
run: |
set -e
if ! python3 -c "import glob, zipfile, sys; whl=glob.glob('dist/*.whl')[0]; z=zipfile.ZipFile(whl); sys.exit(0 if 'upload_paths.py' in z.namelist() else 1)"; then
echo "::error::upload_paths.py is missing from the wheel — check py-modules in pyproject.toml/setup.py"
exit 1
fi
echo "upload_paths.py present in wheel ✓"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: mcp-server/dist/
publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
# Publish on push to main, manual runs (main/tags only), and release tags — never on PRs.
if: >-
github.event_name != 'pull_request' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/mcp-server-v'))
permissions:
id-token: write
environment:
name: pypi
url: https://pypi.org/project/tencent-weknora-mcp/
steps:
- uses: actions/checkout@v4
- name: Check if version is already published
id: check_pypi
working-directory: mcp-server
# Read the version from pyproject.toml and query PyPI. If that exact
# version already exists, skip publishing (so re-runs on an unchanged
# version stay green instead of failing with "File already exists").
run: |
set -e
version="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
echo "packaged version: $version"
exists="$(python3 -c '
import json, sys, urllib.request
v = sys.argv[1]
try:
d = json.load(urllib.request.urlopen("https://pypi.org/pypi/tencent-weknora-mcp/json"))
print("true" if v in d.get("releases", {}) else "false")
except Exception:
# If PyPI is unreachable or the project is brand-new (404), treat as unpublished.
print("false")
' "$version")"
echo "already on PyPI: $exists"
if [ "$exists" = "true" ]; then
echo "should_publish=false" >> "$GITHUB_OUTPUT"
echo "::warning::tencent-weknora-mcp $version is already on PyPI; skipping publish. Bump pyproject.toml (and synced version strings) to release new changes."
else
echo "should_publish=true" >> "$GITHUB_OUTPUT"
fi
- name: Download artifacts
if: steps.check_pypi.outputs.should_publish == 'true'
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish to PyPI
if: steps.check_pypi.outputs.should_publish == 'true'
uses: pypa/gh-action-pypi-publish@release/v1
+451
View File
@@ -0,0 +1,451 @@
name: Release Lite Binaries
on:
# push:
# tags:
# - "v*"
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g. v0.2.0)"
required: true
concurrency:
group: release-lite-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
env:
GO_VERSION: "1.24"
NODE_VERSION: "22"
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
# ── 1. Build frontend once, share as artifact (for Web CLI) ──
build-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Build frontend
working-directory: frontend
run: |
npm ci
npm run build
- uses: actions/upload-artifact@v7
with:
name: frontend-dist
path: frontend/dist/
retention-days: 1
# ── 2a. Build Web CLI binary per platform (native CGO) ──
build-binary:
needs: build-frontend
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
- os: ubuntu-24.04-arm
goos: linux
goarch: arm64
- os: macos-14
goos: darwin
goarch: amd64
- os: macos-14
goos: darwin
goarch: arm64
runs-on: ${{ matrix.os }}
env:
CGO_ENABLED: 1
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- uses: actions/download-artifact@v8
with:
name: frontend-dist
path: web/
- name: Resolve version
id: ver
run: |
if [ -n "${{ inputs.tag }}" ]; then
VERSION="${{ inputs.tag }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Building ${VERSION} for ${{ matrix.goos }}/${{ matrix.goarch }}"
- name: Build
run: |
export EDITION=lite
eval "$(./scripts/get_version.sh env)"
LDFLAGS="-w -s $(./scripts/get_version.sh ldflags)"
export CGO_CFLAGS="-Wno-deprecated-declarations"
if [ "${{ matrix.goos }}" = "darwin" ]; then
export CGO_LDFLAGS="-Wl,-no_warn_duplicate_libraries"
fi
go build -tags "sqlite_fts5" -ldflags="${LDFLAGS}" \
-o WeKnora-lite ./cmd/server
- name: Package tarball
run: |
ARCHIVE="WeKnora-lite_${{ steps.ver.outputs.version }}_${{ matrix.goos }}_${{ matrix.goarch }}"
mkdir -p "${ARCHIVE}/web"
cp WeKnora-lite "${ARCHIVE}/"
cp -r web/* "${ARCHIVE}/web/"
cp .env.lite.example "${ARCHIVE}/"
cp docs/LITE.md "${ARCHIVE}/README.md"
if [ -d config ]; then
cp -r config "${ARCHIVE}/config"
fi
if [ -d migrations/sqlite ]; then
mkdir -p "${ARCHIVE}/migrations/sqlite"
cp -r migrations/sqlite/* "${ARCHIVE}/migrations/sqlite/"
fi
if [ -f deploy/weknora-lite.service ]; then
cp deploy/weknora-lite.service "${ARCHIVE}/"
fi
tar czf "${ARCHIVE}.tar.gz" "${ARCHIVE}"
shasum -a 256 "${ARCHIVE}.tar.gz" > "${ARCHIVE}.tar.gz.sha256"
- uses: actions/upload-artifact@v7
with:
name: release-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
WeKnora-lite_*.tar.gz
WeKnora-lite_*.tar.gz.sha256
retention-days: 3
# ── 2b. Build Desktop APP per platform (Wails) ──
build-desktop-app:
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
platform: darwin/universal
arch_label: macOS_universal
artifact_name: desktop-macos-universal
- os: macos-14
platform: darwin/amd64
arch_label: macOS_amd64
artifact_name: desktop-macos-amd64
- os: macos-14
platform: darwin/arm64
arch_label: macOS_arm64
artifact_name: desktop-macos-arm64
- os: ubuntu-latest
platform: linux/amd64
arch_label: linux_amd64
artifact_name: desktop-linux-amd64
- os: windows-latest
platform: windows/amd64
arch_label: windows_amd64
artifact_name: desktop-windows-amd64
runs-on: ${{ matrix.os }}
env:
CGO_ENABLED: 1
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install Wails CLI
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Install NSIS (Windows)
if: runner.os == 'Windows'
run: choco install nsis -y
- name: Resolve version
id: ver
shell: bash
run: |
if [ -n "${{ inputs.tag }}" ]; then
VERSION="${{ inputs.tag }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Pre-compile Go packages (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
export GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn
export CGO_CFLAGS="-Wno-deprecated-declarations"
cd cmd/desktop
go build -p 1 -buildvcs=false -tags "desktop,sqlite_fts5,production" -o _prebuild_tmp.exe . || true
rm -f _prebuild_tmp.exe
- name: Build desktop app
shell: bash
run: |
export EDITION=lite
export GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn
eval "$(./scripts/get_version.sh env)"
LDFLAGS="$(./scripts/get_version.sh ldflags) -X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'"
export CGO_CFLAGS="-Wno-deprecated-declarations"
BUILD_TAGS="sqlite_fts5"
NSIS_FLAG=""
if [ "${{ runner.os }}" = "Linux" ]; then
BUILD_TAGS="sqlite_fts5 webkit2_41"
fi
if [ "${{ runner.os }}" = "macOS" ]; then
export CGO_LDFLAGS="-Wl,-no_warn_duplicate_libraries"
fi
if [ "${{ runner.os }}" = "Windows" ]; then
NSIS_FLAG="-nsis"
fi
cd cmd/desktop
wails build -clean \
-tags "${BUILD_TAGS}" \
-platform "${{ matrix.platform }}" \
${NSIS_FLAG} \
-ldflags="${LDFLAGS}" \
-o "WeKnora Lite"
# ── macOS: bundle .app with resources, then create DMG ──
- name: Package macOS app
if: runner.os == 'macOS'
run: |
APP_BUNDLE="WeKnora Lite.app"
SRC="cmd/desktop/build/bin/${APP_BUNDLE}"
RESOURCES="${SRC}/Contents/Resources"
mkdir -p "${RESOURCES}/config"
mkdir -p "${RESOURCES}/migrations/sqlite"
if [ -f .env.lite.example ]; then
cp .env.lite.example "${RESOURCES}/.env"
fi
if [ -d migrations/sqlite ]; then
cp -r migrations/sqlite/* "${RESOURCES}/migrations/sqlite/"
fi
if [ -d config ]; then
cp -r config/* "${RESOURCES}/config/"
fi
DMG_NAME="WeKnora-Lite-App_${{ steps.ver.outputs.version }}_${{ matrix.arch_label }}"
DMG_SRC="dmg_staging"
mkdir -p "${DMG_SRC}"
cp -R "cmd/desktop/build/bin/${APP_BUNDLE}" "${DMG_SRC}/"
ln -s /Applications "${DMG_SRC}/Applications"
hdiutil create -volname "WeKnora Lite" \
-srcfolder "${DMG_SRC}" \
-ov -format UDZO \
"${DMG_NAME}.dmg"
shasum -a 256 "${DMG_NAME}.dmg" > "${DMG_NAME}.dmg.sha256"
# ── Linux: package tarball ──
- name: Package Linux app
if: runner.os == 'Linux'
run: |
ARCHIVE="WeKnora-Lite-App_${{ steps.ver.outputs.version }}_${{ matrix.arch_label }}"
mkdir -p "${ARCHIVE}"
cp "cmd/desktop/build/bin/WeKnora Lite" "${ARCHIVE}/"
if [ -f .env.lite.example ]; then
cp .env.lite.example "${ARCHIVE}/"
fi
cp docs/LITE.md "${ARCHIVE}/README.md"
if [ -d config ]; then
cp -r config "${ARCHIVE}/config"
fi
if [ -d migrations/sqlite ]; then
mkdir -p "${ARCHIVE}/migrations/sqlite"
cp -r migrations/sqlite/* "${ARCHIVE}/migrations/sqlite/"
fi
tar czf "${ARCHIVE}.tar.gz" "${ARCHIVE}"
shasum -a 256 "${ARCHIVE}.tar.gz" > "${ARCHIVE}.tar.gz.sha256"
# ── Windows: package NSIS installer or plain exe ──
- name: Package Windows app
if: runner.os == 'Windows'
shell: bash
run: |
ARCHIVE="WeKnora-Lite-App_${{ steps.ver.outputs.version }}_${{ matrix.arch_label }}"
# Wails -nsis generates an installer named {app}-amd64-installer.exe
INSTALLER="cmd/desktop/build/bin/WeKnora Lite-amd64-installer.exe"
if [ -f "$INSTALLER" ]; then
cp "$INSTALLER" "${ARCHIVE}_setup.exe"
sha256sum "${ARCHIVE}_setup.exe" > "${ARCHIVE}_setup.exe.sha256"
else
cp "cmd/desktop/build/bin/WeKnora Lite.exe" "${ARCHIVE}.exe"
sha256sum "${ARCHIVE}.exe" > "${ARCHIVE}.exe.sha256"
fi
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact_name }}
path: |
WeKnora-Lite-App_*
retention-days: 3
# ── 3. Create GitHub Release ──
release:
needs: [build-binary, build-desktop-app]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
pattern: release-*
merge-multiple: true
- uses: actions/download-artifact@v8
with:
pattern: desktop-*
merge-multiple: true
- name: Resolve version
id: ver
run: |
if [ -n "${{ inputs.tag }}" ]; then
VERSION="${{ inputs.tag }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Generate release notes
id: notes
run: |
cat > notes.md << 'NOTES'
## WeKnora Lite ${{ steps.ver.outputs.version }}
### Web CLI 版(命令行 / 服务器部署)
单二进制部署,零外部依赖(无需 Docker / PostgreSQL / Redis)。
```bash
# 1. 解压
tar xzf WeKnora-lite_${{ steps.ver.outputs.version }}_<os>_<arch>.tar.gz
cd WeKnora-lite_${{ steps.ver.outputs.version }}_<os>_<arch>
# 2. 配置
cp .env.lite.example .env.lite
# 编辑 .env.lite,至少确认 OLLAMA_BASE_URL 正确
# 3. 启动 Ollama(如尚未运行)
ollama serve &
ollama pull qwen2.5:7b
ollama pull nomic-embed-text
# 4. 运行
set -a && source .env.lite && set +a
./WeKnora-lite
# 访问 http://localhost:8080
```
| 文件 | 平台 |
|------|------|
| `WeKnora-lite_*_linux_amd64.tar.gz` | Linux x86_64 |
| `WeKnora-lite_*_linux_arm64.tar.gz` | Linux ARM64 |
| `WeKnora-lite_*_darwin_amd64.tar.gz` | macOS Intel |
| `WeKnora-lite_*_darwin_arm64.tar.gz` | macOS Apple Silicon |
---
### 桌面 APP 版(开箱即用,无需命令行)
原生桌面应用,内置 Web 服务,双击即可运行。
| 文件 | 平台 |
|------|------|
| `WeKnora-Lite-App_*_macOS_universal.dmg` | macOS (Intel + Apple Silicon) |
| `WeKnora-Lite-App_*_macOS_amd64.dmg` | macOS Intel |
| `WeKnora-Lite-App_*_macOS_arm64.dmg` | macOS Apple Silicon |
| `WeKnora-Lite-App_*_linux_amd64.tar.gz` | Linux x86_64 |
| `WeKnora-Lite-App_*_windows_amd64_setup.exe` | Windows x86_64 (安装包) |
详细文档见 [LITE.md](docs/LITE.md)。
NOTES
- name: Create release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ steps.ver.outputs.version }}" \
--title "WeKnora Lite ${{ steps.ver.outputs.version }}" \
--notes-file notes.md \
WeKnora-lite_*.tar.gz \
WeKnora-lite_*.tar.gz.sha256 \
WeKnora-Lite-App_* || true
# ── 4. Update Homebrew Formula ──
update-homebrew:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: main
token: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve version
id: ver
run: |
if [ -n "${{ inputs.tag }}" ]; then
VERSION="${{ inputs.tag }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Wait for release assets to be available
run: sleep 15
- name: Update Formula
run: ./scripts/update-homebrew-formula.sh "${{ steps.ver.outputs.version }}"
- name: Commit and push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/weknora-lite.rb
if git diff --cached --quiet; then
echo "No changes to Formula"
else
git commit -m "formula: update weknora-lite to ${{ steps.ver.outputs.version }}"
git push origin main
fi