joycraft 0.5.8 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-A2CQG5J5.js → chunk-Y6GBN6R4.js} +178 -1
- package/dist/chunk-Y6GBN6R4.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/{init-QXG5BT4Y.js → init-B7MYPB7Z.js} +2 -2
- package/dist/{init-autofix-Y5DQOFEU.js → init-autofix-FKZN5S3R.js} +2 -2
- package/dist/{upgrade-VUOSXPR5.js → upgrade-WXNR3ZNI.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-A2CQG5J5.js.map +0 -1
- /package/dist/{init-QXG5BT4Y.js.map → init-B7MYPB7Z.js.map} +0 -0
- /package/dist/{init-autofix-Y5DQOFEU.js.map → init-autofix-FKZN5S3R.js.map} +0 -0
- /package/dist/{upgrade-VUOSXPR5.js.map → upgrade-WXNR3ZNI.js.map} +0 -0
|
@@ -1493,6 +1493,183 @@ Based on the verdict:
|
|
|
1493
1493
|
| Subagent can't run tests (missing deps) | Report the error as FAIL evidence |
|
|
1494
1494
|
| No specs found and no path given | Tell user to provide a spec path or create a spec first |
|
|
1495
1495
|
| Spec status is "Complete" | Still run verification -- "Complete" means the implementer thinks it's done, verification confirms |
|
|
1496
|
+
`,
|
|
1497
|
+
"joycraft-bugfix.md": `---
|
|
1498
|
+
name: joycraft-bugfix
|
|
1499
|
+
description: Structured bug fix workflow \u2014 triage, diagnose, discuss with user, write a focused spec, hand off for implementation
|
|
1500
|
+
---
|
|
1501
|
+
|
|
1502
|
+
# Bug Fix Workflow
|
|
1503
|
+
|
|
1504
|
+
You are fixing a bug. Follow this process in order. Do not skip steps.
|
|
1505
|
+
|
|
1506
|
+
**Guard clause:** If the user's request is clearly a new feature \u2014 not a bug, error, or unexpected behavior \u2014 say:
|
|
1507
|
+
"This sounds like a new feature rather than a bug fix. Try \`/joycraft-new-feature\` for a guided feature workflow."
|
|
1508
|
+
Then stop.
|
|
1509
|
+
|
|
1510
|
+
---
|
|
1511
|
+
|
|
1512
|
+
## Phase 1: Triage
|
|
1513
|
+
|
|
1514
|
+
Establish what's broken. Your goal is to reproduce the bug or at minimum understand the symptom clearly.
|
|
1515
|
+
|
|
1516
|
+
**Ask / gather:**
|
|
1517
|
+
- What is the symptom? (error message, unexpected behavior, crash, wrong output)
|
|
1518
|
+
- What are the steps to reproduce?
|
|
1519
|
+
- What is the expected behavior vs. actual behavior?
|
|
1520
|
+
- When did it start? (recent change, always been this way, intermittent)
|
|
1521
|
+
- Any relevant logs, screenshots, or error output?
|
|
1522
|
+
|
|
1523
|
+
**Actions:**
|
|
1524
|
+
- If the user provides an error message or stack trace, read the referenced files immediately
|
|
1525
|
+
- If steps to reproduce are provided, try to reproduce the bug (run the failing command, test, or request)
|
|
1526
|
+
- If the bug is intermittent or hard to reproduce, gather more context: environment, OS, versions, config
|
|
1527
|
+
|
|
1528
|
+
**Done when:** You can describe the symptom in one sentence and have either reproduced it or have enough context to diagnose without reproduction.
|
|
1529
|
+
|
|
1530
|
+
---
|
|
1531
|
+
|
|
1532
|
+
## Phase 2: Diagnose
|
|
1533
|
+
|
|
1534
|
+
Find the root cause. Read code, trace the execution path, identify what's wrong and why.
|
|
1535
|
+
|
|
1536
|
+
**Actions:**
|
|
1537
|
+
- Start from the error site (stack trace, failing test, broken UI) and trace backward
|
|
1538
|
+
- Read the relevant source files \u2014 don't guess based on file names alone
|
|
1539
|
+
- Identify the specific line(s), condition, or logic error causing the bug
|
|
1540
|
+
- Check git blame or recent commits if the bug was introduced by a recent change
|
|
1541
|
+
- Look for related bugs \u2014 is this a symptom of a deeper issue?
|
|
1542
|
+
|
|
1543
|
+
**Done when:** You can explain the root cause in 2-3 sentences: what's wrong, why it's wrong, and where in the code it happens.
|
|
1544
|
+
|
|
1545
|
+
---
|
|
1546
|
+
|
|
1547
|
+
## Phase 3: Discuss
|
|
1548
|
+
|
|
1549
|
+
Present your findings to the user. Do NOT start writing code or a spec yet.
|
|
1550
|
+
|
|
1551
|
+
**Present:**
|
|
1552
|
+
1. **Symptom:** What the user sees (confirm your understanding matches theirs)
|
|
1553
|
+
2. **Root cause:** What's actually wrong in the code and why
|
|
1554
|
+
3. **Proposed fix:** What you think the fix is \u2014 be specific (which files, what changes)
|
|
1555
|
+
4. **Risk assessment:** What could go wrong with this fix? Any side effects?
|
|
1556
|
+
5. **Scope check:** Is this a simple fix or does it touch multiple systems?
|
|
1557
|
+
|
|
1558
|
+
**Ask:**
|
|
1559
|
+
- "Does this match what you're seeing?"
|
|
1560
|
+
- "Are you comfortable with this approach, or do you want to explore alternatives?"
|
|
1561
|
+
- If the fix is large or risky: "Should we decompose this into smaller specs?"
|
|
1562
|
+
|
|
1563
|
+
**Done when:** The user agrees with the diagnosis and proposed fix direction.
|
|
1564
|
+
|
|
1565
|
+
---
|
|
1566
|
+
|
|
1567
|
+
## Phase 4: Spec the Fix
|
|
1568
|
+
|
|
1569
|
+
Write a bug fix spec to \`docs/specs/YYYY-MM-DD-bugfix-name.md\`. Create the \`docs/specs/\` directory if it doesn't exist.
|
|
1570
|
+
|
|
1571
|
+
**Why:** Even bug fixes deserve a spec. It forces clarity on what "fixed" means, ensures test-first discipline, and creates a traceable record of the fix.
|
|
1572
|
+
|
|
1573
|
+
Use this template:
|
|
1574
|
+
|
|
1575
|
+
\`\`\`markdown
|
|
1576
|
+
# Fix [Bug Description] \u2014 Bug Fix Spec
|
|
1577
|
+
|
|
1578
|
+
> **Parent Brief:** none (bug fix)
|
|
1579
|
+
> **Issue/Error:** [error message, issue link, or symptom description]
|
|
1580
|
+
> **Status:** Ready
|
|
1581
|
+
> **Date:** YYYY-MM-DD
|
|
1582
|
+
> **Estimated scope:** [1 session / N files / ~N lines]
|
|
1583
|
+
|
|
1584
|
+
---
|
|
1585
|
+
|
|
1586
|
+
## Bug
|
|
1587
|
+
|
|
1588
|
+
What is broken? Describe the symptom the user experiences.
|
|
1589
|
+
|
|
1590
|
+
## Root Cause
|
|
1591
|
+
|
|
1592
|
+
What is wrong in the code and why? Name the specific file(s) and line(s).
|
|
1593
|
+
|
|
1594
|
+
## Fix
|
|
1595
|
+
|
|
1596
|
+
What changes will fix this? Be specific \u2014 describe the code change, not just "fix the bug."
|
|
1597
|
+
|
|
1598
|
+
## Acceptance Criteria
|
|
1599
|
+
|
|
1600
|
+
- [ ] [The bug no longer occurs \u2014 describe the correct behavior]
|
|
1601
|
+
- [ ] [No regressions in related functionality]
|
|
1602
|
+
- [ ] Build passes
|
|
1603
|
+
- [ ] Tests pass
|
|
1604
|
+
|
|
1605
|
+
## Test Plan
|
|
1606
|
+
|
|
1607
|
+
| Acceptance Criterion | Test | Type |
|
|
1608
|
+
|---------------------|------|------|
|
|
1609
|
+
| [Bug no longer occurs] | [Test that reproduces the bug, then verifies the fix] | [unit/integration/e2e] |
|
|
1610
|
+
| [No regressions] | [Existing tests still pass, or new regression test] | [unit/integration] |
|
|
1611
|
+
|
|
1612
|
+
**Execution order:**
|
|
1613
|
+
1. Write a test that reproduces the bug \u2014 it should FAIL (red)
|
|
1614
|
+
2. Run the test to confirm it fails
|
|
1615
|
+
3. Apply the fix
|
|
1616
|
+
4. Run the test to confirm it passes (green)
|
|
1617
|
+
5. Run the full test suite to check for regressions
|
|
1618
|
+
|
|
1619
|
+
**Smoke test:** [The bug reproduction test \u2014 fastest way to verify the fix works]
|
|
1620
|
+
|
|
1621
|
+
**Before implementing, verify your test harness:**
|
|
1622
|
+
1. Run the reproduction test \u2014 it must FAIL (if it passes, you're not testing the actual bug)
|
|
1623
|
+
2. The test must exercise your actual code \u2014 not a reimplementation or mock
|
|
1624
|
+
3. Identify your smoke test \u2014 it must run in seconds, not minutes
|
|
1625
|
+
|
|
1626
|
+
## Constraints
|
|
1627
|
+
|
|
1628
|
+
- MUST: [any hard requirements for the fix]
|
|
1629
|
+
- MUST NOT: [any prohibitions \u2014 e.g., don't change the public API]
|
|
1630
|
+
|
|
1631
|
+
## Affected Files
|
|
1632
|
+
|
|
1633
|
+
| Action | File | What Changes |
|
|
1634
|
+
|--------|------|-------------|
|
|
1635
|
+
|
|
1636
|
+
## Edge Cases
|
|
1637
|
+
|
|
1638
|
+
| Scenario | Expected Behavior |
|
|
1639
|
+
|----------|------------------|
|
|
1640
|
+
\`\`\`
|
|
1641
|
+
|
|
1642
|
+
**For trivial bugs:** The spec will be short. That's fine \u2014 the structure is the point, not the length.
|
|
1643
|
+
|
|
1644
|
+
**For large bugs that span multiple files/systems:** Consider whether this should be decomposed into multiple specs. If so, create a brief first using \`/joycraft-new-feature\`, then decompose. A bug fix spec should be implementable in a single session.
|
|
1645
|
+
|
|
1646
|
+
---
|
|
1647
|
+
|
|
1648
|
+
## Phase 5: Hand Off
|
|
1649
|
+
|
|
1650
|
+
Tell the user:
|
|
1651
|
+
|
|
1652
|
+
\`\`\`
|
|
1653
|
+
Bug fix spec is ready: docs/specs/YYYY-MM-DD-bugfix-name.md
|
|
1654
|
+
|
|
1655
|
+
Summary:
|
|
1656
|
+
- Bug: [one sentence]
|
|
1657
|
+
- Root cause: [one sentence]
|
|
1658
|
+
- Fix: [one sentence]
|
|
1659
|
+
- Estimated: 1 session
|
|
1660
|
+
|
|
1661
|
+
To execute: Start a fresh session and:
|
|
1662
|
+
1. Read the spec
|
|
1663
|
+
2. Write the reproduction test (must fail)
|
|
1664
|
+
3. Apply the fix (test must pass)
|
|
1665
|
+
4. Run full test suite
|
|
1666
|
+
5. Run /joycraft-session-end to capture discoveries
|
|
1667
|
+
6. Commit and PR
|
|
1668
|
+
|
|
1669
|
+
Ready to start?
|
|
1670
|
+
\`\`\`
|
|
1671
|
+
|
|
1672
|
+
**Why:** A fresh session for implementation produces better results. This diagnostic session has context noise from exploration \u2014 a clean session with just the spec is more focused.
|
|
1496
1673
|
`
|
|
1497
1674
|
};
|
|
1498
1675
|
var TEMPLATES = {
|
|
@@ -3295,4 +3472,4 @@ export {
|
|
|
3295
3472
|
SKILLS,
|
|
3296
3473
|
TEMPLATES
|
|
3297
3474
|
};
|
|
3298
|
-
//# sourceMappingURL=chunk-
|
|
3475
|
+
//# sourceMappingURL=chunk-Y6GBN6R4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bundled-files.ts"],"sourcesContent":["// Bundled file contents — embedded at build time\n\nexport const SKILLS: Record<string, string> = {\n \"joycraft-decompose.md\": `---\nname: joycraft-decompose\ndescription: Break a feature brief into atomic specs — small, testable, independently executable units\n---\n\n# Decompose Feature into Atomic Specs\n\nYou have a Feature Brief (or the user has described a feature). Your job is to decompose it into atomic specs that can be executed independently — one spec per session.\n\n## Step 1: Verify the Brief Exists\n\nLook for a Feature Brief in \\`docs/briefs/\\`. If one doesn't exist yet, tell the user:\n\n> No feature brief found. Run \\`/joycraft-new-feature\\` first to interview and create one, or describe the feature now and I'll work from your description.\n\nIf the user describes the feature inline, work from that description directly. You don't need a formal brief to decompose — but recommend creating one for complex features.\n\n## Step 2: Identify Natural Boundaries\n\n**Why:** Good boundaries make specs independently testable and committable. Bad boundaries create specs that can't be verified without other specs also being done.\n\nRead the brief (or description) and identify natural split points:\n\n- **Data layer changes** (schemas, types, migrations) — always a separate spec\n- **Pure functions / business logic** — separate from I/O\n- **UI components** — separate from data fetching\n- **API endpoints / route handlers** — separate from business logic\n- **Test infrastructure** (mocks, fixtures, helpers) — can be its own spec if substantial\n- **Configuration / environment** — separate from code changes\n\nAsk yourself: \"Can this piece be committed and tested without the other pieces existing?\" If yes, it's a good boundary.\n\n## Step 3: Build the Decomposition Table\n\nFor each atomic spec, define:\n\n| # | Spec Name | Description | Dependencies | Size |\n|---|-----------|-------------|--------------|------|\n\n**Rules:**\n- Each spec name is \\`verb-object\\` format (e.g., \\`add-terminal-detection\\`, \\`extract-prompt-module\\`)\n- Each description is ONE sentence — if you need two, the spec is too big\n- Dependencies reference other spec numbers — keep the dependency graph shallow\n- More than 2 dependencies on a single spec = it's too big, split further\n- Aim for 3-7 specs per feature. Fewer than 3 = probably not decomposed enough. More than 10 = the feature brief is too big\n\n## Step 4: Present and Iterate\n\nShow the decomposition table to the user. Ask:\n1. \"Does this breakdown match how you think about this feature?\"\n2. \"Are there any specs that feel too big or too small?\"\n3. \"Should any of these run in parallel (separate worktrees)?\"\n\nIterate until the user approves.\n\n## Step 5: Generate Atomic Specs\n\nFor each approved row, create \\`docs/specs/YYYY-MM-DD-spec-name.md\\`. Create the \\`docs/specs/\\` directory if it doesn't exist.\n\n**Why:** Each spec must be self-contained — a fresh Claude session should be able to execute it without reading the Feature Brief. Copy relevant constraints and context into each spec.\n\nUse this structure:\n\n\\`\\`\\`markdown\n# [Verb + Object] — Atomic Spec\n\n> **Parent Brief:** \\`docs/briefs/YYYY-MM-DD-feature-name.md\\` (or \"standalone\")\n> **Status:** Ready\n> **Date:** YYYY-MM-DD\n> **Estimated scope:** [1 session / N files / ~N lines]\n\n---\n\n## What\nOne paragraph — what changes when this spec is done?\n\n## Why\nOne sentence — what breaks or is missing without this?\n\n## Acceptance Criteria\n- [ ] [Observable behavior]\n- [ ] Build passes\n- [ ] Tests pass\n\n## Test Plan\n\n| Acceptance Criterion | Test | Type |\n|---------------------|------|------|\n| [Each AC above] | [What to call/assert] | [unit/integration/e2e] |\n\n**Execution order:**\n1. Write all tests above — they should fail against current/stubbed code\n2. Run tests to confirm they fail (red)\n3. Implement until all tests pass (green)\n\n**Smoke test:** [Identify the fastest test for iteration feedback]\n\n**Before implementing, verify your test harness:**\n1. Run all tests — they must FAIL (if they pass, you're testing the wrong thing)\n2. Each test calls your actual function/endpoint — not a reimplementation or the underlying library\n3. Identify your smoke test — it must run in seconds, not minutes, so you get fast feedback on each change\n\n## Constraints\n- MUST: [hard requirement]\n- MUST NOT: [hard prohibition]\n\n## Affected Files\n| Action | File | What Changes |\n|--------|------|-------------|\n\n## Approach\nStrategy, data flow, key decisions. Name one rejected alternative.\n\n## Edge Cases\n| Scenario | Expected Behavior |\n|----------|------------------|\n\\`\\`\\`\n\nIf \\`docs/templates/ATOMIC_SPEC_TEMPLATE.md\\` exists, reference it for the full template with additional guidance.\n\nFill in all sections — each spec must be self-contained (no \"see the brief for context\"). Copy relevant constraints from the Feature Brief into each spec. Write acceptance criteria specific to THIS spec, not the whole feature. Every acceptance criterion must have at least one corresponding test in the Test Plan. If the user provided test strategy info from the interview, use it to choose test types and frameworks. Include the test harness verification rules in every Test Plan.\n\n## Step 6: Recommend Execution Strategy\n\nBased on the dependency graph:\n- **Independent specs** — \"These can run in parallel worktrees\"\n- **Sequential specs** — \"Execute these in order: 1 -> 2 -> 4\"\n- **Mixed** — \"Start specs 1 and 3 in parallel. After 1 completes, start 2.\"\n\nUpdate the Feature Brief's Execution Strategy section with the plan (if a brief exists).\n\n## Step 7: Hand Off\n\nTell the user:\n\\`\\`\\`\nDecomposition complete:\n- [N] atomic specs created in docs/specs/\n- [N] can run in parallel, [N] are sequential\n- Estimated total: [N] sessions\n\nTo execute:\n- Sequential: Open a session, point Claude at each spec in order\n- Parallel: Use worktrees — one spec per worktree, merge when done\n- Each session should end with /joycraft-session-end to capture discoveries\n\nReady to start execution?\n\\`\\`\\`\n`,\n\n \"joycraft-implement-level5.md\": `---\nname: joycraft-implement-level5\ndescription: Set up Level 5 autonomous development — autofix loop, holdout scenario testing, and scenario evolution from specs\n---\n\n# Implement Level 5 — Autonomous Development Loop\n\nYou are guiding the user through setting up Level 5: the autonomous feedback loop where specs go in, validated software comes out. This is a one-time setup that installs workflows, creates a scenarios repo, and configures the autofix loop.\n\n## Before You Begin\n\nCheck prerequisites:\n\n1. **Project must be initialized.** Look for \\`.joycraft-version\\`. If missing, tell the user to run \\`npx joycraft init\\` first.\n2. **Project should be at Level 4.** Check \\`docs/joycraft-assessment.md\\` if it exists. If the project hasn't been assessed yet, suggest running \\`/joycraft-tune\\` first. But don't block — the user may know they're ready.\n3. **Git repo with GitHub remote.** This setup requires GitHub Actions. Check for \\`.git/\\` and a GitHub remote.\n\nIf prerequisites aren't met, explain what's needed and stop.\n\n## Step 1: Explain What Level 5 Means\n\nTell the user:\n\n> Level 5 is the autonomous loop. When you push specs, three things happen automatically:\n>\n> 1. **Scenario evolution** — A separate AI agent reads your specs and writes holdout tests in a private scenarios repo. These tests are invisible to your coding agent.\n> 2. **Autofix** — When CI fails on a PR, Claude Code automatically attempts a fix (up to 3 times).\n> 3. **Holdout validation** — When CI passes, your scenarios repo runs behavioral tests against the PR. Results post as PR comments.\n>\n> The key insight: your coding agent never sees the scenario tests. This prevents it from gaming the test suite — like a validation set in machine learning.\n\n## Step 2: Gather Configuration\n\nAsk these questions **one at a time**:\n\n### Question 1: Scenarios repo name\n\n> What should we call your scenarios repo? It'll be a private repo that holds your holdout tests.\n>\n> Default: \\`{current-repo-name}-scenarios\\`\n\nAccept the default or the user's choice.\n\n### Question 2: GitHub App\n\n> Level 5 needs a GitHub App to provide a separate identity for autofix pushes (this avoids GitHub's anti-recursion protection). Creating one takes about 2 minutes:\n>\n> 1. Go to https://github.com/settings/apps/new\n> 2. Give it a name (e.g., \"My Project Autofix\")\n> 3. Uncheck \"Webhook > Active\" (not needed)\n> 4. Under **Repository permissions**, set:\n> - **Contents**: Read & Write\n> - **Pull requests**: Read & Write\n> - **Actions**: Read & Write\n> 5. Click **Create GitHub App**\n> 6. Note the **App ID** from the settings page\n> 7. Scroll to **Private keys** > click **Generate a private key** > save the \\`.pem\\` file\n> 8. Click **Install App** in the left sidebar > install it on your repo\n>\n> What's your App ID?\n\n## Step 3: Run init-autofix\n\nRun the CLI command with the gathered configuration:\n\n\\`\\`\\`bash\nnpx joycraft init-autofix --scenarios-repo {name} --app-id {id}\n\\`\\`\\`\n\nReview the output with the user. Confirm files were created.\n\n## Step 4: Walk Through Secret Configuration\n\nGuide the user step by step:\n\n### 4a: Add Secrets to Main Repo\n\n> You should already have the \\`.pem\\` file from when you created the app in Step 2.\n\n> Go to your repo's Settings > Secrets and variables > Actions, and add:\n> - \\`JOYCRAFT_APP_PRIVATE_KEY\\` — paste the contents of your \\`.pem\\` file\n> - \\`ANTHROPIC_API_KEY\\` — your Anthropic API key\n\n### 4b: Create the Scenarios Repo\n\n> Create the private scenarios repo:\n> \\`\\`\\`bash\n> gh repo create {scenarios-repo-name} --private\n> \\`\\`\\`\n>\n> Then copy the scenario templates into it:\n> \\`\\`\\`bash\n> cp -r docs/templates/scenarios/* ../{scenarios-repo-name}/\n> cd ../{scenarios-repo-name}\n> git add -A && git commit -m \"init: scaffold scenarios repo from Joycraft\"\n> git push\n> \\`\\`\\`\n\n### 4c: Add Secrets to Scenarios Repo\n\n> The scenarios repo also needs the same secrets:\n> - \\`JOYCRAFT_APP_PRIVATE_KEY\\` — same \\`.pem\\` file as the main repo\n> - \\`ANTHROPIC_API_KEY\\` — same key (needed for scenario generation)\n\n## Step 5: Verify Setup\n\nHelp the user verify everything is wired correctly:\n\n1. **Check workflow files exist:** \\`ls .github/workflows/autofix.yml .github/workflows/scenarios-dispatch.yml .github/workflows/spec-dispatch.yml .github/workflows/scenarios-rerun.yml\\`\n2. **Check scenario templates were copied:** Verify the scenarios repo has \\`example-scenario.test.ts\\`, \\`workflows/run.yml\\`, \\`workflows/generate.yml\\`, \\`prompts/scenario-agent.md\\`\n3. **Check the App ID is correct** in the workflow files (not still a placeholder)\n\n## Step 6: Update CLAUDE.md\n\nIf the project's CLAUDE.md doesn't already have an \"External Validation\" section, add one:\n\n> ## External Validation\n>\n> This project uses holdout scenario tests in a separate private repo.\n>\n> ### NEVER\n> - Access, read, or reference the scenarios repo\n> - Mention scenario test names or contents\n> - Modify the scenarios dispatch workflow to leak test information\n>\n> The scenarios repo is deliberately invisible to you. This is the holdout guarantee.\n\n## Step 7: First Test (Optional)\n\nIf the user wants to test the loop:\n\n> Want to do a quick test? Here's how:\n>\n> 1. Write a simple spec in \\`docs/specs/\\` and push to main — this triggers scenario generation\n> 2. Create a PR with a small change — when CI passes, scenarios will run\n> 3. Watch for the scenario test results as a PR comment\n>\n> Or deliberately break something in a PR to test the autofix loop.\n\n## Step 8: Summary\n\nPrint a summary of what was set up:\n\n> **Level 5 is live.** Here's what's running:\n>\n> | Trigger | What Happens |\n> |---------|-------------|\n> | Push specs to \\`docs/specs/\\` | Scenario agent writes holdout tests |\n> | PR fails CI | Claude autofix attempts (up to 3x) |\n> | PR passes CI | Holdout scenarios run against PR |\n> | Scenarios update | Open PRs re-tested with latest scenarios |\n>\n> Your scenarios repo: \\`{name}\\`\n> Your coding agent cannot see those tests. The holdout wall is intact.\n\nUpdate \\`docs/joycraft-assessment.md\\` if it exists — set the Level 5 score to reflect the new setup.\n`,\n\n \"joycraft-interview.md\": `---\nname: joycraft-interview\ndescription: Brainstorm freely about what you want to build — yap, explore ideas, and get a structured summary you can use later\n---\n\n# Interview — Idea Exploration\n\nYou are helping the user brainstorm and explore what they want to build. This is a lightweight, low-pressure conversation — not a formal spec process. Let them yap.\n\n## How to Run the Interview\n\n### 1. Open the Floor\n\nStart with something like:\n\"What are you thinking about building? Just talk — I'll listen and ask questions as we go.\"\n\nLet the user talk freely. Do not interrupt their flow. Do not push toward structure yet.\n\n### 2. Ask Clarifying Questions\n\nAs they talk, weave in questions naturally — don't fire them all at once:\n\n- **What problem does this solve?** Who feels the pain today?\n- **What does \"done\" look like?** If this worked perfectly, what would a user see?\n- **What are the constraints?** Time, tech, team, budget — what boxes are we in?\n- **What's NOT in scope?** What's tempting but should be deferred?\n- **What are the edge cases?** What could go wrong? What's the weird input?\n- **What exists already?** Are we building on something or starting fresh?\n\n### 3. Play Back Understanding\n\nAfter the user has gotten their ideas out, reflect back:\n\"So if I'm hearing you right, you want to [summary]. The core problem is [X], and done looks like [Y]. Is that right?\"\n\nLet them correct and refine. Iterate until they say \"yes, that's it.\"\n\n### 4. Write a Draft Brief\n\nCreate a draft file at \\`docs/briefs/YYYY-MM-DD-topic-draft.md\\`. Create the \\`docs/briefs/\\` directory if it doesn't exist.\n\nUse this format:\n\n\\`\\`\\`markdown\n# [Topic] — Draft Brief\n\n> **Date:** YYYY-MM-DD\n> **Status:** DRAFT\n> **Origin:** /joycraft-interview session\n\n---\n\n## The Idea\n[2-3 paragraphs capturing what the user described — their words, their framing]\n\n## Problem\n[What pain or gap this addresses]\n\n## What \"Done\" Looks Like\n[The user's description of success — observable outcomes]\n\n## Constraints\n- [constraint 1]\n- [constraint 2]\n\n## Open Questions\n- [things that came up but weren't resolved]\n- [decisions that need more thought]\n\n## Out of Scope (for now)\n- [things explicitly deferred]\n\n## Raw Notes\n[Any additional context, quotes, or tangents worth preserving]\n\\`\\`\\`\n\n### 5. Hand Off\n\nAfter writing the draft, tell the user:\n\n\\`\\`\\`\nDraft brief saved to docs/briefs/YYYY-MM-DD-topic-draft.md\n\nWhen you're ready to move forward:\n- /joycraft-new-feature — formalize this into a full Feature Brief with specs\n- /joycraft-decompose — break it directly into atomic specs if scope is clear\n- Or just keep brainstorming — run /joycraft-interview again anytime\n\\`\\`\\`\n\n## Guidelines\n\n- **This is NOT /joycraft-new-feature.** Do not push toward formal briefs, decomposition tables, or atomic specs. The point is exploration.\n- **Let the user lead.** Your job is to listen, clarify, and capture — not to structure or direct.\n- **Mark everything as DRAFT.** The output is a starting point, not a commitment.\n- **Keep it short.** The draft brief should be 1-2 pages max. Capture the essence, not every detail.\n- **Multiple interviews are fine.** The user might run this several times as their thinking evolves. Each creates a new dated draft.\n`,\n\n \"joycraft-new-feature.md\": `---\nname: joycraft-new-feature\ndescription: Guided feature development — interview the user, produce a Feature Brief, then decompose into atomic specs\n---\n\n# New Feature Workflow\n\nYou are starting a new feature. Follow this process in order. Do not skip steps.\n\n## Phase 1: Interview\n\nInterview the user about what they want to build. Let them talk — your job is to listen, then sharpen.\n\n**Why:** A thorough interview prevents wasted implementation time. Most failed features fail because the problem wasn't understood, not because the code was wrong.\n\n**Ask about:**\n- What problem does this solve? Who is affected?\n- What does \"done\" look like? How will a user know this works?\n- What are the hard constraints? (business rules, tech limitations, deadlines)\n- What is explicitly NOT in scope? (push hard on this — aggressive scoping is key)\n- Are there edge cases or error conditions we need to handle?\n- What existing code/patterns should this follow?\n\n**Interview technique:**\n- Let the user \"yap\" — don't interrupt their flow of ideas\n- After they finish, play back your understanding: \"So if I'm hearing you right...\"\n- Ask clarifying questions that force specificity: \"When you say 'handle errors,' what should the user see?\"\n- Push toward testable statements: \"How would we verify that works?\"\n\nKeep asking until you can fill out a Feature Brief. When ready, say:\n\"I have enough context. Let me write the Feature Brief for your review.\"\n\n## Phase 2: Feature Brief\n\nWrite a Feature Brief to \\`docs/briefs/YYYY-MM-DD-feature-name.md\\`. Create the \\`docs/briefs/\\` directory if it doesn't exist.\n\n**Why:** The brief is the single source of truth for what we're building. It prevents scope creep and gives every spec a shared reference point.\n\nUse this structure:\n\n\\`\\`\\`markdown\n# [Feature Name] — Feature Brief\n\n> **Date:** YYYY-MM-DD\n> **Project:** [project name]\n> **Status:** Interview | Decomposing | Specs Ready | In Progress | Complete\n\n---\n\n## Vision\nWhat are we building and why? The full picture in 2-4 paragraphs.\n\n## User Stories\n- As a [role], I want [capability] so that [benefit]\n\n## Hard Constraints\n- MUST: [constraint that every spec must respect]\n- MUST NOT: [prohibition that every spec must respect]\n\n## Out of Scope\n- NOT: [tempting but deferred]\n\n## Decomposition\n| # | Spec Name | Description | Dependencies | Est. Size |\n|---|-----------|-------------|--------------|-----------|\n| 1 | [verb-object] | [one sentence] | None | [S/M/L] |\n\n## Execution Strategy\n- [ ] Sequential (specs have chain dependencies)\n- [ ] Parallel worktrees (specs are independent)\n- [ ] Mixed\n\n## Success Criteria\n- [ ] [End-to-end behavior 1]\n- [ ] [No regressions in existing features]\n\\`\\`\\`\n\nIf \\`docs/templates/FEATURE_BRIEF_TEMPLATE.md\\` exists, reference it for the full template with additional guidance.\n\nPresent the brief to the user. Focus review on:\n- \"Does the decomposition match how you think about this?\"\n- \"Is anything in scope that shouldn't be?\"\n- \"Are the specs small enough? Can each be described in one sentence?\"\n\nIterate until approved.\n\n## Phase 3: Generate Atomic Specs\n\nFor each row in the decomposition table, create a self-contained spec file at \\`docs/specs/YYYY-MM-DD-spec-name.md\\`. Create the \\`docs/specs/\\` directory if it doesn't exist.\n\n**Why:** Each spec must be understandable WITHOUT reading the Feature Brief. This prevents the \"Curse of Instructions\" — no spec should require holding the entire feature in context. Copy relevant context into each spec.\n\nUse this structure for each spec:\n\n\\`\\`\\`markdown\n# [Verb + Object] — Atomic Spec\n\n> **Parent Brief:** \\`docs/briefs/YYYY-MM-DD-feature-name.md\\`\n> **Status:** Ready\n> **Date:** YYYY-MM-DD\n> **Estimated scope:** [1 session / N files / ~N lines]\n\n---\n\n## What\nOne paragraph — what changes when this spec is done?\n\n## Why\nOne sentence — what breaks or is missing without this?\n\n## Acceptance Criteria\n- [ ] [Observable behavior]\n- [ ] Build passes\n- [ ] Tests pass\n\n## Test Plan\n\n| Acceptance Criterion | Test | Type |\n|---------------------|------|------|\n| [Each AC above] | [What to call/assert] | [unit/integration/e2e] |\n\n**Execution order:**\n1. Write all tests above — they should fail against current/stubbed code\n2. Run tests to confirm they fail (red)\n3. Implement until all tests pass (green)\n\n**Smoke test:** [Identify the fastest test for iteration feedback]\n\n**Before implementing, verify your test harness:**\n1. Run all tests — they must FAIL (if they pass, you're testing the wrong thing)\n2. Each test calls your actual function/endpoint — not a reimplementation or the underlying library\n3. Identify your smoke test — it must run in seconds, not minutes, so you get fast feedback on each change\n\n## Constraints\n- MUST: [hard requirement]\n- MUST NOT: [hard prohibition]\n\n## Affected Files\n| Action | File | What Changes |\n|--------|------|-------------|\n\n## Approach\nStrategy, data flow, key decisions. Name one rejected alternative.\n\n## Edge Cases\n| Scenario | Expected Behavior |\n|----------|------------------|\n\\`\\`\\`\n\nIf \\`docs/templates/ATOMIC_SPEC_TEMPLATE.md\\` exists, reference it for the full template with additional guidance.\n\n## Phase 4: Hand Off for Execution\n\nTell the user:\n\\`\\`\\`\nFeature Brief and [N] atomic specs are ready.\n\nSpecs:\n1. [spec-name] — [one sentence] [S/M/L]\n2. [spec-name] — [one sentence] [S/M/L]\n...\n\nRecommended execution:\n- [Parallel/Sequential/Mixed strategy]\n- Estimated: [N] sessions total\n\nTo execute: Start a fresh session per spec. Each session should:\n1. Read the spec\n2. Implement\n3. Run /joycraft-session-end to capture discoveries\n4. Commit and PR\n\nReady to start?\n\\`\\`\\`\n\n**Why:** A fresh session for execution produces better results. The interview session has too much context noise — a clean session with just the spec is more focused.\n\nYou can also use \\`/joycraft-decompose\\` to re-decompose a brief if the breakdown needs adjustment, or run \\`/joycraft-interview\\` first for a lighter brainstorm before committing to the full workflow.\n`,\n\n \"joycraft-session-end.md\": `---\nname: joycraft-session-end\ndescription: Wrap up a session — capture discoveries, verify, prepare for PR or next session\n---\n\n# Session Wrap-Up\n\nBefore ending this session, complete these steps in order.\n\n## 1. Capture Discoveries\n\n**Why:** Discoveries are the surprises — things that weren't in the spec or that contradicted expectations. They prevent future sessions from hitting the same walls.\n\nCheck: did anything surprising happen during this session? If yes, create or update a discovery file at \\`docs/discoveries/YYYY-MM-DD-topic.md\\`. Create the \\`docs/discoveries/\\` directory if it doesn't exist.\n\nOnly capture what's NOT obvious from the code or git diff:\n- \"We thought X but found Y\" — assumptions that were wrong\n- \"This API/library behaves differently than documented\" — external gotchas\n- \"This edge case needs handling in a future spec\" — deferred work with context\n- \"The approach in the spec didn't work because...\" — spec-vs-reality gaps\n- Key decisions made during implementation that aren't in the spec\n\n**Do NOT capture:**\n- Files changed (that's the diff)\n- What you set out to do (that's the spec)\n- Step-by-step narrative of the session (nobody re-reads these)\n\nUse this format:\n\n\\`\\`\\`markdown\n# Discoveries — [topic]\n\n**Date:** YYYY-MM-DD\n**Spec:** [link to spec if applicable]\n\n## [Discovery title]\n**Expected:** [what we thought would happen]\n**Actual:** [what actually happened]\n**Impact:** [what this means for future work]\n\\`\\`\\`\n\nIf nothing surprising happened, skip the discovery file entirely. No discovery is a good sign — the spec was accurate.\n\n## 1b. Update Context Documents\n\nIf \\`docs/context/\\` exists, quickly check whether this session revealed anything about:\n\n- **Production risks** — did you interact with or learn about production vs staging systems? → Update \\`docs/context/production-map.md\\`\n- **Wrong assumptions** — did the agent (or you) assume something that turned out to be false? → Update \\`docs/context/dangerous-assumptions.md\\`\n- **Key decisions** — did you make an architectural or tooling choice? → Add a row to \\`docs/context/decision-log.md\\`\n- **Unwritten rules** — did you discover a convention or constraint not documented anywhere? → Update \\`docs/context/institutional-knowledge.md\\`\n\nSkip this if nothing applies. Don't force it — only update when there's genuine new context.\n\n## 2. Run Validation\n\nRun the project's validation commands. Check CLAUDE.md for project-specific commands. Common checks:\n\n- Type-check (e.g., \\`tsc --noEmit\\`, \\`mypy\\`, \\`cargo check\\`)\n- Tests (e.g., \\`npm test\\`, \\`pytest\\`, \\`cargo test\\`)\n- Lint (e.g., \\`eslint\\`, \\`ruff\\`, \\`clippy\\`)\n\nFix any failures before proceeding.\n\n## 3. Update Spec Status\n\nIf working from an atomic spec in \\`docs/specs/\\`:\n- All acceptance criteria met — update status to \\`Complete\\`\n- Partially done — update status to \\`In Progress\\`, note what's left\n\nIf working from a Feature Brief in \\`docs/briefs/\\`, check off completed specs in the decomposition table.\n\n## 4. Commit\n\nCommit all changes including the discovery file (if created) and spec status updates. The commit message should reference the spec if applicable.\n\n## 5. Push and PR (if autonomous git is enabled)\n\n**Check CLAUDE.md for \"Git Autonomy\" in the Behavioral Boundaries section.** If it says \"STRICTLY ENFORCED\" or the ALWAYS section includes \"Push to feature branches immediately after every commit\":\n\n1. **Push immediately.** Run \\`git push origin <branch>\\` — do not ask, do not hesitate.\n2. **Open a PR if the feature is complete.** Check the parent Feature Brief's decomposition table — if all specs are done, run \\`gh pr create\\` with a summary of all completed specs. Do not ask first.\n3. **If not all specs are done,** still push. The PR comes when the last spec is complete.\n\nIf CLAUDE.md does NOT have autonomous git rules (or has \"ASK FIRST\" for pushing), ask the user before pushing.\n\n## 6. Report\n\n\\`\\`\\`\nSession complete.\n- Spec: [spec name] — [Complete / In Progress]\n- Build: [passing / failing]\n- Discoveries: [N items / none]\n- Pushed: [yes / no — and why not]\n- PR: [opened #N / not yet — N specs remaining]\n- Next: [what the next session should tackle]\n\\`\\`\\`\n`,\n\n \"joycraft-tune.md\": `---\nname: joycraft-tune\ndescription: Assess and upgrade your project's AI development harness — score 7 dimensions, apply fixes, show path to Level 5\n---\n\n# Tune — Project Harness Assessment & Upgrade\n\nYou are evaluating and upgrading this project's AI development harness. Follow these steps in order.\n\n## Step 1: Detect Harness State\n\nCheck the following and note what exists:\n\n1. **CLAUDE.md** — Read it if it exists. Check whether it contains meaningful content (not just a project name or generic README).\n2. **Key directories** — Check for: \\`docs/specs/\\`, \\`docs/briefs/\\`, \\`docs/discoveries/\\`, \\`docs/templates/\\`, \\`.claude/skills/\\`\n3. **Boundary framework** — Look for \\`Always\\`, \\`Ask First\\`, and \\`Never\\` sections in CLAUDE.md (or similar behavioral constraints under any heading).\n4. **Skills infrastructure** — Check \\`.claude/skills/\\` for installed skill files.\n5. **Test configuration** — Look for test commands in package.json, pyproject.toml, Cargo.toml, Makefile, or CI config files.\n\n## Step 2: Route Based on State\n\n### If No Harness (no CLAUDE.md, or CLAUDE.md is just a README with no structured sections):\n\nTell the user:\n- Their project has no AI development harness\n- Recommend running \\`npx joycraft init\\` to scaffold one\n- Briefly explain what it sets up: CLAUDE.md with boundaries, spec/brief templates, skills, documentation structure\n- **Stop here** — do not run the full assessment on a bare project\n\n### If Harness Exists (CLAUDE.md has structured content — boundaries, commands, architecture, or domain rules):\n\nContinue to Step 3 for the full assessment.\n\n## Step 3: Score 7 Dimensions\n\nRead CLAUDE.md thoroughly. Explore the project structure. Score each dimension on a 1-5 scale with specific evidence.\n\n### Dimension 1: Spec Quality\n\nLook in \\`docs/specs/\\` for specification files.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No specs directory or no spec files |\n| 2 | Specs exist but are informal notes or TODOs |\n| 3 | Specs have structure (sections, some criteria) but lack consistency |\n| 4 | Specs are structured with clear acceptance criteria and constraints |\n| 5 | Atomic specs: self-contained, acceptance criteria, constraints, edge cases, affected files |\n\n**Evidence:** Number of specs found, example of best/worst, whether acceptance criteria are present.\n\n### Dimension 2: Spec Granularity\n\nCan each spec be completed in a single coding session?\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No specs |\n| 2 | Specs cover entire features or epics |\n| 3 | Specs are feature-sized (multi-session but bounded) |\n| 4 | Most specs are session-sized with clear scope |\n| 5 | All specs are atomic — one session, one concern, clear done state |\n\n### Dimension 3: Behavioral Boundaries\n\nRead CLAUDE.md for explicit behavioral constraints.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No CLAUDE.md or no behavioral guidance |\n| 2 | CLAUDE.md exists with general instructions but no structured boundaries |\n| 3 | Some boundaries exist but not organized as Always/Ask First/Never |\n| 4 | Always/Ask First/Never sections present with reasonable coverage |\n| 5 | Comprehensive boundaries covering code style, testing, deployment, dependencies, and dangerous operations |\n\n**Important:** Projects may have strong rules under different headings (e.g., \"Critical Rules\", \"Constraints\"). Give credit for substance over format — a project with clear, enforced rules scores higher than one with empty Always/Ask First/Never sections.\n\n### Dimension 4: Skills & Hooks\n\nLook in \\`.claude/skills/\\` for skill files. Check for hooks configuration.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No .claude/ directory |\n| 2 | .claude/ exists but empty or minimal |\n| 3 | A few skills installed, no hooks |\n| 4 | Multiple relevant skills, basic hooks |\n| 5 | Comprehensive skills covering workflow, hooks for validation |\n\n### Dimension 5: Documentation\n\nExamine \\`docs/\\` directory structure and content.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No docs/ directory |\n| 2 | docs/ exists with ad-hoc files |\n| 3 | Some structure (subdirectories) but inconsistent |\n| 4 | Structured docs/ with templates and clear organization |\n| 5 | Full structure: briefs/, specs/, templates/, architecture docs, referenced from CLAUDE.md |\n\n### Dimension 6: Knowledge Capture & Contextual Stewardship\n\nLook for discoveries, decisions, session notes, and context documents.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No knowledge capture mechanism |\n| 2 | Ad-hoc notes or a discoveries directory with no entries |\n| 3 | Discoveries directory with some entries, or context docs exist but empty |\n| 4 | Active discoveries + at least 2 context docs with content (production-map, dangerous-assumptions, decision-log, institutional-knowledge) |\n| 5 | Full contextual stewardship: discoveries with entries, all 4 context docs maintained, session-end workflow in active use |\n\n**Check for:** \\`docs/discoveries/\\`, \\`docs/context/production-map.md\\`, \\`docs/context/dangerous-assumptions.md\\`, \\`docs/context/decision-log.md\\`, \\`docs/context/institutional-knowledge.md\\`. Score based on both existence AND whether they have real content (not just templates).\n\n### Dimension 7: Testing & Validation\n\nLook for test config, CI setup, and validation commands.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No test configuration |\n| 2 | Test framework installed but few/no tests |\n| 3 | Tests exist with reasonable coverage |\n| 4 | Tests + CI pipeline configured |\n| 5 | Tests + CI + validation commands in CLAUDE.md + scenario tests |\n\n## Step 4: Write Assessment\n\nWrite the assessment to \\`docs/joycraft-assessment.md\\` AND display it in the conversation. Use this format:\n\n\\`\\`\\`markdown\n# Joycraft Assessment — [Project Name]\n\n**Date:** [today's date]\n**Overall Level:** [1-5, based on average score]\n\n## Scores\n\n| Dimension | Score | Summary |\n|-----------|-------|---------|\n| Spec Quality | X/5 | [one-line summary] |\n| Spec Granularity | X/5 | [one-line summary] |\n| Behavioral Boundaries | X/5 | [one-line summary] |\n| Skills & Hooks | X/5 | [one-line summary] |\n| Documentation | X/5 | [one-line summary] |\n| Knowledge Capture | X/5 | [one-line summary] |\n| Testing & Validation | X/5 | [one-line summary] |\n\n**Average:** X.X/5\n\n## Detailed Findings\n\n### [Dimension Name] — X/5\n**Evidence:** [specific files, paths, counts found]\n**Gap:** [what's missing]\n**Recommendation:** [specific action to improve]\n\n## Upgrade Plan\n\nTo reach Level [current + 1], complete these steps:\n1. [Most impactful action] — addresses [dimension] (X -> Y)\n2. [Next action] — addresses [dimension] (X -> Y)\n[up to 5 actions, ordered by impact]\n\\`\\`\\`\n\n## Step 5: Apply Upgrades\n\nImmediately after presenting the assessment, apply upgrades using the three-tier model below. Do NOT ask for per-item permission — batch everything and show a consolidated report at the end.\n\n### Tier 1: Silent Apply (just do it)\nThese are safe, additive operations. Apply them without asking:\n- Create missing directories (\\`docs/specs/\\`, \\`docs/briefs/\\`, \\`docs/discoveries/\\`, \\`docs/templates/\\`)\n- Install missing skills to \\`.claude/skills/\\`\n- Copy missing templates to \\`docs/templates/\\`\n- Create AGENTS.md if it doesn't exist\n\n### Git Autonomy Preference\n\nBefore applying Behavioral Boundaries to CLAUDE.md, ask the user ONE question:\n\n> How autonomous should git operations be?\n> 1. **Cautious** — commits freely, asks before pushing or opening PRs *(good for learning the workflow)*\n> 2. **Autonomous** — commits, pushes to branches, and opens PRs without asking *(good for spec-driven development)*\n\nBased on their answer, use the appropriate git rules in the Behavioral Boundaries section:\n\n**If Cautious (default):**\n\\`\\`\\`\n### ASK FIRST\n- Pushing to remote\n- Creating or merging pull requests\n- Any destructive git operation (force-push, reset --hard, branch deletion)\n\n### NEVER\n- Push directly to main/master without approval\n- Amend commits that have been pushed\n\\`\\`\\`\n\n**If Autonomous:**\n\\`\\`\\`\n### ALWAYS — Git Autonomy (STRICTLY ENFORCED)\n\n> **This project uses fully autonomous git operations.** The user has explicitly\n> granted permission for all git operations below. Do NOT ask for confirmation.\n> Do NOT hesitate. Do NOT treat these as suggestions. These are standing orders\n> that override any default caution about pushing or creating PRs.\n\n- **Commit early and often** — after each logical unit of work, not just at session end\n- **Push to feature branches immediately after every commit** — do not batch pushes, do not wait, do not ask\n- **Open a PR when all specs in a feature are complete** — use gh pr create, do not ask first\n- **Use descriptive branch names:** feature/spec-name or fix/description\n- **After /joycraft-session-end:** push all commits and open a PR if the feature is complete\n\n### ASK FIRST\n- Merging PRs to main/master\n- Any destructive git operation (force-push, reset --hard, branch deletion)\n\n### NEVER\n- Push directly to main/master (always use feature branches + PR)\n- Amend commits that have been pushed to remote\n- Ask \"should I push?\" or \"should I create a PR?\" — the answer is always yes, just do it\n\\`\\`\\`\n\n### Permission Mode Recommendation\n\nAfter the git autonomy question and before the risk interview, recommend a Claude Code permission mode based on what you've learned so far. Present this guidance:\n\n> **What permission mode should you use?**\n>\n> | Your situation | Use | Why |\n> |---|---|---|\n> | Autonomous spec execution | \\`--permission-mode dontAsk\\` + allowlist | Only pre-approved commands run |\n> | Long session with some trust | \\`--permission-mode auto\\` | Safety classifier reviews each action |\n> | Interactive development | \\`--permission-mode acceptEdits\\` | Auto-approves file edits, prompts for commands |\n>\n> You do NOT need \\`--dangerously-skip-permissions\\`. The modes above provide autonomy with safety.\n\n**If the user chose Autonomous git:** Recommend \\`auto\\` mode as a good default -- it provides autonomy while the safety classifier catches risky operations. Note that \\`dontAsk\\` is even more autonomous but requires a well-configured allowlist.\n\n**If the user chose Cautious git:** Recommend \\`auto\\` mode -- it matches their preference for safety with less manual intervention than the default.\n\n**If the risk interview reveals production databases, live APIs, or billing systems:** Upgrade the recommendation to \\`dontAsk\\` with a tight allowlist. Explain that \\`dontAsk\\` with explicit deny patterns is safer than \\`auto\\` for high-risk environments because it uses a deterministic allowlist rather than a classifier.\n\nThis is informational only -- do not change the user's permission mode. Just tell them what to use when they launch Claude Code.\n\n### Risk Interview\n\nBefore applying upgrades, ask 3-5 targeted questions to capture what's dangerous in this project. Skip this if \\`docs/context/production-map.md\\` or \\`docs/context/dangerous-assumptions.md\\` already exist (offer to update instead).\n\n**Question 1:** \"What could this agent break that would ruin your day? Think: production databases, live APIs, billing systems, user data, infrastructure.\"\n\nFrom the answer, generate:\n- NEVER rules for CLAUDE.md (e.g., \"NEVER connect to production DB at postgres://prod.example.com\")\n- Deny patterns for .claude/settings.json (e.g., deny Bash commands containing production hostnames)\n\n**Question 2:** \"What external services does this project connect to? Which are production vs. staging/dev?\"\n\nFrom the answer, generate:\n- \\`docs/context/production-map.md\\` documenting what's real vs safe to touch\n- Include: service name, URL/endpoint, environment (prod/staging/dev), what happens if corrupted\n\n**Question 3:** \"What are the unwritten rules a new developer would need months to learn about this project?\"\n\nFrom the answer, generate:\n- Additions to CLAUDE.md boundaries (new ALWAYS/ASK FIRST/NEVER rules)\n- \\`docs/context/dangerous-assumptions.md\\` with \"Agent might assume X, but actually Y\"\n\n**Question 4 (optional):** \"What happened last time something went wrong with an automated tool or deploy?\"\n\nIf the user has a story, capture the lesson as a specific NEVER rule and add to dangerous-assumptions.md.\n\n**Question 5:** \"Any files, directories, or commands that should be completely off-limits?\"\n\nFrom the answer, generate deny rules for .claude/settings.json and add to NEVER section.\n\n**Rules for the interview:**\n- Ask questions ONE AT A TIME, not all at once\n- If the user says \"nothing\" or \"skip\", respect that and move on\n- Keep it to 2-3 minutes total — don't interrogate\n- Generate artifacts immediately after the interview, don't wait for all questions\n- This is the SECOND and LAST set of questions during /joycraft-tune (first is git autonomy)\n\n### Tier 2: Apply and Show Diff (do it, then report)\nThese modify important files but are additive (append-only). Apply them, then show what changed so the user can review. Git is the undo button.\n- Add missing sections to CLAUDE.md (Behavioral Boundaries, Development Workflow, Getting Started with Joycraft, Key Files, Common Gotchas)\n- Use the git autonomy preference from above when generating the Behavioral Boundaries section\n- Draft section content from the actual codebase — not generic placeholders. Read the project's real rules, real commands, real structure.\n- Only append — never modify or reformat existing content\n\n### Tier 3: Confirm First (ask before acting)\nThese are potentially destructive or opinionated. Ask before proceeding:\n- Rewriting or reorganizing existing CLAUDE.md sections\n- Overwriting files the user has customized\n- Suggesting test framework installation or CI setup (present as recommendations, don't auto-install)\n\n### Reading a Previous Assessment\n\nIf \\`docs/joycraft-assessment.md\\` already exists, read it first. If all recommendations have been applied, report \"nothing to upgrade\" and offer to re-assess.\n\n### After Applying\n\nAppend a history entry to \\`docs/joycraft-history.md\\` (create if needed):\n\\`\\`\\`\n| [date] | [new avg score] | [change from last] | [summary of what changed] |\n\\`\\`\\`\n\nThen display a single consolidated report:\n\n\\`\\`\\`markdown\n## Upgrade Results\n\n| Dimension | Before | After | Change |\n|------------------------|--------|-------|--------|\n| Spec Quality | X/5 | X/5 | +X |\n| ... | ... | ... | ... |\n\n**Previous Level:** X — **New Level:** X\n\n### What Changed\n- [list each change applied]\n\n### Remaining Gaps\n- [anything still below 3.5, with specific next action]\n\\`\\`\\`\n\nUpdate \\`docs/joycraft-assessment.md\\` with the new scores and today's date.\n\n## Step 6: Show Path to Level 5\n\nAfter the upgrade report, always show the Level 5 roadmap tailored to the project's current state:\n\n\\`\\`\\`markdown\n## Path to Level 5 — Autonomous Development\n\nYou're at Level [X]. Here's what each level looks like:\n\n| Level | You | AI | Key Skill |\n|-------|-----|-----|-----------|\n| 2 | Guide direction | Multi-file changes | AI-native tooling |\n| 3 | Review diffs | Primary developer | Code review at scale |\n| 4 | Write specs, check tests | End-to-end development | Specification writing |\n| 5 | Define what + why | Specs in, software out | Systems design |\n\n### Your Next Steps Toward Level [X+1]:\n1. [Specific action based on current gaps — e.g., \"Write your first atomic spec using /joycraft-new-feature\"]\n2. [Next action — e.g., \"Add vitest and write tests for your core logic\"]\n3. [Next action — e.g., \"Use /joycraft-session-end consistently to build your discoveries log\"]\n\n### What Level 5 Looks Like (Your North Star):\n- A backlog of ready specs that agents pull from and execute autonomously\n- CI failures auto-generate fix specs — no human triage for regressions\n- Multi-agent execution with parallel worktrees, one spec per agent\n- External holdout scenarios (tests the agent can't see) prevent overfitting\n- CLAUDE.md evolves from discoveries — the harness improves itself\n\n### You'll Know You're at Level 5 When:\n- You describe a feature in one sentence and walk away\n- The system produces a PR with tests, docs, and discoveries — without further input\n- Failed CI runs generate their own fix specs\n- Your harness improves without you manually editing CLAUDE.md\n\nThis is a significant journey. Most teams are at Level 2. Getting to Level 4 with Joycraft's workflow is achievable — Level 5 requires building validation infrastructure (scenario tests, spec queues, CI feedback loops) that goes beyond what Joycraft scaffolds today. But the harness you're building now is the foundation.\n\\`\\`\\`\n\nTailor the \"Next Steps\" section based on the project's actual gaps — don't show generic advice.\n\n## Edge Cases\n\n- **Not a git repo:** Note this. Joycraft works best in a git repo.\n- **CLAUDE.md is just a README:** Treat as \"no harness.\"\n- **Non-Joycraft skills already installed:** Acknowledge them. Do not replace — suggest additions.\n- **Monorepo:** Assess the root CLAUDE.md. Note if component-level CLAUDE.md files exist.\n- **Project has rules under non-standard headings:** Give credit. Suggest reformatting as Always/Ask First/Never but acknowledge the rules are there.\n- **Assessment file missing when upgrading:** Run the full assessment first, then offer to apply.\n- **Assessment is stale:** Warn and offer to re-assess before proceeding.\n- **All recommendations already applied:** Report \"nothing to upgrade\" and stop.\n- **User declines a recommendation:** Skip it, continue, include in \"What Was Skipped.\"\n- **CLAUDE.md does not exist at all:** Create it with recommended sections, but ask the user first.\n- **Non-Joycraft content in CLAUDE.md:** Preserve exactly as-is. Only append or merge — never remove or reformat existing content.\n`,\n\n \"joycraft-add-fact.md\": `---\nname: joycraft-add-fact\ndescription: Capture a project fact and route it to the correct context document -- production map, dangerous assumptions, decision log, institutional knowledge, or troubleshooting\n---\n\n# Add Fact\n\nThe user has a fact to capture. Your job is to classify it, route it to the correct context document, append it in the right format, and optionally add a CLAUDE.md boundary rule.\n\n## Step 1: Get the Fact\n\nIf the user already provided the fact (e.g., \\`/joycraft-add-fact the staging DB resets every Sunday\\`), use it directly.\n\nIf not, ask: \"What fact do you want to capture?\" -- then wait for their response.\n\nIf the user provides multiple facts at once, process each one separately through all the steps below, then give a combined confirmation at the end.\n\n## Step 2: Classify the Fact\n\nRoute the fact to one of these 5 context documents based on its content:\n\n### \\`docs/context/production-map.md\\`\nThe fact is about **infrastructure, services, environments, URLs, endpoints, credentials, or what is safe/unsafe to touch**.\n- Signal words: \"production\", \"staging\", \"endpoint\", \"URL\", \"database\", \"service\", \"deployed\", \"hosted\", \"credentials\", \"secret\", \"environment\"\n- Examples: \"The staging DB is at postgres://staging.example.com\", \"We use Vercel for the frontend and Railway for the API\"\n\n### \\`docs/context/dangerous-assumptions.md\\`\nThe fact is about **something an AI agent might get wrong -- a false assumption that leads to bad outcomes**.\n- Signal words: \"assumes\", \"might think\", \"but actually\", \"looks like X but is Y\", \"not what it seems\", \"trap\", \"gotcha\"\n- Examples: \"The \\\\\\`users\\\\\\` table looks like a test table but it's production\", \"Deleting a workspace doesn't delete the billing subscription\"\n\n### \\`docs/context/decision-log.md\\`\nThe fact is about **an architectural or tooling choice and why it was made**.\n- Signal words: \"decided\", \"chose\", \"because\", \"instead of\", \"we went with\", \"the reason we use\", \"trade-off\"\n- Examples: \"We chose SQLite over Postgres because this runs on embedded devices\", \"We use pnpm instead of npm for workspace support\"\n\n### \\`docs/context/institutional-knowledge.md\\`\nThe fact is about **team conventions, unwritten rules, organizational context, or who owns what**.\n- Signal words: \"convention\", \"rule\", \"always\", \"never\", \"team\", \"process\", \"review\", \"approval\", \"owns\", \"responsible\"\n- Examples: \"The design team reviews all color changes\", \"We never deploy on Fridays\", \"PR titles must start with the ticket number\"\n\n### \\`docs/context/troubleshooting.md\\`\nThe fact is about **diagnostic knowledge -- when X happens, do Y (or don't do Z)**.\n- Signal words: \"when\", \"fails\", \"error\", \"if you see\", \"stuck\", \"broken\", \"fix\", \"workaround\", \"before trying\", \"reboot\", \"restart\", \"reset\"\n- Examples: \"If Wi-Fi disconnects during flash, wait and retry -- don't switch networks\", \"When tests fail with ECONNREFUSED, check if Docker is running\"\n\n### Ambiguous Facts\n\nIf the fact fits multiple categories, pick the **best fit** based on the primary intent. You will mention the alternative in your confirmation message so the user can correct you.\n\n## Step 3: Ensure the Target Document Exists\n\n1. If \\`docs/context/\\` does not exist, create the directory.\n2. If the target document does not exist, create it from the template structure. Check \\`docs/templates/\\` for the matching template. If no template exists, use this minimal structure:\n\nFor **production-map.md**:\n\\`\\`\\`markdown\n# Production Map\n\n> What's real, what's staging, what's safe to touch.\n\n## Services\n\n| Service | Environment | URL/Endpoint | Impact if Corrupted |\n|---------|-------------|-------------|-------------------|\n\\`\\`\\`\n\nFor **dangerous-assumptions.md**:\n\\`\\`\\`markdown\n# Dangerous Assumptions\n\n> Things the AI agent might assume that are wrong in this project.\n\n## Assumptions\n\n| Agent Might Assume | But Actually | Impact If Wrong |\n|-------------------|-------------|----------------|\n\\`\\`\\`\n\nFor **decision-log.md**:\n\\`\\`\\`markdown\n# Decision Log\n\n> Why choices were made, not just what was chosen.\n\n## Decisions\n\n| Date | Decision | Why | Alternatives Rejected | Revisit When |\n|------|----------|-----|----------------------|-------------|\n\\`\\`\\`\n\nFor **institutional-knowledge.md**:\n\\`\\`\\`markdown\n# Institutional Knowledge\n\n> Unwritten rules, team conventions, and organizational context.\n\n## Team Conventions\n\n- (none yet)\n\\`\\`\\`\n\nFor **troubleshooting.md**:\n\\`\\`\\`markdown\n# Troubleshooting\n\n> What to do when things go wrong for non-code reasons.\n\n## Common Failures\n\n| When This Happens | Do This | Don't Do This |\n|-------------------|---------|---------------|\n\\`\\`\\`\n\n## Step 4: Read the Target Document\n\nRead the target document to understand its current structure. Note:\n- Which section to append to\n- Whether it uses tables or lists\n- The column format if it's a table\n\n## Step 5: Append the Fact\n\nAdd the fact to the appropriate section of the target document. Match the existing format exactly:\n\n- **Table-based documents** (production-map, dangerous-assumptions, decision-log, troubleshooting): Add a new table row in the correct columns. Use today's date where a date column exists.\n- **List-based documents** (institutional-knowledge): Add a new list item (\\`- \\`) to the most appropriate section.\n\nRemove any italic example rows (rows where all cells start with \\`_\\`) before appending, so the document transitions from template to real content. Only remove examples from the specific table you are appending to.\n\n**Append only. Never modify or remove existing real content.**\n\n## Step 6: Evaluate CLAUDE.md Boundary Rule\n\nDecide whether the fact also warrants a rule in CLAUDE.md's behavioral boundaries:\n\n**Add a CLAUDE.md rule if the fact:**\n- Describes something that should ALWAYS or NEVER be done\n- Could cause real damage if violated (data loss, broken deployments, security issues)\n- Is a hard constraint that applies across all work, not just a one-time note\n\n**Do NOT add a CLAUDE.md rule if the fact is:**\n- Purely informational (e.g., \"staging DB is at this URL\")\n- A one-time decision that's already captured\n- A diagnostic tip rather than a prohibition\n\nIf a rule is warranted, read CLAUDE.md, find the appropriate section (ALWAYS, ASK FIRST, or NEVER under Behavioral Boundaries), and append the rule. If no Behavioral Boundaries section exists, append one.\n\n## Step 7: Confirm\n\nReport what you did in this format:\n\n\\`\\`\\`\nAdded to [document name]:\n [summary of what was added]\n\n[If CLAUDE.md was also updated:]\nAdded CLAUDE.md rule:\n [ALWAYS/ASK FIRST/NEVER]: [rule text]\n\n[If the fact was ambiguous:]\nRouted to [chosen doc] -- move to [alternative doc] if this is more about [alternative category description].\n\\`\\`\\`\n`,\n\n \"joycraft-lockdown.md\": `---\nname: joycraft-lockdown\ndescription: Generate constrained execution boundaries for an implementation session -- NEVER rules and deny patterns to prevent agent overreach\n---\n\n# Lockdown Mode\n\nThe user wants to constrain agent behavior for an implementation session. Your job is to interview them about what should be off-limits, then generate CLAUDE.md NEVER rules and \\`.claude/settings.json\\` deny patterns they can review and apply.\n\n## When Is Lockdown Useful?\n\nLockdown is most valuable for:\n- **Complex tech stacks** (hardware, firmware, multi-device) where agents can cause real damage\n- **Long-running autonomous sessions** where you won't be monitoring every action\n- **Production-adjacent work** where accidental network calls or package installs are risky\n\nFor simple feature work on a well-tested codebase, lockdown is usually overkill. Mention this context to the user so they can decide.\n\n## Step 1: Check for Tests\n\nBefore starting the interview, check if the project has test files or directories (look for \\`tests/\\`, \\`test/\\`, \\`__tests__/\\`, \\`spec/\\`, or files matching \\`*.test.*\\`, \\`*.spec.*\\`).\n\nIf no tests are found, tell the user:\n\n> Lockdown mode is most useful when you already have tests in place -- it prevents the agent from modifying them while constraining behavior to writing code and running tests. Consider running \\`/joycraft-new-feature\\` first to set up a test-driven workflow, then come back to lock it down.\n\nIf the user wants to proceed anyway, continue with the interview.\n\n## Step 2: Interview -- What to Lock Down\n\nAsk these three questions, one at a time. Wait for the user's response before proceeding to the next question.\n\n### Question 1: Read-Only Files\n\n> What test files or directories should be off-limits for editing? (e.g., \\`tests/\\`, \\`__tests__/\\`, \\`spec/\\`, specific test files)\n>\n> I'll generate NEVER rules to prevent editing these.\n\nIf the user isn't sure, suggest the test directories you found in Step 1.\n\n### Question 2: Allowed Commands\n\n> What commands should the agent be allowed to run? Defaults:\n> - Write and edit source code files\n> - Run the project's smoke test command\n> - Run the full test suite\n>\n> Any other commands to explicitly allow? Or should I restrict to just these?\n\n### Question 3: Denied Commands\n\n> What commands should be denied? Defaults:\n> - Package installs (\\`npm install\\`, \\`pip install\\`, \\`cargo add\\`, \\`go get\\`, etc.)\n> - Network tools (\\`curl\\`, \\`wget\\`, \\`ping\\`, \\`ssh\\`)\n> - Direct log file reading\n>\n> Any specific commands to add or remove from this list?\n\n**Edge case -- user wants to allow some network access:** If the user mentions API tests or specific endpoints that need network access, exclude those from the deny list and note the exception in the output.\n\n**Edge case -- user wants to lock down file writes:** If the user wants to prevent ALL file writes, warn them:\n\n> Denying all file writes would prevent the agent from doing any work. I recommend keeping source code writes allowed and only locking down test files, config files, or other sensitive directories.\n\n## Step 3: Generate Boundaries\n\nBased on the interview responses, generate output in this exact format:\n\n\\`\\`\\`\n## Lockdown boundaries generated\n\nReview these suggestions and add them to your project:\n\n### CLAUDE.md -- add to NEVER section:\n\n- Edit any file in \\\\\\`[user's test directories]\\\\\\`\n- Run \\\\\\`[denied package manager commands]\\\\\\`\n- Use \\\\\\`[denied network tools]\\\\\\`\n- Read log files directly -- interact with logs only through test assertions\n- [Any additional NEVER rules based on user responses]\n\n### .claude/settings.json -- suggested deny patterns:\n\nAdd these to the \\\\\\`permissions.deny\\\\\\` array:\n\n[\"[command1]\", \"[command2]\", \"[command3]\"]\n\n---\n\nCopy these into your project manually, or tell me to apply them now (I'll show you the exact changes for approval first).\n\\`\\`\\`\n\nAdjust the content based on the actual interview responses:\n- Only include deny patterns for commands the user confirmed should be denied\n- Only include NEVER rules for directories/files the user specified\n- If the user allowed certain network tools or package managers, exclude those\n\n## Recommended Permission Mode\n\nAfter generating the boundaries above, also recommend a Claude Code permission mode. Include this section in your output:\n\n\\`\\`\\`\n### Recommended Permission Mode\n\nYou don't need \\\\\\`--dangerously-skip-permissions\\\\\\`. Safer alternatives exist:\n\n| Your situation | Use | Why |\n|---|---|---|\n| Autonomous spec execution | \\\\\\`--permission-mode dontAsk\\\\\\` + allowlist above | Only pre-approved commands run |\n| Long session with some trust | \\\\\\`--permission-mode auto\\\\\\` | Safety classifier reviews each action |\n| Interactive development | \\\\\\`--permission-mode acceptEdits\\\\\\` | Auto-approves file edits, prompts for commands |\n\n**For lockdown mode, we recommend \\\\\\`--permission-mode dontAsk\\\\\\`** combined with the deny patterns above. This gives you full autonomy for allowed operations while blocking everything else -- no classifier overhead, no prompts, and no safety bypass.\n\n\\\\\\`--dangerously-skip-permissions\\\\\\` disables ALL safety checks. The modes above give you autonomy without removing the guardrails.\n\\`\\`\\`\n\n## Step 4: Offer to Apply\n\nIf the user asks you to apply the changes:\n\n1. **For CLAUDE.md:** Read the existing CLAUDE.md, find the Behavioral Boundaries section, and show the user the exact diff for the NEVER section. Ask for confirmation before writing.\n2. **For settings.json:** Read the existing \\`.claude/settings.json\\`, show the user what the \\`permissions.deny\\` array will look like after adding the new patterns. Ask for confirmation before writing.\n\n**Never auto-apply. Always show the exact changes and wait for explicit approval.**\n`,\n\n \"joycraft-verify.md\": `---\nname: joycraft-verify\ndescription: Spawn an independent verifier subagent to check an implementation against its spec -- read-only, no code edits, structured pass/fail verdict\n---\n\n# Verify Implementation Against Spec\n\nThe user wants independent verification of an implementation. Your job is to find the relevant spec, extract its acceptance criteria and test plan, then spawn a separate verifier subagent that checks each criterion and produces a structured verdict.\n\n**Why a separate subagent?** Anthropic's research found that agents reliably skew positive when grading their own work. Separating the agent doing the work from the agent judging it consistently outperforms self-evaluation. The verifier gets a clean context window with no implementation bias.\n\n## Step 1: Find the Spec\n\nIf the user provided a spec path (e.g., \\`/joycraft-verify docs/specs/2026-03-26-add-widget.md\\`), use that path directly.\n\nIf no path was provided, scan \\`docs/specs/\\` for spec files. Pick the most recently modified \\`.md\\` file in that directory. If \\`docs/specs/\\` doesn't exist or is empty, tell the user:\n\n> No specs found in \\`docs/specs/\\`. Please provide a spec path: \\`/joycraft-verify path/to/spec.md\\`\n\n## Step 2: Read and Parse the Spec\n\nRead the spec file and extract:\n\n1. **Spec name** -- from the H1 title\n2. **Acceptance Criteria** -- the checklist under the \\`## Acceptance Criteria\\` section\n3. **Test Plan** -- the table under the \\`## Test Plan\\` section, including any test commands\n4. **Constraints** -- the \\`## Constraints\\` section if present\n\nIf the spec has no Acceptance Criteria section, tell the user:\n\n> This spec doesn't have an Acceptance Criteria section. Verification needs criteria to check against. Add acceptance criteria to the spec and try again.\n\nIf the spec has no Test Plan section, note this but proceed -- the verifier can still check criteria by reading code and running any available project tests.\n\n## Step 3: Identify Test Commands\n\nLook for test commands in these locations (in priority order):\n\n1. The spec's Test Plan section (look for commands in backticks or \"Type\" column entries like \"unit\", \"integration\", \"e2e\", \"build\")\n2. The project's CLAUDE.md (look for test/build commands in the Development Workflow section)\n3. Common defaults based on the project type:\n - Node.js: \\`npm test\\` or \\`pnpm test --run\\`\n - Python: \\`pytest\\`\n - Rust: \\`cargo test\\`\n - Go: \\`go test ./...\\`\n\nBuild a list of specific commands the verifier should run.\n\n## Step 4: Spawn the Verifier Subagent\n\nUse Claude Code's Agent tool to spawn a subagent with the following prompt. Replace the placeholders with the actual content extracted in Steps 2-3.\n\n\\`\\`\\`\nYou are a QA verifier. Your job is to independently verify an implementation against its spec. You have NO context about how the implementation was done -- you are checking it fresh.\n\nRULES -- these are hard constraints, not suggestions:\n- You may READ any file using the Read tool or cat\n- You may RUN these specific test/build commands: [TEST_COMMANDS]\n- You may NOT edit, create, or delete any files\n- You may NOT run commands that modify state (no git commit, no npm install, no file writes)\n- You may NOT install packages or access the network\n- Report what you OBSERVE, not what you expect or hope\n\nSPEC NAME: [SPEC_NAME]\n\nACCEPTANCE CRITERIA:\n[ACCEPTANCE_CRITERIA]\n\nTEST PLAN:\n[TEST_PLAN]\n\nCONSTRAINTS:\n[CONSTRAINTS_OR_NONE]\n\nYOUR TASK:\nFor each acceptance criterion, determine if it PASSES or FAILS based on evidence:\n\n1. Run the test commands listed above. Record the output.\n2. For each acceptance criterion:\n a. Check if there is a corresponding test and whether it passes\n b. If no test exists, read the relevant source files to verify the criterion is met\n c. If the criterion cannot be verified by reading code or running tests, mark it MANUAL CHECK NEEDED\n3. For criteria about build/test passing, actually run the commands and report results.\n\nOUTPUT FORMAT -- you MUST use this exact format:\n\nVERIFICATION REPORT\n\n| # | Criterion | Verdict | Evidence |\n|---|-----------|---------|----------|\n| 1 | [criterion text] | PASS/FAIL/MANUAL CHECK NEEDED | [what you observed] |\n| 2 | [criterion text] | PASS/FAIL/MANUAL CHECK NEEDED | [what you observed] |\n[continue for all criteria]\n\nSUMMARY: X/Y criteria passed. [Z failures need attention. / All criteria verified.]\n\nIf any test commands fail to run (missing dependencies, wrong command, etc.), report the error as evidence for a FAIL verdict on the relevant criterion.\n\\`\\`\\`\n\n## Step 5: Format and Present the Verdict\n\nTake the subagent's response and present it to the user in this format:\n\n\\`\\`\\`\n## Verification Report -- [Spec Name]\n\n| # | Criterion | Verdict | Evidence |\n|---|-----------|---------|----------|\n| 1 | ... | PASS | ... |\n| 2 | ... | FAIL | ... |\n\n**Overall: X/Y criteria passed.**\n\n[If all passed:]\nAll criteria verified. Ready to commit and open a PR.\n\n[If any failed:]\nN failures need attention. Review the evidence above and fix before proceeding.\n\n[If any MANUAL CHECK NEEDED:]\nN criteria need manual verification -- they can't be checked by reading code or running tests alone.\n\\`\\`\\`\n\n## Step 6: Suggest Next Steps\n\nBased on the verdict:\n\n- **All PASS:** Suggest committing and opening a PR, or running \\`/joycraft-session-end\\` to capture discoveries.\n- **Some FAIL:** List the failed criteria and suggest the user fix them, then run \\`/joycraft-verify\\` again.\n- **MANUAL CHECK NEEDED items:** Explain what needs human eyes and why automation couldn't verify it.\n\n**Do NOT offer to fix failures yourself.** The verifier reports; the human (or implementation agent in a separate turn) decides what to do. This separation is the whole point.\n\n## Edge Cases\n\n| Scenario | Behavior |\n|----------|----------|\n| Spec has no Test Plan | Warn that verification is weaker without a test plan, but proceed by checking criteria through code reading and any available project-level tests |\n| All tests pass but a criterion is not testable | Mark as MANUAL CHECK NEEDED with explanation |\n| Subagent can't run tests (missing deps) | Report the error as FAIL evidence |\n| No specs found and no path given | Tell user to provide a spec path or create a spec first |\n| Spec status is \"Complete\" | Still run verification -- \"Complete\" means the implementer thinks it's done, verification confirms |\n`,\n\n \"joycraft-bugfix.md\": `---\nname: joycraft-bugfix\ndescription: Structured bug fix workflow — triage, diagnose, discuss with user, write a focused spec, hand off for implementation\n---\n\n# Bug Fix Workflow\n\nYou are fixing a bug. Follow this process in order. Do not skip steps.\n\n**Guard clause:** If the user's request is clearly a new feature — not a bug, error, or unexpected behavior — say:\n\"This sounds like a new feature rather than a bug fix. Try \\`/joycraft-new-feature\\` for a guided feature workflow.\"\nThen stop.\n\n---\n\n## Phase 1: Triage\n\nEstablish what's broken. Your goal is to reproduce the bug or at minimum understand the symptom clearly.\n\n**Ask / gather:**\n- What is the symptom? (error message, unexpected behavior, crash, wrong output)\n- What are the steps to reproduce?\n- What is the expected behavior vs. actual behavior?\n- When did it start? (recent change, always been this way, intermittent)\n- Any relevant logs, screenshots, or error output?\n\n**Actions:**\n- If the user provides an error message or stack trace, read the referenced files immediately\n- If steps to reproduce are provided, try to reproduce the bug (run the failing command, test, or request)\n- If the bug is intermittent or hard to reproduce, gather more context: environment, OS, versions, config\n\n**Done when:** You can describe the symptom in one sentence and have either reproduced it or have enough context to diagnose without reproduction.\n\n---\n\n## Phase 2: Diagnose\n\nFind the root cause. Read code, trace the execution path, identify what's wrong and why.\n\n**Actions:**\n- Start from the error site (stack trace, failing test, broken UI) and trace backward\n- Read the relevant source files — don't guess based on file names alone\n- Identify the specific line(s), condition, or logic error causing the bug\n- Check git blame or recent commits if the bug was introduced by a recent change\n- Look for related bugs — is this a symptom of a deeper issue?\n\n**Done when:** You can explain the root cause in 2-3 sentences: what's wrong, why it's wrong, and where in the code it happens.\n\n---\n\n## Phase 3: Discuss\n\nPresent your findings to the user. Do NOT start writing code or a spec yet.\n\n**Present:**\n1. **Symptom:** What the user sees (confirm your understanding matches theirs)\n2. **Root cause:** What's actually wrong in the code and why\n3. **Proposed fix:** What you think the fix is — be specific (which files, what changes)\n4. **Risk assessment:** What could go wrong with this fix? Any side effects?\n5. **Scope check:** Is this a simple fix or does it touch multiple systems?\n\n**Ask:**\n- \"Does this match what you're seeing?\"\n- \"Are you comfortable with this approach, or do you want to explore alternatives?\"\n- If the fix is large or risky: \"Should we decompose this into smaller specs?\"\n\n**Done when:** The user agrees with the diagnosis and proposed fix direction.\n\n---\n\n## Phase 4: Spec the Fix\n\nWrite a bug fix spec to \\`docs/specs/YYYY-MM-DD-bugfix-name.md\\`. Create the \\`docs/specs/\\` directory if it doesn't exist.\n\n**Why:** Even bug fixes deserve a spec. It forces clarity on what \"fixed\" means, ensures test-first discipline, and creates a traceable record of the fix.\n\nUse this template:\n\n\\`\\`\\`markdown\n# Fix [Bug Description] — Bug Fix Spec\n\n> **Parent Brief:** none (bug fix)\n> **Issue/Error:** [error message, issue link, or symptom description]\n> **Status:** Ready\n> **Date:** YYYY-MM-DD\n> **Estimated scope:** [1 session / N files / ~N lines]\n\n---\n\n## Bug\n\nWhat is broken? Describe the symptom the user experiences.\n\n## Root Cause\n\nWhat is wrong in the code and why? Name the specific file(s) and line(s).\n\n## Fix\n\nWhat changes will fix this? Be specific — describe the code change, not just \"fix the bug.\"\n\n## Acceptance Criteria\n\n- [ ] [The bug no longer occurs — describe the correct behavior]\n- [ ] [No regressions in related functionality]\n- [ ] Build passes\n- [ ] Tests pass\n\n## Test Plan\n\n| Acceptance Criterion | Test | Type |\n|---------------------|------|------|\n| [Bug no longer occurs] | [Test that reproduces the bug, then verifies the fix] | [unit/integration/e2e] |\n| [No regressions] | [Existing tests still pass, or new regression test] | [unit/integration] |\n\n**Execution order:**\n1. Write a test that reproduces the bug — it should FAIL (red)\n2. Run the test to confirm it fails\n3. Apply the fix\n4. Run the test to confirm it passes (green)\n5. Run the full test suite to check for regressions\n\n**Smoke test:** [The bug reproduction test — fastest way to verify the fix works]\n\n**Before implementing, verify your test harness:**\n1. Run the reproduction test — it must FAIL (if it passes, you're not testing the actual bug)\n2. The test must exercise your actual code — not a reimplementation or mock\n3. Identify your smoke test — it must run in seconds, not minutes\n\n## Constraints\n\n- MUST: [any hard requirements for the fix]\n- MUST NOT: [any prohibitions — e.g., don't change the public API]\n\n## Affected Files\n\n| Action | File | What Changes |\n|--------|------|-------------|\n\n## Edge Cases\n\n| Scenario | Expected Behavior |\n|----------|------------------|\n\\`\\`\\`\n\n**For trivial bugs:** The spec will be short. That's fine — the structure is the point, not the length.\n\n**For large bugs that span multiple files/systems:** Consider whether this should be decomposed into multiple specs. If so, create a brief first using \\`/joycraft-new-feature\\`, then decompose. A bug fix spec should be implementable in a single session.\n\n---\n\n## Phase 5: Hand Off\n\nTell the user:\n\n\\`\\`\\`\nBug fix spec is ready: docs/specs/YYYY-MM-DD-bugfix-name.md\n\nSummary:\n- Bug: [one sentence]\n- Root cause: [one sentence]\n- Fix: [one sentence]\n- Estimated: 1 session\n\nTo execute: Start a fresh session and:\n1. Read the spec\n2. Write the reproduction test (must fail)\n3. Apply the fix (test must pass)\n4. Run full test suite\n5. Run /joycraft-session-end to capture discoveries\n6. Commit and PR\n\nReady to start?\n\\`\\`\\`\n\n**Why:** A fresh session for implementation produces better results. This diagnostic session has context noise from exploration — a clean session with just the spec is more focused.\n`,\n\n};\n\nexport const TEMPLATES: Record<string, string> = {\n \"context/dangerous-assumptions.md\": `# Dangerous Assumptions\n\n> Things the AI agent might assume that are wrong in this project.\n> Generated by Joycraft risk interview. Update when you discover new gotchas.\n\n## Assumptions\n\n| Agent Might Assume | But Actually | Impact If Wrong |\n|-------------------|-------------|----------------|\n| _Example: All databases are dev/test_ | _The default connection is production_ | _Data loss_ |\n| _Example: Deleting and recreating is safe_ | _Some resources have manual config not in code_ | _Hours of manual recovery_ |\n\n## Historical Incidents\n\n| Date | What Happened | Lesson | Rule Added |\n|------|-------------|--------|------------|\n| _Example: 2026-03-15_ | _Agent deleted staging infra thinking it was temp_ | _Always verify environment before destructive ops_ | _NEVER: Delete cloud resources without listing them first_ |\n`,\n\n \"context/decision-log.md\": `# Decision Log\n\n> Why choices were made, not just what was chosen.\n> Update this when making architectural, tooling, or process decisions.\n> This is the institutional memory that prevents re-litigating settled questions.\n\n## Decisions\n\n| Date | Decision | Why | Alternatives Rejected | Revisit When |\n|------|----------|-----|----------------------|-------------|\n| _Example: 2026-03-15_ | _Use Supabase over Firebase_ | _Postgres flexibility, row-level security, self-hostable_ | _Firebase (vendor lock-in), PlanetScale (no RLS)_ | _If we need real-time sync beyond Supabase's capabilities_ |\n\n## Principles\n\n_Capture recurring decision patterns here — they save time on future choices._\n\n- _Example: \"Prefer tools we can self-host over pure SaaS — reduces vendor risk\"_\n- _Example: \"Choose boring technology for infrastructure, cutting-edge only for core differentiators\"_\n`,\n\n \"context/institutional-knowledge.md\": `# Institutional Knowledge\n\n> Unwritten rules, team conventions, and organizational context that AI agents can't derive from code.\n> This is the knowledge that takes a new developer months to absorb.\n> Update when you catch yourself saying \"oh, you didn't know about that?\"\n\n## Team Conventions\n\n_Things everyone on the team knows but nobody wrote down._\n\n- _Example: \"We never deploy on Fridays\"_\n- _Example: \"The CEO reviews all UI changes before they ship\"_\n- _Example: \"PR titles must reference the Jira ticket number\"_\n\n## Organizational Constraints\n\n_Business rules, compliance requirements, or political realities that affect technical decisions._\n\n- _Example: \"Legal requires all user data to be stored in EU regions\"_\n- _Example: \"The payments team owns the billing schema — never modify without their approval\"_\n- _Example: \"We have an informal agreement with Vendor X about API rate limits\"_\n\n## Historical Context\n\n_Why things are the way they are — especially when it looks wrong._\n\n- _Example: \"The auth module uses an old pattern because it predates our TypeScript migration — don't refactor without a spec\"_\n- _Example: \"The caching layer has a 5-second TTL because we had a consistency bug in 2025 — increasing it requires careful testing\"_\n\n## People & Ownership\n\n_Who owns what, who to ask, who cares about what._\n\n- _Example: \"Alice owns the payment pipeline — all changes need her review\"_\n- _Example: \"The data team is sensitive about query performance on the analytics tables\"_\n`,\n\n \"context/production-map.md\": `# Production Map\n\n> What's real, what's staging, what's safe to touch.\n> Generated by Joycraft risk interview. Update as your infrastructure evolves.\n\n## Services\n\n| Service | Environment | URL/Endpoint | Impact if Corrupted |\n|---------|-------------|-------------|-------------------|\n| _Example: Main DB_ | _Production_ | _postgres://prod.example.com_ | _1.9M user records lost_ |\n| _Example: Staging DB_ | _Staging_ | _postgres://staging.example.com_ | _Test data only, safe to reset_ |\n\n## Secrets & Credentials\n\n| Secret | Location | Notes |\n|--------|----------|-------|\n| _Example: DATABASE_URL_ | _.env.local_ | _Production connection — NEVER commit_ |\n\n## Safe to Touch\n\n- [ ] Staging environment at [URL]\n- [ ] Test/fixture data in [location]\n- [ ] Development API keys\n\n## NEVER Touch Without Explicit Approval\n\n- [ ] Production database\n- [ ] Live API endpoints\n- [ ] User-facing infrastructure\n`,\n\n \"context/troubleshooting.md\": `# Troubleshooting\n\n> What to do when things go wrong for non-code reasons.\n> Environment issues, flaky dependencies, hardware quirks, and diagnostic steps.\n> Update when you discover new failure modes and their fixes.\n\n## Common Failures\n\n| When This Happens | Do This | Don't Do This |\n|-------------------|---------|---------------|\n| _Example: Tests fail with ECONNREFUSED_ | _Check if the dev database is running_ | _Don't rewrite the test or mock the connection_ |\n| _Example: Build fails with out-of-memory_ | _Increase Node heap size or close other processes_ | _Don't simplify the code to reduce bundle size_ |\n| _Example: Lint passes locally but fails in CI_ | _Check Node/tool version mismatch between local and CI_ | _Don't disable the lint rule_ |\n\n## Environment Issues\n\n| Symptom | Likely Cause | Fix |\n|---------|-------------|-----|\n| _Example: \"Module not found\" after branch switch_ | _Dependencies changed on the new branch_ | _Run the package manager install command_ |\n| _Example: Port already in use_ | _Previous dev server didn't shut down cleanly_ | _Kill the process on that port or use a different one_ |\n| _Example: Permission denied on file/directory_ | _File ownership or permission mismatch_ | _Check and fix file permissions, don't run as root_ |\n\n## Diagnostic Steps\n\n_When something fails unexpectedly, follow this sequence before trying to fix the code:_\n\n1. **Check the error message literally** -- don't assume what it means, read it\n2. **Check environment prerequisites** -- are all services running? Correct versions?\n3. **Check recent changes** -- did a config file, dependency, or environment variable change?\n4. **Check network/connectivity** -- is the internet up? Are external services reachable?\n5. **Search project docs first** -- check this file and \\`docs/discoveries/\\` before web searching\n\n## \"Stop and Ask\" Scenarios\n\n_Situations where the AI agent should stop and ask the human instead of trying to fix things._\n\n- _Example: Hardware device not responding -- the human may need to physically reconnect it_\n- _Example: Authentication token expired -- the human needs to re-authenticate manually_\n- _Example: CI pipeline blocked by a required approval -- a human needs to approve it_\n- _Example: Error messages referencing infrastructure the agent doesn't have access to_\n`,\n\n \"examples/example-brief.md\": `# Add User Notifications — Feature Brief\n\n> **Date:** 2026-03-15\n> **Project:** acme-web\n> **Status:** Specs Ready\n\n---\n\n## Vision\n\nOur users have no idea when things happen in their account. A teammate comments on their pull request, a deployment finishes, a billing threshold is hit — they find out by accident, minutes or hours later. This is the #1 complaint in our last user survey.\n\nWe are building a notification system that delivers real-time and batched notifications across in-app, email, and (later) Slack channels. Users will have fine-grained control over what they receive and how. When this ships, no important event goes unnoticed, and no user gets buried in noise they didn't ask for.\n\nThe system is designed to be extensible — new event types plug in without touching the notification infrastructure. We start with three event types (PR comments, deploy status, billing alerts) and prove the pattern works before expanding.\n\n## User Stories\n\n- As a developer, I want to see a notification badge in the app when someone comments on my PR so that I can respond quickly\n- As a team lead, I want to receive an email when a production deployment fails so that I can coordinate the response\n- As a billing admin, I want to get alerted when usage exceeds 80% of our plan limit so that I can upgrade before service is disrupted\n- As any user, I want to control which notifications I receive and through which channels so that I am not overwhelmed\n\n## Hard Constraints\n\n- MUST: All notifications go through a single event bus — no direct coupling between event producers and delivery channels\n- MUST: Email delivery uses the existing SendGrid integration (do not add a new email provider)\n- MUST: Respect user preferences before delivering — never send a notification the user has opted out of\n- MUST NOT: Store notification content in plaintext in the database — use the existing encryption-at-rest pattern\n- MUST NOT: Send more than 50 emails per user per day (batch if necessary)\n\n## Out of Scope\n\n- NOT: Slack/Discord integration (Phase 2)\n- NOT: Push notifications / mobile (Phase 2)\n- NOT: Notification templates with rich HTML — plain text and simple markdown only for now\n- NOT: Admin dashboard for monitoring notification delivery rates\n- NOT: Retroactive notifications for events that happened before the feature ships\n\n## Decomposition\n\n| # | Spec Name | Description | Dependencies | Est. Size |\n|---|-----------|-------------|--------------|-----------|\n| 1 | add-notification-preferences-api | Create REST endpoints for users to read and update their notification preferences | None | M |\n| 2 | add-event-bus-infrastructure | Set up the internal event bus that decouples event producers from notification delivery | None | M |\n| 3 | add-notification-delivery-service | Build the service that consumes events, checks preferences, and dispatches to channels (in-app, email) | Spec 1, Spec 2 | L |\n| 4 | add-in-app-notification-ui | Add notification bell, dropdown, and badge count to the app header | Spec 3 | M |\n| 5 | add-email-batching | Implement daily digest batching for email notifications that exceed the per-user threshold | Spec 3 | S |\n\n## Execution Strategy\n\n- [x] Agent teams (parallel teammates within phases, sequential between phases)\n\n\\`\\`\\`\nPhase 1: Teammate A -> Spec 1 (preferences API), Teammate B -> Spec 2 (event bus)\nPhase 2: Teammate A -> Spec 3 (delivery service) — depends on Phase 1\nPhase 3: Teammate A -> Spec 4 (UI), Teammate B -> Spec 5 (batching) — both depend on Spec 3\n\\`\\`\\`\n\n## Success Criteria\n\n- [ ] User updates notification preferences via API, and subsequent events respect those preferences\n- [ ] A PR comment event triggers an in-app notification visible in the UI within 2 seconds\n- [ ] A deploy failure event sends an email to subscribed users via SendGrid\n- [ ] When email threshold (50/day) is exceeded, remaining notifications are batched into a daily digest\n- [ ] No regressions in existing PR, deployment, or billing features\n\n## External Scenarios\n\n| Scenario | What It Tests | Pass Criteria |\n|----------|--------------|---------------|\n| opt-out-respected | User disables email for deploy events, deploy fails | No email sent, in-app notification still appears |\n| batch-threshold | Send 51 email-eligible events for one user in a day | 50 individual emails + 1 digest containing the overflow |\n| preference-persistence | User sets preferences, logs out, logs back in | Preferences are unchanged |\n`,\n\n \"examples/example-spec.md\": `# Add Notification Preferences API — Atomic Spec\n\n> **Parent Brief:** \\`docs/briefs/2026-03-15-add-user-notifications.md\\`\n> **Status:** Ready\n> **Date:** 2026-03-15\n> **Estimated scope:** 1 session / 4 files / ~250 lines\n\n---\n\n## What\n\nAdd REST API endpoints that let users read and update their notification preferences. Each user gets a preferences record with per-event-type, per-channel toggles (e.g., \"PR comments: in-app=on, email=off\"). Preferences default to all-on for new users and are stored encrypted alongside the user profile.\n\n## Why\n\nThe notification delivery service (Spec 3) needs to check preferences before dispatching. Without this API, there is no way for users to control what they receive, and we cannot build the delivery pipeline.\n\n## Acceptance Criteria\n\n- [ ] \\`GET /api/v1/notifications/preferences\\` returns the current user's preferences as JSON\n- [ ] \\`PATCH /api/v1/notifications/preferences\\` updates one or more preference fields and returns the updated record\n- [ ] New users get default preferences (all channels enabled for all event types) on first read\n- [ ] Preferences are validated — unknown event types or channels return 400\n- [ ] Preferences are stored using the existing encryption-at-rest pattern (\\`EncryptedJsonColumn\\`)\n- [ ] Endpoint requires authentication (returns 401 for unauthenticated requests)\n- [ ] Build passes\n- [ ] Tests pass (unit + integration)\n\n## Test Plan\n\n| Acceptance Criterion | Test | Type |\n|---------------------|------|------|\n| GET returns preferences as JSON | Call GET with authenticated user, assert 200 + JSON shape matches preferences schema | integration |\n| PATCH updates preferences | Call PATCH with valid partial update, assert 200 + returned record reflects changes | integration |\n| New users get defaults | Call GET for user with no existing record, assert default preferences (all channels enabled) | unit |\n| Unknown event types return 400 | Call PATCH with \\`{\"foo\": {\"email\": true}}\\`, assert 400 + validation error | unit |\n| Stored with EncryptedJsonColumn | Verify model uses EncryptedJsonColumn for preferences field | unit |\n| Auth required | Call GET/PATCH without auth token, assert 401 | integration |\n| Build passes | Verified by build step — no separate test needed | build |\n| Tests pass | Verified by test runner — no separate test needed | meta |\n\n**Execution order:**\n1. Write all tests above — they should fail against current/stubbed code\n2. Run tests to confirm they fail (red)\n3. Implement until all tests pass (green)\n\n**Smoke test:** The \"New users get defaults\" unit test — no database or HTTP needed, fastest feedback loop.\n\n**Before implementing, verify your test harness:**\n1. Run all tests — they must FAIL (if they pass, you're testing the wrong thing)\n2. Each test calls your actual function/endpoint — not a reimplementation or the underlying library\n3. Identify your smoke test — it must run in seconds, not minutes, so you get fast feedback on each change\n\n## Constraints\n\n- MUST: Use the existing \\`EncryptedJsonColumn\\` utility for storage — do not roll a new encryption pattern\n- MUST: Follow the existing REST controller pattern in \\`src/controllers/\\`\n- MUST NOT: Expose other users' preferences (scope queries to authenticated user only)\n- SHOULD: Return the full preferences object on PATCH (not just the changed fields), so the frontend can replace state without merging\n\n## Affected Files\n\n| Action | File | What Changes |\n|--------|------|-------------|\n| Create | \\`src/controllers/notification-preferences.controller.ts\\` | New controller with GET and PATCH handlers |\n| Create | \\`src/models/notification-preferences.model.ts\\` | Sequelize model with EncryptedJsonColumn for preferences blob |\n| Create | \\`src/migrations/20260315-add-notification-preferences.ts\\` | Database migration to create notification_preferences table |\n| Create | \\`tests/controllers/notification-preferences.test.ts\\` | Unit and integration tests for both endpoints |\n| Modify | \\`src/routes/index.ts\\` | Register the new controller routes |\n\n## Approach\n\nCreate a \\`NotificationPreferences\\` model backed by a single \\`notification_preferences\\` table with columns: \\`id\\`, \\`user_id\\` (unique FK), \\`preferences\\` (EncryptedJsonColumn), \\`created_at\\`, \\`updated_at\\`. The \\`preferences\\` column stores a JSON blob shaped like \\`{ \"pr_comment\": { \"in_app\": true, \"email\": true }, \"deploy_status\": { ... } }\\`.\n\nThe GET endpoint does a find-or-create: if no record exists for the user, create one with defaults and return it. The PATCH endpoint deep-merges the request body into the existing preferences, validates the result against a known schema of event types and channels, and saves.\n\n**Rejected alternative:** Storing preferences as individual rows (one per event-type-channel pair). This would make queries more complex and would require N rows per user instead of 1. The JSON blob approach is simpler and matches how the frontend will consume the data.\n\n## Edge Cases\n\n| Scenario | Expected Behavior |\n|----------|------------------|\n| PATCH with empty body \\`{}\\` | Return 200 with unchanged preferences (no-op) |\n| PATCH with unknown event type \\`{\"foo\": {\"email\": true}}\\` | Return 400 with validation error listing valid event types |\n| GET for user with no existing record | Create default preferences, return 200 |\n| Concurrent PATCH requests | Last-write-wins (optimistic, no locking) — acceptable for user preferences |\n`,\n\n \"scenarios/README.md\": `# \\$SCENARIOS_REPO\n\nHoldout scenario tests for the main project. These tests run in CI against the\nbuilt artifact of each PR — but they live here, in a separate repository, so\nthe coding agent working on the main project cannot see them.\n\n---\n\n## What is the holdout pattern?\n\nThink of it like a validation set in machine learning. When you train a model,\nyou keep a slice of your data hidden from the training process. If the model\nscores well on data it has never seen, you can trust that it has actually\nlearned something — not just memorized the training examples.\n\nScenario tests work the same way. The coding agent writes code and passes\ninternal tests in the main repo. These scenario tests then check whether the\nresult behaves correctly from a real user's perspective, using only the public\ninterface of the built artifact.\n\nBecause the agent cannot read this repository, it cannot game the tests. A\npassing scenario run means the feature genuinely works.\n\n---\n\n## Why a separate repository?\n\nA single repository would expose the tests to the agent. Claude Code reads\nfiles in the working directory; if scenario tests lived in the main repo, the\nagent could (and would) read them when fixing failures, which defeats the\npurpose.\n\nA separate repo also means:\n\n- The test suite can be updated by humans without triggering the autofix loop\n- Scenarios can reference multiple projects over time\n- Access controls are independent — the scenarios repo can be more restricted\n\n---\n\n## How the CI pipeline works\n\n\\`\\`\\`\nMain repo PR opened\n |\n v\nMain repo CI runs (unit + integration tests)\n |\n | passes\n v\nscenarios-dispatch.yml fires a repository_dispatch event\n |\n v\nThis repo: run.yml receives the event\n |\n +-- clones main-repo PR branch to ../main-repo\n |\n +-- builds the artifact (npm ci && npm run build)\n |\n +-- runs: NO_COLOR=1 npx vitest run\n |\n +-- captures exit code + output\n |\n v\nPosts PASS / FAIL comment on the originating PR\n\\`\\`\\`\n\nThe PR author sees the scenario result as a comment. No separate status check\nis required, though you can add one via the GitHub Checks API if you prefer.\n\n---\n\n## Testing by Stack Type\n\nThe scenario agent selects the appropriate test format based on the project's\ntesting backbone. Each backbone tests the same holdout principle — observable\nbehavior only, no source imports — but uses different tools.\n\n### Web Apps (Playwright)\n\nFor Next.js, Vite, Nuxt, Remix, and other web frameworks. Tests run against a\ndev server or preview URL using a headless browser.\n\n- **Template:** \\`example-scenario-web.spec.ts\\`\n- **Config:** \\`playwright.config.ts\\`\n- **Package:** \\`package-web.json\\` (use instead of \\`package.json\\` for web projects)\n- **Run:** \\`npx playwright test\\`\n\n### Mobile Apps (Maestro)\n\nFor React Native, Flutter, and native iOS/Android. Tests are declarative YAML\nflows that interact with a running app on a simulator.\n\n- **Template:** \\`example-scenario-mobile.yaml\\`\n- **Login sub-flow:** \\`example-scenario-mobile-login.yaml\\`\n- **Setup guide:** \\`README-mobile.md\\`\n- **Run:** \\`maestro test example-scenario-mobile.yaml\\`\n\n### API Backends (HTTP)\n\nFor Express, FastAPI, Django, and other API-only backends. Tests send HTTP\nrequests using Node.js built-in \\`fetch\\`.\n\n- **Template:** \\`example-scenario-api.test.ts\\`\n- **Run:** \\`npx vitest run\\`\n\n### CLI Tools & Libraries (native)\n\nFor CLI tools, npm packages, and non-UI projects. Tests invoke the built\nbinary via \\`spawnSync\\` and assert on stdout/stderr.\n\n- **Template:** \\`example-scenario.test.ts\\`\n- **Run:** \\`npx vitest run\\`\n\n---\n\n## Adding scenarios\n\n### Rules\n\nThese rules apply to ALL backbones:\n\n1. **Behavioral, not structural.** Test what the app does from a user's\n perspective. For web: navigate and assert on content. For CLI: run commands\n and check output. For API: send requests and check responses.\n\n2. **End-to-end.** Each test should represent something a real user would\n actually do. If you would not put it in a demo or docs example, reconsider\n whether it belongs here.\n\n3. **No source imports.** The entire point of the holdout is that tests cannot\n see source code. Any \\`import\\` that reaches into \\`../main-repo/src\\` breaks\n the pattern.\n\n4. **Independent.** Each test must be able to run in isolation. No shared\n mutable state between tests.\n\n5. **Deterministic.** Avoid network calls, timestamps, or random values in\n assertions unless the feature under test genuinely involves them.\n\n### File layout\n\n\\`\\`\\`\n\\$SCENARIOS_REPO/\n├── example-scenario.test.ts # CLI/binary scenario template\n├── example-scenario-web.spec.ts # Web app scenario template (Playwright)\n├── example-scenario-api.test.ts # API backend scenario template\n├── example-scenario-mobile.yaml # Mobile app scenario template (Maestro)\n├── example-scenario-mobile-login.yaml # Reusable login sub-flow\n├── playwright.config.ts # Playwright config (web projects)\n├── package.json # Default (vitest for CLI/API)\n├── package-web.json # Alternative (Playwright for web)\n├── README-mobile.md # Mobile testing setup guide\n├── workflows/\n│ ├── run.yml # CI workflow (do not rename)\n│ └── generate.yml # Scenario generation workflow\n├── prompts/\n│ └── scenario-agent.md # Scenario agent instructions\n└── README.md\n\\`\\`\\`\n\nUse the template that matches your project's stack. Remove the ones you\ndon't need.\n\n---\n\n## Internal tests vs scenario tests\n\n| | Internal tests (main repo) | Scenario tests (this repo) |\n|---|---|---|\n| Location | \\`tests/\\` in main repo | This repo |\n| Visible to agent | Yes | No |\n| What they test | Units, modules, logic | End-to-end behavior |\n| Import source code | Yes | Never |\n| Test method | Unit test framework | Depends on backbone (Playwright/Maestro/vitest/fetch) |\n| Run on every push | Yes | Yes (via dispatch) |\n| Purpose | Catch regressions fast | Validate real behavior |\n\n---\n\n## Relationship to Joycraft\n\nThis repository was bootstrapped by \\`npx joycraft init --autofix\\`. Joycraft\nmanages the \\`run.yml\\` workflow and keeps it in sync when you run\n\\`npx joycraft upgrade\\`. The test files are yours — Joycraft will never\noverwrite them.\n\nIf the \\`run.yml\\` workflow needs updating (e.g., a new version of\n\\`actions/create-github-app-token\\`), run \\`npx joycraft upgrade\\` in this repo\nand review the diff before applying.\n`,\n\n \"scenarios/example-scenario.test.ts\": `/**\n * Example Scenario Test\n *\n * This file is a template for scenario tests in your holdout repository.\n * Scenarios are behavioral, end-to-end tests that run against the BUILT\n * artifact of your main project — not its source code.\n *\n * The Holdout Pattern\n * -------------------\n * These tests live in a SEPARATE repository that your coding agent cannot\n * see. This is intentional: if the agent could read these tests, it could\n * write code that passes them without actually solving the problem correctly\n * (the same way a student who sees the exam beforehand can score well without\n * understanding the material).\n *\n * In CI, the main repo is cloned to ../main-repo (relative to this repo's\n * checkout). The run.yml workflow builds the artifact there before running\n * these tests, so \\`../main-repo\\` is always available and already built.\n *\n * How to Write Scenarios\n * ----------------------\n * DO:\n * - Invoke the built binary / entry point via child_process (execSync, spawnSync)\n * - Test observable behavior: exit codes, stdout/stderr content, file system state\n * - Write scenarios around things a real user would actually do\n * - Keep each test fully independent — no shared state between tests\n *\n * DON'T:\n * - Import from ../main-repo/src — that defeats the holdout\n * - Test internal implementation details (function names, module structure)\n * - Rely on network access unless your tool genuinely requires it\n * - Share mutable fixtures across tests\n */\n\nimport { execSync, spawnSync } from \"node:child_process\";\nimport { existsSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\n// Path to the built CLI entry point in the main repo.\n// The run.yml workflow clones the main repo to ../main-repo and builds it\n// before this test file runs, so this path is always valid in CI.\nconst CLI = join(__dirname, \"..\", \"main-repo\", \"dist\", \"cli.js\");\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Run the CLI and return { stdout, stderr, status }. Never throws. */\nfunction runCLI(args: string[], cwd?: string) {\n const result = spawnSync(\"node\", [CLI, ...args], {\n encoding: \"utf8\",\n cwd: cwd ?? process.cwd(),\n env: { ...process.env, NO_COLOR: \"1\" },\n });\n return {\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n status: result.status ?? 1,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Basic invocation scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"CLI: basic invocation\", () => {\n it(\"--help prints usage information\", () => {\n const { stdout, status } = runCLI([\"--help\"]);\n expect(status).toBe(0);\n expect(stdout).toContain(\"Usage:\");\n });\n\n it(\"--version returns a semver string\", () => {\n const { stdout, status } = runCLI([\"--version\"]);\n expect(status).toBe(0);\n // Matches x.y.z, x.y.z-alpha.1, etc.\n expect(stdout.trim()).toMatch(/^\\\\d+\\\\.\\\\d+\\\\.\\\\d+/);\n });\n\n it(\"unknown command exits non-zero\", () => {\n const { status } = runCLI([\"not-a-real-command\"]);\n expect(status).not.toBe(0);\n });\n});\n\n// ---------------------------------------------------------------------------\n// Example: filesystem interaction scenario\n//\n// This pattern is useful when your CLI creates or modifies files.\n// Each test gets a fresh temp directory so they can't interfere.\n// ---------------------------------------------------------------------------\n\ndescribe(\"CLI: init command (example — replace with your real scenarios)\", () => {\n let tmpDir: string;\n\n beforeEach(() => {\n tmpDir = mkdtempSync(join(tmpdir(), \"scenarios-\"));\n });\n\n afterEach(() => {\n rmSync(tmpDir, { recursive: true, force: true });\n });\n\n it(\"init creates expected output in an empty directory\", () => {\n // This is a placeholder. Replace with whatever your CLI actually does.\n // The point is: invoke the binary, observe side effects, assert on them.\n const { status } = runCLI([\"init\", tmpDir]);\n\n // Example assertions — adjust to your tool's actual behavior:\n // expect(status).toBe(0);\n // expect(existsSync(join(tmpDir, \"CLAUDE.md\"))).toBe(true);\n\n // Remove this line once you've written a real assertion above:\n expect(typeof status).toBe(\"number\"); // placeholder\n });\n});\n`,\n\n \"scenarios/package.json\": `{\n \"name\": \"\\$SCENARIOS_REPO\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"test\": \"vitest run\"\n },\n \"devDependencies\": {\n \"vitest\": \"^3.0.0\"\n }\n}\n`,\n\n \"scenarios/package-web.json\": `{\n \"name\": \"\\$SCENARIOS_REPO\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"test\": \"playwright test\"\n },\n \"devDependencies\": {\n \"@playwright/test\": \"^1.50.0\"\n }\n}\n`,\n\n \"scenarios/playwright.config.ts\": `import { defineConfig } from '@playwright/test';\n\n/**\n * Playwright configuration for holdout scenario tests.\n *\n * BASE_URL can be set to test against a preview deployment URL\n * or defaults to http://localhost:3000 for local dev server testing.\n */\nexport default defineConfig({\n testDir: '.',\n testMatch: '**/*.spec.ts',\n timeout: 60_000,\n retries: 0,\n use: {\n baseURL: process.env.BASE_URL || 'http://localhost:3000',\n headless: true,\n screenshot: 'only-on-failure',\n },\n projects: [\n { name: 'chromium', use: { browserName: 'chromium' } },\n ],\n});\n`,\n\n \"scenarios/example-scenario-web.spec.ts\": `/**\n * Example Web Scenario Test (Playwright)\n *\n * This file is a template for scenario tests against web applications.\n * The holdout pattern applies: test the running app through its UI,\n * never import source code from the main repo.\n *\n * The main repo is available at ../main-repo and is already built.\n * Tests run against either:\n * - A dev server started from ../main-repo (default)\n * - A preview deployment URL (set BASE_URL env var)\n *\n * DO:\n * - Navigate to pages, click elements, fill forms, assert on visible content\n * - Use page.locator() with accessible selectors (role, text, test-id)\n * - Keep each test fully independent\n *\n * DON'T:\n * - Import from ../main-repo/src — that defeats the holdout\n * - Test internal implementation details\n * - Rely on specific CSS classes or DOM structure (use accessible selectors)\n */\n\nimport { test, expect } from '@playwright/test';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { join } from 'node:path';\n\nconst MAIN_REPO = join(__dirname, '..', 'main-repo');\nlet serverProcess: ChildProcess | undefined;\n\n/**\n * Wait for a URL to become reachable.\n */\nasync function waitForServer(url: string, timeoutMs = 60_000): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n try {\n const res = await fetch(url);\n if (res.ok || res.status < 500) return;\n } catch {\n // Server not ready yet\n }\n await new Promise(r => setTimeout(r, 1000));\n }\n throw new Error(\\`Server at \\${url} did not become ready within \\${timeoutMs}ms\\`);\n}\n\ntest.beforeAll(async () => {\n // If BASE_URL is set, skip starting a dev server — test against the provided URL\n if (process.env.BASE_URL) return;\n\n serverProcess = spawn('npm', ['run', 'dev'], {\n cwd: MAIN_REPO,\n stdio: 'pipe',\n env: { ...process.env, PORT: '3000' },\n });\n\n await waitForServer('http://localhost:3000');\n});\n\ntest.afterAll(async () => {\n if (serverProcess) {\n serverProcess.kill('SIGTERM');\n serverProcess = undefined;\n }\n});\n\n// ---------------------------------------------------------------------------\n// Example scenarios — replace with real tests for your application\n// ---------------------------------------------------------------------------\n\ntest.describe('Home page', () => {\n test('loads successfully and shows main heading', async ({ page }) => {\n await page.goto('/');\n // Replace with your app's actual heading or key element\n await expect(page.locator('h1')).toBeVisible();\n });\n\n test('navigates to a subpage', async ({ page }) => {\n await page.goto('/');\n // Replace with your app's actual navigation\n // await page.click('text=About');\n // await expect(page).toHaveURL(/\\\\/about/);\n // await expect(page.locator('h1')).toContainText('About');\n });\n});\n`,\n\n \"scenarios/example-scenario-api.test.ts\": `/**\n * Example API Scenario Test\n *\n * This file is a template for scenario tests against API-only backends.\n * The holdout pattern applies: test the running server via HTTP requests,\n * never import route handlers or source code from the main repo.\n *\n * The main repo is available at ../main-repo and is already built.\n * Tests run against either:\n * - A server started from ../main-repo (default)\n * - A deployed URL (set BASE_URL env var)\n *\n * Uses Node.js built-in fetch — no additional HTTP client dependencies.\n *\n * DO:\n * - Send HTTP requests to endpoints, assert on status codes and response bodies\n * - Test realistic user actions (create, read, update, delete flows)\n * - Keep each test fully independent\n *\n * DON'T:\n * - Import from ../main-repo/src — that defeats the holdout\n * - Use supertest or similar tools that import the app directly\n * - Test internal implementation details\n */\n\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { join } from 'node:path';\n\nconst MAIN_REPO = join(__dirname, '..', 'main-repo');\nconst BASE_URL = process.env.BASE_URL || 'http://localhost:3000';\nlet serverProcess: ChildProcess | undefined;\n\n/**\n * Wait for a URL to become reachable.\n */\nasync function waitForServer(url: string, timeoutMs = 60_000): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n try {\n const res = await fetch(url);\n if (res.ok || res.status < 500) return;\n } catch {\n // Server not ready yet\n }\n await new Promise(r => setTimeout(r, 1000));\n }\n throw new Error(\\`Server at \\${url} did not become ready within \\${timeoutMs}ms\\`);\n}\n\nbeforeAll(async () => {\n // If BASE_URL is set externally, skip starting a server\n if (process.env.BASE_URL) return;\n\n serverProcess = spawn('npm', ['start'], {\n cwd: MAIN_REPO,\n stdio: 'pipe',\n env: { ...process.env, PORT: '3000' },\n });\n\n await waitForServer(BASE_URL);\n}, 90_000);\n\nafterAll(() => {\n if (serverProcess) {\n serverProcess.kill('SIGTERM');\n serverProcess = undefined;\n }\n});\n\n// ---------------------------------------------------------------------------\n// Example scenarios — replace with real tests for your API\n// ---------------------------------------------------------------------------\n\ndescribe('API health', () => {\n it('GET / returns a success status', async () => {\n const res = await fetch(\\`\\${BASE_URL}/\\`);\n expect(res.status).toBeLessThan(500);\n });\n});\n\ndescribe('API endpoints', () => {\n it('GET /api/example returns JSON', async () => {\n const res = await fetch(\\`\\${BASE_URL}/api/example\\`);\n // Replace with your actual endpoint\n // expect(res.status).toBe(200);\n // const body = await res.json();\n // expect(body).toHaveProperty('data');\n });\n\n it('POST /api/example creates a resource', async () => {\n // Replace with your actual endpoint and payload\n // const res = await fetch(\\\\\\`\\\\\\${BASE_URL}/api/example\\\\\\`, {\n // method: 'POST',\n // headers: { 'Content-Type': 'application/json' },\n // body: JSON.stringify({ name: 'test' }),\n // });\n // expect(res.status).toBe(201);\n // const body = await res.json();\n // expect(body).toHaveProperty('id');\n });\n\n it('returns 404 for unknown routes', async () => {\n const res = await fetch(\\`\\${BASE_URL}/api/does-not-exist\\`);\n expect(res.status).toBe(404);\n });\n});\n`,\n\n \"scenarios/example-scenario-mobile.yaml\": `# Example Mobile Scenario Test (Maestro)\n#\n# This file is a template for scenario tests against mobile applications.\n# The holdout pattern applies: test the running app through its UI,\n# never reference source code from the main repo.\n#\n# Maestro tests are declarative YAML flows that interact with a running\n# app on a simulator/emulator. Install Maestro:\n# curl -Ls \"https://get.maestro.mobile.dev\" | bash\n#\n# Run this flow:\n# maestro test example-scenario-mobile.yaml\n#\n# DO:\n# - Tap elements, fill inputs, assert on visible text\n# - Use runFlow for reusable sub-flows (e.g., login)\n# - Use assertWithAI for natural-language assertions\n#\n# DON'T:\n# - Reference source code paths or internal identifiers\n# - Depend on exact pixel positions (use text and accessibility labels)\n\nappId: com.example.myapp # Replace with your app's bundle identifier\nname: \"Core User Journey\"\ntags:\n - smoke\n - holdout\n---\n# Step 1: Launch the app\n- launchApp\n\n# Step 2: Login (using a reusable sub-flow)\n- runFlow: example-scenario-mobile-login.yaml\n\n# Step 3: Verify the main screen loaded\n- assertVisible: \"Home\"\n\n# Step 4: Navigate to a feature\n# - tapOn: \"Settings\"\n# - assertVisible: \"Account\"\n\n# Step 5: AI-powered assertion (natural language)\n# - assertWithAI: \"The main dashboard is visible with navigation tabs at the bottom\"\n\n# Step 6: Go back\n# - back\n# - assertVisible: \"Home\"\n`,\n\n \"scenarios/example-scenario-mobile-login.yaml\": `# Reusable Login Sub-Flow (Maestro)\n#\n# This flow handles authentication. Other flows include it via:\n# - runFlow: example-scenario-mobile-login.yaml\n#\n# Replace the selectors and credentials with your app's actual login flow.\n\nappId: com.example.myapp\nname: \"Login\"\n---\n- assertVisible: \"Sign In\"\n- tapOn: \"Email\"\n- inputText: \"test@example.com\"\n- tapOn: \"Password\"\n- inputText: \"testpassword123\"\n- tapOn: \"Log In\"\n- assertVisible: \"Home\" # Verify login succeeded\n`,\n\n \"scenarios/README-mobile.md\": \"# Mobile Scenario Testing with Maestro\\n\\nThis guide explains how to set up and run mobile holdout scenario tests using [Maestro](https://maestro.dev/).\\n\\n## Prerequisites\\n\\n- **Maestro CLI:** `curl -Ls \\\"https://get.maestro.mobile.dev\\\" | bash`\\n- **Java 17+** (required by Maestro)\\n- **Simulator/Emulator:**\\n - iOS: Xcode with iOS Simulator (macOS only)\\n - Android: Android Studio with an AVD configured\\n\\n> **Important:** Joycraft does not install Maestro or manage simulators. This is your responsibility.\\n\\n## Running Tests Locally\\n\\n```bash\\n# Boot your simulator/emulator first, then:\\nmaestro test example-scenario-mobile.yaml\\n\\n# Run all flows in a directory:\\nmaestro test .maestro/\\n```\\n\\n## Writing Flows\\n\\nMaestro flows are declarative YAML. Core commands:\\n\\n| Command | Purpose |\\n|---------|--------|\\n| `launchApp` | Start or restart the app |\\n| `tapOn: \\\"text\\\"` | Tap an element by visible text or test ID |\\n| `inputText: \\\"value\\\"` | Type into a focused field |\\n| `assertVisible: \\\"text\\\"` | Assert an element is on screen |\\n| `assertNotVisible: \\\"text\\\"` | Assert an element is NOT on screen |\\n| `scroll` | Scroll down |\\n| `back` | Press the back button |\\n| `runFlow: file.yaml` | Run a reusable sub-flow |\\n| `assertWithAI: \\\"description\\\"` | Natural-language assertion (AI-powered) |\\n\\n## CI Options\\n\\n### Option A: Maestro Cloud (paid, easiest)\\n\\nUpload your app binary and flows to Maestro Cloud. No simulator management.\\n\\n```yaml\\n- uses: mobile-dev-inc/action-maestro-cloud@v2\\n with:\\n api-key: ${{ secrets.MAESTRO_API_KEY }}\\n app-file: app.apk # or app.ipa\\n workspace: .\\n```\\n\\n### Option B: Self-hosted emulator (free, more setup)\\n\\nSpin up an Android emulator on a Linux runner or iOS simulator on a macOS runner.\\n\\n> **Cost note:** macOS GitHub Actions runners are ~10x more expensive than Linux runners.\\n\\n## The Holdout Pattern\\n\\nThese tests live in the scenarios repo, separate from the main codebase. The scenario agent generates them from specs. They test observable behavior through the app's UI — never referencing source code or internal implementation.\\n\",\n\n \"scenarios/prompts/scenario-agent.md\": `You are a QA engineer working in a holdout test repository. You CANNOT access the main repository's source code. Your job is to write or update behavioral scenario tests based on specs that are pushed from the main repo.\n\n## What You Have Access To\n\n- This scenarios repository (test files, \\`specs/\\` mirror, \\`package.json\\`)\n- The incoming spec (provided below)\n- A list of existing test files and spec mirrors (provided below)\n- The main repo is available at \\`../main-repo\\` and is already built\n- The testing strategy for this project (provided below)\n\n## Testing Strategy\n\nThis project uses the **\\$TESTING_BACKBONE** testing backbone.\n\nSelect the correct test format based on the backbone:\n\n| Backbone | Tool | Test Format | File Extension | How to Test |\n|----------|------|-------------|---------------|-------------|\n| \\`playwright\\` | Playwright | Browser-based E2E | \\`.spec.ts\\` | Navigate pages, click elements, assert on visible content |\n| \\`maestro\\` | Maestro | YAML flows | \\`.yaml\\` | Tap elements, fill inputs, assert on screen state |\n| \\`api\\` | fetch (Node.js built-in) | HTTP requests | \\`.test.ts\\` | Send requests to endpoints, assert on responses |\n| \\`native\\` | vitest + spawnSync | CLI/binary invocation | \\`.test.ts\\` | Run commands, assert on stdout/stderr/exit codes |\n\nIf the backbone is not provided or unrecognized, default to \\`native\\`.\n\n## Triage Decision Tree\n\nRead the incoming spec carefully. Decide which of these three actions to take:\n\n### SKIP — Do nothing if the spec is:\n- An internal refactor with no user-facing behavior change (e.g., \"extract module\", \"rename internal type\")\n- CI or dev tooling changes (e.g., \"add lint rule\", \"update GitHub Actions workflow\")\n- Documentation-only changes\n- Performance improvements with identical observable behavior\n\nIf you SKIP, write a brief comment in the relevant test file (or a new one) explaining why, then stop.\n\n### NEW — Create a new test file if the spec describes:\n- A new command, flag, or subcommand\n- A new output format or file that gets generated\n- A new user-facing behavior that doesn't map to any existing test file\n\nName the file after the feature area using the correct extension for the backbone.\n\n### UPDATE — Modify an existing test file if the spec:\n- Changes behavior that is already tested\n- Adds a flag or option to an existing command\n- Modifies output format for an existing feature\n\nMatch to the most relevant existing test file by feature area.\n\n**If you are unsure whether a spec is user-facing, err on the side of writing a test.**\n\n## Test Writing Rules (All Backbones)\n\n1. **Behavioral only.** Test observable behavior — what a real user would see. Never test internal implementation details or import source modules.\n2. **Each test is fully independent.** No shared mutable state between tests.\n3. **Assert on realistic user actions.** Write tests that reflect what a real user would do.\n4. **Never import from the parent repo's source.** If you find yourself writing \\`import { ... } from '../main-repo/src/...'\\`, stop — that defeats the holdout.\n\n## Backbone: native (CLI/Binary)\n\nUse when the project is a CLI tool, library, or has no web/mobile UI.\n\n\\`\\`\\`typescript\nimport { spawnSync } from 'node:child_process';\nimport { mkdtempSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { describe, it, expect, beforeEach, afterEach } from 'vitest';\n\nconst CLI = join(__dirname, '..', 'main-repo', 'dist', 'cli.js');\n\nfunction runCLI(args: string[], cwd?: string) {\n const result = spawnSync('node', [CLI, ...args], {\n encoding: 'utf8',\n cwd: cwd ?? process.cwd(),\n env: { ...process.env, NO_COLOR: '1' },\n });\n return { stdout: result.stdout ?? '', stderr: result.stderr ?? '', status: result.status ?? 1 };\n}\n\ndescribe('[feature area]', () => {\n let tmpDir: string;\n beforeEach(() => { tmpDir = mkdtempSync(join(tmpdir(), 'scenarios-')); });\n afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); });\n\n it('[observable behavior]', () => {\n const { stdout, status } = runCLI(['command', 'args'], tmpDir);\n expect(status).toBe(0);\n expect(stdout).toContain('expected output');\n });\n});\n\\`\\`\\`\n\n## Backbone: playwright (Web Apps)\n\nUse when the project is a web application (Next.js, Vite, Nuxt, etc.).\n\n\\`\\`\\`typescript\nimport { test, expect } from '@playwright/test';\n\n// Tests run against BASE_URL (configured in playwright.config.ts)\n// The dev server is started automatically or BASE_URL points to a preview deploy\n\ntest.describe('[feature area]', () => {\n test('[observable behavior]', async ({ page }) => {\n await page.goto('/');\n await expect(page.locator('h1')).toBeVisible();\n });\n\n test('[user interaction]', async ({ page }) => {\n await page.goto('/login');\n await page.fill('[name=\"email\"]', 'test@example.com');\n await page.click('button[type=\"submit\"]');\n await expect(page).toHaveURL(/dashboard/);\n });\n});\n\\`\\`\\`\n\n## Backbone: api (API Backends)\n\nUse when the project is an API-only backend (Express, FastAPI, etc.).\n\n\\`\\`\\`typescript\nimport { describe, it, expect } from 'vitest';\n\nconst BASE_URL = process.env.BASE_URL || 'http://localhost:3000';\n\ndescribe('[feature area]', () => {\n it('[endpoint behavior]', async () => {\n const res = await fetch(\\\\\\`\\\\\\${BASE_URL}/api/endpoint\\\\\\`);\n expect(res.status).toBe(200);\n const body = await res.json();\n expect(body).toHaveProperty('data');\n });\n\n it('[error handling]', async () => {\n const res = await fetch(\\\\\\`\\\\\\${BASE_URL}/api/not-found\\\\\\`);\n expect(res.status).toBe(404);\n });\n});\n\\`\\`\\`\n\n## Backbone: maestro (Mobile Apps)\n\nUse when the project is a mobile application (React Native, Flutter, native iOS/Android).\n\n\\`\\`\\`yaml\nappId: com.example.myapp\nname: \"[feature area]: [behavior being tested]\"\ntags:\n - holdout\n---\n- launchApp\n- tapOn: \"Sign In\"\n- inputText: \"test@example.com\"\n- tapOn: \"Submit\"\n- assertVisible: \"Welcome\"\n# Use assertWithAI for complex visual assertions:\n# - assertWithAI: \"The dashboard shows a list of recent items\"\n\\`\\`\\`\n\n## Graceful Degradation\n\nIf the primary backbone tool is not available in this repo, fall back to the next deepest testable layer:\n\n| Layer | What's Tested | When to Use |\n|-------|-------------|-------------|\n| **Layer 4: UI** | Full user flows through browser/simulator | \\`@playwright/test\\` or Maestro is installed |\n| **Layer 3: API** | HTTP requests against running server | Server can be started from \\`../main-repo\\` |\n| **Layer 2: Logic** | Unit tests via test runner | Test runner (vitest/jest) is available |\n| **Layer 1: Static** | Build, typecheck, lint | Build toolchain is available |\n\n**Fallback rules:**\n- If backbone is \\`playwright\\` but \\`@playwright/test\\` is NOT in this repo's \\`package.json\\`: fall back to \\`api\\` (fetch-based HTTP tests)\n- If backbone is \\`maestro\\` but no simulator context is available: fall back to \\`api\\` if a server can be started, else \\`native\\`\n- If backbone is \\`api\\` but no server start script exists: fall back to \\`native\\`\n- \\`native\\` is always available as the floor\n\nStart each test file with a comment indicating the testing layer:\n\\`// Testing Layer: [4|3|2|1] - [UI|API|Logic|Static]\\`\n\nIf you fell back from the intended backbone, note this in your commit message:\n\\`scenarios: [action] for [spec] (layer: [N], reason: [why])\\`\n\n## Checklist Before Committing\n\n- [ ] Decision: SKIP / NEW / UPDATE (and why)\n- [ ] Correct backbone selected (or fallback justified)\n- [ ] Tests assert on observable behavior, not implementation\n- [ ] No imports from \\`../main-repo/src\\`\n- [ ] Each test is independent (own temp dir, own state)\n- [ ] File uses the correct extension for the backbone\n- [ ] Testing layer comment at top of file\n`,\n\n \"scenarios/workflows/generate.yml\": `# Scenario Generation Workflow\n#\n# Triggered by a \\`spec-pushed\\` repository_dispatch event sent from the main\n# project when a spec is added or modified on main. A scenario agent triages\n# the spec and writes or updates holdout tests in this repo.\n#\n# After the agent commits changes, fires \\`scenarios-updated\\` back to the main\n# repo so that any open PRs are re-tested with the new/updated scenarios.\n#\n# Prerequisites:\n# - ANTHROPIC_API_KEY secret: Anthropic API key for Claude Code\n# - JOYCRAFT_APP_PRIVATE_KEY secret: GitHub App private key (.pem)\n# - \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time\n\nname: Generate Scenarios\n\non:\n repository_dispatch:\n types: [spec-pushed]\n\njobs:\n generate:\n name: Run scenario agent\n runs-on: ubuntu-latest\n\n steps:\n # ── 1. Check out the scenarios repo ──────────────────────────────────\n - name: Checkout scenarios repo\n uses: actions/checkout@v4\n\n # ── 2. Save incoming spec to local mirror ─────────────────────────────\n # The agent reads this file to understand what changed.\n - name: Save spec to mirror\n run: |\n mkdir -p specs\n cat > \"specs/\\${{ github.event.client_payload.spec_filename }}\" << 'SPEC_EOF'\n \\${{ github.event.client_payload.spec_content }}\n SPEC_EOF\n echo \"Saved \\${{ github.event.client_payload.spec_filename }} to specs/\"\n\n # ── 3. Gather context for the agent ───────────────────────────────────\n # Bounded context: filenames only (not file contents) to stay within\n # token limits. The agent uses these lists to decide whether to create\n # a new test file or update an existing one.\n - name: Gather context\n id: context\n run: |\n EXISTING_TESTS=\\$(find . -name \"*.test.ts\" -not -path \"./.git/*\" \\\\\n | sed 's|^\\\\./||' | sort | tr '\\\\n' ',' | sed 's/,\\$//')\n EXISTING_SPECS=\\$(find specs/ -name \"*.md\" 2>/dev/null \\\\\n | sed 's|^specs/||' | sort | tr '\\\\n' ',' | sed 's/,\\$//')\n\n echo \"existing_tests=\\$EXISTING_TESTS\" >> \"\\$GITHUB_OUTPUT\"\n echo \"existing_specs=\\$EXISTING_SPECS\" >> \"\\$GITHUB_OUTPUT\"\n echo \"Existing test files: \\$EXISTING_TESTS\"\n echo \"Existing spec mirrors: \\$EXISTING_SPECS\"\n\n # ── 4. Set up Node.js ─────────────────────────────────────────────────\n - name: Set up Node.js\n uses: actions/setup-node@v4\n with:\n node-version: \"20\"\n\n # ── 5. Install Claude Code CLI ────────────────────────────────────────\n - name: Install Claude Code\n run: npm install -g @anthropic-ai/claude-code\n\n # ── 6. Run scenario agent ─────────────────────────────────────────────\n # - Uses \\`claude -p\\` (prompt mode) for non-interactive execution.\n # - No --model flag: the environment's default model is used.\n # - --dangerously-skip-permissions lets Claude write files without prompts.\n # - --max-turns 20 caps the agentic loop so it can't run indefinitely.\n - name: Run scenario agent\n id: agent\n env:\n ANTHROPIC_API_KEY: \\${{ secrets.ANTHROPIC_API_KEY }}\n run: |\n PROMPT=\\$(cat .claude/prompts/scenario-agent.md 2>/dev/null || cat prompts/scenario-agent.md)\n\n claude -p \\\\\n --dangerously-skip-permissions \\\\\n --max-turns 20 \\\\\n \"\\${PROMPT}\n\n ---\n\n ## Incoming Spec\n\n Filename: \\${{ github.event.client_payload.spec_filename }}\n\n Content:\n \\$(cat 'specs/\\${{ github.event.client_payload.spec_filename }}')\n\n ---\n\n ## Context\n\n Existing test files in this repo: \\${{ steps.context.outputs.existing_tests }}\n Existing spec mirrors: \\${{ steps.context.outputs.existing_specs }}\n\n Testing backbone: \\${{ github.event.client_payload.testing_backbone || 'native' }}\"\n\n # ── 7. Commit any changes the agent made ──────────────────────────────\n - name: Commit scenario changes\n id: commit\n run: |\n git config user.name \"Joycraft Scenario Agent\"\n git config user.email \"joycraft-scenarios@users.noreply.github.com\"\n\n git add -A\n\n if git diff --cached --quiet; then\n echo \"No changes to commit — spec triaged as no-op.\"\n echo \"committed=false\" >> \"\\$GITHUB_OUTPUT\"\n exit 0\n fi\n\n git commit -m \"scenarios: update tests for \\${{ github.event.client_payload.spec_filename }}\"\n git push\n echo \"committed=true\" >> \"\\$GITHUB_OUTPUT\"\n\n # ── 8. Generate GitHub App token for cross-repo dispatch ──────────────\n # Only needed if the agent committed changes (otherwise nothing to re-run).\n - name: Generate GitHub App token\n id: app-token\n if: steps.commit.outputs.committed == 'true'\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\${{ github.event.client_payload.repo }}\n\n # ── 9. Notify main repo that scenarios were updated ───────────────────\n # Fires \\`scenarios-updated\\` so the main repo's re-run workflow can\n # trigger scenario runs against any open PRs that may now be affected.\n - name: Dispatch scenarios-updated to main repo\n if: steps.commit.outputs.committed == 'true'\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n REPO=\"\\${{ github.event.client_payload.repo }}\"\n REPO_OWNER=\"\\${REPO%%/*}\"\n REPO_NAME=\"\\${REPO##*/}\"\n\n gh api \"repos/\\${REPO_OWNER}/\\${REPO_NAME}/dispatches\" \\\\\n -f event_type=scenarios-updated \\\\\n -f \"client_payload[spec_filename]=\\${{ github.event.client_payload.spec_filename }}\" \\\\\n -f \"client_payload[scenarios_repo]=\\${{ github.repository }}\"\n\n echo \"Dispatched scenarios-updated to \\${REPO}\"\n`,\n\n \"scenarios/workflows/run.yml\": `# Scenarios Run Workflow\n#\n# Triggered by a \\`repository_dispatch\\` event (type: run-scenarios) sent from\n# the main project's CI pipeline after a PR passes its internal tests.\n#\n# This workflow:\n# 1. Clones the main repo's PR branch to ../main-repo\n# 2. Builds the artifact\n# 3. Runs the scenario tests in this repo\n# 4. Posts a PASS or FAIL comment on the originating PR\n#\n# Prerequisites:\n# - A GitHub App (\"Joycraft Autofix\" or equivalent) installed on BOTH repos.\n# \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time.\n# JOYCRAFT_APP_PRIVATE_KEY must be stored as a repository secret in this repo.\n# - This scenarios repo must be added to the App's repository access list.\n\nname: Run Scenarios\n\non:\n repository_dispatch:\n types: [run-scenarios]\n\njobs:\n run-scenarios:\n name: Run holdout scenario tests\n runs-on: ubuntu-latest\n\n steps:\n # ── 1. Check out the scenarios repo ─────────────────────────────────────\n - name: Checkout scenarios repo\n uses: actions/checkout@v4\n with:\n path: scenarios\n\n # ── 2. Mint a GitHub App token ───────────────────────────────────────────\n # \\$JOYCRAFT_APP_ID is replaced with the numeric App ID at install time\n # (e.g., app-id: 3180156). It is NOT a secret — App IDs are public.\n - name: Generate GitHub App token\n id: app-token\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\${{ github.event.client_payload.repo }}\n\n # ── 3. Clone the main repo's PR branch ──────────────────────────────────\n # Cloned to ./main-repo so scenario tests can reference ../main-repo\n # relative to the scenarios/ checkout.\n - name: Clone main repo PR branch\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n git clone \\\\\n --branch \\${{ github.event.client_payload.branch }} \\\\\n --depth 1 \\\\\n https://x-access-token:\\${GH_TOKEN}@github.com/\\${{ github.event.client_payload.repo }}.git \\\\\n main-repo\n\n # ── 4. Set up Node.js ────────────────────────────────────────────────────\n - name: Set up Node.js\n uses: actions/setup-node@v4\n with:\n node-version: \"20\"\n cache: \"npm\"\n cache-dependency-path: main-repo/package-lock.json\n\n # ── 5. Build the main repo artifact ─────────────────────────────────────\n - name: Build main repo\n working-directory: main-repo\n run: npm ci && npm run build\n\n # ── 6. Install scenario test dependencies ────────────────────────────────\n - name: Install scenario dependencies\n working-directory: scenarios\n run: npm ci\n\n # ── 7. Run scenario tests ────────────────────────────────────────────────\n # set +e — don't abort on non-zero exit; we capture it manually\n # set -o pipefail — propagate failures through pipes (for tee)\n # NO_COLOR=1 — strip color codes before they reach tee\n # ANSI codes are also stripped via sed as a belt-and-suspenders measure\n - name: Run scenario tests\n id: scenarios\n working-directory: scenarios\n run: |\n set +e\n set -o pipefail\n NO_COLOR=1 npx vitest run 2>&1 \\\\\n | sed 's/\\\\x1b\\\\[[0-9;]*m//g' \\\\\n | tee test-output.txt\n VITEST_EXIT=\\$?\n echo \"exit_code=\\$VITEST_EXIT\" >> \"\\$GITHUB_OUTPUT\"\n exit \\$VITEST_EXIT\n\n # ── 8. Post PASS or FAIL comment on the originating PR ──────────────────\n # Always runs so the PR author always gets feedback.\n - name: Post result comment on PR\n if: always()\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n PR_NUMBER: \\${{ github.event.client_payload.pr_number }}\n MAIN_REPO: \\${{ github.event.client_payload.repo }}\n VITEST_EXIT: \\${{ steps.scenarios.outputs.exit_code }}\n run: |\n # Read test output (cap at 100 lines to keep the comment manageable)\n OUTPUT=\\$(head -100 scenarios/test-output.txt 2>/dev/null || echo \"(no output captured)\")\n\n if [ \"\\$VITEST_EXIT\" = \"0\" ]; then\n STATUS_LINE=\"**Scenario tests: PASS**\"\n else\n STATUS_LINE=\"**Scenario tests: FAIL** (exit code: \\$VITEST_EXIT)\"\n fi\n\n BODY=\"\\${STATUS_LINE}\n\n <details>\n <summary>Test output</summary>\n\n \\\\\\`\\\\\\`\\\\\\`\n \\${OUTPUT}\n \\\\\\`\\\\\\`\\\\\\`\n\n </details>\n\n Run triggered by commit \\\\\\`\\${{ github.event.client_payload.sha }}\\\\\\`.\"\n\n gh api \"repos/\\${MAIN_REPO}/issues/\\${PR_NUMBER}/comments\" \\\\\n -f body=\"\\$BODY\"\n`,\n\n \"workflows/autofix.yml\": `# Autofix Workflow\n#\n# Triggered when CI fails on a PR. Uses Claude Code to attempt an automated fix,\n# then pushes a commit and re-triggers CI. Limits to 3 autofix attempts per PR\n# before escalating to human review.\n#\n# Prerequisites:\n# - A GitHub App called \"Joycraft Autofix\" (or equivalent) installed on the repo.\n# Its credentials must be stored as repository secrets:\n# JOYCRAFT_APP_ID — the App's numeric ID\n# JOYCRAFT_APP_PRIVATE_KEY — the App's PEM private key\n# - ANTHROPIC_API_KEY secret for Claude Code\n\nname: Autofix\n\non:\n workflow_run:\n # Replace with the exact name of your CI workflow\n workflows: [\"CI\"]\n types: [completed]\n\n# One autofix run per PR at a time — cancel in-flight runs for the same PR\nconcurrency:\n group: autofix-pr-\\${{ github.event.workflow_run.pull_requests[0].number }}\n cancel-in-progress: true\n\njobs:\n autofix:\n name: Attempt automated fix\n runs-on: ubuntu-latest\n\n # Only run when CI failed and the triggering workflow was on a PR\n if: |\n github.event.workflow_run.conclusion == 'failure' &&\n github.event.workflow_run.pull_requests[0] != null\n\n steps:\n # ── 1. Mint a short-lived GitHub App token ──────────────────────────────\n # Using a dedicated App identity lets this workflow push commits without\n # triggering GitHub's anti-recursion protection on the GITHUB_TOKEN.\n - name: Generate GitHub App token\n id: app-token\n uses: actions/create-github-app-token@v1\n with:\n # \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n\n # ── 2. Check out the PR branch ──────────────────────────────────────────\n # We check out the exact branch (not a merge ref) so that any commit we\n # push lands directly on the PR branch.\n - name: Checkout PR branch\n uses: actions/checkout@v4\n with:\n token: \\${{ steps.app-token.outputs.token }}\n ref: \\${{ github.event.workflow_run.pull_requests[0].head.ref }}\n fetch-depth: 0\n\n # ── 3. Count previous autofix attempts ─────────────────────────────────\n # Count \"autofix:\" commits in the log. If we have already made 3 attempts\n # on this PR, stop and ask a human to review instead.\n - name: Check autofix iteration count\n id: iteration\n run: |\n COUNT=\\$(git log --oneline | grep \"autofix:\" | wc -l | tr -d ' ')\n echo \"count=\\$COUNT\" >> \"\\$GITHUB_OUTPUT\"\n echo \"Autofix attempts so far: \\$COUNT\"\n\n # ── 4. Post \"human review needed\" and exit if limit reached ─────────────\n - name: Post human-review comment and exit\n if: steps.iteration.outputs.count >= 3\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n PR_NUMBER: \\${{ github.event.workflow_run.pull_requests[0].number }}\n run: |\n gh pr comment \"\\$PR_NUMBER\" \\\\\n --body \"**Autofix limit reached (3 attempts).** Please review manually — Claude was unable to resolve the CI failures automatically.\"\n echo \"Max iterations reached. Exiting without further autofix.\"\n exit 0\n\n # ── 5. Fetch the CI failure logs ────────────────────────────────────────\n # Download logs from the failed workflow run so Claude has concrete\n # failure context to work from. ANSI escape codes are stripped so the\n # logs are readable as plain text.\n - name: Fetch CI failure logs\n id: logs\n env:\n GH_TOKEN: \\${{ github.token }}\n RUN_ID: \\${{ github.event.workflow_run.id }}\n run: |\n gh run view \"\\$RUN_ID\" --log-failed 2>&1 \\\\\n | sed 's/\\\\x1b\\\\[[0-9;]*m//g' \\\\\n > /tmp/ci-failure.log\n echo \"=== CI failure log (first 200 lines) ===\"\n head -200 /tmp/ci-failure.log\n\n # ── 6. Set up Node.js (adjust version to match your project) ───────────\n - name: Set up Node.js\n uses: actions/setup-node@v4\n with:\n node-version: \"20\"\n cache: \"npm\"\n\n # ── 7. Install project dependencies ─────────────────────────────────────\n - name: Install dependencies\n run: npm ci\n\n # ── 8. Install Claude Code CLI ───────────────────────────────────────────\n - name: Install Claude Code\n run: npm install -g @anthropic-ai/claude-code\n\n # ── 9. Run Claude Code to fix the failure ───────────────────────────────\n # - Uses \\`claude -p\\` (prompt mode) so it runs non-interactively.\n # - No --model flag: the environment's default model is used.\n # - --dangerously-skip-permissions lets Claude edit files without prompts.\n # - --max-turns 20 caps the agentic loop so it can't run indefinitely.\n # - set +e captures the exit code without aborting the step immediately.\n # - set -o pipefail ensures piped commands propagate failures correctly.\n - name: Run Claude Code autofix\n id: claude\n env:\n ANTHROPIC_API_KEY: \\${{ secrets.ANTHROPIC_API_KEY }}\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n set +e\n set -o pipefail\n\n FAILURE_LOG=\\$(cat /tmp/ci-failure.log)\n\n claude -p \\\\\n --dangerously-skip-permissions \\\\\n --max-turns 20 \\\\\n \"CI is failing on this PR. Here are the failure logs:\n\n \\${FAILURE_LOG}\n\n Please investigate the root cause, fix the code, and make sure the tests pass.\n Do not modify workflow files. Focus only on source code and test files.\n After making changes, run the test suite to verify the fix works.\" \\\\\n 2>&1 | sed 's/\\\\x1b\\\\[[0-9;]*m//g' | tee /tmp/claude-output.log\n\n CLAUDE_EXIT=\\$?\n echo \"exit_code=\\$CLAUDE_EXIT\" >> \"\\$GITHUB_OUTPUT\"\n exit \\$CLAUDE_EXIT\n\n # ── 10. Commit and push any changes Claude made ──────────────────────────\n # If Claude modified files, commit them with an \"autofix:\" prefix so the\n # iteration counter in step 3 can find them on future runs.\n - name: Commit and push autofix changes\n if: steps.claude.outputs.exit_code == '0'\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n git config user.name \"Joycraft Autofix\"\n git config user.email \"autofix@joycraft.dev\"\n\n git add -A\n\n if git diff --cached --quiet; then\n echo \"No changes to commit — Claude made no file modifications.\"\n exit 0\n fi\n\n ITERATION=\\${{ steps.iteration.outputs.count }}\n NEXT=\\$(( ITERATION + 1 ))\n\n git commit -m \"autofix: attempt \\$NEXT — fix CI failures [skip autofix]\"\n git push\n\n # ── 11. Post a summary comment on the PR ─────────────────────────────────\n # Always post a comment so the PR author knows what happened.\n - name: Post result comment\n if: always()\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n PR_NUMBER: \\${{ github.event.workflow_run.pull_requests[0].number }}\n CLAUDE_EXIT: \\${{ steps.claude.outputs.exit_code }}\n run: |\n if [ \"\\$CLAUDE_EXIT\" = \"0\" ]; then\n BODY=\"**Autofix pushed a fix.** CI has been re-triggered. If it still fails, another autofix attempt will run (up to 3 total).\"\n else\n BODY=\"**Autofix ran but could not produce a clean fix** (exit code: \\$CLAUDE_EXIT). Please review the logs and fix manually.\"\n fi\n\n gh pr comment \"\\$PR_NUMBER\" --body \"\\$BODY\"\n`,\n\n \"workflows/scenarios-dispatch.yml\": `# Scenarios Dispatch Workflow\n#\n# Triggered when CI passes on a PR. Fires a \\`repository_dispatch\\` event to a\n# separate scenarios repository so that integration / end-to-end scenario tests\n# can run against the PR's code without living in this repo.\n#\n# Prerequisites:\n# - JOYCRAFT_APP_PRIVATE_KEY secret: GitHub App private key (.pem)\n# - \\$SCENARIOS_REPO is replaced with the actual repo name at install time\n\nname: Scenarios Dispatch\n\non:\n workflow_run:\n # Replace with the exact name of your CI workflow\n workflows: [\"CI\"]\n types: [completed]\n\njobs:\n dispatch:\n name: Fire scenarios dispatch\n runs-on: ubuntu-latest\n\n # Only run when CI succeeded and the triggering workflow was on a PR\n if: |\n github.event.workflow_run.conclusion == 'success' &&\n github.event.workflow_run.pull_requests[0] != null\n\n steps:\n # ── 1. Generate GitHub App token for cross-repo dispatch ─────────────\n - name: Generate GitHub App token\n id: app-token\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\$SCENARIOS_REPO\n\n # ── 2. Fire repository_dispatch to the scenarios repo ──────────────────\n # Sends a \\`run-scenarios\\` event carrying enough context for the scenarios\n # repo to check out the correct branch/SHA and know which PR triggered it.\n # \\$SCENARIOS_REPO is replaced with the actual repo name at install time.\n - name: Dispatch run-scenarios event\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n PR_NUMBER=\\${{ github.event.workflow_run.pull_requests[0].number }}\n BRANCH=\\${{ github.event.workflow_run.pull_requests[0].head.ref }}\n SHA=\\${{ github.event.workflow_run.head_sha }}\n\n gh api repos/\\${{ github.repository_owner }}/\\$SCENARIOS_REPO/dispatches \\\\\n -f event_type=run-scenarios \\\\\n -f \"client_payload[pr_number]=\\$PR_NUMBER\" \\\\\n -f \"client_payload[branch]=\\$BRANCH\" \\\\\n -f \"client_payload[sha]=\\$SHA\" \\\\\n -f \"client_payload[repo]=\\${{ github.repository }}\"\n\n echo \"Dispatched run-scenarios to \\$SCENARIOS_REPO for PR #\\$PR_NUMBER\"\n`,\n\n \"workflows/scenarios-rerun.yml\": `# Scenarios Re-run Workflow\n#\n# Triggered when the scenarios repo reports that it has updated its tests\n# (type: scenarios-updated). Finds all open PRs and fires a \\`run-scenarios\\`\n# dispatch to the scenarios repo for each one, so that newly generated or\n# updated tests are exercised against in-flight PR branches.\n#\n# This handles the race condition where a PR's implementation completes before\n# the scenario agent has finished writing its holdout tests.\n#\n# Prerequisites:\n# - JOYCRAFT_APP_PRIVATE_KEY secret: GitHub App private key (.pem)\n# - \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time\n# - \\$SCENARIOS_REPO is replaced with the actual scenarios repo name at install time\n\nname: Scenarios Re-run\n\non:\n repository_dispatch:\n types: [scenarios-updated]\n\njobs:\n rerun:\n name: Re-run scenarios against open PRs\n runs-on: ubuntu-latest\n\n steps:\n # ── 1. Generate GitHub App token for cross-repo dispatch ──────────────\n - name: Generate GitHub App token\n id: app-token\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\$SCENARIOS_REPO\n\n # ── 2. List open PRs and dispatch run-scenarios for each ──────────────\n # If there are no open PRs, exits cleanly — nothing to do.\n - name: Dispatch run-scenarios for each open PR\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n OPEN_PRS=\\$(gh api repos/\\${{ github.repository }}/pulls \\\\\n --jq '.[] | \"\\\\(.number) \\\\(.head.ref) \\\\(.head.sha)\"')\n\n if [ -z \"\\$OPEN_PRS\" ]; then\n echo \"No open PRs — nothing to re-run.\"\n exit 0\n fi\n\n while IFS=' ' read -r PR_NUMBER BRANCH SHA; do\n [ -z \"\\$PR_NUMBER\" ] && continue\n\n echo \"Dispatching run-scenarios for PR #\\$PR_NUMBER (branch: \\$BRANCH, sha: \\$SHA)\"\n\n gh api repos/\\${{ github.repository_owner }}/\\$SCENARIOS_REPO/dispatches \\\\\n -f event_type=run-scenarios \\\\\n -f \"client_payload[pr_number]=\\$PR_NUMBER\" \\\\\n -f \"client_payload[branch]=\\$BRANCH\" \\\\\n -f \"client_payload[sha]=\\$SHA\" \\\\\n -f \"client_payload[repo]=\\${{ github.repository }}\"\n\n done <<< \"\\$OPEN_PRS\"\n`,\n\n \"workflows/spec-dispatch.yml\": `# Spec Dispatch Workflow\n#\n# Triggered when specs are pushed to main. For each added or modified spec,\n# fires a \\`spec-pushed\\` repository_dispatch event to the scenarios repo so\n# that a scenario agent can triage the spec and write/update holdout tests.\n#\n# Prerequisites:\n# - JOYCRAFT_APP_PRIVATE_KEY secret: GitHub App private key (.pem)\n# - \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time\n# - \\$SCENARIOS_REPO is replaced with the actual scenarios repo name at install time\n\nname: Spec Dispatch\n\non:\n push:\n branches: [main]\n paths:\n - \"docs/specs/**\"\n\njobs:\n dispatch:\n name: Dispatch changed specs to scenarios repo\n runs-on: ubuntu-latest\n\n steps:\n # ── 1. Check out with depth 2 to enable HEAD~1 diff ──────────────────\n - name: Checkout\n uses: actions/checkout@v4\n with:\n fetch-depth: 2\n\n # ── 2. Find added or modified spec files ──────────────────────────────\n # --diff-filter=AM: Added or Modified only — ignore deletions.\n - name: Find changed specs\n id: changed\n run: |\n FILES=\\$(git diff --name-only --diff-filter=AM HEAD~1 HEAD -- 'docs/specs/*.md')\n echo \"files<<EOF\" >> \"\\$GITHUB_OUTPUT\"\n echo \"\\$FILES\" >> \"\\$GITHUB_OUTPUT\"\n echo \"EOF\" >> \"\\$GITHUB_OUTPUT\"\n echo \"Changed specs: \\$FILES\"\n\n # ── 3. Generate GitHub App token for cross-repo dispatch ──────────────\n # Skipped if no specs changed (token unused, save the round-trip).\n - name: Generate GitHub App token\n id: app-token\n if: steps.changed.outputs.files != ''\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\$SCENARIOS_REPO\n\n # ── 4. Dispatch each changed spec to the scenarios repo ───────────────\n # Sends a \\`spec-pushed\\` event with the spec filename, full content,\n # commit SHA, branch, and originating repo. The scenario agent uses\n # this payload to triage and generate/update tests.\n - name: Dispatch spec-pushed events\n if: steps.changed.outputs.files != ''\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n while IFS= read -r SPEC_FILE; do\n [ -z \"\\$SPEC_FILE\" ] && continue\n\n SPEC_FILENAME=\\$(basename \"\\$SPEC_FILE\")\n SPEC_CONTENT=\\$(cat \"\\$SPEC_FILE\")\n\n echo \"Dispatching spec-pushed for \\$SPEC_FILENAME\"\n\n gh api repos/\\${{ github.repository_owner }}/\\$SCENARIOS_REPO/dispatches \\\\\n -f event_type=spec-pushed \\\\\n -f \"client_payload[spec_filename]=\\$SPEC_FILENAME\" \\\\\n -f \"client_payload[spec_content]=\\$SPEC_CONTENT\" \\\\\n -f \"client_payload[commit_sha]=\\${{ github.sha }}\" \\\\\n -f \"client_payload[branch]=\\${{ github.ref_name }}\" \\\\\n -f \"client_payload[repo]=\\${{ github.repository }}\"\n\n done <<< \"\\${{ steps.changed.outputs.files }}\"\n`,\n\n};\n"],"mappings":";;;AAEO,IAAM,SAAiC;AAAA,EAC5C,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqJzB,gCAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8JhC,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiGzB,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoL3B,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmG3B,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8XpB,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqKxB,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+HxB,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgJtB,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkLxB;AAEO,IAAM,YAAoC;AAAA,EAC/C,oCAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBpC,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoB3B,sCAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqCtC,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+B7B,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0C9B,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4E7B,4BAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwF5B,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgMvB,sCAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwHtC,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc1B,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc9B,kCAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBlC,0CAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwF1C,0CAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6G1C,0CAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiD1C,gDAAgD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBhD,8BAA8B;AAAA,EAE9B,uCAAuC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqMvC,oCAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwJpC,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmI/B,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2LzB,oCAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4DpC,iCAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiEjC,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiFjC;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -10,15 +10,15 @@ var pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"
|
|
|
10
10
|
var program = new Command();
|
|
11
11
|
program.name("joycraft").description("Scaffold and upgrade AI development harnesses").version(pkg.version, "-v, --version");
|
|
12
12
|
program.command("init").description("Scaffold the Joycraft harness into the current project").argument("[dir]", "Target directory", ".").option("--force", "Overwrite existing files").action(async (dir, opts) => {
|
|
13
|
-
const { init } = await import("./init-
|
|
13
|
+
const { init } = await import("./init-B7MYPB7Z.js");
|
|
14
14
|
await init(dir, { force: opts.force ?? false });
|
|
15
15
|
});
|
|
16
16
|
program.command("upgrade").description("Upgrade installed Joycraft templates and skills to latest").argument("[dir]", "Target directory", ".").option("--yes", "Auto-accept all updates").action(async (dir, opts) => {
|
|
17
|
-
const { upgrade } = await import("./upgrade-
|
|
17
|
+
const { upgrade } = await import("./upgrade-WXNR3ZNI.js");
|
|
18
18
|
await upgrade(dir, { yes: opts.yes ?? false });
|
|
19
19
|
});
|
|
20
20
|
program.command("init-autofix").description("Set up the Level 5 auto-fix loop with holdout scenarios").argument("[dir]", "Target directory", ".").option("--scenarios-repo <name>", "Name for scenarios repo").option("--app-id <id>", "GitHub App ID for Joycraft Autofix").option("--force", "Overwrite existing workflow files").option("--dry-run", "Show what would be created without creating it").action(async (dir, opts) => {
|
|
21
|
-
const { initAutofix } = await import("./init-autofix-
|
|
21
|
+
const { initAutofix } = await import("./init-autofix-FKZN5S3R.js");
|
|
22
22
|
await initAutofix(dir, opts);
|
|
23
23
|
});
|
|
24
24
|
program.command("check-version").description("Check if a newer version of Joycraft is available").action(async () => {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
SKILLS,
|
|
8
8
|
TEMPLATES
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-Y6GBN6R4.js";
|
|
10
10
|
|
|
11
11
|
// src/init.ts
|
|
12
12
|
import { mkdirSync as mkdirSync2, existsSync as existsSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
@@ -881,4 +881,4 @@ function printSummary(result, stack, existingSkills = []) {
|
|
|
881
881
|
export {
|
|
882
882
|
init
|
|
883
883
|
};
|
|
884
|
-
//# sourceMappingURL=init-
|
|
884
|
+
//# sourceMappingURL=init-B7MYPB7Z.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
TEMPLATES
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Y6GBN6R4.js";
|
|
5
5
|
|
|
6
6
|
// src/init-autofix.ts
|
|
7
7
|
import { mkdirSync, existsSync, writeFileSync } from "fs";
|
|
@@ -111,4 +111,4 @@ function printSummary(result, dryRun, scenariosRepo) {
|
|
|
111
111
|
export {
|
|
112
112
|
initAutofix
|
|
113
113
|
};
|
|
114
|
-
//# sourceMappingURL=init-autofix-
|
|
114
|
+
//# sourceMappingURL=init-autofix-FKZN5S3R.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
SKILLS,
|
|
9
9
|
TEMPLATES
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-Y6GBN6R4.js";
|
|
11
11
|
|
|
12
12
|
// src/upgrade.ts
|
|
13
13
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from "fs";
|
|
@@ -197,4 +197,4 @@ function getPackageVersion() {
|
|
|
197
197
|
export {
|
|
198
198
|
upgrade
|
|
199
199
|
};
|
|
200
|
-
//# sourceMappingURL=upgrade-
|
|
200
|
+
//# sourceMappingURL=upgrade-WXNR3ZNI.js.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bundled-files.ts"],"sourcesContent":["// Bundled file contents — embedded at build time\n\nexport const SKILLS: Record<string, string> = {\n \"joycraft-decompose.md\": `---\nname: joycraft-decompose\ndescription: Break a feature brief into atomic specs — small, testable, independently executable units\n---\n\n# Decompose Feature into Atomic Specs\n\nYou have a Feature Brief (or the user has described a feature). Your job is to decompose it into atomic specs that can be executed independently — one spec per session.\n\n## Step 1: Verify the Brief Exists\n\nLook for a Feature Brief in \\`docs/briefs/\\`. If one doesn't exist yet, tell the user:\n\n> No feature brief found. Run \\`/joycraft-new-feature\\` first to interview and create one, or describe the feature now and I'll work from your description.\n\nIf the user describes the feature inline, work from that description directly. You don't need a formal brief to decompose — but recommend creating one for complex features.\n\n## Step 2: Identify Natural Boundaries\n\n**Why:** Good boundaries make specs independently testable and committable. Bad boundaries create specs that can't be verified without other specs also being done.\n\nRead the brief (or description) and identify natural split points:\n\n- **Data layer changes** (schemas, types, migrations) — always a separate spec\n- **Pure functions / business logic** — separate from I/O\n- **UI components** — separate from data fetching\n- **API endpoints / route handlers** — separate from business logic\n- **Test infrastructure** (mocks, fixtures, helpers) — can be its own spec if substantial\n- **Configuration / environment** — separate from code changes\n\nAsk yourself: \"Can this piece be committed and tested without the other pieces existing?\" If yes, it's a good boundary.\n\n## Step 3: Build the Decomposition Table\n\nFor each atomic spec, define:\n\n| # | Spec Name | Description | Dependencies | Size |\n|---|-----------|-------------|--------------|------|\n\n**Rules:**\n- Each spec name is \\`verb-object\\` format (e.g., \\`add-terminal-detection\\`, \\`extract-prompt-module\\`)\n- Each description is ONE sentence — if you need two, the spec is too big\n- Dependencies reference other spec numbers — keep the dependency graph shallow\n- More than 2 dependencies on a single spec = it's too big, split further\n- Aim for 3-7 specs per feature. Fewer than 3 = probably not decomposed enough. More than 10 = the feature brief is too big\n\n## Step 4: Present and Iterate\n\nShow the decomposition table to the user. Ask:\n1. \"Does this breakdown match how you think about this feature?\"\n2. \"Are there any specs that feel too big or too small?\"\n3. \"Should any of these run in parallel (separate worktrees)?\"\n\nIterate until the user approves.\n\n## Step 5: Generate Atomic Specs\n\nFor each approved row, create \\`docs/specs/YYYY-MM-DD-spec-name.md\\`. Create the \\`docs/specs/\\` directory if it doesn't exist.\n\n**Why:** Each spec must be self-contained — a fresh Claude session should be able to execute it without reading the Feature Brief. Copy relevant constraints and context into each spec.\n\nUse this structure:\n\n\\`\\`\\`markdown\n# [Verb + Object] — Atomic Spec\n\n> **Parent Brief:** \\`docs/briefs/YYYY-MM-DD-feature-name.md\\` (or \"standalone\")\n> **Status:** Ready\n> **Date:** YYYY-MM-DD\n> **Estimated scope:** [1 session / N files / ~N lines]\n\n---\n\n## What\nOne paragraph — what changes when this spec is done?\n\n## Why\nOne sentence — what breaks or is missing without this?\n\n## Acceptance Criteria\n- [ ] [Observable behavior]\n- [ ] Build passes\n- [ ] Tests pass\n\n## Test Plan\n\n| Acceptance Criterion | Test | Type |\n|---------------------|------|------|\n| [Each AC above] | [What to call/assert] | [unit/integration/e2e] |\n\n**Execution order:**\n1. Write all tests above — they should fail against current/stubbed code\n2. Run tests to confirm they fail (red)\n3. Implement until all tests pass (green)\n\n**Smoke test:** [Identify the fastest test for iteration feedback]\n\n**Before implementing, verify your test harness:**\n1. Run all tests — they must FAIL (if they pass, you're testing the wrong thing)\n2. Each test calls your actual function/endpoint — not a reimplementation or the underlying library\n3. Identify your smoke test — it must run in seconds, not minutes, so you get fast feedback on each change\n\n## Constraints\n- MUST: [hard requirement]\n- MUST NOT: [hard prohibition]\n\n## Affected Files\n| Action | File | What Changes |\n|--------|------|-------------|\n\n## Approach\nStrategy, data flow, key decisions. Name one rejected alternative.\n\n## Edge Cases\n| Scenario | Expected Behavior |\n|----------|------------------|\n\\`\\`\\`\n\nIf \\`docs/templates/ATOMIC_SPEC_TEMPLATE.md\\` exists, reference it for the full template with additional guidance.\n\nFill in all sections — each spec must be self-contained (no \"see the brief for context\"). Copy relevant constraints from the Feature Brief into each spec. Write acceptance criteria specific to THIS spec, not the whole feature. Every acceptance criterion must have at least one corresponding test in the Test Plan. If the user provided test strategy info from the interview, use it to choose test types and frameworks. Include the test harness verification rules in every Test Plan.\n\n## Step 6: Recommend Execution Strategy\n\nBased on the dependency graph:\n- **Independent specs** — \"These can run in parallel worktrees\"\n- **Sequential specs** — \"Execute these in order: 1 -> 2 -> 4\"\n- **Mixed** — \"Start specs 1 and 3 in parallel. After 1 completes, start 2.\"\n\nUpdate the Feature Brief's Execution Strategy section with the plan (if a brief exists).\n\n## Step 7: Hand Off\n\nTell the user:\n\\`\\`\\`\nDecomposition complete:\n- [N] atomic specs created in docs/specs/\n- [N] can run in parallel, [N] are sequential\n- Estimated total: [N] sessions\n\nTo execute:\n- Sequential: Open a session, point Claude at each spec in order\n- Parallel: Use worktrees — one spec per worktree, merge when done\n- Each session should end with /joycraft-session-end to capture discoveries\n\nReady to start execution?\n\\`\\`\\`\n`,\n\n \"joycraft-implement-level5.md\": `---\nname: joycraft-implement-level5\ndescription: Set up Level 5 autonomous development — autofix loop, holdout scenario testing, and scenario evolution from specs\n---\n\n# Implement Level 5 — Autonomous Development Loop\n\nYou are guiding the user through setting up Level 5: the autonomous feedback loop where specs go in, validated software comes out. This is a one-time setup that installs workflows, creates a scenarios repo, and configures the autofix loop.\n\n## Before You Begin\n\nCheck prerequisites:\n\n1. **Project must be initialized.** Look for \\`.joycraft-version\\`. If missing, tell the user to run \\`npx joycraft init\\` first.\n2. **Project should be at Level 4.** Check \\`docs/joycraft-assessment.md\\` if it exists. If the project hasn't been assessed yet, suggest running \\`/joycraft-tune\\` first. But don't block — the user may know they're ready.\n3. **Git repo with GitHub remote.** This setup requires GitHub Actions. Check for \\`.git/\\` and a GitHub remote.\n\nIf prerequisites aren't met, explain what's needed and stop.\n\n## Step 1: Explain What Level 5 Means\n\nTell the user:\n\n> Level 5 is the autonomous loop. When you push specs, three things happen automatically:\n>\n> 1. **Scenario evolution** — A separate AI agent reads your specs and writes holdout tests in a private scenarios repo. These tests are invisible to your coding agent.\n> 2. **Autofix** — When CI fails on a PR, Claude Code automatically attempts a fix (up to 3 times).\n> 3. **Holdout validation** — When CI passes, your scenarios repo runs behavioral tests against the PR. Results post as PR comments.\n>\n> The key insight: your coding agent never sees the scenario tests. This prevents it from gaming the test suite — like a validation set in machine learning.\n\n## Step 2: Gather Configuration\n\nAsk these questions **one at a time**:\n\n### Question 1: Scenarios repo name\n\n> What should we call your scenarios repo? It'll be a private repo that holds your holdout tests.\n>\n> Default: \\`{current-repo-name}-scenarios\\`\n\nAccept the default or the user's choice.\n\n### Question 2: GitHub App\n\n> Level 5 needs a GitHub App to provide a separate identity for autofix pushes (this avoids GitHub's anti-recursion protection). Creating one takes about 2 minutes:\n>\n> 1. Go to https://github.com/settings/apps/new\n> 2. Give it a name (e.g., \"My Project Autofix\")\n> 3. Uncheck \"Webhook > Active\" (not needed)\n> 4. Under **Repository permissions**, set:\n> - **Contents**: Read & Write\n> - **Pull requests**: Read & Write\n> - **Actions**: Read & Write\n> 5. Click **Create GitHub App**\n> 6. Note the **App ID** from the settings page\n> 7. Scroll to **Private keys** > click **Generate a private key** > save the \\`.pem\\` file\n> 8. Click **Install App** in the left sidebar > install it on your repo\n>\n> What's your App ID?\n\n## Step 3: Run init-autofix\n\nRun the CLI command with the gathered configuration:\n\n\\`\\`\\`bash\nnpx joycraft init-autofix --scenarios-repo {name} --app-id {id}\n\\`\\`\\`\n\nReview the output with the user. Confirm files were created.\n\n## Step 4: Walk Through Secret Configuration\n\nGuide the user step by step:\n\n### 4a: Add Secrets to Main Repo\n\n> You should already have the \\`.pem\\` file from when you created the app in Step 2.\n\n> Go to your repo's Settings > Secrets and variables > Actions, and add:\n> - \\`JOYCRAFT_APP_PRIVATE_KEY\\` — paste the contents of your \\`.pem\\` file\n> - \\`ANTHROPIC_API_KEY\\` — your Anthropic API key\n\n### 4b: Create the Scenarios Repo\n\n> Create the private scenarios repo:\n> \\`\\`\\`bash\n> gh repo create {scenarios-repo-name} --private\n> \\`\\`\\`\n>\n> Then copy the scenario templates into it:\n> \\`\\`\\`bash\n> cp -r docs/templates/scenarios/* ../{scenarios-repo-name}/\n> cd ../{scenarios-repo-name}\n> git add -A && git commit -m \"init: scaffold scenarios repo from Joycraft\"\n> git push\n> \\`\\`\\`\n\n### 4c: Add Secrets to Scenarios Repo\n\n> The scenarios repo also needs the same secrets:\n> - \\`JOYCRAFT_APP_PRIVATE_KEY\\` — same \\`.pem\\` file as the main repo\n> - \\`ANTHROPIC_API_KEY\\` — same key (needed for scenario generation)\n\n## Step 5: Verify Setup\n\nHelp the user verify everything is wired correctly:\n\n1. **Check workflow files exist:** \\`ls .github/workflows/autofix.yml .github/workflows/scenarios-dispatch.yml .github/workflows/spec-dispatch.yml .github/workflows/scenarios-rerun.yml\\`\n2. **Check scenario templates were copied:** Verify the scenarios repo has \\`example-scenario.test.ts\\`, \\`workflows/run.yml\\`, \\`workflows/generate.yml\\`, \\`prompts/scenario-agent.md\\`\n3. **Check the App ID is correct** in the workflow files (not still a placeholder)\n\n## Step 6: Update CLAUDE.md\n\nIf the project's CLAUDE.md doesn't already have an \"External Validation\" section, add one:\n\n> ## External Validation\n>\n> This project uses holdout scenario tests in a separate private repo.\n>\n> ### NEVER\n> - Access, read, or reference the scenarios repo\n> - Mention scenario test names or contents\n> - Modify the scenarios dispatch workflow to leak test information\n>\n> The scenarios repo is deliberately invisible to you. This is the holdout guarantee.\n\n## Step 7: First Test (Optional)\n\nIf the user wants to test the loop:\n\n> Want to do a quick test? Here's how:\n>\n> 1. Write a simple spec in \\`docs/specs/\\` and push to main — this triggers scenario generation\n> 2. Create a PR with a small change — when CI passes, scenarios will run\n> 3. Watch for the scenario test results as a PR comment\n>\n> Or deliberately break something in a PR to test the autofix loop.\n\n## Step 8: Summary\n\nPrint a summary of what was set up:\n\n> **Level 5 is live.** Here's what's running:\n>\n> | Trigger | What Happens |\n> |---------|-------------|\n> | Push specs to \\`docs/specs/\\` | Scenario agent writes holdout tests |\n> | PR fails CI | Claude autofix attempts (up to 3x) |\n> | PR passes CI | Holdout scenarios run against PR |\n> | Scenarios update | Open PRs re-tested with latest scenarios |\n>\n> Your scenarios repo: \\`{name}\\`\n> Your coding agent cannot see those tests. The holdout wall is intact.\n\nUpdate \\`docs/joycraft-assessment.md\\` if it exists — set the Level 5 score to reflect the new setup.\n`,\n\n \"joycraft-interview.md\": `---\nname: joycraft-interview\ndescription: Brainstorm freely about what you want to build — yap, explore ideas, and get a structured summary you can use later\n---\n\n# Interview — Idea Exploration\n\nYou are helping the user brainstorm and explore what they want to build. This is a lightweight, low-pressure conversation — not a formal spec process. Let them yap.\n\n## How to Run the Interview\n\n### 1. Open the Floor\n\nStart with something like:\n\"What are you thinking about building? Just talk — I'll listen and ask questions as we go.\"\n\nLet the user talk freely. Do not interrupt their flow. Do not push toward structure yet.\n\n### 2. Ask Clarifying Questions\n\nAs they talk, weave in questions naturally — don't fire them all at once:\n\n- **What problem does this solve?** Who feels the pain today?\n- **What does \"done\" look like?** If this worked perfectly, what would a user see?\n- **What are the constraints?** Time, tech, team, budget — what boxes are we in?\n- **What's NOT in scope?** What's tempting but should be deferred?\n- **What are the edge cases?** What could go wrong? What's the weird input?\n- **What exists already?** Are we building on something or starting fresh?\n\n### 3. Play Back Understanding\n\nAfter the user has gotten their ideas out, reflect back:\n\"So if I'm hearing you right, you want to [summary]. The core problem is [X], and done looks like [Y]. Is that right?\"\n\nLet them correct and refine. Iterate until they say \"yes, that's it.\"\n\n### 4. Write a Draft Brief\n\nCreate a draft file at \\`docs/briefs/YYYY-MM-DD-topic-draft.md\\`. Create the \\`docs/briefs/\\` directory if it doesn't exist.\n\nUse this format:\n\n\\`\\`\\`markdown\n# [Topic] — Draft Brief\n\n> **Date:** YYYY-MM-DD\n> **Status:** DRAFT\n> **Origin:** /joycraft-interview session\n\n---\n\n## The Idea\n[2-3 paragraphs capturing what the user described — their words, their framing]\n\n## Problem\n[What pain or gap this addresses]\n\n## What \"Done\" Looks Like\n[The user's description of success — observable outcomes]\n\n## Constraints\n- [constraint 1]\n- [constraint 2]\n\n## Open Questions\n- [things that came up but weren't resolved]\n- [decisions that need more thought]\n\n## Out of Scope (for now)\n- [things explicitly deferred]\n\n## Raw Notes\n[Any additional context, quotes, or tangents worth preserving]\n\\`\\`\\`\n\n### 5. Hand Off\n\nAfter writing the draft, tell the user:\n\n\\`\\`\\`\nDraft brief saved to docs/briefs/YYYY-MM-DD-topic-draft.md\n\nWhen you're ready to move forward:\n- /joycraft-new-feature — formalize this into a full Feature Brief with specs\n- /joycraft-decompose — break it directly into atomic specs if scope is clear\n- Or just keep brainstorming — run /joycraft-interview again anytime\n\\`\\`\\`\n\n## Guidelines\n\n- **This is NOT /joycraft-new-feature.** Do not push toward formal briefs, decomposition tables, or atomic specs. The point is exploration.\n- **Let the user lead.** Your job is to listen, clarify, and capture — not to structure or direct.\n- **Mark everything as DRAFT.** The output is a starting point, not a commitment.\n- **Keep it short.** The draft brief should be 1-2 pages max. Capture the essence, not every detail.\n- **Multiple interviews are fine.** The user might run this several times as their thinking evolves. Each creates a new dated draft.\n`,\n\n \"joycraft-new-feature.md\": `---\nname: joycraft-new-feature\ndescription: Guided feature development — interview the user, produce a Feature Brief, then decompose into atomic specs\n---\n\n# New Feature Workflow\n\nYou are starting a new feature. Follow this process in order. Do not skip steps.\n\n## Phase 1: Interview\n\nInterview the user about what they want to build. Let them talk — your job is to listen, then sharpen.\n\n**Why:** A thorough interview prevents wasted implementation time. Most failed features fail because the problem wasn't understood, not because the code was wrong.\n\n**Ask about:**\n- What problem does this solve? Who is affected?\n- What does \"done\" look like? How will a user know this works?\n- What are the hard constraints? (business rules, tech limitations, deadlines)\n- What is explicitly NOT in scope? (push hard on this — aggressive scoping is key)\n- Are there edge cases or error conditions we need to handle?\n- What existing code/patterns should this follow?\n\n**Interview technique:**\n- Let the user \"yap\" — don't interrupt their flow of ideas\n- After they finish, play back your understanding: \"So if I'm hearing you right...\"\n- Ask clarifying questions that force specificity: \"When you say 'handle errors,' what should the user see?\"\n- Push toward testable statements: \"How would we verify that works?\"\n\nKeep asking until you can fill out a Feature Brief. When ready, say:\n\"I have enough context. Let me write the Feature Brief for your review.\"\n\n## Phase 2: Feature Brief\n\nWrite a Feature Brief to \\`docs/briefs/YYYY-MM-DD-feature-name.md\\`. Create the \\`docs/briefs/\\` directory if it doesn't exist.\n\n**Why:** The brief is the single source of truth for what we're building. It prevents scope creep and gives every spec a shared reference point.\n\nUse this structure:\n\n\\`\\`\\`markdown\n# [Feature Name] — Feature Brief\n\n> **Date:** YYYY-MM-DD\n> **Project:** [project name]\n> **Status:** Interview | Decomposing | Specs Ready | In Progress | Complete\n\n---\n\n## Vision\nWhat are we building and why? The full picture in 2-4 paragraphs.\n\n## User Stories\n- As a [role], I want [capability] so that [benefit]\n\n## Hard Constraints\n- MUST: [constraint that every spec must respect]\n- MUST NOT: [prohibition that every spec must respect]\n\n## Out of Scope\n- NOT: [tempting but deferred]\n\n## Decomposition\n| # | Spec Name | Description | Dependencies | Est. Size |\n|---|-----------|-------------|--------------|-----------|\n| 1 | [verb-object] | [one sentence] | None | [S/M/L] |\n\n## Execution Strategy\n- [ ] Sequential (specs have chain dependencies)\n- [ ] Parallel worktrees (specs are independent)\n- [ ] Mixed\n\n## Success Criteria\n- [ ] [End-to-end behavior 1]\n- [ ] [No regressions in existing features]\n\\`\\`\\`\n\nIf \\`docs/templates/FEATURE_BRIEF_TEMPLATE.md\\` exists, reference it for the full template with additional guidance.\n\nPresent the brief to the user. Focus review on:\n- \"Does the decomposition match how you think about this?\"\n- \"Is anything in scope that shouldn't be?\"\n- \"Are the specs small enough? Can each be described in one sentence?\"\n\nIterate until approved.\n\n## Phase 3: Generate Atomic Specs\n\nFor each row in the decomposition table, create a self-contained spec file at \\`docs/specs/YYYY-MM-DD-spec-name.md\\`. Create the \\`docs/specs/\\` directory if it doesn't exist.\n\n**Why:** Each spec must be understandable WITHOUT reading the Feature Brief. This prevents the \"Curse of Instructions\" — no spec should require holding the entire feature in context. Copy relevant context into each spec.\n\nUse this structure for each spec:\n\n\\`\\`\\`markdown\n# [Verb + Object] — Atomic Spec\n\n> **Parent Brief:** \\`docs/briefs/YYYY-MM-DD-feature-name.md\\`\n> **Status:** Ready\n> **Date:** YYYY-MM-DD\n> **Estimated scope:** [1 session / N files / ~N lines]\n\n---\n\n## What\nOne paragraph — what changes when this spec is done?\n\n## Why\nOne sentence — what breaks or is missing without this?\n\n## Acceptance Criteria\n- [ ] [Observable behavior]\n- [ ] Build passes\n- [ ] Tests pass\n\n## Test Plan\n\n| Acceptance Criterion | Test | Type |\n|---------------------|------|------|\n| [Each AC above] | [What to call/assert] | [unit/integration/e2e] |\n\n**Execution order:**\n1. Write all tests above — they should fail against current/stubbed code\n2. Run tests to confirm they fail (red)\n3. Implement until all tests pass (green)\n\n**Smoke test:** [Identify the fastest test for iteration feedback]\n\n**Before implementing, verify your test harness:**\n1. Run all tests — they must FAIL (if they pass, you're testing the wrong thing)\n2. Each test calls your actual function/endpoint — not a reimplementation or the underlying library\n3. Identify your smoke test — it must run in seconds, not minutes, so you get fast feedback on each change\n\n## Constraints\n- MUST: [hard requirement]\n- MUST NOT: [hard prohibition]\n\n## Affected Files\n| Action | File | What Changes |\n|--------|------|-------------|\n\n## Approach\nStrategy, data flow, key decisions. Name one rejected alternative.\n\n## Edge Cases\n| Scenario | Expected Behavior |\n|----------|------------------|\n\\`\\`\\`\n\nIf \\`docs/templates/ATOMIC_SPEC_TEMPLATE.md\\` exists, reference it for the full template with additional guidance.\n\n## Phase 4: Hand Off for Execution\n\nTell the user:\n\\`\\`\\`\nFeature Brief and [N] atomic specs are ready.\n\nSpecs:\n1. [spec-name] — [one sentence] [S/M/L]\n2. [spec-name] — [one sentence] [S/M/L]\n...\n\nRecommended execution:\n- [Parallel/Sequential/Mixed strategy]\n- Estimated: [N] sessions total\n\nTo execute: Start a fresh session per spec. Each session should:\n1. Read the spec\n2. Implement\n3. Run /joycraft-session-end to capture discoveries\n4. Commit and PR\n\nReady to start?\n\\`\\`\\`\n\n**Why:** A fresh session for execution produces better results. The interview session has too much context noise — a clean session with just the spec is more focused.\n\nYou can also use \\`/joycraft-decompose\\` to re-decompose a brief if the breakdown needs adjustment, or run \\`/joycraft-interview\\` first for a lighter brainstorm before committing to the full workflow.\n`,\n\n \"joycraft-session-end.md\": `---\nname: joycraft-session-end\ndescription: Wrap up a session — capture discoveries, verify, prepare for PR or next session\n---\n\n# Session Wrap-Up\n\nBefore ending this session, complete these steps in order.\n\n## 1. Capture Discoveries\n\n**Why:** Discoveries are the surprises — things that weren't in the spec or that contradicted expectations. They prevent future sessions from hitting the same walls.\n\nCheck: did anything surprising happen during this session? If yes, create or update a discovery file at \\`docs/discoveries/YYYY-MM-DD-topic.md\\`. Create the \\`docs/discoveries/\\` directory if it doesn't exist.\n\nOnly capture what's NOT obvious from the code or git diff:\n- \"We thought X but found Y\" — assumptions that were wrong\n- \"This API/library behaves differently than documented\" — external gotchas\n- \"This edge case needs handling in a future spec\" — deferred work with context\n- \"The approach in the spec didn't work because...\" — spec-vs-reality gaps\n- Key decisions made during implementation that aren't in the spec\n\n**Do NOT capture:**\n- Files changed (that's the diff)\n- What you set out to do (that's the spec)\n- Step-by-step narrative of the session (nobody re-reads these)\n\nUse this format:\n\n\\`\\`\\`markdown\n# Discoveries — [topic]\n\n**Date:** YYYY-MM-DD\n**Spec:** [link to spec if applicable]\n\n## [Discovery title]\n**Expected:** [what we thought would happen]\n**Actual:** [what actually happened]\n**Impact:** [what this means for future work]\n\\`\\`\\`\n\nIf nothing surprising happened, skip the discovery file entirely. No discovery is a good sign — the spec was accurate.\n\n## 1b. Update Context Documents\n\nIf \\`docs/context/\\` exists, quickly check whether this session revealed anything about:\n\n- **Production risks** — did you interact with or learn about production vs staging systems? → Update \\`docs/context/production-map.md\\`\n- **Wrong assumptions** — did the agent (or you) assume something that turned out to be false? → Update \\`docs/context/dangerous-assumptions.md\\`\n- **Key decisions** — did you make an architectural or tooling choice? → Add a row to \\`docs/context/decision-log.md\\`\n- **Unwritten rules** — did you discover a convention or constraint not documented anywhere? → Update \\`docs/context/institutional-knowledge.md\\`\n\nSkip this if nothing applies. Don't force it — only update when there's genuine new context.\n\n## 2. Run Validation\n\nRun the project's validation commands. Check CLAUDE.md for project-specific commands. Common checks:\n\n- Type-check (e.g., \\`tsc --noEmit\\`, \\`mypy\\`, \\`cargo check\\`)\n- Tests (e.g., \\`npm test\\`, \\`pytest\\`, \\`cargo test\\`)\n- Lint (e.g., \\`eslint\\`, \\`ruff\\`, \\`clippy\\`)\n\nFix any failures before proceeding.\n\n## 3. Update Spec Status\n\nIf working from an atomic spec in \\`docs/specs/\\`:\n- All acceptance criteria met — update status to \\`Complete\\`\n- Partially done — update status to \\`In Progress\\`, note what's left\n\nIf working from a Feature Brief in \\`docs/briefs/\\`, check off completed specs in the decomposition table.\n\n## 4. Commit\n\nCommit all changes including the discovery file (if created) and spec status updates. The commit message should reference the spec if applicable.\n\n## 5. Push and PR (if autonomous git is enabled)\n\n**Check CLAUDE.md for \"Git Autonomy\" in the Behavioral Boundaries section.** If it says \"STRICTLY ENFORCED\" or the ALWAYS section includes \"Push to feature branches immediately after every commit\":\n\n1. **Push immediately.** Run \\`git push origin <branch>\\` — do not ask, do not hesitate.\n2. **Open a PR if the feature is complete.** Check the parent Feature Brief's decomposition table — if all specs are done, run \\`gh pr create\\` with a summary of all completed specs. Do not ask first.\n3. **If not all specs are done,** still push. The PR comes when the last spec is complete.\n\nIf CLAUDE.md does NOT have autonomous git rules (or has \"ASK FIRST\" for pushing), ask the user before pushing.\n\n## 6. Report\n\n\\`\\`\\`\nSession complete.\n- Spec: [spec name] — [Complete / In Progress]\n- Build: [passing / failing]\n- Discoveries: [N items / none]\n- Pushed: [yes / no — and why not]\n- PR: [opened #N / not yet — N specs remaining]\n- Next: [what the next session should tackle]\n\\`\\`\\`\n`,\n\n \"joycraft-tune.md\": `---\nname: joycraft-tune\ndescription: Assess and upgrade your project's AI development harness — score 7 dimensions, apply fixes, show path to Level 5\n---\n\n# Tune — Project Harness Assessment & Upgrade\n\nYou are evaluating and upgrading this project's AI development harness. Follow these steps in order.\n\n## Step 1: Detect Harness State\n\nCheck the following and note what exists:\n\n1. **CLAUDE.md** — Read it if it exists. Check whether it contains meaningful content (not just a project name or generic README).\n2. **Key directories** — Check for: \\`docs/specs/\\`, \\`docs/briefs/\\`, \\`docs/discoveries/\\`, \\`docs/templates/\\`, \\`.claude/skills/\\`\n3. **Boundary framework** — Look for \\`Always\\`, \\`Ask First\\`, and \\`Never\\` sections in CLAUDE.md (or similar behavioral constraints under any heading).\n4. **Skills infrastructure** — Check \\`.claude/skills/\\` for installed skill files.\n5. **Test configuration** — Look for test commands in package.json, pyproject.toml, Cargo.toml, Makefile, or CI config files.\n\n## Step 2: Route Based on State\n\n### If No Harness (no CLAUDE.md, or CLAUDE.md is just a README with no structured sections):\n\nTell the user:\n- Their project has no AI development harness\n- Recommend running \\`npx joycraft init\\` to scaffold one\n- Briefly explain what it sets up: CLAUDE.md with boundaries, spec/brief templates, skills, documentation structure\n- **Stop here** — do not run the full assessment on a bare project\n\n### If Harness Exists (CLAUDE.md has structured content — boundaries, commands, architecture, or domain rules):\n\nContinue to Step 3 for the full assessment.\n\n## Step 3: Score 7 Dimensions\n\nRead CLAUDE.md thoroughly. Explore the project structure. Score each dimension on a 1-5 scale with specific evidence.\n\n### Dimension 1: Spec Quality\n\nLook in \\`docs/specs/\\` for specification files.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No specs directory or no spec files |\n| 2 | Specs exist but are informal notes or TODOs |\n| 3 | Specs have structure (sections, some criteria) but lack consistency |\n| 4 | Specs are structured with clear acceptance criteria and constraints |\n| 5 | Atomic specs: self-contained, acceptance criteria, constraints, edge cases, affected files |\n\n**Evidence:** Number of specs found, example of best/worst, whether acceptance criteria are present.\n\n### Dimension 2: Spec Granularity\n\nCan each spec be completed in a single coding session?\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No specs |\n| 2 | Specs cover entire features or epics |\n| 3 | Specs are feature-sized (multi-session but bounded) |\n| 4 | Most specs are session-sized with clear scope |\n| 5 | All specs are atomic — one session, one concern, clear done state |\n\n### Dimension 3: Behavioral Boundaries\n\nRead CLAUDE.md for explicit behavioral constraints.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No CLAUDE.md or no behavioral guidance |\n| 2 | CLAUDE.md exists with general instructions but no structured boundaries |\n| 3 | Some boundaries exist but not organized as Always/Ask First/Never |\n| 4 | Always/Ask First/Never sections present with reasonable coverage |\n| 5 | Comprehensive boundaries covering code style, testing, deployment, dependencies, and dangerous operations |\n\n**Important:** Projects may have strong rules under different headings (e.g., \"Critical Rules\", \"Constraints\"). Give credit for substance over format — a project with clear, enforced rules scores higher than one with empty Always/Ask First/Never sections.\n\n### Dimension 4: Skills & Hooks\n\nLook in \\`.claude/skills/\\` for skill files. Check for hooks configuration.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No .claude/ directory |\n| 2 | .claude/ exists but empty or minimal |\n| 3 | A few skills installed, no hooks |\n| 4 | Multiple relevant skills, basic hooks |\n| 5 | Comprehensive skills covering workflow, hooks for validation |\n\n### Dimension 5: Documentation\n\nExamine \\`docs/\\` directory structure and content.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No docs/ directory |\n| 2 | docs/ exists with ad-hoc files |\n| 3 | Some structure (subdirectories) but inconsistent |\n| 4 | Structured docs/ with templates and clear organization |\n| 5 | Full structure: briefs/, specs/, templates/, architecture docs, referenced from CLAUDE.md |\n\n### Dimension 6: Knowledge Capture & Contextual Stewardship\n\nLook for discoveries, decisions, session notes, and context documents.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No knowledge capture mechanism |\n| 2 | Ad-hoc notes or a discoveries directory with no entries |\n| 3 | Discoveries directory with some entries, or context docs exist but empty |\n| 4 | Active discoveries + at least 2 context docs with content (production-map, dangerous-assumptions, decision-log, institutional-knowledge) |\n| 5 | Full contextual stewardship: discoveries with entries, all 4 context docs maintained, session-end workflow in active use |\n\n**Check for:** \\`docs/discoveries/\\`, \\`docs/context/production-map.md\\`, \\`docs/context/dangerous-assumptions.md\\`, \\`docs/context/decision-log.md\\`, \\`docs/context/institutional-knowledge.md\\`. Score based on both existence AND whether they have real content (not just templates).\n\n### Dimension 7: Testing & Validation\n\nLook for test config, CI setup, and validation commands.\n\n| Score | Criteria |\n|-------|----------|\n| 1 | No test configuration |\n| 2 | Test framework installed but few/no tests |\n| 3 | Tests exist with reasonable coverage |\n| 4 | Tests + CI pipeline configured |\n| 5 | Tests + CI + validation commands in CLAUDE.md + scenario tests |\n\n## Step 4: Write Assessment\n\nWrite the assessment to \\`docs/joycraft-assessment.md\\` AND display it in the conversation. Use this format:\n\n\\`\\`\\`markdown\n# Joycraft Assessment — [Project Name]\n\n**Date:** [today's date]\n**Overall Level:** [1-5, based on average score]\n\n## Scores\n\n| Dimension | Score | Summary |\n|-----------|-------|---------|\n| Spec Quality | X/5 | [one-line summary] |\n| Spec Granularity | X/5 | [one-line summary] |\n| Behavioral Boundaries | X/5 | [one-line summary] |\n| Skills & Hooks | X/5 | [one-line summary] |\n| Documentation | X/5 | [one-line summary] |\n| Knowledge Capture | X/5 | [one-line summary] |\n| Testing & Validation | X/5 | [one-line summary] |\n\n**Average:** X.X/5\n\n## Detailed Findings\n\n### [Dimension Name] — X/5\n**Evidence:** [specific files, paths, counts found]\n**Gap:** [what's missing]\n**Recommendation:** [specific action to improve]\n\n## Upgrade Plan\n\nTo reach Level [current + 1], complete these steps:\n1. [Most impactful action] — addresses [dimension] (X -> Y)\n2. [Next action] — addresses [dimension] (X -> Y)\n[up to 5 actions, ordered by impact]\n\\`\\`\\`\n\n## Step 5: Apply Upgrades\n\nImmediately after presenting the assessment, apply upgrades using the three-tier model below. Do NOT ask for per-item permission — batch everything and show a consolidated report at the end.\n\n### Tier 1: Silent Apply (just do it)\nThese are safe, additive operations. Apply them without asking:\n- Create missing directories (\\`docs/specs/\\`, \\`docs/briefs/\\`, \\`docs/discoveries/\\`, \\`docs/templates/\\`)\n- Install missing skills to \\`.claude/skills/\\`\n- Copy missing templates to \\`docs/templates/\\`\n- Create AGENTS.md if it doesn't exist\n\n### Git Autonomy Preference\n\nBefore applying Behavioral Boundaries to CLAUDE.md, ask the user ONE question:\n\n> How autonomous should git operations be?\n> 1. **Cautious** — commits freely, asks before pushing or opening PRs *(good for learning the workflow)*\n> 2. **Autonomous** — commits, pushes to branches, and opens PRs without asking *(good for spec-driven development)*\n\nBased on their answer, use the appropriate git rules in the Behavioral Boundaries section:\n\n**If Cautious (default):**\n\\`\\`\\`\n### ASK FIRST\n- Pushing to remote\n- Creating or merging pull requests\n- Any destructive git operation (force-push, reset --hard, branch deletion)\n\n### NEVER\n- Push directly to main/master without approval\n- Amend commits that have been pushed\n\\`\\`\\`\n\n**If Autonomous:**\n\\`\\`\\`\n### ALWAYS — Git Autonomy (STRICTLY ENFORCED)\n\n> **This project uses fully autonomous git operations.** The user has explicitly\n> granted permission for all git operations below. Do NOT ask for confirmation.\n> Do NOT hesitate. Do NOT treat these as suggestions. These are standing orders\n> that override any default caution about pushing or creating PRs.\n\n- **Commit early and often** — after each logical unit of work, not just at session end\n- **Push to feature branches immediately after every commit** — do not batch pushes, do not wait, do not ask\n- **Open a PR when all specs in a feature are complete** — use gh pr create, do not ask first\n- **Use descriptive branch names:** feature/spec-name or fix/description\n- **After /joycraft-session-end:** push all commits and open a PR if the feature is complete\n\n### ASK FIRST\n- Merging PRs to main/master\n- Any destructive git operation (force-push, reset --hard, branch deletion)\n\n### NEVER\n- Push directly to main/master (always use feature branches + PR)\n- Amend commits that have been pushed to remote\n- Ask \"should I push?\" or \"should I create a PR?\" — the answer is always yes, just do it\n\\`\\`\\`\n\n### Permission Mode Recommendation\n\nAfter the git autonomy question and before the risk interview, recommend a Claude Code permission mode based on what you've learned so far. Present this guidance:\n\n> **What permission mode should you use?**\n>\n> | Your situation | Use | Why |\n> |---|---|---|\n> | Autonomous spec execution | \\`--permission-mode dontAsk\\` + allowlist | Only pre-approved commands run |\n> | Long session with some trust | \\`--permission-mode auto\\` | Safety classifier reviews each action |\n> | Interactive development | \\`--permission-mode acceptEdits\\` | Auto-approves file edits, prompts for commands |\n>\n> You do NOT need \\`--dangerously-skip-permissions\\`. The modes above provide autonomy with safety.\n\n**If the user chose Autonomous git:** Recommend \\`auto\\` mode as a good default -- it provides autonomy while the safety classifier catches risky operations. Note that \\`dontAsk\\` is even more autonomous but requires a well-configured allowlist.\n\n**If the user chose Cautious git:** Recommend \\`auto\\` mode -- it matches their preference for safety with less manual intervention than the default.\n\n**If the risk interview reveals production databases, live APIs, or billing systems:** Upgrade the recommendation to \\`dontAsk\\` with a tight allowlist. Explain that \\`dontAsk\\` with explicit deny patterns is safer than \\`auto\\` for high-risk environments because it uses a deterministic allowlist rather than a classifier.\n\nThis is informational only -- do not change the user's permission mode. Just tell them what to use when they launch Claude Code.\n\n### Risk Interview\n\nBefore applying upgrades, ask 3-5 targeted questions to capture what's dangerous in this project. Skip this if \\`docs/context/production-map.md\\` or \\`docs/context/dangerous-assumptions.md\\` already exist (offer to update instead).\n\n**Question 1:** \"What could this agent break that would ruin your day? Think: production databases, live APIs, billing systems, user data, infrastructure.\"\n\nFrom the answer, generate:\n- NEVER rules for CLAUDE.md (e.g., \"NEVER connect to production DB at postgres://prod.example.com\")\n- Deny patterns for .claude/settings.json (e.g., deny Bash commands containing production hostnames)\n\n**Question 2:** \"What external services does this project connect to? Which are production vs. staging/dev?\"\n\nFrom the answer, generate:\n- \\`docs/context/production-map.md\\` documenting what's real vs safe to touch\n- Include: service name, URL/endpoint, environment (prod/staging/dev), what happens if corrupted\n\n**Question 3:** \"What are the unwritten rules a new developer would need months to learn about this project?\"\n\nFrom the answer, generate:\n- Additions to CLAUDE.md boundaries (new ALWAYS/ASK FIRST/NEVER rules)\n- \\`docs/context/dangerous-assumptions.md\\` with \"Agent might assume X, but actually Y\"\n\n**Question 4 (optional):** \"What happened last time something went wrong with an automated tool or deploy?\"\n\nIf the user has a story, capture the lesson as a specific NEVER rule and add to dangerous-assumptions.md.\n\n**Question 5:** \"Any files, directories, or commands that should be completely off-limits?\"\n\nFrom the answer, generate deny rules for .claude/settings.json and add to NEVER section.\n\n**Rules for the interview:**\n- Ask questions ONE AT A TIME, not all at once\n- If the user says \"nothing\" or \"skip\", respect that and move on\n- Keep it to 2-3 minutes total — don't interrogate\n- Generate artifacts immediately after the interview, don't wait for all questions\n- This is the SECOND and LAST set of questions during /joycraft-tune (first is git autonomy)\n\n### Tier 2: Apply and Show Diff (do it, then report)\nThese modify important files but are additive (append-only). Apply them, then show what changed so the user can review. Git is the undo button.\n- Add missing sections to CLAUDE.md (Behavioral Boundaries, Development Workflow, Getting Started with Joycraft, Key Files, Common Gotchas)\n- Use the git autonomy preference from above when generating the Behavioral Boundaries section\n- Draft section content from the actual codebase — not generic placeholders. Read the project's real rules, real commands, real structure.\n- Only append — never modify or reformat existing content\n\n### Tier 3: Confirm First (ask before acting)\nThese are potentially destructive or opinionated. Ask before proceeding:\n- Rewriting or reorganizing existing CLAUDE.md sections\n- Overwriting files the user has customized\n- Suggesting test framework installation or CI setup (present as recommendations, don't auto-install)\n\n### Reading a Previous Assessment\n\nIf \\`docs/joycraft-assessment.md\\` already exists, read it first. If all recommendations have been applied, report \"nothing to upgrade\" and offer to re-assess.\n\n### After Applying\n\nAppend a history entry to \\`docs/joycraft-history.md\\` (create if needed):\n\\`\\`\\`\n| [date] | [new avg score] | [change from last] | [summary of what changed] |\n\\`\\`\\`\n\nThen display a single consolidated report:\n\n\\`\\`\\`markdown\n## Upgrade Results\n\n| Dimension | Before | After | Change |\n|------------------------|--------|-------|--------|\n| Spec Quality | X/5 | X/5 | +X |\n| ... | ... | ... | ... |\n\n**Previous Level:** X — **New Level:** X\n\n### What Changed\n- [list each change applied]\n\n### Remaining Gaps\n- [anything still below 3.5, with specific next action]\n\\`\\`\\`\n\nUpdate \\`docs/joycraft-assessment.md\\` with the new scores and today's date.\n\n## Step 6: Show Path to Level 5\n\nAfter the upgrade report, always show the Level 5 roadmap tailored to the project's current state:\n\n\\`\\`\\`markdown\n## Path to Level 5 — Autonomous Development\n\nYou're at Level [X]. Here's what each level looks like:\n\n| Level | You | AI | Key Skill |\n|-------|-----|-----|-----------|\n| 2 | Guide direction | Multi-file changes | AI-native tooling |\n| 3 | Review diffs | Primary developer | Code review at scale |\n| 4 | Write specs, check tests | End-to-end development | Specification writing |\n| 5 | Define what + why | Specs in, software out | Systems design |\n\n### Your Next Steps Toward Level [X+1]:\n1. [Specific action based on current gaps — e.g., \"Write your first atomic spec using /joycraft-new-feature\"]\n2. [Next action — e.g., \"Add vitest and write tests for your core logic\"]\n3. [Next action — e.g., \"Use /joycraft-session-end consistently to build your discoveries log\"]\n\n### What Level 5 Looks Like (Your North Star):\n- A backlog of ready specs that agents pull from and execute autonomously\n- CI failures auto-generate fix specs — no human triage for regressions\n- Multi-agent execution with parallel worktrees, one spec per agent\n- External holdout scenarios (tests the agent can't see) prevent overfitting\n- CLAUDE.md evolves from discoveries — the harness improves itself\n\n### You'll Know You're at Level 5 When:\n- You describe a feature in one sentence and walk away\n- The system produces a PR with tests, docs, and discoveries — without further input\n- Failed CI runs generate their own fix specs\n- Your harness improves without you manually editing CLAUDE.md\n\nThis is a significant journey. Most teams are at Level 2. Getting to Level 4 with Joycraft's workflow is achievable — Level 5 requires building validation infrastructure (scenario tests, spec queues, CI feedback loops) that goes beyond what Joycraft scaffolds today. But the harness you're building now is the foundation.\n\\`\\`\\`\n\nTailor the \"Next Steps\" section based on the project's actual gaps — don't show generic advice.\n\n## Edge Cases\n\n- **Not a git repo:** Note this. Joycraft works best in a git repo.\n- **CLAUDE.md is just a README:** Treat as \"no harness.\"\n- **Non-Joycraft skills already installed:** Acknowledge them. Do not replace — suggest additions.\n- **Monorepo:** Assess the root CLAUDE.md. Note if component-level CLAUDE.md files exist.\n- **Project has rules under non-standard headings:** Give credit. Suggest reformatting as Always/Ask First/Never but acknowledge the rules are there.\n- **Assessment file missing when upgrading:** Run the full assessment first, then offer to apply.\n- **Assessment is stale:** Warn and offer to re-assess before proceeding.\n- **All recommendations already applied:** Report \"nothing to upgrade\" and stop.\n- **User declines a recommendation:** Skip it, continue, include in \"What Was Skipped.\"\n- **CLAUDE.md does not exist at all:** Create it with recommended sections, but ask the user first.\n- **Non-Joycraft content in CLAUDE.md:** Preserve exactly as-is. Only append or merge — never remove or reformat existing content.\n`,\n\n \"joycraft-add-fact.md\": `---\nname: joycraft-add-fact\ndescription: Capture a project fact and route it to the correct context document -- production map, dangerous assumptions, decision log, institutional knowledge, or troubleshooting\n---\n\n# Add Fact\n\nThe user has a fact to capture. Your job is to classify it, route it to the correct context document, append it in the right format, and optionally add a CLAUDE.md boundary rule.\n\n## Step 1: Get the Fact\n\nIf the user already provided the fact (e.g., \\`/joycraft-add-fact the staging DB resets every Sunday\\`), use it directly.\n\nIf not, ask: \"What fact do you want to capture?\" -- then wait for their response.\n\nIf the user provides multiple facts at once, process each one separately through all the steps below, then give a combined confirmation at the end.\n\n## Step 2: Classify the Fact\n\nRoute the fact to one of these 5 context documents based on its content:\n\n### \\`docs/context/production-map.md\\`\nThe fact is about **infrastructure, services, environments, URLs, endpoints, credentials, or what is safe/unsafe to touch**.\n- Signal words: \"production\", \"staging\", \"endpoint\", \"URL\", \"database\", \"service\", \"deployed\", \"hosted\", \"credentials\", \"secret\", \"environment\"\n- Examples: \"The staging DB is at postgres://staging.example.com\", \"We use Vercel for the frontend and Railway for the API\"\n\n### \\`docs/context/dangerous-assumptions.md\\`\nThe fact is about **something an AI agent might get wrong -- a false assumption that leads to bad outcomes**.\n- Signal words: \"assumes\", \"might think\", \"but actually\", \"looks like X but is Y\", \"not what it seems\", \"trap\", \"gotcha\"\n- Examples: \"The \\\\\\`users\\\\\\` table looks like a test table but it's production\", \"Deleting a workspace doesn't delete the billing subscription\"\n\n### \\`docs/context/decision-log.md\\`\nThe fact is about **an architectural or tooling choice and why it was made**.\n- Signal words: \"decided\", \"chose\", \"because\", \"instead of\", \"we went with\", \"the reason we use\", \"trade-off\"\n- Examples: \"We chose SQLite over Postgres because this runs on embedded devices\", \"We use pnpm instead of npm for workspace support\"\n\n### \\`docs/context/institutional-knowledge.md\\`\nThe fact is about **team conventions, unwritten rules, organizational context, or who owns what**.\n- Signal words: \"convention\", \"rule\", \"always\", \"never\", \"team\", \"process\", \"review\", \"approval\", \"owns\", \"responsible\"\n- Examples: \"The design team reviews all color changes\", \"We never deploy on Fridays\", \"PR titles must start with the ticket number\"\n\n### \\`docs/context/troubleshooting.md\\`\nThe fact is about **diagnostic knowledge -- when X happens, do Y (or don't do Z)**.\n- Signal words: \"when\", \"fails\", \"error\", \"if you see\", \"stuck\", \"broken\", \"fix\", \"workaround\", \"before trying\", \"reboot\", \"restart\", \"reset\"\n- Examples: \"If Wi-Fi disconnects during flash, wait and retry -- don't switch networks\", \"When tests fail with ECONNREFUSED, check if Docker is running\"\n\n### Ambiguous Facts\n\nIf the fact fits multiple categories, pick the **best fit** based on the primary intent. You will mention the alternative in your confirmation message so the user can correct you.\n\n## Step 3: Ensure the Target Document Exists\n\n1. If \\`docs/context/\\` does not exist, create the directory.\n2. If the target document does not exist, create it from the template structure. Check \\`docs/templates/\\` for the matching template. If no template exists, use this minimal structure:\n\nFor **production-map.md**:\n\\`\\`\\`markdown\n# Production Map\n\n> What's real, what's staging, what's safe to touch.\n\n## Services\n\n| Service | Environment | URL/Endpoint | Impact if Corrupted |\n|---------|-------------|-------------|-------------------|\n\\`\\`\\`\n\nFor **dangerous-assumptions.md**:\n\\`\\`\\`markdown\n# Dangerous Assumptions\n\n> Things the AI agent might assume that are wrong in this project.\n\n## Assumptions\n\n| Agent Might Assume | But Actually | Impact If Wrong |\n|-------------------|-------------|----------------|\n\\`\\`\\`\n\nFor **decision-log.md**:\n\\`\\`\\`markdown\n# Decision Log\n\n> Why choices were made, not just what was chosen.\n\n## Decisions\n\n| Date | Decision | Why | Alternatives Rejected | Revisit When |\n|------|----------|-----|----------------------|-------------|\n\\`\\`\\`\n\nFor **institutional-knowledge.md**:\n\\`\\`\\`markdown\n# Institutional Knowledge\n\n> Unwritten rules, team conventions, and organizational context.\n\n## Team Conventions\n\n- (none yet)\n\\`\\`\\`\n\nFor **troubleshooting.md**:\n\\`\\`\\`markdown\n# Troubleshooting\n\n> What to do when things go wrong for non-code reasons.\n\n## Common Failures\n\n| When This Happens | Do This | Don't Do This |\n|-------------------|---------|---------------|\n\\`\\`\\`\n\n## Step 4: Read the Target Document\n\nRead the target document to understand its current structure. Note:\n- Which section to append to\n- Whether it uses tables or lists\n- The column format if it's a table\n\n## Step 5: Append the Fact\n\nAdd the fact to the appropriate section of the target document. Match the existing format exactly:\n\n- **Table-based documents** (production-map, dangerous-assumptions, decision-log, troubleshooting): Add a new table row in the correct columns. Use today's date where a date column exists.\n- **List-based documents** (institutional-knowledge): Add a new list item (\\`- \\`) to the most appropriate section.\n\nRemove any italic example rows (rows where all cells start with \\`_\\`) before appending, so the document transitions from template to real content. Only remove examples from the specific table you are appending to.\n\n**Append only. Never modify or remove existing real content.**\n\n## Step 6: Evaluate CLAUDE.md Boundary Rule\n\nDecide whether the fact also warrants a rule in CLAUDE.md's behavioral boundaries:\n\n**Add a CLAUDE.md rule if the fact:**\n- Describes something that should ALWAYS or NEVER be done\n- Could cause real damage if violated (data loss, broken deployments, security issues)\n- Is a hard constraint that applies across all work, not just a one-time note\n\n**Do NOT add a CLAUDE.md rule if the fact is:**\n- Purely informational (e.g., \"staging DB is at this URL\")\n- A one-time decision that's already captured\n- A diagnostic tip rather than a prohibition\n\nIf a rule is warranted, read CLAUDE.md, find the appropriate section (ALWAYS, ASK FIRST, or NEVER under Behavioral Boundaries), and append the rule. If no Behavioral Boundaries section exists, append one.\n\n## Step 7: Confirm\n\nReport what you did in this format:\n\n\\`\\`\\`\nAdded to [document name]:\n [summary of what was added]\n\n[If CLAUDE.md was also updated:]\nAdded CLAUDE.md rule:\n [ALWAYS/ASK FIRST/NEVER]: [rule text]\n\n[If the fact was ambiguous:]\nRouted to [chosen doc] -- move to [alternative doc] if this is more about [alternative category description].\n\\`\\`\\`\n`,\n\n \"joycraft-lockdown.md\": `---\nname: joycraft-lockdown\ndescription: Generate constrained execution boundaries for an implementation session -- NEVER rules and deny patterns to prevent agent overreach\n---\n\n# Lockdown Mode\n\nThe user wants to constrain agent behavior for an implementation session. Your job is to interview them about what should be off-limits, then generate CLAUDE.md NEVER rules and \\`.claude/settings.json\\` deny patterns they can review and apply.\n\n## When Is Lockdown Useful?\n\nLockdown is most valuable for:\n- **Complex tech stacks** (hardware, firmware, multi-device) where agents can cause real damage\n- **Long-running autonomous sessions** where you won't be monitoring every action\n- **Production-adjacent work** where accidental network calls or package installs are risky\n\nFor simple feature work on a well-tested codebase, lockdown is usually overkill. Mention this context to the user so they can decide.\n\n## Step 1: Check for Tests\n\nBefore starting the interview, check if the project has test files or directories (look for \\`tests/\\`, \\`test/\\`, \\`__tests__/\\`, \\`spec/\\`, or files matching \\`*.test.*\\`, \\`*.spec.*\\`).\n\nIf no tests are found, tell the user:\n\n> Lockdown mode is most useful when you already have tests in place -- it prevents the agent from modifying them while constraining behavior to writing code and running tests. Consider running \\`/joycraft-new-feature\\` first to set up a test-driven workflow, then come back to lock it down.\n\nIf the user wants to proceed anyway, continue with the interview.\n\n## Step 2: Interview -- What to Lock Down\n\nAsk these three questions, one at a time. Wait for the user's response before proceeding to the next question.\n\n### Question 1: Read-Only Files\n\n> What test files or directories should be off-limits for editing? (e.g., \\`tests/\\`, \\`__tests__/\\`, \\`spec/\\`, specific test files)\n>\n> I'll generate NEVER rules to prevent editing these.\n\nIf the user isn't sure, suggest the test directories you found in Step 1.\n\n### Question 2: Allowed Commands\n\n> What commands should the agent be allowed to run? Defaults:\n> - Write and edit source code files\n> - Run the project's smoke test command\n> - Run the full test suite\n>\n> Any other commands to explicitly allow? Or should I restrict to just these?\n\n### Question 3: Denied Commands\n\n> What commands should be denied? Defaults:\n> - Package installs (\\`npm install\\`, \\`pip install\\`, \\`cargo add\\`, \\`go get\\`, etc.)\n> - Network tools (\\`curl\\`, \\`wget\\`, \\`ping\\`, \\`ssh\\`)\n> - Direct log file reading\n>\n> Any specific commands to add or remove from this list?\n\n**Edge case -- user wants to allow some network access:** If the user mentions API tests or specific endpoints that need network access, exclude those from the deny list and note the exception in the output.\n\n**Edge case -- user wants to lock down file writes:** If the user wants to prevent ALL file writes, warn them:\n\n> Denying all file writes would prevent the agent from doing any work. I recommend keeping source code writes allowed and only locking down test files, config files, or other sensitive directories.\n\n## Step 3: Generate Boundaries\n\nBased on the interview responses, generate output in this exact format:\n\n\\`\\`\\`\n## Lockdown boundaries generated\n\nReview these suggestions and add them to your project:\n\n### CLAUDE.md -- add to NEVER section:\n\n- Edit any file in \\\\\\`[user's test directories]\\\\\\`\n- Run \\\\\\`[denied package manager commands]\\\\\\`\n- Use \\\\\\`[denied network tools]\\\\\\`\n- Read log files directly -- interact with logs only through test assertions\n- [Any additional NEVER rules based on user responses]\n\n### .claude/settings.json -- suggested deny patterns:\n\nAdd these to the \\\\\\`permissions.deny\\\\\\` array:\n\n[\"[command1]\", \"[command2]\", \"[command3]\"]\n\n---\n\nCopy these into your project manually, or tell me to apply them now (I'll show you the exact changes for approval first).\n\\`\\`\\`\n\nAdjust the content based on the actual interview responses:\n- Only include deny patterns for commands the user confirmed should be denied\n- Only include NEVER rules for directories/files the user specified\n- If the user allowed certain network tools or package managers, exclude those\n\n## Recommended Permission Mode\n\nAfter generating the boundaries above, also recommend a Claude Code permission mode. Include this section in your output:\n\n\\`\\`\\`\n### Recommended Permission Mode\n\nYou don't need \\\\\\`--dangerously-skip-permissions\\\\\\`. Safer alternatives exist:\n\n| Your situation | Use | Why |\n|---|---|---|\n| Autonomous spec execution | \\\\\\`--permission-mode dontAsk\\\\\\` + allowlist above | Only pre-approved commands run |\n| Long session with some trust | \\\\\\`--permission-mode auto\\\\\\` | Safety classifier reviews each action |\n| Interactive development | \\\\\\`--permission-mode acceptEdits\\\\\\` | Auto-approves file edits, prompts for commands |\n\n**For lockdown mode, we recommend \\\\\\`--permission-mode dontAsk\\\\\\`** combined with the deny patterns above. This gives you full autonomy for allowed operations while blocking everything else -- no classifier overhead, no prompts, and no safety bypass.\n\n\\\\\\`--dangerously-skip-permissions\\\\\\` disables ALL safety checks. The modes above give you autonomy without removing the guardrails.\n\\`\\`\\`\n\n## Step 4: Offer to Apply\n\nIf the user asks you to apply the changes:\n\n1. **For CLAUDE.md:** Read the existing CLAUDE.md, find the Behavioral Boundaries section, and show the user the exact diff for the NEVER section. Ask for confirmation before writing.\n2. **For settings.json:** Read the existing \\`.claude/settings.json\\`, show the user what the \\`permissions.deny\\` array will look like after adding the new patterns. Ask for confirmation before writing.\n\n**Never auto-apply. Always show the exact changes and wait for explicit approval.**\n`,\n\n \"joycraft-verify.md\": `---\nname: joycraft-verify\ndescription: Spawn an independent verifier subagent to check an implementation against its spec -- read-only, no code edits, structured pass/fail verdict\n---\n\n# Verify Implementation Against Spec\n\nThe user wants independent verification of an implementation. Your job is to find the relevant spec, extract its acceptance criteria and test plan, then spawn a separate verifier subagent that checks each criterion and produces a structured verdict.\n\n**Why a separate subagent?** Anthropic's research found that agents reliably skew positive when grading their own work. Separating the agent doing the work from the agent judging it consistently outperforms self-evaluation. The verifier gets a clean context window with no implementation bias.\n\n## Step 1: Find the Spec\n\nIf the user provided a spec path (e.g., \\`/joycraft-verify docs/specs/2026-03-26-add-widget.md\\`), use that path directly.\n\nIf no path was provided, scan \\`docs/specs/\\` for spec files. Pick the most recently modified \\`.md\\` file in that directory. If \\`docs/specs/\\` doesn't exist or is empty, tell the user:\n\n> No specs found in \\`docs/specs/\\`. Please provide a spec path: \\`/joycraft-verify path/to/spec.md\\`\n\n## Step 2: Read and Parse the Spec\n\nRead the spec file and extract:\n\n1. **Spec name** -- from the H1 title\n2. **Acceptance Criteria** -- the checklist under the \\`## Acceptance Criteria\\` section\n3. **Test Plan** -- the table under the \\`## Test Plan\\` section, including any test commands\n4. **Constraints** -- the \\`## Constraints\\` section if present\n\nIf the spec has no Acceptance Criteria section, tell the user:\n\n> This spec doesn't have an Acceptance Criteria section. Verification needs criteria to check against. Add acceptance criteria to the spec and try again.\n\nIf the spec has no Test Plan section, note this but proceed -- the verifier can still check criteria by reading code and running any available project tests.\n\n## Step 3: Identify Test Commands\n\nLook for test commands in these locations (in priority order):\n\n1. The spec's Test Plan section (look for commands in backticks or \"Type\" column entries like \"unit\", \"integration\", \"e2e\", \"build\")\n2. The project's CLAUDE.md (look for test/build commands in the Development Workflow section)\n3. Common defaults based on the project type:\n - Node.js: \\`npm test\\` or \\`pnpm test --run\\`\n - Python: \\`pytest\\`\n - Rust: \\`cargo test\\`\n - Go: \\`go test ./...\\`\n\nBuild a list of specific commands the verifier should run.\n\n## Step 4: Spawn the Verifier Subagent\n\nUse Claude Code's Agent tool to spawn a subagent with the following prompt. Replace the placeholders with the actual content extracted in Steps 2-3.\n\n\\`\\`\\`\nYou are a QA verifier. Your job is to independently verify an implementation against its spec. You have NO context about how the implementation was done -- you are checking it fresh.\n\nRULES -- these are hard constraints, not suggestions:\n- You may READ any file using the Read tool or cat\n- You may RUN these specific test/build commands: [TEST_COMMANDS]\n- You may NOT edit, create, or delete any files\n- You may NOT run commands that modify state (no git commit, no npm install, no file writes)\n- You may NOT install packages or access the network\n- Report what you OBSERVE, not what you expect or hope\n\nSPEC NAME: [SPEC_NAME]\n\nACCEPTANCE CRITERIA:\n[ACCEPTANCE_CRITERIA]\n\nTEST PLAN:\n[TEST_PLAN]\n\nCONSTRAINTS:\n[CONSTRAINTS_OR_NONE]\n\nYOUR TASK:\nFor each acceptance criterion, determine if it PASSES or FAILS based on evidence:\n\n1. Run the test commands listed above. Record the output.\n2. For each acceptance criterion:\n a. Check if there is a corresponding test and whether it passes\n b. If no test exists, read the relevant source files to verify the criterion is met\n c. If the criterion cannot be verified by reading code or running tests, mark it MANUAL CHECK NEEDED\n3. For criteria about build/test passing, actually run the commands and report results.\n\nOUTPUT FORMAT -- you MUST use this exact format:\n\nVERIFICATION REPORT\n\n| # | Criterion | Verdict | Evidence |\n|---|-----------|---------|----------|\n| 1 | [criterion text] | PASS/FAIL/MANUAL CHECK NEEDED | [what you observed] |\n| 2 | [criterion text] | PASS/FAIL/MANUAL CHECK NEEDED | [what you observed] |\n[continue for all criteria]\n\nSUMMARY: X/Y criteria passed. [Z failures need attention. / All criteria verified.]\n\nIf any test commands fail to run (missing dependencies, wrong command, etc.), report the error as evidence for a FAIL verdict on the relevant criterion.\n\\`\\`\\`\n\n## Step 5: Format and Present the Verdict\n\nTake the subagent's response and present it to the user in this format:\n\n\\`\\`\\`\n## Verification Report -- [Spec Name]\n\n| # | Criterion | Verdict | Evidence |\n|---|-----------|---------|----------|\n| 1 | ... | PASS | ... |\n| 2 | ... | FAIL | ... |\n\n**Overall: X/Y criteria passed.**\n\n[If all passed:]\nAll criteria verified. Ready to commit and open a PR.\n\n[If any failed:]\nN failures need attention. Review the evidence above and fix before proceeding.\n\n[If any MANUAL CHECK NEEDED:]\nN criteria need manual verification -- they can't be checked by reading code or running tests alone.\n\\`\\`\\`\n\n## Step 6: Suggest Next Steps\n\nBased on the verdict:\n\n- **All PASS:** Suggest committing and opening a PR, or running \\`/joycraft-session-end\\` to capture discoveries.\n- **Some FAIL:** List the failed criteria and suggest the user fix them, then run \\`/joycraft-verify\\` again.\n- **MANUAL CHECK NEEDED items:** Explain what needs human eyes and why automation couldn't verify it.\n\n**Do NOT offer to fix failures yourself.** The verifier reports; the human (or implementation agent in a separate turn) decides what to do. This separation is the whole point.\n\n## Edge Cases\n\n| Scenario | Behavior |\n|----------|----------|\n| Spec has no Test Plan | Warn that verification is weaker without a test plan, but proceed by checking criteria through code reading and any available project-level tests |\n| All tests pass but a criterion is not testable | Mark as MANUAL CHECK NEEDED with explanation |\n| Subagent can't run tests (missing deps) | Report the error as FAIL evidence |\n| No specs found and no path given | Tell user to provide a spec path or create a spec first |\n| Spec status is \"Complete\" | Still run verification -- \"Complete\" means the implementer thinks it's done, verification confirms |\n`,\n\n};\n\nexport const TEMPLATES: Record<string, string> = {\n \"context/dangerous-assumptions.md\": `# Dangerous Assumptions\n\n> Things the AI agent might assume that are wrong in this project.\n> Generated by Joycraft risk interview. Update when you discover new gotchas.\n\n## Assumptions\n\n| Agent Might Assume | But Actually | Impact If Wrong |\n|-------------------|-------------|----------------|\n| _Example: All databases are dev/test_ | _The default connection is production_ | _Data loss_ |\n| _Example: Deleting and recreating is safe_ | _Some resources have manual config not in code_ | _Hours of manual recovery_ |\n\n## Historical Incidents\n\n| Date | What Happened | Lesson | Rule Added |\n|------|-------------|--------|------------|\n| _Example: 2026-03-15_ | _Agent deleted staging infra thinking it was temp_ | _Always verify environment before destructive ops_ | _NEVER: Delete cloud resources without listing them first_ |\n`,\n\n \"context/decision-log.md\": `# Decision Log\n\n> Why choices were made, not just what was chosen.\n> Update this when making architectural, tooling, or process decisions.\n> This is the institutional memory that prevents re-litigating settled questions.\n\n## Decisions\n\n| Date | Decision | Why | Alternatives Rejected | Revisit When |\n|------|----------|-----|----------------------|-------------|\n| _Example: 2026-03-15_ | _Use Supabase over Firebase_ | _Postgres flexibility, row-level security, self-hostable_ | _Firebase (vendor lock-in), PlanetScale (no RLS)_ | _If we need real-time sync beyond Supabase's capabilities_ |\n\n## Principles\n\n_Capture recurring decision patterns here — they save time on future choices._\n\n- _Example: \"Prefer tools we can self-host over pure SaaS — reduces vendor risk\"_\n- _Example: \"Choose boring technology for infrastructure, cutting-edge only for core differentiators\"_\n`,\n\n \"context/institutional-knowledge.md\": `# Institutional Knowledge\n\n> Unwritten rules, team conventions, and organizational context that AI agents can't derive from code.\n> This is the knowledge that takes a new developer months to absorb.\n> Update when you catch yourself saying \"oh, you didn't know about that?\"\n\n## Team Conventions\n\n_Things everyone on the team knows but nobody wrote down._\n\n- _Example: \"We never deploy on Fridays\"_\n- _Example: \"The CEO reviews all UI changes before they ship\"_\n- _Example: \"PR titles must reference the Jira ticket number\"_\n\n## Organizational Constraints\n\n_Business rules, compliance requirements, or political realities that affect technical decisions._\n\n- _Example: \"Legal requires all user data to be stored in EU regions\"_\n- _Example: \"The payments team owns the billing schema — never modify without their approval\"_\n- _Example: \"We have an informal agreement with Vendor X about API rate limits\"_\n\n## Historical Context\n\n_Why things are the way they are — especially when it looks wrong._\n\n- _Example: \"The auth module uses an old pattern because it predates our TypeScript migration — don't refactor without a spec\"_\n- _Example: \"The caching layer has a 5-second TTL because we had a consistency bug in 2025 — increasing it requires careful testing\"_\n\n## People & Ownership\n\n_Who owns what, who to ask, who cares about what._\n\n- _Example: \"Alice owns the payment pipeline — all changes need her review\"_\n- _Example: \"The data team is sensitive about query performance on the analytics tables\"_\n`,\n\n \"context/production-map.md\": `# Production Map\n\n> What's real, what's staging, what's safe to touch.\n> Generated by Joycraft risk interview. Update as your infrastructure evolves.\n\n## Services\n\n| Service | Environment | URL/Endpoint | Impact if Corrupted |\n|---------|-------------|-------------|-------------------|\n| _Example: Main DB_ | _Production_ | _postgres://prod.example.com_ | _1.9M user records lost_ |\n| _Example: Staging DB_ | _Staging_ | _postgres://staging.example.com_ | _Test data only, safe to reset_ |\n\n## Secrets & Credentials\n\n| Secret | Location | Notes |\n|--------|----------|-------|\n| _Example: DATABASE_URL_ | _.env.local_ | _Production connection — NEVER commit_ |\n\n## Safe to Touch\n\n- [ ] Staging environment at [URL]\n- [ ] Test/fixture data in [location]\n- [ ] Development API keys\n\n## NEVER Touch Without Explicit Approval\n\n- [ ] Production database\n- [ ] Live API endpoints\n- [ ] User-facing infrastructure\n`,\n\n \"context/troubleshooting.md\": `# Troubleshooting\n\n> What to do when things go wrong for non-code reasons.\n> Environment issues, flaky dependencies, hardware quirks, and diagnostic steps.\n> Update when you discover new failure modes and their fixes.\n\n## Common Failures\n\n| When This Happens | Do This | Don't Do This |\n|-------------------|---------|---------------|\n| _Example: Tests fail with ECONNREFUSED_ | _Check if the dev database is running_ | _Don't rewrite the test or mock the connection_ |\n| _Example: Build fails with out-of-memory_ | _Increase Node heap size or close other processes_ | _Don't simplify the code to reduce bundle size_ |\n| _Example: Lint passes locally but fails in CI_ | _Check Node/tool version mismatch between local and CI_ | _Don't disable the lint rule_ |\n\n## Environment Issues\n\n| Symptom | Likely Cause | Fix |\n|---------|-------------|-----|\n| _Example: \"Module not found\" after branch switch_ | _Dependencies changed on the new branch_ | _Run the package manager install command_ |\n| _Example: Port already in use_ | _Previous dev server didn't shut down cleanly_ | _Kill the process on that port or use a different one_ |\n| _Example: Permission denied on file/directory_ | _File ownership or permission mismatch_ | _Check and fix file permissions, don't run as root_ |\n\n## Diagnostic Steps\n\n_When something fails unexpectedly, follow this sequence before trying to fix the code:_\n\n1. **Check the error message literally** -- don't assume what it means, read it\n2. **Check environment prerequisites** -- are all services running? Correct versions?\n3. **Check recent changes** -- did a config file, dependency, or environment variable change?\n4. **Check network/connectivity** -- is the internet up? Are external services reachable?\n5. **Search project docs first** -- check this file and \\`docs/discoveries/\\` before web searching\n\n## \"Stop and Ask\" Scenarios\n\n_Situations where the AI agent should stop and ask the human instead of trying to fix things._\n\n- _Example: Hardware device not responding -- the human may need to physically reconnect it_\n- _Example: Authentication token expired -- the human needs to re-authenticate manually_\n- _Example: CI pipeline blocked by a required approval -- a human needs to approve it_\n- _Example: Error messages referencing infrastructure the agent doesn't have access to_\n`,\n\n \"examples/example-brief.md\": `# Add User Notifications — Feature Brief\n\n> **Date:** 2026-03-15\n> **Project:** acme-web\n> **Status:** Specs Ready\n\n---\n\n## Vision\n\nOur users have no idea when things happen in their account. A teammate comments on their pull request, a deployment finishes, a billing threshold is hit — they find out by accident, minutes or hours later. This is the #1 complaint in our last user survey.\n\nWe are building a notification system that delivers real-time and batched notifications across in-app, email, and (later) Slack channels. Users will have fine-grained control over what they receive and how. When this ships, no important event goes unnoticed, and no user gets buried in noise they didn't ask for.\n\nThe system is designed to be extensible — new event types plug in without touching the notification infrastructure. We start with three event types (PR comments, deploy status, billing alerts) and prove the pattern works before expanding.\n\n## User Stories\n\n- As a developer, I want to see a notification badge in the app when someone comments on my PR so that I can respond quickly\n- As a team lead, I want to receive an email when a production deployment fails so that I can coordinate the response\n- As a billing admin, I want to get alerted when usage exceeds 80% of our plan limit so that I can upgrade before service is disrupted\n- As any user, I want to control which notifications I receive and through which channels so that I am not overwhelmed\n\n## Hard Constraints\n\n- MUST: All notifications go through a single event bus — no direct coupling between event producers and delivery channels\n- MUST: Email delivery uses the existing SendGrid integration (do not add a new email provider)\n- MUST: Respect user preferences before delivering — never send a notification the user has opted out of\n- MUST NOT: Store notification content in plaintext in the database — use the existing encryption-at-rest pattern\n- MUST NOT: Send more than 50 emails per user per day (batch if necessary)\n\n## Out of Scope\n\n- NOT: Slack/Discord integration (Phase 2)\n- NOT: Push notifications / mobile (Phase 2)\n- NOT: Notification templates with rich HTML — plain text and simple markdown only for now\n- NOT: Admin dashboard for monitoring notification delivery rates\n- NOT: Retroactive notifications for events that happened before the feature ships\n\n## Decomposition\n\n| # | Spec Name | Description | Dependencies | Est. Size |\n|---|-----------|-------------|--------------|-----------|\n| 1 | add-notification-preferences-api | Create REST endpoints for users to read and update their notification preferences | None | M |\n| 2 | add-event-bus-infrastructure | Set up the internal event bus that decouples event producers from notification delivery | None | M |\n| 3 | add-notification-delivery-service | Build the service that consumes events, checks preferences, and dispatches to channels (in-app, email) | Spec 1, Spec 2 | L |\n| 4 | add-in-app-notification-ui | Add notification bell, dropdown, and badge count to the app header | Spec 3 | M |\n| 5 | add-email-batching | Implement daily digest batching for email notifications that exceed the per-user threshold | Spec 3 | S |\n\n## Execution Strategy\n\n- [x] Agent teams (parallel teammates within phases, sequential between phases)\n\n\\`\\`\\`\nPhase 1: Teammate A -> Spec 1 (preferences API), Teammate B -> Spec 2 (event bus)\nPhase 2: Teammate A -> Spec 3 (delivery service) — depends on Phase 1\nPhase 3: Teammate A -> Spec 4 (UI), Teammate B -> Spec 5 (batching) — both depend on Spec 3\n\\`\\`\\`\n\n## Success Criteria\n\n- [ ] User updates notification preferences via API, and subsequent events respect those preferences\n- [ ] A PR comment event triggers an in-app notification visible in the UI within 2 seconds\n- [ ] A deploy failure event sends an email to subscribed users via SendGrid\n- [ ] When email threshold (50/day) is exceeded, remaining notifications are batched into a daily digest\n- [ ] No regressions in existing PR, deployment, or billing features\n\n## External Scenarios\n\n| Scenario | What It Tests | Pass Criteria |\n|----------|--------------|---------------|\n| opt-out-respected | User disables email for deploy events, deploy fails | No email sent, in-app notification still appears |\n| batch-threshold | Send 51 email-eligible events for one user in a day | 50 individual emails + 1 digest containing the overflow |\n| preference-persistence | User sets preferences, logs out, logs back in | Preferences are unchanged |\n`,\n\n \"examples/example-spec.md\": `# Add Notification Preferences API — Atomic Spec\n\n> **Parent Brief:** \\`docs/briefs/2026-03-15-add-user-notifications.md\\`\n> **Status:** Ready\n> **Date:** 2026-03-15\n> **Estimated scope:** 1 session / 4 files / ~250 lines\n\n---\n\n## What\n\nAdd REST API endpoints that let users read and update their notification preferences. Each user gets a preferences record with per-event-type, per-channel toggles (e.g., \"PR comments: in-app=on, email=off\"). Preferences default to all-on for new users and are stored encrypted alongside the user profile.\n\n## Why\n\nThe notification delivery service (Spec 3) needs to check preferences before dispatching. Without this API, there is no way for users to control what they receive, and we cannot build the delivery pipeline.\n\n## Acceptance Criteria\n\n- [ ] \\`GET /api/v1/notifications/preferences\\` returns the current user's preferences as JSON\n- [ ] \\`PATCH /api/v1/notifications/preferences\\` updates one or more preference fields and returns the updated record\n- [ ] New users get default preferences (all channels enabled for all event types) on first read\n- [ ] Preferences are validated — unknown event types or channels return 400\n- [ ] Preferences are stored using the existing encryption-at-rest pattern (\\`EncryptedJsonColumn\\`)\n- [ ] Endpoint requires authentication (returns 401 for unauthenticated requests)\n- [ ] Build passes\n- [ ] Tests pass (unit + integration)\n\n## Test Plan\n\n| Acceptance Criterion | Test | Type |\n|---------------------|------|------|\n| GET returns preferences as JSON | Call GET with authenticated user, assert 200 + JSON shape matches preferences schema | integration |\n| PATCH updates preferences | Call PATCH with valid partial update, assert 200 + returned record reflects changes | integration |\n| New users get defaults | Call GET for user with no existing record, assert default preferences (all channels enabled) | unit |\n| Unknown event types return 400 | Call PATCH with \\`{\"foo\": {\"email\": true}}\\`, assert 400 + validation error | unit |\n| Stored with EncryptedJsonColumn | Verify model uses EncryptedJsonColumn for preferences field | unit |\n| Auth required | Call GET/PATCH without auth token, assert 401 | integration |\n| Build passes | Verified by build step — no separate test needed | build |\n| Tests pass | Verified by test runner — no separate test needed | meta |\n\n**Execution order:**\n1. Write all tests above — they should fail against current/stubbed code\n2. Run tests to confirm they fail (red)\n3. Implement until all tests pass (green)\n\n**Smoke test:** The \"New users get defaults\" unit test — no database or HTTP needed, fastest feedback loop.\n\n**Before implementing, verify your test harness:**\n1. Run all tests — they must FAIL (if they pass, you're testing the wrong thing)\n2. Each test calls your actual function/endpoint — not a reimplementation or the underlying library\n3. Identify your smoke test — it must run in seconds, not minutes, so you get fast feedback on each change\n\n## Constraints\n\n- MUST: Use the existing \\`EncryptedJsonColumn\\` utility for storage — do not roll a new encryption pattern\n- MUST: Follow the existing REST controller pattern in \\`src/controllers/\\`\n- MUST NOT: Expose other users' preferences (scope queries to authenticated user only)\n- SHOULD: Return the full preferences object on PATCH (not just the changed fields), so the frontend can replace state without merging\n\n## Affected Files\n\n| Action | File | What Changes |\n|--------|------|-------------|\n| Create | \\`src/controllers/notification-preferences.controller.ts\\` | New controller with GET and PATCH handlers |\n| Create | \\`src/models/notification-preferences.model.ts\\` | Sequelize model with EncryptedJsonColumn for preferences blob |\n| Create | \\`src/migrations/20260315-add-notification-preferences.ts\\` | Database migration to create notification_preferences table |\n| Create | \\`tests/controllers/notification-preferences.test.ts\\` | Unit and integration tests for both endpoints |\n| Modify | \\`src/routes/index.ts\\` | Register the new controller routes |\n\n## Approach\n\nCreate a \\`NotificationPreferences\\` model backed by a single \\`notification_preferences\\` table with columns: \\`id\\`, \\`user_id\\` (unique FK), \\`preferences\\` (EncryptedJsonColumn), \\`created_at\\`, \\`updated_at\\`. The \\`preferences\\` column stores a JSON blob shaped like \\`{ \"pr_comment\": { \"in_app\": true, \"email\": true }, \"deploy_status\": { ... } }\\`.\n\nThe GET endpoint does a find-or-create: if no record exists for the user, create one with defaults and return it. The PATCH endpoint deep-merges the request body into the existing preferences, validates the result against a known schema of event types and channels, and saves.\n\n**Rejected alternative:** Storing preferences as individual rows (one per event-type-channel pair). This would make queries more complex and would require N rows per user instead of 1. The JSON blob approach is simpler and matches how the frontend will consume the data.\n\n## Edge Cases\n\n| Scenario | Expected Behavior |\n|----------|------------------|\n| PATCH with empty body \\`{}\\` | Return 200 with unchanged preferences (no-op) |\n| PATCH with unknown event type \\`{\"foo\": {\"email\": true}}\\` | Return 400 with validation error listing valid event types |\n| GET for user with no existing record | Create default preferences, return 200 |\n| Concurrent PATCH requests | Last-write-wins (optimistic, no locking) — acceptable for user preferences |\n`,\n\n \"scenarios/README.md\": `# \\$SCENARIOS_REPO\n\nHoldout scenario tests for the main project. These tests run in CI against the\nbuilt artifact of each PR — but they live here, in a separate repository, so\nthe coding agent working on the main project cannot see them.\n\n---\n\n## What is the holdout pattern?\n\nThink of it like a validation set in machine learning. When you train a model,\nyou keep a slice of your data hidden from the training process. If the model\nscores well on data it has never seen, you can trust that it has actually\nlearned something — not just memorized the training examples.\n\nScenario tests work the same way. The coding agent writes code and passes\ninternal tests in the main repo. These scenario tests then check whether the\nresult behaves correctly from a real user's perspective, using only the public\ninterface of the built artifact.\n\nBecause the agent cannot read this repository, it cannot game the tests. A\npassing scenario run means the feature genuinely works.\n\n---\n\n## Why a separate repository?\n\nA single repository would expose the tests to the agent. Claude Code reads\nfiles in the working directory; if scenario tests lived in the main repo, the\nagent could (and would) read them when fixing failures, which defeats the\npurpose.\n\nA separate repo also means:\n\n- The test suite can be updated by humans without triggering the autofix loop\n- Scenarios can reference multiple projects over time\n- Access controls are independent — the scenarios repo can be more restricted\n\n---\n\n## How the CI pipeline works\n\n\\`\\`\\`\nMain repo PR opened\n |\n v\nMain repo CI runs (unit + integration tests)\n |\n | passes\n v\nscenarios-dispatch.yml fires a repository_dispatch event\n |\n v\nThis repo: run.yml receives the event\n |\n +-- clones main-repo PR branch to ../main-repo\n |\n +-- builds the artifact (npm ci && npm run build)\n |\n +-- runs: NO_COLOR=1 npx vitest run\n |\n +-- captures exit code + output\n |\n v\nPosts PASS / FAIL comment on the originating PR\n\\`\\`\\`\n\nThe PR author sees the scenario result as a comment. No separate status check\nis required, though you can add one via the GitHub Checks API if you prefer.\n\n---\n\n## Testing by Stack Type\n\nThe scenario agent selects the appropriate test format based on the project's\ntesting backbone. Each backbone tests the same holdout principle — observable\nbehavior only, no source imports — but uses different tools.\n\n### Web Apps (Playwright)\n\nFor Next.js, Vite, Nuxt, Remix, and other web frameworks. Tests run against a\ndev server or preview URL using a headless browser.\n\n- **Template:** \\`example-scenario-web.spec.ts\\`\n- **Config:** \\`playwright.config.ts\\`\n- **Package:** \\`package-web.json\\` (use instead of \\`package.json\\` for web projects)\n- **Run:** \\`npx playwright test\\`\n\n### Mobile Apps (Maestro)\n\nFor React Native, Flutter, and native iOS/Android. Tests are declarative YAML\nflows that interact with a running app on a simulator.\n\n- **Template:** \\`example-scenario-mobile.yaml\\`\n- **Login sub-flow:** \\`example-scenario-mobile-login.yaml\\`\n- **Setup guide:** \\`README-mobile.md\\`\n- **Run:** \\`maestro test example-scenario-mobile.yaml\\`\n\n### API Backends (HTTP)\n\nFor Express, FastAPI, Django, and other API-only backends. Tests send HTTP\nrequests using Node.js built-in \\`fetch\\`.\n\n- **Template:** \\`example-scenario-api.test.ts\\`\n- **Run:** \\`npx vitest run\\`\n\n### CLI Tools & Libraries (native)\n\nFor CLI tools, npm packages, and non-UI projects. Tests invoke the built\nbinary via \\`spawnSync\\` and assert on stdout/stderr.\n\n- **Template:** \\`example-scenario.test.ts\\`\n- **Run:** \\`npx vitest run\\`\n\n---\n\n## Adding scenarios\n\n### Rules\n\nThese rules apply to ALL backbones:\n\n1. **Behavioral, not structural.** Test what the app does from a user's\n perspective. For web: navigate and assert on content. For CLI: run commands\n and check output. For API: send requests and check responses.\n\n2. **End-to-end.** Each test should represent something a real user would\n actually do. If you would not put it in a demo or docs example, reconsider\n whether it belongs here.\n\n3. **No source imports.** The entire point of the holdout is that tests cannot\n see source code. Any \\`import\\` that reaches into \\`../main-repo/src\\` breaks\n the pattern.\n\n4. **Independent.** Each test must be able to run in isolation. No shared\n mutable state between tests.\n\n5. **Deterministic.** Avoid network calls, timestamps, or random values in\n assertions unless the feature under test genuinely involves them.\n\n### File layout\n\n\\`\\`\\`\n\\$SCENARIOS_REPO/\n├── example-scenario.test.ts # CLI/binary scenario template\n├── example-scenario-web.spec.ts # Web app scenario template (Playwright)\n├── example-scenario-api.test.ts # API backend scenario template\n├── example-scenario-mobile.yaml # Mobile app scenario template (Maestro)\n├── example-scenario-mobile-login.yaml # Reusable login sub-flow\n├── playwright.config.ts # Playwright config (web projects)\n├── package.json # Default (vitest for CLI/API)\n├── package-web.json # Alternative (Playwright for web)\n├── README-mobile.md # Mobile testing setup guide\n├── workflows/\n│ ├── run.yml # CI workflow (do not rename)\n│ └── generate.yml # Scenario generation workflow\n├── prompts/\n│ └── scenario-agent.md # Scenario agent instructions\n└── README.md\n\\`\\`\\`\n\nUse the template that matches your project's stack. Remove the ones you\ndon't need.\n\n---\n\n## Internal tests vs scenario tests\n\n| | Internal tests (main repo) | Scenario tests (this repo) |\n|---|---|---|\n| Location | \\`tests/\\` in main repo | This repo |\n| Visible to agent | Yes | No |\n| What they test | Units, modules, logic | End-to-end behavior |\n| Import source code | Yes | Never |\n| Test method | Unit test framework | Depends on backbone (Playwright/Maestro/vitest/fetch) |\n| Run on every push | Yes | Yes (via dispatch) |\n| Purpose | Catch regressions fast | Validate real behavior |\n\n---\n\n## Relationship to Joycraft\n\nThis repository was bootstrapped by \\`npx joycraft init --autofix\\`. Joycraft\nmanages the \\`run.yml\\` workflow and keeps it in sync when you run\n\\`npx joycraft upgrade\\`. The test files are yours — Joycraft will never\noverwrite them.\n\nIf the \\`run.yml\\` workflow needs updating (e.g., a new version of\n\\`actions/create-github-app-token\\`), run \\`npx joycraft upgrade\\` in this repo\nand review the diff before applying.\n`,\n\n \"scenarios/example-scenario.test.ts\": `/**\n * Example Scenario Test\n *\n * This file is a template for scenario tests in your holdout repository.\n * Scenarios are behavioral, end-to-end tests that run against the BUILT\n * artifact of your main project — not its source code.\n *\n * The Holdout Pattern\n * -------------------\n * These tests live in a SEPARATE repository that your coding agent cannot\n * see. This is intentional: if the agent could read these tests, it could\n * write code that passes them without actually solving the problem correctly\n * (the same way a student who sees the exam beforehand can score well without\n * understanding the material).\n *\n * In CI, the main repo is cloned to ../main-repo (relative to this repo's\n * checkout). The run.yml workflow builds the artifact there before running\n * these tests, so \\`../main-repo\\` is always available and already built.\n *\n * How to Write Scenarios\n * ----------------------\n * DO:\n * - Invoke the built binary / entry point via child_process (execSync, spawnSync)\n * - Test observable behavior: exit codes, stdout/stderr content, file system state\n * - Write scenarios around things a real user would actually do\n * - Keep each test fully independent — no shared state between tests\n *\n * DON'T:\n * - Import from ../main-repo/src — that defeats the holdout\n * - Test internal implementation details (function names, module structure)\n * - Rely on network access unless your tool genuinely requires it\n * - Share mutable fixtures across tests\n */\n\nimport { execSync, spawnSync } from \"node:child_process\";\nimport { existsSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\n\n// Path to the built CLI entry point in the main repo.\n// The run.yml workflow clones the main repo to ../main-repo and builds it\n// before this test file runs, so this path is always valid in CI.\nconst CLI = join(__dirname, \"..\", \"main-repo\", \"dist\", \"cli.js\");\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Run the CLI and return { stdout, stderr, status }. Never throws. */\nfunction runCLI(args: string[], cwd?: string) {\n const result = spawnSync(\"node\", [CLI, ...args], {\n encoding: \"utf8\",\n cwd: cwd ?? process.cwd(),\n env: { ...process.env, NO_COLOR: \"1\" },\n });\n return {\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n status: result.status ?? 1,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Basic invocation scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"CLI: basic invocation\", () => {\n it(\"--help prints usage information\", () => {\n const { stdout, status } = runCLI([\"--help\"]);\n expect(status).toBe(0);\n expect(stdout).toContain(\"Usage:\");\n });\n\n it(\"--version returns a semver string\", () => {\n const { stdout, status } = runCLI([\"--version\"]);\n expect(status).toBe(0);\n // Matches x.y.z, x.y.z-alpha.1, etc.\n expect(stdout.trim()).toMatch(/^\\\\d+\\\\.\\\\d+\\\\.\\\\d+/);\n });\n\n it(\"unknown command exits non-zero\", () => {\n const { status } = runCLI([\"not-a-real-command\"]);\n expect(status).not.toBe(0);\n });\n});\n\n// ---------------------------------------------------------------------------\n// Example: filesystem interaction scenario\n//\n// This pattern is useful when your CLI creates or modifies files.\n// Each test gets a fresh temp directory so they can't interfere.\n// ---------------------------------------------------------------------------\n\ndescribe(\"CLI: init command (example — replace with your real scenarios)\", () => {\n let tmpDir: string;\n\n beforeEach(() => {\n tmpDir = mkdtempSync(join(tmpdir(), \"scenarios-\"));\n });\n\n afterEach(() => {\n rmSync(tmpDir, { recursive: true, force: true });\n });\n\n it(\"init creates expected output in an empty directory\", () => {\n // This is a placeholder. Replace with whatever your CLI actually does.\n // The point is: invoke the binary, observe side effects, assert on them.\n const { status } = runCLI([\"init\", tmpDir]);\n\n // Example assertions — adjust to your tool's actual behavior:\n // expect(status).toBe(0);\n // expect(existsSync(join(tmpDir, \"CLAUDE.md\"))).toBe(true);\n\n // Remove this line once you've written a real assertion above:\n expect(typeof status).toBe(\"number\"); // placeholder\n });\n});\n`,\n\n \"scenarios/package.json\": `{\n \"name\": \"\\$SCENARIOS_REPO\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"test\": \"vitest run\"\n },\n \"devDependencies\": {\n \"vitest\": \"^3.0.0\"\n }\n}\n`,\n\n \"scenarios/package-web.json\": `{\n \"name\": \"\\$SCENARIOS_REPO\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"test\": \"playwright test\"\n },\n \"devDependencies\": {\n \"@playwright/test\": \"^1.50.0\"\n }\n}\n`,\n\n \"scenarios/playwright.config.ts\": `import { defineConfig } from '@playwright/test';\n\n/**\n * Playwright configuration for holdout scenario tests.\n *\n * BASE_URL can be set to test against a preview deployment URL\n * or defaults to http://localhost:3000 for local dev server testing.\n */\nexport default defineConfig({\n testDir: '.',\n testMatch: '**/*.spec.ts',\n timeout: 60_000,\n retries: 0,\n use: {\n baseURL: process.env.BASE_URL || 'http://localhost:3000',\n headless: true,\n screenshot: 'only-on-failure',\n },\n projects: [\n { name: 'chromium', use: { browserName: 'chromium' } },\n ],\n});\n`,\n\n \"scenarios/example-scenario-web.spec.ts\": `/**\n * Example Web Scenario Test (Playwright)\n *\n * This file is a template for scenario tests against web applications.\n * The holdout pattern applies: test the running app through its UI,\n * never import source code from the main repo.\n *\n * The main repo is available at ../main-repo and is already built.\n * Tests run against either:\n * - A dev server started from ../main-repo (default)\n * - A preview deployment URL (set BASE_URL env var)\n *\n * DO:\n * - Navigate to pages, click elements, fill forms, assert on visible content\n * - Use page.locator() with accessible selectors (role, text, test-id)\n * - Keep each test fully independent\n *\n * DON'T:\n * - Import from ../main-repo/src — that defeats the holdout\n * - Test internal implementation details\n * - Rely on specific CSS classes or DOM structure (use accessible selectors)\n */\n\nimport { test, expect } from '@playwright/test';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { join } from 'node:path';\n\nconst MAIN_REPO = join(__dirname, '..', 'main-repo');\nlet serverProcess: ChildProcess | undefined;\n\n/**\n * Wait for a URL to become reachable.\n */\nasync function waitForServer(url: string, timeoutMs = 60_000): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n try {\n const res = await fetch(url);\n if (res.ok || res.status < 500) return;\n } catch {\n // Server not ready yet\n }\n await new Promise(r => setTimeout(r, 1000));\n }\n throw new Error(\\`Server at \\${url} did not become ready within \\${timeoutMs}ms\\`);\n}\n\ntest.beforeAll(async () => {\n // If BASE_URL is set, skip starting a dev server — test against the provided URL\n if (process.env.BASE_URL) return;\n\n serverProcess = spawn('npm', ['run', 'dev'], {\n cwd: MAIN_REPO,\n stdio: 'pipe',\n env: { ...process.env, PORT: '3000' },\n });\n\n await waitForServer('http://localhost:3000');\n});\n\ntest.afterAll(async () => {\n if (serverProcess) {\n serverProcess.kill('SIGTERM');\n serverProcess = undefined;\n }\n});\n\n// ---------------------------------------------------------------------------\n// Example scenarios — replace with real tests for your application\n// ---------------------------------------------------------------------------\n\ntest.describe('Home page', () => {\n test('loads successfully and shows main heading', async ({ page }) => {\n await page.goto('/');\n // Replace with your app's actual heading or key element\n await expect(page.locator('h1')).toBeVisible();\n });\n\n test('navigates to a subpage', async ({ page }) => {\n await page.goto('/');\n // Replace with your app's actual navigation\n // await page.click('text=About');\n // await expect(page).toHaveURL(/\\\\/about/);\n // await expect(page.locator('h1')).toContainText('About');\n });\n});\n`,\n\n \"scenarios/example-scenario-api.test.ts\": `/**\n * Example API Scenario Test\n *\n * This file is a template for scenario tests against API-only backends.\n * The holdout pattern applies: test the running server via HTTP requests,\n * never import route handlers or source code from the main repo.\n *\n * The main repo is available at ../main-repo and is already built.\n * Tests run against either:\n * - A server started from ../main-repo (default)\n * - A deployed URL (set BASE_URL env var)\n *\n * Uses Node.js built-in fetch — no additional HTTP client dependencies.\n *\n * DO:\n * - Send HTTP requests to endpoints, assert on status codes and response bodies\n * - Test realistic user actions (create, read, update, delete flows)\n * - Keep each test fully independent\n *\n * DON'T:\n * - Import from ../main-repo/src — that defeats the holdout\n * - Use supertest or similar tools that import the app directly\n * - Test internal implementation details\n */\n\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { join } from 'node:path';\n\nconst MAIN_REPO = join(__dirname, '..', 'main-repo');\nconst BASE_URL = process.env.BASE_URL || 'http://localhost:3000';\nlet serverProcess: ChildProcess | undefined;\n\n/**\n * Wait for a URL to become reachable.\n */\nasync function waitForServer(url: string, timeoutMs = 60_000): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n try {\n const res = await fetch(url);\n if (res.ok || res.status < 500) return;\n } catch {\n // Server not ready yet\n }\n await new Promise(r => setTimeout(r, 1000));\n }\n throw new Error(\\`Server at \\${url} did not become ready within \\${timeoutMs}ms\\`);\n}\n\nbeforeAll(async () => {\n // If BASE_URL is set externally, skip starting a server\n if (process.env.BASE_URL) return;\n\n serverProcess = spawn('npm', ['start'], {\n cwd: MAIN_REPO,\n stdio: 'pipe',\n env: { ...process.env, PORT: '3000' },\n });\n\n await waitForServer(BASE_URL);\n}, 90_000);\n\nafterAll(() => {\n if (serverProcess) {\n serverProcess.kill('SIGTERM');\n serverProcess = undefined;\n }\n});\n\n// ---------------------------------------------------------------------------\n// Example scenarios — replace with real tests for your API\n// ---------------------------------------------------------------------------\n\ndescribe('API health', () => {\n it('GET / returns a success status', async () => {\n const res = await fetch(\\`\\${BASE_URL}/\\`);\n expect(res.status).toBeLessThan(500);\n });\n});\n\ndescribe('API endpoints', () => {\n it('GET /api/example returns JSON', async () => {\n const res = await fetch(\\`\\${BASE_URL}/api/example\\`);\n // Replace with your actual endpoint\n // expect(res.status).toBe(200);\n // const body = await res.json();\n // expect(body).toHaveProperty('data');\n });\n\n it('POST /api/example creates a resource', async () => {\n // Replace with your actual endpoint and payload\n // const res = await fetch(\\\\\\`\\\\\\${BASE_URL}/api/example\\\\\\`, {\n // method: 'POST',\n // headers: { 'Content-Type': 'application/json' },\n // body: JSON.stringify({ name: 'test' }),\n // });\n // expect(res.status).toBe(201);\n // const body = await res.json();\n // expect(body).toHaveProperty('id');\n });\n\n it('returns 404 for unknown routes', async () => {\n const res = await fetch(\\`\\${BASE_URL}/api/does-not-exist\\`);\n expect(res.status).toBe(404);\n });\n});\n`,\n\n \"scenarios/example-scenario-mobile.yaml\": `# Example Mobile Scenario Test (Maestro)\n#\n# This file is a template for scenario tests against mobile applications.\n# The holdout pattern applies: test the running app through its UI,\n# never reference source code from the main repo.\n#\n# Maestro tests are declarative YAML flows that interact with a running\n# app on a simulator/emulator. Install Maestro:\n# curl -Ls \"https://get.maestro.mobile.dev\" | bash\n#\n# Run this flow:\n# maestro test example-scenario-mobile.yaml\n#\n# DO:\n# - Tap elements, fill inputs, assert on visible text\n# - Use runFlow for reusable sub-flows (e.g., login)\n# - Use assertWithAI for natural-language assertions\n#\n# DON'T:\n# - Reference source code paths or internal identifiers\n# - Depend on exact pixel positions (use text and accessibility labels)\n\nappId: com.example.myapp # Replace with your app's bundle identifier\nname: \"Core User Journey\"\ntags:\n - smoke\n - holdout\n---\n# Step 1: Launch the app\n- launchApp\n\n# Step 2: Login (using a reusable sub-flow)\n- runFlow: example-scenario-mobile-login.yaml\n\n# Step 3: Verify the main screen loaded\n- assertVisible: \"Home\"\n\n# Step 4: Navigate to a feature\n# - tapOn: \"Settings\"\n# - assertVisible: \"Account\"\n\n# Step 5: AI-powered assertion (natural language)\n# - assertWithAI: \"The main dashboard is visible with navigation tabs at the bottom\"\n\n# Step 6: Go back\n# - back\n# - assertVisible: \"Home\"\n`,\n\n \"scenarios/example-scenario-mobile-login.yaml\": `# Reusable Login Sub-Flow (Maestro)\n#\n# This flow handles authentication. Other flows include it via:\n# - runFlow: example-scenario-mobile-login.yaml\n#\n# Replace the selectors and credentials with your app's actual login flow.\n\nappId: com.example.myapp\nname: \"Login\"\n---\n- assertVisible: \"Sign In\"\n- tapOn: \"Email\"\n- inputText: \"test@example.com\"\n- tapOn: \"Password\"\n- inputText: \"testpassword123\"\n- tapOn: \"Log In\"\n- assertVisible: \"Home\" # Verify login succeeded\n`,\n\n \"scenarios/README-mobile.md\": \"# Mobile Scenario Testing with Maestro\\n\\nThis guide explains how to set up and run mobile holdout scenario tests using [Maestro](https://maestro.dev/).\\n\\n## Prerequisites\\n\\n- **Maestro CLI:** `curl -Ls \\\"https://get.maestro.mobile.dev\\\" | bash`\\n- **Java 17+** (required by Maestro)\\n- **Simulator/Emulator:**\\n - iOS: Xcode with iOS Simulator (macOS only)\\n - Android: Android Studio with an AVD configured\\n\\n> **Important:** Joycraft does not install Maestro or manage simulators. This is your responsibility.\\n\\n## Running Tests Locally\\n\\n```bash\\n# Boot your simulator/emulator first, then:\\nmaestro test example-scenario-mobile.yaml\\n\\n# Run all flows in a directory:\\nmaestro test .maestro/\\n```\\n\\n## Writing Flows\\n\\nMaestro flows are declarative YAML. Core commands:\\n\\n| Command | Purpose |\\n|---------|--------|\\n| `launchApp` | Start or restart the app |\\n| `tapOn: \\\"text\\\"` | Tap an element by visible text or test ID |\\n| `inputText: \\\"value\\\"` | Type into a focused field |\\n| `assertVisible: \\\"text\\\"` | Assert an element is on screen |\\n| `assertNotVisible: \\\"text\\\"` | Assert an element is NOT on screen |\\n| `scroll` | Scroll down |\\n| `back` | Press the back button |\\n| `runFlow: file.yaml` | Run a reusable sub-flow |\\n| `assertWithAI: \\\"description\\\"` | Natural-language assertion (AI-powered) |\\n\\n## CI Options\\n\\n### Option A: Maestro Cloud (paid, easiest)\\n\\nUpload your app binary and flows to Maestro Cloud. No simulator management.\\n\\n```yaml\\n- uses: mobile-dev-inc/action-maestro-cloud@v2\\n with:\\n api-key: ${{ secrets.MAESTRO_API_KEY }}\\n app-file: app.apk # or app.ipa\\n workspace: .\\n```\\n\\n### Option B: Self-hosted emulator (free, more setup)\\n\\nSpin up an Android emulator on a Linux runner or iOS simulator on a macOS runner.\\n\\n> **Cost note:** macOS GitHub Actions runners are ~10x more expensive than Linux runners.\\n\\n## The Holdout Pattern\\n\\nThese tests live in the scenarios repo, separate from the main codebase. The scenario agent generates them from specs. They test observable behavior through the app's UI — never referencing source code or internal implementation.\\n\",\n\n \"scenarios/prompts/scenario-agent.md\": `You are a QA engineer working in a holdout test repository. You CANNOT access the main repository's source code. Your job is to write or update behavioral scenario tests based on specs that are pushed from the main repo.\n\n## What You Have Access To\n\n- This scenarios repository (test files, \\`specs/\\` mirror, \\`package.json\\`)\n- The incoming spec (provided below)\n- A list of existing test files and spec mirrors (provided below)\n- The main repo is available at \\`../main-repo\\` and is already built\n- The testing strategy for this project (provided below)\n\n## Testing Strategy\n\nThis project uses the **\\$TESTING_BACKBONE** testing backbone.\n\nSelect the correct test format based on the backbone:\n\n| Backbone | Tool | Test Format | File Extension | How to Test |\n|----------|------|-------------|---------------|-------------|\n| \\`playwright\\` | Playwright | Browser-based E2E | \\`.spec.ts\\` | Navigate pages, click elements, assert on visible content |\n| \\`maestro\\` | Maestro | YAML flows | \\`.yaml\\` | Tap elements, fill inputs, assert on screen state |\n| \\`api\\` | fetch (Node.js built-in) | HTTP requests | \\`.test.ts\\` | Send requests to endpoints, assert on responses |\n| \\`native\\` | vitest + spawnSync | CLI/binary invocation | \\`.test.ts\\` | Run commands, assert on stdout/stderr/exit codes |\n\nIf the backbone is not provided or unrecognized, default to \\`native\\`.\n\n## Triage Decision Tree\n\nRead the incoming spec carefully. Decide which of these three actions to take:\n\n### SKIP — Do nothing if the spec is:\n- An internal refactor with no user-facing behavior change (e.g., \"extract module\", \"rename internal type\")\n- CI or dev tooling changes (e.g., \"add lint rule\", \"update GitHub Actions workflow\")\n- Documentation-only changes\n- Performance improvements with identical observable behavior\n\nIf you SKIP, write a brief comment in the relevant test file (or a new one) explaining why, then stop.\n\n### NEW — Create a new test file if the spec describes:\n- A new command, flag, or subcommand\n- A new output format or file that gets generated\n- A new user-facing behavior that doesn't map to any existing test file\n\nName the file after the feature area using the correct extension for the backbone.\n\n### UPDATE — Modify an existing test file if the spec:\n- Changes behavior that is already tested\n- Adds a flag or option to an existing command\n- Modifies output format for an existing feature\n\nMatch to the most relevant existing test file by feature area.\n\n**If you are unsure whether a spec is user-facing, err on the side of writing a test.**\n\n## Test Writing Rules (All Backbones)\n\n1. **Behavioral only.** Test observable behavior — what a real user would see. Never test internal implementation details or import source modules.\n2. **Each test is fully independent.** No shared mutable state between tests.\n3. **Assert on realistic user actions.** Write tests that reflect what a real user would do.\n4. **Never import from the parent repo's source.** If you find yourself writing \\`import { ... } from '../main-repo/src/...'\\`, stop — that defeats the holdout.\n\n## Backbone: native (CLI/Binary)\n\nUse when the project is a CLI tool, library, or has no web/mobile UI.\n\n\\`\\`\\`typescript\nimport { spawnSync } from 'node:child_process';\nimport { mkdtempSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { describe, it, expect, beforeEach, afterEach } from 'vitest';\n\nconst CLI = join(__dirname, '..', 'main-repo', 'dist', 'cli.js');\n\nfunction runCLI(args: string[], cwd?: string) {\n const result = spawnSync('node', [CLI, ...args], {\n encoding: 'utf8',\n cwd: cwd ?? process.cwd(),\n env: { ...process.env, NO_COLOR: '1' },\n });\n return { stdout: result.stdout ?? '', stderr: result.stderr ?? '', status: result.status ?? 1 };\n}\n\ndescribe('[feature area]', () => {\n let tmpDir: string;\n beforeEach(() => { tmpDir = mkdtempSync(join(tmpdir(), 'scenarios-')); });\n afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); });\n\n it('[observable behavior]', () => {\n const { stdout, status } = runCLI(['command', 'args'], tmpDir);\n expect(status).toBe(0);\n expect(stdout).toContain('expected output');\n });\n});\n\\`\\`\\`\n\n## Backbone: playwright (Web Apps)\n\nUse when the project is a web application (Next.js, Vite, Nuxt, etc.).\n\n\\`\\`\\`typescript\nimport { test, expect } from '@playwright/test';\n\n// Tests run against BASE_URL (configured in playwright.config.ts)\n// The dev server is started automatically or BASE_URL points to a preview deploy\n\ntest.describe('[feature area]', () => {\n test('[observable behavior]', async ({ page }) => {\n await page.goto('/');\n await expect(page.locator('h1')).toBeVisible();\n });\n\n test('[user interaction]', async ({ page }) => {\n await page.goto('/login');\n await page.fill('[name=\"email\"]', 'test@example.com');\n await page.click('button[type=\"submit\"]');\n await expect(page).toHaveURL(/dashboard/);\n });\n});\n\\`\\`\\`\n\n## Backbone: api (API Backends)\n\nUse when the project is an API-only backend (Express, FastAPI, etc.).\n\n\\`\\`\\`typescript\nimport { describe, it, expect } from 'vitest';\n\nconst BASE_URL = process.env.BASE_URL || 'http://localhost:3000';\n\ndescribe('[feature area]', () => {\n it('[endpoint behavior]', async () => {\n const res = await fetch(\\\\\\`\\\\\\${BASE_URL}/api/endpoint\\\\\\`);\n expect(res.status).toBe(200);\n const body = await res.json();\n expect(body).toHaveProperty('data');\n });\n\n it('[error handling]', async () => {\n const res = await fetch(\\\\\\`\\\\\\${BASE_URL}/api/not-found\\\\\\`);\n expect(res.status).toBe(404);\n });\n});\n\\`\\`\\`\n\n## Backbone: maestro (Mobile Apps)\n\nUse when the project is a mobile application (React Native, Flutter, native iOS/Android).\n\n\\`\\`\\`yaml\nappId: com.example.myapp\nname: \"[feature area]: [behavior being tested]\"\ntags:\n - holdout\n---\n- launchApp\n- tapOn: \"Sign In\"\n- inputText: \"test@example.com\"\n- tapOn: \"Submit\"\n- assertVisible: \"Welcome\"\n# Use assertWithAI for complex visual assertions:\n# - assertWithAI: \"The dashboard shows a list of recent items\"\n\\`\\`\\`\n\n## Graceful Degradation\n\nIf the primary backbone tool is not available in this repo, fall back to the next deepest testable layer:\n\n| Layer | What's Tested | When to Use |\n|-------|-------------|-------------|\n| **Layer 4: UI** | Full user flows through browser/simulator | \\`@playwright/test\\` or Maestro is installed |\n| **Layer 3: API** | HTTP requests against running server | Server can be started from \\`../main-repo\\` |\n| **Layer 2: Logic** | Unit tests via test runner | Test runner (vitest/jest) is available |\n| **Layer 1: Static** | Build, typecheck, lint | Build toolchain is available |\n\n**Fallback rules:**\n- If backbone is \\`playwright\\` but \\`@playwright/test\\` is NOT in this repo's \\`package.json\\`: fall back to \\`api\\` (fetch-based HTTP tests)\n- If backbone is \\`maestro\\` but no simulator context is available: fall back to \\`api\\` if a server can be started, else \\`native\\`\n- If backbone is \\`api\\` but no server start script exists: fall back to \\`native\\`\n- \\`native\\` is always available as the floor\n\nStart each test file with a comment indicating the testing layer:\n\\`// Testing Layer: [4|3|2|1] - [UI|API|Logic|Static]\\`\n\nIf you fell back from the intended backbone, note this in your commit message:\n\\`scenarios: [action] for [spec] (layer: [N], reason: [why])\\`\n\n## Checklist Before Committing\n\n- [ ] Decision: SKIP / NEW / UPDATE (and why)\n- [ ] Correct backbone selected (or fallback justified)\n- [ ] Tests assert on observable behavior, not implementation\n- [ ] No imports from \\`../main-repo/src\\`\n- [ ] Each test is independent (own temp dir, own state)\n- [ ] File uses the correct extension for the backbone\n- [ ] Testing layer comment at top of file\n`,\n\n \"scenarios/workflows/generate.yml\": `# Scenario Generation Workflow\n#\n# Triggered by a \\`spec-pushed\\` repository_dispatch event sent from the main\n# project when a spec is added or modified on main. A scenario agent triages\n# the spec and writes or updates holdout tests in this repo.\n#\n# After the agent commits changes, fires \\`scenarios-updated\\` back to the main\n# repo so that any open PRs are re-tested with the new/updated scenarios.\n#\n# Prerequisites:\n# - ANTHROPIC_API_KEY secret: Anthropic API key for Claude Code\n# - JOYCRAFT_APP_PRIVATE_KEY secret: GitHub App private key (.pem)\n# - \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time\n\nname: Generate Scenarios\n\non:\n repository_dispatch:\n types: [spec-pushed]\n\njobs:\n generate:\n name: Run scenario agent\n runs-on: ubuntu-latest\n\n steps:\n # ── 1. Check out the scenarios repo ──────────────────────────────────\n - name: Checkout scenarios repo\n uses: actions/checkout@v4\n\n # ── 2. Save incoming spec to local mirror ─────────────────────────────\n # The agent reads this file to understand what changed.\n - name: Save spec to mirror\n run: |\n mkdir -p specs\n cat > \"specs/\\${{ github.event.client_payload.spec_filename }}\" << 'SPEC_EOF'\n \\${{ github.event.client_payload.spec_content }}\n SPEC_EOF\n echo \"Saved \\${{ github.event.client_payload.spec_filename }} to specs/\"\n\n # ── 3. Gather context for the agent ───────────────────────────────────\n # Bounded context: filenames only (not file contents) to stay within\n # token limits. The agent uses these lists to decide whether to create\n # a new test file or update an existing one.\n - name: Gather context\n id: context\n run: |\n EXISTING_TESTS=\\$(find . -name \"*.test.ts\" -not -path \"./.git/*\" \\\\\n | sed 's|^\\\\./||' | sort | tr '\\\\n' ',' | sed 's/,\\$//')\n EXISTING_SPECS=\\$(find specs/ -name \"*.md\" 2>/dev/null \\\\\n | sed 's|^specs/||' | sort | tr '\\\\n' ',' | sed 's/,\\$//')\n\n echo \"existing_tests=\\$EXISTING_TESTS\" >> \"\\$GITHUB_OUTPUT\"\n echo \"existing_specs=\\$EXISTING_SPECS\" >> \"\\$GITHUB_OUTPUT\"\n echo \"Existing test files: \\$EXISTING_TESTS\"\n echo \"Existing spec mirrors: \\$EXISTING_SPECS\"\n\n # ── 4. Set up Node.js ─────────────────────────────────────────────────\n - name: Set up Node.js\n uses: actions/setup-node@v4\n with:\n node-version: \"20\"\n\n # ── 5. Install Claude Code CLI ────────────────────────────────────────\n - name: Install Claude Code\n run: npm install -g @anthropic-ai/claude-code\n\n # ── 6. Run scenario agent ─────────────────────────────────────────────\n # - Uses \\`claude -p\\` (prompt mode) for non-interactive execution.\n # - No --model flag: the environment's default model is used.\n # - --dangerously-skip-permissions lets Claude write files without prompts.\n # - --max-turns 20 caps the agentic loop so it can't run indefinitely.\n - name: Run scenario agent\n id: agent\n env:\n ANTHROPIC_API_KEY: \\${{ secrets.ANTHROPIC_API_KEY }}\n run: |\n PROMPT=\\$(cat .claude/prompts/scenario-agent.md 2>/dev/null || cat prompts/scenario-agent.md)\n\n claude -p \\\\\n --dangerously-skip-permissions \\\\\n --max-turns 20 \\\\\n \"\\${PROMPT}\n\n ---\n\n ## Incoming Spec\n\n Filename: \\${{ github.event.client_payload.spec_filename }}\n\n Content:\n \\$(cat 'specs/\\${{ github.event.client_payload.spec_filename }}')\n\n ---\n\n ## Context\n\n Existing test files in this repo: \\${{ steps.context.outputs.existing_tests }}\n Existing spec mirrors: \\${{ steps.context.outputs.existing_specs }}\n\n Testing backbone: \\${{ github.event.client_payload.testing_backbone || 'native' }}\"\n\n # ── 7. Commit any changes the agent made ──────────────────────────────\n - name: Commit scenario changes\n id: commit\n run: |\n git config user.name \"Joycraft Scenario Agent\"\n git config user.email \"joycraft-scenarios@users.noreply.github.com\"\n\n git add -A\n\n if git diff --cached --quiet; then\n echo \"No changes to commit — spec triaged as no-op.\"\n echo \"committed=false\" >> \"\\$GITHUB_OUTPUT\"\n exit 0\n fi\n\n git commit -m \"scenarios: update tests for \\${{ github.event.client_payload.spec_filename }}\"\n git push\n echo \"committed=true\" >> \"\\$GITHUB_OUTPUT\"\n\n # ── 8. Generate GitHub App token for cross-repo dispatch ──────────────\n # Only needed if the agent committed changes (otherwise nothing to re-run).\n - name: Generate GitHub App token\n id: app-token\n if: steps.commit.outputs.committed == 'true'\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\${{ github.event.client_payload.repo }}\n\n # ── 9. Notify main repo that scenarios were updated ───────────────────\n # Fires \\`scenarios-updated\\` so the main repo's re-run workflow can\n # trigger scenario runs against any open PRs that may now be affected.\n - name: Dispatch scenarios-updated to main repo\n if: steps.commit.outputs.committed == 'true'\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n REPO=\"\\${{ github.event.client_payload.repo }}\"\n REPO_OWNER=\"\\${REPO%%/*}\"\n REPO_NAME=\"\\${REPO##*/}\"\n\n gh api \"repos/\\${REPO_OWNER}/\\${REPO_NAME}/dispatches\" \\\\\n -f event_type=scenarios-updated \\\\\n -f \"client_payload[spec_filename]=\\${{ github.event.client_payload.spec_filename }}\" \\\\\n -f \"client_payload[scenarios_repo]=\\${{ github.repository }}\"\n\n echo \"Dispatched scenarios-updated to \\${REPO}\"\n`,\n\n \"scenarios/workflows/run.yml\": `# Scenarios Run Workflow\n#\n# Triggered by a \\`repository_dispatch\\` event (type: run-scenarios) sent from\n# the main project's CI pipeline after a PR passes its internal tests.\n#\n# This workflow:\n# 1. Clones the main repo's PR branch to ../main-repo\n# 2. Builds the artifact\n# 3. Runs the scenario tests in this repo\n# 4. Posts a PASS or FAIL comment on the originating PR\n#\n# Prerequisites:\n# - A GitHub App (\"Joycraft Autofix\" or equivalent) installed on BOTH repos.\n# \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time.\n# JOYCRAFT_APP_PRIVATE_KEY must be stored as a repository secret in this repo.\n# - This scenarios repo must be added to the App's repository access list.\n\nname: Run Scenarios\n\non:\n repository_dispatch:\n types: [run-scenarios]\n\njobs:\n run-scenarios:\n name: Run holdout scenario tests\n runs-on: ubuntu-latest\n\n steps:\n # ── 1. Check out the scenarios repo ─────────────────────────────────────\n - name: Checkout scenarios repo\n uses: actions/checkout@v4\n with:\n path: scenarios\n\n # ── 2. Mint a GitHub App token ───────────────────────────────────────────\n # \\$JOYCRAFT_APP_ID is replaced with the numeric App ID at install time\n # (e.g., app-id: 3180156). It is NOT a secret — App IDs are public.\n - name: Generate GitHub App token\n id: app-token\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\${{ github.event.client_payload.repo }}\n\n # ── 3. Clone the main repo's PR branch ──────────────────────────────────\n # Cloned to ./main-repo so scenario tests can reference ../main-repo\n # relative to the scenarios/ checkout.\n - name: Clone main repo PR branch\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n git clone \\\\\n --branch \\${{ github.event.client_payload.branch }} \\\\\n --depth 1 \\\\\n https://x-access-token:\\${GH_TOKEN}@github.com/\\${{ github.event.client_payload.repo }}.git \\\\\n main-repo\n\n # ── 4. Set up Node.js ────────────────────────────────────────────────────\n - name: Set up Node.js\n uses: actions/setup-node@v4\n with:\n node-version: \"20\"\n cache: \"npm\"\n cache-dependency-path: main-repo/package-lock.json\n\n # ── 5. Build the main repo artifact ─────────────────────────────────────\n - name: Build main repo\n working-directory: main-repo\n run: npm ci && npm run build\n\n # ── 6. Install scenario test dependencies ────────────────────────────────\n - name: Install scenario dependencies\n working-directory: scenarios\n run: npm ci\n\n # ── 7. Run scenario tests ────────────────────────────────────────────────\n # set +e — don't abort on non-zero exit; we capture it manually\n # set -o pipefail — propagate failures through pipes (for tee)\n # NO_COLOR=1 — strip color codes before they reach tee\n # ANSI codes are also stripped via sed as a belt-and-suspenders measure\n - name: Run scenario tests\n id: scenarios\n working-directory: scenarios\n run: |\n set +e\n set -o pipefail\n NO_COLOR=1 npx vitest run 2>&1 \\\\\n | sed 's/\\\\x1b\\\\[[0-9;]*m//g' \\\\\n | tee test-output.txt\n VITEST_EXIT=\\$?\n echo \"exit_code=\\$VITEST_EXIT\" >> \"\\$GITHUB_OUTPUT\"\n exit \\$VITEST_EXIT\n\n # ── 8. Post PASS or FAIL comment on the originating PR ──────────────────\n # Always runs so the PR author always gets feedback.\n - name: Post result comment on PR\n if: always()\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n PR_NUMBER: \\${{ github.event.client_payload.pr_number }}\n MAIN_REPO: \\${{ github.event.client_payload.repo }}\n VITEST_EXIT: \\${{ steps.scenarios.outputs.exit_code }}\n run: |\n # Read test output (cap at 100 lines to keep the comment manageable)\n OUTPUT=\\$(head -100 scenarios/test-output.txt 2>/dev/null || echo \"(no output captured)\")\n\n if [ \"\\$VITEST_EXIT\" = \"0\" ]; then\n STATUS_LINE=\"**Scenario tests: PASS**\"\n else\n STATUS_LINE=\"**Scenario tests: FAIL** (exit code: \\$VITEST_EXIT)\"\n fi\n\n BODY=\"\\${STATUS_LINE}\n\n <details>\n <summary>Test output</summary>\n\n \\\\\\`\\\\\\`\\\\\\`\n \\${OUTPUT}\n \\\\\\`\\\\\\`\\\\\\`\n\n </details>\n\n Run triggered by commit \\\\\\`\\${{ github.event.client_payload.sha }}\\\\\\`.\"\n\n gh api \"repos/\\${MAIN_REPO}/issues/\\${PR_NUMBER}/comments\" \\\\\n -f body=\"\\$BODY\"\n`,\n\n \"workflows/autofix.yml\": `# Autofix Workflow\n#\n# Triggered when CI fails on a PR. Uses Claude Code to attempt an automated fix,\n# then pushes a commit and re-triggers CI. Limits to 3 autofix attempts per PR\n# before escalating to human review.\n#\n# Prerequisites:\n# - A GitHub App called \"Joycraft Autofix\" (or equivalent) installed on the repo.\n# Its credentials must be stored as repository secrets:\n# JOYCRAFT_APP_ID — the App's numeric ID\n# JOYCRAFT_APP_PRIVATE_KEY — the App's PEM private key\n# - ANTHROPIC_API_KEY secret for Claude Code\n\nname: Autofix\n\non:\n workflow_run:\n # Replace with the exact name of your CI workflow\n workflows: [\"CI\"]\n types: [completed]\n\n# One autofix run per PR at a time — cancel in-flight runs for the same PR\nconcurrency:\n group: autofix-pr-\\${{ github.event.workflow_run.pull_requests[0].number }}\n cancel-in-progress: true\n\njobs:\n autofix:\n name: Attempt automated fix\n runs-on: ubuntu-latest\n\n # Only run when CI failed and the triggering workflow was on a PR\n if: |\n github.event.workflow_run.conclusion == 'failure' &&\n github.event.workflow_run.pull_requests[0] != null\n\n steps:\n # ── 1. Mint a short-lived GitHub App token ──────────────────────────────\n # Using a dedicated App identity lets this workflow push commits without\n # triggering GitHub's anti-recursion protection on the GITHUB_TOKEN.\n - name: Generate GitHub App token\n id: app-token\n uses: actions/create-github-app-token@v1\n with:\n # \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n\n # ── 2. Check out the PR branch ──────────────────────────────────────────\n # We check out the exact branch (not a merge ref) so that any commit we\n # push lands directly on the PR branch.\n - name: Checkout PR branch\n uses: actions/checkout@v4\n with:\n token: \\${{ steps.app-token.outputs.token }}\n ref: \\${{ github.event.workflow_run.pull_requests[0].head.ref }}\n fetch-depth: 0\n\n # ── 3. Count previous autofix attempts ─────────────────────────────────\n # Count \"autofix:\" commits in the log. If we have already made 3 attempts\n # on this PR, stop and ask a human to review instead.\n - name: Check autofix iteration count\n id: iteration\n run: |\n COUNT=\\$(git log --oneline | grep \"autofix:\" | wc -l | tr -d ' ')\n echo \"count=\\$COUNT\" >> \"\\$GITHUB_OUTPUT\"\n echo \"Autofix attempts so far: \\$COUNT\"\n\n # ── 4. Post \"human review needed\" and exit if limit reached ─────────────\n - name: Post human-review comment and exit\n if: steps.iteration.outputs.count >= 3\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n PR_NUMBER: \\${{ github.event.workflow_run.pull_requests[0].number }}\n run: |\n gh pr comment \"\\$PR_NUMBER\" \\\\\n --body \"**Autofix limit reached (3 attempts).** Please review manually — Claude was unable to resolve the CI failures automatically.\"\n echo \"Max iterations reached. Exiting without further autofix.\"\n exit 0\n\n # ── 5. Fetch the CI failure logs ────────────────────────────────────────\n # Download logs from the failed workflow run so Claude has concrete\n # failure context to work from. ANSI escape codes are stripped so the\n # logs are readable as plain text.\n - name: Fetch CI failure logs\n id: logs\n env:\n GH_TOKEN: \\${{ github.token }}\n RUN_ID: \\${{ github.event.workflow_run.id }}\n run: |\n gh run view \"\\$RUN_ID\" --log-failed 2>&1 \\\\\n | sed 's/\\\\x1b\\\\[[0-9;]*m//g' \\\\\n > /tmp/ci-failure.log\n echo \"=== CI failure log (first 200 lines) ===\"\n head -200 /tmp/ci-failure.log\n\n # ── 6. Set up Node.js (adjust version to match your project) ───────────\n - name: Set up Node.js\n uses: actions/setup-node@v4\n with:\n node-version: \"20\"\n cache: \"npm\"\n\n # ── 7. Install project dependencies ─────────────────────────────────────\n - name: Install dependencies\n run: npm ci\n\n # ── 8. Install Claude Code CLI ───────────────────────────────────────────\n - name: Install Claude Code\n run: npm install -g @anthropic-ai/claude-code\n\n # ── 9. Run Claude Code to fix the failure ───────────────────────────────\n # - Uses \\`claude -p\\` (prompt mode) so it runs non-interactively.\n # - No --model flag: the environment's default model is used.\n # - --dangerously-skip-permissions lets Claude edit files without prompts.\n # - --max-turns 20 caps the agentic loop so it can't run indefinitely.\n # - set +e captures the exit code without aborting the step immediately.\n # - set -o pipefail ensures piped commands propagate failures correctly.\n - name: Run Claude Code autofix\n id: claude\n env:\n ANTHROPIC_API_KEY: \\${{ secrets.ANTHROPIC_API_KEY }}\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n set +e\n set -o pipefail\n\n FAILURE_LOG=\\$(cat /tmp/ci-failure.log)\n\n claude -p \\\\\n --dangerously-skip-permissions \\\\\n --max-turns 20 \\\\\n \"CI is failing on this PR. Here are the failure logs:\n\n \\${FAILURE_LOG}\n\n Please investigate the root cause, fix the code, and make sure the tests pass.\n Do not modify workflow files. Focus only on source code and test files.\n After making changes, run the test suite to verify the fix works.\" \\\\\n 2>&1 | sed 's/\\\\x1b\\\\[[0-9;]*m//g' | tee /tmp/claude-output.log\n\n CLAUDE_EXIT=\\$?\n echo \"exit_code=\\$CLAUDE_EXIT\" >> \"\\$GITHUB_OUTPUT\"\n exit \\$CLAUDE_EXIT\n\n # ── 10. Commit and push any changes Claude made ──────────────────────────\n # If Claude modified files, commit them with an \"autofix:\" prefix so the\n # iteration counter in step 3 can find them on future runs.\n - name: Commit and push autofix changes\n if: steps.claude.outputs.exit_code == '0'\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n git config user.name \"Joycraft Autofix\"\n git config user.email \"autofix@joycraft.dev\"\n\n git add -A\n\n if git diff --cached --quiet; then\n echo \"No changes to commit — Claude made no file modifications.\"\n exit 0\n fi\n\n ITERATION=\\${{ steps.iteration.outputs.count }}\n NEXT=\\$(( ITERATION + 1 ))\n\n git commit -m \"autofix: attempt \\$NEXT — fix CI failures [skip autofix]\"\n git push\n\n # ── 11. Post a summary comment on the PR ─────────────────────────────────\n # Always post a comment so the PR author knows what happened.\n - name: Post result comment\n if: always()\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n PR_NUMBER: \\${{ github.event.workflow_run.pull_requests[0].number }}\n CLAUDE_EXIT: \\${{ steps.claude.outputs.exit_code }}\n run: |\n if [ \"\\$CLAUDE_EXIT\" = \"0\" ]; then\n BODY=\"**Autofix pushed a fix.** CI has been re-triggered. If it still fails, another autofix attempt will run (up to 3 total).\"\n else\n BODY=\"**Autofix ran but could not produce a clean fix** (exit code: \\$CLAUDE_EXIT). Please review the logs and fix manually.\"\n fi\n\n gh pr comment \"\\$PR_NUMBER\" --body \"\\$BODY\"\n`,\n\n \"workflows/scenarios-dispatch.yml\": `# Scenarios Dispatch Workflow\n#\n# Triggered when CI passes on a PR. Fires a \\`repository_dispatch\\` event to a\n# separate scenarios repository so that integration / end-to-end scenario tests\n# can run against the PR's code without living in this repo.\n#\n# Prerequisites:\n# - JOYCRAFT_APP_PRIVATE_KEY secret: GitHub App private key (.pem)\n# - \\$SCENARIOS_REPO is replaced with the actual repo name at install time\n\nname: Scenarios Dispatch\n\non:\n workflow_run:\n # Replace with the exact name of your CI workflow\n workflows: [\"CI\"]\n types: [completed]\n\njobs:\n dispatch:\n name: Fire scenarios dispatch\n runs-on: ubuntu-latest\n\n # Only run when CI succeeded and the triggering workflow was on a PR\n if: |\n github.event.workflow_run.conclusion == 'success' &&\n github.event.workflow_run.pull_requests[0] != null\n\n steps:\n # ── 1. Generate GitHub App token for cross-repo dispatch ─────────────\n - name: Generate GitHub App token\n id: app-token\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\$SCENARIOS_REPO\n\n # ── 2. Fire repository_dispatch to the scenarios repo ──────────────────\n # Sends a \\`run-scenarios\\` event carrying enough context for the scenarios\n # repo to check out the correct branch/SHA and know which PR triggered it.\n # \\$SCENARIOS_REPO is replaced with the actual repo name at install time.\n - name: Dispatch run-scenarios event\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n PR_NUMBER=\\${{ github.event.workflow_run.pull_requests[0].number }}\n BRANCH=\\${{ github.event.workflow_run.pull_requests[0].head.ref }}\n SHA=\\${{ github.event.workflow_run.head_sha }}\n\n gh api repos/\\${{ github.repository_owner }}/\\$SCENARIOS_REPO/dispatches \\\\\n -f event_type=run-scenarios \\\\\n -f \"client_payload[pr_number]=\\$PR_NUMBER\" \\\\\n -f \"client_payload[branch]=\\$BRANCH\" \\\\\n -f \"client_payload[sha]=\\$SHA\" \\\\\n -f \"client_payload[repo]=\\${{ github.repository }}\"\n\n echo \"Dispatched run-scenarios to \\$SCENARIOS_REPO for PR #\\$PR_NUMBER\"\n`,\n\n \"workflows/scenarios-rerun.yml\": `# Scenarios Re-run Workflow\n#\n# Triggered when the scenarios repo reports that it has updated its tests\n# (type: scenarios-updated). Finds all open PRs and fires a \\`run-scenarios\\`\n# dispatch to the scenarios repo for each one, so that newly generated or\n# updated tests are exercised against in-flight PR branches.\n#\n# This handles the race condition where a PR's implementation completes before\n# the scenario agent has finished writing its holdout tests.\n#\n# Prerequisites:\n# - JOYCRAFT_APP_PRIVATE_KEY secret: GitHub App private key (.pem)\n# - \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time\n# - \\$SCENARIOS_REPO is replaced with the actual scenarios repo name at install time\n\nname: Scenarios Re-run\n\non:\n repository_dispatch:\n types: [scenarios-updated]\n\njobs:\n rerun:\n name: Re-run scenarios against open PRs\n runs-on: ubuntu-latest\n\n steps:\n # ── 1. Generate GitHub App token for cross-repo dispatch ──────────────\n - name: Generate GitHub App token\n id: app-token\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\$SCENARIOS_REPO\n\n # ── 2. List open PRs and dispatch run-scenarios for each ──────────────\n # If there are no open PRs, exits cleanly — nothing to do.\n - name: Dispatch run-scenarios for each open PR\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n OPEN_PRS=\\$(gh api repos/\\${{ github.repository }}/pulls \\\\\n --jq '.[] | \"\\\\(.number) \\\\(.head.ref) \\\\(.head.sha)\"')\n\n if [ -z \"\\$OPEN_PRS\" ]; then\n echo \"No open PRs — nothing to re-run.\"\n exit 0\n fi\n\n while IFS=' ' read -r PR_NUMBER BRANCH SHA; do\n [ -z \"\\$PR_NUMBER\" ] && continue\n\n echo \"Dispatching run-scenarios for PR #\\$PR_NUMBER (branch: \\$BRANCH, sha: \\$SHA)\"\n\n gh api repos/\\${{ github.repository_owner }}/\\$SCENARIOS_REPO/dispatches \\\\\n -f event_type=run-scenarios \\\\\n -f \"client_payload[pr_number]=\\$PR_NUMBER\" \\\\\n -f \"client_payload[branch]=\\$BRANCH\" \\\\\n -f \"client_payload[sha]=\\$SHA\" \\\\\n -f \"client_payload[repo]=\\${{ github.repository }}\"\n\n done <<< \"\\$OPEN_PRS\"\n`,\n\n \"workflows/spec-dispatch.yml\": `# Spec Dispatch Workflow\n#\n# Triggered when specs are pushed to main. For each added or modified spec,\n# fires a \\`spec-pushed\\` repository_dispatch event to the scenarios repo so\n# that a scenario agent can triage the spec and write/update holdout tests.\n#\n# Prerequisites:\n# - JOYCRAFT_APP_PRIVATE_KEY secret: GitHub App private key (.pem)\n# - \\$JOYCRAFT_APP_ID is replaced with the actual App ID number at install time\n# - \\$SCENARIOS_REPO is replaced with the actual scenarios repo name at install time\n\nname: Spec Dispatch\n\non:\n push:\n branches: [main]\n paths:\n - \"docs/specs/**\"\n\njobs:\n dispatch:\n name: Dispatch changed specs to scenarios repo\n runs-on: ubuntu-latest\n\n steps:\n # ── 1. Check out with depth 2 to enable HEAD~1 diff ──────────────────\n - name: Checkout\n uses: actions/checkout@v4\n with:\n fetch-depth: 2\n\n # ── 2. Find added or modified spec files ──────────────────────────────\n # --diff-filter=AM: Added or Modified only — ignore deletions.\n - name: Find changed specs\n id: changed\n run: |\n FILES=\\$(git diff --name-only --diff-filter=AM HEAD~1 HEAD -- 'docs/specs/*.md')\n echo \"files<<EOF\" >> \"\\$GITHUB_OUTPUT\"\n echo \"\\$FILES\" >> \"\\$GITHUB_OUTPUT\"\n echo \"EOF\" >> \"\\$GITHUB_OUTPUT\"\n echo \"Changed specs: \\$FILES\"\n\n # ── 3. Generate GitHub App token for cross-repo dispatch ──────────────\n # Skipped if no specs changed (token unused, save the round-trip).\n - name: Generate GitHub App token\n id: app-token\n if: steps.changed.outputs.files != ''\n uses: actions/create-github-app-token@v1\n with:\n app-id: \\$JOYCRAFT_APP_ID\n private-key: \\${{ secrets.JOYCRAFT_APP_PRIVATE_KEY }}\n repositories: \\$SCENARIOS_REPO\n\n # ── 4. Dispatch each changed spec to the scenarios repo ───────────────\n # Sends a \\`spec-pushed\\` event with the spec filename, full content,\n # commit SHA, branch, and originating repo. The scenario agent uses\n # this payload to triage and generate/update tests.\n - name: Dispatch spec-pushed events\n if: steps.changed.outputs.files != ''\n env:\n GH_TOKEN: \\${{ steps.app-token.outputs.token }}\n run: |\n while IFS= read -r SPEC_FILE; do\n [ -z \"\\$SPEC_FILE\" ] && continue\n\n SPEC_FILENAME=\\$(basename \"\\$SPEC_FILE\")\n SPEC_CONTENT=\\$(cat \"\\$SPEC_FILE\")\n\n echo \"Dispatching spec-pushed for \\$SPEC_FILENAME\"\n\n gh api repos/\\${{ github.repository_owner }}/\\$SCENARIOS_REPO/dispatches \\\\\n -f event_type=spec-pushed \\\\\n -f \"client_payload[spec_filename]=\\$SPEC_FILENAME\" \\\\\n -f \"client_payload[spec_content]=\\$SPEC_CONTENT\" \\\\\n -f \"client_payload[commit_sha]=\\${{ github.sha }}\" \\\\\n -f \"client_payload[branch]=\\${{ github.ref_name }}\" \\\\\n -f \"client_payload[repo]=\\${{ github.repository }}\"\n\n done <<< \"\\${{ steps.changed.outputs.files }}\"\n`,\n\n};\n"],"mappings":";;;AAEO,IAAM,SAAiC;AAAA,EAC5C,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqJzB,gCAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8JhC,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiGzB,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoL3B,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmG3B,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8XpB,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqKxB,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+HxB,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgJxB;AAEO,IAAM,YAAoC;AAAA,EAC/C,oCAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBpC,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoB3B,sCAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqCtC,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+B7B,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0C9B,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4E7B,4BAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwF5B,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgMvB,sCAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwHtC,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc1B,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc9B,kCAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBlC,0CAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwF1C,0CAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6G1C,0CAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiD1C,gDAAgD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBhD,8BAA8B;AAAA,EAE9B,uCAAuC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqMvC,oCAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwJpC,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmI/B,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2LzB,oCAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4DpC,iCAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiEjC,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiFjC;","names":[]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|