hecks 3.2.0 → 3.3.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/bluebook/assembly/contracts.rb +2 -1
  3. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  4. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
  5. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -3
  6. data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
  7. data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
  8. data/lib/hecks/bluebook/query.rb +8 -5
  9. data/lib/hecks/cli/check_era.rb +3 -39
  10. data/lib/hecks/cli/interview_draft.rb +86 -31
  11. data/lib/hecks/cli/interview_session.rb +77 -26
  12. data/lib/hecks/cli/interview_shape.rb +179 -0
  13. data/lib/hecks/doors/cli_runner.rb +14 -1
  14. data/lib/hecks/doors/launcher_options.rb +13 -3
  15. data/lib/hecks/doors/mcp_door.rb +16 -2
  16. data/lib/hecks/doors/mcp_door_scope.rb +45 -5
  17. data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
  18. data/lib/hecks/doors.rb +1 -1
  19. data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
  20. data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
  21. data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
  22. data/lib/hecks/embryonaut_bluebook.rb +2 -0
  23. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
  24. data/lib/hecks/fuzzing/qa_settings.rb +15 -34
  25. data/lib/hecks/gate/stages.yml +113 -8
  26. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
  27. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
  28. data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
  29. data/lib/hecks/hecks/adapters/git.rb +59 -0
  30. data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
  31. data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
  32. data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
  33. data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
  34. data/lib/hecks/hecks/adapters/site_toolchain.rb +88 -11
  35. data/lib/hecks/hecks/codebase.behaviors +6 -0
  36. data/lib/hecks/hecks/codebase.bluebook +21 -0
  37. data/lib/hecks/hecks/custodian.behaviors +6 -0
  38. data/lib/hecks/hecks/custodian.bluebook +217 -0
  39. data/lib/hecks/hecks/hecks.hecksagon +24 -0
  40. data/lib/hecks/hecks/hecks.world +8 -3
  41. data/lib/hecks/hecks/site.world +5 -2
  42. data/lib/hecks/language/bluebook/query.bluebook +25 -0
  43. data/lib/hecks/language/bluebook/vocabulary.bluebook +220 -7
  44. data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
  45. data/lib/hecks/language/oidc.json +5 -0
  46. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
  47. data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
  48. data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
  49. data/lib/hecks/projections/deploy/box/settings.rb +40 -6
  50. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +2 -0
  51. data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
  52. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
  53. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
  54. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
  55. data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
  56. data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
  57. data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
  58. data/lib/hecks/projections/deploy/box.rb +28 -4
  59. data/lib/hecks/projections/deploy/fargate.rb +3 -3
  60. data/lib/hecks/projections/deploy/lambda.rb +8 -8
  61. data/lib/hecks/projections/model/deviations.rb +1 -1
  62. data/lib/hecks/projections/site/edge/behaviours.rb +5 -3
  63. data/lib/hecks/projections/site/edge/checks.rb +31 -5
  64. data/lib/hecks/projections/site/edge.rb +25 -11
  65. data/lib/hecks/projections/site/live_edge.rb +162 -0
  66. data/lib/hecks/projections/site/routes_ts.rb +50 -27
  67. data/lib/hecks/projections/site/site_cdn.rb +7 -4
  68. data/lib/hecks/projections/site/table/checks.rb +53 -7
  69. data/lib/hecks/projections/site/table/link_builder.rb +64 -0
  70. data/lib/hecks/projections/site/table/row_builder.rb +21 -6
  71. data/lib/hecks/projections/site/table.rb +16 -5
  72. data/lib/hecks/quality_control/adapters/git_pr.rb +0 -14
  73. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
  74. data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
  75. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
  76. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
  77. data/lib/hecks/quality_control/quality_control.bluebook +76 -124
  78. data/lib/hecks/runtime/interpreting.rb +3 -22
  79. data/lib/hecks/runtime/needs.rb +48 -0
  80. data/lib/hecks/runtime/query_interpreter.rb +3 -0
  81. data/lib/hecks/runtime/registry/verification.rb +3 -3
  82. data/lib/hecks/rust_build/host.rb +147 -0
  83. data/lib/hecks/rust_build/kernel_input.rb +51 -17
  84. data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
  85. data/lib/hecks/rust_build/project_rust.rb +7 -140
  86. data/lib/hecks/rust_build.rb +1 -0
  87. data/lib/hecks/site/bluebook/site.bluebook +80 -14
  88. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  89. data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
  90. data/lib/hecks/storehouse.rb +35 -1
  91. data/lib/hecks/tools/comment_style.rb +4 -2
  92. data/lib/hecks/tools/gate.rb +1 -1
  93. data/lib/hecks/tools/rust_comment_style.rb +2 -4
  94. data/lib/hecks/tools/site_routes.rb +69 -28
  95. data/lib/hecks/tools/tools_doc.rb +204 -0
  96. data/lib/hecks/tools.rb +1 -0
  97. data/lib/hecks/version.rb +1 -1
  98. data/lib/hecks/vocabulary.rb +152 -1
  99. data/lib/hecks.rb +1 -0
  100. data/qa/settings.yml +1 -2
  101. data/rust/build/src/lineage_pass.rs +31 -16
  102. data/rust/build/src/main.rs +4 -2
  103. data/rust/build/src/pipeline.rs +9 -20
  104. data/rust/codegen/src/attr.rs +1 -1
  105. data/rust/codegen/src/bridging.rs +8 -8
  106. data/rust/codegen/src/commands.rs +1 -1
  107. data/rust/codegen/src/constraints.rs +1 -1
  108. data/rust/codegen/src/dependency_planning.rs +1 -1
  109. data/rust/codegen/src/domain_generator.rs +2 -2
  110. data/rust/codegen/src/exemplar.rs +1 -1
  111. data/rust/codegen/src/expr_emitter.rs +1 -1
  112. data/rust/codegen/src/fielded.rs +1 -1
  113. data/rust/codegen/src/json.rs +214 -23
  114. data/rust/codegen/src/json_codec.rs +1 -1
  115. data/rust/codegen/src/literal.rs +6 -3
  116. data/rust/codegen/src/main.rs +23 -13
  117. data/rust/codegen/src/mutations.rs +1 -1
  118. data/rust/codegen/src/naming.rs +5 -3
  119. data/rust/codegen/src/optional_pass.rs +226 -0
  120. data/rust/codegen/src/ports.rs +1 -1
  121. data/rust/codegen/src/queries.rs +3 -3
  122. data/rust/codegen/src/reactions.rs +1 -1
  123. data/rust/codegen/src/read_models.rs +2 -2
  124. data/rust/codegen/src/reference_specs.rs +1 -1
  125. data/rust/codegen/src/registry.rs +4 -4
  126. data/rust/codegen/src/shared.rs +1 -1
  127. data/rust/{build → codegen}/src/sidecars.rs +9 -5
  128. data/rust/codegen/src/skip_reason.rs +1 -1
  129. data/rust/codegen/src/types.rs +4 -4
  130. data/rust/host/HECKS_RELEASE +1 -1
  131. data/rust/host/src/api.rs +1 -1
  132. data/rust/host/src/dispatch.rs +5 -0
  133. data/rust/host/src/needs.rs +193 -29
  134. data/rust/host/src/resend.rs +4 -4
  135. data/rust/host/src/ui_schema.rs +1 -1
  136. data/rust/parser/src/emit.rs +12 -0
  137. data/rust/parser/src/ir.rs +2 -0
  138. data/rust/parser/src/keywords.rs +2 -0
  139. data/rust/parser/src/main.rs +1 -0
  140. data/rust/parser/src/parse/command.rs +2 -44
  141. data/rust/parser/src/parse/mod.rs +1 -0
  142. data/rust/parser/src/parse/needs.rs +60 -0
  143. data/rust/parser/src/parse/query.rs +3 -0
  144. data/rust/src/exemplar/commands.rs +1 -1
  145. data/rust/src/exemplar/constraints.rs +1 -1
  146. data/rust/src/exemplar/fielded.rs +1 -1
  147. data/rust/src/exemplar/json.rs +1 -1
  148. data/rust/src/exemplar/mod.rs +1 -1
  149. data/rust/src/exemplar/mutations.rs +1 -1
  150. data/rust/src/exemplar/queries.rs +1 -1
  151. data/rust/src/exemplar/reactions.rs +1 -1
  152. data/rust/src/exemplar/read_models.rs +1 -1
  153. data/rust/src/exemplar/registry.rs +1 -1
  154. data/rust/src/exemplar/types.rs +1 -1
  155. data/rust/src/kernel/cli.rs +18 -8
  156. data/rust/src/kernel/needs.rs +104 -9
  157. data/rust/src/kernel/read_model.rs +1 -1
  158. data/rust/src/lib.rs +1 -1
  159. metadata +24 -30
  160. data/lib/hecks/rust_build/append_optionals.rb +0 -79
  161. data/lib/hecks/rust_build/rust_literal.rb +0 -33
  162. data/lib/hecks/three_zero/forms.yml +0 -127
  163. data/lib/hecks/three_zero.rb +0 -12
  164. data/rust/build/src/json.rs +0 -421
  165. data/rust/build/src/optional_pass.rs +0 -150
  166. data/rust/project/bridging.rb +0 -257
  167. data/rust/project/commands.rb +0 -877
  168. data/rust/project/constraints.rb +0 -86
  169. data/rust/project/dependency_planning.rb +0 -178
  170. data/rust/project/domain_generator.rb +0 -983
  171. data/rust/project/exemplar.rb +0 -148
  172. data/rust/project/expr_emitter.rb +0 -72
  173. data/rust/project/fielded.rb +0 -141
  174. data/rust/project/json_codec.rb +0 -641
  175. data/rust/project/mutations.rb +0 -695
  176. data/rust/project/naming.rb +0 -223
  177. data/rust/project/ports.rb +0 -65
  178. data/rust/project/queries.rb +0 -493
  179. data/rust/project/reactions.rb +0 -317
  180. data/rust/project/read_models.rb +0 -690
  181. data/rust/project/reference_specs.rb +0 -27
  182. data/rust/project/registry.rb +0 -396
  183. data/rust/project/skip_reason.rb +0 -29
  184. data/rust/project/types.rb +0 -225
  185. data/rust/project/write_if_changed.rb +0 -7
  186. data/rust/project.rb +0 -30
  187. data/rust/project_rust_pipeline.rb +0 -391
