Skip to content

feat(agent): add tools field to onboarding-{company} for Anthropic-sh… #8

feat(agent): add tools field to onboarding-{company} for Anthropic-sh…

feat(agent): add tools field to onboarding-{company} for Anthropic-sh… #8

Workflow file for this run

name: validate
# Quality gate for venture-context repos (template + instances scaffolded via
# /aios:company --create). 5 jobs:
#
# 1. structure — required files + folders
# 2. frontmatter — YAML frontmatter valid on every .md
# 3. credentials — no secrets committed
# 4. wiki-links — all [[refs]] resolve within the bundle
# 5. personalization — no operator-personal leaks (other-venture refs,
# machine-local paths, real operator handles)
#
# This file ships in company-template so every new {company}-context repo
# created via /aios:company --create inherits the gate automatically.
# Template mode (this repo has {Company}/{...} placeholders) and instance
# mode (placeholders replaced) are auto-detected and handled per job.
#
# Security posture: NO use of ${{ github.event.* }} interpolation inside
# run: scripts. All shell commands read from the checked-out filesystem,
# never from event payloads, so command-injection risk is zero.
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
jobs:
structure:
name: Repo structure
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Required top-level files exist
run: |
set -e
for f in README.md CLAUDE.md LICENSE NOTICE; do
test -f "$f" || { echo "::error::Missing required file: $f"; exit 1; }
done
echo "OK: top-level files present (README, CLAUDE, LICENSE, NOTICE)"
- name: context/ exists with at least about_venture.md
run: |
set -e
test -d context || { echo "::error::Missing context/ folder — venture-context repos must hold context files here"; exit 1; }
test -f context/about_venture.md || { echo "::error::Missing context/about_venture.md — the load-bearing identity file"; exit 1; }
echo "OK: context/ present with about_venture.md"
frontmatter:
name: YAML frontmatter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: All context/*.md files have valid YAML frontmatter
run: |
python3 << 'PY'
import sys, re, os, yaml
from pathlib import Path
PATHS = ["context", "agents", "skills", "templates", "hooks", "mcps", "plugins"]
SKIP_FILES = {"CHANGELOG.md", "LICENSE", "NOTICE", "README.md"}
errors = []
for base in PATHS:
if not os.path.isdir(base):
continue
for md in Path(base).rglob("*.md"):
if md.name in SKIP_FILES:
continue
if "/evals/" in str(md):
continue
content = md.read_text(encoding="utf-8", errors="replace")
if not content.startswith("---\n"):
if str(md).startswith("context/"):
errors.append(f"{md}: context/ files must start with YAML frontmatter (---)")
continue
m = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL)
if not m:
errors.append(f"{md}: opens with --- but no closing --- found")
continue
try:
yaml.safe_load(m.group(1))
except yaml.YAMLError as e:
errors.append(f"{md}: invalid YAML in frontmatter — {e}")
if errors:
print("::error::Frontmatter validation failed:")
for e in errors:
print(f" - {e}")
sys.exit(1)
print("OK: all checked .md files have valid frontmatter")
PY
credentials:
name: Credentials guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: No common secret patterns committed
run: |
set -e
PATTERNS=(
'client_secret":[[:space:]]*"GOCSPX-[A-Za-z0-9_-]+'
'AKIA[0-9A-Z]{16}'
'xox[baprs]-[A-Za-z0-9-]{10,}'
'ghp_[A-Za-z0-9]{36}'
'sk_live_[A-Za-z0-9]{24,}'
'-----BEGIN [A-Z ]+PRIVATE KEY-----'
)
FOUND=0
for p in "${PATTERNS[@]}"; do
MATCHES=$(grep -rE "$p" . --include='*.md' --include='*.yml' --include='*.yaml' --include='*.json' --include='*.sh' --include='*.py' --exclude-dir='.git' --exclude-dir='.github' 2>/dev/null || true)
if [ -n "$MATCHES" ]; then
echo "::error::Possible secret leak — pattern: $p"
echo "$MATCHES"
FOUND=1
fi
done
test $FOUND -eq 0 || exit 1
echo "OK: no obvious secret patterns committed"
wiki_links:
name: Wiki-link validator (cross-vault leak guard)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: All wiki-links resolve within the bundle or AIOS allowlist
run: |
python3 << 'PY'
# Catches the cross-vault leak class: [[wiki-links]] that point at
# files not shipped in this repo. When an operator mounts this
# venture-context, only the FILES IN THIS REPO get distributed.
# Wiki-links pointing outside silently break.
#
# Resolution strategy: a wiki-link target resolves if it matches
# ANY of the following derived from files+folders in this repo:
# - file stem (filename without extension)
# - filename with extension
# - full relative path (with or without extension)
# - any path SUFFIX (trailing components) of a real file
# - folder name (last component)
# - any path SUFFIX of a folder
# OR the target is in KNOWN_AIOS_BUNDLED (resolves via The-AIOS/aios
# in any AIOS-mounted operator vault).
import re, sys, os
from pathlib import Path
KNOWN_AIOS_BUNDLED = {
"lawyer", "accountant", "sales-lead-hunter", "code-reviewer",
"compliance-checker", "writing", "onboarding-aios",
"sales-pdf-generator", "deck-builder", "code-review",
"CLAUDE.md", "README.md", "SETUP.md", "TOOLS.md", "CHEATSHEET.md",
"FORTRESS.md", "INTENT.md", "USER.md", "antifragile", "patterns",
"growth", "preferences", "business", "ecosystem", "session-insights",
"profile", "about_me", "personal_voice", "working_style",
"about_business", "role-expectations", "vault-routine", "INTENT",
"_index", "agent-template",
}
resolvable = set()
def add_path_suffixes(path_str):
"""Add a path and all its trailing suffixes to resolvable."""
parts = path_str.split("/")
for i in range(len(parts)):
suffix = "/".join(parts[i:])
resolvable.add(suffix)
resolvable.add(suffix.rstrip("/"))
# Walk all files (any extension)
for f in Path(".").rglob("*"):
if ".git" in f.parts or ".github" in f.parts:
continue
if f.is_file():
rel = str(f)
add_path_suffixes(rel) # full path + suffixes
if f.suffix == ".md":
add_path_suffixes(str(f.with_suffix(""))) # path without .md
resolvable.add(f.stem) # bare stem
# Also read frontmatter `aliases` field — declared aliases
# for the file are legitimate wiki-link targets.
try:
import yaml
text = f.read_text(encoding="utf-8", errors="replace")
m = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL)
if m:
fm = yaml.safe_load(m.group(1))
if fm and isinstance(fm, dict) and "aliases" in fm:
aliases = fm["aliases"]
if isinstance(aliases, str):
aliases = [aliases]
if isinstance(aliases, list):
for a in aliases:
resolvable.add(str(a))
except Exception:
pass # missing yaml module or bad frontmatter — non-fatal
else:
resolvable.add(f.name) # bare filename for non-md (LICENSE, etc.)
elif f.is_dir():
add_path_suffixes(str(f)) # folder + suffixes
errors = []
for md in Path(".").rglob("*.md"):
if ".git" in md.parts or ".github" in md.parts:
continue
text = md.read_text(encoding="utf-8", errors="replace")
# Strip inline code spans (backtick-enclosed) so we don't flag
# pedagogical refs like \`[[ref]]\` that demonstrate syntax, not links.
text_stripped = re.sub(r"`[^`\n]*`", "", text)
# Match [[target]] or [[target|display]] — also tolerate escaped pipe \|
for match in re.finditer(r"\[\[([^\]|\\]+?)(?:\\?\|[^\]]*)?\]\]", text_stripped):
target = match.group(1).strip()
# Strip anchor #section
target = target.split("#")[0]
# Strip leading ./ and trailing /
target = target.lstrip("./").rstrip("/")
if not target:
continue
if target in resolvable:
continue
if target in KNOWN_AIOS_BUNDLED:
continue
errors.append(f"{md}: [[{target}]] does not resolve within bundle or AIOS allowlist")
if errors:
print("::error::Cross-vault wiki-link leaks detected:")
for e in errors:
print(f" - {e}")
print()
print("Fix options:")
print(" 1. Convert to plain text (operator-personal targets won't resolve in other vaults)")
print(" 2. Move the target file into this repo if it should ship with the bundle")
print(" 3. Add the target to KNOWN_AIOS_BUNDLED in this workflow if it ships via The-AIOS/aios")
sys.exit(1)
print("OK: all wiki-links resolve")
PY
personalization:
name: Personalization guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: No operator-personal leaks (auto-detects template vs instance mode)
run: |
set -e
# Template mode auto-detect
if grep -qE '\{Company\}|\{[a-z]+\}' context/about_venture.md 2>/dev/null; then
echo "Detected: template mode. Skipping instance-mode leak checks."
exit 0
fi
FOUND=0
# 1. Machine-local file paths
if grep -rE 'file:///[A-Za-z]+/[A-Za-z]+|/Users/[a-z]+|/home/[a-z]+' context/ 2>/dev/null; then
echo "::error::Machine-local file paths in context/ — don't survive distribution"
FOUND=1
fi
# 1b. Operator-personal home subdirs (other than ~/aios/, the canonical
# framework path that resolves on all operator machines via symlink or
# real clone). ~/obsidian/ is chuy's symlink target; sarah uses ~/aios/
# directly. Other ~/ subdirs (~/cowork/, ~/Downloads/, ~/Desktop/) are
# operator-personal too. Shared context should use ~/aios/ or env vars
# like $HOME, not hardcoded subdirs.
if grep -rE '~/(obsidian|cowork|code/the-aios)/' context/ agents/ skills/ templates/ hooks/ mcps/ plugins/ 2>/dev/null; then
echo "::error::Operator-personal home subdir in shared context — use ~/aios/ (the canonical framework path) or note it as operator-specific. Symmetric with: don't ship /Users/{op}/ paths either."
FOUND=1
fi
# 2. Legacy plugin namespace
for d in context agents skills templates hooks mcps plugins; do
if [ -d "$d" ]; then
if grep -rn 'vault-commands:' "$d" 2>/dev/null; then
echo "::error::Legacy vault-commands:* plugin namespace — rename to aios:*"
FOUND=1
fi
fi
done
# 3. Placeholder leftovers
if grep -rE '\{Company\}|\{acme\}|\{your-org\}|\{YYYY-MM-DD\}' context/ 2>/dev/null; then
echo "::warning::Possible unreplaced template placeholder in context/"
fi
test $FOUND -eq 0 || exit 1
echo "OK: no operator-personal leaks detected"