wizz-method 2.23.0 → 2.25.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 (45) hide show
  1. package/package.json +1 -1
  2. package/skills-registry.yaml +7 -1
  3. package/src/bmm-skills/1-analysis/wizz-document-project/references/static-dependencies.md +124 -0
  4. package/src/bmm-skills/1-analysis/wizz-document-project/scripts/dependencies.py +374 -0
  5. package/src/bmm-skills/1-analysis/wizz-document-project/scripts/test_dependencies.py +148 -0
  6. package/src/bmm-skills/1-analysis/wizz-document-project/scripts/test_dependencies_unit.py +183 -0
  7. package/src/bmm-skills/1-analysis/wizz-document-project/templates/deep-dive-template.md +9 -0
  8. package/src/bmm-skills/1-analysis/wizz-document-project/templates/index-template.md +9 -0
  9. package/src/bmm-skills/1-analysis/wizz-document-project/workflows/deep-dive-instructions.md +4 -0
  10. package/src/bmm-skills/1-analysis/wizz-document-project/workflows/full-scan-instructions.md +7 -1
  11. package/src/bmm-skills/4-implementation/wizz-code-review/customize.toml +19 -0
  12. package/src/bmm-skills/4-implementation/wizz-code-review/references/review-economy.md +5 -1
  13. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-01-gather-context.md +27 -2
  14. package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-02-review.md +2 -0
  15. package/src/bmm-skills/4-implementation/wizz-dev-story/SKILL.md +1 -1
  16. package/src/bmm-skills/4-implementation/wizz-quick-dev/customize.toml +19 -0
  17. package/src/bmm-skills/4-implementation/wizz-quick-dev/references/review-economy.md +5 -1
  18. package/src/bmm-skills/4-implementation/wizz-quick-dev/step-04-review.md +1 -1
  19. package/src/modules/wizz/_shared/approval-gate.md +45 -0
  20. package/src/modules/wizz/_shared/approval-gate.mjs +184 -0
  21. package/src/modules/wizz/_shared/communication-rules.md +2 -0
  22. package/src/modules/wizz/_shared/model-ladder.md +12 -0
  23. package/src/modules/wizz/agents/wizz-ads/SKILL.md +1 -0
  24. package/src/modules/wizz/agents/wizz-copy/SKILL.md +1 -0
  25. package/src/modules/wizz/agents/wizz-growth/SKILL.md +1 -0
  26. package/src/modules/wizz/agents/wizz-maestro/SKILL.md +3 -1
  27. package/src/modules/wizz/agents/wizz-seo/SKILL.md +1 -0
  28. package/src/modules/wizz/agents/wizz-social/SKILL.md +7 -1
  29. package/src/modules/wizz/agents/wizz-social/customize.toml +4 -3
  30. package/src/modules/wizz/subagents/codex/wizz-exec-review.toml +2 -1
  31. package/src/modules/wizz/subagents/gemini/wizz-exec-review.md +2 -1
  32. package/src/modules/wizz/subagents/opencode/wizz-exec-review.md +2 -1
  33. package/src/modules/wizz/subagents/spec.yaml +4 -3
  34. package/src/modules/wizz/subagents/wizz-exec-review.md +2 -1
  35. package/src/skills-lib/wizz-router/references/routing-table-flat.md +1 -1
  36. package/src/skills-lib/wizz-security-audit-pentest/SKILL.md +50 -26
  37. package/src/skills-lib/wizz-security-audit-pentest/references/stage-5-segredos-historico.md +74 -0
  38. package/src/skills-lib/wizz-security-audit-pentest/references/stage-6-advogado-do-diabo.md +33 -0
  39. package/src/skills-lib/wizz-security-audit-pentest/references/stage-7-fusao-plano.md +50 -0
  40. package/tools/installer/README.md +5 -0
  41. package/tools/installer/commands/templates.js +65 -0
  42. package/tools/installer/core/resolved-schema.js +5 -3
  43. package/tools/installer/modules/templates.js +229 -0
  44. package/tools/installer/templates.md +156 -0
  45. package/src/skills-lib/wizz-security-audit-pentest/references/stage-5-fusao-plano.md +0 -27
