Skip to content
Back to skills

Legal Generate

BSecurity

This skill should be used when generating draft legal documents for a project or company. It gathers company context interactively, invokes the soleur:legal:legal-document-generator agent, and writes markdown output.

  • 16 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 27, 2026
data-aipythonrustgoshellbashgitapi

Works with

  • claude code
  • cli
  • api

Security analysis

B75/100
  • criticalSends environment variables or credentials to an external URL

Pro shows the line behind each finding and how to fix it

Scanned September 24, 2026

npx -y skills add jikig-ai/soleur --skill legal-generate --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Legal Generate?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Legal Generate
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/jikig-ai-legal-generate/badge)](https://www.skillsdirectory.com/skills/jikig-ai-legal-generate)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: legal-generate
description: "This skill should be used when generating draft legal documents for a project or company. It gathers company context interactively, invokes the soleur:legal:legal-document-generator agent, and writes markdown output."
---

<!-- soleur-cloud-mode:start -->
**Cloud Mode (Devin):** before pipeline work run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/cloud-detect.sh"`. If `CLAUDE_PLUGIN_ROOT` is unset (cloud exec shells do not export it), resolve the root by IDENTITY, never by script basename: for `d` in `"$HOME/.local/share/devin/cli/plugins/cache"` and `/opt/.devin/plugins`, skip unless `[ -d "$d" ]`, then `MANIFEST="$(find "$d" -path '*/.claude-plugin/plugin.json' -exec grep -l '"name"[[:space:]]*:[[:space:]]*"soleur"' {} + 2>/dev/null | head -1)"`, `ROOT="${MANIFEST%/.claude-plugin/plugin.json}"` — one resolution, two consumers: `$ROOT/scripts/cloud-detect.sh` and `$ROOT/scripts/precommit-guard.sh`. (Shape check, not authentication: a planted `{"name":"soleur"}` dir passes — ADR-179 A11.) `local` or `not-local:no-devin-env` proceeds normally; any other `not-local:<reason>` applies `<plugin-root>/devin/INSTRUCTIONS.md` §Cloud Mode: emit the `--banner`, fan out sequentially with `Reviewed-Coverage: sequential-fallback` disclosure (never claim an independent review ran), `message_user` ack before any secrets read or production mutation, and run `precommit-guard.sh` before any `git commit` — hooks do not fire in cloud.
<!-- soleur-cloud-mode:end -->

<!-- grok-harness-invoke:start -->
**Grok Build (`plugins/soleur/lib/harness.ts` `invokeSkill()`):** Read this SKILL.md in this process and run it to completion. A one-segment `soleur:<name>` in this document names a SKILL — on Grok Build, Read `plugins/soleur/skills/<name>/SKILL.md` in this process; it is not a nested tool_use. A multi-segment id such as `soleur:<domain>:<name>` names an AGENT: spawn it, never Read it, and on Grok Build spawn_subagent takes the id with its colons replaced by hyphens (`agentIdToGrokSubagentType`). **Claude Code:** Skill tool for a skill (`soleur:<name>`), Task tool with `subagent_type` for an agent. Forbidden is executing a subset, not the Read.
<!-- grok-harness-invoke:end -->

# Legal Document Generator

Generate draft legal documents from company context. Supports 14 document types across US, EU/GDPR, and UK jurisdictions. All output is marked as a draft requiring professional legal review.

## Supported Document Types

- Terms & Conditions
- Privacy Policy
- Cookie Policy
- GDPR Policy
- Acceptable Use Policy
- Data Processing Agreement
- Data Protection Disclosure
- Disclaimer / Limitation of Liability
- Master Services Agreement
- Mutual NDA
- One-Way NDA
- Advisor Agreement

Two more types are available on explicit request only — both carry blocking confirmations, so they are never offered in the menu:

- Employee Offer Letter (CA-exempt scope confirm)
- Business Associate Agreement (HIPAA/PHI confirm)

Overlapping types fill a vendored template substrate ([references/templates/](references/templates/)); uncovered types and unsupported jurisdictions are generated from scratch by the agent — routing is its decision, not the menu's.

## Phase 0: Context Gathering

Use the **AskUserQuestion tool** to gather company context. Ask for:

1. **Company name** (required)
2. **Product/service description** (required) -- what the product does, who it serves
3. **Data practices** -- what user data is collected, how it is processed, stored, shared
4. **Jurisdiction** -- which legal frameworks apply (US, EU/GDPR, UK, or multiple)
5. **Contact information** -- email and/or physical address for legal notices

If the user provides arguments after the skill name (e.g., `soleur:legal-generate privacy-policy`), use that as the document type selection and skip Phase 1.

## Phase 1: Document Selection

Use the **AskUserQuestion tool** to select a document type. Offer the four most likely types (e.g. Privacy Policy / Terms & Conditions / Mutual NDA / Master Services Agreement) and rely on the built-in **Other** free-text option for the rest — the menu never lists the gated types.

## Phase 1.5: Substrate Staleness Check

Before invoking the agent, check the freshness of the vendored template corpus:

```bash
NOTICE_FILE="${CLAUDE_PLUGIN_ROOT}/skills/legal-generate/NOTICE" \
  bash "${CLAUDE_PLUGIN_ROOT}/skills/gdpr-gate/scripts/notice-frontmatter.sh" days-stale
```

- **>30 days** — print an advisory banner to stdout: the template corpus has not been verified against upstream recently.
- **>90 days** — additionally print `POSTURE_FAIL:`; the operator follows the chain in `knowledge-base/engineering/policies/content-vendoring.md` to record it in `compliance-posture.md`.
- Advisory only — generation proceeds either way; do not block on staleness.

## Phase 2: Generation

Invoke the `soleur:legal:legal-document-generator` agent via the **Task tool** with the company context and selected document type. The agent resolves the substrate arm (template-fill vs from-scratch) from its own routing table — do not pre-decide it here.

```
Task soleur:legal:legal-document-generator: "Generate a [document type] for [company name].
Company: [name]
Product: [description]
Data practices: [practices]
Jurisdiction: [jurisdiction]
Contact: [contact info]"
```

**Gates measure agreement, not truth (#7349).** All five gates compare the two surfaces against
each other. None asks whether the agreed text is correct, so two byte-identical copies of a false
sentence pass every one of them. Three defects shipped past a full green run in #7349 that way: a
controllership statement drift-reduction copied onto the published page, a duplicated clause left
behind by a half-applied replacement, and "eleven processing activities" in a document whose
register carries thirty-five. Read the prose; do not read the drift number and stop.

**`BODY_EQUIVALENCE_DOCS` is a one-way ratchet.** `terms-and-conditions`, `acceptable-use-policy`
and `disclaimer` are enrolled; each was verified at ZERO normalised drift immediately before
enrolment, because enrolling a drifted document turns a required check red on arrival. Once
enrolled, any edit landing on one surface only reds that check — which is the point. `--print-vocab`
and a mutation check (inject a line, confirm the guard fails, remove it) are the two ways to prove
an enrolment is live rather than decorative.

**Two measurement traps that cost real rounds in #7349.** `collapse()` normalises `[0-9]+ AI
agents` but NOT a bare `[0-9]+ agents`, so count divergence between the record and the published
page can be invisible to the drift gate. And a grep for `Article ` will not match the corpus's <!-- markdownlint-disable-line MD038 -->
plural `Articles 15 through 22` — use `Articles? 1[5-9]`.

## Phase 2.5: Redaction Gate (BLOCKING — runs BEFORE inline presentation)

A generated legal draft can echo a secret or PII that was passed in as company context (a contact email, an API identifier pasted into a data-practices answer). **Presenting the draft inline in Phase 3 is a transcript write boundary** — the same fail-closed rule the incident skill enforces (`incident/SKILL.md` Phase 6): the sentinel must precede inline-emit, not just file-commit. So the redaction gate runs here, before the operator ever sees the draft.

1. Write the generated draft to a `mktemp` file (do NOT emit it inline yet).

Register the cleanup trap **in the same block that allocates the draft**, before anything
can halt. This PR adds an earlier fail-closed exit, so it increases how often the draft is
abandoned mid-flight — and the abandoned file is the UN-REDACTED text, which is precisely
what this gate exists to stop escaping. Leaving it in `mktemp` is a leak with a longer
lifetime than the session (#7450 review-finding C14).

The allocation, the trap and the gate MUST share **one** fence — each fenced block is a
separate Bash call, so a trap registered in its own block fires when *that* block exits,
deleting the draft immediately and leaving `$DRAFT` empty for everything after it. The split
form shipped once and all three of its guarantees were false. The combined fence is in step 2
below.

2. Run the shared hardened engine against it. Resolve the path from the **deployed plugin root**
   (`${CLAUDE_PLUGIN_ROOT}`, the platform-trusted copy — ADR-179's canonical bare anchor, with no
   fallback arm) — NOT a bare `../incident/...` relative path, which depends on the current working
   directory and, from the wrong CWD, exits `127` *outside* the shim (bypassing the shim's fail-closed
   exit-2 normalization). On the Concierge server the deployed-root anchor is load-bearing: a bare
   CWD-relative path would resolve the connected repo's **untrusted** copy of the sentinel (ADR-093).
   The default arm was **removed, not re-pointed**: `review/SKILL.md` instructs `gh pr checkout`, after
   which the git worktree is the *reviewed party's* tree, so that arm resolved this gate's own scanner
   from a file a hostile PR controls (#7450). The **bare anchor is the load-bearing control** — the
   loader substitutes it with the installed root at delivery, so no ambient environment value reaches
   this site (measured: `specs/feat-one-shot-7450-git-root-anchor-untrusted/phase-1-measurement.md`
   Arm 4). The identity preflight below is **defence-in-depth** for surfaces where substitution does
   not govern: it is a *shape* check and cannot tell an install from a checkout carrying the same
   manifest, which is why ADR-179 §(a) measured a shape check passing while an attacker-chosen payload
   executed. The stronger root-outside-the-worktree form was evaluated and rejected — see
   `specs/feat-one-shot-7450-git-root-anchor-untrusted/b1-disposition.md`. Each halt emits a
   `SOLEUR_*` marker on stdout so refusals reach telemetry:

   ```bash
   DRAFT="$(mktemp)" || { echo "SOLEUR_LEGAL_GENERATE_HALT reason=draft-alloc-failed"
                          echo "legal-generate: cannot allocate a draft file — stopping before any draft text exists." >&2
                          exit 2; }
   trap 'rm -f "$DRAFT"' EXIT INT TERM HUP

   # Write the generated draft into "$DRAFT" here — in THIS fence, before the gate below.
   # Use a QUOTED heredoc delimiter (`<<'DRAFT_EOF'`): company-context answers can contain
   # `$(…)`, backticks and `$VAR`, and an unquoted delimiter would EXECUTE the substitutions
   # on the operator's machine and expand `$VAR` to empty — mutating the text the sentinel is
   # about to scan, so a secret's shape can be destroyed by the shell instead of the redactor.
   #   cat > "$DRAFT" <<'DRAFT_EOF'
   #   <the generated draft, verbatim>
   #   DRAFT_EOF

   [ -f "${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json" ] \
     && grep -q '"name"[[:space:]]*:[[:space:]]*"soleur"' "${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json" \
     || { echo "SOLEUR_LEGAL_GENERATE_HALT reason=plugin-root-unverified root=[${CLAUDE_PLUGIN_ROOT}]"
          echo "legal-generate: cannot verify the Soleur plugin installation — stopping before any draft is written." >&2
          echo "  Resolved plugin root: [${CLAUDE_PLUGIN_ROOT}]" >&2
          echo "  If that is EMPTY: no Soleur plugin is loaded in this session. Install it and start a NEW session — re-running here resolves the same empty root." >&2
          echo "  If it names a path: that path is not a Soleur install (a repo checkout is not an install). Run 'claude plugin update soleur@soleur-marketplace' (or the id 'claude plugin list' prints, if you added the repository directly), then RESTART Claude Code — plugin changes apply only on restart. If you installed with --scope project or --scope local, pass the same scope. Reinstall only if that does not clear it." >&2
          echo "  Do NOT hand-edit and publish this draft — the redaction scanner is what makes it safe to share." >&2
          exit 2; }
   SENTINEL="${CLAUDE_PLUGIN_ROOT}/skills/incident/scripts/redact-sentinel.sh"
   [[ -r "$SENTINEL" ]] || { echo "SOLEUR_LEGAL_GENERATE_HALT reason=sentinel-unreadable sentinel=[$SENTINEL]"
          echo "legal-generate: the redaction sentinel is missing from an otherwise valid Soleur install — stopping." >&2
          echo "  Expected at: [$SENTINEL]" >&2
          echo "  The install is partial or out of date. Run 'claude plugin update soleur@soleur-marketplace' (or the id 'claude plugin list' prints, if you added the repository directly), then RESTART Claude Code — plugin changes apply only on restart. If you installed with --scope project or --scope local, pass the same scope. Reinstall only if that does not clear it." >&2
          echo "  Do NOT hand-edit and publish this draft — the redaction scanner is what makes it safe to share." >&2
          exit 2; }
   # EMPTINESS IS A FAILURE, NOT A CLEAN SCAN — the sentinel exits 0 on zero bytes, so an
   # unwritten draft passes the gate vacuously and Phase 3 presents the un-redacted text
   # inline. Sibling of `linear-fetch`'s `[ -n "$PERSIST_SAFE" ]`.
   [ -s "$DRAFT" ] || { echo "SOLEUR_LEGAL_GENERATE_HALT reason=draft-empty draft=[$DRAFT]"
          echo "legal-generate: the draft file is empty — nothing was scanned, so nothing is safe to share." >&2
          echo "  Write the draft into \"\$DRAFT\" in the SAME fence as this gate, then re-run." >&2
          echo "  An empty file is a failure, not a clean scan: the sentinel exits 0 on zero bytes." >&2
          exit 2; }
   bash "$SENTINEL" "$DRAFT"
   sentinel_rc=$?

   # Vendor-residue audit — MUST run in THIS fence: the trap above deletes
   # "$DRAFT" at block exit, so any audit in a later fence greps a dead path
   # and can never evaluate true. All greps print hits; any hit halts.
   residue=0
   grep -inE 'general[-.[:space:]]?legal' "$DRAFT" && residue=1            # vendor marks
   grep -inE 'attorney[- ]draft|prepared by[^.]{0,30}(attorney|law firm)|reviewed by[^.]{0,30}attorney' "$DRAFT" && residue=1  # credential-claim leakage
   grep -nE '<mark' "$DRAFT" && residue=1                                 # unfilled substrate slots
   grep -nP '\[[^\]]+\](?!\()' "$DRAFT" && residue=1                      # bare-bracket remnants (markdown links excluded)
   grep -nE 'OPTION [AB]|\[Select one|TEMPLATE ' "$DRAFT" && residue=1    # un-deleted decision constructs / scaffold rows
   # Advisory (cannot hard-halt): a legitimately chosen Option B carries
   # "DecisionLayer" text. Print hits for the operator; the OPTION/[Select
   # grep above already halts on an UNRESOLVED choice construct.
   grep -inE 'decisionlayer|decision science research' "$DRAFT" || true
   if (( residue )); then
     echo "SOLEUR_LEGAL_GENERATE_HALT reason=vendor-residue draft=[$DRAFT]"
     echo "legal-generate: the draft still carries substrate/vendor residue — see the lines above; do not present." >&2
     exit 2
   fi
   exit "$sentinel_rc"
   ```

   The engine is owned by the `incident` skill and shared cross-skill by relative reference (see ADR-095).

3. Dispatch on the exit code (fail-closed):
   - **exit 0 (clean)** — proceed to Phase 3 and present the draft.
   - **exit 1 (redaction needed)** — print the finding lines (each is meta-redacted; never the full token), revise/redact the offending context, regenerate, and re-run the gate until it exits 0. Do NOT present or write an un-cleared draft.
   - **exit 2 (cannot-evaluate)** — halt. The engine could not run (skill bug, `python3` absent, unreadable tmpfile). Do NOT present or write; surface the error.

No un-scanned draft ever crosses the transcript or lands on disk.

## Phase 3: Output

The vendor-residue audit ran inside the Phase 2.5 fence — `$DRAFT` is deleted by the trap when that block exits, so nothing here may re-grep it. If the fence exited 2 with `reason=vendor-residue`, do not present; report the residue lines it printed.

<decision_gate>

Present the generated document to the user. Use the **AskUserQuestion tool** with options:

- **Accept** -- Write to disk
- **Edit** -- Provide feedback to revise (return to Phase 2 with feedback)
- **Reject** -- Discard and exit

</decision_gate>

On acceptance, write the markdown file to the user-specified path or default `docs/legal/<type>.md` (e.g., `docs/legal/privacy-policy.md`).

Report: "Draft written to `<path>`. This document requires professional legal review before use."

## Important Guidelines

- All generated documents include mandatory DRAFT disclaimers -- do not remove them
- Gather context interactively every time -- do not assume context from previous sessions
- One document type per invocation -- to generate multiple types, run the skill multiple times
- Output format is markdown only -- Eleventy .njk wrapping is out of scope for this skill
- **But the mirror is NOT out of scope for the corpus.** `docs/legal/<doc>.md` is the canonical
  record; `plugins/soleur/docs/pages/legal/<doc>.md` is the surface users actually read at
  soleur.ai/legal/. Writing canonical only leaves the published site unchanged AND trips
  [lint-legal-mirror-drift-baseline.sh](../../../../scripts/lint-legal-mirror-drift-baseline.sh), which exits 2 on a document that exists on
  exactly one surface. After generating, create the mirror in the same commit.
- Five CI gates ride `docs/legal/**` (#7387). Reproduce locally before pushing:
  `bash scripts/lint-legal-scope-block-placement.sh --base origin/main` (added scope blocks:
  referent agreement, attachment, discharge -- run `--print-vocab` for the accepted phrasings)
  and `bash scripts/lint-legal-mirror-drift-baseline.sh --base origin/main` (canonical<->mirror
  drift ratchet). The other three are `check-tc-document-sha.sh` (re-pin `legal-doc-shas.ts`
  after any canonical edit), `legal-doc-consistency.test.ts`, and the `EXPECTED_COUNT` sentinel.
- If the user asks for a document type not in the supported list, suggest the closest match or explain that the type is not supported

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.