chore: import upstream snapshot with attribution
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 2s
Web CI / web typecheck + build (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
CodeQL Analysis / Analyze (go) (push) Failing after 16s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:02:43 +08:00
commit d083df1fdb
731 changed files with 122439 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
# CODEOWNERS — auto-requests maintainer review on sensitive paths.
# Last matching pattern wins. See MAINTAINERS.md for the team + ladder.
#
# Scope principle: gate what CI can't catch (a bad change here passes the
# tests silently) + the catastrophic updater + governance docs. Code that
# test-all.mjs / Renovate / dependency-review already guard (scan.mjs,
# generate-pdf.mjs, test-all.mjs, package.json) is intentionally NOT gated,
# so routine contributor PRs aren't bottlenecked on a single owner. Broaden
# this list once there is more than one code owner.
# Repo docs & governance
/README*.md @santifer
/CONTRIBUTING.md @santifer
/MAINTAINERS.md @santifer
/ARCHITECTURE.md @santifer
/.github/CODEOWNERS @santifer
# Agent behavior & the multi-CLI contract (prompt layer — CI-blind)
/CLAUDE.md @santifer
/AGENTS.md @santifer
/CODEX.md @santifer
/OPENCODE.md @santifer
/GEMINI.md @santifer
# Scoring & the system/user data contract (CI-blind; silent-danger)
/modes/_shared.md @santifer
/DATA_CONTRACT.md @santifer
# The self-updater (catastrophic if wrong — touches user installs)
/update-system.mjs @santifer
/updater-migration-tests.mjs @santifer
# Plugin governance chokepoint (a bad change here ships unreviewed code to
# every user — the registry pin, the gates, and the CI must stay single-owner).
/plugins-registry.json @santifer
/validate-plugin-registry.mjs @santifer
/plugin-install.mjs @santifer
/plugin-audit.mjs @santifer
/plugins/_engine.mjs @santifer
/plugins/_lock.mjs @santifer
/plugins/_net.mjs @santifer
/plugins/_registry.mjs @santifer
/.github/workflows/ @santifer
/docs/PLUGIN_REVIEW.md @santifer
# The experimental web UI — owned by the web track (dual-track review with the
# core maintainer on any PR that also touches root files).
/web/ @santifer
+1
View File
@@ -0,0 +1 @@
github: santifer
+57
View File
@@ -0,0 +1,57 @@
name: Bug Report
description: Report a bug in career-ops
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
> ⚠️ **Privacy first.** career-ops runs locally. **Never** paste your CV, scan results, pipeline data, job listings, or other personal info — issues are **public forever**. Keep your data on your machine; share only sanitized snippets.
- type: checkboxes
id: coc
attributes:
label: Code of Conduct
options:
- label: I agree to follow the [Code of Conduct](https://github.com/santifer/career-ops/blob/main/CODE_OF_CONDUCT.md)
required: true
- type: checkboxes
id: search
attributes:
label: Existing issues
options:
- label: I searched existing issues and this hasn't been reported yet
required: true
- type: textarea
id: description
attributes:
label: What happened?
description: A clear description of the bug
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: How can we reproduce this?
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: What should have happened?
- type: input
id: cli
attributes:
label: CLI tool
placeholder: "e.g., Claude Code, OpenCode, Codex"
- type: input
id: os
attributes:
label: OS and Node.js version
placeholder: "e.g., macOS 15 / Node 20.11"
- type: textarea
id: doctor
attributes:
label: Output of npm run doctor
description: Paste the output here (helps us diagnose setup issues)
render: shell
+11
View File
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Setup Help / How-to Questions
url: https://discord.gg/8pRpHETxa4
about: For setup help and general questions, ask in Discord instead of opening an issue.
- name: GitHub Discussions
url: https://github.com/santifer/career-ops/discussions
about: For ideas, Q&A, and community conversations.
- name: "📤 Sharing scan results or automation output?"
url: https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md
about: Your scans and reports stay local (data/ + reports/). Please don't file them here — they'd be public, and they're not needed upstream. Point any scheduled run at your own fork.
@@ -0,0 +1,51 @@
name: Feature Request
description: Suggest a new feature or improvement
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
> ⚠️ **Privacy first.** career-ops runs locally. **Never** paste your CV, scan results, pipeline data, job listings, or other personal info — issues are **public forever**. Keep your data on your machine; share only sanitized snippets.
- type: checkboxes
id: coc
attributes:
label: Code of Conduct
options:
- label: I agree to follow the [Code of Conduct](https://github.com/santifer/career-ops/blob/main/CODE_OF_CONDUCT.md)
required: true
- type: checkboxes
id: search
attributes:
label: Existing issues
options:
- label: I searched existing issues and this hasn't been requested yet
required: true
- type: textarea
id: problem
attributes:
label: Problem
description: What problem does this solve? Why do you need this?
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed solution
description: How would you like this to work?
validations:
required: true
- type: dropdown
id: area
attributes:
label: Area
options:
- Evaluation / Scoring
- PDF Generation
- Portal Scanner
- Dashboard TUI
- Batch Processing
- Language / i18n
- Documentation
- Other
validations:
required: true
+65
View File
@@ -0,0 +1,65 @@
name: "I Got Hired!"
description: Share your success story — you landed a job using career-ops
labels: ["hired"]
body:
- type: markdown
attributes:
value: |
Congrats on the new role! Your story helps others see what's possible.
- type: input
id: role
attributes:
label: What role did you land?
placeholder: "e.g., Senior Backend Engineer, Product Manager, ML Engineer"
validations:
required: true
- type: input
id: company_type
attributes:
label: Company type
placeholder: "e.g., startup, big tech, agency, remote-first"
- type: textarea
id: story
attributes:
label: Your story
description: How did career-ops help? What features made the difference?
placeholder: "e.g., I evaluated 50+ listings, the scoring helped me focus on 3 real matches, generated tailored CVs for each..."
validations:
required: true
- type: dropdown
id: feature
attributes:
label: Most useful feature
options:
- A-F Evaluation scoring
- PDF / CV generation
- Portal scanner
- Interview prep (STAR stories)
- Batch processing
- Follow-up tracker
- Ghost job detection (Block G)
- Other
validations:
required: true
- type: input
id: listings_evaluated
attributes:
label: How many listings did you evaluate?
placeholder: "e.g., 30, 100, 500+"
- type: input
id: time_to_hire
attributes:
label: Time from first scan to offer
placeholder: "e.g., 2 weeks, 1 month, 3 months"
- type: textarea
id: advice
attributes:
label: One tip for others
description: What would you tell someone just starting their job search with career-ops?
- type: checkboxes
id: permission
attributes:
label: Permission
options:
- label: I'm okay with my story being featured in the README and release notes
required: false
@@ -0,0 +1,52 @@
name: Plugin registration
description: Register your career-ops-plugin-<name> repo so it can be listed as an approved community plugin
labels: ["plugin-submission", "area:plugins"]
body:
- type: markdown
attributes:
value: |
> ⚠️ **Privacy first.** Never paste your CV, scan results, or other personal data — issues are **public forever**.
This issue becomes your plugin's **home** (its changelog + compatibility/quarantine board). After it's filed, open a **registry PR** (the plugin template can do this for you on a release tag) to actually list it. See [docs/PLUGINS.md](https://github.com/santifer/career-ops/blob/main/docs/PLUGINS.md).
- type: checkboxes
id: coc
attributes:
label: Code of Conduct
options:
- label: I agree to follow the [Code of Conduct](https://github.com/santifer/career-ops/blob/main/CODE_OF_CONDUCT.md)
required: true
- type: input
id: repo
attributes:
label: Repo URL
description: Must be named `career-ops-plugin-<name>` (a public GitHub repo you own).
placeholder: https://github.com/your-user/career-ops-plugin-greenhouse
validations:
required: true
- type: input
id: desc
attributes:
label: One-line description
description: Mission-framed, what it does.
validations:
required: true
- type: textarea
id: hooks
attributes:
label: Hooks + keys + hosts
description: Which hooks (provider/ingest/search/notify/export), which env keys it needs, which hosts it talks to.
validations:
required: true
- type: checkboxes
id: attest
attributes:
label: Attestations
options:
- label: It is human-in-the-loop — it never auto-submits an application
required: true
- label: It reads PUBLIC data or the user's OWN account only — no centralized infrastructure
required: true
- label: MIT-compatible license; no personal data in the repo
required: true
- label: No commercial / hosted-service / monetization wording — career-ops is free and local-first
required: true
+57
View File
@@ -0,0 +1,57 @@
name: Web (alpha) Bug
description: Report a bug in the experimental career-ops web UI (opt-in alpha)
labels: ["web-alpha", "area:web"]
body:
- type: markdown
attributes:
value: |
Thanks for trying the **experimental career-ops web UI** (opt-in alpha)! 🙏
Tip: the in-app **Report a bug** button (bottom-left pill) pre-fills most of this for you.
**Please do NOT paste your CV, profile, real application answers, or private job URLs.**
- type: checkboxes
id: coc
attributes:
label: Code of Conduct
options:
- label: I agree to follow the [Code of Conduct](https://github.com/santifer/career-ops/blob/main/CODE_OF_CONDUCT.md)
required: true
- type: textarea
id: description
attributes:
label: What happened?
description: What were you doing, and what went wrong?
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: How can we reproduce it?
- type: input
id: version
attributes:
label: Version / channel
placeholder: "e.g., 2.0.0-rc.1 · beta · abc1234"
validations:
required: true
- type: input
id: screen
attributes:
label: Screen / route
placeholder: "e.g., /apply"
- type: input
id: cli
attributes:
label: CLI tool
placeholder: "e.g., Claude Code, Gemini, OpenCode"
- type: input
id: env
attributes:
label: OS / Browser
placeholder: "e.g., macOS 15 / Chrome 138"
- type: textarea
id: logs
attributes:
label: Recent errors (from the in-app reporter)
description: Paste the auto-captured diagnostics here.
render: shell
+28
View File
@@ -0,0 +1,28 @@
## What does this PR do?
<!-- Describe the change in 1-3 sentences -->
## Related issue
<!-- Link the issue this PR addresses, if it has one. Format: Fixes #123 / Closes #123 -->
<!-- Issue-first is asked for NEW FEATURES, new modes/commands, and architecture changes — it saves you from building something we'd have to redirect. -->
<!-- No issue needed, send the PR straight in: bug fixes, new zero-auth scanner providers, docs, and translations. We don't want to slow these down. -->
## Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation / translation
- [ ] Refactor (no behavior change)
## Checklist
- [ ] I have read [CONTRIBUTING.md](https://github.com/santifer/career-ops/blob/main/CONTRIBUTING.md)
- [ ] If this is a new feature or architecture change, I opened an issue first (bug fixes, providers, docs & translations are exempt — send those straight in)
- [ ] My PR does not include personal data (CV, email, real names, scan results, or pipeline data)
- [ ] I ran `node test-all.mjs` and all tests pass
- [ ] My changes respect the [Data Contract](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) (no modifications to user-layer files)
- [ ] My changes align with the [project roadmap](https://github.com/santifer/career-ops/discussions/156)
---
Questions? [Join the Discord](https://discord.gg/8pRpHETxa4) for faster feedback.
@@ -0,0 +1,44 @@
<!--
Use this template for a PR that adds or updates ONE plugins-registry/<id>.json file.
Select it by appending ?template=plugin-registry.md to the PR-compare URL, or
the plugin template's release workflow opens it for you on a release tag.
ONE plugin per PR. Change ONLY that plugin's entry.
-->
## Plugin registry change
Home issue: #____
Paste the registry entry (one object), pinned to the exact reviewed commit:
```json
{
"name": "career-ops-plugin-<name>",
"id": "<name>",
"repo": "https://github.com/<you>/career-ops-plugin-<name>",
"author": "<you>",
"hooks": ["provider"],
"description": "Mission-framed one-liner.",
"requiredEnv": [],
"allowedHosts": ["api.example.com"],
"skill": true,
"license": "MIT",
"version": "1.0.0",
"sha": "<40-hex-commit>"
}
```
### Maintainer review checklist
- [ ] Naming `career-ops-plugin-<name>`; `id` == name minus the prefix
- [ ] Minimum files present (manifest.json, index.mjs, README.md, LICENSE)
- [ ] Manifest valid: apiVersion 1, `humanInTheLoop: true`, hooks ⊆ {provider, ingest, search, notify, export} — **no apply/submit**
- [ ] MIT-compatible LICENSE; no personal data in the repo
- [ ] Egress: `allowedHosts` are real public hosts; no IP literals / metadata / `*.internal`; no localhost without `allowsLocalhost` + a reason
- [ ] Static audit clean: no `child_process`/`playwright`/raw sockets/global `fetch`/`eval`/bare-dependency imports (egress only via `ctx.fetch`)
- [ ] No core-owned secrets in `requiredEnv`
- [ ] Reads PUBLIC data or the user's OWN account only — no centralized infrastructure, no auto-submit, no blind-apply
- [ ] No commercial / hosted-service / monetization wording (the project is free and local-first)
- [ ] If it ships a skill: domain-scoped — does not instruct the agent to edit core files, change scoring, reveal secrets, or act outside its hooks
- [ ] `sha` is pinned to the exact reviewed commit
- [ ] CI (`plugin-registry-validate`) is green
+34
View File
@@ -0,0 +1,34 @@
# Security Policy
## Reporting a Vulnerability
**Do NOT open a public issue for security vulnerabilities.**
Instead, please email **hi@santifer.io** with:
1. Description of the vulnerability
2. Steps to reproduce
3. Potential impact
4. Suggested fix (if any)
You will receive a response within 72 hours. We will work with you to understand and address the issue before any public disclosure.
## Scope
Security issues in the following are in scope:
- **Scripts** (`*.mjs`) — command injection, path traversal, SSRF
- **Dashboard** (`dashboard/`) — any Go binary vulnerabilities
- **Templates** (`templates/`) — XSS in generated HTML/PDF
- **Configuration** — secrets exposure, unsafe defaults
## Out of Scope
- Issues in third-party dependencies (report upstream)
- Issues requiring physical access to the user's machine
- Social engineering attacks
- career-ops is a local tool — there is no hosted service to attack
## Disclosure Policy
We follow coordinated disclosure. Once a fix is released, we will credit the reporter (unless they prefer anonymity) in the release notes.
+19
View File
@@ -0,0 +1,19 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
- package-ecosystem: "gomod"
directory: "/dashboard"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
+62
View File
@@ -0,0 +1,62 @@
"🔴 core-architecture":
- changed-files:
- any-glob-to-any-file:
- AGENTS.md
- CLAUDE.md
- OPENCODE.md
- DATA_CONTRACT.md
- modes/_shared.md
"⚠️ agent-behavior":
- changed-files:
- any-glob-to-any-file:
- modes/*.md
- modes/**/*.md
- .claude/skills/**
- .opencode/skills/**
- .qwen/skills/**
- .antigravitycli/skills/**
- .grok/skills/**
"🔧 scripts":
- changed-files:
- any-glob-to-any-file:
- "*.mjs"
- batch/**
"📄 docs":
- changed-files:
- any-glob-to-any-file:
- docs/**
- "README*.md"
- CONTRIBUTING.md
- GOVERNANCE.md
- CODE_OF_CONDUCT.md
- SECURITY.md
- SUPPORT.md
"🌐 i18n":
- changed-files:
- any-glob-to-any-file:
- "README.*.md"
- modes/de/**
- modes/fr/**
- modes/hi/**
- modes/ja/**
- modes/ru/**
"📊 dashboard":
- changed-files:
- any-glob-to-any-file:
- dashboard/**
"📦 dependencies":
- changed-files:
- any-glob-to-any-file:
- package.json
- package-lock.json
"⚙️ ci":
- changed-files:
- any-glob-to-any-file:
- .github/**
@@ -0,0 +1,87 @@
name: Auto-triage scan output
# career-ops runs locally. Some users point their own scheduled automation at
# this repo by mistake and file their personal scan results / run reports as
# issues here. Those are created via the API / `gh issue create`, which bypasses
# the issue templates and `blank_issues_enabled: false` entirely — so only a
# workflow can catch them. This one explains, labels, and closes them: it
# protects users' personal job-search data and keeps the public tracker clean.
#
# Deliberately CONSERVATIVE — it acts only when BOTH are true:
# (a) the issue did NOT come through a template (no Code of Conduct checkbox), and
# (b) it carries an unmistakable automated-report marker (boilerplate the
# scheduled-scan reports ship with — not merely the word "scan", which
# legitimate scanner-provider features use).
# It never locks the issue, so a real report can always be reopened.
on:
issues:
types: [opened, reopened]
permissions:
issues: write
jobs:
triage:
runs-on: ubuntu-latest
# Never auto-close issues opened by the owner or org members.
if: ${{ github.event.issue.author_association != 'OWNER' && github.event.issue.author_association != 'MEMBER' }}
steps:
- uses: actions/github-script@v9
with:
script: |
const issue = context.payload.issue;
const body = issue.body || '';
const hay = `${issue.title || ''}\n${body}`;
// Gate A — template-created issues carry the CoC checkbox. Real
// contributions always come through a template, so never touch them.
if (/Code of Conduct/i.test(body)) return;
// Gate B — must carry an unmistakable automated-report marker.
const markers = [
/Generated by career-ops automated routine/i,
/Scheduled Portal Scan/i,
/Scheduled Run Report/i,
// Original shipped marker (unchanged): bare "Automated Scan"[ Result| Failed].
/Automated [Ss]can( (Result|Failed))?/i,
// Also catch the machine report header "## Automated <Weekly|Daily|Nightly|…> Scan"
// (one interposed adjective). Header-anchored (`^#{1,6}\s+…`, multiline) so human issue
// titles or prose that merely mention an "automated … scan" are NOT auto-closed — only
// the generated markdown section header matches.
/^#{1,6}\s+Automated (?:\w+ )?[Ss]can\b/im,
// Emoji-led scan-digest title, e.g. "🔍 Scan results — <date> — N new leads" (#1791).
/^\s*(?:🔍|🔎|📊|📋|🧭|📡|✨)\s*Scan\b/iu,
/Auto-scan Results/i,
/Daily AI Job Scan/i,
/Morning (Briefing|Automation Run)/i,
/Nightly Graphify/i,
/This is an automated scan result/i,
/onboarding (required|not complete)/i,
/Gmail MCP token/i,
/new offers added to pipeline/i,
];
if (!markers.some((re) => re.test(hay))) return;
const { owner, repo } = context.repo;
const issue_number = issue.number;
await github.rest.issues.createComment({
owner, repo, issue_number,
body: [
`Hey @${issue.user.login} — this looks like automated output from your own career-ops setup landing on the upstream repo. 👋`,
``,
`career-ops runs **entirely locally**: your pipeline, scans, and reports live on your own machine (\`data/\`, \`reports/\`) — they never need to be filed here, and posting them upstream puts personal job-search data on a public tracker. Closing to keep your data private and the tracker tidy.`,
``,
`If you have a scheduled run that opens issues, point it at your **own fork** or a private repo — not \`${owner}/${repo}\`. Real bugs are always welcome via the bug-report template. Thanks for building with career-ops! 🚀`,
].join('\n'),
});
try {
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['scan-output'] });
} catch (e) {
core.info(`label add skipped: ${e.message}`);
}
await github.rest.issues.update({ owner, repo, issue_number, state: 'closed', state_reason: 'not_planned' });
core.info(`Closed #${issue_number} as scan-output.`);
+57
View File
@@ -0,0 +1,57 @@
name: 'CodeQL Analysis'
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 4 * * 1' # Weekly Monday 4am UTC
permissions:
actions: read
contents: read
security-events: write
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ['javascript-typescript', 'go']
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Setup Go
if: matrix.language == 'go'
uses: actions/setup-go@v6
with:
go-version-file: dashboard/go.mod
cache-dependency-path: dashboard/go.sum
- name: Build Go
if: matrix.language == 'go'
# go build skips *_test.go, so CodeQL's traced build never saw test
# files (tool status showed Go at 13/26 files scanned). Compiling the
# test packages without running them (-run '^$') lets the extractor
# cover them too. The two windows-tagged files stay out by platform.
run: |
cd dashboard
go build ./...
go test -run '^$' ./...
- name: Autobuild
if: matrix.language != 'go'
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: '/language:${{ matrix.language }}'
+26
View File
@@ -0,0 +1,26 @@
name: Dependency Review
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: write
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Dependency Review
# TODO: remove continue-on-error once the Dependency graph is enabled in
# Settings > Security & Analysis (see #343). Until then, this is a temporary
# workaround to unblock PRs — note that fail-on-severity: high is still
# configured but will not block merges while continue-on-error is true.
continue-on-error: true
uses: actions/dependency-review-action@v5.0.0
with:
fail-on-severity: high
comment-summary-in-pr: always
+16
View File
@@ -0,0 +1,16 @@
name: Label PRs
on:
pull_request_target:
types: [opened, synchronize]
jobs:
label:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/labeler@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
+64
View File
@@ -0,0 +1,64 @@
name: No user data in PRs
# Hard-blocks any PR that adds or modifies user-layer (private data) files.
# These live ONLY on the candidate's machine per DATA_CONTRACT.md and must never
# enter the repo — they are personal job-search data. Sample/fixture data belongs
# under examples/. Tracked scaffolding (.gitkeep keepers, dir READMEs) is exempt.
# Paths mirror USER_PATHS in update-system.mjs. Uses the API (listFiles) so it
# works for fork PRs too.
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: read
jobs:
guard:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const pull_number = context.payload.pull_request.number;
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number, per_page: 100,
});
// User-layer paths from DATA_CONTRACT.md and update-system.mjs.
// voice-dna.md is omitted on purpose: it ships as a populated
// system default, so edits to it are legit.
const USER_PATHS = [
/^cv\.md$/,
/^config\/profile\.yml$/,
/^modes\/_profile\.md$/,
/^modes\/_custom\.md$/,
/^portals\.yml$/,
/^article-digest\.md$/,
/^interview-prep\//,
/^data\//,
/^reports\//,
/^output\//,
/^jds\//,
/^writing-samples\//,
];
// Tracked scaffolding that legitimately lives under those dirs.
const isScaffold = (f) => /(^|\/)\.gitkeep$/.test(f) || /(^|\/)README\.md$/.test(f);
const bad = files
.filter((f) => f.status !== 'removed')
.map((f) => f.filename)
.filter((f) => USER_PATHS.some((re) => re.test(f)) && !isScaffold(f));
if (bad.length) {
core.setFailed(
'This PR adds/modifies user-layer (private data) files. These live ONLY on your ' +
'machine per DATA_CONTRACT.md and must never be committed — use examples/ for ' +
'sample data.\nOffending files:\n ' + bad.join('\n ')
);
} else {
core.info('OK — no user-layer (private data) files touched.');
}
@@ -0,0 +1,63 @@
name: 'Plugin registry validate'
# Runs on PRs that touch the curated plugin registry. Uses `pull_request` (NOT
# pull_request_target), read-only permissions, and NO secrets — so cloning +
# statically auditing a submitter's plugin can never reach a token or write the
# repo. No plugin code is executed (the manifest is parsed, the audit is static).
on:
pull_request:
paths:
- 'plugins-registry/**'
- 'plugins-registry.json' # legacy single-file location (pre-migration forks)
permissions:
contents: read
# Cancel superseded runs when the same PR is pushed again.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
validate:
name: Validate registry entries
runs-on: ubuntu-latest
steps:
# fetch-depth: 0 so the "One plugin per PR" diff below has the base
# branch's history + merge-base available (a shallow checkout truncates it).
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: '24'
# Deterministic shape gate (no network).
- name: Validate registry shape
run: node validate-plugin-registry.mjs
# Deep gate: clone each entry at its pinned SHA + static-validate + audit.
# No secrets are present in this job; nothing the plugin ships is executed.
- name: Clone + audit each registry entry
run: node validate-plugin-registry.mjs --deep
- name: One plugin per PR
# base_ref goes through env, never ${{ }}-spliced into the script —
# anything expanded inside run: is literal shell code (template injection).
env:
BASE_REF: ${{ github.base_ref }}
run: |
# Make origin/$BASE_REF resolvable (checkout of a PR merge ref does not
# create the base remote-tracking branch on its own).
git fetch --no-tags origin "$BASE_REF"
# One-time migration exemption: the PR that removes the legacy single
# file necessarily rewrites every entry into plugins-registry/<id>.json.
# This can only ever match the migration itself — once it lands,
# plugins-registry.json no longer exists for any future PR to delete,
# so the one-plugin gate below still holds for all registration PRs.
if git diff --name-only --diff-filter=D "origin/$BASE_REF"...HEAD -- plugins-registry.json | grep -q .; then
echo "Migration PR (removes legacy plugins-registry.json) — one-plugin-per-PR gate not applicable."
exit 0
fi
CHANGED=$(git diff --name-only "origin/$BASE_REF"...HEAD -- 'plugins-registry/*.json' plugins-registry.json | wc -l)
if [ "$CHANGED" -gt 1 ]; then echo "A registry PR may add or bump exactly one plugins-registry/<id>.json file"; exit 1; fi
+36
View File
@@ -0,0 +1,36 @@
name: Release Please
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v5
id: release
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
# Publish the npm scaffolder only when a release was just created.
# Runs in the same job because a release created via GITHUB_TOKEN does
# not trigger separate `on: release` workflows.
- uses: actions/checkout@v7
if: ${{ steps.release.outputs.release_created }}
- uses: actions/setup-node@v6
if: ${{ steps.release.outputs.release_created }}
with:
node-version: 24
registry-url: "https://registry.npmjs.org"
- name: Publish scaffolder to npm
if: ${{ steps.release.outputs.release_created }}
run: npm publish --provenance --access public
working-directory: scaffolder
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+26
View File
@@ -0,0 +1,26 @@
name: SBOM Generation
on:
release:
types: [published]
permissions:
contents: write
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Generate SBOM (SPDX)
uses: anchore/sbom-action@v0
with:
artifact-name: career-ops-sbom.spdx.json
format: spdx-json
output-file: career-ops-sbom.spdx.json
- name: Upload SBOM to release
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload ${{ github.event.release.tag_name }} career-ops-sbom.spdx.json
+34
View File
@@ -0,0 +1,34 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '0 6 * * 1' # Every Monday at 6am UTC
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
with:
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
activity in 60 days. It will be closed in 14 days if no further activity occurs.
If this is still relevant, please comment to keep it open.
stale-pr-message: >
This PR has been automatically marked as stale because it has not had
activity in 30 days. It will be closed in 14 days if no further activity occurs.
If you're still working on this, please rebase and comment to keep it open.
close-issue-message: 'Closed due to inactivity. Feel free to reopen if this is still relevant.'
close-pr-message: 'Closed due to inactivity. Feel free to reopen with a fresh rebase if you want to continue.'
days-before-issue-stale: 60
days-before-pr-stale: 30
days-before-issue-close: 14
days-before-pr-close: 14
stale-issue-label: 'stale'
stale-pr-label: 'stale'
exempt-issue-labels: 'pinned,security,roadmap'
exempt-pr-labels: 'pinned,security'
+24
View File
@@ -0,0 +1,24 @@
name: Tests
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: '24'
- uses: actions/setup-go@v6
with:
go-version: '1.26'
# Skip the postinstall (npx playwright install chromium); the tests
# never launch a browser, so the ~170 MB download is pure CI overhead.
- run: npm install --ignore-scripts
- run: node test-all.mjs --quick
- name: Go dashboard tests
run: go test ./...
working-directory: dashboard
+35
View File
@@ -0,0 +1,35 @@
name: Web CI
# CI for the experimental web UI (web/). Informative by design: it is NOT a
# required check for the core — a red here never blocks a core merge, and core
# changes never trigger it. The web/ tree versions and releases as its own
# release-please component.
on:
pull_request:
paths:
- 'web/**'
push:
branches: [main]
paths:
- 'web/**'
permissions:
contents: read
jobs:
web:
name: web typecheck + build
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
cache-dependency-path: web/package-lock.json
- run: npm ci
- run: npx tsc --noEmit
- run: npm run build
+33
View File
@@ -0,0 +1,33 @@
name: Welcome
on:
pull_request_target:
types: [opened]
issues:
types: [opened]
jobs:
welcome:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/first-interaction@v3
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
pr_message: |
Welcome to career-ops, @${{ github.actor }}! Thanks for your first PR.
A few things to know:
- Tests will run automatically — check the status below
- Make sure you've linked a related issue (required for features)
- Read [CONTRIBUTING.md](https://github.com/santifer/career-ops/blob/main/CONTRIBUTING.md) if you haven't
We'll review your PR soon. Join our [Discord](https://discord.gg/8pRpHETxa4) if you have questions.
issue_message: |
Thanks for opening your first issue, @${{ github.actor }}! We'll take a look soon.
In the meantime:
- Check [SUPPORT.md](https://github.com/santifer/career-ops/blob/main/SUPPORT.md) for setup help
- Join our [Discord](https://discord.gg/8pRpHETxa4) for quick answers