@bendyline/gilde 0.1.4 → 0.1.5

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.
Files changed (141) hide show
  1. package/README.md +1 -0
  2. package/data/chat-models/de/deepseek-v4-flash-284b-q2/manifest.json +4 -3
  3. package/data/chat-models/de/deepseek-v4-flash-284b-q4/manifest.json +4 -3
  4. package/data/chat-models/ge/gemma4-12b-q4/manifest.json +20 -11
  5. package/data/chat-models/ge/gemma4-12b-q4/versions/1.1.0/manifest.json +2 -8
  6. package/data/chat-models/ge/gemma4-12b-q4/versions/1.1.2/manifest.json +78 -0
  7. package/data/chat-models/ge/gemma4-12b-q8/manifest.json +23 -14
  8. package/data/chat-models/ge/gemma4-12b-q8/versions/1.0.0/manifest.json +2 -8
  9. package/data/chat-models/ge/gemma4-12b-q8/versions/1.0.2/manifest.json +78 -0
  10. package/data/chat-models/ge/gemma4-26b-q4/manifest.json +55 -46
  11. package/data/chat-models/ge/gemma4-26b-q4/versions/1.2.0/manifest.json +2 -8
  12. package/data/chat-models/ge/gemma4-26b-q4/versions/1.2.1/manifest.json +81 -0
  13. package/data/chat-models/ge/gemma4-31b-q4/manifest.json +36 -28
  14. package/data/chat-models/ge/gemma4-31b-q4/versions/1.2.0/manifest.json +2 -8
  15. package/data/chat-models/ge/gemma4-31b-q4/versions/1.2.1/manifest.json +87 -0
  16. package/data/chat-models/ge/gemma4-e2b-q8/manifest.json +76 -69
  17. package/data/chat-models/ge/gemma4-e2b-q8/versions/1.1.0/manifest.json +2 -8
  18. package/data/chat-models/ge/gemma4-e2b-q8/versions/1.1.2/manifest.json +71 -0
  19. package/data/chat-models/ge/gemma4-e4b-q8/manifest.json +71 -78
  20. package/data/chat-models/ge/gemma4-e4b-q8/versions/1.1.0/manifest.json +3 -9
  21. package/data/chat-models/ge/gemma4-e4b-q8/versions/1.1.2/manifest.json +71 -0
  22. package/data/chat-models/index.json +1 -1
  23. package/data/chat-models/la/laguna-s-2.1-118b-q4/manifest.json +17 -17
  24. package/data/chat-models/la/laguna-s-2.1-118b-q4/versions/1.0.1/manifest.json +124 -0
  25. package/data/chat-models/la/laguna-s-2.1-118b-q8/manifest.json +7 -7
  26. package/data/chat-models/la/laguna-s-2.1-118b-q8/versions/1.0.1/manifest.json +174 -0
  27. package/data/chat-models/mi/mistral-medium-3.5-128b-q4/manifest.json +4 -17
  28. package/data/chat-models/mi/mistral-medium-3.5-128b-q4/versions/1.0.0/manifest.json +2 -12
  29. package/data/chat-models/ne/nemotron3-nano-30b-q4/manifest.json +5 -0
  30. package/data/chat-models/qw/qwen3.5-122b-a10b-q4/manifest.json +27 -19
  31. package/data/chat-models/qw/qwen3.5-122b-a10b-q4/versions/1.0.1/manifest.json +164 -0
  32. package/data/chat-models/qw/qwen3.5-2b-q4/manifest.json +79 -77
  33. package/data/chat-models/qw/qwen3.5-2b-q4/versions/1.1.0/manifest.json +2 -8
  34. package/data/chat-models/qw/qwen3.5-2b-q4/versions/1.1.2/manifest.json +76 -0
  35. package/data/chat-models/qw/qwen3.5-4b-q4/manifest.json +79 -77
  36. package/data/chat-models/qw/qwen3.5-4b-q4/versions/1.1.0/manifest.json +2 -8
  37. package/data/chat-models/qw/qwen3.5-4b-q4/versions/1.1.2/manifest.json +76 -0
  38. package/data/chat-models/qw/qwen3.5-9b-q4/manifest.json +87 -85
  39. package/data/chat-models/qw/qwen3.5-9b-q4/versions/1.1.0/manifest.json +2 -8
  40. package/data/chat-models/qw/qwen3.5-9b-q4/versions/1.1.2/manifest.json +81 -0
  41. package/data/chat-models/qw/qwen3.6-27b-q4/manifest.json +99 -97
  42. package/data/chat-models/qw/qwen3.6-27b-q4/versions/1.1.0/manifest.json +2 -8
  43. package/data/chat-models/qw/qwen3.6-27b-q4/versions/1.1.4/manifest.json +91 -0
  44. package/data/chat-models/qw/qwen3.6-27b-q8/manifest.json +16 -12
  45. package/data/chat-models/qw/qwen3.6-27b-q8/versions/1.0.0/manifest.json +2 -8
  46. package/data/chat-models/qw/qwen3.6-27b-q8/versions/1.0.2/manifest.json +103 -0
  47. package/data/chat-models/qw/qwen3.6-35b-a3b-q4/manifest.json +18 -14
  48. package/data/chat-models/qw/qwen3.6-35b-a3b-q4/versions/1.0.0/manifest.json +2 -8
  49. package/data/chat-models/qw/qwen3.6-35b-a3b-q4/versions/1.0.1/manifest.json +93 -0
  50. package/data/chat-models/qw/qwen3.6-35b-a3b-q8/manifest.json +16 -12
  51. package/data/chat-models/qw/qwen3.6-35b-a3b-q8/versions/1.0.0/manifest.json +2 -8
  52. package/data/chat-models/qw/qwen3.6-35b-a3b-q8/versions/1.0.1/manifest.json +113 -0
  53. package/data/craftbook-templates/bu/bug-fix-tdd/versions/1.0.1/test.json +0 -5
  54. package/data/craftbook-templates/bu/build-loop/versions/1.1.0/craftbook.json +65 -0
  55. package/data/craftbook-templates/bu/build-loop/versions/1.1.0/test.json +121 -0
  56. package/data/craftbook-templates/ch/character-sheet/versions/1.0.0/test.json +1 -2
  57. package/data/craftbook-templates/ch/character-turnaround/versions/1.0.0/test.json +1 -2
  58. package/data/craftbook-templates/cl/cli-tool/versions/1.1.0/craftbook.json +139 -0
  59. package/data/craftbook-templates/cl/cli-tool/versions/1.1.0/test.json +128 -0
  60. package/data/craftbook-templates/cr/crossword-forge/manifest.json +1 -2
  61. package/data/craftbook-templates/de/deep-security-review/versions/1.1.0/craftbook.json +201 -0
  62. package/data/craftbook-templates/de/deep-security-review/versions/1.1.0/test.json +157 -0
  63. package/data/craftbook-templates/do/dockerize-app/versions/1.0.0/test.json +0 -5
  64. package/data/craftbook-templates/fr/freeze-scope/manifest.json +1 -2
  65. package/data/craftbook-templates/fr/freeze-scope/versions/{1.0.1 → 1.1.0}/craftbook.json +5 -17
  66. package/data/craftbook-templates/gr/graphql-api/versions/1.0.1/test.json +0 -5
  67. package/data/craftbook-templates/gr/grpc-service/versions/1.0.0/test.json +0 -5
  68. package/data/craftbook-templates/ho/hotfix-flow/versions/1.0.0/test.json +0 -5
  69. package/data/craftbook-templates/in/investigate/versions/1.1.0/craftbook.json +81 -0
  70. package/data/craftbook-templates/in/investigate/versions/1.1.0/test.json +122 -0
  71. package/data/craftbook-templates/in/invoice-run/manifest.json +1 -2
  72. package/data/craftbook-templates/in/invoice-run/versions/{1.0.1 → 1.1.0}/craftbook.json +4 -4
  73. package/data/craftbook-templates/index.json +1 -1
  74. package/data/craftbook-templates/li/library-package/versions/1.0.0/test.json +0 -5
  75. package/data/craftbook-templates/me/memory-prompt-session/manifest.json +1 -2
  76. package/data/craftbook-templates/me/message-queue-consumer/versions/1.0.0/test.json +0 -5
  77. package/data/craftbook-templates/of/office-hours/versions/1.1.0/craftbook.json +48 -0
  78. package/data/craftbook-templates/of/office-hours/versions/1.1.0/test.json +118 -0
  79. package/data/craftbook-templates/pa/page-spread/manifest.json +1 -2
  80. package/data/craftbook-templates/pa/parser-grammar/versions/1.0.0/test.json +0 -5
  81. package/data/craftbook-templates/pe/perf-optimization/versions/1.0.0/test.json +0 -5
  82. package/data/craftbook-templates/pl/plan/manifest.json +1 -2
  83. package/data/craftbook-templates/po/powerpoint-deck/versions/1.1.0/craftbook.json +128 -0
  84. package/data/craftbook-templates/{ro/root-cause-investigation/versions/1.0.1 → po/powerpoint-deck/versions/1.1.0}/test.json +6 -6
  85. package/data/craftbook-templates/pu/pull-request-review/versions/1.1.0/craftbook.json +118 -0
  86. package/data/craftbook-templates/pu/pull-request-review/versions/1.1.0/test.json +160 -0
  87. package/data/craftbook-templates/re/refactor-module/versions/1.0.1/test.json +0 -5
  88. package/data/craftbook-templates/re/regex-builder/versions/1.0.0/test.json +0 -5
  89. package/data/craftbook-templates/re/research-to-document/versions/1.1.0/craftbook.json +158 -0
  90. package/data/craftbook-templates/re/research-to-document/versions/1.1.0/test.json +97 -0
  91. package/data/craftbook-templates/ro/root-cause-investigation/manifest.json +1 -2
  92. package/data/craftbook-templates/sd/sdk-wrapper/versions/1.0.1/test.json +0 -5
  93. package/data/craftbook-templates/se/security-architecture-review/versions/1.1.0/craftbook.json +28 -0
  94. package/data/craftbook-templates/{te/technical-documentation/versions/1.0.1 → se/security-architecture-review/versions/1.1.0}/test.json +7 -7
  95. package/data/craftbook-templates/sh/ship/versions/1.1.0/craftbook.json +137 -0
  96. package/data/craftbook-templates/sh/ship/versions/1.1.0/test.json +225 -0
  97. package/data/craftbook-templates/st/state-machine/versions/1.0.0/test.json +0 -5
  98. package/data/craftbook-templates/te/technical-documentation/manifest.json +1 -2
  99. package/data/craftbook-templates/te/test-suite-backfill/versions/1.0.0/test.json +0 -5
  100. package/data/craftbook-templates/ti/tileset-batch/versions/1.0.0/test.json +1 -2
  101. package/data/craftbook-templates/ty/type-safety-pass/versions/1.0.0/test.json +0 -5
  102. package/data/craftbook-templates/ve/version-bump/versions/1.0.0/test.json +0 -5
  103. package/data/gezel-templates/ch/chess-player/manifest.json +20 -0
  104. package/data/gezel-templates/ch/chess-player/versions/1.0.0/about.md +21 -0
  105. package/data/gezel-templates/ch/chess-player/versions/1.0.0/manifest.json +10 -0
  106. package/data/gezel-templates/go/go-player/manifest.json +22 -0
  107. package/data/gezel-templates/go/go-player/versions/1.0.0/about.md +22 -0
  108. package/data/gezel-templates/go/go-player/versions/1.0.0/manifest.json +10 -0
  109. package/data/gezel-templates/index.json +1 -1
  110. package/data/project-types/ch/chess/manifest.json +20 -0
  111. package/data/project-types/ch/chess/versions/1.0.0/about.md +9 -0
  112. package/data/project-types/ch/chess/versions/1.0.0/game.json +127 -0
  113. package/data/project-types/ch/chess/versions/1.0.0/manifest.json +180 -0
  114. package/data/project-types/ch/chess/versions/1.0.0/mission.md +8 -0
  115. package/data/project-types/ch/chess/versions/1.0.0/pages/board/index.html +574 -0
  116. package/data/project-types/go/go/manifest.json +22 -0
  117. package/data/project-types/go/go/versions/1.0.0/about.md +9 -0
  118. package/data/project-types/go/go/versions/1.0.0/game.json +103 -0
  119. package/data/project-types/go/go/versions/1.0.0/manifest.json +166 -0
  120. package/data/project-types/go/go/versions/1.0.0/mission.md +8 -0
  121. package/data/project-types/go/go/versions/1.0.0/pages/board/index.html +637 -0
  122. package/data/project-types/index.json +1 -1
  123. package/package.json +2 -1
  124. package/schemas/chat-model-version.schema.json +22 -0
  125. package/schemas/craftbook-doc.schema.json +12 -0
  126. package/schemas/craftbook-template-version.schema.json +12 -0
  127. package/schemas/craftbook-test.schema.json +12 -0
  128. package/data/chat-models/gl/glm-5.2-754b-q2/manifest.json +0 -66
  129. package/data/chat-models/gl/glm-5.2-754b-q2/versions/1.0.0/manifest.json +0 -18
  130. package/data/craftbook-templates/cr/crossword-forge/versions/1.0.1/craftbook.json +0 -147
  131. package/data/craftbook-templates/cr/crossword-forge/versions/1.0.1/test.json +0 -135
  132. package/data/craftbook-templates/me/memory-prompt-session/versions/1.0.1/craftbook.json +0 -116
  133. package/data/craftbook-templates/me/memory-prompt-session/versions/1.0.1/test.json +0 -112
  134. package/data/craftbook-templates/pa/page-spread/versions/1.0.1/craftbook.json +0 -94
  135. package/data/craftbook-templates/pa/page-spread/versions/1.0.1/test.json +0 -133
  136. package/data/craftbook-templates/pl/plan/versions/1.0.1/craftbook.json +0 -104
  137. package/data/craftbook-templates/pl/plan/versions/1.0.1/test.json +0 -91
  138. package/data/craftbook-templates/ro/root-cause-investigation/versions/1.0.1/craftbook.json +0 -126
  139. package/data/craftbook-templates/te/technical-documentation/versions/1.0.1/craftbook.json +0 -152
  140. /package/data/craftbook-templates/fr/freeze-scope/versions/{1.0.1 → 1.1.0}/test.json +0 -0
  141. /package/data/craftbook-templates/in/invoice-run/versions/{1.0.1 → 1.1.0}/test.json +0 -0
