From f43914b3de843ffd201fef9bec5b3881d332a8a1 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 15:36:03 +0200 Subject: [PATCH 01/17] ci: document workflow conventions Explains the SHA-pinning rule, least-privilege permissions discipline, harden-runner audit-mode defaults, where each workflow surfaces its results, and the fork-PR permission caveat. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/README.md | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/README.md diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..3eb5491 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,53 @@ +# GitHub Actions Workflows + +This directory contains the repository's CI workflows. All workflows +follow these conventions. + +## Pin every action to a full 40-char SHA + +Every `uses:` line pins the action to a commit SHA, with the human +readable version in a trailing comment: + +```yaml +- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.7 +``` + +This prevents a silent supply-chain swap if an upstream tag is moved. + +To bump an action, resolve the new SHA from its tag: + +```bash +gh api repos/OWNER/REPO/git/ref/tags/VERSION --jq '.object.sha' +``` + +Dependabot opens weekly PRs that do this automatically; manual bumps +use the same command. + +## Minimize `permissions:` + +Every workflow sets `permissions: {}` at the top level and re-grants +only what a job needs at the job or step scope. + +## First step is harden-runner (audit mode) + +Every job's first step is `step-security/harden-runner` in `audit` mode. +Logs surface at the Actions run page. After the egress baseline is +known, a follow-up PR flips audit → block. + +## Where results surface + +| Workflow | Results appear in | +|----------------|------------------------------------------------------------------------------| +| shellcheck | Checks tab (PR annotations) | +| lint-prose | Checks tab (yamllint / markdownlint / lychee annotations) | +| codeql | Security tab → Code scanning alerts | +| gitleaks | Checks tab; PR inline summary when `pull-requests: write` is available | +| scorecard | Security tab → Code scanning alerts, plus the public OpenSSF Scorecard report | + +## Fork-PR caveat + +PRs opened from a fork run with a read-only `GITHUB_TOKEN` and no +repository secrets. The workflows are designed to function under this +constraint: none use `pull_request_target`, and gitleaks degrades its +inline-comment behaviour to workflow annotations when it cannot post +PR comments. From 417b70d47f94d391bb4ee411d2ec411bebbbce20 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 15:40:47 +0200 Subject: [PATCH 02/17] fix(shell): remove dead pattern __* in case statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pattern `_*|__*` is redundant because `_*` already matches any string starting with an underscore, so `__*` is unreachable (SC2221, SC2222). No behavior change — scripts continue to skip any skill directory whose name starts with `_`. Surfaced by the new shellcheck workflow added in the same PR. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- bin/maya | 2 +- setup.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/maya b/bin/maya index 643bb55..b6dbd4c 100755 --- a/bin/maya +++ b/bin/maya @@ -49,7 +49,7 @@ list_skills() { # Skip internal dirs case "$name" in - _*|__*) continue ;; + _*) continue ;; esac # Check it's a real skill (has manifest.toml) diff --git a/setup.sh b/setup.sh index ddee200..08c364e 100755 --- a/setup.sh +++ b/setup.sh @@ -76,7 +76,7 @@ SKILL_COUNT=0 for d in "$MAYA_ROOT/skills"/*/; do [ -d "$d" ] || continue name="$(basename "$d")" - case "$name" in _*|__*) continue ;; esac + case "$name" in _*) continue ;; esac [ -f "$d/manifest.toml" ] || continue SKILL_COUNT=$((SKILL_COUNT + 1)) done From e52f3a32f2620c99122b331375a4b1a75ac3023b Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 15:40:52 +0200 Subject: [PATCH 03/17] ci: add shellcheck workflow for repo shell scripts Runs shellcheck at severity=warning on push to main and on PRs that touch .sh/.bash files or skills/. Pinned, least-privileged, and harden-runner-wrapped per the workflow conventions in .github/workflows/README.md. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/shellcheck.yml | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/shellcheck.yml diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000..e9f7161 --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,43 @@ +name: shellcheck + +on: + pull_request: + paths: + - '**/*.sh' + - '**/*.bash' + - 'skills/**' + - 'setup.sh' + - 'bin/**' + push: + branches: [main] + paths: + - '**/*.sh' + - '**/*.bash' + - 'skills/**' + - 'setup.sh' + - 'bin/**' + +permissions: {} + +concurrency: + group: shellcheck-${{ github.ref }} + cancel-in-progress: true + +jobs: + shellcheck: + name: shellcheck + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + with: + egress-policy: audit + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 + with: + severity: warning + scandir: '.' + ignore_paths: >- + .git + .worktrees From bee820a6bb244a44a209eabdb88085af9a4cb730 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 15:59:27 +0200 Subject: [PATCH 04/17] ci: add markdownlint config Disables line-length (prose), inline-HTML (badges), and first-line-must-be-heading (CHANGELOG) rules. Everything else stays at the markdownlint defaults. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .markdownlint.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .markdownlint.yaml diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..223b311 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,13 @@ +# markdownlint rules. See https://github.com/DavidAnson/markdownlint +# Prose-heavy docs — keep the ruleset minimal to avoid fighting content. +default: true + +# MD013 — Line length. Prose can run long; disable. +MD013: false + +# MD033 — Inline HTML. Allowed for badge markup and the occasional
. +MD033: false + +# MD041 — First line in file must be a top-level heading. CHANGELOG and +# some templates violate this intentionally; disable. +MD041: false From ceef971f64db7e8a25f272fbb105630a81826d93 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 15:59:44 +0200 Subject: [PATCH 05/17] ci: add .lycheeignore for localhost and template placeholders Suppresses link-checker false positives from localhost, example.* domains, and the issue-template new-issue URLs (which 404 until the maintainer enables them in repo settings). Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .lycheeignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .lycheeignore diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 0000000..b8ae12e --- /dev/null +++ b/.lycheeignore @@ -0,0 +1,8 @@ +# URLs lychee should skip. See https://github.com/lycheeverse/lychee +# Localhost and example domains never resolve in CI. +^https?://localhost +^https?://127\.0\.0\.1 +^https?://example\.(com|org|net) +# Issue-template URLs in .github/ISSUE_TEMPLATE produce 404 until the +# maintainer enables the corresponding template in repo settings. +^https?://github\.com/cvander/Maya/issues/new\?template= From 97939b9b9dd1cde7c49da728ece9c8790b66be72 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:04:31 +0200 Subject: [PATCH 06/17] ci: add prose-lint workflow (yamllint, markdownlint, lychee) Three parallel jobs triggered on PR and push-to-main for changes under *.md / *.yml / *.yaml. Lychee runs with fail=false (annotations only) for the first month to establish a clean baseline; a follow-up flips it to blocking. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/lint-prose.yml | 71 ++++++++++++++++++++++++++++++++ .yamllint.yaml | 26 ++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 .github/workflows/lint-prose.yml create mode 100644 .yamllint.yaml diff --git a/.github/workflows/lint-prose.yml b/.github/workflows/lint-prose.yml new file mode 100644 index 0000000..d3bb8bd --- /dev/null +++ b/.github/workflows/lint-prose.yml @@ -0,0 +1,71 @@ +name: lint-prose + +on: + pull_request: + paths: + - '**/*.md' + - '**/*.yml' + - '**/*.yaml' + push: + branches: [main] + paths: + - '**/*.md' + - '**/*.yml' + - '**/*.yaml' + +permissions: {} + +concurrency: + group: lint-prose-${{ github.ref }} + cancel-in-progress: true + +jobs: + yamllint: + name: yamllint + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + with: + egress-policy: audit + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: karancode/yamllint-github-action@fdef6bc189425ecc84cc4543b2674566c0827053 # v2.1.1 + with: + yamllint_file_or_dir: '.' + yamllint_config_filepath: '.yamllint.yaml' + yamllint_strict: true + yamllint_comment: false + + markdownlint: + name: markdownlint + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + with: + egress-policy: audit + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: DavidAnson/markdownlint-cli2-action@b4c9feab76d8025d1e83c653fa3990936df0e6c8 # v16.0.0 + with: + globs: '**/*.md' + + lychee: + name: lychee (link-check) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + with: + egress-policy: audit + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: lycheeverse/lychee-action@2b973e86fc7b1f6b36a93795fe2c9c6ae1118621 # v1.10.0 + with: + args: >- + --no-progress + --exclude-path .worktrees + --exclude-path .git + '**/*.md' + fail: false diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..638748e --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,26 @@ +# yamllint config. See https://yamllint.readthedocs.io/ +# Workflows use actionlint for deeper syntax checks; yamllint here is +# style-only across the repo. +extends: default + +rules: + line-length: + max: 200 + level: warning + truthy: + # allow yes/no/on/off in GitHub Actions syntax + allowed-values: ['true', 'false', 'yes', 'no', 'on', 'off'] + check-keys: false + comments: + min-spaces-from-content: 1 + # PR #1 files (dependabot.yml, CITATION.cff) and most repo YAML omit the + # `---` start marker; disable the rule so strict mode doesn't retroactively + # fail on them. Re-enable per-file if we adopt the marker project-wide. + document-start: disable + # Preamble comments in config.example.yaml use a loose indent style; + # comments-indentation is a pure style rule — disable to avoid churn. + comments-indentation: disable + +ignore: | + .worktrees/ + .github/workflows/ From 88b0dca04128d7b1dedcdf5d078e049cc640375d Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:22:51 +0200 Subject: [PATCH 07/17] ci: tune markdownlint (disable MD034, exclude data/music dirs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Disable MD034 (bare URLs): prose and research files legitimately contain bare URLs; mechanical <...> wrapping is not worth the churn. - Exclude docs/music/ and data/ from markdownlint globs: these hold content-as-data (artist contact lists, close-out records) that share the fixture pattern — data, not docs. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/lint-prose.yml | 8 +++++++- .markdownlint.yaml | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint-prose.yml b/.github/workflows/lint-prose.yml index d3bb8bd..c92c441 100644 --- a/.github/workflows/lint-prose.yml +++ b/.github/workflows/lint-prose.yml @@ -49,7 +49,13 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: DavidAnson/markdownlint-cli2-action@b4c9feab76d8025d1e83c653fa3990936df0e6c8 # v16.0.0 with: - globs: '**/*.md' + globs: | + **/*.md + !**/fixtures/** + !.worktrees/** + !docs/plans/** + !docs/music/** + !data/** lychee: name: lychee (link-check) diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 223b311..41a826f 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -11,3 +11,8 @@ MD033: false # MD041 — First line in file must be a top-level heading. CHANGELOG and # some templates violate this intentionally; disable. MD041: false + +# MD034 — No bare URLs. Prose and data files (CoC, research notes, +# artist contact lists) legitimately include bare URLs and emails. +# Disable rather than mechanically wrap every occurrence in <...>. +MD034: false From ed5a59395e9215b68e7b510b124c2decacaab154 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:22:59 +0200 Subject: [PATCH 08/17] fix(docs): add blank lines around headings/lists, language tags to code fences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses markdownlint findings (MD022, MD032, MD040, MD049) surfaced by the new lint-prose workflow: - MD022/MD032: add required blank lines before/after headings and lists across skills/, docs/, evals/, research/, and top-level docs. - MD040: tag untagged code fences as `text` (README, evals/README, docs/operations/closing — ASCII-art tree / form templates). - MD049: normalize emphasis markers to asterisks in AGENTS.md. Auto-fixed by `markdownlint-cli2 --fix`; code fences and emphasis were adjusted manually. Verified locally via `act` against the pinned markdownlint-cli2-action. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- AGENTS.md | 12 +++++++++++- CHANGELOG.md | 1 + README.md | 2 +- docs/calendar.md | 1 + docs/compliance/pest-log.md | 1 + docs/menu/current.md | 5 +++++ docs/operations/closing.md | 2 +- evals/README.md | 13 ++++++++++++- evals/ci_only/README.md | 5 +++++ research/sf-bar-operations-2026-04-11.md | 10 ++++++++++ skills/CONTRACT.md | 1 + skills/README.md | 9 +++++++++ skills/compliance_check/SKILL.md | 2 ++ skills/compliance_docs/SKILL.md | 2 ++ skills/eighty_six/SKILL.md | 2 ++ skills/inventory_check/SKILL.md | 2 ++ skills/vendor_contact/SKILL.md | 1 + skills/vendor_order/SKILL.md | 1 + skills/vendor_order_review/SKILL.md | 1 + 19 files changed, 69 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 44ed01f..7c446e1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,6 +30,7 @@ Read all three as one system. If they ever conflict, `SOUL.md` wins on character Maya's operational world is a physical bar in San Francisco. Everything she does runs through that lens. **The space:** + - A real neighborhood bar - not a concept, not a brand - Live music occasional, booked by Maya personally - Small but intentional wine list @@ -38,6 +39,7 @@ Maya's operational world is a physical bar in San Francisco. Everything she does - The room should feel like it has been there longer than the latest wave trying to improve the neighborhood **Systems she uses** *(fill in specifics as known)*: + - POS / register: open to modern solutions, evaluating what works - Scheduling: her own system, probably a shared calendar or text chain with staff - Inventory: she knows it; formal tracking TBD @@ -45,11 +47,13 @@ Maya's operational world is a physical bar in San Francisco. Everything she does - Tech: she tries things. She's from Silicon Valley. If it makes the bar better, she'll test it. **Staff:** + - Small, low-turnover crew - Maya trains them, pays them fairly, trusts them once earned - They've been here for years - they're not just employees **The regulars:** + - Same ~30 core faces most nights - Mixed - doctors, plumbers, tech workers, retirees - They are the bar's identity as much as she is @@ -73,6 +77,7 @@ This is the part that usually doesn't get written down: Maya handles most things herself. But she knows the line. **Handle directly:** + - Vendor calls, orders, complaints - Staff scheduling, conflicts, performance - Regular customer situations (within reason) @@ -81,12 +86,14 @@ Maya handles most things herself. But she knows the line. - Day-to-day financial decisions **Flag to the right person:** + - Lease negotiations, rent increases → her lawyer - Licensing issues, ABC violations → lawyer immediately - A regular in genuine crisis (not just venting) → she knows when to refer, not just listen - Anything involving the building ownership situation → handle carefully, with counsel **Never act unilaterally on:** + - Signing or agreeing to anything contractual - Public statements about the bar, the building, or neighborhood disputes - Sharing information about regulars outside the bar context @@ -115,6 +122,7 @@ Maya is from Silicon Valley. She's not afraid of technology. She tries things, t But she's not chasing trends. Every tool earns its place. If it adds friction, complexity, or takes the human out of the room, it goes. The bar is the filter, not the other way around. **The agent should:** + - Suggest tools and integrations when they solve a real problem - Help evaluate and test new tech honestly - Keep the setup simple enough to run on a Mac Mini behind the bar @@ -173,6 +181,7 @@ Maya wakes up fresh each session. These files are her continuity. - **Long-term:** `MEMORY.md` - the distilled version: key staff context, building situation, regulars worth remembering, decisions made **What's worth writing down:** + - Staff changes or conflicts - Building/lease developments - Vendor changes or issues @@ -181,6 +190,7 @@ Maya wakes up fresh each session. These files are her continuity. - Anything that would matter to know next session **What's not worth writing down:** + - Every transaction - Routine nights - Anything a regular shared in confidence that doesn't need to be carried forward @@ -205,4 +215,4 @@ Maya is direct in one-on-one conversation. In group contexts - if she's ever in --- -_This file evolves as the bar does. When something changes - the building situation, the staff, the way she runs the place - update it. It's operational, not decorative._ +*This file evolves as the bar does. When something changes - the building situation, the staff, the way she runs the place - update it. It's operational, not decorative.* diff --git a/CHANGELOG.md b/CHANGELOG.md index 43139a7..08f97b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this ## [Unreleased] ### Added + - Initial project structure: OpenClaw, Hermes, and Claude Code configurations - Skill catalog and templates (see [skills/README.md](skills/README.md)) - Domain documentation: menu, inventory, permits, seating, calendar diff --git a/README.md b/README.md index 7de8bd5..bc768fd 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ We care about bringing this technology to traditional businesses. Not to moderni ## Architecture -``` +```text Maya/ ├── config/ │ ├── openclaw/ # OpenClaw agent configuration diff --git a/docs/calendar.md b/docs/calendar.md index 5e95d66..a4914ad 100644 --- a/docs/calendar.md +++ b/docs/calendar.md @@ -45,6 +45,7 @@ Dates that affect the menu, staffing, or how the room runs. Not every holiday ma Add dates here when they earn a spot. Not every holiday needs a plan. If it doesn't change how the bar runs, it doesn't go on the calendar. Include: + - The date (exact or approximate) - What it affects (menu, staffing, crowd, all of the above) - What to watch for diff --git a/docs/compliance/pest-log.md b/docs/compliance/pest-log.md index 0e06998..0e3543d 100644 --- a/docs/compliance/pest-log.md +++ b/docs/compliance/pest-log.md @@ -16,6 +16,7 @@ Weekly visual inspection. SFDPH requires documentation of pest monitoring. ## Inspection Areas Check these every week: + - [ ] Behind bar (under speed rail, behind bottles) - [ ] Storage room / back stock - [ ] Under sinks (bar and bathroom) diff --git a/docs/menu/current.md b/docs/menu/current.md index 4172c67..adfaac3 100644 --- a/docs/menu/current.md +++ b/docs/menu/current.md @@ -9,6 +9,7 @@ The menu is not static. It changes based on what we buy out, what's in season, s ## Cocktails ### Old Fashioned + - 2 oz bourbon (Evan Williams BiB, house pour) - 1 demerara sugar cube - 2-3 dashes Angostura bitters @@ -20,6 +21,7 @@ No cherry. No soda. Keep it honest. **Price:** $14 ### Dry Martini + - 2.5 oz gin (Ford's, house pour) - 0.5 oz dry vermouth (Dolin) - Lemon twist or olive @@ -29,6 +31,7 @@ Stirred, not shaken. Cold glass. **Price:** $15 ### Manhattan + - 2 oz rye (Rittenhouse BiB) - 1 oz sweet vermouth (Carpano Antica) - 2 dashes Angostura bitters @@ -39,6 +42,7 @@ Stirred, served up. **Price:** $15 ### Margarita + - 2 oz tequila (Espolon Blanco) - 1 oz fresh lime juice - 0.75 oz simple syrup @@ -49,6 +53,7 @@ Fresh lime only. No sour mix, ever. **Price:** $14 ### Dark & Stormy + - 2 oz rum (Flor de Cana 4yr) - Fever-Tree ginger beer - Lime wedge diff --git a/docs/operations/closing.md b/docs/operations/closing.md index d4548c6..5c138b9 100644 --- a/docs/operations/closing.md +++ b/docs/operations/closing.md @@ -45,7 +45,7 @@ Last call is at 1:30 AM. Doors locked at 2:00 AM. This gets done before anyone l Write down anything the next shift needs to know: -``` +```text Date: ____-__-__ Bartender: ________________ diff --git a/evals/README.md b/evals/README.md index 9673cee..bef1335 100644 --- a/evals/README.md +++ b/evals/README.md @@ -4,7 +4,7 @@ Evaluation framework for Maya's 15 skills. Validates skill quality through deter ## Structure -``` +```text evals/ framework.py # Eval runner + scoring engine offline/ # Deterministic, no API key needed @@ -52,7 +52,9 @@ python evals/framework.py --all ## Offline Evals ### test_skill_md_validity + Validates every skill's SKILL.md: + - YAML frontmatter parses correctly - Required fields present: name, description, version, author, license, platforms - metadata.hermes section with tags and requires_tools/requires_toolsets @@ -60,13 +62,17 @@ Validates every skill's SKILL.md: - Procedure contains a `python -m skills.` command ### test_fixture_coverage + Validates fixture directory structure: + - Every skill has fixtures/ with happy_path/ containing expected.json - Every skill has at least one warn/edge case fixture directory - Minimum 2 fixture scenarios per skill ### test_finding_codes + Validates finding structure in expected.json files: + - Required keys: severity, code, subject, message - Severity values: info, warn, fail - Codes use UPPER_SNAKE_CASE format @@ -74,7 +80,9 @@ Validates finding structure in expected.json files: - Non-empty subject and message strings ### test_exit_code_mapping + Validates status/exit code consistency: + - happy_path status is "ok" (exit 0) - warn_case/low_stock status is "warn" (exit 1) - All Result fields present per CONTRACT.md @@ -85,6 +93,7 @@ Validates status/exit code consistency: ## CI-Only Evals YAML scenario specs with placeholder scorers. Each defines: + - `eval_spec.yaml`: scoring method, pass threshold, requirements - `scenarios/*.yaml`: input, expected output, scoring rubric - `scorer.py`: placeholder scoring function @@ -94,7 +103,9 @@ These will be implemented when Hermes integration is ready. ## Adding Evals ### Offline + Add a `test_*.py` file to `evals/offline/`. Use stdlib unittest. Discover skills dynamically via `SKILLS_DIR.glob("*/manifest.toml")`. ### CI-only + Add a new directory under `evals/ci_only/` with eval_spec.yaml, scenarios/, and scorer.py. diff --git a/evals/ci_only/README.md b/evals/ci_only/README.md index 997a94b..2f39d17 100644 --- a/evals/ci_only/README.md +++ b/evals/ci_only/README.md @@ -6,6 +6,7 @@ They are not run in offline mode. ## Structure Each eval directory contains: + - `eval_spec.yaml` - Evaluation definition (scoring method, threshold, requirements) - `scenarios/` - Individual test scenarios as YAML files - `scorer.py` - Placeholder scoring logic (to be implemented with Hermes integration) @@ -13,16 +14,20 @@ Each eval directory contains: ## Evals ### hermes_invocation + Tests that Hermes correctly routes natural language queries to the right Maya skill with the correct arguments. ### schedule_quality + Tests that schedule-draft produces reasonable schedules given staff and event inputs. ### vendor_completeness + Tests that vendor-order-review produces complete order lists from known inventory states. ### compliance_accuracy + Tests that compliance-check correctly identifies expiring certs and overdue items. ## Running diff --git a/research/sf-bar-operations-2026-04-11.md b/research/sf-bar-operations-2026-04-11.md index 82b8fcf..ab48bd0 100644 --- a/research/sf-bar-operations-2026-04-11.md +++ b/research/sf-bar-operations-2026-04-11.md @@ -11,6 +11,7 @@ San Francisco neighborhood bars face a unique combination of expensive/scarce AB ## SF Bar Operations Landscape ### ABC Licensing (California) + - **License types**: Type 42 (beer/wine on-premise), Type 47 (full liquor on-premise), Type 48 (bar, no food requirement) - **Cost**: Market price ~$110K for transferable licenses in SF; limited supply creates bidding wars - **Timeline**: 150-180 days for new applications (ABC + MVIP/CCU inspections) @@ -19,6 +20,7 @@ San Francisco neighborhood bars face a unique combination of expensive/scarce AB - **SF-specific**: Limited license count makes them scarce assets; transfers are slow even for previously-licensed locations ### Inventory Management (Industry Standard) + | Tool | Price | Key Features | Best For | |------|-------|-------------|----------| | Bar Patrol | $49-69/mo | Bluetooth scales, auto-reorders, POS export, recipe costing | Small independents | @@ -29,17 +31,20 @@ San Francisco neighborhood bars face a unique combination of expensive/scarce AB | Square for Retail | POS-bundled | Barcode scanning, low-stock alerts, local tax | Already on Square | ### Reorder Workflows + - Par levels set per item (min stock before reorder) - Apps calculate variance (expected vs actual usage) - Automated vendor order drafts from low-stock triggers - Demand forecasting tied to local events, tourist seasons, day-of-week patterns ### Compliance Deadlines (SF-Specific) + - **SFDPH (Health Dept)**: Weekly pest logs, monthly cooler temperature records, FIFO rotation, sanitation logs - **ABC**: Annual renewal, unannounced compliance audits, hours enforcement (no sales after 2 AM) - **Labor**: SF minimum wage $18.67/hr (2026), 21+ requirement for liquor service ### Staffing + - Minimum age 21 for serving liquor - SF's high cost of living makes retention difficult - Most small bars run lean (2-3 bartenders, owner-operator model) @@ -71,6 +76,7 @@ San Francisco neighborhood bars face a unique combination of expensive/scarce AB **Maya is a good template for Phase 1 of a bar management system, not a replacement for dedicated bar inventory apps.** For a bar owner who is also a developer (or has one as a friend), Maya's approach has genuine advantages: + - **$0/month** vs $49-69/month for Bar Patrol - **Full data ownership** -- your inventory data is in readable files, not locked in a SaaS - **Customizable** -- you can add columns, change thresholds, script whatever you want @@ -78,6 +84,7 @@ For a bar owner who is also a developer (or has one as a friend), Maya's approac - **Git history** -- every inventory change is tracked, diffable, revertible But for a typical SF bar owner who just wants inventory done: + - Bar Patrol or Backbar is the pragmatic choice - Mobile interface matters more than data ownership - $49/month is trivial vs the cost of one wasted keg @@ -98,16 +105,19 @@ Based on how SF bars actually operate: ## Sources ### Web (Perplexity) + - Bar Patrol, BevSpot, Wisk, Backbar, Partender, Square for Retail feature comparisons - CA ABC licensing requirements and process documentation - SFDPH health code requirements for food/beverage establishments ### X/Twitter + - @clintolsen on SF liquor license scarcity (Jan 2025, 551 likes) - @DDaarriius on ABC audit enforcement (Apr 2026) - General bar inventory management discussions ### YouTube + - [How To Get A California Liquor License](https://www.youtube.com/watch?v=OlHg7BIsSl0) - Permit Place - [ABC License Types Explained](https://www.youtube.com/watch?v=LzOB71rjbfw) - AAA Liquor License Consulting - [Top 4 Bar Inventory Apps 2026](https://www.youtube.com/watch?v=z1DeX1wTqy4) - Dave Allred/Bar Patrol diff --git a/skills/CONTRACT.md b/skills/CONTRACT.md index b6a13a1..a3e4617 100644 --- a/skills/CONTRACT.md +++ b/skills/CONTRACT.md @@ -115,6 +115,7 @@ required_environment_variables: [] # must match manifest.toml env_required Skills log via `_lib.log.event(name, **fields)`. Events are appended as JSONL to `logs/skills/.jsonl`. Rules: + - Event names are fixed dotted strings (e.g. `inventory_check.started`). No f-strings in names. - Fields are structured scalars only: counts, file names, exit codes, durations. - No free-text interpolation. No item names, brand names, or PII in log fields. diff --git a/skills/README.md b/skills/README.md index ef7e176..b008139 100644 --- a/skills/README.md +++ b/skills/README.md @@ -22,11 +22,13 @@ python -m skills.eighty_six --add "Pliny the Elder" --reason "Keg kicked" ## Available Skills ### Inventory + | Skill | Status | Description | |-------|--------|-------------| | `inventory-check` | implemented | Scan current stock levels, flag low items | ### Vendors + | Skill | Status | Description | |-------|--------|-------------| | `vendor-order-review` | implemented | Review upcoming order needs across all vendors | @@ -34,6 +36,7 @@ python -m skills.eighty_six --add "Pliny the Elder" --reason "Keg kicked" | `vendor-contact` | implemented | Generate email body or phone script for a vendor | ### Scheduling + | Skill | Status | Description | |-------|--------|-------------| | `schedule-view` | implemented | Show the current week's schedule | @@ -41,6 +44,7 @@ python -m skills.eighty_six --add "Pliny the Elder" --reason "Keg kicked" | `schedule-notify` | implemented | Generate per-staff notification messages | ### Close-Out + | Skill | Status | Description | |-------|--------|-------------| | `close-out` | implemented | End-of-night cash reconciliation + tips + waste | @@ -48,17 +52,20 @@ python -m skills.eighty_six --add "Pliny the Elder" --reason "Keg kicked" | `cost-analysis` | implemented | Pour cost, COGS, and margin analysis per drink | ### 86 List + | Skill | Status | Description | |-------|--------|-------------| | `eighty-six` | implemented | Manage items currently unavailable (add/remove/list) | ### Compliance + | Skill | Status | Description | |-------|--------|-------------| | `compliance-check` | implemented | Review upcoming compliance deadlines and cert expirations | | `compliance-docs` | implemented | Check completeness of compliance documentation | ### Music + | Skill | Status | Description | |-------|--------|-------------| | `music-book` | implemented | Generate booking outreach for musicians | @@ -69,12 +76,14 @@ python -m skills.eighty_six --add "Pliny the Elder" --reason "Keg kicked" Each skill is a Python package under `skills/`. Follow the contract in [CONTRACT.md](CONTRACT.md). Use `skills/_skeleton/` as your starting template. It includes: + - `__main__.py` with `extra_parser` pattern for custom CLI args - `main.py` with `run(ctx) -> Result` entry point - `test_main.py` with golden-output test pattern - `manifest.toml` and `SKILL.md` templates Keep skills: + - **Single-purpose** - one skill, one job - **CLI-friendly** - works from a terminal, returns clean output - **Fail-safe** - the bar runs without them; failures log, not crash diff --git a/skills/compliance_check/SKILL.md b/skills/compliance_check/SKILL.md index bff0415..b16f917 100644 --- a/skills/compliance_check/SKILL.md +++ b/skills/compliance_check/SKILL.md @@ -54,6 +54,7 @@ python -m skills.compliance_check --compliance-dir path/to/compliance --format j ``` Parse the JSON result: + - `status`: "ok" (no issues) or "warn" (findings present) - `findings`: array with codes CERT_EXPIRING, CERT_EXPIRED, LOG_OVERDUE, PERMIT_RENEWAL, INSPECTION_DUE - `data.certs_checked`: number of staff certs scanned @@ -73,6 +74,7 @@ Parse the JSON result: ## Verification After running, confirm: + - Exit code is 0 or 1 (not 2, 3, or 10) - stdout is valid JSON (when using --format json) - `data.certs_checked` >= 0 (files were actually read) diff --git a/skills/compliance_docs/SKILL.md b/skills/compliance_docs/SKILL.md index b3094ca..39e0083 100644 --- a/skills/compliance_docs/SKILL.md +++ b/skills/compliance_docs/SKILL.md @@ -45,6 +45,7 @@ python -m skills.compliance_docs --compliance-dir path/to/compliance --format js ``` Parse the JSON result: + - `status`: "ok" (all docs present and current) or "warn" (findings present) - `findings`: array with codes DOC_MISSING, DOC_EMPTY, DOC_STALE - `data.docs_checked`: number of docs audited @@ -64,6 +65,7 @@ Parse the JSON result: ## Verification After running, confirm: + - Exit code is 0 or 1 (not 2, 3, or 10) - stdout is valid JSON (when using --format json) - `data.docs_checked` > 0 (directory was actually read) diff --git a/skills/eighty_six/SKILL.md b/skills/eighty_six/SKILL.md index 5c29dbe..258c3f5 100644 --- a/skills/eighty_six/SKILL.md +++ b/skills/eighty_six/SKILL.md @@ -48,6 +48,7 @@ python -m skills.eighty_six --remove "Pliny the Elder" --format json ``` Parse the JSON result: + - `status`: "ok" (all clear or action completed) or "warn" (items on 86 list) - `findings`: array with CURRENTLY_86D, ITEM_86D, or ITEM_BACK codes - `data.items`: list of currently 86'd items (on --list) @@ -66,6 +67,7 @@ Parse the JSON result: ## Verification After running, confirm: + - Exit code is 0 or 1 (not 2, 3, or 10) - stdout is valid JSON (when using --format json) - For --add: item appears in subsequent --list diff --git a/skills/inventory_check/SKILL.md b/skills/inventory_check/SKILL.md index 06cef5f..82f9eaf 100644 --- a/skills/inventory_check/SKILL.md +++ b/skills/inventory_check/SKILL.md @@ -45,6 +45,7 @@ python -m skills.inventory_check --inventory-dir path/to/inventory --format json ``` Parse the JSON result: + - `status`: "ok" (nothing to reorder) or "warn" (items need attention) - `findings`: array of LOW_STOCK items with category, item name, qty, reorder_at - `data.items_scanned`: total items checked @@ -63,6 +64,7 @@ Parse the JSON result: ## Verification After running, confirm: + - Exit code is 0 or 1 (not 2, 3, or 10) - stdout is valid JSON (when using --format json) - `data.items_scanned` > 0 (files were actually read) diff --git a/skills/vendor_contact/SKILL.md b/skills/vendor_contact/SKILL.md index 612405a..eaaab7a 100644 --- a/skills/vendor_contact/SKILL.md +++ b/skills/vendor_contact/SKILL.md @@ -44,6 +44,7 @@ python -m skills.vendor_contact --vendor "Anchor Distributing" --method phone -- ``` Parse the JSON result: + - `status`: "ok" (vendor not found) or "warn" (draft generated) - `data.message`: the email body or phone script text - `data.contact_info`: vendor contact details (rep, phone, email) diff --git a/skills/vendor_order/SKILL.md b/skills/vendor_order/SKILL.md index 35088f8..162dc3a 100644 --- a/skills/vendor_order/SKILL.md +++ b/skills/vendor_order/SKILL.md @@ -41,6 +41,7 @@ python -m skills.vendor_order --vendor "Southern Glazer's" --input-file /tmp/rev ``` Parse the JSON result: + - `status`: "ok" (no orders for vendor) or "warn" (order draft generated) - `data.order_text`: plain text order ready to copy-paste - `data.vendor`: matched vendor name diff --git a/skills/vendor_order_review/SKILL.md b/skills/vendor_order_review/SKILL.md index 2b5ecaa..db87bc2 100644 --- a/skills/vendor_order_review/SKILL.md +++ b/skills/vendor_order_review/SKILL.md @@ -41,6 +41,7 @@ python -m skills.vendor_order_review --input-file /tmp/inventory.json --format j ``` Parse the JSON result: + - `status`: "ok" (no orders needed) or "warn" (orders to place) - `findings`: array of ORDER_NEEDED items with vendor, item, and quantity - `data.orders_by_vendor`: orders grouped by vendor name From 6602c6a70e641c8790ab3d2c944f34ecbc15ca7e Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:23:32 +0200 Subject: [PATCH 09/17] ci: add CodeQL workflow pre-wired for python and javascript-typescript Matrix covers the two languages skill code is most likely to arrive in. Today both legs find zero source files and exit quickly. When the first .py or .ts file lands, analysis activates automatically without a CI change. Scheduled weekly on Monday 06:00 UTC. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/codeql.yml | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..b474c86 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,58 @@ +name: codeql + +on: + pull_request: + branches: [main] + push: + branches: [main] + schedule: + # Weekly Monday 06:00 UTC — independent of scorecard's Tuesday slot. + - cron: '0 6 * * 1' + +permissions: {} + +concurrency: + group: codeql-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + # Pre-wired for the languages skill code is most likely to arrive + # in. Today both legs find zero source files and exit quickly; + # the first .py / .ts PR activates real analysis automatically. + language: [python, javascript-typescript] + # Python became a first-class language in upstream PR #5 (2026-04-21): + # maya/, skills/*/main.py, evals/, tests/. The javascript-typescript + # leg finds zero sources today and exits quickly; it's kept to + # auto-activate when the first TS skill or UI lands. + steps: + - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + with: + egress-policy: audit + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Initialize CodeQL + uses: github/codeql-action/init@51313611dfbc5bd21517390ab8e8517fd8363fb1 # v3.25.15 + with: + languages: ${{ matrix.language }} + paths-ignore: | + docs/** + **/*.md + .worktrees/** + **/fixtures/** + evals/**/scenarios/** + skills/_skeleton/** + - name: Autobuild + uses: github/codeql-action/autobuild@51313611dfbc5bd21517390ab8e8517fd8363fb1 # v3.25.15 + - name: Analyze + uses: github/codeql-action/analyze@51313611dfbc5bd21517390ab8e8517fd8363fb1 # v3.25.15 + with: + category: "/language:${{ matrix.language }}" From 45daaa98e089537063627494538ef2c7c20f5470 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:30:57 +0200 Subject: [PATCH 10/17] ci: add .gitleaks.toml with upstream defaults + generic PII rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the upstream gitleaks default rule set (cloud tokens, generic-api-key, slack-webhook, etc.) with generic PII patterns: email addresses, US and MX phone numbers, and credit-card-shaped numerics. An allowlist suppresses obvious false positives in example files, docs, issue templates, and colocated Python unit tests (maya/**/test_*.py) that hold synthetic PII as test data. Bar-specific PII patterns can be supplied via the GITLEAKS_PRIVATE_CONFIG repository secret; see the gitleaks workflow for the overlay mechanism. Note on regex syntax: gitleaks uses Go's RE2-based stdlib engine, which does not support lookarounds or backreferences — rules use word boundaries and explicit separators instead. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .gitleaks.toml | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .gitleaks.toml diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..d3e607a --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,84 @@ +# Gitleaks rules for cvander/Maya. +# Layers: upstream default rules + a small set of generic PII patterns. +# A private overlay can be supplied at CI time via the +# GITLEAKS_PRIVATE_CONFIG repository secret; see +# .github/workflows/gitleaks.yml for the mechanism. +# +# Regex constraint: gitleaks uses Go's RE2-based stdlib engine, which +# does NOT support lookarounds (? Date: Thu, 23 Apr 2026 16:31:21 +0200 Subject: [PATCH 11/17] ci: add gitleaks workflow with optional private-overlay hook Runs gitleaks on every PR and push-to-main using the .gitleaks.toml rule set. If the GITLEAKS_PRIVATE_CONFIG repository secret is set, its contents are appended to the config at CI time (runner-local, never committed) so bar-specific PII rules can be maintained outside the public repo. GITLEAKS_LICENSE is read if present (required only under GitHub Organization accounts). Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/gitleaks.yml | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/gitleaks.yml diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 0000000..59fd392 --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,58 @@ +name: gitleaks + +on: + pull_request: + push: + branches: [main] + +permissions: {} + +concurrency: + group: gitleaks-${{ github.ref }} + cancel-in-progress: true + +jobs: + gitleaks: + name: gitleaks + runs-on: ubuntu-latest + permissions: + contents: read + # pull-requests: write is granted only for same-repo PRs; + # fork PRs automatically degrade to read-only and gitleaks + # falls back to workflow annotations. + pull-requests: write + steps: + - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + with: + egress-policy: audit + + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + fetch-depth: 0 + + # Private-overlay hook. If the GITLEAKS_PRIVATE_CONFIG secret is + # set, write it to a runner-local file and append its contents + # onto .gitleaks.toml; gitleaks merges [[rules]] tables across + # the single resulting config file. No-op when the secret is + # unset. The appended file never leaves the runner. + - name: Merge private gitleaks overlay (if provided) + env: + PRIVATE_CONFIG: ${{ secrets.GITLEAKS_PRIVATE_CONFIG }} + run: | + set -euo pipefail + if [ -n "${PRIVATE_CONFIG:-}" ]; then + printf '%s\n' "$PRIVATE_CONFIG" > .gitleaks-private.toml + # Append private rules; gitleaks deduplicates by rule id. + printf '\n# --- Private overlay (appended at CI time) ---\n' >> .gitleaks.toml + cat .gitleaks-private.toml >> .gitleaks.toml + echo "Merged private overlay ($(wc -l < .gitleaks-private.toml) lines)." + else + echo "No private overlay configured." + fi + + - uses: gitleaks/gitleaks-action@83373cf2f8c4db6e24b41c1a9b086bb9619e9cd3 # v2.3.7 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Free for personal accounts; license only needed under orgs. + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} + GITLEAKS_CONFIG: .gitleaks.toml From 1dcc77aa434b18179f5c24046577eccf015ab6a1 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:31:43 +0200 Subject: [PATCH 12/17] docs: document GITLEAKS_PRIVATE_CONFIG format in maintainer setup Expands the optional-secrets section with the expected TOML shape of the private overlay, so the maintainer can add bar-specific PII rules without touching the public repo. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- docs/MAINTAINER-SETUP.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/MAINTAINER-SETUP.md b/docs/MAINTAINER-SETUP.md index 45c4d63..b72adf5 100644 --- a/docs/MAINTAINER-SETUP.md +++ b/docs/MAINTAINER-SETUP.md @@ -82,10 +82,24 @@ exist: a GitHub Organization account (gitleaks-action is free for personal accounts). Leaving it unset is correct for the current personal-account setup. -- **`GITLEAKS_PRIVATE_CONFIG`** — optional overlay of bar-specific - gitleaks rules (regulars' names, local PII patterns, etc.) that must - not be committed to the public repo. See the `.github/workflows/gitleaks.yml` - comment block for format. Leaving it unset is a safe default. +- **`GITLEAKS_PRIVATE_CONFIG`** — optional overlay of gitleaks rules + that must stay private (regulars' names, local PII patterns, etc.). + + **Format:** the secret value is pasted verbatim into a file and + appended onto `.gitleaks.toml` at CI time. Supply one or more + additional rule tables in the standard gitleaks TOML syntax: + + ```toml + [[rules]] + id = "private-regular-alias" + description = "Known nickname for a specific regular; redact on commit" + regex = '''(?i)\b(alias-one|alias-two)\b''' + tags = ["pii", "private"] + ``` + + **Set via:** Settings → Secrets and variables → Actions → New + repository secret. Leaving it unset is a safe default — the overlay + step is a no-op when the secret is empty. ## 5. Once everything above is done From de04666dd94dd4378fdf5b40976eeac6bbbe0004 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:36:23 +0200 Subject: [PATCH 13/17] ci: add OpenSSF Scorecard workflow Runs weekly on Tuesday 06:00 UTC plus push-to-main and branch_protection_rule changes. Publishes the public SARIF-backed report to api.securityscorecards.dev and uploads findings to the GitHub Security tab. id-token: write is the minimum required to sign results for public attestation. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/scorecard.yml | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/scorecard.yml diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..72d27cd --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,45 @@ +name: scorecard + +on: + branch_protection_rule: + push: + branches: [main] + schedule: + # Weekly Tuesday 06:00 UTC — independent of CodeQL's Monday slot. + - cron: '0 6 * * 2' + +permissions: {} + +concurrency: + group: scorecard-${{ github.ref }} + cancel-in-progress: true + +jobs: + analysis: + name: scorecard analysis + runs-on: ubuntu-latest + permissions: + security-events: write + id-token: write + contents: read + actions: read + steps: + - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + with: + egress-policy: audit + + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + persist-credentials: false + + - name: Run scorecard analysis + uses: ossf/scorecard-action@ff5dd8929f96a8a4dc67d13f32b8c75057829621 # v2.4.0 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: Upload SARIF to code scanning + uses: github/codeql-action/upload-sarif@51313611dfbc5bd21517390ab8e8517fd8363fb1 # v3.25.15 + with: + sarif_file: results.sarif From 41835a37f55814fb568a45f0565909d4e4047eb1 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:49:08 +0200 Subject: [PATCH 14/17] fix(ci): repin codeql-action and scorecard-action to commit SHAs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier resolution used `gh api .../git/ref/tags/ --jq '.object.sha'`, which for annotated tags returns the tag-object SHA rather than the underlying commit SHA. github/codeql-action and ossf/scorecard-action both ship annotated tags, so both were mis-pinned. Replaces: - github/codeql-action@51313611... (tag object) → afb54ba3... (commit), across 3 uses in codeql.yml and 1 use in scorecard.yml - ossf/scorecard-action@ff5dd892... (tag object) → 62b2cac7... (commit) in scorecard.yml Resolved via `gh api repos///commits/ --jq '.sha'`, which always returns the commit SHA. The other 7 pinned actions use lightweight tags and were unaffected. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecard.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b474c86..227c938 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,7 +40,7 @@ jobs: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Initialize CodeQL - uses: github/codeql-action/init@51313611dfbc5bd21517390ab8e8517fd8363fb1 # v3.25.15 + uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: languages: ${{ matrix.language }} paths-ignore: | @@ -51,8 +51,8 @@ jobs: evals/**/scenarios/** skills/_skeleton/** - name: Autobuild - uses: github/codeql-action/autobuild@51313611dfbc5bd21517390ab8e8517fd8363fb1 # v3.25.15 + uses: github/codeql-action/autobuild@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 - name: Analyze - uses: github/codeql-action/analyze@51313611dfbc5bd21517390ab8e8517fd8363fb1 # v3.25.15 + uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 72d27cd..92ca1cd 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -33,13 +33,13 @@ jobs: persist-credentials: false - name: Run scorecard analysis - uses: ossf/scorecard-action@ff5dd8929f96a8a4dc67d13f32b8c75057829621 # v2.4.0 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif publish_results: true - name: Upload SARIF to code scanning - uses: github/codeql-action/upload-sarif@51313611dfbc5bd21517390ab8e8517fd8363fb1 # v3.25.15 + uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: sarif_file: results.sarif From ef1d3d358fe2ecf299b949cd2c42f0d31eb19589 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:49:15 +0200 Subject: [PATCH 15/17] fix(ci): tighten gitleaks test-file allowlist to known subtrees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous patterns `(^|/)test_[^/]+\.py$` and `_test\.py$` allowlisted ANY Python test-named file anywhere in the repo, meaning a real secret committed under a file named test_foo.py at the repo root or in an unknown path would be silently suppressed. Restricts the allowlist to recognized source subtrees only — maya/, skills/, evals/, tests/ — so a misnamed file in a new subtree is still scanned by default. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .gitleaks.toml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitleaks.toml b/.gitleaks.toml index d3e607a..7febef9 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -63,10 +63,11 @@ paths = [ '''^tests/.*/fixtures/.*''', # Python unit tests colocated with modules (e.g. maya/onboard/test_*.py) # hold inline synthetic PII as test data. Skill fixtures cover the - # skills/ case; this pattern covers tests under maya/ and anywhere - # else the project follows the colocated-test convention. - '''(^|/)test_[^/]+\.py$''', - '''_test\.py$''', + # skills/ case; these patterns cover tests under recognized source + # subtrees only — a file named test_x.py at the repo root or in an + # unknown path is NOT allowlisted and must still pass scanning. + '''^(maya|skills|evals|tests)/.*/test_[^/]+\.py$''', + '''^(maya|skills|evals|tests)/.*_test\.py$''', # The vendors docs file uses 555-prefix fake phones as placeholders # by convention — real vendor data is never committed. '''^docs/vendors/.*''', From f8b85cccf5f3f9d9430117de449264fe696964a4 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:49:21 +0200 Subject: [PATCH 16/17] ci: widen lint path filters to trigger on config and workflow edits Without these additions, a PR that only modifies a linter config (.lycheeignore, .markdownlint.yaml, .yamllint.yaml) or the workflow file itself would not re-run the corresponding linter. Adds the config paths and each workflow's own path to its trigger list. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/lint-prose.yml | 8 ++++++++ .github/workflows/shellcheck.yml | 2 ++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/lint-prose.yml b/.github/workflows/lint-prose.yml index c92c441..2e93418 100644 --- a/.github/workflows/lint-prose.yml +++ b/.github/workflows/lint-prose.yml @@ -6,12 +6,20 @@ on: - '**/*.md' - '**/*.yml' - '**/*.yaml' + - '.lycheeignore' + - '.markdownlint.yaml' + - '.yamllint.yaml' + - '.github/workflows/lint-prose.yml' push: branches: [main] paths: - '**/*.md' - '**/*.yml' - '**/*.yaml' + - '.lycheeignore' + - '.markdownlint.yaml' + - '.yamllint.yaml' + - '.github/workflows/lint-prose.yml' permissions: {} diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index e9f7161..470bd34 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -8,6 +8,7 @@ on: - 'skills/**' - 'setup.sh' - 'bin/**' + - '.github/workflows/shellcheck.yml' push: branches: [main] paths: @@ -16,6 +17,7 @@ on: - 'skills/**' - 'setup.sh' - 'bin/**' + - '.github/workflows/shellcheck.yml' permissions: {} From 447c15837e42b14cdbc0c5e6576742432bbc6079 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Arango Gutierrez Date: Thu, 23 Apr 2026 16:49:28 +0200 Subject: [PATCH 17/17] ci: write gitleaks private overlay to $RUNNER_TEMP, not workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous implementation left .gitleaks-private.toml in the runner workspace after the merge step. Any subsequent artifact upload — in this workflow or a later one — could have leaked the overlay contents. Writes the overlay to mktemp (which GitHub-hosted runners place under $RUNNER_TEMP, auto-wiped on job teardown) and adds a trap to clean up on any exit path, including early failures. The public .gitleaks.toml still gets the appended rules so gitleaks can read a single merged config. Refs: #7 Signed-off-by: Carlos Eduardo Arango Gutierrez --- .github/workflows/gitleaks.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml index 59fd392..7d55999 100644 --- a/.github/workflows/gitleaks.yml +++ b/.github/workflows/gitleaks.yml @@ -41,11 +41,16 @@ jobs: run: | set -euo pipefail if [ -n "${PRIVATE_CONFIG:-}" ]; then - printf '%s\n' "$PRIVATE_CONFIG" > .gitleaks-private.toml - # Append private rules; gitleaks deduplicates by rule id. + # Write overlay to $RUNNER_TEMP (auto-wiped, never in workspace + # so it cannot leak via artifact upload). trap cleans up on any + # exit path. gitleaks deduplicates rules by id, so duplicated + # ids in the overlay just refine the public config. + TMP_OVERLAY="$(mktemp)" + trap 'rm -f "$TMP_OVERLAY"' EXIT + printf '%s\n' "$PRIVATE_CONFIG" > "$TMP_OVERLAY" printf '\n# --- Private overlay (appended at CI time) ---\n' >> .gitleaks.toml - cat .gitleaks-private.toml >> .gitleaks.toml - echo "Merged private overlay ($(wc -l < .gitleaks-private.toml) lines)." + cat "$TMP_OVERLAY" >> .gitleaks.toml + echo "Merged private overlay ($(wc -l < "$TMP_OVERLAY") lines)." else echo "No private overlay configured." fi