chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
github: santifer
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.`);
|
||||
@@ -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 }}'
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user