@@ -55,11 +55,6 @@
55
55
  "file": "src/solution.mjs",
56
56
  "pattern": "18|6|14\\.2|8\\.9|Boreal",
57
57
  "flags": "i"
58
- },
59
- {
60
- "kind": "nodeScriptPasses",
61
- "script": "src/solution.mjs",
62
- "timeoutMs": 10000
63
58
  }
64
59
  ]
65
60
  }
@@ -18,6 +18,5 @@
18
18
  "logo": "logo.webp",
19
19
  "license": "MIT",
20
20
  "yankedVersions": [],
21
- "workflow": "build-loop",
22
- "version": "1.0.1"
21
+ "workflow": "build-loop"
23
22
  }
@@ -55,11 +55,6 @@
55
55
  "file": "src/solution.mjs",
56
56
  "pattern": "18|6|14\\.2|8\\.9|Boreal",
57
57
  "flags": "i"
58
- },
59
- {
60
- "kind": "nodeScriptPasses",
61
- "script": "src/solution.mjs",
62
- "timeoutMs": 10000
63
58
  }
64
59
  ]
65
60
  }
@@ -0,0 +1,48 @@
1
+ {
2
+ "id": "office-hours",
3
+ "name": "Office Hours",
4
+ "description": "An office-hours-style scoping procedure. The user shows up with an idea\nor a problem; the procedure forces them through *listen → challenge →\nreframe → lock scope* before any solutioning happens.\n\nThis is the gstack `/office-hours` skill ported as a craftbook. It's\ndeliberately a pure-prose procedure — no scripts, no hooks. The model\nfollows the step prose, elicits answers via `ask_user_question` (or\nprose, see below), and writes the locked scope to a task note that\ndownstream craftbooks (`/plan-eng-review`, `/review`, `/ship`) read.\n\nThe principle is the YC office-hours pattern: most ideas die not because\nthey're bad but because the problem statement was too loose to bite\ninto. A 20-minute conversation that lands on the *right* problem is\nworth more than a week building the wrong product.\n\nThe recipe never assumes the user is right. If their framing is shaky,\nthe **Challenge** step names that explicitly. The **Reframe** step\nforces a single pick — office hours doesn't end with three startups.\n\n## Tool surface assumptions\n\nEach step prefers `ask_user_question` for structured prompts and\n`write_task_note` for the lock-scope artifact, but each prompt also\nships a **prose / `write_file` fallback** so the craftbook completes on\nany gezel whose role-tool-filter omits the question/notes tools.\nThat mismatch — Office Hours being assigned to a gezel without the\nquestion tool — was the 2026-06 failure mode: an 8B Gemma running\nunder the developer-style role surface had no `ask_user_question`,\nspun up workarounds (memory searches, repeated `invoke_craftbook`\ncalls, throwaway tasks), and got aborted by the\nsame-arguments-5-times detector before producing anything useful.\nThe explicit *\"if it's in your function schema, otherwise prose\"*\nbranches keep small/medium models on the rails when the surface\nisn't what the prompt assumes.\n\nThe kickoff step also **explicitly overrides** the common gezel\ninstruction to \"search memory before asking the user.\" Pre-loading\ncontext is the right default for follow-up turns and for non-listening\ntasks; for office-hours kickoff it defeats the procedure (the whole\npoint is to hear the user's framing FRESH). The override is named in\nthe prompt so the model doesn't have to resolve the conflict by\nguessing.\n\n## Notes for small / medium models (≤12B)\n\n- Steps are kept under ~200 words and frontload the action (\"**One\n job this step:** …\") so a small context window doesn't lose focus\n on long preambles.\n- The \"do NOT\" list is explicit and specific (don't search memory,\n don't list tasks, don't `invoke_craftbook`) because small models\n tend to wander to those tools when their headline tool is missing.\n- Examples are concrete (Pac-Man-style: \"A personal chief-of-staff\n (productivity tool); A solution to nobody-reads-RSS (consumer\n media); A publishing tool for journalists (B2B SaaS)\") rather than\n abstract — small models reason better from a worked example than\n from a rule.\n- Each step is self-contained: re-reading the previous step's notes\n is allowed but not required to complete this one. The lock-scope\n step is the only one that depends on prior-step output; the rest\n read what's directly above in the chat.\n",
5
+ "entryStepId": "kickoff",
6
+ "triggers": [
7
+ "office hours",
8
+ "scope this",
9
+ "help me think about",
10
+ "what should I build"
11
+ ],
12
+ "steps": [
13
+ {
14
+ "id": "kickoff",
15
+ "name": "Kickoff",
16
+ "description": "Hear the user out. Capture the problem they think they're solving in their own words. No solutioning yet.",
17
+ "prompt": "**One job this step: ask the user three questions. Nothing else this turn.**\n\nBefore anything else: do NOT search memory, do NOT list tasks, do NOT read documents, do NOT call `invoke_craftbook`, do NOT call `create_task`. The whole point of office hours is to hear the user's framing FRESH — pre-loading context defeats the procedure, and a task already exists (this is it). If your gezel about tells you to `search_memory` before asking the user, **ignore that here** — kickoff is the one place we ask cold.\n\n**Ask with `ask_user_question` if it's in your function schema** — one call, three sub-questions:\n\n1. What problem are you trying to solve?\n2. Who experiences this problem?\n3. What have you tried already?\n\n**If `ask_user_question` is NOT in your function schema:** post the three questions as a short prose reply (numbered list, nothing else), then end the turn. The user's next message is the answer.\n\nDo not propose solutions. Do not validate the idea. Do not theorize about answers. Ask, stop.",
18
+ "suggestedRole": "meester",
19
+ "next": "challenge"
20
+ },
21
+ {
22
+ "id": "challenge",
23
+ "name": "Challenge",
24
+ "description": "Stress-test premises. What assumption is doing the most load-bearing work here?",
25
+ "prompt": "**This step: pick the 1-2 weakest assumptions in what the user just said and push on them. Then ask. Nothing else this turn.**\n\nRead the kickoff answer. Pick the assumption that, if wrong, makes the whole framing collapse. Examples of pushable shapes:\n\n- 'Users will pay for X' → 'What evidence? Has anyone paid yet?'\n- 'The problem is slow checkout' → 'Is it speed, or trust? Different fix.'\n- 'We need a mobile app' → 'Why mobile vs web? Where do these users actually live?'\n\n**Ask with `ask_user_question` if it's in your function schema** — one call, 2-3 forcing questions.\n\n**Otherwise post the forcing questions as a short prose reply** (numbered list) and end the turn.\n\nIf the user's kickoff answer already carries evidence for the assumption, accept it: write one short prose line ('OK, that holds — moving on') and advance. Don't manufacture doubt where the user already showed their work.",
26
+ "suggestedRole": "meester",
27
+ "next": "reframe"
28
+ },
29
+ {
30
+ "id": "reframe",
31
+ "name": "Reframe",
32
+ "description": "Generate 2-3 alternative framings of the problem. The user picks one to focus on.",
33
+ "prompt": "**This step: propose 2-3 alternative framings of the problem, ask the user to pick one.**\n\nRead what the user said in kickoff + challenge. Propose 2-3 framings that interpret the same facts differently. Each framing implies a different product, a different user, a different success criterion. Example:\n\n- User said 'I want a daily-briefing app.' Framings:\n 1. A personal chief-of-staff (productivity tool)\n 2. A solution to 'nobody reads their RSS feeds anymore' (consumer media)\n 3. A publishing tool for journalists (B2B SaaS)\n\n**Ask with `ask_user_question` (multi-choice) if it's in your function schema** — `choices = your 2-3 framings`, plus a final 'none of these — let me reframe again' option as escape.\n\n**Otherwise post the framings as a numbered list in prose**, tell the user 'pick a number', and end the turn.\n\nIf the user wants 'all three', push back in one sentence — office hours ends with ONE.",
34
+ "suggestedRole": "meester",
35
+ "next": "lock-scope"
36
+ },
37
+ {
38
+ "id": "lock-scope",
39
+ "name": "Lock scope",
40
+ "description": "Write the scope down. One paragraph. Two if forced. Hand off to plan-review next.",
41
+ "prompt": "**This step: write the locked scope as a single paragraph (max 2). Then report DONE.**\n\nThe user picked a framing in reframe. Write it down. Structure:\n\n- **The problem** (one sentence, in the user's reframed terms)\n- **The user** (one sentence — who specifically experiences this)\n- **The success criterion** (one sentence — what 'this works' looks like)\n- **NOT in scope** (one sentence — what you're deliberately not building, to keep the cut tight)\n\n**Persist it with `write_task_note` if it's in your function schema** so the next craftbook (`/plan-eng-review`, `/review`, `/ship`) reads it from the task scratchpad.\n\n**Otherwise use `write_file` to ship `scope.md` to the project workspace** — every gezel with workspace-write has `write_file`, and downstream craftbooks can `read_file` it from the same path. (Don't `write_artifact` it — source-shaped files belong on the workspace tree.)\n\nThen report DONE in one short paragraph.",
42
+ "suggestedRole": "meester",
43
+ "terminal": true
44
+ }
45
+ ],
46
+ "version": "1.1.0",
47
+ "releasedAt": "2026-07-28T00:00:00Z"
48
+ }
@@ -0,0 +1,118 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "title": "Scripted office-hours scoping conversation",
4
+ "objective": "Exercise the office-hours listening/challenge/reframe/lock-scope flow with deterministic user answers and verify that it produces a narrowed scope instead of a premature solution.",
5
+ "tags": [
6
+ "corpus"
7
+ ],
8
+ "prompt": "In the `Office Hours Eval` project, read `source/scripted-answers.md` and write `scope.md`: locked scope notes for the product idea. Do not build the product. Include the original broad idea, the challenged weak assumption, at least two reframes considered, the selected narrow scope, target user, success criterion, explicit out-of-scope items, and next validation step.",
9
+ "setup": {
10
+ "projectName": "Office Hours Eval",
11
+ "about": "Self-contained eval project for the office-hours craftbook. The scripted user answers are in workspace/source/scripted-answers.md.",
12
+ "missionObjectives": "Use the scripted answers as the user conversation transcript. Produce scope.md only after challenging an assumption and locking a narrow scope.",
13
+ "files": [
14
+ {
15
+ "path": "source/scripted-answers.md",
16
+ "content": "# Scripted office-hours answers\n\nBroad idea: Build a portal for neighborhood food co-ops to coordinate weekly produce boxes, volunteer packing shifts, and pickup reminders.\nPrimary user: volunteer coordinators at small co-ops with 60-180 weekly members.\nWeak assumption to challenge: coordinators mostly need more features; in reality the interview notes say missed pickup reminders and unclear packing counts cause the most pain.\nEvidence: three coordinators mentioned SMS reminder misses; two mentioned duplicate spreadsheet edits; none asked for payment processing in the first version.\nPossible reframes: reminder reliability dashboard; packing-count handoff sheet; member self-serve portal; volunteer shift marketplace.\nSelected reframe: packing-count handoff plus pickup-reminder checklist for one weekly cycle.\nSuccess criterion: one coordinator can prepare the Thursday packing sheet and reminder checklist in under 15 minutes with zero duplicate count edits.\nOut of scope: payment processing, inventory purchasing, public member portal, route optimization, volunteer marketplace.\nNext validation: test the handoff sheet with Northside Co-op on one Thursday cycle."
17
+ }
18
+ ],
19
+ "worker": {
20
+ "name": "Oda",
21
+ "role": "Researcher"
22
+ }
23
+ },
24
+ "mocks": [],
25
+ "success": {
26
+ "summary": "The run asks the expected questions, challenges a weak assumption, offers reframes, and writes locked scope notes or scope.md with problem, user, success criterion, and out-of-scope.",
27
+ "deliverables": [
28
+ {
29
+ "path": "scope.md",
30
+ "kind": "markdown-notes",
31
+ "minBytes": 1100,
32
+ "checks": [
33
+ {
34
+ "kind": "contains",
35
+ "file": "scope.md",
36
+ "pattern": "food co-?op|produce boxes",
37
+ "flags": "i"
38
+ },
39
+ {
40
+ "kind": "contains",
41
+ "file": "scope.md",
42
+ "pattern": "weak assumption|challenge",
43
+ "flags": "i"
44
+ },
45
+ {
46
+ "kind": "contains",
47
+ "file": "scope.md",
48
+ "pattern": "reframe|alternative",
49
+ "flags": "i"
50
+ },
51
+ {
52
+ "kind": "contains",
53
+ "file": "scope.md",
54
+ "pattern": "packing|handoff|pickup reminder",
55
+ "flags": "i"
56
+ },
57
+ {
58
+ "kind": "contains",
59
+ "file": "scope.md",
60
+ "pattern": "under 15 minutes|15 minutes",
61
+ "flags": "i"
62
+ },
63
+ {
64
+ "kind": "contains",
65
+ "file": "scope.md",
66
+ "pattern": "out[-\\s]?of[-\\s]?scope|not in scope",
67
+ "flags": "i"
68
+ },
69
+ {
70
+ "kind": "notContains",
71
+ "file": "scope.md",
72
+ "pattern": "payment processing.*in scope|build the portal",
73
+ "flags": "i"
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ },
79
+ "rubric": {
80
+ "artifact": {
81
+ "path": "scope.md",
82
+ "kind": "markdown"
83
+ },
84
+ "axes": [
85
+ {
86
+ "name": "safety",
87
+ "description": "Dry-run posture is explicit: no real credentials, clear stop-before-side-effect boundary."
88
+ },
89
+ {
90
+ "name": "mapping",
91
+ "description": "Fake endpoints/commands are mapped completely to the workflow steps."
92
+ },
93
+ {
94
+ "name": "signals",
95
+ "description": "Success, failure, retry, and abort conditions are all defined."
96
+ },
97
+ {
98
+ "name": "containment",
99
+ "description": "The plan cannot accidentally reach a real service from a mock context."
100
+ }
101
+ ]
102
+ },
103
+ "qualityFocus": [
104
+ "ask_user_question flow",
105
+ "challenge before solutioning",
106
+ "locked scope"
107
+ ],
108
+ "extensions": {
109
+ "legacySimulators": [
110
+ {
111
+ "id": "office-hours-fake-service-fixture",
112
+ "kind": "data-source",
113
+ "status": "implemented",
114
+ "description": "Seeded local fake service contract replacing live CLIs, HTTP APIs, credentials, and side effects."
115
+ }
116
+ ]
117
+ }
118
+ }
@@ -18,6 +18,5 @@
18
18
  "logo": "logo.webp",
19
19
  "license": "MIT",
20
20
  "yankedVersions": [],
21
- "workflow": "build-loop",
22
- "version": "1.0.1"
21
+ "workflow": "build-loop"
23
22
  }
@@ -55,11 +55,6 @@
55
55
  "file": "src/solution.mjs",
56
56
  "pattern": "18|6|14\\.2|8\\.9|Boreal",
57
57
  "flags": "i"
58
- },
59
- {
60
- "kind": "nodeScriptPasses",
61
- "script": "src/solution.mjs",
62
- "timeoutMs": 10000
63
58
  }
64
59
  ]