@@ -0,0 +1,148 @@
1
+ """Integration tests: WIZZ_DEPCRUISE=/absolute/path/to/depcruise python3 -m unittest discover ..."""
2
+ import contextlib
3
+ import importlib.util
4
+ import io
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import subprocess
9
+ import sys
10
+ import tempfile
11
+ import unittest
12
+ from unittest.mock import patch
13
+
14
+ SCRIPT = Path(__file__).with_name('dependencies.py')
15
+ CRUISER = os.environ.get('WIZZ_DEPCRUISE')
16
+
17
+
18
+ @unittest.skipUnless(CRUISER, 'Set WIZZ_DEPCRUISE to a real dependency-cruiser executable')
19
+ class DependenciesTest(unittest.TestCase):
20
+ def setUp(self):
21
+ self.temp = tempfile.TemporaryDirectory()
22
+ self.addCleanup(self.temp.cleanup)
23
+ self.root = Path(self.temp.name) / 'repo'
24
+ self.root.mkdir()
25
+ self.output = Path(self.temp.name) / 'output'
26
+ self.git('init', '-q')
27
+ self.git('config', 'user.email', 'fixture@example.invalid')
28
+ self.git('config', 'user.name', 'Dependency fixture')
29
+ self.write('tsconfig.json', json.dumps({'compilerOptions': {
30
+ 'baseUrl': '.', 'paths': {'@/*': ['./src/*']}}}))
31
+ self.write('src/a.ts', 'import type { B } from "@/b";\nexport const load = () => import("./c");\n')
32
+ self.write('src/b.ts', 'export type B = number;\n')
33
+ self.write('src/c.ts', 'import "./b";\n')
34
+ self.git('add', '.')
35
+ self.git('commit', '-qm', 'test: fixture')
36
+
37
+ def write(self, name, text):
38
+ path = self.root / name
39
+ path.parent.mkdir(parents=True, exist_ok=True)
40
+ path.write_text(text)
41
+
42
+ def git(self, *args):
43
+ return subprocess.check_output(['git', *args], cwd=self.root).decode()
44
+
45
+ def cli(self, command, *args):
46
+ return json.loads(subprocess.check_output([
47
+ sys.executable, str(SCRIPT), '--root', str(self.root),
48
+ '--output', str(self.output), command, *args]))
49
+
50
+ def scan(self):
51
+ return self.cli('scan', '--cruiser', CRUISER, '--tsconfig', 'tsconfig.json')
52
+
53
+ def graph(self):
54
+ return json.loads((self.output / 'graph.json').read_text())
55
+
56
+ def test_alias_type_dynamic_transitive_and_noop(self):
57
+ self.assertEqual(self.scan()['modules_parsed'], 3)
58
+ graph = self.graph()
59
+ self.assertEqual({d['resolved'] for d in graph['modules']['src/a.ts']},
60
+ {'src/b.ts', 'src/c.ts'})
61
+ self.assertEqual(self.cli('refresh', '--cruiser', CRUISER, '--tsconfig', 'tsconfig.json'), dict(source_hash_reads=0, modules_parsed=0, source_count=3))
62
+ files = self.output / 'files.json'
63
+ files.write_text('["src/b.ts"]')
64
+ impact = self.cli('impact', '--files', str(files))
65
+ self.assertEqual(impact['direct'], ['src/a.ts', 'src/c.ts'])
66
+ self.assertEqual(impact['transitive'], [])
67
+
68
+ def test_noop_rejects_any_source_hash_read(self):
69
+ self.scan()
70
+ spec = importlib.util.spec_from_file_location('dependencies', SCRIPT)
71
+ module = importlib.util.module_from_spec(spec)
72
+ spec.loader.exec_module(module)
73
+ original = Path.read_bytes
74
+
75
+ def guarded_read(path):
76
+ if path.suffix in module.EXTENSIONS:
77
+ self.fail('Unchanged source was reread: ' + str(path))
78
+ return original(path)
79
+
80
+ argv = [str(SCRIPT), '--root', str(self.root), '--output', str(self.output),
81
+ 'scan', '--cruiser', CRUISER, '--tsconfig', 'tsconfig.json']
82
+ with patch.object(Path, 'read_bytes', guarded_read), patch.object(sys, 'argv', argv):
83
+ with contextlib.redirect_stdout(io.StringIO()) as stdout:
84
+ module.main()
85
+ self.assertEqual(json.loads(stdout.getvalue())['modules_parsed'], 0)
86
+
87
+ def test_incremental_commit_and_dirty_edits(self):
88
+ self.scan()
89
+ old_commit = self.graph()['commit']
90
+ self.write('src/b.ts', 'export type B = string;\n')
91
+ self.assertEqual(self.scan()['modules_parsed'], 1)
92
+ self.git('add', '.')
93
+ self.git('commit', '-qm', 'test: change type')
94
+ self.assertEqual(self.scan()['modules_parsed'], 0)
95
+ self.assertEqual(self.graph()['commit_changes'], ['src/b.ts'])
96
+ self.assertEqual(self.cli('impact', '--base', old_commit)['touched'], ['src/b.ts'])
97
+
98
+ def test_deleted_import_retains_previous_edges_after_noop(self):
99
+ self.scan()
100
+ self.write('src/c.ts', '')
101
+ self.scan()
102
+ self.scan()
103
+ files = self.output / 'files.json'
104
+ files.write_text('["src/b.ts"]')
105
+ self.assertIn('src/c.ts', self.cli('impact', '--files', str(files))['direct'])
106
+ (self.root / 'src/b.ts').unlink()
107
+ self.assertEqual(self.scan()['modules_parsed'], 2)
108
+ self.assertNotIn('src/b.ts', self.graph()['modules'])
109
+
110
+ def test_untracked_addition_and_config_invalidation(self):
111
+ self.scan()
112
+ self.write('src/new.ts', 'import "./a";\n')
113
+ self.assertEqual(self.scan()['modules_parsed'], 4)
114
+ self.write('tsconfig.json', '{"compilerOptions": {"baseUrl": "."}}')
115
+ self.assertEqual(self.scan()['modules_parsed'], 4)
116
+
117
+ def test_diff_paths_with_spaces_export_links_and_stale_cleanup(self):
118
+ self.write('src/has space.ts', 'import "./b"; import "external-test-module";\n')
119
+ self.git('add', '.')
120
+ self.git('commit', '-qm', 'test: spaces')
121
+ self.scan()
122
+ self.write('src/has space.ts', 'import "./c";\n')
123
+ diff = self.output / 'change.diff'
124
+ diff.write_text(self.git('diff', 'HEAD'))
125
+ self.assertEqual(self.cli('impact', '--diff', str(diff))['touched'], ['src/has space.ts'])
126
+ self.cli('export')
127
+ notes = self.output / 'obsidian'
128
+ self.assertEqual(len(list(notes.glob('*.md'))), 4)
129
+ self.assertTrue(any('[[' in p.read_text() for p in notes.glob('*.md')))
130
+ self.assertTrue(any('[[external-' in p.read_text() for p in notes.glob('*.md')))
131
+ (notes / 'module-stale.md').write_text('stale')
132
+ self.cli('export')
133
+ self.assertFalse((notes / 'module-stale.md').exists())
134
+
135
+ def test_cycle_and_diff_rename(self):
136
+ self.write('src/b.ts', 'import "./a"; export type B = number;\n')
137
+ self.scan()
138
+ files = self.output / 'files.json'
139
+ files.write_text('["src/b.ts"]')
140
+ self.assertEqual(self.cli('impact', '--files', str(files))['transitive'], [])
141
+ self.git('mv', 'src/c.ts', 'src/renamed.ts')
142
+ diff = self.output / 'rename.diff'
143
+ diff.write_text(self.git('diff', '--cached', '-M'))
144
+ self.assertEqual(self.cli('impact', '--diff', str(diff))['touched'], ['src/c.ts', 'src/renamed.ts'])
145
+
146
+
147
+ if __name__ == '__main__':
148
+ unittest.main()
@@ -0,0 +1,183 @@
1
+ """Always-run behavioral tests: Git fixtures and controlled scanner boundary, no npm tool."""
2
+ import contextlib
3
+ import importlib.util
4
+ import io
5
+ import json
6
+ from pathlib import Path
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ from types import SimpleNamespace
11
+ import unittest
12
+ from unittest.mock import patch
13
+
14
+ SPEC = importlib.util.spec_from_file_location('dependencies', Path(__file__).with_name('dependencies.py'))
15
+ dep = importlib.util.module_from_spec(SPEC)
16
+ SPEC.loader.exec_module(dep)
17
+
18
+
19
+ class DependencyUnitTest(unittest.TestCase):
20
+ def setUp(self):
21
+ self.temp = tempfile.TemporaryDirectory()
22
+ self.addCleanup(self.temp.cleanup)
23
+ self.root = Path(self.temp.name) / 'repo'
24
+ self.root.mkdir()
25
+ self.output = Path(self.temp.name) / 'cache'
26
+ self.git('init', '-q')
27
+ self.git('config', 'user.email', 'fixture@example.invalid')
28
+ self.git('config', 'user.name', 'Fixture')
29
+ self.write('src/a.js', 'import "./b.js";\n')
30
+ self.write('src/b.js', 'export default 1;\n')
31
+ self.git('add', '.')
32
+ self.git('commit', '-qm', 'test: baseline')
33
+ self.edges = {'src/a.js': [{'module': './b.js', 'resolved': 'src/b.js'}], 'src/b.js': []}
34
+ self.args = SimpleNamespace(cruiser=sys.executable, tsconfig=None)
35
+
36
+ def git(self, *args):
37
+ return subprocess.check_output(['git', *args], cwd=self.root)
38
+
39
+ def write(self, name, text):
40
+ target = self.root / name
41
+ target.parent.mkdir(parents=True, exist_ok=True)
42
+ target.write_text(text)
43
+
44
+ def scan(self):
45
+ original = dep.run
46
+
47
+ def boundary(root, *args):
48
+ if args[0] == 'git':
49
+ return original(root, *args)
50
+ if args[-1] == '--version':
51
+ return 'fixture-version'
52
+ if args[-1] == '--info':
53
+ return 'typescript available'
54
+ batch = args[args.index('--') + 1:]
55
+ return json.dumps({'modules': [{'source': name, 'dependencies': self.edges.get(name, [])} for name in batch]})
56
+
57
+ with patch.object(dep, 'run', boundary), contextlib.redirect_stdout(io.StringIO()) as stdout:
58
+ dep.scan(self.args, self.root, self.output)
59
+ return json.loads(stdout.getvalue())
60
+
61
+ def impact(self, files):
62
+ selection = Path(self.temp.name) / 'files.json'
63
+ selection.write_text(json.dumps(files))
64
+ args = SimpleNamespace(base=None, files=str(selection))
65
+ with contextlib.redirect_stdout(io.StringIO()) as stdout:
66
+ dep.impact(args, self.root, self.output)
67
+ return json.loads(stdout.getvalue())
68
+
69
+ def test_deleted_edges_survive_multiple_changed_scans_and_commit(self):
70
+ self.scan()
71
+ baseline = dep.load_graph(self.output, self.root)['baseline_commit']
72
+ (self.root / 'src/b.js').unlink()
73
+ self.edges['src/a.js'] = []
74
+ self.scan()
75
+ for i in range(3):
76
+ self.write('src/unrelated.js', f'export default {i};\n')
77
+ self.scan()
78
+ self.git('add', '.')
79
+ self.git('commit', '-qm', 'test: remove')
80
+ self.scan()
81
+ self.assertEqual(self.impact(['src/b.js'])['direct'], ['src/a.js'])
82
+ self.assertEqual(dep.load_graph(self.output, self.root)['baseline_commit'], baseline)
83
+
84
+ def test_asset_create_delete_and_config_changes_invalidate_importer(self):
85
+ self.edges['src/a.js'] = [{'module': './data.json', 'couldNotResolve': True}]
86
+ self.scan()
87
+ self.write('src/data.json', '{}')
88
+ self.edges['src/a.js'] = [{'module': './data.json', 'resolved': 'src/data.json'}]
89
+ self.assertEqual(self.scan()['modules_parsed'], 2)
90
+ self.assertEqual(self.impact(['src/data.json'])['direct'], ['src/a.js'])
91
+ self.write('src/data.json', '{"resolve": 1}')
92
+ self.assertEqual(self.scan()['modules_parsed'], 2)
93
+ (self.root / 'src/data.json').unlink()
94
+ self.edges['src/a.js'] = [{'module': './data.json', 'couldNotResolve': True}]
95
+ self.assertEqual(self.scan()['modules_parsed'], 2)
96
+ self.assertTrue(dep.load_graph(self.output, self.root)['modules']['src/a.js'][0]['couldNotResolve'])
97
+
98
+ def test_noop_reuses_source_hashes(self):
99
+ self.scan()
100
+ original = dep.digest
101
+ def digest(target):
102
+ self.assertNotIn(target.suffix, dep.EXTENSIONS)
103
+ return original(target)
104
+ with patch.object(dep, 'digest', digest):
105
+ self.assertEqual(self.scan()['source_hash_reads'], 0)
106
+
107
+ def test_recovery_corruption_unknown_commit_and_unborn_repository(self):
108
+ self.scan()
109
+ (self.output / 'graph.json').write_text('{bad')
110
+ self.assertEqual(self.scan()['modules_parsed'], 2)
111
+ graph = dep.load_graph(self.output, self.root)
112
+ graph['commit'] = 'f' * 40
113
+ dep.save(self.output / 'graph.json', graph)
114
+ self.assertEqual(self.scan()['modules_parsed'], 2)
115
+ self.git('checkout', '--orphan', 'unborn')
116
+ self.assertEqual(self.scan()['modules_parsed'], 0)
117
+ self.assertIsNone(dep.load_graph(self.output, self.root)['commit'])
118
+
119
+ def test_cache_never_supplies_executable_and_repo_tool_is_rejected(self):
120
+ self.scan()
121
+ marker = self.root / 'executed'
122
+ tool = self.root / 'payload'
123
+ tool.write_text('#!/bin/sh\ntouch ' + str(marker) + '\n')
124
+ tool.chmod(0o700)
125
+ graph = dep.load_graph(self.output, self.root)
126
+ graph['config']['cruiser'] = str(tool)
127
+ dep.save(self.output / 'graph.json', graph)
128
+ argv = ['dependencies.py', '--root', str(self.root), '--output', str(self.output), 'refresh']
129
+ with patch.dict('os.environ', {}, clear=True), patch.object(sys, 'argv', argv):
130
+ with self.assertRaisesRegex(ValueError, 'trusted absolute'):
131
+ dep.main()
132
+ with self.assertRaisesRegex(ValueError, 'outside'):
133
+ dep.trusted_tool(self.root, str(tool))
134
+ self.assertFalse(marker.exists())
135
+ self.assertFalse(dep.canonical_output(self.root).is_relative_to(self.root))
136
+
137
+ def test_files_normalize_reject_invalid_and_distinguish_transitive(self):
138
+ self.edges['src/c.js'] = [{'module': './a.js', 'resolved': 'src/a.js'}]
139
+ self.write('src/c.js', 'import "./a.js";')
140
+ self.scan()
141
+ result = self.impact(['./src/b.js', str(self.root / 'src/b.js')])
142
+ self.assertEqual(result['touched'], ['src/b.js'])
143
+ self.assertEqual(result['direct'], ['src/a.js'])
144
+ self.assertEqual(result['transitive'], ['src/c.js'])
145
+ for value in ({}, 'src/b.js', [None], [5], [''], ['../outside'], ['/outside']):
146
+ with self.subTest(value=value), self.assertRaises(ValueError):
147
+ self.impact(value)
148
+
149
+ def test_diff_default_no_prefix_relative_rename_non_utf8_and_bad_input(self):
150
+ self.write('src/a.js', 'import "./b.js";\n// change\n')
151
+ diff = Path(self.temp.name) / 'changes.diff'
152
+ for flags in ([], ['--no-prefix']):
153
+ diff.write_bytes(self.git('diff', *flags))
154
+ self.assertEqual(dep.diff_files(self.root, diff), ['src/a.js'])
155
+ standard_diff = self.git('diff')
156
+ self.write('src/b.js', 'export default 2;\n')
157
+ no_prefix_diff = self.git('diff', '--no-prefix', '--', 'src/b.js')
158
+ diff.write_bytes(standard_diff + no_prefix_diff)
159
+ self.assertEqual(dep.diff_files(self.root, diff), ['src/a.js', 'src/b.js'])
160
+ self.git('checkout', '--', 'src/b.js')
161
+ diff.write_bytes(self.git('diff', '--relative=src', '--no-prefix'))
162
+ self.assertEqual(dep.diff_files(self.root, diff, 'src'), ['src/a.js'])
163
+ self.git('mv', 'src/b.js', 'src/new name.js')
164
+ diff.write_bytes(self.git('diff', '--cached', '-M'))
165
+ self.assertEqual(dep.diff_files(self.root, diff), ['src/b.js', 'src/new name.js'])
166
+ (self.root / 'src/a.js').write_bytes(b'// invalid utf8: \xff\n')
167
+ diff.write_bytes(self.git('diff'))
168
+ self.assertEqual(dep.diff_files(self.root, diff), ['src/a.js'])
169
+ self.write('a/old.js', 'export default 1;\n')
170
+ self.git('add', 'a/old.js')
171
+ self.git('commit', '-qm', 'test: no-prefix rename base')
172
+ (self.root / 'b').mkdir()
173
+ self.git('mv', 'a/old.js', 'b/new.js')
174
+ diff.write_bytes(self.git('diff', '--cached', '-M', '--no-prefix'))
175
+ self.assertEqual(dep.diff_files(self.root, diff), ['a/old.js', 'b/new.js'])
176
+ for data in (b'', b'garbage', b'diff --git a/x b/x\n@@ invalid'):
177
+ diff.write_bytes(data)
178
+ with self.assertRaises((ValueError, subprocess.CalledProcessError)):
179
+ dep.diff_files(self.root, diff)
180
+
181
+
182
+ if __name__ == '__main__':
183
+ unittest.main()
@@ -14,6 +14,15 @@
14
14
  **Key Responsibilities:** {{responsibilities}}
