SLOPSHOPPER

cure-policy-guard

Refuses file writes that break a written Cure house rule (no cron in workflows, Vertex-only Gemini in Level5, no new AI vendors in minors' repos, no hard-coded…

newguardcommandprocess
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · cure-policy-guard
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /policy-guard ⎿ cure-policy-guard: cure-policy-guard rules: no-cron · level5-vertex-only · minors-new-ai-vendor · hardcoded-secret ⎿ cure-policy-guard: No overrides recorded. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

Product Engineering Skills

The complete skill library that Cure Consulting Group uses to build apps, platforms, and products. These skills encode our standards, frameworks, and processes — so every project ships with the same level of rigor.

Now available as a Claude Code Plugin — install once, get auto-updates across all projects.

How It's Organized

ProductEngineeringSkills/
├── .claude-plugin/           # Plugin manifest
│   └── plugin.json
├── skills/{domain}/          # 80 skills, organized by domain (engineering/platform/product/business/marketing/security/legal)
│   ├── sdlc/
│   ├── android-feature-scaffold/
│   ├── incident-response/     # NEW
│   ├── accessibility-audit/   # NEW
│   ├── performance-review/    # NEW
│   ├── database-architect/    # NEW
│   ├── infrastructure-scaffold/ # NEW
│   ├── project-bootstrap/
│   ├── e2e-testing/
│   ├── test-accounts/
│   ├── uat/
│   ├── compliance-architect/
│   ├── data-migration/
│   ├── feature-flags/
│   ├── release-management/
│   ├── observability/
│   ├── client-handoff/
│   ├── llmops/
│   ├── disaster-recovery/
│   ├── dora-metrics/
│   ├── design-system/
│   ├── client-communication/
│   ├── i18n/
│   ├── notification-architect/
│   ├── offline-first/
│   ├── chaos-engineering/
│   ├── edge-computing/
│   ├── finops/
│   ├── micro-frontends/
│   ├── growth-engineering/
│   ├── green-software/
│   ├── proposal-generator/
│   ├── api-gateway/
│   ├── ... (75 total — see docs/OVERVIEW.md for full inventory)
│   └── legal-doc-scaffold/
├── agents/                   # 35 custom subagent definitions
├── personas/                 # 4 cross-domain engagement archetypes
│   ├── code-reviewer.md      # Security + quality review agent
│   ├── project-bootstrapper.md  # New project setup agent
│   ├── test-runner.md        # Execute test suites, report coverage
│   ├── pr-reviewer.md        # Automated PR diff review
│   ├── refactor-assistant.md # Safe refactoring with test validation
│   ├── ci-debugger.md        # Diagnose failed CI/CD runs
│   ├── release-coordinator.md # Version bump, changelog, deploy validation
│   ├── doc-generator.md      # API docs, ADRs, changelogs from code
│   ├── codebase-explainer.md # Onboarding — explain architecture, trace flows
│   ├── migration-validator.md # Database migration safety checks
│   ├── deployment-validator.md # Pre-deployment checklist validation
│   ├── dependency-auditor.md # Vulnerability and outdated package audit
│   ├── api-validator.md      # OpenAPI spec and contract validation
│   ├── product-analyst.md    # Feature adoption, analytics instrumentation
│   ├── ux-researcher.md      # Usability analysis, friction mapping
│   ├── roadmap-strategist.md # RICE scoring, dependency mapping, roadmaps
│   ├── competitive-intel.md  # Feature matrices, positioning, moat analysis
│   ├── content-strategist.md # Editorial calendars, SEO, content briefs
│   ├── campaign-analyst.md   # Attribution, funnel analysis, channel ROI
│   ├── brand-guardian.md     # Voice/tone, visual identity, microcopy audit
│   ├── growth-analyst.md     # Activation, retention, viral mechanics
│   ├── financial-analyst.md  # Revenue forecasts, unit economics, scenarios
│   ├── market-intelligence.md # TAM/SAM/SOM, trends, market timing
│   ├── investor-relations.md # Board updates, KPIs, fundraising narratives
│   ├── contract-reviewer.md  # SOW/contract risk, terms, IP review
│   ├── data-analyst.md       # Schema exploration, queries, data quality
│   ├── metrics-dashboard.md  # KPI definitions, SLOs, dashboard wireframes
│   ├── ab-test-analyst.md    # Experiment design, statistical analysis
│   ├── qa-engineer.md         # Test planning, edge cases, regression, quality gates
│   ├── accessibility-checker.md # WCAG 2.2 automated compliance
│   └── firebase-security-auditor.md # Firestore rules and Functions audit
├── hooks/                    # Multi-layer automated enforcement
│   └── hooks.json            # Command + Prompt hooks (9 event types)
├── rules/                    # 11 path-specific coding standards
│   ├── android.md             # Loads for *.kt files
│   ├── ios.md                 # Loads for *.swift files
│   ├── web.md                 # Loads for *.ts/*.tsx files
│   ├── firebase.md            # Loads for functions/**
│   ├── python.md              # Loads for *.py files
│   ├── go.md                  # Loads for *.go files
│   ├── rust.md                # Loads for *.rs files
│   ├── sql.md                 # Loads for *.sql, migrations/**
│   ├── docker.md              # Loads for Dockerfile, *.dockerfile
│   ├── terraform.md           # Loads for *.tf, *.tfvars
│   └── cicd.md                # Loads for .github/workflows/**
├── output-styles/            # 9 custom output formatting styles
│   ├── prd/                   # Product docs (PRDs, GTM, research)
│   ├── code-generation/       # Code scaffolds and implementations
│   ├── financial-analysis/    # Cost models, SaaS metrics
│   ├── audit-report/          # Audits, reviews, compliance
│   ├── api-specification/     # OpenAPI specs, endpoint docs
│   ├── architecture-decision/ # ADRs, RFCs, trade-off matrices
│   ├── runbook/               # Incident runbooks, DR procedures
│   ├── test-plan/             # Test plans, coverage reports
│   └── monitoring-alert/      # Alert definitions, thresholds
├── .mcp.json                 # MCP server configs (GitHub, Sentry, Firestore, PostgreSQL)
├── .lsp.json                 # LSP server configs (TypeScript, Python/Pyright)
├── marketplace.json          # Plugin marketplace manifest
├── settings.json             # Default permission rules
├── claude-commands/           # Legacy format (backwards compat, 64 files)
├── gemini skills/             # Google Gemini skills (.skill ZIP)
├── CLAUDE.md                  # Project instructions (Claude)
├── GEMINI.md                  # Project instructions (Gemini CLI)
├── AGENT-GUIDE.md             # How to structure prompts for agents & skills
├── setup.sh                  # Setup script for Antigravity & other projects
└── README.md

Installation

Via GitHub Package (Recommended)

Install the plugin as an npm package from GitHub Packages. This is the easiest way to keep all your projects up to date.

1. Authenticate with GitHub Packages (one-time setup):

# Create a Personal Access Token (PAT) with read:packages scope at
# https://github.com/settings/tokens, then:
npm login --scope=@cure-consulting-group --registry=https://npm.pkg.github.com

Or add to your project's .npmrc:

@cure-consulting-group:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

2. Install in your project:

npm install @cure-consulting-group/product-engineering-skills

The postinstall script automatically:

  • Symlinks the package to ~/.claude/plugins/ProductEngineeringSkills
  • Registers the plugin in ~/.claude/settings.json

All 80 skills, 39 agents, 4 personas, hooks, rules, and output styles are immediately available.

3. Enable auto-updates with Dependabot (recommended):

Add .github/dependabot.yml to your project (or run setup.sh which does this automatically):

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    allow:
      - dependency-name: "@cure-consulting-group/product-engineering-skills"
    labels:
      - "dependencies"
      - "skills-update"
    commit-message:
      prefix: "chore"
      include: "scope"

Dependabot will open a PR in your project whenever a new version is published. Merge it and every agent on that project gets the updated skills.

4. Manual update:

npm update @cure-consulting-group/product-engineering-skills

As a Claude Code Plugin (Manual)

# Load the plugin during a session
claude --plugin-dir /path/to/ProductEngineeringSkills

# Or for development/testing
claude --plugin-dir ./ProductEngineeringSkills

Once loaded, all skills are available as namespaced commands:

/cure-product-engineering:sdlc
/cure-product-engineering:feature-audit
/cure-product-engineering:android-feature-scaffold
/cure-product-engineering:incident-response
/cure-product-engineering:accessibility-audit

Hooks, agents, rules, output styles, and MCP servers are all included automatically.

Setup Script (Antigravity & Other Projects)

The fastest way to onboard any project:

# From the target project directory
/path/to/ProductEngineeringSkills/setup.sh

# Or specify the project path
/path/to/ProductEngineeringSkills/setup.sh /path/to/antigravity-app

# Install globally for ALL projects
/path/to/ProductEngineeringSkills/setup.sh --global

# Legacy mode (just copy skills, no hooks/agents)
/path/to/ProductEngineeringSkills/setup.sh --legacy

The setup script will:

  1. Clone/update the plugin to ~/.claude/plugins/
  2. Detect your project type (Android/iOS/Web/Firebase)
  3. Install only the relevant path-specific rules
  4. Create a project CLAUDE.md with skill references
  5. Add .claude/settings.local.json to .gitignore

Via Marketplace

# Add the Cure Consulting marketplace
claude marketplace add https://github.com/Cure-Consulting-Group/ProductEngineeringSkills/marketplace.json

# Install the plugin
claude plugin install cure-product-engineering

Legacy Method (Copy Commands)

Copy the claude-commands/ files into your project's .claude/commands/ directory:

cp claude-commands/*.md /path/to/your/project/.claude/commands/

Then use them as slash commands:

/sdlc — Generate SDLC artifacts
/android-feature-scaffold — Scaffold an Android feature module
/feature-audit — Audit a completed feature

Using with Google Gemini

Import the .skill files from gemini skills/ into your Gemini workspace. Each .skill file is a ZIP archive containing:

  • SKILL.md — The main skill definition
  • references/ — Supporting documents and templates

Skill Inventory (64 Skills)

Product & Strategy (7)

SkillWhat It DoesAuto-Invoked?
product-managerOKRs, roadmaps, RICE prioritization, feature briefsYes
product-designApple HIG, Material Design 3, design tokens, accessibility-firstYes
market-researchTAM/SAM/SOM, competitive analysis, ICP definition (read-only)Yes
go-to-marketGTM plans, launch strategy, channel selection, growth playbooksYes
product-marketingBrand strategy, messaging frameworks, campaignsYes
customer-onboardingActivation flows, empty states, email sequences, retentionYes
seo-content-engineTechnical SEO, structured data, content strategyYes

Engineering & Architecture (18)

SkillWhat It DoesAuto-Invoked?
sdlcPRDs, ADRs, RFCs, Epics, Stories, Task specs — full SDLCYes
android-feature-scaffoldClean Architecture Android scaffolding (MVI, Compose, Hilt)Yes
ios-architectSwift/SwiftUI Clean Architecture, MVVM, structured concurrencyYes
nextjs-feature-scaffoldApp Router, Server/Client components, Tailwind patternsYes
firebase-architectFirestore schema, security rules, Cloud FunctionsYes
api-architectREST/GraphQL design, versioning, auth, rate limitingYes
api-gatewayAPI gateway and BFF layers, rate limiting, GraphQL federationYes
stripe-integrationStripe payments + subscriptions via Firebase FunctionsYes
ai-feature-builderLLM integration, RAG pipelines, prompt engineeringYes
llmopsLLM operationalization — prompt versioning, eval pipelines, cost optimization, guardrailsYes
database-architectSchema design, migrations, indexing for Firestore/PostgreSQL/SQLiteYes
data-migrationETL pipelines, zero-downtime cutover, validation, rollback strategiesYes
infrastructure-scaffoldCloud infra configs for Firebase, GCP, Vercel, DockerYes
edge-computingEdge functions, CDN strategies, cache invalidation, edge middlewareYes
micro-frontendsModule federation, monorepo management, independent deploymentsYes
offline-firstOffline-first architecture, sync strategies, conflict resolution, optimistic UIYes
i18nInternationalization — string extraction, RTL, locale-aware formatting, translation workflowsYes
notification-architectPush (FCM/APNs), in-app messaging, email, preference managementYes

Quality & Security (11)

SkillWhat It DoesAuto-Invoked?
feature-audit5-phase post-completion audit with scored gap reportYes (read-only, forked)
testing-strategyTesting pyramid, platform standards, coverage rulesYes
e2e-testingE2E test suites with page objects, visual regression, CI integrationYes
test-accountsTest user personas, seed data scripts, environment credentialsYes
uatUAT plans, acceptance criteria checklists, go/no-go release gatesYes
security-reviewOWASP checklist, auth/data/API/mobile/web securityYes (read-only, forked)
compliance-architectHIPAA, COPPA, GDPR, PCI compliance frameworks, consent flows, audit trailsYes
accessibility-auditWCAG 2.2 compliance, screen readers, inclusive designYes (read-only, forked)
performance-reviewPerformance budgets, load testing, optimization strategiesYes
chaos-engineeringResilience testing, failure injection, graceful degradation, game daysYes
green-softwareSustainable software practices, carbon-aware computing, energy efficiencyYes

Operations & Delivery (12)

SkillWhat It DoesAuto-Invoked?
project-bootstrapBootstrap repo with CLAUDE.md + STATE.md via codebase inspection and developer interviewYes
project-managerSprint planning, RACI, risk registers, retrospectivesYes
ci-cd-pipelineGitHub Actions, build/test/deploy, environments, secretsYes
release-managementApp store submissions, staged rollouts, versioning, ASO, changelogsYes
feature-flagsProgressive rollouts, A/B testing, kill switches, experimentation frameworksYes
observabilityStructured logging, distributed tracing, alerting, SLO/SLI, dashboardsYes
dora-metricsDORA and SPACE metrics — deployment frequency, lead time, MTTR, developer experienceYes
analytics-implementationEvent taxonomy, tracking plans, funnels, dashboardsYes
incident-responseRunbooks, severity classification, post-mortems, escalationYes
disaster-recoveryDR and business continuity — RTO/RPO, backup strategies, failover, DR testingYes
growth-engineeringActivation funnels, referral programs, lifecycle automation, PLG patternsYes
design-systemDesign tokens, component libraries, Storybook/Catalog, cross-platform consistencyYes

Business & Finance (7)

SkillWhat It DoesAuto-Invoked?
engineering-cost-modelProject estimates, infrastructure costs, build vs buyYes (read-only)
saas-financial-modelUnit economics, MRR/ARR, pricing tiers, break-evenYes (read-only)
finopsCloud cost optimization, budget alerts, resource right-sizing, FinOps practicesYes
investor-reportingInvestor updates, board decks, portfolio financials, cap table, runway modelingYes
fundraising-materialsPitch decks, data rooms, investor updates, cap table scenarios, fundraising pipelineYes
burn-rate-trackerBurn rates, runway scenarios, break-even analysis, cash flow projectionsYes
legal-doc-scaffoldToS, Privacy Policy, SOW, NDA scaffoldsNo (manual only)

Portfolio Management (2) — NEW

SkillWhat It DoesAuto-Invoked?
portfolio-registryProduct portfolio registry — single source of truth for all products, stacks, teams, stagesYes
technology-radarThoughtWorks-style technology radar — Adopt/Trial/Assess/Hold across the portfolioYes

Consulting Operations (3)

SkillWhat It DoesAuto-Invoked?
client-handoffHandoff packages, runbooks, credential transfers, maintenance SLAs, knowledge transferYes
client-communicationSprint demo scripts, stakeholder updates, risk escalation, executive summariesYes
proposal-generatorConsulting proposals, SOWs, milestone pricing, engagement structureNo (manual only)

Platform Design (4)

SkillWhat It DoesAuto-Invoked?
android-design-expertMaterial Design 3 — dynamic color, component tokens, adaptive layouts, motion, Compose patternsYes
ios-design-expertApple HIG — SF Symbols, Dynamic Type, navigation patterns, SwiftUI componentsYes
web-design-expertResponsive design, CSS architecture, design tokens, container queries, accessibility-first, TailwindYes
stitch-designAI-native UI design via Stitch MCP — vibe design, mockups, screen generation, design tokens, component exportYes

Recurring Automation (Loops & Routines)

The library ships a standard maintenance loop (self-provisioned to .claude/loop.md on first session start — run bare /loop to use it), Recurring Mode sections in the goal-shaped skills (finops, burn-rate-tracker, investor-reporting, security-review, and others), and copy-paste cloud-routine recipes in docs/AUTOMATION.md. Library upkeep cadence: docs/MAINTENANCE.md. Mechanism selection and unattended-run guardrails: /cure-product-engineering:engagement-automation.

Hooks (Multi-Layer Automated Enforcement)

The plugin ships command and prompt hooks across 9 event types: SessionStart, PreCompact, PostCompact, ConfigChange, PostToolUseFailure, UserPromptSubmit, PreToolUse, Stop, SubagentStop. Highlights: a Stop-hook quality gate (blocks "done" without verification), a PreToolUse static security guard on skill/agent/persona files, and a ConfigChange audit trigger when skill files change mid-session.

New in v4.0: Hooks now suggest and auto-trigger agents based on context. Every code edit, test run, deployment, and PR action recommends the most relevant agent(s).

Command Hooks (Deterministic)

HookEventWhat It DoesAgent Integration
WelcomeSessionStartConfirms plugin loaded with counts; full inventory stays in docs/OVERVIEW.mdPoints to inventory
Git statusSessionStartReports current branch, uncommitted changes, last commit—
Dependency checkSessionStartDetects outdated packagesSuggests dependency-auditor
Code edit advisorPostToolUse (Edit/Write)Context-aware suggestions based on file type (.kt, .swift, .ts, .sql, .tf, etc.)Suggests code-reviewer, test-runner, brand-guardian, migration-validator
Command advisorPostToolUse (Bash)Post-action guidance for tests, installs, deploys, PRs, releasesSuggests ci-debugger, dependency-auditor, pr-reviewer, release-coordinator
Failure recoveryPostToolUseFailureDiagnoses failure type and suggests fix approachAuto-suggests ci-debugger, deployment-validator, dependency-auditor
Destructive prompt guardUserPromptSubmitDetects destructive operations in promptsBlocks and confirms
Protected filesPreToolUse (Edit/Write)Blocks edits to .env, lock files, credentials, tfstate—
Dangerous commandsPreToolUse (Bash)Blocks force push, destructive rm, DROP TABLE, prod deploys—
Context re-injectionPreCompactRe-injects all 80 skills, 39 agents, 4 personas, and Cure standardsFull inventory preserved
Post-compact restorePostCompactConfirms context restored with agent availability—
Subagent start bannerSubagentStartAnnounces agent with role, standards, and companion agentsLists companion agents
Subagent completionSubagentStopSuggests follow-up agents (test-runner, code-reviewer, pr-reviewer)Agent chaining
Task quality checkTaskCompletedValidates tests, security, docs, brand consistencySuggests test-runner, code-reviewer, doc-generator, brand-guardian

Prompt Hooks (LLM-Validated)

HookEventWhat It DoesAgent Integration
Code quality gatePreToolUse (Edit/Write)Haiku validates: no secrets, no debug logs, no disabled tests, no any types—
Deployment safetyPreToolUse (Bash)Haiku validates: blocks production deployments outside CI/CD—
Intent classifierUserPromptSubmitHaiku classifies prompt intent and suggests the most relevant agent(s) from all 30Maps prompts → agents with confidence scores

Agent Hooks (Multi-Turn Verification)

HookEventWhat It DoesAgent Integration
Completion validatorStopValidates: tests for new code, security review for sensitive changes, rollback for migrations, docs for features, brand consistency for UI, analytics for events, API contractsSuggests specific agents for each gap found

MCP Server Integrations

Pre-configured MCP servers in .mcp.json:

ServerTypeWhat It Does
GitHubHTTPPR management, issue tracking, code search
SentryHTTPError monitoring, issue tracking, release health
FirestorestdioDirect database queries, schema inspection
PostgreSQLstdioDatabase queries, schema inspection, migrations

LSP Server Integrations

Pre-configured LSP servers in .lsp.json:

ServerLanguageWhat It Provides
TypeScript.ts, .tsx, .jsType checking, auto-imports, refactoring, go-to-definition
Python (Pyright)*.pyStatic type analysis, import resolution, error diagnostics

Output Styles

Custom output formatting for different artifact types:

StyleUsed ByKey Rules
prdProduct skills (PRDs, GTM, research)Numbered sections, decision matrices, executive summaries
code-generationEngineering skills (scaffolds)File tree first, dependency order, complete runnable code
financial-analysisBusiness skills (costs, models)ASCII tables, explicit assumptions, sensitivity analysis
audit-reportQuality skills (audits, reviews)Severity scoring, checklists, remediation with effort estimates
api-specificationAPI design skillsOpenAPI 3.0 blocks, endpoint tables, request/response examples
architecture-decisionADR and RFC skillsContext/decision/consequences format, trade-off matrices
runbookIncident response, disaster recoveryNumbered steps, command blocks, decision trees, escalation paths
test-planTesting strategy, QA skillsCoverage tables, test case templates, pass/fail criteria
monitoring-alertObservability, incident responseAlert definition tables, threshold rationale, runbook links

Custom Agents (30)

Engineering Agents (14)

AgentPurposeToolsAuto-Triggered By
code-reviewerSecurity + quality review against Cure standardsRead-onlyStop hook, SubagentStop
project-bootstrapperSet up new projects with correct architecture
Source 2 files
hooks/register.ts 143 lines
1import type { On } from 'claude-code'
2
3import { addedText, check, dependencyNames, parentDirs, targetPath, type Violation } from './rules'
4
5/**
6 * cure-policy-guard: refuses a file write that breaks a written Cure house
7 * rule, unless the person at the keyboard overrides it, and records every
8 * override.
9 *
10 * Scope: Claude's own Write / Edit / MultiEdit / NotebookEdit calls. A Bash
11 * command that writes a file (`cat > x`, `sed -i`) is not read here; the
12 * repos' CI and scanners remain the backstop for those.
13 *
14 * Fails closed on the question, open on the plumbing: if the person cannot be
15 * asked (headless, dismissed), the write is refused; if the repo or
16 * package.json cannot be read, the rules that need them see '' / [] and the
17 * path- and text-only rules still apply.
18 */
19
20const FILE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit'])
21const OVERRIDES_KEY = 'overrides'
22const MAX_OVERRIDES = 200
23
24type Override = { at: string; rule: string; path: string; repo: string }
25
26const REFUSE = 'Refuse the write'
27const ALLOW = 'Allow this once (logged)'
28
29export function register(on: On) {
30  on('session.start', async ($, e, next) => {
31    await $.command.register({
32      name: 'policy-guard',
33      description: 'Show the house rules the guard enforces and the recent overrides',
34    })
35    return next(e)
36  })
37
38  on('command.run', { command: 'policy-guard' }, async $ => {
39    const log = ((await $.store.get(OVERRIDES_KEY)) as Override[] | undefined) ?? []
40    const recent = log.slice(-10).reverse()
41    const lines = [
42      'cure-policy-guard rules: no-cron · level5-vertex-only · minors-new-ai-vendor · hardcoded-secret',
43      recent.length === 0
44        ? 'No overrides recorded.'
45        : 'Recent overrides:\n' +
46          recent.map(o => `  ${o.at}  ${o.rule}  ${o.repo}  ${o.path}`).join('\n'),
47    ]
48    return { text: lines.join('\n') }
49  })
50
51  on('tool.call', async ($, e, next) => {
52    if (!FILE_TOOLS.has(String(e.tool))) return next(e)
53    const path = targetPath(e)
54    const added = addedText(e)
55    if (!path || !added) return next(e)
56
57    const { repo, root } = await locateRepo($, path)
58    const deps = await nearestDependencies($, path, root)
59    const violations = check({ path, repo, added, deps })
60    if (violations.length === 0) return next(e)
61
62    const summary = violations.map(v => `[${v.rule}] ${v.reason}`).join('\n')
63    let answer: string
64    try {
65      answer = await $.ui.ask(
66        `cure-policy-guard: this ${String(e.tool)} to ${shortPath(path)} breaks a house rule:\n${summary}\nAllow it anyway?`,
67        { options: [REFUSE, ALLOW], header: 'House rule' },
68      )
69    } catch {
70      return { deny: denial(violations, 'nobody could be asked to override it') }
71    }
72    if (answer !== ALLOW) return { deny: denial(violations, 'the user refused it') }
73
74    await recordOverride($, violations, path, repo)
75    $.ui.log(`cure-policy-guard: override recorded for ${violations.map(v => v.rule).join(', ')} on ${shortPath(path)}`)
76    return next(e)
77  })
78}
79
80function denial(violations: Violation[], why: string): string {
81  return (
82    `Blocked by cure-policy-guard (${why}). ` +
83    violations.map(v => `${v.rule}: ${v.reason} Source: ${v.source}.`).join(' ') +
84    ' Do not retry the same write; change the approach or ask the user.'
85  )
86}
87
88export const shortPath = (p: string, maxLen = 38): string => {
89  const parts = p.split('/').filter(Boolean)
90  const tail = parts.slice(-3).join('/')
91  if (tail.length <= maxLen) return tail
92  const filename = parts[parts.length - 1] ?? ''
93  if (parts.length <= 2) return tail
94  const dir = parts[parts.length - 2] ?? ''
95  const mid = `.../${dir}/${filename}`
96  return mid.length <= maxLen ? mid : `.../${filename}`
97}
98
99/** The repository's name (main checkout's directory, also from a worktree) and root. */
100async function locateRepo($: any, path: string): Promise<{ repo: string; root: string }> {
101  try {
102    const dir = await firstExistingDir($, path)
103    if (!dir) return { repo: '', root: '' }
104    const common = await $.process.run(['git', '-C', dir, 'rev-parse', '--path-format=absolute', '--git-common-dir'])
105    const top = await $.process.run(['git', '-C', dir, 'rev-parse', '--show-toplevel'])
106    if (common.exitCode !== 0 || top.exitCode !== 0) return { repo: '', root: '' }
107    const commonDir = common.stdout.trim().replace(/\/$/, '')
108    const repoDir = commonDir.endsWith('/.git') ? commonDir.slice(0, -5) : commonDir
109    return { repo: repoDir.split('/').pop() ?? '', root: top.stdout.trim() }
110  } catch {
111    return { repo: '', root: '' }
112  }
113}
114
115async function firstExistingDir($: any, path: string): Promise<string | undefined> {
116  for (const dir of parentDirs(path)) {
117    if (await $.fs.exists(dir)) return dir
118  }
119  return undefined
120}
121
122/** Dependencies of the nearest package.json between the file and the repo root. */
123async function nearestDependencies($: any, path: string, root: string): Promise<string[]> {
124  if (!root) return []
125  try {
126    for (const dir of parentDirs(path)) {
127      if (!dir.startsWith(root)) break
128      const pkg = `${dir}/package.json`
129      if (await $.fs.exists(pkg)) return dependencyNames(await $.fs.read(pkg))
130    }
131  } catch {
132    // unreadable: treat as no dependencies, which makes the vendor rule stricter
133  }
134  return []
135}
136
137async function recordOverride($: any, violations: Violation[], path: string, repo: string) {
138  const log = ((await $.store.get(OVERRIDES_KEY)) as Override[] | undefined) ?? []
139  const at = new Date(await $.clock.now()).toISOString()
140  for (const v of violations) log.push({ at, rule: v.rule, path, repo })
141  await $.store.set(OVERRIDES_KEY, log.slice(-MAX_OVERRIDES))
142}
143
hooks/rules.ts 189 lines
1/**
2 * The house rules the guard enforces, as pure functions of one file write.
3 *
4 * Each rule comes from a decision already written down in a Cure repo; the
5 * `source` says where, so a refusal can be traced to the rule and argued with.
6 * Rules look only at text a write ADDS, so an edit that leaves an existing
7 * violation alone is not blocked here (the repo's own CI owns old debt).
8 */
9
10export type Write = {
11  /** Absolute path of the file being written. */
12  path: string
13  /** Basename of the git repository root holding the file, or '' outside one. */
14  repo: string
15  /** The text this write introduces (whole file for Write, new_string for Edit). */
16  added: string
17  /** Dependency names in the nearest package.json, if one was found. */
18  deps: readonly string[]
19}
20
21export type Violation = {
22  rule: string
23  reason: string
24  source: string
25}
26
27const isEnvFile = (path: string) => /(^|\/)\.env(\.[^/]*)?$/.test(path)
28
29/** Org policy since 2026-08-08: no `schedule:` trigger in any workflow. */
30export function noCron(w: Write): Violation | undefined {
31  if (!/(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/.test(w.path)) return undefined
32  if (!/^\s*schedule\s*:/m.test(w.added)) return undefined
33  return {
34    rule: 'no-cron',
35    reason:
36      'adds a `schedule:` trigger to a workflow. Org policy since 2026-08-08 bans cron in GitHub Actions; use push, PR, dispatch or an age gate on a push trigger instead.',
37    source: 'DistrictZero/CLAUDE.md "No cron. Org policy since 2026-08-08"; github_policy scanner',
38  }
39}
40
41const CONSUMER_GEMINI = /@google\/generative-ai\b|generativelanguage\.googleapis\.com|\bGEMINI_API_KEY\b/
42
43/** Level5 handles PHI: Gemini only through Vertex AI under the GCP BAA. */
44export function level5VertexOnly(w: Write): Violation | undefined {
45  if (w.repo !== 'Level5') return undefined
46  const hit = CONSUMER_GEMINI.exec(w.added)
47  if (!hit) return undefined
48  return {
49    rule: 'level5-vertex-only',
50    reason: `introduces \`${hit[0]}\`, the consumer Gemini API path. Level5 processes PHI, and only Vertex AI is covered by the Google Cloud BAA.`,
51    source: 'Level5/docs/compliance/BAA_INVENTORY.md:33-34, :48',
52  }
53}
54
55/** Repos whose data is about minors or students (COPPA/FERPA-adjacent). */
56export const MINORS_REPOS: ReadonlySet<string> = new Set([
57  'initiated-recruiting',
58  'initiated-recruiting-nil',
59  'SPEDTECH',
60  'LearnLift',
61  'iep-and-thrive',
62])
63
64/** Model-vendor SDKs, as an import names them. */
65export const AI_VENDOR_PACKAGES: readonly string[] = [
66  'openai',
67  '@anthropic-ai/sdk',
68  '@google/genai',
69  '@google/generative-ai',
70  '@google-cloud/vertexai',
71  'cohere-ai',
72  '@mistralai/mistralai',
73  'groq-sdk',
74  'together-ai',
75  'replicate',
76  '@typesafe-ai/sdk',
77  'typesafe',
78]
79
80const importedPackages = (text: string): string[] => {
81  const found = new Set<string>()
82  const re = /(?:from\s+|require\(\s*|import\(\s*|import\s+)['"]([^'"]+)['"]/g
83  for (const m of text.matchAll(re)) {
84    const spec = m[1]
85    const name = spec.startsWith('@') ? spec.split('/').slice(0, 2).join('/') : spec.split('/')[0]
86    found.add(name)
87  }
88  return [...found]
89}
90
91/** A new AI vendor in a minors' repo needs a decision, not a default. */
92export function minorsNewAiVendor(w: Write): Violation | undefined {
93  if (!MINORS_REPOS.has(w.repo)) return undefined
94  const added = importedPackages(w.added).filter(
95    p => AI_VENDOR_PACKAGES.includes(p) && !w.deps.includes(p),
96  )
97  if (added.length === 0) return undefined
98  return {
99    rule: 'minors-new-ai-vendor',
100    reason: `imports ${added.map(p => `\`${p}\``).join(', ')}, an AI vendor this repo does not already depend on. ${w.repo} handles data about minors or students; a new processor needs a data-processing decision first (several vendors, TypeSafe among them, exclude under-18 data by policy).`,
101    source: 'Jev / AI-vendor review 2026-10-04; SPEDTECH privacy page "de-identified prompts only"',
102  }
103}
104
105const SECRET_PATTERNS: readonly [string, RegExp][] = [
106  ['Google API key', /AIza[0-9A-Za-z_-]{35}/],
107  ['Anthropic API key', /sk-ant-[0-9A-Za-z_-]{20,}/],
108  ['OpenAI API key', /\bsk-(?:proj-)?[0-9A-Za-z_-]{32,}/],
109  ['GitHub token', /\b(?:ghp|gho|ghs|ghu)_[0-9A-Za-z]{36}\b|\bgithub_pat_[0-9A-Za-z_]{40,}/],
110  ['Stripe secret key', /\b(?:sk|rk)_live_[0-9A-Za-z]{20,}/],
111  ['private key', /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/],
112  ['service-account key', /"private_key"\s*:\s*"-----BEGIN/],
113]
114
115/** A credential literal in a tracked file. `.env*` files are where keys belong. */
116export function hardcodedSecret(w: Write): Violation | undefined {
117  if (isEnvFile(w.path)) return undefined
118  for (const [kind, re] of SECRET_PATTERNS) {
119    if (re.test(w.added)) {
120      return {
121        rule: 'hardcoded-secret',
122        reason: `writes what looks like a ${kind} into ${w.path.split('/').pop()}. Keep credentials in Secret Manager, a GitHub secret or a gitignored .env, and read them at run time.`,
123        source: 'TIR scripts/data-fixes/*.js shipped a hard-coded Gemini key (found 2026-10-04)',
124      }
125    }
126  }
127  return undefined
128}
129
130export const RULES = [noCron, level5VertexOnly, minorsNewAiVendor, hardcodedSecret] as const
131
132/** Every rule the write breaks, in RULES order. */
133export function check(w: Write): Violation[] {
134  return RULES.map(rule => rule(w)).filter((v): v is Violation => v !== undefined)
135}
136
137/** The text a built-in file tool call adds; '' for a call this guard does not read. */
138export function addedText(e: Record<string, unknown>): string {
139  const str = (v: unknown) => (typeof v === 'string' ? v : '')
140  switch (e.tool) {
141    case 'Write':
142      return str(e.content)
143    case 'Edit':
144      return str(e.new_string)
145    case 'MultiEdit':
146      return Array.isArray(e.edits)
147        ? e.edits.map(x => str((x as Record<string, unknown>)?.new_string)).join('\n')
148        : ''
149    case 'NotebookEdit':
150      return str(e.new_source)
151    default:
152      return ''
153  }
154}
155
156/** The path a built-in file tool call writes; '' when it names none. */
157export function targetPath(e: Record<string, unknown>): string {
158  const p = e.file_path ?? e.notebook_path
159  return typeof p === 'string' ? p : ''
160}
161
162/** Dependency names from a package.json's text; [] when it does not parse. */
163export function dependencyNames(packageJson: string | undefined): string[] {
164  if (!packageJson) return []
165  try {
166    const pkg = JSON.parse(packageJson) as Record<string, Record<string, string> | undefined>
167    return [
168      ...Object.keys(pkg.dependencies ?? {}),
169      ...Object.keys(pkg.devDependencies ?? {}),
170      ...Object.keys(pkg.optionalDependencies ?? {}),
171      ...Object.keys(pkg.peerDependencies ?? {}),
172    ]
173  } catch {
174    return []
175  }
176}
177
178/** The directories above a path, nearest first, ending at '/'. */
179export function parentDirs(path: string): string[] {
180  const out: string[] = []
181  let dir = path.replace(/\/+$/, '')
182  while (dir.includes('/')) {
183    dir = dir.slice(0, dir.lastIndexOf('/'))
184    out.push(dir === '' ? '/' : dir)
185    if (dir === '') break
186  }
187  return out
188}
189