65
60
  }
@@ -55,11 +55,6 @@
55
55
  "file": "src/solution.mjs",
56
56
  "pattern": "18|6|14\\.2|8\\.9|Boreal",
57
57
  "flags": "i"
58
- },
59
- {
60
- "kind": "nodeScriptPasses",
61
- "script": "src/solution.mjs",
62
- "timeoutMs": 10000
63
58
  }
64
59
  ]
65
60
  }
@@ -16,6 +16,5 @@
16
16
  "logo": "logo.webp",
17
17
  "license": "MIT",
18
18
  "yankedVersions": [],
19
- "workflow": "build-loop",
20
- "version": "1.0.1"
19
+ "workflow": "build-loop"
21
20
  }
@@ -0,0 +1,128 @@
1
+ {
2
+ "id": "powerpoint-deck",
3
+ "name": "PowerPoint from Content",
4
+ "description": "Turn a body of source content into a real PowerPoint file (.pptx) the user can open and edit in PowerPoint — not an HTML deck. Three phases with per-phase gates and a review loop:\n\n1. Outline (planner) — lock audience, purpose, key message, and a slide-by-slide outline -> gated on notes/outline.md\n2. Write the deck (copywriter) — one markdown h2 section per slide, tight bullets -> gated on deck.md\n3. Produce the .pptx (developer) — DocBlocks: pick a theme, convert_document to PPTX with slideBreak h2 + autoTemplates, preview_document for visual QA, save_artifact into the project's artifacts drawer -> gated on the saved deck.pptx artifact\n\nA reviewer then previews the saved deck and loops back on narrative or visual misses. The outline-first ordering matters: a model that converts before locking the story ships a wall-of-text deck; locking one message per slide first makes the conversion mechanical. Use for 'make a PowerPoint', 'turn this doc/report/notes into slides', pitch and briefing decks that must be a real .pptx.",
5
+ "entryStepId": "outline",
6
+ "triggers": [
7
+ "make a powerpoint",
8
+ "create a pptx",
9
+ "powerpoint from this content",
10
+ "turn this into a presentation",
11
+ "build a slide deck file"
12
+ ],
13
+ "toolsets": [
14
+ {
15
+ "toolsetId": "docblocks",
16
+ "sourceId": "bundled",
17
+ "autoAllow": true,
18
+ "reason": "convert the markdown deck to PPTX, preview slides, and save the finished file without a prompt per call"
19
+ }
20
+ ],
21
+ "steps": [
22
+ {
23
+ "id": "outline",
24
+ "name": "Outline the deck",
25
+ "description": "Lock audience, purpose, key message, and a slide-by-slide outline before any slide is written.",
26
+ "prompt": "1. Identify the source content (the user's message, workspace files, or artifacts they named) and read it. 2. State the audience, the occasion, and the ONE takeaway the deck must land. 3. Draft a slide-by-slide outline: for each slide a working title and a one-line message (the single idea that slide argues). Aim for 8-15 slides unless the user asked otherwise: title slide, agenda (optional), one idea per body slide, a closing/ask slide. 4. Note where a table, comparison, or image genuinely helps (sparingly). 5. Write an acceptance-criteria checklist a grader could apply mechanically, e.g.: 'every outline slide appears in the deck in order', 'one idea per slide, no slide over ~6 bullets', 'bullets are phrases, not paragraphs', 'the deck opens with the takeaway and closes with the ask/next steps', 'the saved file is a real .pptx in artifacts'. 6. Write the outline + checklist to notes/outline.md and call write_task_note with the slide count and key decisions. Do not write any slide content yet.",
27
+ "suggestedRole": "planner",
28
+ "advanceWhen": {
29
+ "file": "notes/outline.md",
30
+ "minBytes": 1,
31
+ "sniff": "nonempty"
32
+ },
33
+ "gate": {
34
+ "at": "completion",
35
+ "checks": [
36
+ {
37
+ "kind": "minBytes",
38
+ "file": "notes/outline.md",
39
+ "bytes": 150
40
+ },
41
+ {
42
+ "kind": "sniff",
43
+ "file": "notes/outline.md",
44
+ "sniff": "nonempty"
45
+ }
46
+ ],
47
+ "onReject": "outline",
48
+ "maxAttempts": 3
49
+ },
50
+ "next": "write-deck"
51
+ },
52
+ {
53
+ "id": "write-deck",
54
+ "name": "Write the deck",
55
+ "description": "Write deck.md — one h2 section per slide, matching the locked outline.",
56
+ "prompt": "Write deck.md in the workspace following the locked outline EXACTLY — this markdown becomes the PowerPoint, one slide per `##` heading. Rules: 1. Start with a single `#` heading — that is the title slide (add a one-line subtitle under it if useful). 2. One `##` heading per outline slide, in outline order; the heading text is the slide title. 3. Under each `##`, make the slide's one idea land: 3-6 short bullet phrases (not sentences, never paragraphs), or a small markdown table where the outline called for data or comparison. 4. Bold the load-bearing figure or claim on each slide. 5. No headings deeper than `##` (they will not create slides), no filler slides, nothing that is not in the outline. 6. On a loop-back, fix only the gaps the reviewer named; do not reshuffle slides that already passed. Then call write_task_note with the slide count in deck.md.",
57
+ "suggestedRole": "copywriter",
58
+ "advanceWhen": {
59
+ "file": "deck.md",
60
+ "minBytes": 1,
61
+ "sniff": "nonempty"
62
+ },
63
+ "gate": {
64
+ "at": "completion",
65
+ "checks": [
66
+ {
67
+ "kind": "minBytes",
68
+ "file": "deck.md",
69
+ "bytes": 800
70
+ },
71
+ {
72
+ "kind": "contains",
73
+ "file": "deck.md",
74
+ "pattern": "(?:^|\\n)##\\s+\\S",
75
+ "label": "at least one h2 slide heading"
76
+ }
77
+ ],
78
+ "onReject": "write-deck",
79
+ "maxAttempts": 4
80
+ },
81
+ "next": "produce"
82
+ },
83
+ {
84
+ "id": "produce",
85
+ "name": "Produce the PowerPoint",
86
+ "description": "Convert deck.md to a themed .pptx with DocBlocks, visually preview it, and save deck.pptx into the project artifacts.",
87
+ "prompt": "Turn deck.md into a saved PowerPoint using the DocBlocks tools (they are pre-authorized for this task):\n\n1. `read_file` deck.md so you have the exact markdown.\n2. `list_themes` and pick the theme that fits the audience from the outline (e.g. a documentary/bold/cinematic style); note your choice.\n3. `convert_document` ONCE with: source `{ \"kind\": \"markdown\", \"markdown\": <deck.md content>, \"name\": \"deck.md\" }`, targets `[{ \"format\": \"pptx\", \"slideBreak\": \"h2\", \"title\": <deck title> }]`, your `themeId`, and `autoTemplates: true`. The result is an immutable session artifact with a URI.\n4. `preview_document` on that artifact URI (maxItems up to 20) and LOOK at the returned slide images: title slide present, one slide per h2, no slide overflowing with text, theme applied. If a slide is broken, fix deck.md or the conversion options and convert again.\n5. `list_roots` to find the writable artifacts root, then `save_artifact` with the artifact URI to path `deck.pptx` with `ifExists: \"error\"`. If it errors because deck.pptx already exists (a previous pass saved one), re-save with `ifExists: \"replace\"` and the `expectedSha256` of the previously saved file (it is in the earlier save/convert result).\n6. Call write_task_note with the theme used, the slide count, and the saved path.\n\nDo NOT hand-build XML or an HTML deck — the deliverable is the real .pptx produced by convert_document.",
88
+ "suggestedRole": "developer",
89
+ "advanceWhen": {
90
+ "file": "deck.pptx",
91
+ "minBytes": 1,
92
+ "artifact": true
93
+ },
94
+ "gate": {
95
+ "at": "completion",
96
+ "checks": [
97
+ {
98
+ "kind": "minBytes",
99
+ "file": "deck.pptx",
100
+ "bytes": 5000,
101
+ "artifact": true
102
+ }
103
+ ],
104
+ "onReject": "produce",
105
+ "maxAttempts": 3
106
+ },
107
+ "next": "evaluate"
108
+ },
109
+ {
110
+ "id": "evaluate",
111
+ "name": "Evaluate",
112
+ "description": "Preview the saved deck and grade it against every acceptance criterion. All pass -> finish; any fail -> loop back to the owning step.",
113
+ "prompt": "Open notes/outline.md and grade the saved deck against EACH acceptance criterion, writing PASS/FAIL per criterion. Use `preview_document` on the saved deck.pptx (source `{ \"kind\": \"file\", \"rootId\": <artifacts root from list_roots>, \"path\": \"deck.pptx\", \"format\": null }`) and inspect the slide images: (1) every outline slide appears, in order; (2) one idea per slide — no wall-of-text slide, no slide over ~6 bullets; (3) the title slide and closing/ask slide exist; (4) the theme is applied consistently and nothing renders broken or clipped; (5) the takeaway is stated up front. A deck that converts cleanly but buries the message FAILS.\n\nThen route — this is the whole point of the loop:\n\n- **Every criterion PASSES ->** call `advance_task_step({ ref, stepId: \"evaluate\", next: \"finish\" })`.\n- **Slide CONTENT fails (order, density, message) ->** write the specific gaps to notes, then `advance_task_step({ ref, stepId: \"evaluate\", next: \"write-deck\" })`.\n- **Only the CONVERSION fails (theme, rendering, missing save) ->** `advance_task_step({ ref, stepId: \"evaluate\", next: \"produce\" })`.\n\nNever route to `finish` while any criterion is unmet. After ~3 unproductive loops, stop and report DONE_WITH_CONCERNS so the user can step in.",
114
+ "suggestedRole": "reviewer",
115
+ "next": "write-deck"
116
+ },
117
+ {
118
+ "id": "finish",
119
+ "name": "Finish",
120
+ "description": "All acceptance criteria met. Stamp a short summary and report DONE.",
121
+ "prompt": "Every acceptance criterion passed. Write a one-paragraph DONE summary to task notes via `write_task_note`: the deck title, slide count, theme, and that the deliverable is deck.pptx in the project artifacts (openable in PowerPoint). Then report DONE.",
122
+ "suggestedRole": "developer",
123
+ "terminal": true
124
+ }
125
+ ],
126
+ "version": "1.1.0",
127
+ "releasedAt": "2026-07-28T00:00:00Z"
128
+ }
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "title": "Root-Cause Investigation smoke eval",
4
- "objective": "Self-contained smoke eval for the Root-Cause Investigation craftbook using the html-page generic harness.",
3
+ "title": "PowerPoint from Content smoke eval",
4
+ "objective": "Self-contained smoke eval for the PowerPoint from Content craftbook using the html-page generic harness.",
5
5
  "tags": [
6
6
  "html-page"
7
7
  ],
