diff --git a/.github/ISSUE_TEMPLATE/claim-existing-seed.yml b/.github/ISSUE_TEMPLATE/claim-existing-seed.yml new file mode 100644 index 0000000..41d7057 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/claim-existing-seed.yml @@ -0,0 +1,59 @@ +name: Claim Existing Seed +description: Claim an existing seed use case to write the detailed analysis +title: "[Claim] SAFE-UC-____ " +labels: + - use-case + - claim +body: + - type: markdown + attributes: + value: | + **Use this form to claim an existing seed** (Status = Seed in the table). + This prevents duplicate work — once claimed, other contributors know + someone is actively writing the analysis. + + Browse available seeds: [README table](../../README.md#safe-use-case-id--naics-2022-crosswalk). + + - type: input + id: use_case_id + attributes: + label: SAFE Use Case ID + description: > + Copy the exact ID from the README table (e.g., SAFE-UC-0012). + Only IDs with Status = Seed are available for claiming. + placeholder: SAFE-UC-0012 + validations: + required: true + + - type: textarea + id: plan + attributes: + label: Write-up plan + description: > + Outline how you will expand the seed into a full analysis. Which sections + of the template will you complete? Do you have evidence sources lined up? + Rough timeline? This helps maintainers prioritize review. + placeholder: | + Sections I plan to cover: + - Workflow description with tool inventory and trust boundaries + - Kill-chain / failure analysis (at least 3 stages) + - SAFE-MCP technique mappings with concrete control recommendations + + Evidence sources: + - + - + + Timeline: ~2 weeks for draft PR + validations: + required: true + + - type: checkboxes + id: acknowledgements + attributes: + label: Acknowledgements + description: Both boxes are required before submitting. + options: + - label: I have read CONTRIBUTING.md safety and disclosure rules. + required: true + - label: I will not include sensitive information or exploit instructions. + required: true diff --git a/.github/ISSUE_TEMPLATE/propose-new-use-case.yml b/.github/ISSUE_TEMPLATE/propose-new-use-case.yml new file mode 100644 index 0000000..d08deab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/propose-new-use-case.yml @@ -0,0 +1,91 @@ +name: Propose New Use Case +description: Propose a new SAFE use case that is not yet listed in the registry +title: "[Proposal] " +labels: + - use-case + - proposal + - triage +body: + - type: markdown + attributes: + value: | + **What happens when you submit this form:** + 1. Automation assigns the next available `SAFE-UC-XXXX` ID and renames this issue. + 2. A maintainer reviews your proposal and comments `/accept` when approved. + 3. A scaffold PR is created with the seed README, registry entry, and index row — assigned to you. + + - type: input + id: title + attributes: + label: Use case title + description: > + A short, specific name for the agentic workflow — this becomes the heading + of the seed README and the row label in the registry table. + Use the pattern " + + ". + placeholder: "e.g., AI-assisted claims processing assistant" + validations: + required: true + + - type: textarea + id: summary + attributes: + label: Workflow summary + description: > + Describe what the agentic workflow does, who uses it, what tools/data it + touches, and why safe controls matter. This text is copied verbatim into + the seed README and the JSON registry, so write 3-6 clear sentences. + placeholder: | + This workflow allows to using . + The agent operates in and interacts with . + Key safety concerns include and . + validations: + required: true + + - type: dropdown + id: industry + attributes: + label: Industry + description: > + Select every industry this workflow applies to. This maps to NAICS 2022 + codes in the registry. Pick the closest match — you can refine later in + the seed README. + multiple: true + options: + - Retail & E-Commerce + - Finance & Insurance + - Healthcare + - Manufacturing + - Information & Software + - Transportation & Logistics + - Professional & Technical Services + - Administrative & Support Services + - Public Administration & Government + - Education + - Other + validations: + required: true + + - type: textarea + id: evidence + attributes: + label: Public evidence links + description: > + Paste at least 1 public URL that proves this workflow exists in the real + world (product docs, vendor announcements, news articles, research papers). + Per CONTRIBUTING.md, every use case must be grounded in public evidence. + placeholder: | + https://docs.example.com/ai-claims-assistant + https://www.example.com/blog/announcing-ai-claims + validations: + required: true + + - type: checkboxes + id: acknowledgements + attributes: + label: Acknowledgements + description: Both boxes are required before submitting. + options: + - label: I have read CONTRIBUTING.md safety and disclosure rules. + required: true + - label: I will not include sensitive information or exploit instructions. + required: true diff --git a/.github/ISSUE_TEMPLATE/use-case-claim.yml b/.github/ISSUE_TEMPLATE/use-case-claim.yml deleted file mode 100644 index 07efa4b..0000000 --- a/.github/ISSUE_TEMPLATE/use-case-claim.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Use Case Claim / Proposal -description: Claim an existing seed ID or propose a new SAFE use-case entry -title: "[Use Case] NEW - " -labels: - - use-case - - triage -body: - - type: markdown - attributes: - value: | - Use this form to coordinate contributions and avoid duplicate work. - - If you are using an existing `SAFE-UC-XXXX`, you can open a Draft PR directly and skip this issue form. - - For **Claim existing seed ID**: enter the exact `SAFE-UC-XXXX` from `README.md`. - - For **Propose new ID**: enter `new` and automation will assign the next available ID. - - - type: dropdown - id: request_type - attributes: - label: Request type - options: - - Claim existing seed ID - - Propose new ID - validations: - required: true - - - type: input - id: use_case_id - attributes: - label: SAFE Use Case ID - description: For existing claims use `SAFE-UC-XXXX` (e.g. `SAFE-UC-0012`). For new IDs enter `new`. - placeholder: SAFE-UC-0012 or new - validations: - required: true - - - type: input - id: title - attributes: - label: Proposed title - placeholder: Short, specific workflow title - validations: - required: true - - - type: textarea - id: summary - attributes: - label: Workflow summary - description: 3-6 sentences describing what the workflow does and why it matters. - validations: - required: true - - - type: textarea - id: naics - attributes: - label: NAICS 2022 mapping - description: List code + name pairs (one per line). - placeholder: | - 51 - Information - 513210 - Software Publishers - validations: - required: true - - - type: input - id: target_status - attributes: - label: Target status - description: Must use canonical values only. - placeholder: seed / draft / published - validations: - required: true - - - type: textarea - id: evidence - attributes: - label: Public evidence links - description: Provide at least 1 public reference link. - placeholder: | - - https://... - - https://... - validations: - required: true - - - type: textarea - id: notes - attributes: - label: Collaboration notes - description: Risks, assumptions, or help needed. - validations: - required: false - - - type: checkboxes - id: acknowledgements - attributes: - label: Acknowledgements - options: - - label: I have read CONTRIBUTING.md safety and disclosure rules. - required: true - - label: I will not include sensitive information or exploit instructions. - required: true diff --git a/.github/workflows/auto-seed.yml b/.github/workflows/auto-seed.yml new file mode 100644 index 0000000..7712aab --- /dev/null +++ b/.github/workflows/auto-seed.yml @@ -0,0 +1,311 @@ +name: Auto-seed Use Case + +on: + issues: + types: [opened] + issue_comment: + types: [created] + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + # ── Phase 1: Assign next SAFE-UC ID when a proposal issue is opened ─────── + assign-id: + name: Assign use case ID + runs-on: ubuntu-latest + if: >- + github.event_name == 'issues' && + contains(github.event.issue.labels.*.name, 'proposal') + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Assign next ID and update issue + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const body = context.payload.issue.body; + + // Parse a section from the GitHub issue form body + function parseSection(body, header) { + const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp( + `### ${escaped}\\s*\\n\\n([\\s\\S]*?)(?=\\n### |$)` + ); + const match = body.match(regex); + return match ? match[1].trim() : ''; + } + + const title = parseSection(body, 'Use case title'); + if (!title) { + core.setFailed('Could not parse use case title from issue body'); + return; + } + + // Read registry and compute next sequential ID + const registry = JSON.parse( + fs.readFileSync('use-cases.naics2022.crosswalk.json', 'utf8') + ); + const highest = Math.max( + ...registry.map(e => parseInt(e.id.replace('SAFE-UC-', ''), 10)) + ); + const nextId = `SAFE-UC-${String(highest + 1).padStart(4, '0')}`; + + const author = context.payload.issue.user.login; + const issueNumber = context.payload.issue.number; + + // Update issue title with assigned ID + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + title: `[${nextId}] ${title}`, + }); + + // Assign issue to the proposer + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + assignees: [author], + }); + + // Post confirmation comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: [ + `Assigned **${nextId}**.`, + '', + 'A maintainer will review this proposal.', + 'Once approved, a maintainer will comment `/accept` to scaffold the seed and create a PR.', + ].join('\n'), + }); + + # ── Phase 2: Scaffold seed + create PR when maintainer comments /accept ──── + scaffold: + name: Scaffold seed PR + runs-on: ubuntu-latest + if: >- + github.event_name == 'issue_comment' && + contains(github.event.comment.body, '/accept') && + contains(github.event.issue.labels.*.name, 'proposal') && + !contains(github.event.issue.labels.*.name, 'seeded') + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Verify maintainer permission + uses: actions/github-script@v7 + with: + script: | + const { data: perm } = + await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: context.payload.comment.user.login, + }); + const allowed = ['admin', 'maintain', 'write']; + if (!allowed.includes(perm.permission)) { + core.setFailed( + `User ${context.payload.comment.user.login} does not have write access` + ); + } + + - name: Parse issue + id: parse + uses: actions/github-script@v7 + with: + script: | + const body = context.payload.issue.body; + const issueTitle = context.payload.issue.title; + + // Extract SAFE-UC-XXXX from issue title (assigned in Phase 1) + const idMatch = issueTitle.match(/\[(SAFE-UC-\d{4})\]/); + if (!idMatch) { + core.setFailed( + 'No SAFE-UC ID found in issue title. Was Phase 1 (assign-id) run?' + ); + return; + } + + function parseSection(body, header) { + const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp( + `### ${escaped}\\s*\\n\\n([\\s\\S]*?)(?=\\n### |$)` + ); + const match = body.match(regex); + return match ? match[1].trim() : ''; + } + + const title = parseSection(body, 'Use case title'); + const summary = parseSection(body, 'Workflow summary'); + const industry = parseSection(body, 'Industry'); + const evidence = parseSection(body, 'Public evidence links'); + + if (!title || !summary || !industry || !evidence) { + core.setFailed('Missing required fields in issue body'); + return; + } + + // Map human-readable industry names → NAICS codes + const naicsMap = { + 'Retail & E-Commerce': { code: '44-45', name: 'Retail Trade' }, + 'Finance & Insurance': { code: '52', name: 'Finance and Insurance' }, + 'Healthcare': { code: '62', name: 'Health Care and Social Assistance' }, + 'Manufacturing': { code: '31-33', name: 'Manufacturing' }, + 'Information & Software': { code: '51', name: 'Information' }, + 'Transportation & Logistics': { code: '48-49', name: 'Transportation and Warehousing' }, + 'Professional & Technical Services': { code: '54', name: 'Professional, Scientific, and Technical Services' }, + 'Administrative & Support Services': { code: '56', name: 'Administrative and Support and Waste Management and Remediation Services' }, + 'Public Administration & Government': { code: '92', name: 'Public Administration' }, + 'Education': { code: '61', name: 'Educational Services' }, + 'Other': { code: '81', name: 'Other Services (except Public Administration)' }, + }; + + const naicsEntries = industry + .split(/[,\n]/) + .map(s => s.trim()) + .filter(s => s.length > 0) + .map(ind => naicsMap[ind]) + .filter(Boolean); + + if (naicsEntries.length === 0) { + core.setFailed('No valid industry selections found'); + return; + } + + core.setOutput('safe_uc_id', idMatch[1]); + core.setOutput('title', title); + core.setOutput('summary', summary); + core.setOutput('naics_json', JSON.stringify(naicsEntries)); + core.setOutput('evidence', evidence); + core.setOutput('author', context.payload.issue.user.login); + + - name: Run scaffold + env: + SAFE_UC_ID: ${{ steps.parse.outputs.safe_uc_id }} + TITLE: ${{ steps.parse.outputs.title }} + SUMMARY: ${{ steps.parse.outputs.summary }} + NAICS_JSON: ${{ steps.parse.outputs.naics_json }} + EVIDENCE: ${{ steps.parse.outputs.evidence }} + AUTHOR: ${{ steps.parse.outputs.author }} + run: | + chmod +x scripts/scaffold-use-case.sh + ./scripts/scaffold-use-case.sh \ + --id "$SAFE_UC_ID" \ + --title "$TITLE" \ + --summary "$SUMMARY" \ + --naics "$NAICS_JSON" \ + --evidence "$EVIDENCE" \ + --author "$AUTHOR" \ + --date "$(date +%Y-%m-%d)" + + - name: Create branch, commit, and push + id: branch + env: + SAFE_UC_ID: ${{ steps.parse.outputs.safe_uc_id }} + TITLE: ${{ steps.parse.outputs.title }} + run: | + SLUG=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//' | head -c 50) + BRANCH="seed/$(echo "${SAFE_UC_ID}" | tr '[:upper:]' '[:lower:]')-${SLUG}" + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$BRANCH" + git add -A + git commit -m "feat: seed ${SAFE_UC_ID} — ${TITLE}" + git push origin "$BRANCH" + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + + - name: Create PR and update issue + uses: actions/github-script@v7 + env: + SAFE_UC_ID: ${{ steps.parse.outputs.safe_uc_id }} + TITLE: ${{ steps.parse.outputs.title }} + SUMMARY: ${{ steps.parse.outputs.summary }} + AUTHOR: ${{ steps.parse.outputs.author }} + BRANCH: ${{ steps.branch.outputs.branch }} + with: + script: | + const safeUcId = process.env.SAFE_UC_ID; + const title = process.env.TITLE; + const summary = process.env.SUMMARY; + const author = process.env.AUTHOR; + const branch = process.env.BRANCH; + const issueNumber = context.payload.issue.number; + + // Create the pull request + const prBody = [ + `## Summary`, + ``, + `Scaffolded seed for **${safeUcId}** \u2014 ${title}.`, + ``, + summary, + ``, + `Closes #${issueNumber}`, + ``, + `## Changes`, + ``, + `- \`use-cases/${safeUcId}/README.md\` \u2014 seed page`, + `- \`use-cases.naics2022.crosswalk.json\` \u2014 registry entry`, + `- \`README.md\` \u2014 index table row`, + ``, + `---`, + `Auto-generated by the [auto-seed workflow](../actions/workflows/auto-seed.yml)`, + ].join('\n'); + + const { data: pr } = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `feat: seed ${safeUcId} \u2014 ${title}`, + head: branch, + base: 'main', + body: prBody, + }); + + // Assign PR to the proposer + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + assignees: [author], + }); + + // Comment on the issue with PR link + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: [ + `Scaffold PR created: ${pr.html_url}`, + ``, + `Review the PR and merge when ready.`, + ].join('\n'), + }); + + // Update labels: add seeded, remove triage + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + labels: ['seeded'], + }); + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + name: 'triage', + }); + } catch (e) { + // Label may not exist; ignore + } diff --git a/README.md b/README.md index decdb7f..f389a95 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,7 @@ Safety rules (non‑negotiable): **no sensitive info, no exploit instructions, g Quick contributor workflow: - Read [`CONTRIBUTING.md`](CONTRIBUTING.md) (source of truth). -- If using an existing `SAFE-UC-XXXX`, you can open a Draft PR directly. -- Claim/propose work via [Use Case Claim / Proposal](.github/ISSUE_TEMPLATE/use-case-claim.yml) (`new` auto-assigns the next SAFE-UC ID). +- Propose a new use case via [Propose New Use Case](.github/ISSUE_TEMPLATE/propose-new-use-case.yml) or claim a seed via [Claim Existing Seed](.github/ISSUE_TEMPLATE/claim-existing-seed.yml). - Ensure PR check **`Registry and Docs Validation`** passes. - Get required DSO signoff before merge (details in `CONTRIBUTING.md`). @@ -93,6 +92,8 @@ Quick contributor workflow: | [SAFE-UC-0032](use-cases/SAFE-UC-0032/) | Agentic orchestration for marketplace embedded lending | [Finance and Insurance (52)][naics-52]
[Other Activities Related to Credit Intermediation (522390)][naics-522390]
[Electronic Shopping and Mail-Order Houses (454110)][naics-454110] | Draft | | [SAFE-UC-0033](use-cases/SAFE-UC-0033/) | Skill-driven web app regression testing assistant for pull requests | [Information (51)][naics-51]
[Software Publishers (513210)][naics-513210] | Draft | +> **Don't see your use case?** [Propose a new one](https://github.com/safe-agentic-framework/safe-agentic-use-cases/issues/new?template=propose-new-use-case.yml) — automation assigns the next ID, scaffolds the seed, and opens a PR for you. + --- ### NAICS links (official U.S. Census Bureau) diff --git a/scripts/scaffold-use-case.sh b/scripts/scaffold-use-case.sh new file mode 100755 index 0000000..9215fda --- /dev/null +++ b/scripts/scaffold-use-case.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash +set -euo pipefail + +############################################################################### +# scaffold-use-case.sh — Create a seed use case (folder, README, registry, index) +# +# Usage: +# ./scripts/scaffold-use-case.sh \ +# --id SAFE-UC-0034 \ +# --title "Supply chain prediction assistant" \ +# --summary "Predict supply chain disruptions ..." \ +# --naics '[{"code":"48-49","name":"Transportation and Warehousing"}]' \ +# --evidence "https://example.com/1\nhttps://example.com/2" \ +# --author "octocat" \ +# --date "2026-02-21" +############################################################################### + +# ── Parse arguments ────────────────────────────────────────────────────────── + +SAFE_UC_ID="" +TITLE="" +SUMMARY="" +NAICS_JSON="" +EVIDENCE="" +AUTHOR="" +DATE="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --id) SAFE_UC_ID="$2"; shift 2 ;; + --title) TITLE="$2"; shift 2 ;; + --summary) SUMMARY="$2"; shift 2 ;; + --naics) NAICS_JSON="$2"; shift 2 ;; + --evidence) EVIDENCE="$2"; shift 2 ;; + --author) AUTHOR="$2"; shift 2 ;; + --date) DATE="$2"; shift 2 ;; + *) echo "Unknown argument: $1" >&2; exit 1 ;; + esac +done + +# ── Validate inputs ────────────────────────────────────────────────────────── + +: "${SAFE_UC_ID:?Missing --id}" +: "${TITLE:?Missing --title}" +: "${SUMMARY:?Missing --summary}" +: "${NAICS_JSON:?Missing --naics}" +: "${EVIDENCE:?Missing --evidence}" +: "${AUTHOR:?Missing --author}" +: "${DATE:?Missing --date}" + +[[ "$SAFE_UC_ID" =~ ^SAFE-UC-[0-9]{4}$ ]] || { echo "Invalid ID format: $SAFE_UC_ID" >&2; exit 1; } +echo "$NAICS_JSON" | jq -e 'type == "array" and length > 0' >/dev/null 2>&1 || { echo "Invalid NAICS JSON" >&2; exit 1; } + +# ── Setup paths ────────────────────────────────────────────────────────────── + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +REGISTRY_FILE="use-cases.naics2022.crosswalk.json" +README_FILE="README.md" +UC_DIR="use-cases/${SAFE_UC_ID}" + +if [[ -d "$UC_DIR" ]]; then + echo "Directory already exists: $UC_DIR" >&2 + exit 1 +fi + +# ── Backup originals for rollback ──────────────────────────────────────────── + +cp "$REGISTRY_FILE" "${REGISTRY_FILE}.bak" +cp "$README_FILE" "${README_FILE}.bak" + +rollback() { + echo "Rolling back changes..." >&2 + mv -f "${REGISTRY_FILE}.bak" "$REGISTRY_FILE" + mv -f "${README_FILE}.bak" "$README_FILE" + rm -rf "$UC_DIR" +} +trap rollback ERR + +# ── 1. Create directory ───────────────────────────────────────────────────── + +mkdir -p "$UC_DIR" + +# ── 2. Build NAICS display string for seed README metadata ─────────────────── + +NAICS_DISPLAY="" +NAICS_COUNT=$(echo "$NAICS_JSON" | jq 'length') +for i in $(seq 0 $((NAICS_COUNT - 1))); do + CODE=$(echo "$NAICS_JSON" | jq -r ".[$i].code") + NAME=$(echo "$NAICS_JSON" | jq -r ".[$i].name") + if [[ -n "$NAICS_DISPLAY" ]]; then + NAICS_DISPLAY="${NAICS_DISPLAY}, " + fi + NAICS_DISPLAY="${NAICS_DISPLAY}${NAME} (${CODE})" +done + +# ── 3. Build evidence bullet list ─────────────────────────────────────────── + +EVIDENCE_LINES="" +while IFS= read -r line; do + line="$(echo "$line" | sed 's/^[[:space:]]*-[[:space:]]*//' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')" + [[ -z "$line" ]] && continue + EVIDENCE_LINES="${EVIDENCE_LINES} +- ${line}" +done <<< "$EVIDENCE" +# Remove leading newline +EVIDENCE_LINES="${EVIDENCE_LINES#$'\n'}" + +# ── 4. Create seed README ─────────────────────────────────────────────────── + +cat > "${UC_DIR}/README.md" << SEEDEOF +# ${TITLE} + +> Seed page for **SAFE-AUCA**. Expand this into a full analysis using [\`templates/use-case-template.md\`](../../templates/use-case-template.md). + +## Metadata + +| Field | Value | +|---|---| +| **SAFE Use Case ID** | \`${SAFE_UC_ID}\` | +| **Status** | \`seed\` | +| **NAICS 2022** | \`${NAICS_DISPLAY}\` | +| **Last updated** | \`${DATE}\` | + +### Evidence (public links) + +${EVIDENCE_LINES} + +## Workflow Description (Seed) + +${SUMMARY} + +## In Scope / Out Of Scope + +- **In scope:** TBD +- **Out of scope:** TBD + +## SAFE-MCP Mapping (Seed Skeleton) + +| Kill-chain stage | Failure/attack pattern | SAFE-MCP technique(s) | Recommended controls | Tests | +|---|---|---|---|---| +| TBD | TBD | TBD | TBD | TBD | + +## Next Steps + +- Expand this page to \`draft\` using the full template in \`templates/use-case-template.md\`. +- Add public evidence links and concrete control/test mappings. +SEEDEOF + +# ── 5. Append to registry JSON ────────────────────────────────────────────── + +REGISTRY_ENTRY=$(jq -n \ + --arg id "$SAFE_UC_ID" \ + --arg title "$TITLE" \ + --arg repo_path "use-cases/${SAFE_UC_ID}/README.md" \ + --argjson naics "$NAICS_JSON" \ + --arg summary "$SUMMARY" \ + '{id: $id, title: $title, status: "seed", repo_path: $repo_path, naics_2022: $naics, summary: $summary}') + +jq --argjson entry "$REGISTRY_ENTRY" '. += [$entry]' "$REGISTRY_FILE" > "${REGISTRY_FILE}.tmp" +mv "${REGISTRY_FILE}.tmp" "$REGISTRY_FILE" + +# ── 6. Insert row in README.md index table ─────────────────────────────────── + +# Build NAICS column (with en-dash for combined sectors, reference-style links) +NAICS_TABLE_COL="" +for i in $(seq 0 $((NAICS_COUNT - 1))); do + CODE=$(echo "$NAICS_JSON" | jq -r ".[$i].code") + NAME=$(echo "$NAICS_JSON" | jq -r ".[$i].name") + # Use en-dash (–) for display of combined sector codes like 44-45 → 44–45 + DISPLAY_CODE=$(echo "$CODE" | sed 's/-/–/') + SLUG="naics-${CODE}" + if [[ -n "$NAICS_TABLE_COL" ]]; then + NAICS_TABLE_COL="${NAICS_TABLE_COL}
" + fi + NAICS_TABLE_COL="${NAICS_TABLE_COL}[${NAME} (${DISPLAY_CODE})][${SLUG}]" +done + +TABLE_ROW="| [${SAFE_UC_ID}](use-cases/${SAFE_UC_ID}/) | ${TITLE} | ${NAICS_TABLE_COL} | Seed |" + +# Insert the new row after the last existing table row +awk -v row="$TABLE_ROW" ' + /^\| \[SAFE-UC-[0-9][0-9][0-9][0-9]\]\(/ { last_line = NR } + { lines[NR] = $0 } + END { + for (i = 1; i <= NR; i++) { + print lines[i] + if (i == last_line) { + print row + } + } + } +' "$README_FILE" > "${README_FILE}.tmp" +mv "${README_FILE}.tmp" "$README_FILE" + +# ── 7. Add NAICS reference link definitions if missing ─────────────────────── + +for i in $(seq 0 $((NAICS_COUNT - 1))); do + CODE=$(echo "$NAICS_JSON" | jq -r ".[$i].code") + SLUG="naics-${CODE}" + + if ! grep -q "^\[${SLUG}\]:" "$README_FILE"; then + # Determine Census Bureau URL based on code format + case "$CODE" in + *-*) URL="https://www.census.gov/data/tables/2022/econ/economic-census/naics-sector-${CODE}.html" ;; + *) URL="https://www.census.gov/naics/?chart=2022&details=${CODE}&input=${CODE}" ;; + esac + echo "[${SLUG}]: ${URL}" >> "$README_FILE" + fi +done + +# ── 8. Clean up backups (success path) ────────────────────────────────────── + +trap - ERR +rm -f "${REGISTRY_FILE}.bak" "${README_FILE}.bak" + +echo "Scaffold complete for ${SAFE_UC_ID}: ${TITLE}"