From 22b68bf34e6bb645385a28b64a0e4f29703a4e4b Mon Sep 17 00:00:00 2001 From: Selcuk Atli Date: Fri, 2 Oct 2026 16:48:16 -0400 Subject: [PATCH] Add another petition workspace to an existing case scaffold-case.sh --add writes only the missing petition directory, leaving resources/, case-overview.md and existing petition workspaces untouched. Covered by scripts/test-scaffold.sh, now part of npm test and CI. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/validate.yml | 2 + README.md | 2 +- commands/new-case.md | 3 + package.json | 3 +- scripts/test-scaffold.sh | 55 +++++++++++++++++++ skills/petition-workspace/SKILL.md | 13 +++++ .../scripts/scaffold-case.sh | 42 ++++++++++++-- 7 files changed, 113 insertions(+), 7 deletions(-) create mode 100755 scripts/test-scaffold.sh diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 39d10eb..5a42af6 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -27,6 +27,8 @@ jobs: test -d /tmp/case/resources/speaking/showcase-demo node skills/petition-portal/scripts/build-portal.mjs /tmp/case -o /tmp/packet.html test -s /tmp/packet.html + - name: Add another petition workspace to an existing case + run: bash scripts/test-scaffold.sh - name: Similarity check catches near-duplicate letters run: | mkdir -p /tmp/l diff --git a/README.md b/README.md index c5c0b37..8167079 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ wrappers over the skills; the logic lives in the skills themselves. | Command | | |---|---| -| `/new-case [o1a\|eb1a\|both]` | Scaffold a case workspace | +| `/new-case [o1a\|eb1a\|both]` | Scaffold a case workspace, or add another petition type to an existing one | | `/triage` | Interview-driven criteria feasibility assessment | | `/status` | Stage, blockers, and prioritized next actions | | `/add-evidence` | File a document and record it in one step | diff --git a/commands/new-case.md b/commands/new-case.md index 9942bfe..1733fca 100644 --- a/commands/new-case.md +++ b/commands/new-case.md @@ -10,6 +10,9 @@ Arguments: `$ARGUMENTS` — expected as ` [o1a|eb1a|both]`, defaulti 2. Run its scaffold script: `${CLAUDE_PLUGIN_ROOT}/skills/petition-workspace/scripts/scaffold-case.sh ` It refuses to write into a non-empty directory — do not work around that; ask the user. + If the directory is already a case and the user wants another petition type alongside + it, use `scaffold-case.sh --add ` instead — it adds only the new + petition workspace and leaves everything else untouched. 3. Then help fill in `case-overview.md`, starting with the field of endeavor. Push for a narrow, specific field statement; it is the highest-leverage line in the whole case. 4. Remind the user once that this workspace will hold sensitive records and should not go diff --git a/package.json b/package.json index 00b8557..6feb7a1 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "scripts": { "validate": "node scripts/validate-skills.mjs", "check-pii": "node scripts/check-pii.mjs", - "test": "npm run validate && npm run check-pii" + "test": "npm run validate && npm run check-pii && npm run test-scaffold", + "test-scaffold": "bash scripts/test-scaffold.sh" }, "devDependencies": { "gray-matter": "^4.0.3", diff --git a/scripts/test-scaffold.sh b/scripts/test-scaffold.sh new file mode 100755 index 0000000..4fc444d --- /dev/null +++ b/scripts/test-scaffold.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Exercise scaffold-case.sh end to end: fresh scaffold, and --add for another +# petition workspace on an existing case. +# Run: bash scripts/test-scaffold.sh +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SCAFFOLD="$ROOT/skills/petition-workspace/scripts/scaffold-case.sh" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +fails=0 + +ok() { echo " ok $1"; } +fail() { echo " FAIL $1" >&2; fails=$((fails + 1)); } +check() { local name="$1"; shift; if "$@" >/dev/null 2>&1; then ok "$name"; else fail "$name"; fi; } +refuses() { local name="$1"; shift; if "$@" >/dev/null 2>&1; then fail "$name"; else ok "$name"; fi; } +criteria() { grep -c '^### Criterion' "$1"; } + +echo "scaffold-case.sh" + +case="$TMP/case" +"$SCAFFOLD" "$case" o1a >/dev/null +check "fresh o1a scaffold has 8 criteria" test "$(criteria "$case/o1a/evidence-list.md")" = 8 +check "fresh o1a scaffold has no eb1a/" test ! -e "$case/eb1a" +refuses "plain scaffold still refuses a non-empty directory" "$SCAFFOLD" "$case" eb1a + +# Simulate a case in progress: user edits that --add must not touch. +echo "Field of endeavor: industrial robotics" >> "$case/case-overview.md" +echo "| | Org chart | [x] collected | resources/company-docs/org-chart.png |" >> "$case/o1a/evidence-list.md" +mkdir -p "$case/resources/awards/meridian" && echo x > "$case/resources/awards/meridian/cert.pdf" +before="$(cd "$case" && find case-overview.md README.md .gitignore resources o1a -type f -exec shasum {} + | sort)" + +"$SCAFFOLD" --add "$case" eb1a >/dev/null +check "--add eb1a creates eb1a/ with 10 criteria" test "$(criteria "$case/eb1a/evidence-list.md")" = 10 +check "--add eb1a writes 10 criterion argument files" test "$(ls "$case/eb1a/evidence-by-criterion" | wc -l | tr -d ' ')" = 10 +check "--add eb1a writes overview and narrative" test -s "$case/eb1a/overview.md" -a -s "$case/eb1a/petition-narrative.md" +after="$(cd "$case" && find case-overview.md README.md .gitignore resources o1a -type f -exec shasum {} + | sort)" +check "--add leaves existing case files untouched" test "$before" = "$after" + +refuses "--add refuses an existing petition workspace" "$SCAFFOLD" --add "$case" eb1a +refuses "--add both refuses when either already exists" "$SCAFFOLD" --add "$case" both +refuses "--add refuses a directory that is not a case" "$SCAFFOLD" --add "$TMP/nope" o1a +refuses "--add requires a petition type" "$SCAFFOLD" --add "$case" +refuses "--add rejects an unknown petition type" "$SCAFFOLD" --add "$case" h1b + +# The added workspace must work with the rest of the toolchain. +printf 'x' > "$TMP/f.png" +check "add-evidence files into the added workspace" \ + node "$ROOT/skills/petition-workspace/scripts/add-evidence.mjs" "$case" -p eb1a -c 7 -d "Showcase demo" "$TMP/f.png" +check "added workspace's evidence-list records the exhibit" grep -q "Showcase demo" "$case/eb1a/evidence-list.md" +check "review packet builds over both workspaces" \ + node "$ROOT/skills/petition-portal/scripts/build-portal.mjs" "$case" -o "$TMP/packet.html" + +if [ "$fails" -gt 0 ]; then echo "$fails failure(s)" >&2; exit 1; fi +echo "test-scaffold: all checks passed" diff --git a/skills/petition-workspace/SKILL.md b/skills/petition-workspace/SKILL.md index 578a26f..33a4fdf 100644 --- a/skills/petition-workspace/SKILL.md +++ b/skills/petition-workspace/SKILL.md @@ -17,6 +17,19 @@ scripts/scaffold-case.sh [o1a|eb1a|both] Refuses to write into a non-empty directory. Creates `resources/` for raw material, one directory per petition type, `communications/`, and `case-overview.md`. +## Add another petition workspace to an existing case + +```bash +scripts/scaffold-case.sh --add +``` + +For a case that started as an O-1A and is now also pursuing an EB-1A (or the reverse). +Writes only the new petition directory — `resources/`, `case-overview.md`, and any +existing petition directory are left untouched. Refuses if the directory is not a case +(no `case-overview.md`) or the petition directory already exists. Afterwards, update +*Petition type(s)* in `case-overview.md`; the new workspace numbers its exhibits +independently and draws on the same shared `resources/`. + ## File evidence with the tool, not by hand ```bash diff --git a/skills/petition-workspace/scripts/scaffold-case.sh b/skills/petition-workspace/scripts/scaffold-case.sh index da13823..ee34d47 100755 --- a/skills/petition-workspace/scripts/scaffold-case.sh +++ b/skills/petition-workspace/scripts/scaffold-case.sh @@ -1,21 +1,34 @@ #!/usr/bin/env bash # Scaffold a petition case workspace. # Usage: scaffold-case.sh [o1a|eb1a|both] +# scaffold-case.sh --add +# +# --add adds another petition workspace to an existing case (an O-1A case later +# pursuing an EB-1A). It writes only the missing petition directories and never +# touches resources/, case-overview.md, or a petition directory that already exists. set -euo pipefail -TARGET="${1:?usage: scaffold-case.sh [o1a|eb1a|both]}" -TYPE="${2:-o1a}" +USAGE="usage: scaffold-case.sh [o1a|eb1a|both] | scaffold-case.sh --add " +ADD=0 +if [ "${1:-}" = "--add" ]; then ADD=1; shift; fi + +TARGET="${1:?$USAGE}" +if [ "$ADD" = 1 ]; then TYPE="${2:?$USAGE}"; else TYPE="${2:-o1a}"; fi TPL="$(cd "$(dirname "$0")/../templates/case" && pwd)" case "$TYPE" in o1a|eb1a|both) ;; *) echo "type must be o1a, eb1a, or both" >&2; exit 2 ;; esac -if [ -e "$TARGET" ] && [ -n "$(ls -A "$TARGET" 2>/dev/null)" ]; then +if [ "$ADD" = 1 ]; then + if [ ! -f "$TARGET/case-overview.md" ]; then + echo "not a case workspace (no case-overview.md): $TARGET — scaffold it without --add" >&2 + exit 1 + fi +elif [ -e "$TARGET" ] && [ -n "$(ls -A "$TARGET" 2>/dev/null)" ]; then echo "refusing to scaffold into non-empty directory: $TARGET" >&2 + echo "to add another petition workspace to an existing case: scaffold-case.sh --add $TARGET " >&2 exit 1 fi -mkdir -p "$TARGET"/{resources/{awards,membership,press,judging,patents,publications,compensation,company-docs,recommendation-letters,education,speaking,photos},communications/{attorneys,recommenders},reference} - petitions=() case "$TYPE" in o1a) petitions=(o1a) ;; @@ -23,6 +36,18 @@ case "$TYPE" in both) petitions=(o1a eb1a) ;; esac +if [ "$ADD" = 1 ]; then + for p in "${petitions[@]}"; do + if [ -e "$TARGET/$p" ]; then + echo "refusing to overwrite existing petition workspace: $TARGET/$p" >&2 + exit 1 + fi + done +fi + +# Idempotent: on --add this only fills in shared folders an older case is missing. +mkdir -p "$TARGET"/{resources/{awards,membership,press,judging,patents,publications,compensation,company-docs,recommendation-letters,education,speaking,photos},communications/{attorneys,recommenders},reference} + # Criterion names per petition type. O-1A has 8; EB-1A has 10, and the numbering # diverges from criterion 7 onward — do not share one list between them. o1a_criteria=( @@ -65,6 +90,13 @@ for p in "${petitions[@]}"; do "$(echo "$p" | tr '[:lower:]' '[:upper:]')" > "$dir/petition-narrative.md" done +if [ "$ADD" = 1 ]; then + echo "added ${petitions[*]} petition workspace to case at $TARGET" + echo "update 'Petition type(s)' in case-overview.md; resources/ is shared, exhibit numbering is independent" + for p in "${petitions[@]}"; do find "$TARGET/$p" -type f | sort | sed "s|^$TARGET|.|"; done + exit 0 +fi + cat > "$TARGET/case-overview.md" <<'EOF' # Case Overview