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,172 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "time"
5
+ require_relative "command"
6
+ require_relative "../models/finding_write_ups"
7
+ require_relative "../models/audit"
8
+ require_relative "../models/request_id"
9
+ require_relative "acknowledgement"
10
+ require_relative "correction"
11
+ require_relative "repoint"
12
+ require_relative "audit_completion"
13
+
14
+ module Principles
15
+ # Each runs one command: reads models, applies their rules and prints through a view.
16
+ # controllers/CLAUDE.md owns the boundary.
17
+ module Controllers
18
+ # Answering an audit's requests: this tool checks the answer against the project's files and, when the
19
+ # audit completes, writes the committed audit record. Nothing else is stored: each request id is a
20
+ # digest of what the request depends on, so an answer is checked against the project as it is now (D25).
21
+ class Answer < Command
22
+ #: (String, String?, Hash[String, untyped]) -> untyped
23
+ def call(command, id, options)
24
+ naming_the_executable do
25
+ case command
26
+ when "submit-answer" then submit_answer(required_id(id), submitted(required_id(id), options))
27
+ when "show-answer-check"
28
+ check_answer(required_id(id), submitted(required_id(id), options), options.fetch("file"))
29
+ else unknown(command)
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # The answer from --file, where - means it was read from stdin, checked against the schema for the phase
37
+ # the request id names.
38
+ #: (String, Hash[String, untyped]) -> untyped
39
+ def submitted(request_text, options)
40
+ @project.answer(options.fetch("file"), stdin: options["stdin"], phase: Models::RequestId.parse(request_text).phase)
41
+ end
42
+
43
+ # Accepts the answer and writes the record changes it makes.
44
+ #: (String, untyped) -> untyped
45
+ def submit_answer(request_text, response)
46
+ output, changes = judged(request_text, response)
47
+ wrote = changes.map { |record| @audit_records.save(record) }
48
+ audit_result.present(output).merge("wrote" => wrote)
49
+ end
50
+
51
+ # Runs every check submit-answer would, and says what it would write, writing nothing; its submit command
52
+ # gives answer_file, the --file value it was checked from, again.
53
+ #: (String, untyped, String) -> untyped
54
+ def check_answer(request_text, response, answer_file)
55
+ _, changes = judged(request_text, response)
56
+ written_up = Models::FindingWriteUps.of(@project, changes).written_up
57
+ # Only a correction's fixed dispositions claim map entries; for any other answer this is empty, read from
58
+ # no map, and left out.
59
+ audit_result.acceptable(request_text, changes.map { |record| @audit_records.file(record.principle_id) },
60
+ answer_file:, open_findings_will_print: (written_up unless written_up.empty?),
61
+ map_entry_changes: claimed_map_entry_changes(request_text, response))
62
+ .merge("wrote" => [])
63
+ end
64
+
65
+ # What each map entry a correction's fix claims changed, from the map its audit started from; its audit is
66
+ # built only when a fix claims one.
67
+ #: (String, untyped) -> Array[Hash[String, untyped]]
68
+ def claimed_map_entry_changes(request_text, response)
69
+ handed(Correction).map_entry_changes(response) { audit(Models::RequestId.parse(request_text).principle_id) }
70
+ end
71
+
72
+ # The result of answering, and each record change the answer makes, not yet written.
73
+ #: (String, untyped) -> [untyped, Array[Models::AuditRecord]]
74
+ def judged(request_text, response)
75
+ request_id = Models::RequestId.parse(request_text)
76
+ request_id.check_answer(response)
77
+ case request_id.phase
78
+ when "acknowledgement" then handed(Acknowledgement).answered(request_id, response)
79
+ when "repointing" then handed(Repoint).answered(request_id, response)
80
+ when "assessment" then answer_assessment(request_id, response, audit(request_id.principle_id))
81
+ else handed(Correction).answered(request_id, response, audit(request_id.principle_id))
82
+ end
83
+ end
84
+
85
+ # The audit of principle id, against its members' hashes now.
86
+ #: (String) -> Models::Audit
87
+ def audit(id)
88
+ map = @member_maps.find(id)
89
+ Models::Audit.new(map: map, now: @project.snapshots(map).hashes)
90
+ end
91
+
92
+ #: (Models::RequestId, untyped, Models::Audit) -> [untyped, Array[Models::AuditRecord]]
93
+ def answer_assessment(request_id, response, audit)
94
+ basis = Models::AssessmentBasis.new(response.fetch("basis"))
95
+ judged_against = record_judged_against(audit)
96
+ prior = checked_request(request_id, basis, audit, judged_against.loads)
97
+ basis.printed_after.refusing_stale_first(audit.id, @audit_records.find(audit.id)) do
98
+ judged_assessment(response, audit, prior, judged_against, basis)
99
+ end
100
+ end
101
+
102
+ # The assessment's fields and its decisions on the prior findings checked, then the audit completed or its
103
+ # correction asked for.
104
+ #: (untyped, Models::Audit, Models::PriorFindings, Models::RecordJudgedAgainst, Models::AssessmentBasis)
105
+ #| -> [untyped, Array[Models::AuditRecord]]
106
+ def judged_assessment(response, audit, prior, judged_against, basis)
107
+ assessed = audit.assessment(response, judged_against, Time.now.utc.iso8601) do |locator|
108
+ snapshot(locator)
109
+ end
110
+ findings = assessed.findings
111
+ decided = prior.decided(response["prior_finding_decisions"], findings, records: @audit_records) do |locator|
112
+ snapshot(locator)
113
+ end
114
+ correction(audit.assessed_start(basis.base, assessed, grounds(audit)), audit, findings, decided, basis)
115
+ end
116
+
117
+ # The request an assessment answers, checked against the project now, with the prior findings it listed.
118
+ #: (Models::RequestId, Models::AssessmentBasis, Models::Audit, Models::LoadsToCheck) -> Models::PriorFindings
119
+ def checked_request(request_id, basis, audit, loads)
120
+ audit.check_assessment_request(request_id, basis: basis, committed: committed(basis.base, audit.map),
121
+ record: @audit_records.find(audit.id), loads: loads)
122
+ end
123
+
124
+ # Completes an audit with no findings. Otherwise asks the agent to correct them. Each is refused once the last
125
+ # completed record changed after the assessment's request was printed, the completion unless it writes
126
+ # nothing (D50).
127
+ #: (Models::AuditStart, Models::Audit, Array[untyped], Models::DecidedPriorFindings, Models::AssessmentBasis)
128
+ #| -> [untyped, Array[Models::AuditRecord]]
129
+ def correction(start, audit, findings, decided, assessed)
130
+ if findings.empty?
131
+ return handed(AuditCompletion, replacing: assessed.printed_after).completed(start, audit, [], [], decided)
132
+ end
133
+
134
+ correction_request(start, audit, findings, decided, assessed)
135
+ end
136
+
137
+ # The correction request for findings, and outside git the open record that keeps what the audit began from.
138
+ # It is refused before it is printed once the last completed record changed, so no fix is made for an audit
139
+ # that cannot complete (D50). The check results travel in the basis, so the completed record keeps them (D30).
140
+ #: (Models::AuditStart, Models::Audit, Array[untyped], Models::DecidedPriorFindings, Models::AssessmentBasis)
141
+ #| -> [untyped, Array[Models::AuditRecord]]
142
+ def correction_request(start, audit, findings, decided, assessed)
143
+ assessed.printed_after.confirm_unchanged(audit.id, @audit_records.find(audit.id))
144
+ basis = printed_basis(start, findings, decided, assessed)
145
+ [audit_result.correction_request(request_id: basis.request_id(audit.id, audit.now).to_s,
146
+ principle_id: audit.id, findings: findings, basis: basis.to_h,
147
+ placeholders: Models::Disposition::PLACEHOLDERS),
148
+ open_record_changes(start, audit, basis.to_h)]
149
+ end
150
+
151
+ # The basis a correction request prints, for the agent to hand back with its answer.
152
+ # Its revision is the one the assessment was printed after, carried forward, so the correction is refused once
153
+ # that record changes (#646, D50).
154
+ #: (Models::AuditStart, Array[untyped], Models::DecidedPriorFindings, Models::AssessmentBasis)
155
+ #| -> Models::CorrectionBasis
156
+ def printed_basis(start, findings, decided, assessed)
157
+ Models::CorrectionBasis.printed(start, findings: findings, prior: decided, revision: assessed.revision)
158
+ end
159
+
160
+ # Outside git, the open record that keeps what the audit began from, and the last completed record, so a
161
+ # restarted audit still lists its prior findings (D35); in git, nothing.
162
+ #: (Models::AuditStart, Models::Audit, Hash[String, untyped]) -> Array[Models::AuditRecord]
163
+ def open_record_changes(start, audit, basis)
164
+ return [] if start.base
165
+
166
+ last = @audit_records.find(audit.id)&.last_completed
167
+ record = handed(AuditCompletion).record(start, basis.fetch("findings"), "open", audit.now, last)
168
+ @audit_records.changes(record)
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,203 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "command"
5
+ require_relative "../models/audit"
6
+ require_relative "../models/evidence_lines_since"
7
+ require_relative "../models/findings_left_open"
8
+ require_relative "../models/meta_principle"
9
+ require_relative "../models/other_principles"
10
+ require_relative "../models/finding_write_ups"
11
+ require_relative "../models/uncommitted_outside_the_map"
12
+ require_relative "../views/assessment_request"
13
+ require_relative "../views/findings_result"
14
+ require_relative "../views/loads_to_check_result"
15
+ require_relative "../views/open_findings_elsewhere_result"
16
+ require_relative "../views/open_findings_line"
17
+ require_relative "../views/other_principles_unjudged_result"
18
+ require_relative "../views/outside_the_map_result"
19
+ require_relative "../views/package_since_last_audit_result"
20
+ require_relative "../views/prior_findings_result"
21
+ require_relative "acknowledgement"
22
+ require_relative "members_to_assess"
23
+ require_relative "repoint"
24
+
25
+ module Principles
26
+ # Each runs one command: reads models, applies their rules and prints through a view.
27
+ # controllers/CLAUDE.md owns the boundary.
28
+ module Controllers
29
+ # Starting and showing an audit: start-audit hands the agent the assessment request, and show-audit
30
+ # prints the committed record. Starting writes nothing: the request id is a digest of what the request
31
+ # depends on, so Answer checks the answer against the project as it is then (D25).
32
+ class Audit < Command
33
+ #: (String, String?, Hash[String, untyped]) -> untyped
34
+ def call(command, id, options)
35
+ return started_audit(required_id(id), options) if command == "start-audit"
36
+
37
+ naming_the_executable do
38
+ case command
39
+ when "show-audit" then shown_audit(required_id(id))
40
+ when "list-open-findings" then options["short"] ? open_findings_line(id, options) : open_findings(id)
41
+ else unknown(command)
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # The audit start-audit's options ask for: a one-member audit with --member, a repoint of the open
49
+ # findings' write-ups with --write-ups (D59), else a full audit.
50
+ #: (String, Hash[String, untyped]) -> Hash[String, untyped]
51
+ def started_audit(id, options)
52
+ return acknowledge(id, options["member"]) if options["member"]
53
+ return handed(Repoint).start(id) if options["write_ups"]
54
+
55
+ naming_the_executable { start_audit(id) }
56
+ end
57
+
58
+ # id's committed record. With none, an id no map has is refused as show-principle refuses it, since
59
+ # starting its audit would be refused too; a record kept after its map was removed still shows (#577).
60
+ #: (String) -> Hash[String, untyped]
61
+ def shown_audit(id)
62
+ @member_maps.find(id) unless @audit_records.exist?(id)
63
+ @audit_records.fetch(id).shown.merge("wrote" => [])
64
+ end
65
+
66
+ # A one-member audit of member_id (D33).
67
+ #: (String, String) -> Hash[String, untyped]
68
+ def acknowledge(id, member_id)
69
+ handed(Acknowledgement).start(id, member_id)
70
+ end
71
+
72
+ # The findings the last completed audit of map id, or of every map, deferred or accepted (#292, D35).
73
+ #: (String?) -> Hash[String, untyped]
74
+ def open_findings(id)
75
+ listed = Models::FindingWriteUps.of(@project, @audit_records.of(@member_maps.ids_of(id))).listed
76
+ Views::FindingsResult.new(invocation).open(listed).merge("wrote" => [])
77
+ end
78
+
79
+ # The one line saying how many findings are open and how long the oldest has waited until now, and which
80
+ # write-ups can no longer be found (#694), or nothing when none is open, for a session-start hook (D47),
81
+ # worded for the session's start, startup unless --session-start names another (#717).
82
+ #: (String?, Hash[String, untyped]) -> Views::Lines
83
+ def open_findings_line(id, options)
84
+ records = @audit_records.of(@member_maps.ids_of(id))
85
+ waiting = Models::FindingsLeftOpen.of(records).waiting_at(Time.now.utc)
86
+ Views::OpenFindingsLine.new(invocation).short(waiting, Models::FindingWriteUps.of(@project, records).not_found,
87
+ source: options["session_start"] || "startup")
88
+ end
89
+
90
+ #: (String) -> untyped
91
+ def start_audit(id)
92
+ auditable = @member_maps.auditable(id)
93
+ base = base_commit(id, auditable)
94
+ current = @project.snapshots(auditable)
95
+ audit = Models::Audit.new(map: auditable, now: current.hashes)
96
+ listing = handed(MembersToAssess, principle_id: id).listed(audit, current)
97
+ output = assessment_request(audit, base, listing)
98
+ audit_result.present(with_record_notice(output, id, base)).merge("wrote" => [])
99
+ end
100
+
101
+ # The request, listing the findings the last completed audit left to decide, and with an id that digests them
102
+ # (D35) and the revision of that audit's record (#653), but not the findings other principles leave open on
103
+ # members sharing these lines (D62).
104
+ #: (Models::Audit, String?, MembersToAssess::Listing) -> Hash[String, untyped]
105
+ def assessment_request(audit, base, listing)
106
+ record = @audit_records.find(audit.id)
107
+ prior = audit.prior_findings(record)
108
+ loads = loads_to_check(audit).listed
109
+ basis = Models::AssessmentBasis.printed(base, record)
110
+ audit_result.assessment_request(request_id: audit.assessment_id(basis, prior.digested, loads).to_s,
111
+ request: request_to_assess(audit, basis, listing.members,
112
+ parts_to_read(audit, prior, listing, loads)))
113
+ end
114
+
115
+ # What the agent assesses: the members, with what else it reads beside them (D60).
116
+ #: (Models::Audit, Models::AssessmentBasis, Array[Hash[String, untyped]], Array[Views::RequestPart])
117
+ #| -> Views::AssessmentRequest
118
+ def request_to_assess(audit, basis, members, parts)
119
+ Views::AssessmentRequest.new(principle_id: audit.id, description: audit.map.description, members: members,
120
+ basis: basis.to_h, parts: parts)
121
+ end
122
+
123
+ # What the agent reads beside the members, in order (D60): the files outside the map that differ from the
124
+ # base commit, which a check and the answer's evidence read as they are now (#409); the prior findings
125
+ # (D35); the findings other principles leave open on members sharing these lines (D62); and the members to
126
+ # check against the activation (#355); each when it lists any; and, on the meta-principle's audit, the release
127
+ # since its last audit (D68) and the other principles with members no audit has judged (D71).
128
+ #: (Models::Audit, Models::PriorFindings, MembersToAssess::Listing, Hash[String, untyped]?)
129
+ #| -> Array[Views::RequestPart]
130
+ def parts_to_read(audit, prior, listing, loads)
131
+ [outside_the_map(audit), prior_findings_part(prior),
132
+ Views::OpenFindingsElsewhereResult.new(listing.open_findings_elsewhere).part,
133
+ Views::LoadsToCheckResult.new(invocation, loads, audit.id).part, package_since_last_audit(audit.map),
134
+ other_principles_unjudged(audit.map)].compact
135
+ end
136
+
137
+ # The part listing each other principle with members no audit has judged as they are now, as
138
+ # list-member-problems reports them, for the principle whose audit states them; nil for any other (D71).
139
+ #: (Models::MemberMap) -> Views::RequestPart?
140
+ def other_principles_unjudged(map)
141
+ return unless map.keeps_others_held?
142
+
143
+ listed = Models::OtherPrinciples.of(@project, but: map.id).unjudged.map do |other, members|
144
+ Views::PrincipleWithChangedMembers.new(principle_id: other, members: members)
145
+ end
146
+ Views::OtherPrinciplesUnjudgedResult.new(listed).part
147
+ end
148
+
149
+ # The part stating the release of principles the last completed audit ran under beside the one now, and
150
+ # whether the passage changed since, for a principle whose audit keeps one; nil for any other (D68).
151
+ #: (Models::MemberMap) -> Views::RequestPart?
152
+ def package_since_last_audit(map)
153
+ now = @tool.release_kept_by(map) or return
154
+ last = @audit_records.find(map.id)&.last_completed&.package
155
+ Views::PackageSinceLastAuditResult.new(invocation, at_last_audit: last&.version, now: now.version,
156
+ passage_changed: last && now.passage_changed_since?(last))
157
+ .part
158
+ end
159
+
160
+ # The part listing the prior findings, in git with each evidence item saying whether its text changed since the
161
+ # commit the audit that left them open was based on, and whether it is found now (#858, #877); nil when there are
162
+ # none.
163
+ #: (Models::PriorFindings) -> Views::RequestPart?
164
+ def prior_findings_part(prior)
165
+ shown = prior.shown { |item, base| Models::EvidenceLinesSince.of(item, since: base, project: @project).facts }
166
+ Views::PriorFindingsResult.new(shown, last_audited_commit: prior.last_audited_commit).part
167
+ end
168
+
169
+ # The part naming the files outside the map that differ from the base commit, or nil when none do.
170
+ #: (Models::Audit) -> Views::RequestPart?
171
+ def outside_the_map(audit)
172
+ outside = @history.uncommitted_outside(audited_files(audit.id, audit.map))
173
+ Views::OutsideTheMapResult.new(outside && Models::UncommittedOutsideTheMap.new(outside).shown).part
174
+ end
175
+
176
+ # The commit the audit is judged against, over the map and every member's file; nil outside git.
177
+ #: (String, Models::MemberMap) -> String?
178
+ def base_commit(id, map)
179
+ @history.base_commit(id, audited_files(id, map))
180
+ end
181
+
182
+ # The map's file and every member's file.
183
+ #: (String, Models::MemberMap) -> Array[String]
184
+ def audited_files(id, map)
185
+ [@member_maps.file(id), *map.members.map(&:path)]
186
+ end
187
+
188
+ # The request, with a notice when the last audit's record was left uncommitted.
189
+ #: (Hash[String, untyped], String, String?) -> Hash[String, untyped]
190
+ def with_record_notice(output, id, base)
191
+ return output unless base && uncommitted_record?(id)
192
+
193
+ output.merge("notice" => audit_result.uncommitted_record_notice(@audit_records.file(id)))
194
+ end
195
+
196
+ # Whether the last audit's record exists but differs from the last commit, so it may be left behind.
197
+ #: (String) -> bool
198
+ def uncommitted_record?(id)
199
+ @audit_records.exist?(id) && @history.uncommitted([@audit_records.file(id)]).any?
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,113 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "time"
5
+ require_relative "command"
6
+ require_relative "../models/audit"
7
+ require_relative "../models/commits_since_base"
8
+ require_relative "../models/other_principles"
9
+ require_relative "../views/prior_finding_decisions_result"
10
+
11
+ module Principles
12
+ # Each runs one command: reads models, applies their rules and prints through a view.
13
+ # controllers/CLAUDE.md owns the boundary.
14
+ module Controllers
15
+ # A full audit's record: the open record a correction request leaves outside git, and the last step, which
16
+ # submit-answer reaches from an assessment with no findings or from a correction, with the record it writes,
17
+ # the files to commit and what landed since the base commit.
18
+ class AuditCompletion < Command
19
+ # map_changes are the changes a correction declared to the map, which name the map file to commit (D40).
20
+ # replacing is the revision of the last completed record when the request that completes the audit was
21
+ # printed, which refuses to replace a record changed since (#646).
22
+ #: (Models::Project, tool: RunningTool, ?map_changes: Models::DeclaredMapChanges?,
23
+ #| ?replacing: Models::LastCompletedRevision?) -> void
24
+ def initialize(project, tool:, map_changes: nil, replacing: nil)
25
+ super(project, tool: tool)
26
+ @map_changes = map_changes #: Models::DeclaredMapChanges?
27
+ @replacing = replacing #: Models::LastCompletedRevision?
28
+ end
29
+
30
+ # The completed result, and the record change it makes, not yet written.
31
+ # Each prior finding's decision is applied: a kept one stays in the record, and a closed one is reported (D35).
32
+ #: (Models::AuditStart, Models::Audit, Array[Models::Disposition], Array[untyped], Models::DecidedPriorFindings)
33
+ #| -> [untyped, Array[Models::AuditRecord]]
34
+ def completed(start, audit, dispositions, findings, decided)
35
+ written = record(start, audit.outcomes(findings, dispositions, decided), "completed", audit.now)
36
+ changes = @audit_records.changes(written)
37
+ @replacing&.confirm_writable(audit.id, @audit_records.find(audit.id), changes)
38
+ [completion_result(start, audit, dispositions, decided, written), changes]
39
+ end
40
+
41
+ # The record an audit writes, made now, saying what its assessments answered, with each assessment carried
42
+ # forward taken from the record it replaces (D49), and the release of principles it ran under when its
43
+ # principle keeps one (D68).
44
+ #: (Models::AuditStart, Array[untyped], String, Hash[String, String], ?Models::AuditRecord?)
45
+ #| -> Models::AuditRecord
46
+ def record(start, findings, status, members, last_completed = nil)
47
+ Models::AuditRecord.written(start, status: status, audited_at: Time.now.utc.iso8601, members: members,
48
+ findings: findings)
49
+ .judged(start.judged_against(Views::AssessmentRequest.question))
50
+ .carrying(@audit_records.find(start.id)).packaged(@tool.release_kept_by(start.map))
51
+ .keeping(last_completed)
52
+ end
53
+
54
+ private
55
+
56
+ # What the completed audit prints: its resolved and open findings, as the record it writes holds them, so they
57
+ # agree with list-open-findings (#714); what it changed; and each prior finding's decision (D35).
58
+ #: (Models::AuditStart, Models::Audit, Array[Models::Disposition], Models::DecidedPriorFindings,
59
+ #| Models::AuditRecord) -> untyped
60
+ def completion_result(start, audit, dispositions, decided, written)
61
+ decisions = Views::PriorFindingDecisionsResult.new(kept_ids: decided.kept_ids, closed: decided.closed,
62
+ dropped: decided.dropped,
63
+ waits_on_dropped: decided.waits_on_dropped,
64
+ evidence_replaced: decided.evidence_replaced)
65
+ audit_result.completion(principle_id: audit.id, resolved: written.resolved_ids(decided),
66
+ open: written.open_ids, changes: changes(start, audit, dispositions, written),
67
+ decisions: decisions)
68
+ end
69
+
70
+ # What the audit changed in the project: the files to commit, in git the commits since the base, the
71
+ # other principles whose members its fixes changed, and the members its correction added (T69).
72
+ #: (Models::AuditStart, Models::Audit, Array[Models::Disposition], Models::AuditRecord) -> Views::AuditChanges
73
+ def changes(start, audit, dispositions, written)
74
+ changed = audit.changed_files(dispositions)
75
+ added = start.added(now: audit.now)
76
+ map = @map_changes&.files_to_commit(@member_maps.file(audit.id), added: added) || []
77
+ files = @audit_records.files_to_commit(written, changed + map, start.reported)
78
+ Views::AuditChanges.new(files_to_commit: files, landed: landed_since_base(start, audit), added: added,
79
+ other_changed: other_changed(audit, changed),
80
+ cited_files_missing: @audit_records.cited_files_missing(written))
81
+ end
82
+
83
+ # Each other principle with a member in paths, the files this audit's fixes changed, whose own text now
84
+ # differs from what this tool last knew of it or whose reference broke, as list-member-problems reports it
85
+ # (#410). A map this tool
86
+ # cannot read names none here; list-member-problems reports it.
87
+ #: (Models::Audit, Array[String]) -> Array[Views::PrincipleWithChangedMembers]
88
+ def other_changed(audit, paths)
89
+ Models::OtherPrinciples.of(@project, but: audit.id).changed_or_broken_in(paths).map do |id, changed|
90
+ Views::PrincipleWithChangedMembers.new(principle_id: id, members: changed.map(&:to_h))
91
+ end
92
+ end
93
+
94
+ # In git, what landed since the base commit, as the completed result prints it; nil outside git.
95
+ #: (Models::AuditStart, Models::Audit) -> Views::LandedSinceBase?
96
+ def landed_since_base(start, audit)
97
+ landed = commits_since_base(start, audit) or return nil
98
+ Views::LandedSinceBase.new(commits_by_member: landed.by_member, head_past_base: landed.head_past_base)
99
+ end
100
+
101
+ # In git, for each member that changed during the audit, the commits since the base commit that touched
102
+ # its file, so the owner can see how each fix landed (alpha feedback A4), and HEAD now. nil outside git.
103
+ #: (Models::AuditStart, Models::Audit) -> Models::CommitsSinceBase?
104
+ def commits_since_base(start, audit)
105
+ base = start.base or return nil
106
+ by_member = start.changed(now: audit.now).to_h do |member|
107
+ [member, @history.commits_since(base, audit.path_of(member))]
108
+ end
109
+ Models::CommitsSinceBase.new(base: base, head: @history.head, by_member: by_member)
110
+ end
111
+ end
112
+ end
113
+ end