principles 0.0.1

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 (278) hide show
  1. checksums.yaml +7 -0
  2. data/exe/principles +10 -0
  3. data/lib/principles/after_commit_hook_options.rb +44 -0
  4. data/lib/principles/cli.rb +149 -0
  5. data/lib/principles/command_line.rb +188 -0
  6. data/lib/principles/command_routes.rb +60 -0
  7. data/lib/principles/controllers/acknowledgement.rb +106 -0
  8. data/lib/principles/controllers/answer.rb +172 -0
  9. data/lib/principles/controllers/audit.rb +203 -0
  10. data/lib/principles/controllers/audit_completion.rb +113 -0
  11. data/lib/principles/controllers/command.rb +201 -0
  12. data/lib/principles/controllers/correction.rb +99 -0
  13. data/lib/principles/controllers/help.rb +107 -0
  14. data/lib/principles/controllers/inventory.rb +181 -0
  15. data/lib/principles/controllers/mapping.rb +56 -0
  16. data/lib/principles/controllers/member_entries.rb +149 -0
  17. data/lib/principles/controllers/members_a_commit_changed.rb +43 -0
  18. data/lib/principles/controllers/members_to_assess.rb +160 -0
  19. data/lib/principles/controllers/meta_principle_setup.rb +92 -0
  20. data/lib/principles/controllers/repoint.rb +68 -0
  21. data/lib/principles/controllers/running_tool.rb +41 -0
  22. data/lib/principles/data/audit_files.rb +96 -0
  23. data/lib/principles/data/bounded_file.rb +45 -0
  24. data/lib/principles/data/candidate_files_found.rb +105 -0
  25. data/lib/principles/data/command-steps.json +305 -0
  26. data/lib/principles/data/command_steps_file.rb +50 -0
  27. data/lib/principles/data/expected_type.rb +71 -0
  28. data/lib/principles/data/ignored_by_git.rb +54 -0
  29. data/lib/principles/data/json_text.rb +57 -0
  30. data/lib/principles/data/known_fields.rb +42 -0
  31. data/lib/principles/data/linked_path.rb +102 -0
  32. data/lib/principles/data/map_files.rb +104 -0
  33. data/lib/principles/data/principle_id.rb +32 -0
  34. data/lib/principles/data/principles_subfolder.rb +93 -0
  35. data/lib/principles/data/project_files.rb +109 -0
  36. data/lib/principles/data/repository.rb +165 -0
  37. data/lib/principles/data/running_version.rb +44 -0
  38. data/lib/principles/data/schema_check.rb +139 -0
  39. data/lib/principles/data/schemas/acknowledgement-answer.schema.json +25 -0
  40. data/lib/principles/data/schemas/answer-guide.schema.json +99 -0
  41. data/lib/principles/data/schemas/assessment-answer.schema.json +332 -0
  42. data/lib/principles/data/schemas/audit-record.schema.json +277 -0
  43. data/lib/principles/data/schemas/command-steps.schema.json +83 -0
  44. data/lib/principles/data/schemas/correction-answer-guide.schema.json +68 -0
  45. data/lib/principles/data/schemas/correction-answer.schema.json +306 -0
  46. data/lib/principles/data/schemas/member-map.schema.json +39 -0
  47. data/lib/principles/data/schemas/repointing-answer.schema.json +44 -0
  48. data/lib/principles/error.rb +102 -0
  49. data/lib/principles/invocation_options.rb +77 -0
  50. data/lib/principles/models/accepted_loading.rb +36 -0
  51. data/lib/principles/models/acknowledgement.rb +203 -0
  52. data/lib/principles/models/activation.rb +64 -0
  53. data/lib/principles/models/agent_loads.rb +64 -0
  54. data/lib/principles/models/answer_fields.rb +67 -0
  55. data/lib/principles/models/answer_place.rb +53 -0
  56. data/lib/principles/models/answer_text.rb +37 -0
  57. data/lib/principles/models/answered_time.rb +65 -0
  58. data/lib/principles/models/assessment_answer.rb +131 -0
  59. data/lib/principles/models/assessment_basis.rb +47 -0
  60. data/lib/principles/models/assessment_report.rb +62 -0
  61. data/lib/principles/models/audit.rb +200 -0
  62. data/lib/principles/models/audit_record.rb +249 -0
  63. data/lib/principles/models/audit_records.rb +103 -0
  64. data/lib/principles/models/audit_start.rb +96 -0
  65. data/lib/principles/models/audited_member_lines.rb +45 -0
  66. data/lib/principles/models/candidate_files.rb +66 -0
  67. data/lib/principles/models/candidate_inventory.rb +67 -0
  68. data/lib/principles/models/candidate_listing.rb +32 -0
  69. data/lib/principles/models/carried_assessments.rb +76 -0
  70. data/lib/principles/models/check_since_last_audit.rb +61 -0
  71. data/lib/principles/models/cited_files.rb +39 -0
  72. data/lib/principles/models/command_steps.rb +270 -0
  73. data/lib/principles/models/commits_brought_in.rb +27 -0
  74. data/lib/principles/models/commits_since_base.rb +32 -0
  75. data/lib/principles/models/correction_answer.rb +66 -0
  76. data/lib/principles/models/correction_basis.rb +70 -0
  77. data/lib/principles/models/dated_line.rb +29 -0
  78. data/lib/principles/models/decided_prior_findings.rb +106 -0
  79. data/lib/principles/models/declared_map_changes.rb +156 -0
  80. data/lib/principles/models/disposition.rb +233 -0
  81. data/lib/principles/models/entry_file.rb +65 -0
  82. data/lib/principles/models/entry_files.rb +46 -0
  83. data/lib/principles/models/evidence_item.rb +48 -0
  84. data/lib/principles/models/evidence_lines_since.rb +57 -0
  85. data/lib/principles/models/evidence_replacements.rb +99 -0
  86. data/lib/principles/models/finding_write_ups.rb +108 -0
  87. data/lib/principles/models/findings_left_open.rb +34 -0
  88. data/lib/principles/models/first_raised_claim.rb +128 -0
  89. data/lib/principles/models/frontmatter_fields.rb +128 -0
  90. data/lib/principles/models/hook_commands.rb +100 -0
  91. data/lib/principles/models/ignored_candidates.rb +46 -0
  92. data/lib/principles/models/json_digest.rb +35 -0
  93. data/lib/principles/models/json_path_query.rb +131 -0
  94. data/lib/principles/models/json_path_selectors.rb +64 -0
  95. data/lib/principles/models/json_path_string.rb +76 -0
  96. data/lib/principles/models/kept_prior_finding.rb +70 -0
  97. data/lib/principles/models/last_completed_revision.rb +70 -0
  98. data/lib/principles/models/line_span.rb +49 -0
  99. data/lib/principles/models/loaded_files.rb +60 -0
  100. data/lib/principles/models/loading_decision.rb +45 -0
  101. data/lib/principles/models/loading_facts.rb +45 -0
  102. data/lib/principles/models/loads_to_check.rb +93 -0
  103. data/lib/principles/models/locate_refusal.rb +23 -0
  104. data/lib/principles/models/located_text.rb +43 -0
  105. data/lib/principles/models/locator.rb +52 -0
  106. data/lib/principles/models/map_members.rb +171 -0
  107. data/lib/principles/models/markdown_file.rb +92 -0
  108. data/lib/principles/models/member.rb +171 -0
  109. data/lib/principles/models/member_assessments.rb +144 -0
  110. data/lib/principles/models/member_elsewhere.rb +74 -0
  111. data/lib/principles/models/member_file.rb +156 -0
  112. data/lib/principles/models/member_grounds.rb +32 -0
  113. data/lib/principles/models/member_history.rb +68 -0
  114. data/lib/principles/models/member_lines.rb +95 -0
  115. data/lib/principles/models/member_loads.rb +197 -0
  116. data/lib/principles/models/member_map.rb +219 -0
  117. data/lib/principles/models/member_map_history.rb +143 -0
  118. data/lib/principles/models/member_maps.rb +185 -0
  119. data/lib/principles/models/member_moves.rb +95 -0
  120. data/lib/principles/models/member_problems.rb +253 -0
  121. data/lib/principles/models/member_sighting.rb +176 -0
  122. data/lib/principles/models/member_snapshots.rb +36 -0
  123. data/lib/principles/models/member_state.rb +35 -0
  124. data/lib/principles/models/members_elsewhere.rb +46 -0
  125. data/lib/principles/models/members_sharing_lines.rb +116 -0
  126. data/lib/principles/models/meta_principle.rb +51 -0
  127. data/lib/principles/models/missing_files.rb +85 -0
  128. data/lib/principles/models/misspelt_member_id.rb +45 -0
  129. data/lib/principles/models/open_finding.rb +221 -0
  130. data/lib/principles/models/open_findings_elsewhere.rb +45 -0
  131. data/lib/principles/models/other_principles.rb +47 -0
  132. data/lib/principles/models/placement.rb +42 -0
  133. data/lib/principles/models/plain_text_file.rb +32 -0
  134. data/lib/principles/models/principles_folder_move.rb +59 -0
  135. data/lib/principles/models/principles_package.rb +53 -0
  136. data/lib/principles/models/prior_finding_decision.rb +162 -0
  137. data/lib/principles/models/prior_finding_outcomes.rb +60 -0
  138. data/lib/principles/models/prior_findings.rb +109 -0
  139. data/lib/principles/models/problem_report.rb +110 -0
  140. data/lib/principles/models/project.rb +214 -0
  141. data/lib/principles/models/project_history.rb +203 -0
  142. data/lib/principles/models/record_judged_against.rb +42 -0
  143. data/lib/principles/models/recorded_entry.rb +93 -0
  144. data/lib/principles/models/recorded_finding.rb +97 -0
  145. data/lib/principles/models/recorded_findings.rb +58 -0
  146. data/lib/principles/models/recorded_members.rb +149 -0
  147. data/lib/principles/models/repoint.rb +112 -0
  148. data/lib/principles/models/reported_check.rb +130 -0
  149. data/lib/principles/models/request_id.rb +70 -0
  150. data/lib/principles/models/runs_from.rb +36 -0
  151. data/lib/principles/models/skill_loads.rb +123 -0
  152. data/lib/principles/models/still_holds_restatement.rb +87 -0
  153. data/lib/principles/models/text_size.rb +26 -0
  154. data/lib/principles/models/uncommitted_outside_the_map.rb +30 -0
  155. data/lib/principles/models/waits_on.rb +58 -0
  156. data/lib/principles/models/write_up_opening.rb +93 -0
  157. data/lib/principles/models/write_up_pointer.rb +50 -0
  158. data/lib/principles/models/write_up_repoint.rb +39 -0
  159. data/lib/principles/prose_list.rb +23 -0
  160. data/lib/principles/supported_ruby.rb +93 -0
  161. data/lib/principles/version.rb +11 -0
  162. data/lib/principles/views/acknowledged_since_last_audit.rb +34 -0
  163. data/lib/principles/views/acknowledgement_result.rb +44 -0
  164. data/lib/principles/views/activation_in_hand.rb +47 -0
  165. data/lib/principles/views/assessment_request.rb +131 -0
  166. data/lib/principles/views/audit_changes.rb +79 -0
  167. data/lib/principles/views/audit_commit.rb +55 -0
  168. data/lib/principles/views/audit_result.rb +147 -0
  169. data/lib/principles/views/candidate_files_result.rb +95 -0
  170. data/lib/principles/views/check_result_template.rb +75 -0
  171. data/lib/principles/views/diff_since_last_audit.rb +29 -0
  172. data/lib/principles/views/findings_result.rb +45 -0
  173. data/lib/principles/views/help.rb +119 -0
  174. data/lib/principles/views/invocation.rb +66 -0
  175. data/lib/principles/views/landed_since_base.rb +27 -0
  176. data/lib/principles/views/lines_shared_with_member.rb +111 -0
  177. data/lib/principles/views/loads_to_check_result.rb +45 -0
  178. data/lib/principles/views/map_result.rb +169 -0
  179. data/lib/principles/views/member_change_since_baseline.rb +58 -0
  180. data/lib/principles/views/member_repairs.rb +82 -0
  181. data/lib/principles/views/members_a_commit_changed.rb +66 -0
  182. data/lib/principles/views/members_with_no_baseline.rb +56 -0
  183. data/lib/principles/views/meta_principle_passage.rb +23 -0
  184. data/lib/principles/views/meta_principle_result.rb +170 -0
  185. data/lib/principles/views/open_findings_elsewhere_result.rb +32 -0
  186. data/lib/principles/views/open_findings_line.rb +125 -0
  187. data/lib/principles/views/other_principles_unjudged_result.rb +32 -0
  188. data/lib/principles/views/outside_the_map_result.rb +31 -0
  189. data/lib/principles/views/package_since_last_audit_result.rb +34 -0
  190. data/lib/principles/views/principle_read_result.rb +86 -0
  191. data/lib/principles/views/principle_with_changed_members.rb +26 -0
  192. data/lib/principles/views/principles_with_no_completed_audit.rb +30 -0
  193. data/lib/principles/views/printed_principle.rb +45 -0
  194. data/lib/principles/views/prior_finding_decisions_result.rb +35 -0
  195. data/lib/principles/views/prior_findings_result.rb +80 -0
  196. data/lib/principles/views/problem_lines.rb +84 -0
  197. data/lib/principles/views/problem_result.rb +107 -0
  198. data/lib/principles/views/prompts/acknowledge-a-change/say-whether-the-change-keeps-the-meaning.md +12 -0
  199. data/lib/principles/views/prompts/answer-any-judgement-request/what-a-self-audit-cannot-show.md +6 -0
  200. data/lib/principles/views/prompts/assess-every-member/answer-guide.json +63 -0
  201. data/lib/principles/views/prompts/assess-every-member/assess-each-member-and-the-set.md +12 -0
  202. data/lib/principles/views/prompts/assess-every-member/check-roles-other-principles-give-these-lines.md +7 -0
  203. data/lib/principles/views/prompts/assess-every-member/decide-each-member-loading.md +17 -0
  204. data/lib/principles/views/prompts/assess-every-member/decide-each-prior-finding.md +38 -0
  205. data/lib/principles/views/prompts/assess-every-member/give-problem-evidence-at-still-holds.md +2 -0
  206. data/lib/principles/views/prompts/assess-every-member/give-the-evidence-a-prior-finding-lacks.md +7 -0
  207. data/lib/principles/views/prompts/assess-every-member/have-a-changed-check-reviewed-before-running-it.md +4 -0
  208. data/lib/principles/views/prompts/assess-every-member/have-a-new-check-reviewed-before-running-it.md +3 -0
  209. data/lib/principles/views/prompts/assess-every-member/have-hidden-characters-in-a-check-explained.md +4 -0
  210. data/lib/principles/views/prompts/assess-every-member/judge-acknowledged-edits-together.md +6 -0
  211. data/lib/principles/views/prompts/assess-every-member/judge-the-keeping-against-the-other-principles.md +7 -0
  212. data/lib/principles/views/prompts/assess-every-member/judge-the-keeping-under-the-release-since-the-last-audit.md +10 -0
  213. data/lib/principles/views/prompts/assess-every-member/judge-the-set-with-carried-assessments.md +10 -0
  214. data/lib/principles/views/prompts/assess-every-member/keep-the-last-review-basis-unless-you-give-a-new-one.md +1 -0
  215. data/lib/principles/views/prompts/assess-every-member/name-another-principles-open-finding-rather-than-raise-it.md +9 -0
  216. data/lib/principles/views/prompts/assess-every-member/read-whether-each-prior-finding-s-evidence-changed.md +12 -0
  217. data/lib/principles/views/prompts/assess-every-member/run-a-named-check-at-every-full-audit.md +2 -0
  218. data/lib/principles/views/prompts/assess-every-member/run-the-check-then-report-it.md +2 -0
  219. data/lib/principles/views/prompts/assess-every-member/run-the-reviewed-check-and-say-what-its-review-rested-on.md +6 -0
  220. data/lib/principles/views/prompts/assess-every-member/uncommitted-files-outside-the-map.md +4 -0
  221. data/lib/principles/views/prompts/assess-every-member/why-each-member-is-judged-again.md +24 -0
  222. data/lib/principles/views/prompts/dispose-of-each-finding/answer-guide.json +34 -0
  223. data/lib/principles/views/prompts/dispose-of-each-finding/fix-or-record-each-finding.md +24 -0
  224. data/lib/principles/views/prompts/find-a-principle-to-map/ask-the-owner-before-mapping.md +1 -0
  225. data/lib/principles/views/prompts/find-a-principle-to-map/choose-one-principle.md +5 -0
  226. data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-ignored-folders.md +2 -0
  227. data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-ignored.md +3 -0
  228. data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-possibly-test-data.md +2 -0
  229. data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-untracked.md +2 -0
  230. data/lib/principles/views/prompts/find-a-principle-to-map/what-each-command-writes.md +4 -0
  231. data/lib/principles/views/prompts/find-a-principle-to-map/where-the-inventory-looks.md +10 -0
  232. data/lib/principles/views/prompts/learn-what-a-command-does/add-member.md +45 -0
  233. data/lib/principles/views/prompts/learn-what-a-command-does/change-activation.md +21 -0
  234. data/lib/principles/views/prompts/learn-what-a-command-does/change-description.md +2 -0
  235. data/lib/principles/views/prompts/learn-what-a-command-does/change-member-check.md +17 -0
  236. data/lib/principles/views/prompts/learn-what-a-command-does/change-member-role.md +9 -0
  237. data/lib/principles/views/prompts/learn-what-a-command-does/create-principle.md +6 -0
  238. data/lib/principles/views/prompts/learn-what-a-command-does/every-command-and-how-they-chain.md +28 -0
  239. data/lib/principles/views/prompts/learn-what-a-command-does/list-candidate-files.md +17 -0
  240. data/lib/principles/views/prompts/learn-what-a-command-does/list-member-problems.md +73 -0
  241. data/lib/principles/views/prompts/learn-what-a-command-does/list-open-findings.md +71 -0
  242. data/lib/principles/views/prompts/learn-what-a-command-does/list-principles.md +7 -0
  243. data/lib/principles/views/prompts/learn-what-a-command-does/move-member.md +11 -0
  244. data/lib/principles/views/prompts/learn-what-a-command-does/remove-member-check.md +10 -0
  245. data/lib/principles/views/prompts/learn-what-a-command-does/remove-member.md +6 -0
  246. data/lib/principles/views/prompts/learn-what-a-command-does/show-answer-check.md +16 -0
  247. data/lib/principles/views/prompts/learn-what-a-command-does/show-audit.md +20 -0
  248. data/lib/principles/views/prompts/learn-what-a-command-does/show-meta-principle-passage.md +52 -0
  249. data/lib/principles/views/prompts/learn-what-a-command-does/show-principle.md +86 -0
  250. data/lib/principles/views/prompts/learn-what-a-command-does/start-audit.md +68 -0
  251. data/lib/principles/views/prompts/learn-what-a-command-does/submit-answer.md +56 -0
  252. data/lib/principles/views/prompts/repoint-write-ups/point-each-moved-write-up-at-its-new-place.md +10 -0
  253. data/lib/principles/views/prompts/report-after-a-commit/it-refused-after-the-commits-touched-the-maps.md +2 -0
  254. data/lib/principles/views/prompts/report-after-a-commit/others-are-still-listed.md +1 -0
  255. data/lib/principles/views/prompts/report-after-a-commit/the-commits-changed-these-members.md +2 -0
  256. data/lib/principles/views/prompts/report-after-a-commit/this-command-shows-each-one.md +1 -0
  257. data/lib/principles/views/prompts/report-the-completed-audit/commit-only-what-the-audit-changed.md +2 -0
  258. data/lib/principles/views/prompts/report-the-completed-audit/replace-the-default-commit-message.md +4 -0
  259. data/lib/principles/views/prompts/report-the-completed-audit/what-completion-means.md +2 -0
  260. data/lib/principles/views/prompts/set-principles-up/entry-file-passage.md +3 -0
  261. data/lib/principles/views/prompts/set-principles-up/name-the-step-each-member-lacks.md +15 -0
  262. data/lib/principles/views/prompts/set-principles-up/place-the-passage-with-the-user.md +31 -0
  263. data/lib/principles/views/prompts/set-principles-up/set-up-first.md +2 -0
  264. data/lib/principles/views/prompts/set-principles-up/what-the-passage-presumes.md +4 -0
  265. data/lib/principles/views/prompts/start-a-session/act-on-the-open-findings.md +5 -0
  266. data/lib/principles/views/prompts/start-a-session/it-returns-after-each-clear-or-compaction-too.md +1 -0
  267. data/lib/principles/views/prompts/start-a-session/it-returns-at-each-session-start.md +1 -0
  268. data/lib/principles/views/prompts/start-a-session/raise-them-at-the-next-pause.md +1 -0
  269. data/lib/principles/views/prompts/start-a-session/raise-them-before-the-first-task.md +1 -0
  270. data/lib/principles/views/prompts.rb +71 -0
  271. data/lib/principles/views/repoint_result.rb +80 -0
  272. data/lib/principles/views/request_part.rb +26 -0
  273. data/lib/principles/views/request_parts.rb +26 -0
  274. data/lib/principles/views/shell_word.rb +29 -0
  275. data/lib/principles/views/visible_text.rb +26 -0
  276. data/lib/principles/views/workflow_diagram.rb +77 -0
  277. data/lib/principles.rb +5 -0
  278. metadata +326 -0