@@ -30,7 +30,7 @@ module Hecks
30
30
  DOC_LINE = %r{\A\s*(?:///(?!/)|//!)}
31
31
 
32
32
  # `// TMPL:name BEGIN`/`END` sentinels are parsed verbatim by
33
- # `rust/project/exemplar.rb`; every prose rule skips them outright.
33
+ # `rust/codegen/src/exemplar.rs`; every prose rule skips them outright.
34
34
  TMPL_MARKER = /\bTMPL:\S+\s+(?:BEGIN|END)\b/i
35
35
  MOD_DOC_LINE = %r{\A\s*//!}
36
36
  ATTR_LINE = /\A\s*#\[/
@@ -45,9 +45,7 @@ module Hecks
45
45
  # Rust-specific acronyms beyond the Ruby linter's list.
46
46
  RUST_ACRONYMS = %w[EOF RAII RHS SQS DLQ JIT LSP NFA DFA CR].to_set.freeze
47
47
 
48
- RUST_PROPER_NOUNS = {
49
- "EMBRYONAUT" => "Embryonaut"
50
- }.freeze
48
+ RUST_PROPER_NOUNS = {}.freeze
51
49
 
52
50
  RUST_EXTRA_WORDS = %w[
53
51
  cardinality routability prepend safer diff diffed stats earliest unbracketed
@@ -14,41 +14,47 @@ module Hecks
14
14
  # it declares an edge, into the behaviours and listener rules of its template
15
15
  # (`Projections::Site::SiteCdn`).
16
16
  #
17
- # hecks site site_projection.project_site [<project>] [--out=<dir>] [--check]
17
+ # hecks site site_projection.project_site [<project>] [--out=<dir>] [--template=<file>]
18
+ # [--extension=ts|mts] [--check]
18
19
  #
19
- # `<project>` is the directory whose `bluebook/` holds the chapter that declares the rows and a
20
- # hecksagon that attaches Site; it defaults to the checkout. `routes.ts` is written to
21
- # `<project>/generated` unless `--out` names another directory. The template is rewritten in
22
- # place between its `BEGIN`/`END GENERATED site_cdn` markers, or under `--out` a copy of it is
23
- # written at the same relative path. With `--check` nothing is written: the tool answers 1 and
24
- # names each file that differs from the table.
20
+ # `<project>` (default: the working directory) holds `bluebook/` and the `vendor/` it attaches
21
+ # from. `routes.ts` goes to `<project>/generated` or to `--out`, anywhere. The template is the
22
+ # `Edge` row's, relative to the project, or `--template`, anywhere; it is rewritten in place
23
+ # between its `BEGIN`/`END GENERATED site_cdn` markers. With `--out` alone, `--out` receives a
24
+ # copy of it instead. `--extension=mts` names the module `routes.mts`; `--check` writes nothing.
25
25
  module SiteRoutes
26
26
  # Where the files go, relative to the project, when `--out` names no directory.
27
27
  DEFAULT_OUT = "generated"
28
28
 
29
- USAGE = "usage: hecks site site_projection.project_site [<project>] [--out=<dir>] [--check]"
29
+ # The extensions the module may take; the first is the default.
30
+ EXTENSIONS = %w[ts mts].freeze
31
+
32
+ USAGE = "usage: hecks site site_projection.project_site [<project>] [--out=<dir>] " \
33
+ "[--template=<file>] [--extension=#{EXTENSIONS.join('|')}] [--check]".freeze
30
34
 
31
35
  module_function
32
36
 
33
37
  # Projects the route table, writes what differs and prints a line for each file written.
34
38
  #
35
39
  # @param argv [Array<String>] the project directory, then the flags
36
- # @param root [String] the checkout, used as the project when `argv` names none
40
+ # @param root [String] the project used when `argv` names none; the working directory
37
41
  # @return [Integer] 0, or 1 when `--check` finds a file out of date
38
- # @raise [SystemExit] with the reason on stderr when the project declares no route table or
39
- # the table is refused
40
- def main(argv, root: Tools::ROOT)
42
+ # @raise [SystemExit] with the reason on stderr when the project declares no route table, the
43
+ # table is refused, or a flag is refused
44
+ def main(argv, root: Dir.pwd)
41
45
  argv = argv.dup
42
- out = nil
46
+ out = template = extension = nil
43
47
  check = false
44
48
  OptionParser.new do |parser|
45
49
  parser.on("--out=DIR") { |value| out = value }
50
+ parser.on("--template=FILE") { |value| template = value }
51
+ parser.on("--extension=EXT") { |value| extension = value }
46
52
  parser.on("--check") { check = true }
47
53
  end.parse!(argv)
48
54
  project = argv.empty? ? root : File.expand_path(argv.shift)
49
55
  abort USAGE unless argv.empty?
50
56
 
51
- files = projection(project, out: out)
57
+ files = projection(project, out: out, template: template, extension: extension)
52
58
  stale = files.reject { |path, text| File.file?(path) && File.read(path) == text }.keys
53
59
  return report(stale, project, check) if check
54
60
 
@@ -63,16 +69,26 @@ module Hecks
63
69
 
64
70
  # @param root [String] the project directory
65
71
  # @param out [String, nil] the directory to write to; `generated/` of the project when nil
72
+ # @param template [String, nil] the template to rewrite in place, wherever it lies; the
73
+ # `Edge` row's template, relative to the project, when nil
74
+ # @param extension [String, nil] `ts` (the default) or `mts`, the module's file extension
66
75
  # @return [Hash{String => String}] each file's absolute path to the text it should hold
67
- # @raise [SystemExit] when the project declares no route table or the table is refused
68
- def projection(root, out: nil)
76
+ # @raise [SystemExit] when the project declares no route table, the table is refused, or a
77
+ # path or the extension is refused
78
+ def projection(root, out: nil, template: nil, extension: nil)
79
+ extension ||= EXTENSIONS.first
80
+ unless EXTENSIONS.include?(extension)
81
+ abort "project_site: --extension is one of #{EXTENSIONS.join(', ')}, not #{extension.inspect}"
82
+ end
83
+ dir = out ? File.expand_path(out) : File.join(root, DEFAULT_OUT)
84
+ abort "project_site: --out #{dir} is a file; it names the directory routes.#{extension} goes in" if File.file?(dir)
85
+
69
86
  registry = registry_for(root)
70
87
  chapter = Projections::Site::Table.chapter(registry)
71
88
  table = Projections::Site::Table.read(chapter, registry: registry)
72
89
  files = Projector.call(:site_routes_ts, bluebook: chapter, options: { table: table })
73
- dir = out ? File.expand_path(out) : File.join(root, DEFAULT_OUT)
74
- written = files.to_h { |name, text| [File.join(dir, name), text] }
75
- written.merge(template_files(root, out, chapter, table, registry))
90
+ written = files.to_h { |name, text| [File.join(dir, name.sub(/\.ts\z/, ".#{extension}")), text] }
91
+ written.merge(template_files(root, out, chapter, table, registry, template: template))
76
92
  rescue Projections::Site::Table::Invalid => e
77
93
  abort "project_site: #{e.message}"
78
94
  end
@@ -84,25 +100,50 @@ module Hecks
84
100
  # @param chapter [Bluebook::Chapter] the chapter that declares the route table
85
101
  # @param table [Projections::Site::Table] the checked table
86
102
  # @param registry [Hecks::Runtime::Registry] the registry the project booted into
103
+ # @param template [String, nil] a template named on the command line, rewritten in place
87
104
  # @return [Hash{String => String}] the template's path to its text; empty with no edge
88
- # @raise [SystemExit] when the template is missing or lacks a region
89
- def template_files(root, out, chapter, table, registry)
90
- edge = Projections::Site::Edge.read(chapter, table: table, vocabulary: Projections::Site::Table.vocabulary(registry))
91
- return {} unless edge
105
+ # @raise [SystemExit] when a template is named and the project declares no edge, the
106
+ # template is missing or lacks a region it needs, or holds a region an edge without a load
107
+ # balancer does not use
108
+ def template_files(root, out, chapter, table, registry, template: nil)
109
+ edge = Projections::Site::Edge.read(chapter, table: table, template: template,
110
+ vocabulary: Projections::Site::Table.vocabulary(registry))
111
+ unless edge
112
+ abort "project_site: --template names #{template}, but the project declares no Edge rows" if template
113
+ return {}
114
+ end
92
115
 
93
116
  regions = Projector.call(:site_cdn, bluebook: chapter, options: { table: table, edge: edge })
94
- relative = edge.setting.template
95
- source = File.join(root, relative)
96
- abort "project_site: the template #{relative} does not exist in #{root}" unless File.file?(source)
117
+ relative = template || edge.setting.template
118
+ source = template ? File.expand_path(template) : File.join(root, relative)
119
+ unless File.file?(source)
120
+ abort "project_site: the template #{relative} does not exist#{" in #{root}" unless template}"
121
+ end
97
122
 
98
- text = regions.reduce(File.read(source)) do |current, (name, block)|
123
+ text = rewritten(File.read(source), regions, relative, edge)
124
+ in_place = template || out.nil?
125
+ { (in_place ? source : File.join(File.expand_path(out), relative)) => text }
126
+ end
127
+
128
+ # @param text [String] the template
129
+ # @param regions [Hash{String => String}] each region's name to the block that goes in it
130
+ # @param relative [String] the template's name, for a message
131
+ # @param edge [Projections::Site::Edge] the checked edge
132
+ # @return [String] the template with each region rewritten
133
+ # @raise [SystemExit] when a region is missing, or a listener_rules region is left behind
134
+ # by an edge with no load balancer
135
+ def rewritten(text, regions, relative, edge)
136
+ if !edge.alb? && Projections::Site::Regions.region?(text, "listener_rules")
137
+ abort "project_site: #{relative} has a BEGIN/END GENERATED site_cdn listener_rules region, " \
138
+ "and the Edge row says alb: false; remove the region"
139
+ end
140
+ regions.reduce(text) do |current, (name, block)|
99
141
  unless Projections::Site::Regions.region?(current, name)
100
142
  abort "project_site: #{relative} has no BEGIN/END GENERATED site_cdn #{name} region"
101
143
  end
102
144
 
103
145
  Projections::Site::Regions.replace(current, name, block)
104
146
  end
105
- { (out ? File.join(File.expand_path(out), relative) : source) => text }
106
147
  end
107
148
 
108
149
  # Loads the project's chapters the way a deploy does: the bluebooks, then the hecksagons that
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks"
4
+ require "hecks/vocabulary"
5
+ require_relative "../tools"
6
+
7
+ module Hecks
8
+ module Tools
9
+ # Writes the launcher forms of `docs/tools.md` from the `RetiredScript` rows of the Vocabulary
10
+ # chapter. Each row names a command (or, in QualityControl, a query); its form is rendered from
11
+ # that command's own arguments, the same projection the launcher parses against, so a form can
12
+ # neither keep an argument the command dropped nor miss one it gained.
13
+ #
14
+ # Only the tables between the `generated:begin tools` and `generated:end tools` comments are
15
+ # written; the prose around them is hand-written and left alone. With `--check` nothing is
16
+ # written: the tool answers 1 and names the sections that differ from the rows.
17
+ module ToolsDoc
18
+ # The document, relative to the checkout.
19
+ DOCUMENT = "docs/tools.md"
20
+
21
+ # The sections of the document, in the order it lists them.
22
+ SECTIONS = %w[Custodian Deploy Site Codebase QualityControl].freeze
23
+
24
+ # The first line of a table, and the line that closes it.
25
+ HEADER = "| launcher | replaces |\n|---|---|"
26
+
27
+ module_function
28
+
29
+ # @param argv [Array<String>] `--check` to compare without writing
30
+ # @param root [String] the checkout
31
+ # @return [Integer] 0, or 1 when `--check` finds a section out of date
32
+ # @raise [SystemExit] when a section has no marked table
33
+ def main(argv, root: Tools::ROOT)
34
+ path = File.join(root, DOCUMENT)
35
+ text = File.read(path, encoding: "UTF-8")
36
+ fresh = projection(text, root)
37
+ return report(text, fresh) if argv.include?("--check")
38
+
39
+ File.write(path, fresh, encoding: "UTF-8") unless fresh == text
40
+ puts(fresh == text ? "tools_doc: #{rows.size} rows, every table current" : "wrote #{DOCUMENT}")
41
+ 0
42
+ end
43
+
44
+ # @return [Array<Hash{String => String}>] the `RetiredScript` rows, in table order
45
+ def rows = Hecks::Vocabulary.rows("RetiredScript")
46
+
47
+ # The launcher form each retired script became, as the table lists it.
48
+ #
49
+ # @param root [String] the checkout
50
+ # @return [Hash{String => String}] script name to its forms, joined with `; `; a command no
51
+ # script preceded (`(new)`) has none
52
+ def forms(root: Tools::ROOT)
53
+ @forms ||= {}
54
+ @forms[root] ||= begin
55
+ context = Context.new(root)
56
+ rows.reject { |row| row["script"] == "(new)" }.group_by { |row| row["script"] }
57
+ .transform_values { |group| group.map { |row| form(row, context) }.join("; ") }
58
+ end
59
+ end
60
+
61
+ # @param text [String] the document as it stands
62
+ # @param root [String] the checkout
63
+ # @return [String] the document with each section's table rewritten from the rows
64
+ # @raise [SystemExit] when a section has no marked table
65
+ def projection(text, root)
66
+ context = Context.new(root)
67
+ SECTIONS.reduce(text) do |held, section|
68
+ region = /(<!-- generated:begin tools section=#{section} -->\n).*?(<!-- generated:end tools -->)/m
69
+ abort "tools_doc: #{DOCUMENT} has no marked table for #{section}" unless held.match?(region)
70
+
71
+ held.sub(region) { "#{Regexp.last_match(1)}#{table(section, context)}\n#{Regexp.last_match(2)}" }
72
+ end
73
+ end
74
+
75
+ # @param section [String] one of `SECTIONS`
76
+ # @param context [Context] the booted chapters
77
+ # @return [String] the section's table, header included
78
+ def table(section, context)
79
+ lines = rows.select { |row| row["section"] == section }.group_by { |row| row["script"] }.map do |script, group|
80
+ cell = group.map { |row| cell(row, context) }.join("; ")
81
+ "| #{cell} | #{script == '(new)' ? '(new: no `bin/` script)' : "`bin/#{script}`"} |"
82
+ end
83
+ [HEADER, *lines].join("\n")
84
+ end
85
+
86
+ # One form as the table shows it: in backticks, with a `|` escaped.
87
+ def cell(row, context) = "`#{form(row, context).gsub('|', '\|')}`"
88
+
89
+ # @param row [Hash{String => String}] a `RetiredScript` row
90
+ # @param context [Context] the booted chapters
91
+ # @return [String] the launcher form of the row's command or query
92
+ def form(row, context)
93
+ return row["form"] unless row["form"].to_s.empty?
94
+
95
+ spec, words = context.spec_for(row)
96
+ passes = row["passes"].to_s
97
+ text = [*words, *argument_words(spec, skip: passes.empty? ? [] : %w[arguments])].join(" ")
98
+ text += %( [arguments="#{passes}"]) unless passes.empty?
99
+ text += " (#{row['note']})" unless row["note"].to_s.empty?
100
+ "hecks #{text}"
101
+ end
102
+
103
+ # The words after the verb: the first argument bare, the rest `name=`, flags last.
104
+ #
105
+ # @param spec [Hash] a projected command or query
106
+ # @param skip [Array<String>] arguments not to spell, because the row documents them itself
107
+ # @return [Array<String>] each argument as the launcher spells it
108
+ def argument_words(spec, skip: [])
109
+ named = by_attribute(spec[:arguments].reject do |argument|
110
+ argument[:minted] || skip.include?(argument[:path].split(".").first)
111
+ end)
112
+ # The launcher reads a bare word as the first argument, when that is not a flag.
113
+ bare = named.first unless named.empty? || named.first[:type] == "Boolean"
114
+ flags, plain = (named - [bare].compact).partition { |argument| argument[:type] == "Boolean" }
115
+ [*(bracket(bare, "<#{bare[:path]}>") if bare),
116
+ *plain.map { |argument| bracket(argument, "#{argument[:path]}=") },
117
+ *flags.map { |argument| flag_word(argument) }]
118
+ end
119
+
120
+ # A one-field value object's `x.value` leaf is spelled as the attribute, `x`; a value object
121
+ # with more fields keeps one leaf each.
122
+ #
123
+ # @param leaves [Array<Hash>] projected leaf arguments
124
+ # @return [Array<Hash>] the arguments the launcher takes, one per attribute for a scalar
125
+ def by_attribute(leaves)
126
+ leaves.group_by { |argument| argument[:path].split(".").first }.flat_map do |head, group|
127
+ group.size == 1 && group.first[:path].end_with?(".value") ? [group.first.merge(path: head)] : group
128
+ end
129
+ end
130
+
131
+ # @param argument [Hash] a projected argument
132
+ # @param word [String] how it is spelled
133
+ # @return [String] `word`, in brackets when the argument is optional
134
+ def bracket(argument, word) = argument[:required] ? word : "[#{word}]"
135
+
136
+ # `--confirm` is what makes a verb act, so it stands bare; any other flag is optional.
137
+ def flag_word(argument)
138
+ word = "--#{argument[:path].tr('_', '-')}"
139
+ argument[:path] == "confirm" ? word : "[#{word}]"
140
+ end
141
+
142
+ # @param text [String] the document as it stands
143
+ # @param fresh [String] the document the rows give
144
+ # @return [Integer] 0 when current, else 1 with the stale sections on stderr
145
+ def report(text, fresh)
146
+ return 0 if text == fresh
147
+
148
+ stale = SECTIONS.reject { |section| section_text(text, section) == section_text(fresh, section) }
149
+ warn "tools_doc: #{DOCUMENT} differs from the RetiredScript rows in: #{stale.join(', ')} " \
150
+ "(run hecks regeneration_run.project_tools_doc --confirm)"
151
+ 1
152
+ end
153
+
154
+ # @return [String, nil] the marked table of one section
155
+ def section_text(text, section)
156
+ text[/<!-- generated:begin tools section=#{section} -->\n(.*?)<!-- generated:end tools -->/m, 1]
157
+ end
158
+
159
+ # The booted Hecks chapters the forms are rendered from, built once.
160
+ class Context
161
+ # @param root [String] the checkout
162
+ def initialize(root)
163
+ @runtime = Hecks.boot(File.join(root, "lib/hecks/hecks"), install_doors: false)
164
+ @projections = {}
165
+ end
166
+
167
+ # @param row [Hash{String => String}] a `RetiredScript` row
168
+ # @return [Array(Hash, Array<String>)] the projected command or query, and the words that
169
+ # name it on the command line
170
+ # @raise [SystemExit] when the row names no command or query
171
+ def spec_for(row)
172
+ chapter = %w[Custodian Codebase].include?(row["section"]) ? "Hecks" : row["section"]
173
+ cli = projection(chapter)
174
+ name = "#{Naming.snake(row['aggregate'])}.#{row['verb']}"
175
+ command = cli[:commands][cli[:names][:command][name]]
176
+ question = cli[:questions][cli[:names][:question][name]]
177
+ spec = command || question or abort "tools_doc: #{chapter} has no command or query #{name}"
178
+ [spec, words(chapter, name, row)]
179
+ end
180
+
181
+ private
182
+
183
+ def projection(chapter)
184
+ @projections[chapter] ||= begin
185
+ launcher = Doors::LauncherOptions.settings(@runtime, chapter)
186
+ options = Doors::LauncherOptions.projection(launcher, "hecks")
187
+ Projector.call(:cli, bluebook: @runtime.registry.bluebook(chapter), options: options)
188
+ end
189
+ end
190
+
191
+ # `hecks ir`, `hecks console`, `hecks quality_control sweep.run`, `hecks host.check_era`.
192
+ # The launcher takes a name that is only a query as a query, so no `query` word is needed.
193
+ def words(chapter, name, row)
194
+ settings = Doors::LauncherOptions.settings(@runtime, chapter) || {}
195
+ route = (settings[:names] || {}).key(name)
196
+ route ||= row["verb"] if chapter == "Hecks" && Array(settings[:legacy]).include?(row["verb"])
197
+ return [route] if route
198
+
199
+ [*(Naming.snake(chapter) unless chapter == "Hecks"), name]
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
data/lib/hecks/tools.rb CHANGED
@@ -23,6 +23,7 @@ module Hecks
23
23
  "argument_gate_matrix" => ["tools/argument_gate_matrix", "ArgumentGateMatrix"],
24
24
  "project_ci_gates" => ["tools/ci_gates", "CiGates"],
25
25
  "decide_ci_gate" => ["tools/ci_gate_decision", "CiGateDecision"],
26
+ "project_tools_doc" => ["tools/tools_doc", "ToolsDoc"],
26
27
  "project_deploy" => ["tools/deploy_recipe", "DeployRecipe"],
27
28
  "project_site" => ["tools/site_routes", "SiteRoutes"],
28
29
  "lint_deploy_recipes" => ["tools/deploy_recipe_lint", "DeployRecipeLint"],
data/lib/hecks/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Hecks
2
2
  # Read directly by the gemspec, so this file must never require the
3
3
  # rest of the gem (its dependencies aren't resolved yet at that point).
4
- VERSION = "3.2.0".freeze
4
+ VERSION = "3.3.0".freeze
5
5
  end
@@ -51,7 +51,7 @@ module Hecks
51
51
  ].freeze,
52
52
  "CiGate" => [
53
53
  {"name"=>"runtime_changed", "workflow"=>"ci.yml", "mode"=>"touches", "pattern"=>"^lib/hecks/runtime/", "push"=>"skip", "label"=>"does this change touch lib/hecks/runtime/**?"}.freeze,
54
- {"name"=>"postgres_io_relevant_changed", "workflow"=>"ci-postgres-io-parallel.yml", "mode"=>"skips_unless", "pattern"=>"^(docs/|editors/|release/|deploy/|\\.claude/|\\.githooks/|rust/(parser|codegen|host|build|lsp|web|tests|project)/|rust/project\\.rb$|rust/project_rust_pipeline\\.rb$|\\.rubocop\\.yml$|\\.rubocop_todo\\.yml$|\\.mcp\\.json$|\\.rspec-local\\.example$|README\\.md$|CHANGELOG\\.md$|CONTRIBUTING\\.md$|SECURITY\\.md$|LICENSE$|\\.gitignore$)", "push"=>"before_sha", "label"=>"does this change touch anything rspec_postgres_io_parallel covers?"}.freeze
54
+ {"name"=>"postgres_io_relevant_changed", "workflow"=>"ci-postgres-io-parallel.yml", "mode"=>"skips_unless", "pattern"=>"^(docs/|editors/|release/|deploy/|\\.claude/|\\.githooks/|rust/(parser|codegen|host|build|lsp|web|tests)/|\\.rubocop\\.yml$|\\.rubocop_todo\\.yml$|\\.mcp\\.json$|\\.rspec-local\\.example$|README\\.md$|CHANGELOG\\.md$|CONTRIBUTING\\.md$|SECURITY\\.md$|LICENSE$|\\.gitignore$)", "push"=>"before_sha", "label"=>"does this change touch anything rspec_postgres_io_parallel covers?"}.freeze
55
55
  ].freeze,
56
56
  "Comparison" => [
57
57
  {"symbol"=>">=", "compares_less_than"=>"true", "compares_equal"=>"false", "negated"=>"true"}.freeze,
@@ -137,6 +137,34 @@ module Hecks
137
137
  {"name"=>"TrueClass"}.freeze,
138
138
  {"name"=>"FalseClass"}.freeze
139
139
  ].freeze,
140
+ "QaDial" => [
141
+ {"name"=>"cadence_seconds", "type"=>"Integer", "meaning"=>"Minimum gap in seconds between one tick's report and the next dispatch; 0 is immediately. Read by the `/loop hecks_qa` caller, not by any script."}.freeze,
142
+ {"name"=>"pr_cap_per_day", "type"=>"Integer", "meaning"=>"Most PRs (Patch plus Improvement) `hecks quality_control patch.open` opens per calendar day; 0 is uncapped."}.freeze,
143
+ {"name"=>"widening_tiers", "type"=>"Array", "meaning"=>"Fuzz depth by `Target.clean_streak`, in three discrete tiers, read by `SweepDepth.for_streak`. The last row (`upto: Float::INFINITY`) is the ceiling."}.freeze,
144
+ {"name"=>"sweep_max_parallel", "type"=>"Integer", "meaning"=>"How many `hecks quality_control query sweep.run <target>` children `--all` keeps running at once."}.freeze,
145
+ {"name"=>"liveness_fallback_seconds", "type"=>"Integer", "meaning"=>"How long the `/loop hecks_qa` caller waits for a tick's report before assuming it stalled."}.freeze,
146
+ {"name"=>"draft_only", "type"=>"Boolean", "meaning"=>"Whether `hecks quality_control patch.open` opens its PR as a draft; false opens it ready for review."}.freeze,
147
+ {"name"=>"auto_merge", "type"=>"Boolean", "meaning"=>"Whether `hecks quality_control patch.open` queues the PR for auto-merge once it is open."}.freeze,
148
+ {"name"=>"adversarial_fraction", "type"=>"Numeric", "meaning"=>"Fraction (0.0..1.0) of generated command steps mutated adversarially; 0 turns the layer off. Higher finds refusal-ordering splits sooner but reaches deep state less often."}.freeze,
149
+ {"name"=>"guided_generation", "type"=>"Boolean", "meaning"=>"Coverage-guided generation across one sweep's seeds (`Hecks::Fuzzing::CoverageCampaign`)."}.freeze,
150
+ {"name"=>"corpus_splice_probability", "type"=>"Numeric", "meaning"=>"Fraction (0.0..1.0) of seeds that start from a prefix of an earlier seed's new coverage. Off by default: measured, it trades breadth for depth (banking reached 1.41x the deep-state tuples, chess lost 0.12x of all tuples), while favoring rare verbs alone never lost."}.freeze,
151
+ {"name"=>"favor_rare_verbs", "type"=>"Integer", "meaning"=>"How many of the least-hit verbs the next seed weights up; 0 turns the lever off."}.freeze,
152
+ {"name"=>"self_consistency_checks", "type"=>"Boolean", "meaning"=>"Whether `hecks quality_control query sweep.run` also runs the self-consistency pass (cold rehydration, replay idempotency, value-object JSON round trip) on the same generated sequence."}.freeze,
153
+ {"name"=>"shrink_budget", "type"=>"Integer", "meaning"=>"Candidate replays `hecks quality_control query sweep.run` may spend shrinking one surprised check; 0 turns shrinking off. `concurrency` is never shrunk: a race does not reproduce on demand."}.freeze,
154
+ {"name"=>"yield_weight_seconds", "type"=>"Integer", "meaning"=>"Seconds of staleness that one point of yield score is worth in `RotationPriority.pick`. Zero would make yield invisible, so pick a number that lets it matter."}.freeze,
155
+ {"name"=>"yield_decay_percent", "type"=>"Integer", "meaning"=>"Percent of the prior yield score kept at each release, so recent findings dominate. Applied by the caller: the mutation language cannot express \"decay, then add\" in `Release`."}.freeze,
156
+ {"name"=>"rotation_stale_floor_seconds", "type"=>"Integer", "meaning"=>"Once a waiting target is this stale, `RotationPriority.pick` takes it whatever its yield score, so a quiet target cannot starve."}.freeze,
157
+ {"name"=>"persistence_parity_seed_cap", "type"=>"Integer", "meaning"=>"Ceiling on `--seeds` for `--persistence-parity`, which pays for real Postgres per dispatch. `hecks quality_control query sweep.run` clamps down, never up, and prints a note."}.freeze,
158
+ {"name"=>"concurrency_seed_cap", "type"=>"Integer", "meaning"=>"Ceiling on `--seeds` for the concurrency mode, which needs two disposable schemas per seed."}.freeze,
159
+ {"name"=>"adapter_parity_pairs", "type"=>"Hash", "meaning"=>"The two adapters each adapter-parity mode compares, by mode name; a new pairing is a new entry. `adapter_parity_sqlite` needs no `database:`/`schema:` because sqlite has no disposable database."}.freeze,
160
+ {"name"=>"modes", "type"=>"Hash", "meaning"=>"Which comparison modes `hecks quality_control query sweep.run` may run: the enabled ones intersected with what `TargetCapabilities.infer(path)` finds in the target. `--modes a,b,c` overrides for one run. `wasm_front` and `adapter_parity_postgres` are off because nothing runs them; the others are built and off because their I/O cost across the rotation is unmeasured. `hecks quality_control query sweep.run` refuses to start when a mode outside `TargetCapabilities::RUNNABLE_MODES` is enabled."}.freeze,
161
+ {"name"=>"role_draw_probability", "type"=>"Numeric", "meaning"=>"How often a generated role-gated command step carries a drawn caller (`role:`/`actor_id:`); 0 turns the draw off."}.freeze,
162
+ {"name"=>"dry_run_fraction", "type"=>"Numeric", "meaning"=>"Fraction of generated command steps offered as a dry run instead of a real dispatch; 0 is off. Kept low: a dry run creates no state for later steps to act on."}.freeze,
163
+ {"name"=>"generated_domains_per_tick", "type"=>"Integer", "meaning"=>"How many generated domains one `hecks quality_control sweep.tick` checks after the rotation sweep; 0 skips the step."}.freeze,
164
+ {"name"=>"generated_domains_rust", "type"=>"Boolean", "meaning"=>"Whether generated domains are also compared against a compiled Rust binary built in a scratch copy."}.freeze,
165
+ {"name"=>"generated_domain_seeds", "type"=>"Integer", "meaning"=>"Generated sequences run per generated domain."}.freeze,
166
+ {"name"=>"structural_refusal_boundary", "type"=>"Array", "meaning"=>"Construct families a skipped query verb may belong to. A skip inside the list is logged Held, one outside it Surprised. Shrink-only: spec/fuzzing/rust_gap_manifest_spec.rb fails when a family here is declared by no committed manifest."}.freeze
167
+ ].freeze,
140
168
  "QueryComparator" => [
141
169
  {"name"=>"eq"}.freeze,
142
170
  {"name"=>"ne"}.freeze,
@@ -330,6 +358,129 @@ module Hecks
330
358
  {"refusal"=>"AttributeAbsent", "site"=>"absent_read", "template"=>"{aggregate} {field} is absent on this record — declared, not optional, and added since it was written. Backfill it in a translation (backfill :{field}, default: ...), or declare it optional: true"}.freeze,
331
359
  {"refusal"=>"ProjectionAbsent", "site"=>"absent_read", "template"=>"{aggregate} {field} is not yet projected on this record — declared via projects :{field}, but no rebuild sweep has populated it. Run the sweep, or read {reference}.{remote_field} directly if this rule cannot wait"}.freeze
332
360
  ].freeze,
361
+ "RetiredScript" => [
362
+ {"script"=>"ir", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"ir"}.freeze,
363
+ {"script"=>"shape", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"shape"}.freeze,
364
+ {"script"=>"stores", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"stores"}.freeze,
365
+ {"script"=>"history", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"history"}.freeze,
366
+ {"script"=>"statements", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"statements"}.freeze,
367
+ {"script"=>"narrate", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"narrate"}.freeze,
368
+ {"script"=>"docs", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"docs"}.freeze,
369
+ {"script"=>"project_diagrams", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"project_diagrams"}.freeze,
370
+ {"script"=>"project_glossary", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"glossary"}.freeze,
371
+ {"script"=>"model_check", "section"=>"Custodian", "aggregate"=>"ModelCheckRun", "verb"=>"model_check"}.freeze,
372
+ {"script"=>"run", "section"=>"Custodian", "aggregate"=>"Operation", "verb"=>"run", "form"=>"hecks run [domain] <verb [name=value …] | script.json | - | '{\"steps\":[…]}'>"}.freeze,
373
+ {"script"=>"project", "section"=>"Custodian", "aggregate"=>"Operation", "verb"=>"refresh_projections"}.freeze,
374
+ {"script"=>"behaviors", "section"=>"Custodian", "aggregate"=>"Operation", "verb"=>"run_behaviors"}.freeze,
375
+ {"script"=>"console", "section"=>"Custodian", "aggregate"=>"Operation", "verb"=>"open_console"}.freeze,
376
+ {"script"=>"follow", "section"=>"Custodian", "aggregate"=>"Operation", "verb"=>"follow"}.freeze,
377
+ {"script"=>"smoke_test", "section"=>"Custodian", "aggregate"=>"Operation", "verb"=>"smoke_test"}.freeze,
378
+ {"script"=>"smoke_http", "section"=>"Custodian", "aggregate"=>"Operation", "verb"=>"smoke_http"}.freeze,
379
+ {"script"=>"check_era", "section"=>"Custodian", "aggregate"=>"Host", "verb"=>"check_era"}.freeze,
380
+ {"script"=>"merge_tail", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"merge_tail"}.freeze,
381
+ {"script"=>"reattest_era", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"reattest"}.freeze,
382
+ {"script"=>"backfill_era_projections", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"backfill_projections"}.freeze,
383
+ {"script"=>"scaffold_translation", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"scaffold_translation"}.freeze,
384
+ {"script"=>"translation_audit", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"audit_translation"}.freeze,
385
+ {"script"=>"translation_audit", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"approve_translation"}.freeze,
386
+ {"script"=>"compact", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"compact"}.freeze,
387
+ {"script"=>"heki_compact", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"compact_heki"}.freeze,
388
+ {"script"=>"vendor_bluebook", "section"=>"Custodian", "aggregate"=>"Package", "verb"=>"vendor", "note"=>"waits: a refusal exits 1 with its reason on stderr"}.freeze,
389
+ {"script"=>"project_cli", "section"=>"Custodian", "aggregate"=>"Door", "verb"=>"project_cli"}.freeze,
390
+ {"script"=>"hecks_mcp_door", "section"=>"Custodian", "aggregate"=>"Door", "verb"=>"serve_mcp"}.freeze,
391
+ {"script"=>"project_rust", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"project_rust"}.freeze,
392
+ {"script"=>"project_wasm", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"build_wasm"}.freeze,
393
+ {"script"=>"project_host", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"build_host"}.freeze,
394
+ {"script"=>"project_wasm_browser", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"build_browser_wasm"}.freeze,
395
+ {"script"=>"rust_coverage", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"rust_coverage"}.freeze,
396
+ {"script"=>"rust_coverage", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"check_coverage_allowlist"}.freeze,
397
+ {"script"=>"rust_conformance", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"check_conformance"}.freeze,
398
+ {"script"=>"rust_conformance_fuzz", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"fuzz_conformance"}.freeze,
399
+ {"script"=>"fuzz", "section"=>"Custodian", "aggregate"=>"FuzzRun", "verb"=>"fuzz"}.freeze,
400
+ {"script"=>"generate", "section"=>"Custodian", "aggregate"=>"FuzzRun", "verb"=>"generate_sequence"}.freeze,
401
+ {"script"=>"bench", "section"=>"Custodian", "aggregate"=>"FuzzRun", "verb"=>"bench"}.freeze,
402
+ {"script"=>"(new)", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"hold_first"}.freeze,
403
+ {"script"=>"project_deploy", "section"=>"Deploy", "aggregate"=>"Recipe", "verb"=>"project"}.freeze,
404
+ {"script"=>"lint_deploy_recipes", "section"=>"Deploy", "aggregate"=>"MakefileCheck", "verb"=>"lint"}.freeze,
405
+ {"script"=>"deploy_template_diff", "section"=>"Deploy", "aggregate"=>"TemplateComparison", "verb"=>"diff"}.freeze,
406
+ {"script"=>"project_oidc", "section"=>"Deploy", "aggregate"=>"OidcManifest", "verb"=>"project_oidc"}.freeze,
407
+ {"script"=>"project_tenant", "section"=>"Deploy", "aggregate"=>"Tenant", "verb"=>"provision"}.freeze,
408
+ {"script"=>"project_tenant", "section"=>"Deploy", "aggregate"=>"Tenant", "verb"=>"reprovision"}.freeze,
409
+ {"script"=>"(new)", "section"=>"Site", "aggregate"=>"SiteProjection", "verb"=>"project_site"}.freeze,
410
+ {"script"=>"project_model", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_model"}.freeze,
411
+ {"script"=>"project_vocabulary", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_vocabulary"}.freeze,
412
+ {"script"=>"project_rust_vocabulary", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_rust_vocabulary"}.freeze,
413
+ {"script"=>"project_refusal_wording", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_refusal_wording"}.freeze,
414
+ {"script"=>"project_reserved_names", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_reserved_names"}.freeze,
415
+ {"script"=>"project_parser_table", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_parser_table"}.freeze,
416
+ {"script"=>"project_bootstrap_table", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_bootstrap_table"}.freeze,
417
+ {"script"=>"project_field_hints", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_field_hints"}.freeze,
418
+ {"script"=>"expression_projection", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_expression_tables"}.freeze,
419
+ {"script"=>"reference", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"project_reference"}.freeze,
420
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"word_status"}.freeze,
421
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"propose"}.freeze,
422
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"admit"}.freeze,
423
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"deprecate"}.freeze,
424
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"retire"}.freeze,
425
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"rename"}.freeze,
426
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"propose_argument"}.freeze,
427
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"admit_argument"}.freeze,
428
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"deprecate_argument"}.freeze,
429
+ {"script"=>"evolve", "section"=>"Codebase", "aggregate"=>"LanguageRun", "verb"=>"retire_argument"}.freeze,
430
+ {"script"=>"project_kernel_capabilities", "section"=>"Codebase", "aggregate"=>"KernelRun", "verb"=>"project_kernel_capabilities"}.freeze,
431
+ {"script"=>"rust_kernel_coverage", "section"=>"Codebase", "aggregate"=>"KernelRun", "verb"=>"measure_kernel_coverage"}.freeze,
432
+ {"script"=>"check_engine_agreement", "section"=>"Codebase", "aggregate"=>"ConformanceRun", "verb"=>"check_engine_agreement"}.freeze,
433
+ {"script"=>"doc_coverage", "section"=>"Codebase", "aggregate"=>"ConformanceRun", "verb"=>"measure_doc_coverage"}.freeze,
434
+ {"script"=>"argument_gate_matrix", "section"=>"Codebase", "aggregate"=>"ConformanceRun", "verb"=>"argument_gate_matrix"}.freeze,
435
+ {"script"=>"regen_codegen_domains", "section"=>"Codebase", "aggregate"=>"RegenerationRun", "verb"=>"regenerate_corpus"}.freeze,
436
+ {"script"=>"standardize_comments", "section"=>"Codebase", "aggregate"=>"StyleRun", "verb"=>"report_comments"}.freeze,
437
+ {"script"=>"standardize_comments", "section"=>"Codebase", "aggregate"=>"StyleRun", "verb"=>"check_comments"}.freeze,
438
+ {"script"=>"standardize_comments", "section"=>"Codebase", "aggregate"=>"StyleRun", "verb"=>"fix_comments"}.freeze,
439
+ {"script"=>"standardize_comments", "section"=>"Codebase", "aggregate"=>"StyleRun", "verb"=>"write_comment_baseline"}.freeze,
440
+ {"script"=>"standardize_comments", "section"=>"Codebase", "aggregate"=>"StyleRun", "verb"=>"check_comments_unchanged"}.freeze,
441
+ {"script"=>"standardize_comments_rust", "section"=>"Codebase", "aggregate"=>"StyleRun", "verb"=>"report_rust_comments"}.freeze,
442
+ {"script"=>"standardize_comments_rust", "section"=>"Codebase", "aggregate"=>"StyleRun", "verb"=>"check_rust_comments"}.freeze,
443
+ {"script"=>"standardize_comments_rust", "section"=>"Codebase", "aggregate"=>"StyleRun", "verb"=>"fix_rust_comments"}.freeze,
444
+ {"script"=>"canonicalise", "section"=>"Codebase", "aggregate"=>"StyleRun", "verb"=>"canonicalise"}.freeze,
445
+ {"script"=>"codemod_hoist_local_givens", "section"=>"Codebase", "aggregate"=>"CodemodRun", "verb"=>"hoist_local_givens"}.freeze,
446
+ {"script"=>"codemod_implicit_append_fields", "section"=>"Codebase", "aggregate"=>"CodemodRun", "verb"=>"drop_implicit_append_fields"}.freeze,
447
+ {"script"=>"rspec_shard_files", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"shard_specs"}.freeze,
448
+ {"script"=>"rspec_io_parallel_files", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"list_io_parallel_specs"}.freeze,
449
+ {"script"=>"rspec_io_parallel_files", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"write_io_parallel_spec_list"}.freeze,
450
+ {"script"=>"refresh_rspec_runtime_baseline", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"refresh_runtime_baseline"}.freeze,
451
+ {"script"=>"spec_example", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"run_spec_example"}.freeze,
452
+ {"script"=>"stress_concurrency_specs", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"stress_concurrency"}.freeze,
453
+ {"script"=>"regenerate_persistence_legacy_fixtures", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"regenerate_legacy_fixtures"}.freeze,
454
+ {"script"=>"seed_semantics_corpus", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"seed_semantics_corpus"}.freeze,
455
+ {"script"=>"pattern-cases", "section"=>"Codebase", "aggregate"=>"TestSuiteRun", "verb"=>"record_pattern_cases"}.freeze,
456
+ {"script"=>"corpus", "section"=>"Codebase", "aggregate"=>"CorpusRun", "verb"=>"rust_domains"}.freeze,
457
+ {"script"=>"corpus", "section"=>"Codebase", "aggregate"=>"CorpusRun", "verb"=>"regen_order"}.freeze,
458
+ {"script"=>"corpus", "section"=>"Codebase", "aggregate"=>"CorpusRun", "verb"=>"corpus_rust_coverage"}.freeze,
459
+ {"script"=>"query_ir", "section"=>"Codebase", "aggregate"=>"CorpusRun", "verb"=>"ir_constructs"}.freeze,
460
+ {"script"=>"query_ir", "section"=>"Codebase", "aggregate"=>"CorpusRun", "verb"=>"ir_duplicates"}.freeze,
461
+ {"script"=>"query_ir", "section"=>"Codebase", "aggregate"=>"CorpusRun", "verb"=>"ir_impact"}.freeze,
462
+ {"script"=>"hecks_query_ir_mcp", "section"=>"Codebase", "aggregate"=>"CorpusRun", "verb"=>"serve_query_ir_mcp"}.freeze,
463
+ {"script"=>"present", "section"=>"Codebase", "aggregate"=>"CorpusRun", "verb"=>"present"}.freeze,
464
+ {"script"=>"release", "section"=>"Codebase", "aggregate"=>"PublishingRun", "verb"=>"publish", "note"=>"without --confirm, the old --dry-run"}.freeze,
465
+ {"script"=>"release_gem", "section"=>"Codebase", "aggregate"=>"PublishingRun", "verb"=>"publish_gem"}.freeze,
466
+ {"script"=>"(new)", "section"=>"Codebase", "aggregate"=>"GateRun", "verb"=>"gate"}.freeze,
467
+ {"script"=>"qa_tick", "section"=>"QualityControl", "aggregate"=>"Sweep", "verb"=>"tick"}.freeze,
468
+ {"script"=>"qa_sweep", "section"=>"QualityControl", "aggregate"=>"Sweep", "verb"=>"run", "passes"=>"--all --seeds N --steps N --adversarial F --role-draw F --dry-run F --self-consistency BOOL --modes a,b --persistence-parity --no-parity"}.freeze,
469
+ {"script"=>"qa_sweep", "section"=>"QualityControl", "aggregate"=>"Target", "verb"=>"release"}.freeze,
470
+ {"script"=>"qa_pr_check", "section"=>"QualityControl", "aggregate"=>"Clearance", "verb"=>"check_pull_requests"}.freeze,
471
+ {"script"=>"qa_open_pr", "section"=>"QualityControl", "aggregate"=>"Patch", "verb"=>"open"}.freeze,
472
+ {"script"=>"qa_open_pr", "section"=>"QualityControl", "aggregate"=>"Improvement", "verb"=>"open"}.freeze,
473
+ {"script"=>"qa_log_bug", "section"=>"QualityControl", "aggregate"=>"Bug", "verb"=>"log"}.freeze,
474
+ {"script"=>"qa_seed_angles", "section"=>"QualityControl", "aggregate"=>"Angle", "verb"=>"seed"}.freeze,
475
+ {"script"=>"qa_seed_targets", "section"=>"QualityControl", "aggregate"=>"Target", "verb"=>"seed"}.freeze,
476
+ {"script"=>"qa_generated_domains", "section"=>"QualityControl", "aggregate"=>"Target", "verb"=>"check_generated_domains", "passes"=>"--domains 3 --start N --forms a,b --seeds 5 --steps 25 --adversarial 0.3 --rust --shrink-budget 200 --domain-shrink-budget 40 --promote dir --name N --from-dials --blueprint F --source F"}.freeze,
477
+ {"script"=>"qa_mine_combinations", "section"=>"QualityControl", "aggregate"=>"Target", "verb"=>"mine_combinations", "passes"=>"--candidates 3 --rust --seeds 5 --steps 25 --adversarial 0.3 --repair-rounds 1 --agent CMD --from DIR --against PATH --brief"}.freeze,
478
+ {"script"=>"qa_domain_novelty", "section"=>"QualityControl", "aggregate"=>"Target", "verb"=>"judge_novelty", "passes"=>"--against PATH …"}.freeze,
479
+ {"script"=>"qa_discover_external_domains", "section"=>"QualityControl", "aggregate"=>"Target", "verb"=>"discover_external_domains", "passes"=>"--projects-dir ~/Projects --max-depth 3 --known-path PATH …"}.freeze,
480
+ {"script"=>"qa_postgres_migrate", "section"=>"QualityControl", "aggregate"=>"Sweep", "verb"=>"migrate_ledger_from_heki", "passes"=>"[aggregate …] --force", "note"=>"a dry run without --force"}.freeze,
481
+ {"script"=>"qa_postgres_role", "section"=>"QualityControl", "aggregate"=>"Sweep", "verb"=>"create_ledger_role"}.freeze,
482
+ {"script"=>"qa_concurrency_racer", "section"=>"QualityControl", "aggregate"=>"Sweep", "verb"=>"race", "note"=>"not user-facing; ProcessPool starts it"}.freeze
483
+ ].freeze,
333
484
  "RustReservedWord" => [
334
485
  {"word"=>"as"}.freeze,
335
486
  {"word"=>"break"}.freeze,
data/lib/hecks.rb CHANGED
@@ -34,6 +34,7 @@ require_relative "hecks/doors/cli_runner"
34
34
  require_relative "hecks/storehouse"
35
35
  require_relative "hecks/mcp_stdio_guard"
36
36
  require_relative "hecks/doors/mcp_door_scope"
37
+ require_relative "hecks/doors/mcp_guide_cache"
37
38
  require_relative "hecks/framework"
38
39
  require_relative "hecks/vendoring"
39
40
  require_relative "hecks/embryonaut_bluebook"