15
15
  **Integration Points:** {{integration_summary}}
16
16
 
17
+ ## Static Dependency Map
18
+
19
+ Graph: {{dependency_graph_path}} — commit {{dependency_graph_commit}}.
20
+ Stable baseline: {{dependency_graph_baseline_commit}}.
21
+ Obsidian notes: {{dependency_notes_path}}.
22
+ Use the external-cache convention in shared docs; keep absolute local paths out of Git.
23
+ Coverage, unresolved imports and cache metrics: {{dependency_map_status}}.
24
+ Manual visual acceptance in Obsidian: {{dependency_visual_acceptance}}.
25
+
17
26
  ## Complete File Inventory
18
27
 
19
28
  {{#each files_in_inventory}}
@@ -164,6 +164,15 @@ This documentation was generated specifically to enable AI agents to understand
164
164
  **Deployment changes:**
165
165
  {{#if has_deployment_guide}}→ Reference: `deployment-guide.md`{{else}}→ Review CI/CD configs in project{{/if}}
166
166
 
167
+ ## Static Dependency Map
168
+
169
+ {{dependency_graph_path}} — commit {{dependency_graph_commit}}.
170
+ Stable baseline: {{dependency_graph_baseline_commit}}.
171
+ Obsidian notes: {{dependency_notes_path}}.
172
+ Use the external-cache convention in shared docs; keep absolute local paths out of Git.
173
+ Coverage, unresolved imports and cache metrics: {{dependency_map_status}}.
174
+ Manual visual acceptance in Obsidian: {{dependency_visual_acceptance}}.
175
+
167
176
  ---
168
177
 
169
178
  _Documentation generated by WIZZ Method `document-project` workflow_
@@ -82,6 +82,10 @@ This will read EVERY file in this area. Proceed? [y/n]
82
82
  </step>
83
83
 
84
84
  <step n="13b" goal="Comprehensive exhaustive scan of target area">
85
+ <check if="workflow_mode == deep_dive">
86
+ <action>Read ../references/static-dependencies.md and run {skill-root}/scripts/dependencies.py scan and export for {project-root}, using the canonical external cache (omit --output) and supplying a trusted external --cruiser or WIZZ_DEPCRUISE and explicit applicable --tsconfig. Never execute a path from graph.json. If installation is needed, ask before downloading packages. Generate imports without model calls; use graph.json to locate dependencies before selecting files. Cite graph.json and obsidian/ in the deep-dive artifact, including commit, stable baseline, cache metrics and unresolved-import limitations; use the external-cache convention rather than absolute local paths in shared docs. If tooling is unavailable or the language unsupported, record that limitation and continue; Python/Swift support and manual Obsidian visual acceptance are not established.</action>
87
+ <action>The unchanged second graph run must report source_hash_reads=0 and modules_parsed=0. This cache only covers dependency extraction: the exhaustive semantic review below still reads the selected source files literally.</action>
88
+ </check>
85
89
  <action>Set scan_mode = "exhaustive"</action>
86
90
  <action>Initialize file_inventory = []</action>
87
91
  <critical>You must read every line of every file in scope and capture a plain-language explanation (what the file does, side effects, why it matters) that future developer agents can act on. No shortcuts.</critical>
@@ -94,7 +94,7 @@ Your choice [1/2/3]:
94
94
  - Pattern-based analysis without reading source files
95
95
  - Scans: Config files, package manifests, directory structure
96
96
  - Best for: Quick project overview, initial understanding
97
- - File reading: Minimal (configs, README, package.json, etc.)
97
+ - Agent file reading: Minimal (configs, README, package.json, etc.). The deterministic dependency scanner reads JS/TS source separately to produce the import map.
98
98
 
99
99
  **2. Deep Scan** (10-30 minutes)
100
100
 
@@ -286,6 +286,12 @@ Are there any other important documents or key areas I should focus on while ana
286
286
 
287
287
  <step n="4" goal="Perform conditional analysis based on project type requirements" if="workflow_mode != deep_dive">
288
288
 
289
+ <check if="(workflow_mode == initial_scan OR workflow_mode == full_rescan) AND (scan_level == deep OR scan_level == exhaustive)">
290
+ <action>Read ../references/static-dependencies.md. Run {skill-root}/scripts/dependencies.py scan and export against {project-root}, with an explicit installed dependency-cruiser executable and the project's tsconfig when applicable. Omit --output to use the canonical external cache ~/.cache/wizz/dependencies/&lt;checkout-sha256-prefix&gt;; never put generated paths inside project knowledge or Git. Supply --cruiser from a trusted external installation or WIZZ_DEPCRUISE; never use the value in graph.json. If installation is needed, ask before downloading packages. Do not call a model to extract imports or infer unresolved edges.</action>
291
+ <action>Read the resulting graph.json and record its external-cache convention, commit, stable baseline, source coverage, unresolved imports and cache metrics. Cite graph.json and obsidian/ in the generated index and architecture documentation. On an unchanged second run, require source_hash_reads=0 and modules_parsed=0; configuration/cache metadata may still be read.</action>
292
+ <action>If prerequisites or supported JS/TS sources are unavailable, record the map as unavailable with the concrete reason; do not claim Python/Swift support or fabricate a graph. Continue the existing documentation workflow with that limitation. Export validation does not complete the pending manual Obsidian visual acceptance.</action>
293
+ </check>
294
+
289
295
  <critical>BATCHING STRATEGY FOR DEEP/EXHAUSTIVE SCANS</critical>
290
296
 
291
297
  <check if="scan_level == deep OR scan_level == exhaustive">
@@ -83,6 +83,25 @@ review_critical_paths = [
83
83
  "secrets, keys and .env files",
84
84
  "cryptography",
85
85
  "CI and deploy workflows",
86
+ "deletion of user or production data",
87
+ "external sends and publishing (e-mail, webhooks, posts, public pages)",
88
+ "personal data (PII) and user data handling",
89
+ "infrastructure and DNS",
90
+ "new or replaced dependencies",
91
+ ]
92
+
93
+ # Fixed floor for the `critical` tier: git pathspec globs. Any changed file
94
+ # matching one forces `critical` before the session judges anything; the
95
+ # session's judgment can only raise from there, never lower. Model
96
+ # confidence never lowers it either. See references/review-economy.md § 2.
97
+
98
+ review_critical_globs = [
99
+ "**/migrations/**",
100
+ "**/*.sql",
101
+ ".github/workflows/**",
102
+ "**/.env*",
103
+ "**/middleware.*",
104
+ "**/Dockerfile*",
86
105
  ]
87
106
 
88
107
  # Review layers for the review step. `instruction` is the layer's whole
@@ -37,7 +37,11 @@ The session reads the file list (`git diff --name-status`) and, when unsure, the
37
37
 
38
38
  **Provided or pasted diffs, and no VCS.** There is no `git --numstat` to read. Count `files` from the diff's own `diff --git` (or, lacking that, `+++`) headers, and `changed_lines` from the `+`/`-` body lines (the `+++`/`---` header lines themselves don't count). The table above applies without exception in both workflows — `small` included.
39
39
 
40
- `{workflow.review_critical_paths}` is a plain-language list of sensitive areas, not a pattern list. Default: authentication, sessions, passwords and credentials, authorization, permissions and RLS, payments, billing and checkout, database migrations, schema and SQL, secrets, keys and `.env` files, cryptography, CI and deploy workflows. Judge by meaning, not by substring: a security check added inside an otherwise generic file counts as `critical`; a document that merely talks about auth does not.
40
+ **Fixed floor first.** Before any judgment, match the changed file list against `{workflow.review_critical_globs}` (git pathspec globs; default: `**/migrations/**`, `**/*.sql`, `.github/workflows/**`, `**/.env*`, `**/middleware.*`, `**/Dockerfile*`). Any match sets the tier to `critical`, and nothing below can lower it. CI files sit on the floor because a change there can weaken the very checks that review relies on.
41
+
42
+ `{workflow.review_critical_paths}` is a plain-language list of sensitive areas, not a pattern list. Default: authentication, sessions, passwords and credentials, authorization, permissions and RLS, payments, billing and checkout, database migrations, schema and SQL, secrets, keys and `.env` files, cryptography, CI and deploy workflows, deletion of user or production data, external sends and publishing (e-mail, webhooks, posts, public pages), personal data (PII) and user data handling, infrastructure and DNS, new or replaced dependencies. Judge by meaning, not by substring: a security check added inside an otherwise generic file counts as `critical`; a document that merely talks about auth does not.
43
+
44
+ **Model confidence does not count.** A confidence the executor or a reviewer states about its own output never lowers the tier, never drops a layer, and never skips review: self-reported LLM confidence is poorly calibrated and runs high.
41
45
 
42
46
  Raise the tier when in doubt, never lower it — a change that clearly touches something the list above doesn't name (e.g. a data migration done in application code) still earns `critical`. State the tier, the reason and the resulting layer count to the user before launching any layer.
43
47
 
@@ -15,7 +15,7 @@ story_key: '' # set at runtime when discovered from sprint status
15
15
 
16
16
  - YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
17
17
  - The prompt that triggered this workflow IS the intent — not a hint.
18
- - Writing `{diff_file}`, `{clean_diff_file}` and the claims file is the only change this step may make. Otherwise it is read-only.
18
+ - Before CHECKPOINT, writing `{diff_file}`, `{clean_diff_file}` and the claims file is the only change this step may make. After confirmation, the optional static-impact refresh may update only its external cache and temporary metrics.
19
19
 
20
20
  ## INSTRUCTIONS
21
21
 
@@ -79,7 +79,7 @@ story_key: '' # set at runtime when discovered from sprint status
79
79
  - **Otherwise:** build `{clean_diff_file}` per § 1 — the identical git command that wrote `{diff_file}` for the chosen source, plus one `:(top,exclude,glob)<pattern>` pathspec per entry in `{workflow.review_exclude}`, appended after the positive pathspecs. For **uncommitted changes**, also append untracked files to `{clean_diff_file}` the same way they were appended to `{diff_file}`, but listed with the positive-plus-exclude pathspecs from § 1 so any excluded file is skipped. Get `files` and `changed_lines` from `git -C "$TOP" diff --no-ext-diff --no-color --no-textconv --numstat` with the same source args and pathspecs, plus per-file `git -C "$TOP" diff --no-index --numstat -- /dev/null <path>` for the untracked ones (§ 1).
80
80
  - If a git command in this instruction fails: delete any partially written `{clean_diff_file}` temp file, then set `{clean_diff_file}` = `{diff_file}` and `{tier}` = `critical`, say so, and continue.
81
81
  - If nothing is left after exclusions (`{clean_diff_file}` is empty, untracked files included, even though `{diff_file}` is non-empty): HALT, list the exclusions, and ask the user whether to review the raw diff anyway. If they do, set `{clean_diff_file}` = `{diff_file}` and pick the tier per § 2 from the raw diff. If they decline, delete `{diff_file}` (when `{diff_file_owned}` is true) and `{clean_diff_file}` per § 5 and end the workflow.
82
- - Otherwise, pick the tier per § 2: read `git diff --name-status` for the same source (and the relevant hunks when unsure) and judge it against `{workflow.review_critical_paths}`.
82
+ - Otherwise, pick the tier per § 2: read `git diff --name-status` for the same source; if any changed path matches `{workflow.review_critical_globs}`, the tier is `critical` (fixed floor). Otherwise read the relevant hunks when unsure and judge it against `{workflow.review_critical_paths}`.
83
83
  - State the tier, the reason, and the resulting Forte subagent count to the user before launching any layer.
84
84
 
85
85
  5. **Stage the claims file.** Collect the change's own narrative: for a branch diff or commit range, the commit messages it covers (`git log <base>..<head>`); for other sources, whatever description of the change the user or conversation supplied. Write it verbatim to a uniquely-named file in the system temp directory and set `{claims_file}` to its path. If there is no narrative, set `{claims_file}` = `''`. Do not analyze or summarize the narrative — it is input for one review layer, staged as a file precisely so the other layers never see it.
@@ -103,6 +103,31 @@ story_key: '' # set at runtime when discovered from sprint status
103
103
 
104
104
  Present a summary before proceeding: diff stats (files changed, lines added/removed), the excluded files — the file paths present in `{diff_file}` but absent from `{clean_diff_file}`, each with the exclusion pattern that matched it — `{tier}` and how many Forte review subagents that tier will launch, `{review_mode}`, and loaded spec/context docs (if any). HALT and wait for user confirmation to proceed. If the user declines, delete `{diff_file}` (when `{diff_file_owned}` is true), `{clean_diff_file}`, and `{claims_file}` (when non-empty) per `references/review-economy.md` § 5, and end the workflow.
105
105
 
106
+ ### Static dependency impact — only after the confirmed CHECKPOINT
107
+
108
+ Locate the installed `wizz-document-project` skill. Its canonical cache is
109
+ `~/.cache/wizz/dependencies/<checkout-sha256-prefix>`: omit `--output` for both
110
+ commands so the script computes the same directory from the resolved checkout
111
+ root. Check for that cache without executing anything recorded inside it.
112
+
113
+ After the checkpoint is confirmed, run `dependencies.py --root <root> refresh`
114
+ with a trusted absolute external `--cruiser` (or `WIZZ_DEPCRUISE`) and explicit
115
+ `--tsconfig` when applicable, then `dependencies.py --root <root> impact --diff
116
+ {diff_file}`. Use the full selected diff, including manifests and lockfiles;
117
+ `{clean_diff_file}` remains the review layers' input. For a supplied diff made
118
+ with Git `--relative`, supply its known `--diff-relative <directory>` or report
119
+ impact unavailable until its path base is known. Never guess the executable from
120
+ `graph.json` or download/install a scanner implicitly. Keep refresh metrics in a
121
+ temp file; read only touched/direct/transitive output for review context.
122
+
123
+ Name the graph's commit and stable baseline, unresolved-import limits and the
124
+ conservative union of all observed edges since that baseline. Multiple refreshes
125
+ preserve removed edges; this can over-report impact. A cache created after the
126
+ diff cannot recover historical edges it never observed. Without a trusted tool,
127
+ valid cache or valid diff, state “static impact unavailable” and continue review.
128
+ An empty list is never proof of no runtime impact. No additional review layer or
129
+ model import extraction is introduced.
130
+
106
131
  ## NEXT
107
132
 
108
133
  Read fully and follow `./step-02-review.md`
@@ -14,6 +14,8 @@ layer_status: '' # set at runtime: every configured layer's id marked active or
14
14
 
15
15
  ## INSTRUCTIONS
16
16
 
17
+ 0. **Subagent guard.** Before anything else, check that you can launch subagents (in Claude Code, the `Agent` tool is in your tool list). If you can't because you are yourself running inside a subagent (a `wizz-exec-*` or any delegated agent), delete `{diff_file}`, `{clean_diff_file}` and any findings temp files per `references/review-economy.md` § 5, then HALT with status `blocked` and blocking condition `rodando dentro de subagente: wizz-code-review roda na sessão principal`. Return no verdict and no findings, and never fall back to reading the diff yourself in a single pass. The separate-session fallback in instruction 3 is only for a main session on a platform without subagents.
18
+
17
19
  1. The review layers are `{workflow.review_layers}`, resolved during activation. If `{tier}` is empty or not one of `small`/`normal`/`critical`, set it to `critical` now and tell the user why, per `references/review-economy.md` § 2.
18
20
 
19
21
  2. For each layer in `{workflow.review_layers}`, record the outcome in `{layer_status}` as you go:
@@ -492,7 +492,7 @@ Activation is complete. If `activation_steps_prepend` or `activation_steps_appen
492
492
  - Review the implemented story and test the changes
493
493
  - Verify all acceptance criteria are met
494
494
  - Ensure deployment readiness if applicable
495
- - Run `code-review` workflow for peer review
495
+ - Run `wizz-code-review` for peer review, in the main session (never delegated to a subagent, and never by the executor that implemented the story)
496
496
  - Optional: If Test Architect module installed, run the Test Architect `automate` workflow to expand guardrail tests
497
497
  </action>
498
498
 
@@ -83,6 +83,25 @@ review_critical_paths = [
83
83
  "secrets, keys and .env files",
84
84
  "cryptography",
85
85
  "CI and deploy workflows",
86
+ "deletion of user or production data",
87
+ "external sends and publishing (e-mail, webhooks, posts, public pages)",
88
+ "personal data (PII) and user data handling",
89
+ "infrastructure and DNS",
90
+ "new or replaced dependencies",
91
+ ]
92
+
93
+ # Fixed floor for the `critical` tier: git pathspec globs. Any changed file
94
+ # matching one forces `critical` before the session judges anything; the
95
+ # session's judgment can only raise from there, never lower. Model
96
+ # confidence never lowers it either. See references/review-economy.md § 2.
97
+
98
+ review_critical_globs = [
99
+ "**/migrations/**",
100
+ "**/*.sql",
101
+ ".github/workflows/**",
102
+ "**/.env*",
103
+ "**/middleware.*",
104
+ "**/Dockerfile*",
86
105
  ]
87
106
 
88
107
  # Review layers for the review step. `instruction` is the layer's whole
@@ -37,7 +37,11 @@ The session reads the file list (`git diff --name-status`) and, when unsure, the
37
37
 
38
38
  **Provided or pasted diffs, and no VCS.** There is no `git --numstat` to read. Count `files` from the diff's own `diff --git` (or, lacking that, `+++`) headers, and `changed_lines` from the `+`/`-` body lines (the `+++`/`---` header lines themselves don't count). The table above applies without exception in both workflows — `small` included.
39
39
 
40
- `{workflow.review_critical_paths}` is a plain-language list of sensitive areas, not a pattern list. Default: authentication, sessions, passwords and credentials, authorization, permissions and RLS, payments, billing and checkout, database migrations, schema and SQL, secrets, keys and `.env` files, cryptography, CI and deploy workflows. Judge by meaning, not by substring: a security check added inside an otherwise generic file counts as `critical`; a document that merely talks about auth does not.
40
+ **Fixed floor first.** Before any judgment, match the changed file list against `{workflow.review_critical_globs}` (git pathspec globs; default: `**/migrations/**`, `**/*.sql`, `.github/workflows/**`, `**/.env*`, `**/middleware.*`, `**/Dockerfile*`). Any match sets the tier to `critical`, and nothing below can lower it. CI files sit on the floor because a change there can weaken the very checks that review relies on.
41
+
42
+ `{workflow.review_critical_paths}` is a plain-language list of sensitive areas, not a pattern list. Default: authentication, sessions, passwords and credentials, authorization, permissions and RLS, payments, billing and checkout, database migrations, schema and SQL, secrets, keys and `.env` files, cryptography, CI and deploy workflows, deletion of user or production data, external sends and publishing (e-mail, webhooks, posts, public pages), personal data (PII) and user data handling, infrastructure and DNS, new or replaced dependencies. Judge by meaning, not by substring: a security check added inside an otherwise generic file counts as `critical`; a document that merely talks about auth does not.
43
+
44
+ **Model confidence does not count.** A confidence the executor or a reviewer states about its own output never lowers the tier, never drops a layer, and never skips review: self-reported LLM confidence is poorly calibrated and runs high.
41
45
 
42
46
  Raise the tier when in doubt, never lower it — a change that clearly touches something the list above doesn't name (e.g. a data migration done in application code) still earns `critical`. State the tier, the reason and the resulting layer count to the user before launching any layer.
43
47
 
@@ -38,7 +38,7 @@ Compute the spec's repo-relative path first — `git -C "$TOP" ls-files --full-n
38
38
  - If a git command in this section fails: set `{clean_diff_file}` = `{diff_file}` and `{tier}` = `critical`, say so, and continue.
39
39
  - If `{clean_diff_file}` is empty (untracked files included) and excluding only the spec pathspec — with none of the `review_exclude` globs applied — already empties it too: the spec's own edit was the only change. There is nothing to review: skip the Review section entirely (no layer launches, `{failed_layers}` stays empty) and go straight to Classify, which finds nothing against the empty diff. Do not fall back to the raw diff in this case.
40
40
  - Else if nothing is left after exclusions for any other reason (a `review_exclude` glob, not the spec exclude, accounts for the emptiness): review the raw diff per § 1 — `{clean_diff_file}` = `{diff_file}`, tier picked from the raw diff.
41
- - Otherwise, pick the tier per § 2 (`small` included, on equal footing with `normal`/`critical`) by reading `git -C "$TOP" diff --name-status {baseline_commit} -- ':(top)' ':(top,exclude,literal)<repo-relative spec path>'` and judging it against `{workflow.review_critical_paths}`.
41
+ - Otherwise, pick the tier per § 2 (`small` included, on equal footing with `normal`/`critical`) by reading `git -C "$TOP" diff --name-status {baseline_commit} -- ':(top)' ':(top,exclude,literal)<repo-relative spec path>'`: any changed path matching `{workflow.review_critical_globs}` makes it `critical` (fixed floor); otherwise judge it against `{workflow.review_critical_paths}`.
42
42
 
43
43
  Announce the exclusions once, from this run only. Tell the user the tier, the exclusions, and the resulting Forte subagent count before launching any layer.
44
44
 
@@ -0,0 +1,45 @@
1
+ # Approval boundary unavailable: paid and publishing execution blocked
2
+
3
+ The local `approval-gate.mjs` currently supports **estimates only**. Both `approve` and `run` fail closed, including with a matching receipt, a TTY or an imported function. There is no exported approval function. Local files, digests, TTYs and secrets accessible to the agent cannot authenticate human consent. Removing one command from an allow list does not establish that boundary.
4
+
5
+ The source is [approval-gate.mjs](approval-gate.mjs). After installation, find it at `_wizz/wizz/_shared/approval-gate.mjs` relative to the project root. This implementation does **not** meet the requirement for a protected human approval command and must not be used to execute paid or publishing actions. Do not bypass the blocked wrapper by calling the underlying tool. A human may independently perform an action outside the agent workflow; this does not enable the wrapper.
6
+
7
+ ## Prepare and inspect an estimate
8
+
9
+ Scope: a **paid action** incurs a provider charge or consumes a metered paid quota, including a paid quote/estimator. **Publishing** makes the reviewed deliverable available to an audience or updates its externally hosted state: deployment/release, posting/scheduling content, sending a campaign, uploading a public asset, or pushing deliverable commits/tags to a remote repository. Local edits, local commits, tests, builds/previews, free read-only queries, and internal coordinator status/report messages are outside this publishing scope unless they themselves trigger a charge or publication. Task authorization does not enable the blocked executor.
10
+
11
+ Requests here describe local CLI actions. MCP actions have tool names and structured arguments rather than an absolute `argv[0]`; they cannot be represented or executed by this wrapper. Show their cost/scope as a blocked proposal and stop pending a protected host adapter. Never invent an executable to make an MCP action fit or call the underlying MCP/tool directly to bypass the gate.
12
+
13
+ 1. Write a new request per action with `action`, `kind` (`paid` or `publish`), absolute existing `cwd`, `argv` (absolute executable plus string arguments), `env` (explicit string values, `{}` for none), `artifacts` (nonempty list of reviewed input files relative to cwd), `expiresAt` (future ISO date, at most 24 hours away), and `estimate` with numeric `amount`, ISO `currency`, `basis` (units × price, scope, retries and uncertainty), and `source` (quote source and date). Unknown cost blocks the workflow: obtain a free quote first. A free publication may have amount zero; a paid action must be positive. Never call a paid estimator without its own protected approval.
14
+ 2. Run `node _wizz/wizz/_shared/approval-gate.mjs estimate <request.json>` from the project root. Show the estimate and its blocked status to the human. This reads local files and makes no provider call; supplied prices are never fetched or invented. Output lists environment names with `[REDACTED]` values; the actual values still affect the fingerprint. Keep secrets out of argv, action text, estimate text and paths, which are displayed. Malformed JSON errors do not echo request contents.
15
+ 3. Stop. `approve` and `run` discard any legacy receipt and return an error without executing the requested command. Even malformed, expired, changed or missing requests cannot preserve a receipt for later replay. No reapproval flow is available yet.
16
+
17
+ Example preparation (replace executable, files, date and quote with actual values):
18
+
19
+ ```json
20
+ {
21
+ "action": "Prepare the approved batch estimate",
22
+ "kind": "paid",
23
+ "cwd": "/absolute/project",
24
+ "argv": ["/absolute/node", "scripts/generate.mjs", "brief.json"],
25
+ "env": {},
26
+ "artifacts": ["brief.json", "scripts/generate.mjs"],
27
+ "expiresAt": "2026-10-31T12:00:00Z",
28
+ "estimate": {
29
+ "amount": 2.5,
30
+ "currency": "USD",
31
+ "basis": "10 units × USD 0.25; one attempt, excluding tax",
32
+ "source": "Provider quote, retrieved 2026-10-30"
33
+ }
34
+ }
35
+ ```
36
+
37
+ The estimate fingerprint includes the request (including explicit environment), executable bytes, reviewed artifact bytes and existing file arguments (including `--option=file`), with resolved symlink targets. Changing these invalidates the fingerprint. Ambient `PATH` or `NODE_OPTIONS` does not authorize anything. The fingerprint is public and is not an approval credential; it does not capture imported dependencies, directory contents, remote state or eliminate time-of-check/time-of-use races.
38
+
39
+ Artifacts use strict relative paths: no absolute/drive paths, backslashes, empty segments, `.` or `..`; resolved artifact targets must remain inside the real cwd. Missing/nonregular files fail with a labeled error. Preparation reads at most 1 MiB of request JSON, 64 MiB per executable/artifact/argument file and 128 MiB total for those files. Oversized inputs fail before hashing; these bounds and fingerprints do not create an approval boundary.
40
+
41
+ ## Required before enabling execution
42
+
43
+ Implement and test a host-owned consent boundary outside the agent's control, bound to the exact action, cost, environment, executable and reviewed input snapshot. It must issue and atomically consume single-use authorization, reject changes and expiry, and handle failures and uncertain provider outcomes without automatic retries. The execution path must use the reviewed snapshot and explicit environment. No shared secret in this repository, prompt, process environment or agent-readable file supplies that boundary. No protected host API is integrated here, so execution remains blocked.
44
+
45
+ Keep preparation files under the project-local ignored `_wizz-output/approval-gate/` directory (verify that the project ignores it). Do not store credentials in requests. Estimates and overall project consent do not approve an action, and an estimate does not enforce a provider spending cap.