wizz-method 1.14.0 → 1.16.0

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 (148) hide show
  1. package/package.json +3 -2
  2. package/skills-registry.yaml +4 -0
  3. package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/__pycache__/lint_spine.cpython-313.pyc +0 -0
  4. package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/tests/__pycache__/test_lint_spine.cpython-313-pytest-9.1.1.pyc +0 -0
  5. package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +3 -5
  6. package/src/bmm-skills/4-implementation/wizz-code-review/customize.toml +71 -0
  7. package/src/bmm-skills/4-implementation/wizz-code-review/references/claims-check.md +14 -0
  8. package/src/bmm-skills/4-implementation/wizz-code-review/references/deletion-check.md +14 -0
  9. package/src/bmm-skills/4-implementation/wizz-code-review/review-prompts/edge-case-hunter.md +110 -0
  10. package/src/bmm-skills/4-implementation/wizz-code-review/review-prompts/verification-gap.md +113 -0
  11. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-01-gather-context.md +32 -23
  12. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-02-review.md +10 -13
  13. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-03-triage.md +16 -22
  14. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-04-present.md +4 -2
  15. package/src/bmm-skills/4-implementation/wizz-quick-dev/customize.toml +76 -0
  16. package/src/bmm-skills/4-implementation/wizz-quick-dev/references/claims-check.md +14 -0
  17. package/src/bmm-skills/4-implementation/wizz-quick-dev/references/deletion-check.md +14 -0
  18. package/src/bmm-skills/4-implementation/wizz-quick-dev/review-prompts/edge-case-hunter.md +110 -0
  19. package/src/bmm-skills/4-implementation/wizz-quick-dev/review-prompts/verification-gap.md +113 -0
  20. package/src/bmm-skills/4-implementation/wizz-quick-dev/step-04-review.md +38 -16
  21. package/src/bmm-skills/4-implementation/wizz-retrospective/SKILL.md +56 -1488
  22. package/src/bmm-skills/4-implementation/wizz-retrospective/customize.toml +3 -5
  23. package/src/bmm-skills/4-implementation/wizz-retrospective/references/acceptance-verdict.md +55 -0
  24. package/src/bmm-skills/4-implementation/wizz-retrospective/references/aggregate-views.md +17 -0
  25. package/src/bmm-skills/4-implementation/wizz-retrospective/references/evidence-gathering.md +30 -0
  26. package/src/bmm-skills/4-implementation/wizz-retrospective/references/retro-document.md +84 -0
  27. package/src/bmm-skills/4-implementation/wizz-retrospective/references/team-discussion.md +22 -0
  28. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/__pycache__/sprint_status.cpython-313.pyc +0 -0
  29. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/git_evidence.py +304 -0
  30. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/sprint_status.py +746 -0
  31. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
  32. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-313-pytest-9.1.1.pyc +0 -0
  33. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/fixtures/sprint-status-template.yaml +71 -0
  34. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/test_git_evidence.py +750 -0
  35. package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/test_sprint_status.py +1579 -0
  36. package/src/bmm-skills/4-implementation/wizz-sprint-planning/SKILL.md +39 -296
  37. package/src/bmm-skills/4-implementation/wizz-sprint-planning/customize.toml +1 -3
  38. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/fix-sprint-status.md +30 -0
  39. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/generate-tracking.md +25 -0
  40. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/readiness-gate.md +20 -0
  41. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/status-view.md +14 -0
  42. package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/validate.md +10 -0
  43. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/__pycache__/sprint_plan.cpython-313.pyc +0 -0
  44. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/sprint_plan.py +697 -0
  45. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-313-pytest-9.1.1.pyc +0 -0
  46. package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/tests/test_sprint_plan.py +524 -0
  47. package/src/bmm-skills/4-implementation/wizz-sprint-planning/sprint-status-template.yaml +9 -7
  48. package/src/bmm-skills/module-help.csv +3 -3
  49. package/src/core-skills/_shared/handoff-protocol.md +7 -8
  50. package/src/core-skills/module-help.csv +1 -0
  51. package/src/core-skills/wizz-advanced-elicitation/SKILL.md +36 -114
  52. package/src/core-skills/wizz-advanced-elicitation/{methods.csv → assets/methods.csv} +29 -27
  53. package/src/core-skills/wizz-advanced-elicitation/customize.toml +54 -0
  54. package/src/core-skills/wizz-advanced-elicitation/scripts/__pycache__/pick_methods.cpython-313.pyc +0 -0
  55. package/src/core-skills/wizz-advanced-elicitation/scripts/pick_methods.py +233 -0
  56. package/src/core-skills/wizz-advanced-elicitation/scripts/tests/__pycache__/test_pick_methods.cpython-313-pytest-9.1.1.pyc +0 -0
  57. package/src/core-skills/wizz-advanced-elicitation/scripts/tests/test_pick_methods.py +228 -0
  58. package/src/core-skills/wizz-brainstorming/SKILL.md +2 -2
  59. package/src/core-skills/wizz-brainstorming/assets/brain-selector.html +2 -0
  60. package/src/core-skills/wizz-brainstorming/references/converge.md +1 -1
  61. package/src/core-skills/wizz-brainstorming/references/finalize.md +1 -1
  62. package/src/core-skills/wizz-brainstorming/references/headless.md +1 -1
  63. package/src/core-skills/wizz-brainstorming/references/mode-autonomous.md +1 -1
  64. package/src/core-skills/wizz-brainstorming/scripts/__pycache__/brain.cpython-313.pyc +0 -0
  65. package/src/core-skills/wizz-brainstorming/scripts/brain.py +36 -6
  66. package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-313-pytest-9.1.1.pyc +0 -0
  67. package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-314.pyc +0 -0
  68. package/src/core-skills/wizz-brainstorming/scripts/tests/test_brain.py +24 -2
  69. package/src/core-skills/wizz-customize/scripts/__pycache__/list_customizable_skills.cpython-313.pyc +0 -0
  70. package/src/core-skills/wizz-customize/scripts/tests/__pycache__/test_list_customizable_skills.cpython-313-pytest-9.1.1.pyc +0 -0
  71. package/src/core-skills/wizz-forge-idea/SKILL.md +107 -0
  72. package/src/core-skills/wizz-forge-idea/customize.toml +41 -0
  73. package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-313.pyc +0 -0
  74. package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-314.pyc +0 -0
  75. package/src/core-skills/wizz-forge-idea/scripts/resolve_personas.py +275 -0
  76. package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-313-pytest-9.1.1.pyc +0 -0
  77. package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-314.pyc +0 -0
  78. package/src/core-skills/wizz-forge-idea/scripts/tests/test_resolve_personas.py +138 -0
  79. package/src/core-skills/wizz-party-mode/SKILL.md +37 -54
  80. package/src/core-skills/wizz-party-mode/customize.toml +61 -2
  81. package/src/core-skills/wizz-party-mode/references/create-party.md +8 -3
  82. package/src/core-skills/wizz-party-mode/references/mode-agent-team.md +3 -1
  83. package/src/core-skills/wizz-party-mode/references/mode-subagent.md +16 -4
  84. package/src/core-skills/wizz-party-mode/references/party-memory.md +51 -0
  85. package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-313.pyc +0 -0
  86. package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-314.pyc +0 -0
  87. package/src/core-skills/wizz-party-mode/scripts/resolve_party.py +22 -7
  88. package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-313-pytest-9.1.1.pyc +0 -0
  89. package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-314.pyc +0 -0
  90. package/src/core-skills/wizz-party-mode/scripts/tests/{test-resolve_party.py → test_resolve_party.py} +8 -0
  91. package/src/core-skills/wizz-review-edge-case-hunter/SKILL.md +17 -3
  92. package/src/modules/wizz/README.md +1 -1
  93. package/src/modules/wizz/_shared/model-ladder.md +22 -0
  94. package/src/modules/wizz/_shared/token-economy.md +6 -2
  95. package/src/modules/wizz/agents/wizz-ads/customize.toml +1 -1
  96. package/src/modules/wizz/agents/wizz-copy/customize.toml +1 -1
  97. package/src/modules/wizz/agents/wizz-designer/customize.toml +1 -1
  98. package/src/modules/wizz/agents/wizz-growth/customize.toml +11 -1
  99. package/src/modules/wizz/agents/wizz-maestro/customize.toml +1 -1
  100. package/src/modules/wizz/agents/wizz-memoria/customize.toml +1 -1
  101. package/src/modules/wizz/agents/wizz-qa/customize.toml +1 -1
  102. package/src/modules/wizz/agents/wizz-seo/customize.toml +1 -1
  103. package/src/modules/wizz/agents/wizz-social/customize.toml +1 -1
  104. package/src/modules/wizz/overrides/wizz-agent-analyst.toml +1 -1
  105. package/src/modules/wizz/overrides/wizz-agent-architect.toml +1 -1
  106. package/src/modules/wizz/overrides/wizz-agent-dev.toml +1 -1
  107. package/src/modules/wizz/overrides/wizz-agent-pm.toml +1 -1
  108. package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +1 -1
  109. package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +1 -1
  110. package/src/modules/wizz/subagents/codex/wizz-exec-haiku.toml +16 -0
  111. package/src/modules/wizz/subagents/codex/wizz-exec-opus.toml +18 -0
  112. package/src/modules/wizz/subagents/codex/wizz-exec-review.toml +17 -0
  113. package/src/modules/wizz/subagents/codex/wizz-exec-sonnet.toml +17 -0
  114. package/src/modules/wizz/subagents/gemini/wizz-exec-haiku.md +15 -0
  115. package/src/modules/wizz/subagents/gemini/wizz-exec-opus.md +17 -0
  116. package/src/modules/wizz/subagents/gemini/wizz-exec-review.md +15 -0
  117. package/src/modules/wizz/subagents/gemini/wizz-exec-sonnet.md +16 -0
  118. package/src/modules/wizz/subagents/opencode/wizz-exec-haiku.md +14 -0
  119. package/src/modules/wizz/subagents/opencode/wizz-exec-opus.md +16 -0
  120. package/src/modules/wizz/subagents/opencode/wizz-exec-review.md +16 -0
  121. package/src/modules/wizz/subagents/opencode/wizz-exec-sonnet.md +15 -0
  122. package/src/modules/wizz/subagents/wizz-exec-opus.md +16 -0
  123. package/src/modules/wizz/subagents/wizz-exec-review.md +15 -0
  124. package/src/scripts/__pycache__/memlog.cpython-313.pyc +0 -0
  125. package/src/scripts/tests/__pycache__/test_memlog.cpython-313-pytest-9.1.1.pyc +0 -0
  126. package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-313-pytest-9.1.1.pyc +0 -0
  127. package/src/skills-lib/pesquisa-de-publico-do-piva/README.md +83 -0
  128. package/src/skills-lib/pesquisa-de-publico-do-piva/SKILL.md +65 -0
  129. package/src/skills-lib/pesquisa-de-publico-do-piva/assets/template.html +414 -0
  130. package/src/skills-lib/pesquisa-de-publico-do-piva/prompts.md +131 -0
  131. package/src/skills-lib/wizz-offer-forge/README.md +13 -0
  132. package/src/skills-lib/wizz-offer-forge/SKILL.md +74 -0
  133. package/src/skills-lib/wizz-offer-forge/references/empilhamento-valor.md +25 -0
  134. package/src/skills-lib/wizz-offer-forge/references/garantia-reversa.md +22 -0
  135. package/src/skills-lib/wizz-offer-forge/references/mecanismo-unico.md +28 -0
  136. package/src/skills-lib/wizz-offer-forge/references/ponte-trafego.md +21 -0
  137. package/src/skills-lib/wizz-router/references/routing-table-flat.md +2 -0
  138. package/tools/installer/core/installer.js +57 -0
  139. package/tools/installer/ide/_config-driven.js +44 -30
  140. package/tools/installer/ide/platform-codes.yaml +42 -3
  141. package/tools/installer/modules/external-manager.js +140 -2
  142. package/tools/installer/modules/official-modules.js +68 -21
  143. package/tools/installer/prompts.js +45 -103
  144. package/tools/installer/ui.js +64 -7
  145. package/wizz-modules.yaml +65 -11
  146. package/src/bmm-skills/4-implementation/wizz-sprint-planning/checklist.md +0 -34
  147. package/src/core-skills/wizz-brainstorming/scripts/memlog.py +0 -202
  148. package/src/core-skills/wizz-brainstorming/scripts/tests/test_memlog.py +0 -265
