From 50f3e846019a70aa5a6de296c612090d67660eae Mon Sep 17 00:00:00 2001 From: 0xVox Date: Wed, 13 May 2026 00:01:24 -0400 Subject: [PATCH 1/7] ci(templates): add PR + security tracker issue templates for Linear/Slack mirror sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two issue templates under .github/ISSUE_TEMPLATE/ for long-lived, auditable mirrors of in-flight upstream PRs: - pr_tracker.yml: general PR mirror (scope, evidence, decision log, closure) - security_tracker.yml: high-priority variant (CWE, severity, reachability, verification, disclosure hygiene) Both carry a `pr-mirror` label so the Linear evermind-dash project and the Slack #bots channel can subscribe by label. Bilingual EN + 中文. --- .github/ISSUE_TEMPLATE/pr_tracker.yml | 109 ++++++++++++++++++ .github/ISSUE_TEMPLATE/security_tracker.yml | 116 ++++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/pr_tracker.yml create mode 100644 .github/ISSUE_TEMPLATE/security_tracker.yml diff --git a/.github/ISSUE_TEMPLATE/pr_tracker.yml b/.github/ISSUE_TEMPLATE/pr_tracker.yml new file mode 100644 index 000000000..1dbe050ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/pr_tracker.yml @@ -0,0 +1,109 @@ +name: PR tracker (mirror) +description: Mirror an in-flight pull request as a tracking issue for Linear and Slack sync / 镜像一个进行中的 PR 作为追踪 issue +title: "[PR Track] #: " +labels: ["pr-mirror", "tracking"] +body: + - type: markdown + attributes: + value: | + Use this when you want a long-lived, auditable record of an upstream PR. + Linear and Slack subscribe to issues with the `pr-mirror` label. + 镜像一个 PR 用于 Linear / Slack 长期可审计追踪。带 `pr-mirror` 标签的 issue 会被订阅。 + - type: input + id: pr_number + attributes: + label: PR number + placeholder: "e.g. 196" + validations: + required: true + - type: input + id: pr_url + attributes: + label: PR URL + placeholder: https://github.com/EverMind-AI/EverOS/pull/ + validations: + required: true + - type: input + id: author + attributes: + label: Author handle + placeholder: "@github-login" + validations: + required: true + - type: dropdown + id: area + attributes: + label: Area + options: + - methods/EverCore + - methods/HyperMem + - benchmarks/EverMemBench + - benchmarks/EvoAgentBench + - use-cases + - documentation + - CI / build / release + - other + validations: + required: true + - type: dropdown + id: lane + attributes: + label: Review lane + description: How this PR should be triaged. / 该 PR 的优先级处理通道。 + options: + - hotfix (block release until merged) + - normal (standard review) + - docs-only (light review) + - exploratory (no merge intent) + validations: + required: true + - type: textarea + id: scope + attributes: + label: Scope summary + description: One paragraph. What does the PR change, and what is intentionally left out? + placeholder: | + Changes: + - ... + Out of scope: + - ... + validations: + required: true + - type: textarea + id: evidence + attributes: + label: Evidence snapshot + description: | + Required before this mirror can be closed. Paste the CI summary, test command output, + or the link to the run. "No mirror closes without evidence." + 关闭镜像 issue 前必填。粘贴 CI 摘要、测试命令输出或 run 链接。 + render: shell + validations: + required: true + - type: textarea + id: decisions + attributes: + label: Decision log + description: Notable review decisions (approvals, requested changes, deferrals). + placeholder: | + - 2026-05-13 @reviewer: requested change on tests/test_x.py + - 2026-05-13 @author: scoped follow-up to PR #... + - type: input + id: linear_issue + attributes: + label: Linear issue (optional) + placeholder: "EVE-123" + - type: input + id: slack_thread + attributes: + label: Slack thread (optional) + placeholder: "https://everminddash.slack.com/archives/.../p..." + - type: checkboxes + id: closure + attributes: + label: Closure criteria + description: Check all that apply before closing this mirror. + options: + - label: PR is merged, closed, or marked won't-fix upstream. + - label: Evidence snapshot above reflects the final state. + - label: Linear and Slack records have been updated (if linked). diff --git a/.github/ISSUE_TEMPLATE/security_tracker.yml b/.github/ISSUE_TEMPLATE/security_tracker.yml new file mode 100644 index 000000000..10844bf12 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/security_tracker.yml @@ -0,0 +1,116 @@ +name: Security tracker (mirror) +description: Mirror a security PR or disclosure for Linear and Slack escalation / 镜像一个安全 PR 或披露 +title: "[Security Track] CWE-: " +labels: ["security", "pr-mirror", "tracking", "urgent"] +body: + - type: markdown + attributes: + value: | + Use this for any PR or disclosure that affects credentials, authn/authz, data exposure, + supply chain, or sandbox escape. The `urgent` label routes this to high-priority + notifications in Slack and Linear. + 用于凭证、认证授权、数据暴露、供应链、沙箱逃逸等安全 PR / 披露。`urgent` 标签会触发高优先级通知。 + Do NOT include exploit details that are not already public in the upstream PR. + 请勿写入未在 upstream PR 公开的利用细节。 + - type: input + id: cwe + attributes: + label: CWE id + placeholder: "CWE-798" + validations: + required: true + - type: input + id: pr_url + attributes: + label: Upstream PR or advisory URL + placeholder: https://github.com/EverMind-AI/EverOS/pull/ + validations: + required: true + - type: dropdown + id: severity + attributes: + label: Severity + options: + - Critical (full auth bypass / unauthenticated RCE / mass data loss) + - High (privileged data access / credential exposure / persistent compromise) + - Medium (limited data access / requires user interaction) + - Low (defense-in-depth / hardening) + validations: + required: true + - type: dropdown + id: exposure + attributes: + label: Reachability + description: How reachable is this in the documented quickstart / default config? + options: + - Default config (reproducible from a clean clone) + - Default config + network position + - Non-default config but documented + - Hypothetical / not yet reproducible + validations: + required: true + - type: textarea + id: affected + attributes: + label: Affected components + description: File paths, services, or versions impacted. + placeholder: | + - methods/EverCore/docker-compose.yaml (memsys-milvus-minio block) + - methods/EverCore/env.template + validations: + required: true + - type: textarea + id: fix_summary + attributes: + label: Proposed fix summary + description: One paragraph. What does the PR change? Cite the contract that makes it fail-closed. + validations: + required: true + - type: textarea + id: evidence + attributes: + label: Verification evidence + description: | + Required before closure. Show the commands and output that prove the fix works AND + that the unpatched state was exploitable. "No security mirror closes without evidence." + 关闭前必填。展示证明 fix 生效以及未修复状态可利用的命令与输出。 + render: shell + validations: + required: true + - type: textarea + id: residual + attributes: + label: Residual risk / follow-ups + description: Anything intentionally out of scope, plus follow-up issues that should be filed. + placeholder: | + - docs/installation/ still references the old default in examples; follow-up sweep needed. + - Consider adding a CI lint to catch hardcoded secrets in docker-compose files. + - type: input + id: linear_issue + attributes: + label: Linear issue (optional) + placeholder: "EVE-123" + - type: input + id: slack_thread + attributes: + label: Slack thread (optional) + placeholder: "https://everminddash.slack.com/archives/.../p..." + - type: checkboxes + id: disclosure + attributes: + label: Disclosure hygiene + description: Confirm before submitting. + options: + - label: This mirror contains no exploit details beyond what is already public in the upstream PR. + required: true + - label: The upstream PR or advisory link is correct and reachable. + required: true + - label: A maintainer has been pinged in Slack #bots or via Linear EVE if Severity is Critical or High. + - type: checkboxes + id: closure + attributes: + label: Closure criteria + options: + - label: Upstream PR merged, advisory published, or risk formally accepted. + - label: Verification evidence above reflects the merged state. + - label: Residual-risk follow-ups have issues filed (or explicitly waived). From 8a121390bd2494826dc1cf109ff4cfa4f529bc57 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Wed, 13 May 2026 01:26:59 -0400 Subject: [PATCH 2/7] ci(sync): add auto-rebase workflow keeping fork main current with upstream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs every 6 hours via cron + manual workflow_dispatch. - Rebases fork main onto upstream/main (preserves fork-only commits like the issue templates) - Force-pushes with --force-with-lease for safety - Opens a tracking issue on conflict instead of failing silently Uses default GITHUB_TOKEN — no PAT needed since we only push to fork. --- .github/workflows/sync-upstream.yml | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 000000000..bfd8d8844 --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,61 @@ +name: Sync fork from upstream + +on: + schedule: + - cron: '17 */6 * * *' # 每 6 小时跑一次,分钟 17 错开高峰 + workflow_dispatch: # 也支持手动触发 + +permissions: + contents: write + issues: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Add upstream remote + run: git remote add upstream https://github.com/EverMind-AI/EverOS.git + + - name: Fetch upstream + run: git fetch upstream + + - name: Rebase fork main onto upstream/main + id: rebase + run: | + git checkout main + set +e + git rebase upstream/main + rc=$? + if [ $rc -ne 0 ]; then + git rebase --abort + echo "conflict=true" >> "$GITHUB_OUTPUT" + exit $rc + fi + echo "conflict=false" >> "$GITHUB_OUTPUT" + + - name: Push to fork main + if: steps.rebase.outputs.conflict == 'false' + run: git push origin main --force-with-lease + + - name: Open issue on conflict + if: failure() && steps.rebase.outputs.conflict == 'true' + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[sync] Rebase conflict syncing fork from upstream (${new Date().toISOString().slice(0,10)})`, + body: `Auto-sync from \`upstream/main\` failed due to rebase conflicts.\n\nRun id: ${context.runId}\nWorkflow: ${context.workflow}\n\nResolve manually:\n\n\`\`\`\ncd ~/EverOS && git fetch upstream && git rebase upstream/main\n# resolve conflicts, then:\ngit push origin main --force-with-lease\n\`\`\``, + labels: ['tracking'] + }); From 831bcd8945c92cd132fdd74ef966e1635cc033d3 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Wed, 13 May 2026 01:41:46 -0400 Subject: [PATCH 3/7] ci(linear): add issue mirror workflow for pr-mirror labeled issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Triggers on issues.opened and issues.labeled. When pr-mirror label is present, creates a corresponding Linear issue in the EverMind-Dash project via Linear GraphQL API. Comments back on GitHub with the EVE-id link. Idempotency: skips if a '🔗 Linear:' marker comment already exists. Priority: 'urgent' label -> Linear urgent (1); otherwise medium (3). On API failure: applies 'sync-failed' label for triage. Requires (configured separately): Secret: LINEAR_API_KEY (Linear Personal API key, lin_api_*) Vars: LINEAR_TEAM_ID (EverMind team UUID) LINEAR_PROJECT_ID (EverMind-Dash project UUID) --- .github/workflows/linear-sync.yml | 118 ++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 .github/workflows/linear-sync.yml diff --git a/.github/workflows/linear-sync.yml b/.github/workflows/linear-sync.yml new file mode 100644 index 000000000..82b01e7c5 --- /dev/null +++ b/.github/workflows/linear-sync.yml @@ -0,0 +1,118 @@ +name: Linear sync for tracking mirrors + +on: + issues: + types: [opened, labeled] + +permissions: + issues: write + contents: read + +jobs: + sync: + if: contains(github.event.issue.labels.*.name, 'pr-mirror') + runs-on: ubuntu-latest + steps: + - name: Mirror GitHub issue to Linear + uses: actions/github-script@v7 + env: + LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }} + LINEAR_TEAM_ID: ${{ vars.LINEAR_TEAM_ID }} + LINEAR_PROJECT_ID: ${{ vars.LINEAR_PROJECT_ID }} + with: + script: | + const issue = context.payload.issue; + const repo = `${context.repo.owner}/${context.repo.repo}`; + + // Idempotency: skip if Linear marker comment already exists + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + per_page: 100 + }); + if (comments.some(c => c.body.includes('🔗 Linear:'))) { + core.info('Already synced (marker comment present). Skipping.'); + return; + } + + const isUrgent = issue.labels.some(l => l.name === 'urgent'); + const priority = isUrgent ? 1 : 3; // 1=Urgent, 3=Medium + + const description = [ + `**Source**: [${repo}#${issue.number}](${issue.html_url})`, + '', + '---', + '', + issue.body || '_(no body provided)_', + '', + '---', + '', + `_Auto-synced from GitHub by [linear-sync workflow](https://github.com/${repo}/actions)._` + ].join('\n'); + + const mutation = ` + mutation IssueCreate($input: IssueCreateInput!) { + issueCreate(input: $input) { + success + issue { id identifier url } + } + } + `; + + const response = await fetch('https://api.linear.app/graphql', { + method: 'POST', + headers: { + 'Authorization': process.env.LINEAR_API_KEY, + 'Content-Type': 'application/json', + 'x-apollo-operation-name': 'IssueCreate' + }, + body: JSON.stringify({ + query: mutation, + variables: { + input: { + title: issue.title, + description: description, + teamId: process.env.LINEAR_TEAM_ID, + projectId: process.env.LINEAR_PROJECT_ID, + priority: priority + } + } + }) + }); + + const data = await response.json(); + + if (!response.ok || data.errors || !data?.data?.issueCreate?.success) { + const errMsg = 'Linear API error: ' + JSON.stringify(data, null, 2); + core.error(errMsg); + + try { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'sync-failed', + color: 'D93F0B', + description: 'Linear sync workflow failed for this issue' + }); + } catch (e) { /* exists already */ } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['sync-failed'] + }); + + throw new Error(errMsg); + } + + const linearIssue = data.data.issueCreate.issue; + core.info(`Created ${linearIssue.identifier} -> ${linearIssue.url}`); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `🔗 Linear: [${linearIssue.identifier}](${linearIssue.url})\n\n_Auto-created by linear-sync workflow._` + }); From bee6f1d4a0750fa54718baf769d81b9a3a682579 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Wed, 13 May 2026 01:59:47 -0400 Subject: [PATCH 4/7] docs(templates): align security_tracker Slack reference to actual channel Update the disclosure-hygiene checkbox to reference #p-evermind-dash (the actual Slack channel linked to the EverMind-Dash Linear project) instead of the placeholder #bots. --- .github/ISSUE_TEMPLATE/security_tracker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/security_tracker.yml b/.github/ISSUE_TEMPLATE/security_tracker.yml index 10844bf12..2f2c96c82 100644 --- a/.github/ISSUE_TEMPLATE/security_tracker.yml +++ b/.github/ISSUE_TEMPLATE/security_tracker.yml @@ -105,7 +105,7 @@ body: required: true - label: The upstream PR or advisory link is correct and reachable. required: true - - label: A maintainer has been pinged in Slack #bots or via Linear EVE if Severity is Critical or High. + - label: A maintainer has been pinged in Slack #p-evermind-dash or via Linear EVE if Severity is Critical or High. - type: checkboxes id: closure attributes: From 63be3787b70630f92916da3957fd9cfae0f1ece1 Mon Sep 17 00:00:00 2001 From: 0xVox Date: Wed, 13 May 2026 02:03:39 -0400 Subject: [PATCH 5/7] fix(linear-sync): prevent duplicate Linear issues from multi-label events Two compounding fixes to avoid creating multiple Linear issues from a single GitHub issue creation: 1. concurrency group keyed on issue.number with cancel-in-progress=false serializes runs per issue. Second run will see the first run's comment and skip via existing idempotency check. 2. Tighten 'labeled' event filter to only fire when the added label is pr-mirror itself, not any other label. Eliminates the four extra runs that gh issue create --label A --label B ... triggers (one issues.opened + four issues.labeled = 5 events for a 4-label create). Reproduction: gh issue create with 4 labels including pr-mirror was firing the workflow 5 times concurrently. Idempotency check has a ~5s race window before the first run posts its bot comment, so 2-3 runs created duplicate Linear issues before the rest skipped. Verified via Issue #4 sync producing both EVE-3 and EVE-4. --- .github/workflows/linear-sync.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linear-sync.yml b/.github/workflows/linear-sync.yml index 82b01e7c5..c2af07de5 100644 --- a/.github/workflows/linear-sync.yml +++ b/.github/workflows/linear-sync.yml @@ -8,9 +8,15 @@ permissions: issues: write contents: read +concurrency: + group: linear-sync-issue-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: sync: - if: contains(github.event.issue.labels.*.name, 'pr-mirror') + if: | + contains(github.event.issue.labels.*.name, 'pr-mirror') && + (github.event.action == 'opened' || github.event.label.name == 'pr-mirror') runs-on: ubuntu-latest steps: - name: Mirror GitHub issue to Linear From fe80ca1fd86f64ac27664aa58b41da73b3b2d00c Mon Sep 17 00:00:00 2001 From: 0xVox <35294173+Fearvox@users.noreply.github.com> Date: Wed, 13 May 2026 02:29:51 -0400 Subject: [PATCH 6/7] ci(watch): add overnight fork patrol Adds the fork overnight patrol workflow, Linear-aware tracking issue creation, and docs guard support for coming-soon use-case placeholders. Verified with local script checks and passing Docs CI. --- .github/scripts/overnight-watch.mjs | 371 ++++++++++++++++++++++++ .github/workflows/docs.yml | 3 + .github/workflows/overnight-watch.yml | 56 ++++ docs/fork-playground/overnight-watch.md | 54 ++++ 4 files changed, 484 insertions(+) create mode 100755 .github/scripts/overnight-watch.mjs create mode 100644 .github/workflows/overnight-watch.yml create mode 100644 docs/fork-playground/overnight-watch.md diff --git a/.github/scripts/overnight-watch.mjs b/.github/scripts/overnight-watch.mjs new file mode 100755 index 000000000..5fc2b1c48 --- /dev/null +++ b/.github/scripts/overnight-watch.mjs @@ -0,0 +1,371 @@ +#!/usr/bin/env node + +import { execFileSync } from "node:child_process"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const config = { + repoOwner: process.env.REPO_OWNER || "Fearvox", + repoName: process.env.REPO_NAME || "EverOS", + upstreamRepo: process.env.UPSTREAM_REPO || "EverMind-AI/EverOS", + watchBranch: + process.env.WATCH_BRANCH || "codex-watch-overnight-2026-05-13", + ownerTimezone: process.env.OWNER_TIMEZONE || "America/Los_Angeles", + linearTeamId: + process.env.LINEAR_TEAM_ID || "233391d6-ec9e-4aa8-b534-16a221b8119a", + linearProjectId: + process.env.LINEAR_PROJECT_ID || "39aa3865-345c-4313-9dc0-ab3b509c5d21", + createTrackingIssue: + (process.env.CREATE_TRACKING_ISSUE || "false").toLowerCase() === "true", + githubToken: process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "", + linearApiKey: process.env.LINEAR_API_KEY || "", +}; + +const repoSlug = `${config.repoOwner}/${config.repoName}`; +const recentWindowHours = Number(process.env.WATCH_WINDOW_HOURS || 24); +const since = new Date(Date.now() - recentWindowHours * 60 * 60 * 1000); +const now = new Date(); + +function run(command, args, options = {}) { + const result = execFileSync(command, args, { + encoding: "utf8", + stdio: ["ignore", "pipe", options.inheritStderr ? "inherit" : "pipe"], + env: { + ...process.env, + GH_TOKEN: config.githubToken || process.env.GH_TOKEN, + }, + }); + return result.trim(); +} + +function tryRun(command, args, fallback = "") { + try { + return run(command, args); + } catch (error) { + return fallback; + } +} + +function ghJson(endpoint) { + const output = run("gh", [ + "api", + "-H", + "Accept: application/vnd.github+json", + endpoint, + ]); + return JSON.parse(output); +} + +function ensureRemote(name, url) { + const remotes = tryRun("git", ["remote"]).split("\n").filter(Boolean); + if (!remotes.includes(name)) { + run("git", ["remote", "add", name, url]); + } +} + +function fetchGitState() { + ensureRemote("upstream", `https://github.com/${config.upstreamRepo}.git`); + tryRun("git", ["fetch", "origin", "--prune"], ""); + tryRun("git", ["fetch", "upstream", "main", "--prune"], ""); + + const forkHead = tryRun("git", ["rev-parse", "--short", "origin/main"], "unknown"); + const upstreamHead = tryRun( + "git", + ["rev-parse", "--short", "upstream/main"], + "unknown", + ); + const counts = tryRun( + "git", + ["rev-list", "--left-right", "--count", "origin/main...upstream/main"], + "0\t0", + ) + .split(/\s+/) + .map((value) => Number(value)); + + const watchBranchSha = tryRun( + "git", + ["ls-remote", "--heads", "origin", config.watchBranch], + "", + ) + .split(/\s+/)[0] + ?.slice(0, 12); + + return { + forkHead, + upstreamHead, + forkAhead: counts[0] || 0, + forkBehind: counts[1] || 0, + watchBranchSha: watchBranchSha || "", + }; +} + +function fetchGitHubState() { + const runs = ghJson( + `/repos/${repoSlug}/actions/runs?per_page=50&status=completed`, + ).workflow_runs || []; + const failedRuns = runs + .filter((runInfo) => new Date(runInfo.created_at) >= since) + .filter((runInfo) => + ["failure", "cancelled", "timed_out"].includes(runInfo.conclusion), + ) + .slice(0, 10); + + const upstreamPulls = ghJson( + `/repos/${config.upstreamRepo}/pulls?state=open&sort=updated&direction=desc&per_page=30`, + ) + .filter((pull) => new Date(pull.updated_at) >= since) + .slice(0, 10); + + const forkPulls = ghJson( + `/repos/${repoSlug}/pulls?state=open&sort=updated&direction=desc&per_page=30`, + ) + .filter((pull) => new Date(pull.updated_at) >= since) + .slice(0, 10); + + return { failedRuns, upstreamPulls, forkPulls }; +} + +function lineForPull(pull) { + return `- #${pull.number} ${pull.title} (${pull.user.login}, updated ${pull.updated_at})`; +} + +function lineForRun(runInfo) { + return `- ${runInfo.name}: ${runInfo.conclusion} (${runInfo.html_url})`; +} + +function renderReport(gitState, githubState) { + const findings = []; + + if (!gitState.watchBranchSha) { + findings.push(`Watch branch is not on origin: ${config.watchBranch}`); + } + if (gitState.forkBehind > 0) { + findings.push(`Fork main is behind upstream/main by ${gitState.forkBehind} commit(s).`); + } + if (githubState.failedRuns.length > 0) { + findings.push( + `${githubState.failedRuns.length} completed workflow run(s) failed in the last ${recentWindowHours}h.`, + ); + } + + const verdict = findings.length > 0 ? "FLAG" : "PASS"; + const localNow = now.toLocaleString("en-US", { + timeZone: config.ownerTimezone, + hour12: false, + }); + + return { + verdict, + body: [ + `# Overnight Fork Watch: ${verdict}`, + "", + `Generated: ${now.toISOString()} (${config.ownerTimezone}: ${localNow})`, + `Repository: ${repoSlug}`, + `Upstream: ${config.upstreamRepo}`, + `Watch branch: \`${config.watchBranch}\``, + "", + "## Drift", + "", + `- origin/main: \`${gitState.forkHead}\``, + `- upstream/main: \`${gitState.upstreamHead}\``, + `- fork ahead: ${gitState.forkAhead}`, + `- fork behind: ${gitState.forkBehind}`, + `- watch branch on origin: ${ + gitState.watchBranchSha ? `yes (\`${gitState.watchBranchSha}\`)` : "no" + }`, + "", + "## Findings", + "", + findings.length ? findings.map((item) => `- ${item}`).join("\n") : "- None.", + "", + `## Fork Workflow Failures (${recentWindowHours}h)`, + "", + githubState.failedRuns.length + ? githubState.failedRuns.map(lineForRun).join("\n") + : "- None.", + "", + `## Upstream PRs Updated (${recentWindowHours}h)`, + "", + githubState.upstreamPulls.length + ? githubState.upstreamPulls.map(lineForPull).join("\n") + : "- None.", + "", + `## Fork PRs Updated (${recentWindowHours}h)`, + "", + githubState.forkPulls.length + ? githubState.forkPulls.map(lineForPull).join("\n") + : "- None.", + "", + "## Operator Notes", + "", + "- This issue is safe for public tracking: no local paths, host/IP values, or secrets are included.", + "- A GitHub issue created by `GITHUB_TOKEN` does not trigger secondary workflows, so this watch mirrors to Linear directly when `LINEAR_API_KEY` is available.", + ].join("\n"), + }; +} + +function ensureLabel(name, color, description) { + tryRun("gh", [ + "label", + "create", + name, + "--repo", + repoSlug, + "--color", + color, + "--description", + description, + ]); +} + +function issueHasLinearMarker(issueNumber) { + const comments = ghJson(`/repos/${repoSlug}/issues/${issueNumber}/comments?per_page=100`); + return comments.some((comment) => comment.body.includes("Linear:")); +} + +async function mirrorToLinear(issueNumber, title, body) { + if (!config.linearApiKey || issueHasLinearMarker(issueNumber)) { + return; + } + + const mutation = ` + mutation IssueCreate($input: IssueCreateInput!) { + issueCreate(input: $input) { + success + issue { id identifier url } + } + } + `; + + const response = await fetch("https://api.linear.app/graphql", { + method: "POST", + headers: { + Authorization: config.linearApiKey, + "Content-Type": "application/json", + "x-apollo-operation-name": "IssueCreate", + }, + body: JSON.stringify({ + query: mutation, + variables: { + input: { + title, + description: [ + `**Source**: https://github.com/${repoSlug}/issues/${issueNumber}`, + "", + "---", + "", + body, + ].join("\n"), + teamId: config.linearTeamId, + projectId: config.linearProjectId, + priority: 3, + }, + }, + }), + }); + + const data = await response.json(); + if (!response.ok || data.errors || !data?.data?.issueCreate?.success) { + ensureLabel("sync-failed", "D93F0B", "Linear sync workflow failed for this issue"); + tryRun("gh", [ + "issue", + "edit", + String(issueNumber), + "--repo", + repoSlug, + "--add-label", + "sync-failed", + ]); + throw new Error(`Linear API error: ${JSON.stringify(data)}`); + } + + const linearIssue = data.data.issueCreate.issue; + const marker = `Linear: [${linearIssue.identifier}](${linearIssue.url})\n\n_Auto-created by overnight-watch._`; + run("gh", ["issue", "comment", String(issueNumber), "--repo", repoSlug, "--body", marker]); +} + +function findExistingIssue(title) { + const issues = JSON.parse( + run("gh", [ + "issue", + "list", + "--repo", + repoSlug, + "--state", + "open", + "--label", + "overnight-watch", + "--json", + "number,title", + "--limit", + "20", + ]), + ); + return issues.find((issue) => issue.title === title); +} + +async function createOrUpdateTrackingIssue(report) { + if (!config.createTrackingIssue || report.verdict === "PASS") { + return; + } + + ensureLabel("overnight-watch", "1D76DB", "Automated overnight fork watch"); + ensureLabel("tracking", "5319E7", "Long-lived tracking item"); + ensureLabel("pr-mirror", "0E8A16", "Mirrored into Linear or Slack tracking"); + + const date = now.toISOString().slice(0, 10); + const title = `[watch] Overnight fork patrol: ${date}`; + const existing = findExistingIssue(title); + const tempDir = mkdtempSync(join(tmpdir(), "everos-watch-")); + const bodyFile = join(tempDir, "body.md"); + writeFileSync(bodyFile, report.body); + + if (existing) { + run("gh", [ + "issue", + "comment", + String(existing.number), + "--repo", + repoSlug, + "--body-file", + bodyFile, + ]); + await mirrorToLinear(existing.number, title, report.body); + return; + } + + const created = run("gh", [ + "issue", + "create", + "--repo", + repoSlug, + "--title", + title, + "--body-file", + bodyFile, + "--label", + "overnight-watch", + "--label", + "tracking", + "--label", + "pr-mirror", + ]); + const match = created.match(/\/issues\/(\d+)/); + if (match) { + await mirrorToLinear(Number(match[1]), title, report.body); + } +} + +async function main() { + const gitState = fetchGitState(); + const githubState = fetchGitHubState(); + const report = renderReport(gitState, githubState); + console.log(report.body); + await createOrUpdateTrackingIssue(report); +} + +main().catch((error) => { + console.error(error.message); + process.exit(1); +}); diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a8eec180d..aa74ce5fa 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -103,6 +103,9 @@ jobs: banner_match = re.search(r"\[!\[[^\]]*\]\([^)]+\)\]\(([^)]+)\)", cell) primary_match = re.search(r"^\[(?:Code|Plugin|Live Demo)\]\(([^)]+)\)", cell, flags=re.M) + if "Coming soon" in cell and not primary_match: + continue + if not banner_match: failures.append(f"{path}: {title}: missing linked banner") elif not primary_match: diff --git a/.github/workflows/overnight-watch.yml b/.github/workflows/overnight-watch.yml new file mode 100644 index 000000000..3a0e84897 --- /dev/null +++ b/.github/workflows/overnight-watch.yml @@ -0,0 +1,56 @@ +name: Fork overnight watch + +on: + schedule: + # 02:13 America/Los_Angeles during daylight time. GitHub cron is UTC. + - cron: "13 9 * * *" + workflow_dispatch: + inputs: + create_tracking_issue: + description: "Open/update a tracking issue when the watch verdict is FLAG" + required: false + type: boolean + default: true + watch_branch: + description: "Fork playground branch to verify on origin" + required: false + type: string + default: "codex-watch-overnight-2026-05-13" + +permissions: + actions: read + contents: read + issues: write + pull-requests: read + +concurrency: + group: overnight-watch-${{ github.ref }} + cancel-in-progress: false + +jobs: + watch: + if: github.repository == 'Fearvox/EverOS' + runs-on: ubuntu-latest + env: + REPO_OWNER: ${{ vars.REPO_OWNER || 'Fearvox' }} + REPO_NAME: ${{ vars.REPO_NAME || 'EverOS' }} + UPSTREAM_REPO: EverMind-AI/EverOS + WATCH_BRANCH: ${{ inputs.watch_branch || vars.WATCH_BRANCH || 'codex-watch-overnight-2026-05-13' }} + OWNER_TIMEZONE: ${{ vars.OWNER_TIMEZONE || 'America/Los_Angeles' }} + LINEAR_TEAM_ID: ${{ vars.LINEAR_TEAM_ID || '233391d6-ec9e-4aa8-b534-16a221b8119a' }} + LINEAR_PROJECT_ID: ${{ vars.LINEAR_PROJECT_ID || '39aa3865-345c-4313-9dc0-ab3b509c5d21' }} + LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }} + GH_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ github.token }} + CREATE_TRACKING_ISSUE: ${{ inputs.create_tracking_issue || github.event_name == 'schedule' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Run overnight watch + run: node .github/scripts/overnight-watch.mjs diff --git a/docs/fork-playground/overnight-watch.md b/docs/fork-playground/overnight-watch.md new file mode 100644 index 000000000..379fd21b8 --- /dev/null +++ b/docs/fork-playground/overnight-watch.md @@ -0,0 +1,54 @@ +# Overnight Fork Watch + +This fork can move fast, but the upstream feed should stay boring and auditable. +The overnight watch is a small GitHub Actions patrol for `Fearvox/EverOS`. + +## What It Checks + +- `origin/main` drift against `EverMind-AI/EverOS` `upstream/main`. +- Whether the active playground branch exists on the fork: + `codex-watch-overnight-2026-05-13`. +- Failed, cancelled, or timed-out fork workflow runs in the last 24 hours. +- Upstream and fork pull requests updated in the last 24 hours. + +## Tracking Behavior + +The workflow prints a public-safe report on every run. If the verdict is `FLAG`, +it opens or updates a GitHub issue labeled: + +- `overnight-watch` +- `tracking` +- `pr-mirror` + +Issues created by `GITHUB_TOKEN` do not trigger secondary workflows. Because of +that, the watch mirrors the tracking issue to Linear directly when +`LINEAR_API_KEY` is available. The target Linear team/project are: + +- Team: `233391d6-ec9e-4aa8-b534-16a221b8119a` +- Project: `39aa3865-345c-4313-9dc0-ab3b509c5d21` + +A `FLAG` verdict does not fail the watch workflow by itself. Runtime errors +still fail the workflow, but expected drift or downstream failures are reported +through the tracking issue so the watch does not poison its own next run. + +## Manual Run + +```bash +REPO_OWNER=Fearvox \ +REPO_NAME=EverOS \ +WATCH_BRANCH=codex-watch-overnight-2026-05-13 \ +OWNER_TIMEZONE=America/Los_Angeles \ +LINEAR_TEAM_ID=233391d6-ec9e-4aa8-b534-16a221b8119a \ +LINEAR_PROJECT_ID=39aa3865-345c-4313-9dc0-ab3b509c5d21 \ +CREATE_TRACKING_ISSUE=false \ +node .github/scripts/overnight-watch.mjs +``` + +Set `CREATE_TRACKING_ISSUE=true` only when you want the local run to mutate +GitHub issues. + +## Public-Surface Hygiene + +Reports intentionally avoid local absolute paths, host/IP values, token names +beyond the required GitHub secret names, and operator-only commands. They should +be safe to show in Discord or a screen share. From 49fd7cac7849f0a049072b85be7dd1bf9fdf93d3 Mon Sep 17 00:00:00 2001 From: Claude Sleep Date: Wed, 13 May 2026 03:02:44 -0400 Subject: [PATCH 7/7] =?UTF-8?q?docs(may-agent):=2000-vision=20=E2=80=94=20?= =?UTF-8?q?strategy,=20decision=20matrix,=20and=20success=20criteria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit May Agent vision doc: fork Hermes (MIT) + Rust runtime + Evercore memory kernel. Decision matrix for 8 key architecture choices. 5-criterion success definition for May 31. Top 5 risks with mitigations. Target market: Chinese enterprise (WeCom/Feishu/Tanka) + international. Co-Authored-By: Claude Opus 4.7 --- .planning/may-agent/00-vision.md | 141 +++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 .planning/may-agent/00-vision.md diff --git a/.planning/may-agent/00-vision.md b/.planning/may-agent/00-vision.md new file mode 100644 index 000000000..64f5e33a6 --- /dev/null +++ b/.planning/may-agent/00-vision.md @@ -0,0 +1,141 @@ +# 00 — Vision: May Agent Deliverable + +**Status**: Draft +**Date**: 2026-05-13 +**Audience**: EverMind internal (Yifan, 邓Sir, 七仔, Nolan) +**Sibling docs**: See INDEX.md for full document map + +## TL;DR + +Ship a competitive agent by May 31 that stands against OpenClaw and Hermes in +the Chinese enterprise market. Architecture: fork Hermes (MIT) + Rust rewrite +of the runtime layer + Evercore as the memory kernel. Differentiator: Persist +Memory + enterprise ecosystem (企业微信/飞书/Tanka) + benchmark narrative +(EverMem Bench + Evil Agent Bench). + +## Vision Statement + +The May Agent is **EverMind's first production agent product**. It combines +three proven pieces: + +1. **Hermes agent core** (MIT license, 147k★, battle-tested tool dispatch, + multi-model routing, 20+ messaging platforms) +2. **Rust runtime** (sandbox, plugin loader, CLI — replacing Hermes's 600K+ + lines of Python CLI/TUI code with a fast, safe, single-binary runtime) +3. **Evercore memory kernel** (tenant-scoped long-term memory, multi-tenant + storage, prompt-optimized recall) + +The result: an agent that **remembers across sessions**, runs **safely in +Rust sandboxes**, and deploys **natively into Chinese enterprise chat +ecosystems** (企业微信, 飞书, Tanka). + +## Why This Architecture + +### The "Fork Hermes" Decision + +| Option | Pros | Cons | Verdict | +|--------|------|------|---------| +| Build from scratch | Full control, no legacy | Won't hit May deadline | ❌ Too slow | +| Wrap Hermes as-is | Fastest path | Python GIL, no Rust sandbox, hard to embed | ❌ No differentiation | +| Fork + rewrite runtime | Proven core, Rust differentiation, embeddable | Integration risk, Rust team buy-in | ✅ Chosen | +| Use OpenClaw instead | Strong community | Not MIT-licensed, different ecosystem | ❌ Licensing risk | + +**Decision**: Fork Hermes (MIT) and rewrite the runtime in Rust while keeping +the agent core, tool registry, and model providers intact. + +### The "Rust Runtime" Decision + +The Hermes `cli.py` is 615K lines. The TUI is a separate React/Ink app. The +environments layer (docker, ssh, modal, etc.) is Python. Replacing all of this +with Rust gives us: + +- **Single binary** — no Python venv, no `pip install`, no dependency hell +- **Memory safety** — sandbox enforcement at the type level, not Seatbelt hacks +- **Embeddable** — Tauri shell for desktop, WASM for web, FFI for mobile +- **Performance** — tokio async runtime, zero-copy where possible + +### The "Evercore Kernel" Decision + +Evercore replaces Hermes's pluggable memory layer (`plugins/memory/`). Instead +of swapping between honcho/mem0/supermemory, the agent gets **one memory +backend that actually works**: + +- Tenant-scoped storage (multi-user, multi-project) +- Long-term memory with prompt-optimized retrieval +- EN/ZH bilingual prompt alignment (see `methods/EverCore/src/memory_layer/prompts/`) +- Evaluated on EverMem Bench and EvoAgent Bench + +This is 邓Sir's "abstract Memory" suggestion in practice: keep the Hermes +memory plugin interface, swap the implementation. + +## Target Market + +### Primary: Chinese enterprise AI assistant + +- 企业微信 (WeCom) integration via Hermes's existing gateway pattern +- 飞书 (Feishu/Lark) integration — same pattern, different platform adapter +- Tanka integration — Chinese team communication platform +- Persist Memory as the key differentiator — "the agent that remembers your + project context across sprints" + +### Secondary: International agent market + +- Competitive with OpenClaw on benchmarks +- Open-source narrative via fork → upstream PR strategy +- Discord community (Nolan's inter-PR marketing play) + +## Success Criteria (May 31) + +| Criterion | Threshold | Stretch | +|-----------|-----------|---------| +| Agent ships as single binary | `cargo build --release` produces a working binary | Binary < 50MB | +| Memory works across sessions | EverCore stores + retrieves from prior sessions | >80% recall accuracy on EverMem Bench | +| At least 1 enterprise platform | WeCom OR Feishu adapter works end-to-end | Both platforms | +| Sandbox prevents escape | Basic file-write/network egress containment | Passes Evil Agent Bench escape suite | +| Benchmark narrative ready | EverMem + Evil Agent Bench results published | Beats OpenClaw on 2+ axes | + +## Non-Goals (May 31) + +- Full CLI parity with Hermes (the CLI is 615K lines — we ship the runtime, + not a clone of `hermes --tui`) +- All 8 memory provider plugins (Evercore replaces them) +- All 20+ messaging platforms (1-2 enterprise platforms + 1 international) +- RL training pipeline (environments/ + rl_cli.py — out of scope) +- Mobile apps (architecture supports it, not shipping May 31) + +## Decision Matrix + +| Decision | Owner | Date | Status | +|----------|-------|------|--------| +| Fork Hermes (MIT) | 邓Sir + Nolan | 2026-05-12 | ✅ Approved | +| Rust rewrite (not Python fork) | 邓Sir + Nolan | 2026-05-12 | ✅ Approved | +| Evercore as memory kernel | 邓Sir | 2026-05-12 | ✅ Approved | +| Target WeCom + Feishu | Nolan | 2026-05-13 | 📋 Proposed | +| EverMem Bench as primary eval | Nolan | 2026-05-13 | 📋 Proposed | +| Evil Agent Bench for safety | Nolan | 2026-05-13 | 📋 Proposed | +| Tauri for desktop shell | TBD | — | 🕐 Needs research | +| tokio for async runtime | TBD | — | 🕐 Needs research | + +## Risk Log (top 5) + +1. **Rust team buy-in** — Yifan flagged: "团队大部分人习惯 Python,第一反应会抗拒" + → Mitigation: keep Python for agent core, Rust only for runtime layer +2. **May 31 deadline** — 18 days from today + → Mitigation: scope to "single binary + 1 platform + memory works" +3. **Hermes upstream churn** — Hermes ships rapidly (v0.13 as of May 2026) + → Mitigation: fork at a stable SHA, rebase selectively +4. **邓Sir bandwidth** — "all in 自己的 Agent 品牌上线,可能无暇顾及多" + → Mitigation: Nolan owns integration; 邓Sir reviews architecture +5. **Evercore API stability** — EverCore is pre-1.0 + → Mitigation: pin to a specific EverCore version, define API contract in 30-evercore-integration-contract.md + +## References + +- Hermes recon: `.planning/hermes-recon/architecture.md` (this repo) +- EverCore memory manager: `methods/EverCore/src/agentic_layer/memory_manager.py` +- EverCore API: `methods/EverCore/src/infra_layer/adapters/input/api/` +- EverMem Bench: `benchmarks/EverMemBench/` +- EvoAgent Bench: `benchmarks/EvoAgentBench/` +- Fork strategy: `.planning/hermes-recon/architecture.md#fork-strategy` +- Memory context: `~/.claude/projects/.../memory/project_evermind_2026_05.md` +- Hermes upstream: `NousResearch/hermes-agent` (147k★, MIT, SHA: HEAD 2026-05-13)