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. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..227c938 --- /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@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + with: + languages: ${{ matrix.language }} + paths-ignore: | + docs/** + **/*.md + .worktrees/** + **/fixtures/** + evals/**/scenarios/** + skills/_skeleton/** + - name: Autobuild + uses: github/codeql-action/autobuild@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + - name: Analyze + uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 0000000..7d55999 --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,63 @@ +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 + # 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 "$TMP_OVERLAY" >> .gitleaks.toml + echo "Merged private overlay ($(wc -l < "$TMP_OVERLAY") 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 diff --git a/.github/workflows/lint-prose.yml b/.github/workflows/lint-prose.yml new file mode 100644 index 0000000..2e93418 --- /dev/null +++ b/.github/workflows/lint-prose.yml @@ -0,0 +1,85 @@ +name: lint-prose + +on: + pull_request: + paths: + - '**/*.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: {} + +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 + !**/fixtures/** + !.worktrees/** + !docs/plans/** + !docs/music/** + !data/** + + 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/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..92ca1cd --- /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@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@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + with: + sarif_file: results.sarif diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000..470bd34 --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,45 @@ +name: shellcheck + +on: + pull_request: + paths: + - '**/*.sh' + - '**/*.bash' + - 'skills/**' + - 'setup.sh' + - 'bin/**' + - '.github/workflows/shellcheck.yml' + push: + branches: [main] + paths: + - '**/*.sh' + - '**/*.bash' + - 'skills/**' + - 'setup.sh' + - 'bin/**' + - '.github/workflows/shellcheck.yml' + +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 diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..7febef9 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,85 @@ +# 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 (?. +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 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/ 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/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/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 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/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 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