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,55 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "prompts"
5
+ require_relative "../prose_list"
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 completed audit in git says to commit what it changed: the command committing those files alone, with
11
+ # its default subject, which AuditChanges#commit builds. ByHand is the form outside git, with no command.
12
+ class AuditCommit
13
+ #: (files_to_commit: Array[String], subject: String, command: String) -> void
14
+ def initialize(files_to_commit:, subject:, command:)
15
+ @files_to_commit = files_to_commit
16
+ @subject = subject
17
+ @command = command
18
+ freeze
19
+ end
20
+
21
+ # The command, and beside it what replacing its default subject asks, since an agent reading commit_with
22
+ # alone would otherwise run the default (#853).
23
+ #: () -> Hash[String, String]
24
+ def fields
25
+ { "commit_with" => @command,
26
+ "commit_message_to_replace" => Prompts.text("report-the-completed-audit/replace-the-default-commit-message",
27
+ subject: @subject) }
28
+ end
29
+
30
+ # The step that commits the files, the command ending its line (#811), up to the report that follows; its
31
+ # wording is its prompt's. Why the command stops during a merge is its own to say, on stderr (#828).
32
+ #: () -> String
33
+ def next_step
34
+ commit = Prompts.text("report-the-completed-audit/commit-only-what-the-audit-changed",
35
+ files: ProseList.new(@files_to_commit).to_s, command: @command)
36
+ "#{commit}\nThen"
37
+ end
38
+
39
+ # Outside git, where no command could run as printed: only the files to commit.
40
+ class ByHand
41
+ #: (Array[String]) -> void
42
+ def initialize(files_to_commit)
43
+ @files_to_commit = files_to_commit
44
+ freeze
45
+ end
46
+
47
+ #: () -> Hash[String, String]
48
+ def fields = {}
49
+
50
+ #: () -> String
51
+ def next_step = "Commit #{ProseList.new(@files_to_commit)}. Then"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,147 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "assessment_request"
5
+ require_relative "audit_changes"
6
+ require_relative "invocation"
7
+ require_relative "prior_finding_decisions_result"
8
+ require_relative "prompts"
9
+
10
+ module Principles
11
+ # What this tool prints, from plain values a controller hands it. views/CLAUDE.md owns the boundary.
12
+ module Views
13
+ # The JSON result of an audit command. Printed commands name the executable and project that are
14
+ # running now, so they are added here each time: a request stays answerable after a move or reinstall
15
+ # (alpha 008 friction 16). A completed result names the files to commit and how to show the record,
16
+ # not what the agent has just written (alpha 008 friction 14, alpha 009 friction 3, alpha 010
17
+ # friction 2).
18
+ class AuditResult
19
+ #: (Invocation) -> void
20
+ def initialize(invocation)
21
+ @invocation = invocation
22
+ freeze
23
+ end
24
+
25
+ #: (Hash[String, untyped]) -> Hash[String, untyped]
26
+ def present(output)
27
+ output["status"] == "agent_judgement_required" ? request(output) : output
28
+ end
29
+
30
+ # show-answer-check: the answer passed every check, and what submitting it would write. open_findings_will_print
31
+ # previews each written-up finding's write_up_opening, and is nil when the record would keep none (#411).
32
+ # map_entry_changes gives each claimed map entry's changed role and check, left out when there is none (#462).
33
+ # answer_file is the --file value the answer was checked from, which the submit command gives again (#859).
34
+ #: (String, Array[String], answer_file: String, ?open_findings_will_print: Array[Hash[String, untyped]]?,
35
+ #| ?map_entry_changes: Array[Hash[String, untyped]]) -> Hash[String, untyped]
36
+ def acceptable(request_id, would_write, answer_file:, open_findings_will_print: nil, map_entry_changes: [])
37
+ submit = @invocation.submit_answer(request_id, file: answer_file)
38
+ waits = "Submitting it writes nothing yet: the audit record is written when the audit completes.\n"
39
+ waits = "" unless would_write.empty?
40
+ result = { "status" => "answer_acceptable", "request_id" => request_id, "would_write" => would_write }
41
+ result = result.merge("open_findings_will_print" => open_findings_will_print) if open_findings_will_print
42
+ result = result.merge("map_entry_changes" => map_entry_changes) unless map_entry_changes.empty?
43
+ result.merge("next_action" => "#{waits}Submit the same answer unchanged, as you gave it: #{submit}")
44
+ end
45
+
46
+ # start-audit: the assessment request, as a judgement to answer under request_id.
47
+ #: (request_id: String, request: AssessmentRequest) -> Hash[String, untyped]
48
+ def assessment_request(request_id:, request:)
49
+ judgement("assessment", request_id, request.context, request.template)
50
+ end
51
+
52
+ # The correction request that follows an assessment with findings: one disposition for each, with a guide
53
+ # to every disposition's shape (#414). It says what was stored, so an assessment submitted twice plainly
54
+ # replaced nothing: in git nothing is, and outside git, where basis has no base commit, the open record
55
+ # keeps what the audit began from.
56
+ # placeholders is the text the template leaves in each field the agent writes (#725).
57
+ #: (request_id: String, principle_id: String, findings: Array[untyped], basis: Hash[String, untyped],
58
+ #| placeholders: Hash[String, String]) -> Hash[String, untyped]
59
+ def correction_request(request_id:, principle_id:, findings:, basis:, placeholders:)
60
+ context = { "principle_id" => principle_id, "findings" => findings,
61
+ "question" => Prompts.text("dispose-of-each-finding/fix-or-record-each-finding"),
62
+ "answer_guide" => Prompts.data("dispose-of-each-finding/answer-guide") }
63
+ template = { "basis" => basis, "dispositions" => findings.map do |finding|
64
+ { "finding_id" => finding["id"], "action" => nil, "reason" => placeholders.fetch("reason"),
65
+ "changed_members" => [], "evidence" => [],
66
+ "reassessment" => placeholders.fetch("reassessment"), "recorded_at" => nil,
67
+ "waits_on" => nil }
68
+ end }
69
+ judgement("correction", request_id, context, template).merge("stored" => stored(basis["base_commit"]))
70
+ end
71
+
72
+ # A completed audit: its resolved and open findings, the files to commit and, in git, each changed
73
+ # member's commits since the base commit and the command that commits those files alone (#811), each prior
74
+ # finding's decision, and the next step. The command's subject states only what the audit did, which the
75
+ # agent rewords to its project's convention.
76
+ #: (principle_id: String, resolved: Array[untyped], open: Array[untyped], changes: AuditChanges,
77
+ #| decisions: PriorFindingDecisionsResult) -> Hash[String, untyped]
78
+ def completion(principle_id:, resolved:, open:, changes:, decisions:)
79
+ changed = changes.to_h
80
+ commit = changes.commit("Audit #{principle_id}: #{resolved.length} resolved, #{open.length} open",
81
+ @invocation.project)
82
+ output = { "status" => "audit_completed", "principle_id" => principle_id, "resolved_findings" => resolved,
83
+ "open_findings" => open, "files_to_commit" => changes.files_to_commit,
84
+ "limits" => Prompts.text("report-the-completed-audit/what-completion-means"),
85
+ **changed.except("files_to_commit"), **commit.fields }
86
+ next_action = completed_next_action(principle_id, changes, commit)
87
+ decisions.added_to(output).merge("next_action" => next_action,
88
+ "full_record" => @invocation.in_project("show-audit", principle_id))
89
+ end
90
+
91
+ # The notice start-audit adds when the last audit's record is not committed.
92
+ #: (String) -> String
93
+ def uncommitted_record_notice(file)
94
+ "The last audit's record, #{file}, is not committed. Commit it, so the project keeps it."
95
+ end
96
+
97
+ private
98
+
99
+ # What submitting the assessment stored, and what submitting it again would do: in git, where the
100
+ # audit has a base commit, nothing; outside git, the open record.
101
+ #: (String?) -> String
102
+ def stored(base_commit)
103
+ again = "Submitting the same assessment again prints this same request"
104
+ unless base_commit
105
+ return "This audit's open record, listed under wrote when it was first written, keeps what the audit " \
106
+ "began from. #{again} and leaves that record as it is."
107
+ end
108
+
109
+ "Nothing is stored yet: this request was computed from your assessment and the project. #{again} and " \
110
+ "replaces nothing; the audit record is written when the audit completes."
111
+ end
112
+
113
+ #: (String, String, Hash[String, untyped], Hash[String, untyped]) -> Hash[String, untyped]
114
+ def judgement(phase, request_id, context, template)
115
+ { "status" => "agent_judgement_required", "request_id" => request_id, "phase" => phase,
116
+ "context" => context, "response_template" => { "request_id" => request_id }.merge(template),
117
+ "limits" => Prompts.text("answer-any-judgement-request/what-a-self-audit-cannot-show") }
118
+ end
119
+
120
+ #: (Hash[String, untyped]) -> Hash[String, untyped]
121
+ def request(output)
122
+ submit = @invocation.submit_answer(output.fetch("request_id"))
123
+ output.merge("project" => @invocation.project, "answer_with" => submit)
124
+ end
125
+
126
+ # A completed audit's next step, from what it changed and how to commit it.
127
+ #: (String, AuditChanges, AuditCommit | AuditCommit::ByHand) -> String
128
+ def completed_next_action(id, changes, commit)
129
+ "#{commit.next_step} report the outcome to the project's owner.\n" \
130
+ "#{others_changed(changes.other_changed)}" \
131
+ "After members change, audit this principle again: #{@invocation.in_project("start-audit", id)}"
132
+ end
133
+
134
+ # The other principles whose members this audit's fixes changed, each to re-audit, or one member's edit
135
+ # to acknowledge; the agent judges which (#410). Nothing when there are none.
136
+ #: (Array[PrincipleWithChangedMembers]) -> String
137
+ def others_changed(others)
138
+ return "" if others.empty?
139
+
140
+ commands = others.map { |each| "#{@invocation.in_project("start-audit", each.principle_id)}\n" }
141
+ "This audit's fixes also changed or broke members of other principles, listed under " \
142
+ "other_principles_with_changed_members. Repair a broken one from show-principle's repair_with. To " \
143
+ "acknowledge one member's edit instead, add --member MEMBER to its audit. Re-audit each:\n#{commands.join}"
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,95 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "invocation"
5
+ require_relative "prompts"
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
+ # list-candidate-files' result: the files that look like agent guidance, and the question of which
11
+ # principle several of them reinforce, asked before anything is mapped.
12
+ class CandidateFilesResult
13
+ # What the search for candidates found, as plain values: the inventory, the files git does not track, nil
14
+ # outside git, where nothing checked them (#386), and the lists to ask the owner about, each with the paths to
15
+ # name in it (#405, #412).
16
+ class Found
17
+ #: Hash[String, Array[String]]
18
+ attr_reader :owner_asks
19
+
20
+ #: (inventory: Hash[String, untyped], untracked: Array[String]?,
21
+ #| owner_asks: Hash[String, Array[String]]) -> void
22
+ def initialize(inventory:, untracked:, owner_asks:)
23
+ @inventory = inventory
24
+ @untracked = untracked
25
+ @owner_asks = owner_asks
26
+ freeze
27
+ end
28
+
29
+ # The inventory as printed: each file by path, size and link, where the inventory looks, and untracked only
30
+ # in git.
31
+ #: () -> Hash[String, untyped]
32
+ def listed
33
+ files = @inventory.fetch("files").map { |file| file.slice("path", "characters", "symlink_to") }
34
+ listed = @inventory.merge("files" => files,
35
+ "scope" => Prompts.text("find-a-principle-to-map/where-the-inventory-looks"))
36
+ @untracked ? listed.merge("untracked" => @untracked) : listed
37
+ end
38
+ end
39
+
40
+ # The placeholders the printed create-principle command leaves for the agent to fill.
41
+ CREATE_PRINCIPLE_USAGE = "ID --description \"What the principle is and why it matters\""
42
+
43
+ #: (Invocation) -> void
44
+ def initialize(invocation)
45
+ @invocation = invocation
46
+ freeze
47
+ end
48
+
49
+ # The inventory, and the question of which principle to map, with one item for each list under found's owner_asks
50
+ # to ask the owner about. Until principles is set_up, next_action puts setting it up first.
51
+ #: (Found, existing_maps: Array[String], map_file: String, set_up: bool) -> Hash[String, untyped]
52
+ def question(found, existing_maps:, map_file:, set_up:)
53
+ { "status" => "question", "inventory" => found.listed, "existing_maps" => existing_maps,
54
+ "writes" => Prompts.text("find-a-principle-to-map/what-each-command-writes", map_file: map_file),
55
+ "question" => principle_question(found.owner_asks),
56
+ "answer_with" => @invocation.in_project("create-principle", usage: CREATE_PRINCIPLE_USAGE),
57
+ **(set_up ? {} : { "next_action" => set_up_first }) }
58
+ end
59
+
60
+ private
61
+
62
+ # The line that puts setting principles up first, ending in its command.
63
+ #: () -> String
64
+ def set_up_first
65
+ Prompts.text("set-principles-up/set-up-first",
66
+ command: @invocation.in_project("show-meta-principle-passage"))
67
+ end
68
+
69
+ # Which principle to map, then one item per list to ask the owner about.
70
+ #: (Hash[String, Array[String]]) -> String
71
+ def principle_question(owner_asks)
72
+ asks = owner_asks.map { |key, paths| "- #{owner_ask(key, named: inline(paths))}" }
73
+ [Prompts.text("find-a-principle-to-map/choose-one-principle"),
74
+ *([Prompts.text("find-a-principle-to-map/ask-the-owner-before-mapping"), *asks].join("\n") if asks.any?)]
75
+ .join("\n\n")
76
+ end
77
+
78
+ # The paths an ask names after its list's name, each quoted, since a path may hold a comma; none, nothing.
79
+ #: (Array[String]) -> String
80
+ def inline(paths) = paths.empty? ? "" : " (#{paths.map { |path| "`#{path}`" }.join(", ")})"
81
+
82
+ # The ask about one inventory list, naming the paths after the list's name.
83
+ #: (String, named: String) -> String
84
+ def owner_ask(key, named:)
85
+ case key
86
+ when "possibly_test_data" then Prompts.text("find-a-principle-to-map/owner-ask-possibly-test-data", named:)
87
+ when "untracked" then Prompts.text("find-a-principle-to-map/owner-ask-untracked", named:)
88
+ when "ignored" then Prompts.text("find-a-principle-to-map/owner-ask-ignored", named:)
89
+ when "ignored_folders" then Prompts.text("find-a-principle-to-map/owner-ask-ignored-folders", named:)
90
+ else raise ArgumentError, "no owner ask for #{key}"
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,75 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "prompts"
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 check_result a checked member's assessment template starts with: its command, and a not_run_reason
10
+ # that says what to do before replacing it. A check no audit has run, or one changed since, is reviewed
11
+ # before it runs (#227), and any hidden character it holds is named where that decision is made (#260).
12
+ class CheckResultTemplate
13
+ #: (String, Hash[String, untyped]?, ?Array[String]) -> void
14
+ def initialize(check, since_last_audit, hidden_characters = [])
15
+ @check = check
16
+ @since_last_audit = since_last_audit
17
+ @hidden_characters = hidden_characters
18
+ freeze
19
+ end
20
+
21
+ #: () -> Hash[String, untyped]
22
+ def to_h
23
+ { "command" => @check, "not_run_reason" => [hidden_characters, not_run_reason].compact.join(" ") }
24
+ end
25
+
26
+ private
27
+
28
+ #: () -> String?
29
+ def hidden_characters
30
+ return if @hidden_characters.empty?
31
+
32
+ Prompts.text("assess-every-member/have-hidden-characters-in-a-check-explained",
33
+ characters: @hidden_characters.join("; "))
34
+ end
35
+
36
+ # What to do before replacing the field, then that a named check runs at every full audit, which holds for
37
+ # each status.
38
+ #: () -> String
39
+ def not_run_reason
40
+ [*steps, Prompts.text("assess-every-member/run-a-named-check-at-every-full-audit")].join(" ")
41
+ end
42
+
43
+ # An unchanged check is run and reported, keeping any line its last review rested on; a new or changed one is
44
+ # reviewed first, its review introduced by what it is, then run and reported with what that review rested on,
45
+ # as both ask the same way.
46
+ #: () -> Array[String]
47
+ def steps
48
+ case @since_last_audit&.fetch("status")
49
+ when "unchanged" then [Prompts.text("assess-every-member/run-the-check-then-report-it"), *kept_review]
50
+ when "changed"
51
+ [Prompts.text("assess-every-member/have-a-changed-check-reviewed-before-running-it"), run_reviewed]
52
+ else [Prompts.text("assess-every-member/have-a-new-check-reviewed-before-running-it"), run_reviewed]
53
+ end
54
+ end
55
+
56
+ # That a run keeps the line the last audit's review rested on, quoted with since when, unless a new one is
57
+ # given, so whether it still holds can be judged from the template (#849); a record from before D63 kept none,
58
+ # so there is nothing to say (#826).
59
+ #: () -> Array[String]
60
+ def kept_review
61
+ return [] unless @since_last_audit&.key?("review_basis")
62
+
63
+ [Prompts.text("assess-every-member/keep-the-last-review-basis-unless-you-give-a-new-one",
64
+ since: @since_last_audit.fetch("review_basis_since"),
65
+ basis: @since_last_audit.fetch("review_basis"))]
66
+ end
67
+
68
+ # Run a reviewed check, then report it with what its review rested on.
69
+ #: () -> String
70
+ def run_reviewed
71
+ Prompts.text("assess-every-member/run-the-reviewed-check-and-say-what-its-review-rested-on")
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "shell_word"
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 command that shows the edits to a member's file since the last full audit's base commit, to the files as
10
+ # they are now, which the audit judges. Each word is quoted for the shell, and each path read by git as written,
11
+ # not as a pattern (#558). Renames are followed, so over a moved file's path then and now the diff shows the
12
+ # edit, not a new file (#557).
13
+ class DiffSinceLastAudit
14
+ # last_audited_commit is the last full audit's base commit; paths are the member's file then and now, once each.
15
+ #: (last_audited_commit: String, paths: Array[String]) -> void
16
+ def initialize(last_audited_commit:, paths:)
17
+ @last_audited_commit = last_audited_commit
18
+ @paths = paths
19
+ freeze
20
+ end
21
+
22
+ #: () -> String
23
+ def to_s
24
+ words = [@last_audited_commit, "--", *@paths].map { |word| ShellWord.new(word).to_s }
25
+ "git --literal-pathspecs diff -M #{words.join(" ")}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "invocation"
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
+ # list-open-findings' result: each finding the last completed audit of a principle deferred or accepted, what that
10
+ # covers, and what to do next (#292, D35).
11
+ class FindingsResult
12
+ SCOPE = "Each principle's last completed audit only, as .principles/audits/ID.json holds it. The next full " \
13
+ "audit decides each: one that still holds stays in the record, and one it closes is then only in git: " \
14
+ "git log -p -- .principles/audits/ID.json shows it."
15
+
16
+ NONE_OPEN = "No finding is deferred or accepted in the last completed audit of any principle listed."
17
+
18
+ #: (Invocation) -> void
19
+ def initialize(invocation)
20
+ @invocation = invocation
21
+ freeze
22
+ end
23
+
24
+ # findings holds each deferred or accepted finding, with its principle and action.
25
+ #: (Array[Hash[String, untyped]]) -> Hash[String, untyped]
26
+ def open(findings)
27
+ { "findings" => findings, "scope" => SCOPE, "next_action" => next_action(findings) }
28
+ end
29
+
30
+ private
31
+
32
+ #: (Array[Hash[String, untyped]]) -> String
33
+ def next_action(findings)
34
+ return NONE_OPEN if findings.empty?
35
+
36
+ audits = findings.map { |finding| finding.fetch("principle") }.uniq.map do |id|
37
+ @invocation.in_project("start-audit", id)
38
+ end
39
+ "Read each finding where its recorded_at points; null means it was never written up. A deferral's waits_on " \
40
+ "is whom it was asked to wait on, to decide what, as of waits_on_since: check that it still holds before " \
41
+ "chasing it. Its principle's next full audit asks you to decide each one:\n#{audits.join("\n")}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,119 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "prompts"
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
+ # What --help prints: Markdown, because it is a prompt the agent reads before running a command (ticket 001
10
+ # D15). It is kept short, since every line lands in the agent's context. Refusal codes stay out: a refusal
11
+ # arrives with its own code and repair when it happens.
12
+ module Help
13
+ # What the page says after an agent's step: what the tool then checks, what it cannot check, and the
14
+ # usage of the command the agent runs next.
15
+ class Handover
16
+ #: String?
17
+ attr_reader :checks
18
+ #: String?
19
+ attr_reader :cannot_check
20
+ #: String?
21
+ attr_reader :then_run
22
+ # When the agent runs then_run, where it is the agent's to time; "Then" otherwise (#627).
23
+ #: String?
24
+ attr_reader :then_run_when
25
+
26
+ #: (?checks: String?, ?cannot_check: String?, ?then_run: String?, ?then_run_when: String?) -> void
27
+ def initialize(checks: nil, cannot_check: nil, then_run: nil, then_run_when: nil)
28
+ @checks = checks
29
+ @cannot_check = cannot_check
30
+ @then_run = then_run
31
+ @then_run_when = then_run_when
32
+ freeze
33
+ end
34
+
35
+ # The command to run next ends its line, with no backticks, so it pastes as it stands.
36
+ #: (String) -> Array[String]
37
+ def lines(executable)
38
+ [("The tool then checks that #{checks}" if checks), ("It cannot check #{cannot_check}" if cannot_check),
39
+ ("#{then_run_when ? "#{then_run_when}, run" : "Then run"}: #{executable} #{then_run}" if then_run)].compact
40
+ end
41
+ end
42
+
43
+ # One numbered step as the page prints it: whose step it is, what it does, what follows an agent's
44
+ # step, and each branch's condition with the number of the step it leads to.
45
+ class Step
46
+ #: bool
47
+ attr_reader :agent
48
+ #: String
49
+ attr_reader :does
50
+ #: Handover
51
+ attr_reader :handover
52
+ #: Array[[String, Integer]]
53
+ attr_reader :branches
54
+
55
+ #: (agent: bool, does: String, ?handover: Handover, ?branches: Array[[String, Integer]]) -> void
56
+ def initialize(agent:, does:, handover: Handover.new, branches: [])
57
+ @agent = agent
58
+ @does = does
59
+ @handover = handover
60
+ @branches = branches.freeze #: Array[[String, Integer]]
61
+ freeze
62
+ end
63
+ end
64
+
65
+ # The overview: what the tool is, each group's heading with its commands' usage and summary, and how
66
+ # the commands chain, with each command it prints started by the running executable.
67
+ class Overview
68
+ #: (version: String, groups: Array[[String, Array[[String, String]]]], executable: String) -> void
69
+ def initialize(version:, groups:, executable:)
70
+ @version = version
71
+ @groups = groups
72
+ @executable = executable
73
+ freeze
74
+ end
75
+
76
+ #: () -> String
77
+ def to_s
78
+ commands = @groups.map do |heading, entries|
79
+ "## #{heading}\n\n#{entries.map { |usage, summary| "- `#{usage}`: #{summary}" }.join("\n")}"
80
+ end
81
+ Prompts.markdown("learn-what-a-command-does/every-command-and-how-they-chain",
82
+ version: @version, commands: commands.join("\n\n"), executable: @executable)
83
+ end
84
+ end
85
+
86
+ # One command's page, headed by the command as the running executable takes it: its prose prompt, what it
87
+ # writes, then its numbered steps.
88
+ class CommandPage
89
+ #: (usage: String, prompt: String, writes: String, steps: Array[Step], executable: String) -> void
90
+ def initialize(usage:, prompt:, writes:, steps:, executable:)
91
+ @usage = usage
92
+ @executable = executable
93
+ @prompt = prompt
94
+ @writes = writes
95
+ @steps = steps
96
+ freeze
97
+ end
98
+
99
+ #: () -> String
100
+ def to_s
101
+ numbered = @steps.each_with_index.map { |step, index| "#{index + 1}. #{step_text(step)}" }
102
+ ["# #{@executable} #{@usage}", Prompts.markdown(@prompt, executable: @executable), @writes, "## Steps",
103
+ numbered.join("\n"), "A step that fails prints a refusal with its code and a repair.",
104
+ "`--project PATH` is optional: it defaults to the current directory."].join("\n\n")
105
+ end
106
+
107
+ private
108
+
109
+ #: (Step) -> String
110
+ def step_text(step)
111
+ branches = step.branches.map { |condition, number| "If #{condition}: step #{number}." }
112
+ [step.agent ? "**You:** #{step.does}" : "The tool: #{step.does}", *step.handover.lines(@executable),
113
+ *branches]
114
+ .join("\n ")
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,66 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "shell_word"
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
+ # How the running tool is invoked, so a printed command names the executable and project that are
10
+ # running now. A controller builds it for each run; a view uses it to print commands, and it is never
11
+ # stored (alpha 008 friction 16).
12
+ class Invocation
13
+ #: String
14
+ attr_reader :executable
15
+ #: String
16
+ attr_reader :project
17
+
18
+ #: (executable: String, project: String) -> void
19
+ def initialize(executable:, project:)
20
+ @executable = executable
21
+ @project = project
22
+ freeze
23
+ end
24
+
25
+ # A command to run in this project, which it names right after the executable, as model repairs do (#580).
26
+ # Each value is shell-quoted, because ids, paths and headings come from project files that anyone may have
27
+ # written (#227). options are flags with their values, such as
28
+ # ["--starting-at", text], quoted the same way. usage is this tool's own placeholder text, such as
29
+ # "--section HEADING", left for the agent to fill and never quoted. When a value starts with "-", as a
30
+ # path may, every option comes first and "--" ends them, so the value is not read as an option (#240).
31
+ #: (String, *String, ?options: Array[String], ?usage: String?) -> String
32
+ def in_project(command, *values, options: [], usage: nil) = listed_in_project(command, values, options:, usage:)
33
+
34
+ # The command that answers request request_id in this project: from file, the --file value an answer was
35
+ # checked from, as given, or before there is one, from a FILE the agent names (#859).
36
+ #: (String, ?file: String?) -> String
37
+ def submit_answer(request_id, file: nil)
38
+ return in_project("submit-answer", request_id, usage: "--file FILE") unless file
39
+
40
+ in_project("submit-answer", request_id, options: ["--file", file])
41
+ end
42
+
43
+ # The same command, its values given as one list, as a refusal's repair command holds them (D56).
44
+ #: (String, Array[String], ?options: Array[String], ?usage: String?) -> String
45
+ def listed_in_project(command, values, options: [], usage: nil)
46
+ flags = options.map { |value| shell_word(value) }
47
+ words = values.map { |value| shell_word(value) }
48
+ if values.any? { |value| value.start_with?("-") }
49
+ return [@executable, project_option, command, *flags, usage, "--", *words].compact.join(" ")
50
+ end
51
+
52
+ [@executable, project_option, command, *words, *flags, usage].compact.join(" ")
53
+ end
54
+
55
+ #: () -> String
56
+ def project_option
57
+ "--project #{shell_word(@project)}"
58
+ end
59
+
60
+ private
61
+
62
+ #: (String) -> String
63
+ def shell_word(value) = ShellWord.new(value).to_s
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,27 @@
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
+ # In git, what landed after a completed audit's base commit, as its result prints it: the commits since the base
8
+ # that touched each member the audit changed, by member id, and the base and HEAD when HEAD differs from the base
9
+ # and none touched one (#740). Outside git a completed audit has none, so it is given only in git.
10
+ class LandedSinceBase
11
+ #: (commits_by_member: Hash[String, Array[Hash[String, String]]], ?head_past_base: [String, String]?) -> void
12
+ def initialize(commits_by_member:, head_past_base: nil)
13
+ @commits_by_member = commits_by_member
14
+ @head_past_base = head_past_base
15
+ freeze
16
+ end
17
+
18
+ # The fields as printed: commits_since_base, and head_moved_past_base only when head_past_base is given.
19
+ #: () -> Hash[String, untyped]
20
+ def to_h
21
+ printed = { "commits_since_base" => @commits_by_member } #: Hash[String, untyped]
22
+ base, head = @head_past_base
23
+ @head_past_base ? printed.merge("head_moved_past_base" => { "base_commit" => base, "head" => head }) : printed
24
+ end
25
+ end
26
+ end
27
+ end