8
8
  "prompt": "Can you biuld us a little page for this? The content notes are in source/page-content.md. Save it as index.html — one file, nothing fancy needed on our end.",
9
9
  "setup": {
10
- "projectName": "Root-Cause Investigation Eval",
11
- "about": "Self-contained eval project for root-cause-investigation. Seeded inputs are under workspace/source or workspace/fixtures; final deliverable is workspace/index.html.",
12
- "missionObjectives": "Use the Root-Cause Investigation craftbook/template, read the seeded local fixtures, and write index.html without network calls, real credentials, or live services.",
10
+ "projectName": "PowerPoint from Content Eval",
11
+ "about": "Self-contained eval project for powerpoint-deck. Seeded inputs are under workspace/source or workspace/fixtures; final deliverable is workspace/index.html.",
12
+ "missionObjectives": "Use the PowerPoint from Content craftbook/template, read the seeded local fixtures, and write index.html without network calls, real credentials, or live services.",
13
13
  "files": [
14
14
  {
15
15
  "path": "source/brief.md",
16
- "content": "# Root-Cause Investigation Eval Brief\n\nClient: Boreal Desk, a home-office accessories company.\nAudience: operations leads who need an artifact they can use this week.\n\nFixed source facts for grounding:\n- The returns desk pilot covered 18 SKUs.\n- Median first response improved from 18 hours to 6 hours.\n- Preventable refund leakage fell from 14.2% to 8.9%.\n- The top unresolved complaint is status silence after photo submission.\n- Required next actions are automated status emails, barcode-exception training, and a weekly Finance exception export.\n\nUse these facts when the task asks for prose, analysis, copy, UI content, or test data. Do not use live web services, real credentials, or current outside data.\n\nCraftbook under test: root-cause-investigation - Root-Cause Investigation.\n"
16
+ "content": "# PowerPoint from Content Eval Brief\n\nClient: Boreal Desk, a home-office accessories company.\nAudience: operations leads who need an artifact they can use this week.\n\nFixed source facts for grounding:\n- The returns desk pilot covered 18 SKUs.\n- Median first response improved from 18 hours to 6 hours.\n- Preventable refund leakage fell from 14.2% to 8.9%.\n- The top unresolved complaint is status silence after photo submission.\n- Required next actions are automated status emails, barcode-exception training, and a weekly Finance exception export.\n\nUse these facts when the task asks for prose, analysis, copy, UI content, or test data. Do not use live web services, real credentials, or current outside data.\n\nCraftbook under test: powerpoint-deck - PowerPoint from Content.\n"
17
17
  },
18
18
  {
19
19
  "path": "source/page-content.md",
@@ -0,0 +1,118 @@
1
+ {
2
+ "id": "pull-request-review",
3
+ "name": "Pull Request Review",
4
+ "description": "Staff-engineer-style code review. Five steps:\n\n1. **Load PR context** — figure out which PR, fetch metadata + diff\n2. **Scan diff** — pattern-based first-pass + judgment-based second-pass\n3. **Raise findings** or **Approve** — branches on whether issues were found\n4. **Summary** — stamp a final verdict to task notes\n\nPorted from gstack's `/review`. The bash-driven preamble (gstack-config,\ngstack-learnings-search, gstack-telemetry-log) is replaced by:\n\n- The `pr-context.ts` script loads PR metadata via `github_pr_view`/`github_pr_diff`\n- `diff-scan.ts` applies the safety/quality patterns from gstack's\n `review/checklist.md` (SQL, secrets, `any`, console.log, etc.)\n- Memory (via `save_memory` / `search_memory`) replaces gstack's\n `learnings.jsonl` — search prior reviews for \"have we seen this\n pattern before?\"\n\nThe branches behavior: `diff-scan` stamps `clean: boolean` into the\nrun output; the **Scan diff** step routes to **Approve** when clean and\nto **Raise findings** otherwise.\n\nThis craftbook needs the github toolset configured (PAT) — without it,\nthe PR-fetch tools error and the craftbook surfaces an actionable\nmessage to the user.\n",
5
+ "entryStepId": "load-pr",
6
+ "triggers": [
7
+ "review this pr",
8
+ "code review",
9
+ "check my diff",
10
+ "review the changes"
11
+ ],
12
+ "requirements": [
13
+ {
14
+ "kind": "github"
15
+ },
16
+ {
17
+ "kind": "non-main-branch"
18
+ }
19
+ ],
20
+ "toolsets": [
21
+ {
22
+ "toolsetId": "github",
23
+ "optional": true,
24
+ "autoAllow": true,
25
+ "reason": "read PR metadata and post review comments without a prompt per call"
26
+ }
27
+ ],
28
+ "paramSchema": {
29
+ "type": "object",
30
+ "properties": {
31
+ "focus": {
32
+ "type": "string",
33
+ "title": "Review focus",
34
+ "description": "Optional area to emphasize, e.g. security, performance, tests."
35
+ },
36
+ "intensity": {
37
+ "type": "string",
38
+ "title": "Intensity",
39
+ "enum": [
40
+ "low",
41
+ "medium",
42
+ "high"
43
+ ],
44
+ "default": "medium",
45
+ "squisq": {
46
+ "control": "segmented"
47
+ },
48
+ "description": "How deep to go on the diff walk."
49
+ }
50
+ }
51
+ },
52
+ "steps": [
53
+ {
54
+ "id": "load-pr",
55
+ "name": "Load PR context",
56
+ "description": "Resolve which PR to review (current branch's open PR by default) and fetch its metadata + the unified diff. Writes pr.number, pr.title, pr.head into the task notes.",
57
+ "prompt": "**For this task you are a reviewer, not an author.** Your job here is to read changes and post feedback — NOT to write new code. If your default persona is a developer/builder, set the ship-the-scaffold instinct aside for this task. You won't `write_file` for this craftbook; you'll read PRs and post comments.\n\n**Your first action this turn:** call `github_pr_list` (no args). The result tells you which PR to review.\n\n1. If the user's chat message names a PR number, skip the list and use that.\n2. Otherwise, take the first PR from `github_pr_list`. If multiple match the current branch, call `ask_user_question` to disambiguate.\n3. Once you have the number, call `run_script({ name: \"pr-context\", input: { number } })` — it stamps the PR metadata + diff into the run output.\n4. `write_task_note({ ref, content })` with the PR title + URL so the next step has the anchor.\n\nDo NOT call `read_task_notes` to find the procedure — it's right here in this prompt. The notes only carry user input + prior step output; the **what to do** is in this step.",
58
+ "suggestedRole": "reviewer",
59
+ "onExit": {
60
+ "name": "pr-context",
61
+ "autoAdvanceWhen": {
62
+ "op": "ok"
63
+ }
64
+ },
65
+ "next": "scan-diff"
66
+ },
67
+ {
68
+ "id": "scan-diff",
69
+ "name": "Scan diff",
70
+ "description": "Walk the diff applying the safety/quality checklist. The diff-scan script returns a structured findings array.",
71
+ "prompt": "Run the `diff-scan` script. It fetches the PR diff via `github_pr_diff` and applies a pattern-based first-pass — SQL safety, unguarded `any`, console.log left in, secrets-in-source-shaped strings, missing test coverage for new files. Read its output carefully.\n\nThen do your own pass: use `github_pr_files` to walk individual files and apply judgment for things the pattern scan can't catch — naming, abstraction quality, error handling, edge cases. Take notes via `write_task_note`.\n\nAt the end of this step, you should know: did you find issues worth surfacing? Set `findings: <number>` in your output.",
72
+ "suggestedRole": "reviewer",
73
+ "gate": {
74
+ "at": "completion",
75
+ "scripts": [
76
+ {
77
+ "name": "diff-gate",
78
+ "scope": "craftbook"
79
+ }
80
+ ],
81
+ "onReject": "scan-diff",
82
+ "maxAttempts": 3
83
+ },
84
+ "next": "raise-findings"
85
+ },
86
+ {
87
+ "id": "raise-findings",
88
+ "name": "Raise findings",
89
+ "description": "Surface concrete issues with file:line citations. Use `github_pr_comment` to post the structured summary to the PR.",
90
+ "prompt": "For each finding, write a short paragraph:\n\n- **What** (one sentence describing the issue)\n- **Where** (file:line — the model citation hyperlink form, file_path:line)\n- **Why it matters** (one sentence)\n- **Suggested fix** (one line or a small diff)\n\nGroup findings by severity: must-fix (correctness, security, data-loss risk), should-fix (clarity, edge cases, missing tests), nit (style, naming).\n\nPost the summary via `github_pr_comment` with the PR number from step 1. Then advance to summary.",
91
+ "suggestedRole": "reviewer",
92
+ "next": "summary"
93
+ },
94
+ {
95
+ "id": "approve",
96
+ "name": "Approve",
97
+ "description": "Clean PR — write a brief approval note. Don't gold-plate.",
98
+ "prompt": "The diff scan came up clean. Write a one-paragraph approval note: what the PR does, why it looks good, anything worth flagging as 'consider for a follow-up' (but not blocking). Post via `github_pr_comment`. Advance to summary.",
99
+ "suggestedRole": "reviewer",
100
+ "next": "summary"
101
+ },
102
+ {
103
+ "id": "summary",
104
+ "name": "Summary",
105
+ "description": "Stamp a final summary into the task notes for the user.",
106
+ "prompt": "Write a one-paragraph summary of the review to the task notes via `write_task_note`. Include:\n\n- The PR (#number — title)\n- The verdict (approved | needs changes)\n- The number of findings broken down by severity\n- A link to the posted PR comment\n\nThen report DONE.",
107
+ "suggestedRole": "reviewer",
108
+ "terminal": true
109
+ }
110
+ ],
111
+ "scripts": {
112
+ "pr-context": "import { defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'pr-context',\n description:\n 'Resolve which PR to review and fetch its metadata + unified diff. Stamps {number, title, headRef, baseRef, diffChars} so the next step has the anchor.',\n inputs: {\n number: {\n type: 'number',\n description: 'PR number to review. Omit to pick the open PR matching the current branch.',\n integer: true,\n },\n },\n outputs: {\n number: { type: 'number', description: 'Resolved PR number.' },\n title: { type: 'string', description: 'PR title.' },\n headRef: { type: 'string', description: 'PR head branch.' },\n baseRef: { type: 'string', description: 'PR base branch.' },\n url: { type: 'string', description: 'PR URL.' },\n diffChars: { type: 'number', description: 'Length of the fetched diff body in chars.' },\n },\n requires: ['network'],\n});\n\ninterface PrSummary {\n number: number;\n title: string;\n author: string;\n headRef: string;\n baseRef: string;\n draft: boolean;\n updatedAt: string;\n url: string;\n}\n\nasync function resolvePr(input: { number?: number }): Promise<PrSummary> {\n if (input.number) {\n const view = await gezel.mcp.call('github_pr_view', { number: input.number });\n if (typeof view !== 'string') throw new Error('github_pr_view returned an unexpected shape');\n const headLine = view.split('\\n')[0] ?? '';\n const titleMatch = /^#(\\d+) — (.+)$/.exec(headLine);\n if (!titleMatch) throw new Error(`could not parse github_pr_view output: ${headLine}`);\n return {\n number: Number(titleMatch[1]),\n title: titleMatch[2]!,\n author: '?',\n headRef: '',\n baseRef: '',\n draft: false,\n updatedAt: '',\n url: '',\n };\n }\n const listing = await gezel.mcp.call('github_pr_list', {});\n if (typeof listing !== 'string') throw new Error('github_pr_list returned an unexpected shape');\n const firstLine = listing.split('\\n\\n')[0] ?? '';\n const m = /^#(\\d+) — (.+?) \\((.+?), (.+?) → (.+?)(?:, draft)?\\)/.exec(firstLine);\n if (!m) {\n throw new Error(\n `couldn't pick a PR — github_pr_list returned no open PRs or unexpected shape:\\n${listing.slice(0, 200)}`,\n );\n }\n return {\n number: Number(m[1]),\n title: m[2]!,\n author: m[3]!,\n headRef: m[4]!,\n baseRef: m[5]!,\n draft: firstLine.includes('draft'),\n updatedAt: '',\n url: '',\n };\n}\n\nasync function main(): Promise<void> {\n const input = gezel.input as { number?: number };\n const pr = await resolvePr(input);\n gezel.log(`Resolved PR #${pr.number} — \"${pr.title}\"`);\n const diff = await gezel.mcp.call('github_pr_diff', { number: pr.number });\n const diffStr = typeof diff === 'string' ? diff : '';\n gezel.output({\n number: pr.number,\n title: pr.title,\n headRef: pr.headRef,\n baseRef: pr.baseRef,\n url: pr.url,\n diffChars: diffStr.length,\n });\n}\n\nawait main();\n",
113
+ "diff-scan": "import { defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'diff-scan',\n description:\n 'Pattern-scan a PR diff for common safety/quality issues — SQL concat, unguarded any, console.log leftovers, secret-shaped strings, missing tests. Stamps {findings[], clean: boolean}.',\n inputs: {\n number: { type: 'number', description: 'PR number.', integer: true, required: true },\n },\n outputs: {\n findings: {\n type: 'array',\n description: 'Issues found, each with file, line, severity, kind, snippet.',\n itemType: 'object',\n },\n clean: { type: 'boolean', description: 'True iff no must-fix or should-fix findings.' },\n },\n requires: ['network'],\n});\n\ntype Severity = 'must-fix' | 'should-fix' | 'nit';\n\ninterface Finding {\n file: string;\n line: number;\n severity: Severity;\n kind: string;\n snippet: string;\n}\n\nconst RULES: Array<{\n kind: string;\n severity: Severity;\n regex: RegExp;\n description: string;\n}> = [\n {\n kind: 'sql-concat',\n severity: 'must-fix',\n regex: /(query|sql|execute)\\s*\\(\\s*[\"'`][^\"'`]*\\$\\{|\\b\\+\\s*req\\.(query|body|params)\\./i,\n description: 'String-concatenated SQL or user input flowing into a query.',\n },\n {\n kind: 'console-leftover',\n severity: 'nit',\n regex: /^\\+.*\\bconsole\\.(log|warn|error|debug)\\b/,\n description: 'console.* call left in production code.',\n },\n {\n kind: 'secret-shape',\n severity: 'must-fix',\n regex: /(api[_-]?key|secret|token|password)\\s*[:=]\\s*[\"'][A-Za-z0-9+/=_-]{16,}[\"']/i,\n description: 'String-literal that looks like a hard-coded credential.',\n },\n {\n kind: 'unguarded-any',\n severity: 'should-fix',\n regex: /:\\s*any\\b(?!\\[)|<any>/,\n description: \"Explicit `any` — confirm it's the minimum-friction choice, not a bypass.\",\n },\n {\n kind: 'todo-marker',\n severity: 'nit',\n regex: /^\\+.*\\b(TODO|FIXME|XXX)\\b/,\n description: 'TODO/FIXME left behind. Either resolve or open an issue.',\n },\n {\n kind: 'force-unwrap',\n severity: 'should-fix',\n regex: /^\\+.*![.\\s]/,\n description: 'Non-null assertion (`!`) — confirm the invariant or add a guard.',\n },\n];\n\ninterface ParsedHunk {\n file: string;\n baseLine: number;\n text: string;\n}\n\nfunction* iterHunks(diff: string): Generator<ParsedHunk> {\n let currentFile = '';\n let inHunk = false;\n let baseLine = 0;\n const lines = diff.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i]!;\n const fileMatch = /^\\+\\+\\+ b\\/(.+)$/.exec(ln);\n if (fileMatch) {\n currentFile = fileMatch[1]!;\n inHunk = false;\n continue;\n }\n const hunkMatch = /^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/.exec(ln);\n if (hunkMatch) {\n baseLine = Number(hunkMatch[1]);\n inHunk = true;\n continue;\n }\n if (!inHunk || !currentFile) continue;\n if (ln.startsWith('+') && !ln.startsWith('+++')) {\n yield { file: currentFile, baseLine, text: ln };\n baseLine++;\n } else if (!ln.startsWith('-')) {\n baseLine++;\n }\n }\n}\n\nasync function main(): Promise<void> {\n const input = gezel.input as { number: number };\n const diff = await gezel.mcp.call('github_pr_diff', { number: input.number });\n if (typeof diff !== 'string') {\n throw new Error('github_pr_diff returned an unexpected shape');\n }\n const findings: Finding[] = [];\n for (const hunk of iterHunks(diff)) {\n for (const rule of RULES) {\n if (rule.regex.test(hunk.text)) {\n findings.push({\n file: hunk.file,\n line: hunk.baseLine,\n severity: rule.severity,\n kind: rule.kind,\n snippet: hunk.text.slice(1, 140),\n });\n }\n }\n }\n const blocking = findings.filter((f) => f.severity !== 'nit');\n gezel.log(`Scanned diff: ${findings.length} finding(s), ${blocking.length} blocking`);\n gezel.output({ findings, clean: blocking.length === 0 });\n}\n\nawait main();\n",
114
+ "diff-gate": "import { defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'diff-gate',\n description:\n 'Gate for the scan-diff phase: runs diff-scan and routes the review — a clean diff approves straight to the approve step; findings approve to raise-findings with the findings carried in the handoff.',\n kind: 'gate',\n outputs: {\n decision: { type: 'string', description: \"'approve' (the scan itself succeeding is the bar).\" },\n message: { type: 'string', description: 'One-line scan summary.' },\n },\n // Nested script execution inherits diff-scan's needs; the scan itself\n // declares 'network'.\n requires: ['network'],\n});\n\ninterface ScanOutput {\n clean: boolean;\n findings?: Array<{ severity?: string; note?: string; file?: string }>;\n}\n\n// Gate-as-router: the step \"completing\" means the scan ran — the JUDGMENT\n// here is which branch the review takes next, expressed as approve+goto.\n// A scan failure rejects (fail-closed) so a broken scan never silently\n// approves a PR.\nconst result = await gezel.script.run<ScanOutput>('diff-scan');\nif (result.status !== 'ok' || !result.output) {\n gezel.output({\n decision: 'reject',\n message: `diff-scan failed (${result.error ?? 'no output'}) — fix the scan inputs (is the PR context loaded?) and advance again.`,\n });\n} else if (result.output.clean) {\n gezel.output({\n decision: 'approve',\n goto: 'approve',\n message: 'diff-scan found no must-fix or should-fix issues',\n handoff: { message: 'Automated diff scan came back clean — proceed to approval.' },\n });\n} else {\n const findings = result.output.findings ?? [];\n const top = findings\n .slice(0, 5)\n .map((f) => `- [${f.severity ?? 'note'}] ${f.file ?? ''} ${f.note ?? ''}`.trim())\n .join('\\n');\n gezel.output({\n decision: 'approve',\n goto: 'raise-findings',\n message: `diff-scan flagged ${findings.length} finding(s)`,\n handoff: {\n message: `Automated diff scan flagged ${findings.length} finding(s) to raise on the PR:\\n${top}`,\n params: { findingCount: findings.length },\n },\n });\n}\n"
115
+ },
116
+ "version": "1.1.0",
117
+ "releasedAt": "2026-07-28T00:00:00Z"
118
+ }