@@ -0,0 +1,228 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["pytest>=8.0"]
4
+ # ///
5
+ """Tests for pick_methods.py.
6
+
7
+ Run: python3 scripts/tests/test_pick_methods.py
8
+ or: python3 --with pytest -m pytest scripts/tests/test_pick_methods.py
9
+ """
10
+ import json
11
+ import random
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ import pytest
16
+
17
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
18
+ import pick_methods # noqa: E402
19
+
20
+ CSV = """num,category,method_name,description,output_pattern
21
+ 1,risk,Pre-mortem Analysis,Imagine future failure then work backwards,failure → causes → prevention
22
+ 2,risk,Assumption Audit,List and stress-test every assumption,list → rate → stress-test
23
+ 3,core,First Principles Analysis,Rebuild from fundamental truths,assumptions → truths → new approach
24
+ 4,core,Socratic Questioning,Targeted questions reveal hidden assumptions,questions → revelations → understanding
25
+ 5,creative,SCAMPER Method,Seven creativity lenses,S→C→A→M→P→E→R
26
+ """
27
+
28
+ EXTRA = [
29
+ {
30
+ "code": "reg-inversion",
31
+ "category": "domain",
32
+ "method_name": "Regulatory Inversion",
33
+ "description": "Start from the compliance constraint",
34
+ "output_pattern": "constraint → possibility",
35
+ },
36
+ {
37
+ "code": "premortem-lite",
38
+ "category": "risk",
39
+ "method_name": "Pre-mortem Analysis",
40
+ "description": "RETUNED pre-mortem",
41
+ "output_pattern": "failure → prevention",
42
+ },
43
+ ]
44
+
45
+
46
+ @pytest.fixture
47
+ def lib(tmp_path):
48
+ csv_path = tmp_path / "methods.csv"
49
+ csv_path.write_text(CSV, encoding="utf-8")
50
+ return csv_path
51
+
52
+
53
+ def rows(lib):
54
+ return pick_methods.load(lib)
55
+
56
+
57
+ # --- load / merge -----------------------------------------------------------
58
+
59
+ def test_load_all_fields_present(lib):
60
+ r = rows(lib)
61
+ assert len(r) == 5
62
+ assert r[0]["method_name"] == "Pre-mortem Analysis"
63
+ assert all(set(pick_methods.FIELDS) <= set(row) for row in r)
64
+
65
+
66
+ def test_load_extra_json_literal_and_file(tmp_path, lib):
67
+ literal = pick_methods.load_extra(json.dumps(EXTRA))
68
+ f = tmp_path / "extra.json"
69
+ f.write_text(json.dumps(EXTRA), encoding="utf-8")
70
+ from_file = pick_methods.load_extra(str(f))
71
+ assert literal == from_file
72
+ assert literal[0]["method_name"] == "Regulatory Inversion"
73
+ assert literal[0]["num"] == "" # missing fields normalize to empty
74
+ assert literal[0]["code"] == "reg-inversion" # code survives loading
75
+
76
+
77
+ def test_merge_extra_replaces_by_name_and_appends(lib):
78
+ merged = pick_methods.merge_extra(rows(lib), pick_methods.load_extra(json.dumps(EXTRA)))
79
+ assert len(merged) == 6 # 5 shipped, 1 replaced in place, 1 appended
80
+ premortem = next(r for r in merged if r["method_name"] == "Pre-mortem Analysis")
81
+ assert premortem["description"] == "RETUNED pre-mortem"
82
+ assert premortem["num"] == "1" # replacement inherits the shipped num
83
+ appended = next(r for r in merged if r["method_name"] == "Regulatory Inversion")
84
+ assert appended["num"] == "6" # appended extras get the next free num
85
+ assert dict(pick_methods.categories(merged))["domain"] == 1 # new category is first-class
86
+
87
+
88
+ def test_extras_are_addressable_by_num(lib):
89
+ merged = pick_methods.merge_extra(rows(lib), pick_methods.load_extra(json.dumps(EXTRA)))
90
+ found, missing = pick_methods.find(merged, ["6", "1"])
91
+ assert [r["method_name"] for r in found] == ["Regulatory Inversion", "Pre-mortem Analysis"]
92
+ assert missing == []
93
+
94
+
95
+ # --- categories / filter / find / exclude -----------------------------------
96
+
97
+ def test_categories_counts_sorted(lib):
98
+ assert pick_methods.categories(rows(lib)) == [("core", 2), ("creative", 1), ("risk", 2)]
99
+
100
+
101
+ def test_filter_is_case_insensitive(lib):
102
+ got = pick_methods.filter_cats(rows(lib), ["RISK"])
103
+ assert {r["method_name"] for r in got} == {"Pre-mortem Analysis", "Assumption Audit"}
104
+
105
+
106
+ def test_filter_none_returns_all(lib):
107
+ assert len(pick_methods.filter_cats(rows(lib), None)) == 5
108
+
109
+
110
+ def test_find_by_name_num_and_missing(lib):
111
+ found, missing = pick_methods.find(rows(lib), ["scamper method", "3", "Nope"])
112
+ assert [r["method_name"] for r in found] == ["SCAMPER Method", "First Principles Analysis"]
113
+ assert missing == ["Nope"]
114
+
115
+
116
+ def test_exclude_skips_named(lib):
117
+ got = pick_methods.exclude(rows(lib), ["pre-mortem analysis", "SCAMPER Method"])
118
+ assert {r["method_name"] for r in got} == {
119
+ "Assumption Audit", "First Principles Analysis", "Socratic Questioning",
120
+ }
121
+
122
+
123
+ # --- spread sampling ---------------------------------------------------------
124
+
125
+ def test_spread_hits_distinct_categories(lib):
126
+ for seed in range(20):
127
+ picks = pick_methods.spread_sample(rows(lib), 3, random.Random(seed))
128
+ assert len({r["category"] for r in picks}) == 3
129
+
130
+
131
+ def test_spread_wraps_when_categories_run_out(lib):
132
+ picks = pick_methods.spread_sample(rows(lib), 5, random.Random(0))
133
+ assert len(picks) == 5
134
+ assert len({r["method_name"] for r in picks}) == 5 # no duplicates
135
+
136
+
137
+ def test_spread_clamps_to_pool(lib):
138
+ assert len(pick_methods.spread_sample(rows(lib), 99, random.Random(0))) == 5
139
+
140
+
141
+ # --- CLI ---------------------------------------------------------------------
142
+
143
+ def run(args, lib, capsys):
144
+ code = pick_methods.main(["--file", str(lib), *args])
145
+ captured = capsys.readouterr()
146
+ return code, captured.out, captured.err
147
+
148
+
149
+ def test_cli_categories(lib, capsys):
150
+ code, out, _ = run(["categories"], lib, capsys)
151
+ assert code == 0
152
+ assert "risk\t2" in out
153
+
154
+
155
+ def test_cli_list_requires_scope(lib, capsys):
156
+ code, _, err = run(["list"], lib, capsys)
157
+ assert code == 2
158
+ assert "--category" in err
159
+
160
+
161
+ def test_cli_list_category_and_all(lib, capsys):
162
+ code, out, _ = run(["list", "--category", "core"], lib, capsys)
163
+ assert code == 0 and len(out.strip().splitlines()) == 2
164
+ assert "Socratic Questioning" in out and "SCAMPER" not in out
165
+ code, out, _ = run(["list", "--all"], lib, capsys)
166
+ assert code == 0 and "SCAMPER" in out
167
+
168
+
169
+ def test_cli_show_found_and_missing(lib, capsys):
170
+ code, out, err = run(["show", "Assumption Audit", "Ghost"], lib, capsys)
171
+ assert code == 0
172
+ assert "stress-test" in out
173
+ assert "not found: Ghost" in err
174
+ code, _, _ = run(["show", "Ghost"], lib, capsys)
175
+ assert code == 1
176
+
177
+
178
+ def test_cli_random_spread_exclude(lib, capsys):
179
+ code, out, _ = run(
180
+ ["random", "-n", "3", "--spread", "--exclude", "SCAMPER Method"], lib, capsys
181
+ )
182
+ assert code == 0
183
+ lines = [ln for ln in out.strip().splitlines() if ln]
184
+ assert len(lines) == 3
185
+ assert "SCAMPER" not in out
186
+
187
+
188
+ def test_cli_random_clamps_and_empty_pool(lib, capsys):
189
+ code, out, _ = run(["random", "-n", "99"], lib, capsys)
190
+ assert code == 0 and len(out.strip().splitlines()) == 5
191
+ code, _, err = run(["random", "--category", "nope"], lib, capsys)
192
+ assert code == 1 and "no methods match" in err
193
+
194
+
195
+ def test_cli_extra_inline_json(lib, capsys):
196
+ code, out, _ = run(
197
+ ["--extra", json.dumps(EXTRA), "list", "--category", "domain"], lib, capsys
198
+ )
199
+ assert code == 0 and "Regulatory Inversion" in out
200
+
201
+
202
+ def test_cli_bad_extra_and_missing_file(tmp_path, lib, capsys):
203
+ code, _, err = run(["--extra", str(tmp_path / "gone.json"), "categories"], lib, capsys)
204
+ assert code == 2 and "--extra" in err
205
+ code = pick_methods.main(["--file", str(tmp_path / "gone.csv"), "categories"])
206
+ assert code == 2
207
+
208
+
209
+ def test_cli_json_output(lib, capsys):
210
+ code, out, _ = run(["--json", "show", "1"], lib, capsys)
211
+ assert code == 0
212
+ data = json.loads(out)
213
+ assert data[0]["method_name"] == "Pre-mortem Analysis"
214
+
215
+
216
+ # --- shipped catalog integration ----------------------------------------------
217
+
218
+ def test_shipped_catalog_loads_clean():
219
+ shipped = pick_methods.DEFAULT_FILE
220
+ assert shipped.is_file(), f"shipped catalog missing: {shipped}"
221
+ r = pick_methods.load(shipped)
222
+ assert len(r) >= 60
223
+ for row in r:
224
+ assert row["category"] and row["method_name"] and row["description"], row
225
+
226
+
227
+ if __name__ == "__main__":
228
+ sys.exit(pytest.main([__file__, "-q"]))
@@ -34,13 +34,13 @@ These fight your defaults, in every mode; hold them deliberately. The stance you
34
34
  - **Keep shifting the creative domain** — every 5–10 turns (or ~10 ideas when you're generating), usually by moving to the next technique.
35
35
  - **One prompt per message while in dialogue (Facilitator, Creative Partner); no multiple-choice menus.** Don't stack questions into a wall or hand a menu that invites lazy picking — both pull the user out of generating. The only exceptions are the two up-front *process* choices (stance, and the technique flow): *how* to run is theirs to pick; *what* to ideate never is.
36
36
 
37
- **The memlog** is the session's memory: the single source every output builds from, and the file a resume reloads. Whatever isn't in it is gone. Log every idea, decision, question, and bit of user direction — anything you'd regret losing if the window closed — one line each, the gist in the user's meaning, in time order; never edit or reorder. Skip your prompts and small talk. All writes go through `scripts/memlog.py` (atomic; don't read it back mid-session — resume is the one exception):
37
+ **The memlog** is the session's memory: the single source every output builds from, and the file a resume reloads. Whatever isn't in it is gone. Log every idea, decision, question, and bit of user direction — anything you'd regret losing if the window closed — one line each, the gist in the user's meaning, in time order; never edit or reorder. Skip your prompts and small talk. All writes go through the shared `{project-root}/_wizz/scripts/memlog.py` (atomic; don't read it back mid-session — resume is the one exception):
38
38
 
39
39
  - `memlog.py init --workspace {doc_workspace} --field topic="<topic>" --field goal="<goal>" --field mode="<facilitator|partner|autonomous>"` — create it once topic, goal, and stance are known.
40
40
  - `memlog.py append --workspace {doc_workspace} --type <kind> --text "<one-line gist>"` — log one entry. `--type` ∈ `idea`/`insight`/`question`/`decision`/`direction`/`technique` (a switch: `--text "started <name>"`); omit for a plain note. Add `--by user`/`--by coach` to mark authorship — **required in Creative Partner mode** (renders `(idea by user)`); skip it otherwise.
41
41
  - `memlog.py set --workspace {doc_workspace} --key status --value complete` — flip status at wrap-up.
42
42
 
43
- (Each is `python3 {skill-root}/scripts/memlog.py …`.)
43
+ (Each is `python3 {project-root}/_wizz/scripts/memlog.py …`.)
44
44
 
45
45
  ## Run a Session
46
46
 
@@ -222,6 +222,8 @@
222
222
  function checkedInvent(){ return inventBoxes.filter(function(b){ return b.checked; }); }
223
223
 
224
224
  function update(){
225
+ // rand can't exceed what the pool can supply — keep the counter honest with the draw
226
+ if (state.rand > randomPool().length){ state.rand = randomPool().length; }
225
227
  $('pickN').textContent = checkedTech().length;
226
228
  $('randN').textContent = state.rand;
227
229
  $('invN').textContent = state.inv;
@@ -17,7 +17,7 @@ Pick by what the decision needs:
17
17
  - **PMI (Plus / Minus / Interesting)** — when one strong candidate needs pressure-testing before commitment: list its pluses, minuses, and the merely-interesting, then judge.
18
18
  - **MoSCoW** — when scoping a build: sort into Must / Should / Could / Won't-this-time.
19
19
 
20
- Log the surviving directions and the reasoning with `python3 {skill-root}/scripts/memlog.py append --type decision --text "<one-line gist>"` (use `--by` in Creative Partner mode). Two or three convergence moves chained is fine (e.g. cluster → score the clusters); more than that is usually over-processing.
20
+ Log the surviving directions and the reasoning with `python3 {project-root}/_wizz/scripts/memlog.py append --type decision --text "<one-line gist>"` (use `--by` in Creative Partner mode). Two or three convergence moves chained is fine (e.g. cluster → score the clusters); more than that is usually over-processing.
21
21
 
22
22
  ## Then finalize
23
23
 
@@ -9,7 +9,7 @@ In Facilitator mode this is the one place your own creative contribution is welc
9
9
  1. **Hand them the mirror first.** Reflect a vivid sampling of *their* ideas back — deliberately include the odd, random, or buried ones from earlier, not just the recent obvious ones (in Creative Partner mode the `(... by user)` tags tell you which were theirs). Ask what they see now: conclusions, synergies, themes, the few that actually matter. Let them connect first; their own pattern-recognition is the point.
10
10
  2. **Then add the connections they would miss.** Lean in creatively — not new raw ideas, but the non-obvious links: this idea from technique one quietly solves that tension from technique four; these three are one idea wearing three hats; this wildcard is the real breakthrough.
11
11
 
12
- Record the insights and chosen directions with `memlog.py append --type insight`. **Then run `python3 {skill-root}/scripts/memlog.py set --workspace {doc_workspace} --key status --value complete`** — the session is done and must stop being offered for resume. Do this even if the user declines every artifact below.
12
+ Record the insights and chosen directions with `memlog.py append --type insight`. **Then run `python3 {project-root}/_wizz/scripts/memlog.py set --workspace {doc_workspace} --key status --value complete`** — the session is done and must stop being offered for resume. Do this even if the user declines every artifact below.
13
13
 
14
14
  ## Artifacts
15
15
 
@@ -29,7 +29,7 @@ Free-form structured payload in the first message; provide what applies:
29
29
 
30
30
  ## Run
31
31
 
32
- 1. Bind `{doc_workspace}` and create the memlog with `python3 {skill-root}/scripts/memlog.py init --workspace {doc_workspace} --field topic="<topic>" [--field goal="<goal>"]`. It remains the canonical source every artifact derives from.
32
+ 1. Bind `{doc_workspace}` and create the memlog with `python3 {project-root}/_wizz/scripts/memlog.py init --workspace {doc_workspace} --field topic="<topic>" [--field goal="<goal>"]`. It remains the canonical source every artifact derives from.
33
33
  2. Run the divergent session per **The inversion**, capturing each idea with `memlog.py append --workspace {doc_workspace} --type idea --text "<idea>"` as it lands, and marking each technique switch with `memlog.py append --type technique --text "started <name>"`.
34
34
  3. Synthesize: surface the conclusions, connections, and the few directions that matter; record them with `memlog.py append --type insight`, then run `memlog.py set --workspace {doc_workspace} --key status --value complete`.
35
35
  4. Produce the requested artifacts from the log — `brainstorm.html` (the imaginative, self-contained, no-template report) and/or the succinct `brainstorm-intent.md` — the same artifacts `references/finalize.md` describes, delegating each to a subagent that reads the log as its sole source. (Headless produces the `artifacts` payload directly; it does not ask, unlike the interactive opt-in.)
@@ -5,6 +5,6 @@ The user handed you the topic and wants to see what you come up with on your own
5
5
  - **Run a real divergent session yourself.** Pick and run techniques on your own (use `brain.py` as in `## Choosing Techniques`, but *you* choose — no menu for the user), capturing each idea to the memlog with `--type idea --by coach`, marking each technique switch with a `technique` entry, shifting the creative domain every ~10 ideas, aiming past 100. Push past the obvious.
6
6
  - **Don't pepper the user with questions** — this is your run. One quick confirm of topic and goal up front is plenty.
7
7
  - **When it's mined out, synthesize and produce the keepsake.** Go to `## Wrap-Up` (`references/finalize.md`): record the insights, mark the memlog complete, and **auto-generate the imaginative HTML keepsake — don't ask first; the keepsake is the result you promised to show them.** Offer the other artifacts (intent doc, etc.) after.
8
- - **Then, because a human is here, offer to keep going together.** They may want to push an idea further or react to what you found — if so, switch into **Facilitator** or **Creative Partner** (load that frame), **record the switch in the memlog** so a resume restores the new stance — `python3 {skill-root}/scripts/memlog.py set --workspace {doc_workspace} --key mode --value <facilitator|partner>` — and continue from the same memlog.
8
+ - **Then, because a human is here, offer to keep going together.** They may want to push an idea further or react to what you found — if so, switch into **Facilitator** or **Creative Partner** (load that frame), **record the switch in the memlog** so a resume restores the new stance — `python3 {project-root}/_wizz/scripts/memlog.py set --workspace {doc_workspace} --key mode --value <facilitator|partner>` — and continue from the same memlog.
9
9
 
10
10
  This is the interactive sibling of headless mode (`references/headless.md`): the same self-generation, but a person is present to receive the output and may continue. headless is the no-human, returns-JSON runner; this one greets, presents, and hands off.
@@ -23,8 +23,11 @@ rather than stdout: dumping the full catalog into context is a footgun, so reach
23
23
  whole library at once must always be an explicit, deliberate choice.
24
24
 
25
25
  `--extra PATH` merges a JSON overlay of additional techniques (customize.toml's
26
- `additional_techniques`) into every command, so custom techniques and whole new
27
- categories are first-class everywhereincluding the browse page and category draws.
26
+ `additional_techniques`) into every command. An extra whose technique_name matches
27
+ a shipped row (case-insensitive) REPLACES it retune a shipped technique; others
28
+ append, so custom techniques and whole new categories are first-class everywhere —
29
+ including the browse page and category draws. (Same overlay semantics as
30
+ wizz-advanced-elicitation's pick_methods.py.)
28
31
 
29
32
  Default output is lean text for an LLM to read; pass --json for structured output.
30
33
  """
@@ -48,10 +51,11 @@ OPTIONAL_FIELDS = ("detail", "provenance", "good_for", "audience")
48
51
 
49
52
 
50
53
  def load(file: Path) -> list[dict]:
51
- with open(file, newline="", encoding="utf-8") as f:
54
+ # utf-8-sig: tolerate BOM-prefixed catalogs (Excel "CSV UTF-8", Notepad)
55
+ with open(file, newline="", encoding="utf-8-sig") as f:
52
56
  rows = list(csv.DictReader(f))
53
57
  for r in rows:
54
- for k in OPTIONAL_FIELDS:
58
+ for k in FIELDS:
55
59
  r.setdefault(k, "")
56
60
  r[k] = (r.get(k) or "").strip()
57
61
  return rows
@@ -63,9 +67,13 @@ def load_extra(file: Path) -> list[dict]:
63
67
  customize.toml's `additional_techniques` become first-class across *every*
64
68
  subcommand (categories/list/random/show/html), so the browse page and
65
69
  category draws include them too, not just the in-chat flows."""
66
- data = json.loads(file.read_text(encoding="utf-8"))
70
+ data = json.loads(file.read_text(encoding="utf-8-sig"))
71
+ if not isinstance(data, list):
72
+ raise ValueError("--extra must be a JSON array of objects")
67
73
  rows = []
68
74
  for item in data:
75
+ if not isinstance(item, dict):
76
+ raise ValueError(f"each --extra entry must be a JSON object, got: {item!r}")
69
77
  rows.append({
70
78
  "category": str(item.get("category", "")).strip(),
71
79
  "technique_name": str(item.get("technique_name", "")).strip(),
@@ -78,6 +86,22 @@ def load_extra(file: Path) -> list[dict]:
78
86
  return rows
79
87
 
80
88
 
89
+ def merge_extra(rows: list[dict], extras: list[dict]) -> list[dict]:
90
+ """Extras replace a catalog row with the same technique_name (case-insensitive),
91
+ otherwise append — the same overlay semantics as pick_methods.py, so
92
+ customize.toml additional_* entries behave identically across sibling skills."""
93
+ merged = list(rows)
94
+ index = {r["technique_name"].lower(): i for i, r in enumerate(merged)}
95
+ for e in extras:
96
+ key = e["technique_name"].lower()
97
+ if key in index:
98
+ merged[index[key]] = e
99
+ else:
100
+ index[key] = len(merged)
101
+ merged.append(e)
102
+ return merged
103
+
104
+
81
105
  def categories(rows: list[dict]) -> list[tuple[str, int]]:
82
106
  counts: dict[str, int] = {}
83
107
  for r in rows:
@@ -415,6 +439,8 @@ SELECTOR_TEMPLATE = r"""<!DOCTYPE html>
415
439
  function checkedInvent(){ return inventBoxes.filter(function(b){ return b.checked; }); }
416
440
 
417
441
  function update(){
442
+ // rand can't exceed what the pool can supply — keep the counter honest with the draw
443
+ if (state.rand > randomPool().length){ state.rand = randomPool().length; }
418
444
  $('pickN').textContent = checkedTech().length;
419
445
  $('randN').textContent = state.rand;
420
446
  $('invN').textContent = state.inv;
@@ -693,7 +719,11 @@ def main(argv: list[str] | None = None) -> int:
693
719
  if not args.extra.is_file():
694
720
  print(f"error: --extra file not found: {args.extra}", file=sys.stderr)
695
721
  return 2
696
- rows += load_extra(args.extra)
722
+ try:
723
+ rows = merge_extra(rows, load_extra(args.extra))
724
+ except (OSError, ValueError) as e:
725
+ print(f"error: could not read --extra: {e}", file=sys.stderr)
726
+ return 2
697
727
  csv_dir = args.file.resolve().parent
698
728
 
699
729
  if args.cmd == "categories":
@@ -3,6 +3,7 @@
3
3
  # dependencies = ["pytest>=8.0"]
4
4
  # ///
5
5
  """Tests for brain.py. Run: uv run -m pytest scripts/tests/test_brain.py"""
6
+ import json
6
7
  import sys
7
8
  from pathlib import Path
8
9
 
@@ -186,6 +187,27 @@ def test_extra_appears_in_list_and_random(lib, extra, capsys):
186
187
  assert "Regulatory Inversion" in capsys.readouterr().out
187
188
 
188
189
 
190
+ def test_extra_replaces_shipped_row_by_name(lib, extra, tmp_path, capsys):
191
+ shipped = brain.load(Path(lib))[0]
192
+ overlay = tmp_path / "replace.json"
193
+ overlay.write_text(
194
+ json.dumps([{"category": shipped["category"], "technique_name": shipped["technique_name"], "description": "RETUNED"}]),
195
+ encoding="utf-8",
196
+ )
197
+ brain.main(["--file", str(lib), "--extra", str(overlay), "list", "--all"])
198
+ out = capsys.readouterr().out
199
+ assert "RETUNED" in out
200
+ assert out.count(shipped["technique_name"]) == 1 # replaced, not duplicated
201
+
202
+
203
+ def test_extra_malformed_exits_cleanly(lib, tmp_path, capsys):
204
+ bad = tmp_path / "bad.json"
205
+ for content in ('{not json', '{"a": 1}', '["not-an-object"]'):
206
+ bad.write_text(content, encoding="utf-8")
207
+ assert brain.main(["--file", str(lib), "--extra", str(bad), "categories"]) == 2
208
+ assert "could not read --extra" in capsys.readouterr().err
209
+
210
+
189
211
  def test_extra_is_first_class_in_html(lib, extra, tmp_path):
190
212
  out = tmp_path / "sel.html"
191
213
  assert brain.main(["--file", str(lib), "--extra", str(extra), "html", "--out", str(out)]) == 0
@@ -207,11 +229,11 @@ def test_unknown_category_style_uses_fallback_glyph():
207
229
 
208
230
  def test_shipped_selector_is_in_sync_with_catalog():
209
231
  # foolproofing: if someone edits brain-methods.csv they must regenerate the page.
210
- # Regenerate with: python3 brain.py html --out assets/brain-selector.html
232
+ # Regenerate with: uv run brain.py html --out assets/brain-selector.html
211
233
  asset = brain.DEFAULT_FILE.parent / "brain-selector.html"
212
234
  assert asset.is_file(), "missing assets/brain-selector.html — generate it"
213
235
  expected = brain.html_doc(brain.load(brain.DEFAULT_FILE))
214
236
  assert asset.read_text(encoding="utf-8") == expected, (
215
237
  "assets/brain-selector.html is stale; regenerate: "
216
- "python3 brain.py html --out assets/brain-selector.html"
238
+ "uv run brain.py html --out assets/brain-selector.html"
217
239
  )
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: wizz-forge-idea
3
+ description: Test a half-formed idea in a questioning conversation, with different personas probing its weak points, until the user can act on it or drop it with confidence. Optionally writes a short brief for planning skills to build on. Use when the user says 'forge an idea', 'pressure-test this idea', 'stress-test my thinking', or 'harden this idea'
4
+ ---
5
+
6
+ # Wizz Forge Idea
7
+
8
+ ## Overview
9
+
10
+ Take a half-formed idea and pressure-test it in conversation, while changing your mind is still cheap, until it becomes something the user can act on with conviction or reject. The main risk is what the user has not examined yet: unchecked assumptions and unresolved decisions usually become more expensive problems later.
11
+
12
+ The main goal is better thinking, not producing an artifact. Strengthening an idea, rejecting it, or thinking it through more clearly are all complete outcomes. Writing `forged-idea.md` to hand off to another workflow is optional. Do not steer the conversation toward "shall we build it?"
13
+
14
+ This skill can be used on many kinds of ideas. When the idea is about a product or feature, what survives may be written to `forged-idea.md` for later planning.
15
+
16
+ Lead by questioning, not lecturing. Ask one question at a time, press on weak points, and do not let vague claims pass without examination.
17
+
18
+ ## Conventions
19
+
20
+ - Scripts live in two places — run each from the exact path written, never assume co-location: the shared core scripts (`memlog.py`, `resolve_customization.py`, `resolve_config.py`) are installed by Wizz core at `{project-root}/_wizz/scripts/` and are never bundled here; this skill's own `resolve_personas.py` is at `{skill-root}/scripts/`.
21
+ - `{workflow.<name>}` resolves to fields in the merged `customize.toml` `[workflow]` table.
22
+
23
+ ## On Activation
24
+
25
+ 1. Resolve customization: `python3 {project-root}/_wizz/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly with defaults. Apply the resolved `{workflow.*}` values throughout.
26
+ 2. Run each `{workflow.activation_steps_prepend}` entry; treat each `{workflow.persistent_facts}` entry as foundational context (`file:` entries load their contents, `skill:` names a skill to consult, others are facts verbatim).
27
+ 3. Resolve central config: `python3 {project-root}/_wizz/scripts/resolve_config.py --project-root {project-root} --key core`; from the merged JSON read `{user_name}`, `{communication_language}`, `{output_folder}`. On failure use neutral defaults; never block. Greet `{user_name}` in `{communication_language}` and stay in it.
28
+ 4. Note whether a Wizz persona is already active in this conversation — the user loaded one (e.g. the analyst, the storyteller) and invoked the forge from within it. If so, that persona leads the session, in voice, throughout.
29
+ 5. Resume: glob `{workflow.forge_output_path}/**/.memlog.md` (recursive, so it still finds sessions when `run_folder_pattern` is overridden to nest paths) and read only each match's frontmatter to find any whose `status` is not `complete`. Offer to resume one — then read its full memlog once to rebuild state and continue append-only — or to start fresh.
30
+ 6. Run each `{workflow.activation_steps_append}` entry.
31
+
32
+ ## Open the session
33
+
34
+ Start by scrutinizing the idea, not endorsing it.
35
+
36
+ ### Discover intent
37
+ Identify:
38
+ - the subject idea,
39
+ - the user's goal for the session,
40
+ - whether the idea is new or a change to an existing project
41
+
42
+ If any of these are already clear from the prompt that invoked this skill or previous context, ask the user to confirm and continue.
43
+
44
+ Otherwise ask for what's missing, in order:
45
+ - what is the idea?
46
+ - do you want to clarify and understand it, test whether it holds up, or make it better?
47
+ - is it a new idea or a change to an existing project? If the latter, what project is it, and where can I find its files or other relevant materials?
48
+
49
+ ### Steering the conversation
50
+
51
+ Tell the user they can say **"attack this"**, **"defend this"**, or **"switch roles"** at any time to change how the current idea is argued. In attack mode, do not agree with the idea; look for contradictions, weak assumptions, and failure cases. In defend mode, argue for the strongest version of the idea. Tell the user they can also name a persona or party at any time to change who participates in the session.
52
+
53
+ ### Set up the session
54
+
55
+ Derive a kebab-case `{slug}` for the idea and bind the session workspace `{workspace} = {workflow.forge_output_path}/{workflow.run_folder_pattern}` (the pattern fills with `{slug}`). Create the memlog once the goal is known:
56
+ `python3 {project-root}/_wizz/scripts/memlog.py init --workspace {workspace} --field idea="<idea>" --field goal="<goal>"`
57
+
58
+ Tell the user the path; state is on disk now, so the session survives interruption. If init fails, don't abort — run the forge in-conversation and tell the user state won't persist this session.
59
+
60
+ ## The forge
61
+
62
+ Let the session goal set the first move: for clarifying, pin down terms, boundaries, and assumptions; for testing, go after the central claim first; for making it better, drive each unresolved branch to a concrete decision.
63
+
64
+ Work one question at a time, in dependency order.
65
+
66
+ Include your current best answer or hypothesis when it helps the user respond. A concrete proposal is easier to accept, reject, or revise than an open-ended prompt. Find discoverable answers yourself instead of asking.
67
+
68
+ Do not assume the user's terms are precise. When a term is fuzzy or overloaded, name the ambiguity and ask for a precise choice before continuing. For example, do not let `user`, `buyer`, and `payer` collapse into one entity unless the idea actually requires that.
69
+
70
+ For ideas about an existing project, treat the project's files and materials as the source of truth. Do not accept a label or summary as proof. Find the relevant material yourself and check the user's claim against it. If the material contradicts the user's claim, stop and resolve that before continuing.
71
+
72
+ When a branch resolves, pause before moving on. Give the user a chance to raise any remaining concern.
73
+
74
+ Do not use agreement or praise to make the interaction smoother; they lower pressure and lead to shallower thinking. Agreement is allowed only when it helps the user think better. Praise is noise. Continued engagement and ego-stroking are not objectives. In attack mode, never agree with the idea until the user ends the mode. For each answer, either challenge the weak point or build on the strong point, whichever helps the user think better.
75
+
76
+ Capture as you go — each decision, assumption, crack, kill, and locked idea, one bullet in the user's meaning:
77
+ `python3 {project-root}/_wizz/scripts/memlog.py append --workspace {workspace} --type <decision|assumption|crack|kill|direction|lock|note> --text "<gist>"`
78
+ A `lock` is an idea the user hardens — settled, not to be reopened; locks are what `forged-idea.md` is distilled from. Don't read the memlog back except on resume. If the user raises a different branch, capture it and stay put — the loop and the stray insight both survive.
79
+
80
+ ## The personas
81
+
82
+ If a Wizz persona was already active when the forge started, keep that persona as the lead voice.
83
+
84
+ Resolve the available persona pool once, as soon as the goal is known:
85
+ `python3 {skill-root}/scripts/resolve_personas.py --project-root {project-root} --skill {skill-root}`
86
+ The script returns installed Wizz agents (`agents`), user-defined personas (`members`), and saved parties (`parties`). Parties may include a `scene`; some are open-cast. This gives you the same roster information as `wizz-party-mode` without invoking it.
87
+
88
+ Each turn uses two voices:
89
+ - **One available persona** — choose an installed agent or user-defined persona whose expertise fits the current branch. Vary this voice every few turns; do not let one voice dominate. If the user names a specific persona, use it. If the user calls a saved party, use the whole party and its scene. If the user asks to go one-on-one, use only the requested persona. If no pool is available, generate this voice yourself.
90
+ - **One generated persona** — create a fresh outside voice, such as a competitor, buyer, finance reviewer, domain expert, or critic. Give it a name and enough characterization to keep its viewpoint distinct.
91
+
92
+ Use these voices in character to pressure-test the current branch: find sharper objections, missing assumptions, and stronger defenses. Cross-examine them for what matters, then synthesize their input into your next question. Do not let the session turn into a panel debate or persona performance.
93
+
94
+ Voice the personas yourself by default. Spawn separate agents only when a branch needs independent reasoning that should not be influenced by one shared voice.
95
+
96
+ ## Exits
97
+
98
+ The session can end in three valid states:
99
+
100
+ - **Hardened** — the idea is stronger and specific enough to use. Distill the memlog into `{workspace}/forged-idea.md`. Keep it extremely short: only the decisions, rejected options, and reasons that matter downstream, in the user's meaning. Do not write a prose summary, template, or conversation recap. If it reads like a document, it is too long. If planning or dev skills are installed (`wizz-spec`, `wizz-prd`, `wizz-prfaq`, `wizz-quick-dev`), offer the file as their input; if none are, the file stands on its own — never treat a missing skill as an error.
101
+ - **Killed** — the idea does not hold up. Say so plainly and record why. Finding that out early is a valid outcome.
102
+ - **Clearer** — the user understands the idea better, but there is no hardened idea to hand off. Leave the memlog as the record; no `forged-idea.md` is needed.
103
+
104
+ Always render `{workspace}/forge-report.html` as a self-contained HTML file the user can open, with inline CSS and an inline-SVG seal or stamp. Summarize the outcome, the locked decisions, what was rejected and why, and the weak points that survived scrutiny, in the user's meaning. Credit the personas and parties that pressure-tested the idea by name, icon, and voice. Render a prominent wax-seal-style or stamped outcome mark, matched to the result: `HARDENED`, an `Idea Death Certificate` stamped `KILLED` with the cause of death, or `CLARIFIED`. Tell the user the path.
105
+
106
+ Flip the status at the end: `python3 {project-root}/_wizz/scripts/memlog.py set --workspace {workspace} --key status --value complete`.
107
+ If `{workflow.on_complete}` is non-empty, run all instructions in order.
@@ -0,0 +1,41 @@
1
+ # DO NOT EDIT -- overwritten on every update.
2
+ #
3
+ # Workflow customization surface for wizz-forge-idea.
4
+ #
5
+ # Override files (not edited here):
6
+ # {project-root}/_wizz/custom/wizz-forge-idea.toml (team)
7
+ # {project-root}/_wizz/custom/wizz-forge-idea.user.toml (personal)
8
+
9
+ [workflow]
10
+
11
+ # --- Configurable below. Overrides merge per Wizz structural rules: ---
12
+ # scalars: override wins • arrays: append
13
+
14
+ # Steps to run before the standard activation (config load, greet).
15
+ activation_steps_prepend = []
16
+
17
+ # Steps to run after greet but before the session begins.
18
+ activation_steps_append = []
19
+
20
+ # Persistent facts the interrogator keeps in mind for the whole session
21
+ # (domain constraints, house rules, what's off the table). Each entry is a
22
+ # literal sentence, a skill prefixed with `skill:`, or a `file:`-prefixed
23
+ # path/glob whose contents are loaded as facts. Empty by default — repo-wide context
24
+ # belongs in AGENTS.md (see wizz-generate-project-context), which every skill already sees. Use
25
+ # this for context only the forge needs, loaded on demand rather than carried as constant
26
+ # memory (e.g. `file:{project-root}/**/project-context.md` if you keep one).
27
+ persistent_facts = []
28
+
29
+ # Executed when the session completes. Scalar or array of instructions. Empty for none.
30
+ on_complete = []
31
+
32
+ # Parent folder for all forge sessions. Each session gets its own run
33
+ # folder underneath (see run_folder_pattern). Lands directly under
34
+ # {output_folder} so the forge works in core-only installs.
35
+ forge_output_path = "{output_folder}/forge"
36
+
37
+ # Run-folder pattern inside forge_output_path. Resolved against the
38
+ # idea-derived slug at activation. Same slug = same folder, so resuming
39
+ # an idea reuses its memlog. Override to add {date} or other components
40
+ # if a fresh dated history per run is preferred.
41
+ run_folder_pattern = "{slug}"