@@ -0,0 +1,170 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "invocation"
5
+ require_relative "meta_principle_passage"
6
+ require_relative "printed_principle"
7
+
8
+ module Principles
9
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
10
+ module Views
11
+ # What show-meta-principle-passage prints: the map its printed add-member writes, the facts of the project's entry
12
+ # files, the one passage any of them would take, and the commands that map the passage once the agent has placed
13
+ # it with the user (D68, D71, D74).
14
+ class MetaPrincipleResult
15
+ # How the printed add-member commands map the passage: heading is the passage's heading, which they name as
16
+ # its member's section, beside role, the role they give that member; step_role is the role they give a
17
+ # passage the project has that carries one of the passage's steps, and place_role the one they give any other
18
+ # place that keeps the principles held, such as a timer or a settings line (D75).
19
+ class PassageMapping
20
+ #: String
21
+ attr_reader :heading, :role, :step_role, :place_role
22
+
23
+ #: (heading: String, role: String, step_role: String, place_role: String) -> void
24
+ def initialize(heading:, role:, step_role:, place_role:)
25
+ @heading = heading
26
+ @role = role
27
+ @step_role = step_role
28
+ @place_role = place_role
29
+ freeze
30
+ end
31
+ end
32
+
33
+ # mapping is how the printed commands map the passage; in_git says whether a commit can be the member's
34
+ # baseline.
35
+ #: (Invocation, PassageMapping, in_git: bool) -> void
36
+ def initialize(invocation, mapping, in_git:)
37
+ @invocation = invocation
38
+ @mapping = mapping
39
+ @in_git = in_git
40
+ freeze
41
+ end
42
+
43
+ # The passage, as the agent pastes it into an entry file, its lines kept.
44
+ #: () -> String
45
+ def passage = MetaPrinciplePassage.new(heading: @mapping.heading).text
46
+
47
+ # What the passage presumes of a teammate without principles, given its estimated size.
48
+ #: (Integer) -> String
49
+ def presumes(estimated_tokens)
50
+ Prompts.text("set-principles-up/what-the-passage-presumes", estimated_tokens: estimated_tokens.to_s)
51
+ end
52
+
53
+ # Where the passage can go, as the agent places it from: how this run was started and whether the plugin's
54
+ # session-start hook runs in this session, each root entry file's facts, and the passage's text with its size
55
+ # and what it presumes of a teammate without principles.
56
+ class Placement
57
+ #: (runs_from: String, session_start_hook_in_this_session: bool, entry_files: Array[Hash[String, untyped]],
58
+ #| passage: Hash[String, untyped]) -> void
59
+ def initialize(runs_from:, session_start_hook_in_this_session:, entry_files:, passage:)
60
+ @fields = { "runs_from" => runs_from,
61
+ "session_start_hook_in_this_session" => session_start_hook_in_this_session,
62
+ "entry_files" => entry_files, "passage" => passage }.freeze #: Hash[String, untyped]
63
+ freeze
64
+ end
65
+
66
+ #: () -> Hash[String, untyped]
67
+ def to_h = @fields
68
+
69
+ # The passage's estimated size, which loads in every session that loads the entry file it goes in.
70
+ #: () -> Integer
71
+ def estimated_tokens = @fields.fetch("passage").fetch("estimated_tokens")
72
+ end
73
+
74
+ # The map as the project holds it: already_mapped says the map was there before this run, and
75
+ # members_in_entry_files names its members already in an entry file, by id and path; set_up says the map has a
76
+ # member, so the question asks which keeping step each lacks in place of asking to place the passage (D75).
77
+ class MapInProject
78
+ #: bool
79
+ attr_reader :set_up
80
+
81
+ #: (already_mapped: bool, members_in_entry_files: Array[Hash[String, String]], set_up: bool) -> void
82
+ def initialize(already_mapped:, members_in_entry_files:, set_up:)
83
+ @fields = { "already_mapped" => already_mapped,
84
+ "members_in_entry_files" => members_in_entry_files }.freeze #: Hash[String, untyped]
85
+ @set_up = set_up
86
+ freeze
87
+ end
88
+
89
+ #: () -> Hash[String, untyped]
90
+ def to_h = @fields
91
+ end
92
+
93
+ # existing_principles lists the project's other principles as list-principles lists them.
94
+ #: (PrintedPrinciple, Placement, MapInProject, existing_principles: Array[Hash[String, untyped]])
95
+ #| -> Hash[String, untyped]
96
+ def proposal(principle, placement, map_in_project, existing_principles:)
97
+ { "status" => "question", "principle_id" => principle.id, "map_file" => principle.file,
98
+ "map" => principle.map, **map_in_project.to_h, **placement.to_h,
99
+ "existing_principles" => existing_principles,
100
+ "question" => map_in_project.set_up ? steps_lacking(principle, placement) : question(principle.id),
101
+ "answer_with" => add_member(principle.id), "next_action" => next_action(principle.id, existing_principles) }
102
+ end
103
+
104
+ private
105
+
106
+ # What to agree with the user and place, with the command that maps a passage the project has in a step's
107
+ # place, and the one that maps the passage once it is placed.
108
+ #: (String) -> String
109
+ def question(id)
110
+ Prompts.text("set-principles-up/place-the-passage-with-the-user",
111
+ command: add_member(id), existing: add_existing(id), any_place: add_place(id))
112
+ end
113
+
114
+ # Each mapped member's place and role, and which keeping step each lacks, with the passage as one way to add a
115
+ # missing step, at its size, and the command that maps another place.
116
+ #: (PrintedPrinciple, Placement) -> String
117
+ def steps_lacking(principle, placement)
118
+ members = principle.members.map { |member| "- #{member.fetch("id")}: #{member.fetch("role")}" }.join("\n")
119
+ Prompts.text("set-principles-up/name-the-step-each-member-lacks",
120
+ members: members, command: add_member(principle.id), any_place: add_place(principle.id),
121
+ estimated_tokens: placement.estimated_tokens.to_s)
122
+ end
123
+
124
+ # The add-member that maps any other place that keeps the principles held, by its path and a role naming the
125
+ # step it runs and what starts it.
126
+ #: (String) -> String
127
+ def add_place(id)
128
+ @invocation.in_project("add-member", id, usage: "PATH --role \"#{@mapping.place_role}\"")
129
+ end
130
+
131
+ # The add-member that maps a passage the project already has, which carries one of the passage's steps.
132
+ #: (String) -> String
133
+ def add_existing(id)
134
+ @invocation.in_project("add-member", id, usage: "PATH --section \"HEADING\" --role \"#{@mapping.step_role}\"")
135
+ end
136
+
137
+ #: (String) -> String
138
+ def add_member(id)
139
+ @invocation.in_project("add-member", id,
140
+ usage: "ENTRY_FILE --section \"#{@mapping.heading}\" --role \"#{@mapping.role}\"")
141
+ end
142
+
143
+ # What follows placing the passage: committing it, which outside git is not the member's baseline, and the
144
+ # first full audit, which waits until the project has a principle of its own to keep held.
145
+ #: (String, Array[Hash[String, untyped]]) -> String
146
+ def next_action(id, existing_principles)
147
+ baseline = if @in_git
148
+ "Then commit .principles/ and the entry file you changed."
149
+ else
150
+ "This principle's first full audit gives the passage's member its baseline, since this project " \
151
+ "is not in git."
152
+ end
153
+ [baseline, first_audit(id, existing_principles)].join(" ")
154
+ end
155
+
156
+ #: (String, Array[Hash[String, untyped]]) -> String
157
+ def first_audit(id, existing_principles)
158
+ if existing_principles.empty?
159
+ return "This principle's first full audit judges whether the keeping works, so leave it until one of the " \
160
+ "project's own principles is mapped.\n" \
161
+ "To find the first of them: #{@invocation.in_project("list-candidate-files")}"
162
+ end
163
+
164
+ "This principle's first full audit judges whether the keeping works for the principles under " \
165
+ "existing_principles.\n" \
166
+ "To start it: #{@invocation.in_project("start-audit", id)}"
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "prompts"
5
+ require_relative "request_part"
6
+
7
+ module Principles
8
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
9
+ module Views
10
+ # How a full audit shows the findings other principles leave open on members sharing its members' lines: each
11
+ # once, which mapped_elsewhere names by id, with what to do with them (#772, D62).
12
+ class OpenFindingsElsewhereResult
13
+ # findings are each such finding, with its principle, id, problem and action.
14
+ #: (Array[Hash[String, untyped]]) -> void
15
+ def initialize(findings)
16
+ @findings = findings
17
+ freeze
18
+ end
19
+
20
+ # The assessment request's part that lists them and says what to do with them; nil when there are none. It adds
21
+ # nothing to the template: a finding here is decided in its own principle's audit.
22
+ #: () -> RequestPart?
23
+ def part
24
+ return if @findings.empty?
25
+
26
+ instruction = Prompts.text("assess-every-member/name-another-principles-open-finding-rather-than-raise-it")
27
+ RequestPart.new(shown: { "open_findings_elsewhere" => @findings,
28
+ "open_findings_elsewhere_instruction" => instruction }, slots: {})
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,125 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "invocation"
5
+ require_relative "problem_lines"
6
+ require_relative "visible_text"
7
+ require_relative "prompts"
8
+
9
+ module Principles
10
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
11
+ module Views
12
+ # list-open-findings --short: the one line a session-start hook puts into the agent's context while any
13
+ # finding is open, and nothing when none is (D47). It says how many wait, how long the oldest has, what that
14
+ # one waits on, and the command that lists them all, worded as something to act on.
15
+ class OpenFindingsLine
16
+ # A waits_on longer than this is cut, so the line stays one short line.
17
+ WAITS_ON_LIMIT = 80
18
+
19
+ #: (Invocation) -> void
20
+ def initialize(invocation)
21
+ @invocation = invocation
22
+ freeze
23
+ end
24
+
25
+ # waiting is nil when no finding is open, or the count of open findings with the oldest one's principle,
26
+ # finding_id, days_open and, when it says, waits_on. not_found is each open finding, by principle and
27
+ # finding_id, whose write-up can no longer be found where its recorded_at points (#694). source is the
28
+ # session's start: startup, clear or compact.
29
+ #: (Hash[String, untyped]?, ?Array[Hash[String, untyped]], ?source: String) -> Lines
30
+ def short(waiting, not_found = [], source: "startup")
31
+ Lines.new(stdout: waiting ? "#{line(waiting, not_found, source)}\n" : "", stderr: "", status: 0)
32
+ end
33
+
34
+ private
35
+
36
+ #: (Hash[String, untyped], Array[Hash[String, untyped]], String) -> String
37
+ def line(waiting, not_found, source)
38
+ count = waiting.fetch("count")
39
+ them, each = pronouns(count)
40
+ Prompts.text("start-a-session/act-on-the-open-findings",
41
+ when: raise_when(source), returns: returns(source),
42
+ findings: "#{counted(count)}#{waited(waiting)} (#{named(waiting)})",
43
+ without_waits_on: without_waits_on(count, waiting.fetch("without_waits_on", 0)),
44
+ write_ups: write_ups(not_found),
45
+ them: them, each: each, command: @invocation.in_project("list-open-findings"))
46
+ end
47
+
48
+ # When to raise the findings: before the user's first task at startup, and at the next pause once the session is
49
+ # under way, after /clear or a compaction (#717).
50
+ #: (String) -> String
51
+ def raise_when(source)
52
+ return Prompts.text("start-a-session/raise-them-before-the-first-task") if source == "startup"
53
+
54
+ Prompts.text("start-a-session/raise-them-at-the-next-pause")
55
+ end
56
+
57
+ # When the line comes back: at each session start, and after each clear or compaction too (#741).
58
+ #: (String) -> String
59
+ def returns(source)
60
+ return Prompts.text("start-a-session/it-returns-at-each-session-start") if source == "startup"
61
+
62
+ Prompts.text("start-a-session/it-returns-after-each-clear-or-compaction-too")
63
+ end
64
+
65
+ # How many of several open findings record no waits_on, since the line names only the oldest's (#736). One
66
+ # open finding's own name already shows whether it does, so its line says nothing more.
67
+ #: (Integer, Integer) -> String
68
+ def without_waits_on(count, without)
69
+ return "" if count == 1 || without.zero?
70
+ return " All #{count} have no waits_on." if without == count
71
+
72
+ " #{without} #{without == 1 ? "has" : "have"} no waits_on."
73
+ end
74
+
75
+ # The words the line refers to the findings by, all of them and each one, singular or plural together.
76
+ #: (Integer) -> [String, String]
77
+ def pronouns(count)
78
+ count == 1 ? %w[it it] : %w[them each]
79
+ end
80
+
81
+ # The sentence naming the write-ups that can no longer be found, the first by its principle and id, or
82
+ # nothing when every pointer resolves, so the usual line is unchanged.
83
+ #: (Array[Hash[String, untyped]]) -> String
84
+ def write_ups(not_found)
85
+ first = not_found.first or return ""
86
+ named = "#{first.fetch("principle")} #{VisibleText.new(first.fetch("finding_id"))}"
87
+ return " The write-up of #{named} cannot be found where its recorded_at points, and the list says why." \
88
+ if not_found.one?
89
+
90
+ " The write-ups of #{not_found.length} findings, the first #{named}, cannot be found where their " \
91
+ "recorded_at points, and the list says why."
92
+ end
93
+
94
+ #: (Integer) -> String
95
+ def counted(count)
96
+ count == 1 ? "1 audit finding is still open, " : "#{count} audit findings are still open, the oldest "
97
+ end
98
+
99
+ #: (Hash[String, untyped]) -> String
100
+ def waited(waiting)
101
+ days = waiting.fetch("days_open")
102
+ return "for less than a day" if days < 1
103
+
104
+ "for #{days} #{days == 1 ? "day" : "days"}"
105
+ end
106
+
107
+ # The oldest finding's principle and id, and what it waits on, cut when long. The id and waits_on are free
108
+ # text, so each is cut by characters first and then escaped, which may run past the limit to show them (#227).
109
+ #: (Hash[String, untyped]) -> String
110
+ def named(waiting)
111
+ named = "#{waiting.fetch("principle")} #{VisibleText.new(waiting.fetch("finding_id"))}"
112
+ waits_on = waiting["waits_on"] or return named
113
+ waits_on = closed("#{waits_on[0, WAITS_ON_LIMIT - 3]}...") if waits_on.length > WAITS_ON_LIMIT
114
+ "#{named}, waits on: #{VisibleText.new(waits_on)}"
115
+ end
116
+
117
+ # A cut waits_on with a ")" for each "(" the cut left open, so the line's own parenthesis still reads as
118
+ # closing the finding's name (#685).
119
+ #: (String) -> String
120
+ def closed(cut)
121
+ cut + (")" * [cut.count("(") - cut.count(")"), 0].max)
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "principle_with_changed_members"
5
+ require_relative "prompts"
6
+ require_relative "request_part"
7
+
8
+ module Principles
9
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
10
+ module Views
11
+ # How the meta-principle's full audit states, as a fact, each other principle with members no audit has judged
12
+ # as they are now, which a check on its passage once reported, beside the release since its last audit (D71).
13
+ class OtherPrinciplesUnjudgedResult
14
+ # principles holds each other principle with such members, by id with their states; empty when there are none.
15
+ #: (Array[PrincipleWithChangedMembers]) -> void
16
+ def initialize(principles)
17
+ @principles = principles
18
+ freeze
19
+ end
20
+
21
+ # The request's part listing them under principles, with why, how to weigh them, as the release part beside it
22
+ # gives its own.
23
+ #: () -> RequestPart
24
+ def part
25
+ why = Prompts.text("assess-every-member/judge-the-keeping-against-the-other-principles")
26
+ RequestPart.new(shown: { "other_principles_with_unjudged_changes" => { "principles" => @principles.map(&:to_h),
27
+ "why" => why } },
28
+ slots: {})
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "prompts"
5
+ require_relative "request_part"
6
+
7
+ module Principles
8
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
9
+ module Views
10
+ # How a full audit's request names the files outside the map that differ from the base commit, which a check and
11
+ # the answer's evidence read as they are now (#409, #426).
12
+ class OutsideTheMapResult
13
+ # outside names the files outside, as paths and any count not_named; nil when there are none or no base commit.
14
+ #: (Hash[String, untyped]?) -> void
15
+ def initialize(outside)
16
+ @outside = outside
17
+ freeze
18
+ end
19
+
20
+ # The request's part naming the files outside, with why they matter to the answer; nil when there are none,
21
+ # and then nothing is printed.
22
+ #: () -> RequestPart?
23
+ def part
24
+ outside = @outside or return
25
+
26
+ why = Prompts.text("assess-every-member/uncommitted-files-outside-the-map")
27
+ RequestPart.new(shown: { "uncommitted_files_outside_the_map" => outside.merge("why" => why) }, slots: {})
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "invocation"
5
+ require_relative "prompts"
6
+ require_relative "request_part"
7
+
8
+ module Principles
9
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
10
+ module Views
11
+ # How the meta-principle's full audit states the release of principles its last completed audit ran under beside
12
+ # the one running now, as facts for the agent to judge (D68).
13
+ class PackageSinceLastAuditResult
14
+ # at_last_audit is the version the last completed audit ran under, or nil when none recorded one; now is the
15
+ # version running now; passage_changed says whether the passage setup proposes changed since, or is nil with
16
+ # nothing to compare. invocation names the setup command the instruction prints.
17
+ #: (Invocation, at_last_audit: String?, now: String, passage_changed: bool?) -> void
18
+ def initialize(invocation, at_last_audit:, now:, passage_changed:)
19
+ @invocation = invocation
20
+ @since = { "version_at_last_audit" => at_last_audit, "version_now" => now,
21
+ "passage_changed" => passage_changed }.freeze #: Hash[String, untyped]
22
+ freeze
23
+ end
24
+
25
+ # The request's part stating the release since the last audit, with what to do about a change.
26
+ #: () -> RequestPart
27
+ def part
28
+ why = Prompts.text("assess-every-member/judge-the-keeping-under-the-release-since-the-last-audit",
29
+ setup: @invocation.in_project("show-meta-principle-passage"))
30
+ RequestPart.new(shown: { "package_since_last_audit" => @since.merge("why" => why) }, slots: {})
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,86 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "invocation"
5
+ require_relative "printed_principle"
6
+ require_relative "member_repairs"
7
+ require_relative "prompts"
8
+
9
+ module Principles
10
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
11
+ module Views
12
+ # The JSON results of reading principles, show-principle and list-principles, with commands for the invocation
13
+ # running now. They change with what a read reports, such as freshness and repairs; MapResult prints the writes.
14
+ class PrincipleReadResult
15
+ #: (Invocation) -> void
16
+ def initialize(invocation)
17
+ @invocation = invocation
18
+ freeze
19
+ end
20
+
21
+ # show-principle: each member with where its text is now and its freshness, and each member in broken, the ids
22
+ # of those an audit would refuse, with its repair_with commands (#272).
23
+ # hook_settings_not_read names each project settings file whose hooks this tool could not read (#491).
24
+ # loaded_files lists each file the members load once, with the total of those loading at session start (#483).
25
+ #: (PrintedPrinciple, broken: Array[String], ?hook_settings_not_read: Array[Hash[String, String]],
26
+ #| ?loaded_files: Hash[String, untyped]) -> Hash[String, untyped]
27
+ def shown(principle, broken:, hook_settings_not_read: [], loaded_files: {})
28
+ unread = hook_settings_not_read.empty? ? {} : { "hook_settings_not_read" => hook_settings_not_read }
29
+ { "id" => principle.id, "map_file" => principle.file, **principle.declared,
30
+ "members" => principle.members.map { |member| repaired(principle.id, member, broken) },
31
+ **loaded_files, **unread, "next_action" => shown_next_action(principle, broken) }
32
+ end
33
+
34
+ # list-principles: each entry is { "id", "description", "members_by_freshness" }. Until principles is set_up,
35
+ # its next_action puts setting it up first.
36
+ #: (Array[Hash[String, untyped]], set_up: bool) -> Hash[String, untyped]
37
+ def listing(entries, set_up:)
38
+ next_action = if entries.empty?
39
+ "To map one, start with: #{@invocation.in_project("list-candidate-files")}"
40
+ else
41
+ "To see a principle's members: #{@invocation.in_project("show-principle", usage: "ID")}\n" \
42
+ "To audit it: #{@invocation.in_project("start-audit", usage: "ID")}"
43
+ end
44
+ first = set_up ? [] : [set_up_first]
45
+ { "principles" => entries, "next_action" => [*first, next_action].join("\n") }
46
+ end
47
+
48
+ # list-principles' note on the meta-principle's map, kept in file, when it has no members, with the two ways on
49
+ # (#931).
50
+ #: (String) -> Hash[String, String]
51
+ def no_members(file)
52
+ setup = @invocation.in_project("show-meta-principle-passage")
53
+ { "no_members" => "This map has no members, as a setup that wrote it before its first member, or removing " \
54
+ "its last member, leaves it. Either remove #{file} and commit the removal, or add its " \
55
+ "first member with the add-member this prints: #{setup}" }
56
+ end
57
+
58
+ private
59
+
60
+ # The line that puts setting principles up first, ending in its command.
61
+ #: () -> String
62
+ def set_up_first
63
+ Prompts.text("set-principles-up/set-up-first",
64
+ command: @invocation.in_project("show-meta-principle-passage"))
65
+ end
66
+
67
+ # member, with its repairs when broken names it.
68
+ #: (String, Hash[String, untyped], Array[String]) -> Hash[String, untyped]
69
+ def repaired(id, member, broken)
70
+ MemberRepairs.new(@invocation).beside(id, member, broken: broken.include?(member.fetch("id")))
71
+ end
72
+
73
+ # Repair each member in broken, the ids of those an audit would refuse; otherwise audit when one changed or was
74
+ # never assessed.
75
+ #: (PrintedPrinciple, Array[String]) -> String
76
+ def shown_next_action(principle, broken)
77
+ unless broken.empty?
78
+ return "Repair each member that cannot be found or read before auditing, with one of the commands in " \
79
+ "its repair_with: #{broken.join(", ")}."
80
+ end
81
+
82
+ "Audit when a member is changed or unassessed: #{@invocation.in_project("start-audit", principle.id)}"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Principles
5
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
6
+ module Views
7
+ # One other principle whose members a completed audit's fixes changed or broke, as its result names it under
8
+ # other_principles_with_changed_members, to re-audit by its id (#410, #823).
9
+ class PrincipleWithChangedMembers
10
+ #: (principle_id: String, members: Array[Hash[String, untyped]]) -> void
11
+ def initialize(principle_id:, members:)
12
+ @principle_id = principle_id
13
+ @members = members
14
+ freeze
15
+ end
16
+
17
+ # The principle to re-audit.
18
+ #: String
19
+ attr_reader :principle_id
20
+
21
+ # The fields as printed: the principle's id and each changed or broken member, by id with its state.
22
+ #: () -> Hash[String, untyped]
23
+ def to_h = { "principle_id" => @principle_id, "members" => @members }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../prose_list"
5
+
6
+ module Principles
7
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
8
+ module Views
9
+ # The principles no full audit has completed, as list-member-problems' next_action names them in one line, then
10
+ # each one's audit to start on a line of its own, filled and shell-quoted so the agent types no id (D58, #731,
11
+ # #755).
12
+ class PrinciplesWithNoCompletedAudit
13
+ # audits holds each principle's start-audit command, in the order of ids.
14
+ #: (ids: Array[String], audits: Array[String]) -> void
15
+ def initialize(ids:, audits:)
16
+ @ids = ids
17
+ @audits = audits
18
+ freeze
19
+ end
20
+
21
+ #: () -> String
22
+ def to_s
23
+ one = @ids.length == 1
24
+ carry = one ? "its members carry it" : "their members carry them"
25
+ "No full audit has completed for #{ProseList.new(@ids)}, so whether #{carry} is unknown. " \
26
+ "To start #{one ? "one" : "each"}:\n#{@audits.join("\n")}"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Principles
5
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
6
+ module Views
7
+ # A principle as printed: its id, map file, description and members. Each member is a plain hash that
8
+ # already carries its "id", and on show-principle its snapshot and freshness too.
9
+ class PrintedPrinciple
10
+ #: String
11
+ attr_reader :id
12
+ #: String
13
+ attr_reader :file
14
+ #: String?
15
+ attr_reader :description
16
+ #: Array[Hash[String, untyped]]
17
+ attr_reader :members
18
+ #: Hash[String, untyped]?
19
+ attr_reader :activation
20
+
21
+ # activation is when the agent said the principle's work is in hand, or nil when it has not said (D38).
22
+ #: (id: String, file: String, description: String?, members: Array[Hash[String, untyped]],
23
+ #| ?activation: Hash[String, untyped]?) -> void
24
+ def initialize(id:, file:, description:, members:, activation: nil)
25
+ @id = id
26
+ @file = file
27
+ @description = description
28
+ @members = members.freeze #: Array[Hash[String, untyped]]
29
+ @activation = activation #: Hash[String, untyped]?
30
+ freeze
31
+ end
32
+
33
+ # What the principle declares as a whole: its description, and its activation once the agent has said it.
34
+ #: () -> Hash[String, untyped]
35
+ def declared
36
+ { "description" => @description, **({ "activation" => @activation } if @activation) }
37
+ end
38
+
39
+ #: () -> Hash[String, untyped]
40
+ def map
41
+ { **declared, "members" => @members }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Principles
5
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
6
+ module Views
7
+ # How a completed full audit's result says what it decided about each prior finding (D35).
8
+ class PriorFindingDecisionsResult
9
+ # kept_ids are the prior findings it kept and closed those it closed; dropped is any pointer a kept one's
10
+ # decision dropped (#374), waits_on_dropped any waits_on it dropped, with null or by accepting it (D52), and
11
+ # evidence_replaced each kept evidence item a decision gave a new place, as it was and is now (D70).
12
+ #: (kept_ids: Array[String], closed: Array[Hash[String, untyped]], dropped: Array[Hash[String, untyped]],
13
+ #| waits_on_dropped: Array[Hash[String, untyped]], evidence_replaced: Array[Hash[String, untyped]]) -> void
14
+ def initialize(kept_ids:, closed:, dropped:, waits_on_dropped:, evidence_replaced:)
15
+ @kept_ids = kept_ids
16
+ @closed = closed
17
+ @dropped = dropped
18
+ @waits_on_dropped = waits_on_dropped
19
+ @evidence_replaced = evidence_replaced
20
+ freeze
21
+ end
22
+
23
+ # output, with these decisions added; unchanged when there were no prior findings to decide.
24
+ #: (Hash[String, untyped]) -> Hash[String, untyped]
25
+ def added_to(output)
26
+ return output if @kept_ids.empty? && @closed.empty?
27
+
28
+ decided = output.merge("prior_findings_kept" => @kept_ids, "prior_findings_closed" => @closed)
29
+ decided = decided.merge("prior_findings_pointers_dropped" => @dropped) unless @dropped.empty?
30
+ decided = decided.merge("prior_findings_waits_on_dropped" => @waits_on_dropped) unless @waits_on_dropped.empty?
31
+ @evidence_replaced.empty? ? decided : decided.merge("prior_findings_evidence_replaced" => @evidence_replaced)
32
+ end
33
+ end
34
+ end
35
+ end