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,185 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../error"
5
+ require_relative "../data/map_files"
6
+ require_relative "member_map"
7
+ require_relative "meta_principle"
8
+
9
+ module Principles
10
+ # The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
11
+ module Models
12
+ # Every member map in the project, loaded and saved through Data::MapFiles.
13
+ class MemberMaps
14
+ # A principle id names its principle in lower-kebab-case.
15
+ ID_FORMAT = Data::PrincipleId::FORMAT #: Regexp
16
+
17
+ #: (Data::MapFiles) -> void
18
+ def initialize(files)
19
+ @files = files
20
+ end
21
+
22
+ #: () -> Array[String]
23
+ def ids
24
+ @files.ids
25
+ end
26
+
27
+ # The id of map id, or of every map when id is nil; an id no map has is refused, as find refuses it.
28
+ #: (String?) -> Array[String]
29
+ def ids_of(id)
30
+ return ids unless id
31
+
32
+ find(id)
33
+ [id]
34
+ end
35
+
36
+ # Every path a map this tool can read names, once each; a map it cannot read names none, so one broken map
37
+ # still leaves the others' files known.
38
+ #: () -> Array[String]
39
+ def readable_paths
40
+ ids.flat_map do |id|
41
+ find(id).members.paths
42
+ rescue Error
43
+ []
44
+ end.uniq
45
+ end
46
+
47
+ # Whether a map named id exists, whatever it holds.
48
+ #: (String) -> bool
49
+ def mapped?(id) = @files.exist?(id)
50
+
51
+ # Whether principles is set up: the meta-principle's map names a keeping place, a member of any kind. That says
52
+ # a place is named, not that the principle is held, which its audit finds. A map this version cannot read, in a
53
+ # newer format, unparseable or invalid, counts, since it may well have members, so no read refuses over it (D75).
54
+ #: () -> bool
55
+ def set_up?
56
+ return false unless mapped?(MetaPrinciple::ID)
57
+
58
+ begin
59
+ !find(MetaPrinciple::ID).unstarted?
60
+ rescue Error
61
+ true
62
+ end
63
+ end
64
+
65
+ # The map named id, refused when there is none; Data::MapFiles refuses one that is unparseable.
66
+ #: (String) -> MemberMap
67
+ def find(id)
68
+ refuse_unknown(id)
69
+ unique(MemberMap.new(id, @files.read(id)))
70
+ end
71
+
72
+ # The map id as text read elsewhere holds it, such as its file at commit, the one an audit began from.
73
+ #: (String, String, ?commit: String?) -> MemberMap
74
+ def parsed(id, text, commit: nil)
75
+ unique(MemberMap.new(id, @files.parse(id, text, commit: commit)), commit)
76
+ end
77
+
78
+ # A new map for id with no members, not yet saved. It is refused unless id is lower-kebab-case and not
79
+ # yet mapped, and is not the meta-principle's, which setup starts; only then is the description asked for.
80
+ #: (String) { () -> String } -> MemberMap
81
+ def create(id, &)
82
+ refuse_unavailable(id)
83
+ refuse_meta_principle_id(id)
84
+ MemberMap.new(id, { "description" => yield, "members" => [] })
85
+ end
86
+
87
+ # The map a new member of principle id joins: the map named id, or, while the meta-principle has none, the map
88
+ # it starts with, not yet saved, so that map is written only with its first member (T74). Any other id no map
89
+ # has is refused, as find refuses it.
90
+ #: (String) -> MemberMap
91
+ def addable(id)
92
+ return find(id) if mapped?(id) || !started_by_setup?(id)
93
+
94
+ MemberMap.new(id, MetaPrinciple.starting_fields)
95
+ end
96
+
97
+ # The map named id, refused when it has no members to audit.
98
+ #: (String) -> MemberMap
99
+ def auditable(id)
100
+ map = find(id)
101
+ return map unless map.members.empty?
102
+
103
+ raise Error.new("map_has_no_members", "#{id} has no members to audit",
104
+ "Add its members: #{Error::EXECUTABLE} add-member #{id} PATH --role ROLE", file(id))
105
+ end
106
+
107
+ # Writes map's file and names it, relative to the project.
108
+ #: (MemberMap) -> String
109
+ def save(map)
110
+ @files.write(map.id, map.to_h)
111
+ file(map.id)
112
+ end
113
+
114
+ # The file map id is kept in, relative to the project.
115
+ #: (String) -> String
116
+ def file(id)
117
+ @files.relative(id)
118
+ end
119
+
120
+ # The file map id was kept in under the folder's earlier name, relative to the project (#536).
121
+ #: (String) -> String
122
+ def earlier_file(id)
123
+ @files.earlier_relative(id)
124
+ end
125
+
126
+ private
127
+
128
+ # Whether principle id's map is started by setup and written by the add-member it prints, never by
129
+ # create-principle: only the meta-principle's, whose description and activation ship with the tool (T74).
130
+ #: (String) -> bool
131
+ def started_by_setup?(id) = id == MetaPrinciple::ID
132
+
133
+ # map, refused when two of its members share an id or a key, as MapMembers#refuse_repeats says.
134
+ #: (MemberMap, ?String?) -> MemberMap
135
+ def unique(map, commit = nil)
136
+ where = commit ? "#{file(map.id)} at commit #{commit[0, 12]}" : file(map.id)
137
+ map.members.refuse_repeats(where: where, file: file(map.id))
138
+ map
139
+ end
140
+
141
+ # A principle no map names, with the commands that create one and list the rest. For the meta-principle's id the
142
+ # repair leads to setup, not create-principle, which would give its map another description and no activation;
143
+ # setup writes no map, and the add-member it prints creates it (T74).
144
+ #: (String) -> void
145
+ def refuse_unknown(id)
146
+ return if mapped?(id)
147
+
148
+ raise Error.new("unknown_principle", "No map named #{id}",
149
+ "#{creating(id)}\nTo see the mapped ids: #{Error::EXECUTABLE} list-principles")
150
+ end
151
+
152
+ # The repair line that creates principle id's map.
153
+ #: (String) -> String
154
+ def creating(id)
155
+ return MetaPrinciple::SET_UP if started_by_setup?(id)
156
+
157
+ "To create it: #{Error::EXECUTABLE} create-principle #{id} --description TEXT"
158
+ end
159
+
160
+ # The meta-principle's map is written by the add-member setup prints, with its first member; create-principle
161
+ # would write it with no members, another description and no activation (T74).
162
+ #: (String) -> void
163
+ def refuse_meta_principle_id(id)
164
+ return unless started_by_setup?(id)
165
+
166
+ raise Error.new("meta_principle_id",
167
+ "#{id} is set up by show-meta-principle-passage, whose printed add-member creates its map " \
168
+ "with its first member", MetaPrinciple::SET_UP)
169
+ end
170
+
171
+ #: (String) -> void
172
+ def refuse_unavailable(id)
173
+ unless id.match?(ID_FORMAT)
174
+ raise Error.new("invalid_id", "#{id} is not lower-kebab-case",
175
+ "Name the principle in lower-kebab-case, such as express-intent.")
176
+ end
177
+ return unless @files.exist?(id)
178
+
179
+ raise Error.new("principle_exists", "#{id} is already mapped",
180
+ "To change its description: #{Error::EXECUTABLE} change-description #{id} --description TEXT",
181
+ file(id))
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,95 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../error"
5
+ require_relative "member"
6
+ require_relative "member_map"
7
+
8
+ module Principles
9
+ # The domain: maps, audits and command steps, with every rule a schema cannot express.
10
+ # models/CLAUDE.md owns the boundary.
11
+ module Models
12
+ # The members a correction says the agent moved after the audit began, from each old id to its new one. A
13
+ # fix can rewrite the text a member's locator quotes; the agent then runs move-member and declares the
14
+ # move, and this tool checks it against the map at the base commit (D28).
15
+ class MemberMoves
16
+ #: (untyped) -> void
17
+ def initialize(moves)
18
+ @moves = parsed(moves) #: Hash[String, String]
19
+ freeze
20
+ end
21
+
22
+ #: () -> bool
23
+ def empty?
24
+ @moves.empty?
25
+ end
26
+
27
+ # Refuses moves outside git, where there is no base map to check them against.
28
+ #: (String?, principle_id: String) -> String
29
+ def check_in_git(base, principle_id:)
30
+ return base if base
31
+
32
+ raise Error.new("moves_need_git", "moved_members needs git, to read the map #{principle_id} began from",
33
+ "Outside git, start a fresh audit: #{Error::EXECUTABLE} start-audit #{principle_id}")
34
+ end
35
+
36
+ # The base map with each declared member moved to its place in current, each keeping its role.
37
+ #: (MemberMap, MemberMap) -> MemberMap
38
+ def applied(base, current)
39
+ @moves.reduce(base) { |map, (from, to)| map.moving(from, located(current, to)) }
40
+ end
41
+
42
+ # The refusal of a map that changed beyond the moves declared.
43
+ #: (String, String) -> Error
44
+ def undeclared(principle_id, map_file)
45
+ Error.new("undeclared_move", "The map of #{principle_id} changed during this audit beyond the moves declared",
46
+ "Declare each member you moved in moved_members, or start a fresh audit: " \
47
+ "#{Error::EXECUTABLE} start-audit #{principle_id}", map_file)
48
+ end
49
+
50
+ # The member's id after the declared moves.
51
+ #: (String) -> String
52
+ def renamed(member_id)
53
+ @moves.fetch(member_id, member_id)
54
+ end
55
+
56
+ # Each finding with its members' ids after the moves.
57
+ #: (Array[untyped]) -> Array[untyped]
58
+ def findings(findings)
59
+ findings.map { |finding| finding.merge("member_ids" => finding.fetch("member_ids").map { |id| renamed(id) }) }
60
+ end
61
+
62
+ # The values in by_id, such as each member's hash or its record entry's fields, keyed by each member's id
63
+ # after the moves.
64
+ #: [V] (Hash[String, V]) -> Hash[String, V]
65
+ def hashes(by_id)
66
+ by_id.transform_keys { |id| renamed(id) }
67
+ end
68
+
69
+ private
70
+
71
+ #: (untyped) -> Hash[String, String]
72
+ def parsed(moves)
73
+ return {} if moves.nil?
74
+
75
+ unless moves.is_a?(Array) && moves.all? { |move| move.is_a?(Hash) && move.values_at("from", "to").all?(String) }
76
+ raise Error.new("invalid_answer", "moved_members must be a list of {\"from\": OLD_ID, \"to\": NEW_ID}",
77
+ "List each member you moved with move-member, by its id before and after.")
78
+ end
79
+
80
+ moves.to_h { |move| [move.fetch("from"), move.fetch("to")] }
81
+ end
82
+
83
+ # The current member to's place in its file, without its role.
84
+ #: (MemberMap, String) -> Hash[String, untyped]
85
+ def located(current, to)
86
+ unless current.members.taken?(to)
87
+ raise Error.new("unknown_member", "#{to} is not a member of #{current.id}",
88
+ "Name in moved_members the id show-principle lists after the move.")
89
+ end
90
+
91
+ current.members.found(to).to_h.except("role")
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,253 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../error"
5
+ require_relative "audit_record"
6
+ require_relative "map_members"
7
+ require_relative "member_map_history"
8
+ require_relative "member_state"
9
+ require_relative "locate_refusal"
10
+
11
+ module Principles
12
+ # The domain logic, which loads and saves itself through data/. models/CLAUDE.md owns the boundary.
13
+ module Models
14
+ # One map's members, each as broken, changed, unchanged, unchanged since its mapping commit or with no
15
+ # baseline, against what this tool last knew of its text: the last audit's hash for it, or else its text at
16
+ # the commit that mapped it where it is.
17
+ # A member is changed only when its own text changed, so an edit elsewhere in its file is not one.
18
+ class MemberProblems
19
+ # What this tool last knew of each member's text, by key: the last audit's hashes and the commit it began
20
+ # from, and each member's hash and commit at the commit that mapped it where it is (#243).
21
+ class Baselines
22
+ # The last audit's hashes for members' map, and each member's hash and commit at the commit that mapped it
23
+ # where it is, read with the map as that commit held it; none outside git.
24
+ #: (Project, MapMembers) -> Baselines
25
+ def self.of(project, members)
26
+ id = members.principle_id
27
+ audit = project.audit_records.find(id)
28
+ mapped = MemberMapHistory.new(id: id, history: project.history, member_maps: project.member_maps)
29
+ .mapped_baselines(members.keys)
30
+ new(audit: audit, mapped: mapped.transform_values { |each| each.fetch("sha256") },
31
+ mapped_in: mapped.transform_values { |each| each.fetch("commit") }, in_git: project.history.inside_git?)
32
+ end
33
+
34
+ # Whether the project is in git, so a member with no baseline is one no commit holds yet.
35
+ #: bool
36
+ attr_reader :in_git
37
+
38
+ # audit is the principle's audit record, if any: its hashes, the commit it began from and whether it is
39
+ # completed, so a one-member audit is offered only where one would be accepted (#626).
40
+ #: (audit: AuditRecord?, mapped: Hash[String, String], mapped_in: Hash[String, String], in_git: bool)
41
+ #| -> void
42
+ def initialize(audit:, mapped:, mapped_in:, in_git:)
43
+ @audited = audit&.members&.hashes_by_key || {} #: Hash[String, String]
44
+ @audit = audit #: AuditRecord?
45
+ @audit_base = audit&.base_commit #: String?
46
+ @mapped = mapped
47
+ @mapped_in = mapped_in
48
+ @in_git = in_git
49
+ freeze
50
+ end
51
+
52
+ # The hash this tool last knew for the member known by key, and what it was: the last audit's, or else its
53
+ # hash at the commit that mapped it, with why no audit gave one; neither when it has no baseline.
54
+ #: (String) -> [String?, Hash[String, untyped]?]
55
+ def of(key)
56
+ if @audited.key?(key)
57
+ [@audited[key], { "baseline" => "audit", "commit" => @audit_base }]
58
+ elsif @mapped.key?(key)
59
+ [@mapped[key], { "baseline" => "mapping_commit", "commit" => @mapped_in[key],
60
+ "no_audit_because" => @audited.empty? ? "no_audit_record" : "not_in_last_audit" }]
61
+ else
62
+ [nil, nil]
63
+ end
64
+ end
65
+
66
+ # Why a member these baselines do not hold has none: in git no commit holds it where its map places
67
+ # it, and outside git there is no history.
68
+ #: () -> String
69
+ def none_because = @in_git ? "not_committed" : "outside_git"
70
+
71
+ # Whether no full audit of the principle has completed: it has no record, or an open one that keeps none
72
+ # (D58).
73
+ #: () -> bool
74
+ def no_completed_audit?
75
+ audit = @audit
76
+ audit.nil? || audit.last_completed.nil?
77
+ end
78
+
79
+ # Whether a one-member audit may judge member's change to now_sha256: in git, and where the audit record gives
80
+ # no refusal, which Acknowledgement refuses by too, so list-member-problems offers no command certain to be
81
+ # refused (#626, #632).
82
+ #: (Member, String?) -> bool
83
+ def acknowledgeable?(member, now_sha256)
84
+ audit = @audit
85
+ @in_git && !audit.nil? && audit.acknowledgement_refusal(member, now_sha256:).nil?
86
+ end
87
+ end
88
+
89
+ # One map's members in project against what this tool last knew of each. A map that cannot be read is
90
+ # refused when it was asked for, and listed as broken among every map.
91
+ #: (Project, String, ?listing_all: bool) -> MemberProblems
92
+ def self.for(project, id, listing_all: false)
93
+ members = project.member_maps.find(id).members
94
+ new(members: members, now: project.current_by_key(members), baselines: Baselines.of(project, members))
95
+ rescue Error => e
96
+ raise unless listing_all
97
+
98
+ none = MapMembers.new(id, [])
99
+ new(members: none, now: {}, baselines: Baselines.of(project, none), unreadable: e.payload)
100
+ end
101
+
102
+ # now holds, for each member by key, its hash now or the refusal that says why it cannot be found.
103
+ # unreadable is the refusal for a map this tool could not read, which is listed as one broken entry.
104
+ #: (members: MapMembers, now: Hash[String, untyped],
105
+ #| baselines: Baselines, ?unreadable: Hash[String, untyped]?) -> void
106
+ def initialize(members:, now:, baselines:, unreadable: nil)
107
+ @members = members
108
+ @now = now
109
+ @baselines = baselines
110
+ @unreadable = unreadable
111
+ freeze
112
+ end
113
+
114
+ # Every member as one entry: its map, id and state, and for a changed member what it was compared with.
115
+ #: () -> Array[Hash[String, untyped]]
116
+ def entries
117
+ if @unreadable
118
+ return [{ "principle" => principle_id, "state" => "unreadable_map" }.merge(@unreadable.except("status"))]
119
+ end
120
+
121
+ @members.map { |member| entry(member) }
122
+ end
123
+
124
+ # The entries whose reference is broken.
125
+ #: () -> Array[Hash[String, untyped]]
126
+ def broken
127
+ entries.select { |each| MemberState::BROKEN.include?(each.fetch("state")) }
128
+ end
129
+
130
+ # Whether a member an audit or acknowledgement assessed changed since, rather than only a member edited since
131
+ # the commit that mapped it, which no audit assessed (D44).
132
+ #: () -> bool
133
+ def assessed_change? = changed_members.any? { |member| assessed?(member) }
134
+
135
+ # The entries whose own text changed since this tool last knew it, those an audit assessed first. Given a
136
+ # block, each gains "history", what the block makes of its member, the hash it was compared with, and its
137
+ # hash now.
138
+ #: () ?{ (Member, String, String) -> Hash[String, untyped] } -> Array[Hash[String, untyped]]
139
+ def changed(&history)
140
+ assessed, unassessed = changed_members.partition { |member| assessed?(member) }
141
+ (assessed + unassessed).map do |member|
142
+ listed = entry(member)
143
+ next listed unless history
144
+
145
+ listed.merge("history" => history.call(member, baseline(member.key).first.to_s, @now[member.key].to_s))
146
+ end
147
+ end
148
+
149
+ # Each member in paths whose own text changed or whose reference is now broken, with its state, as a completed
150
+ # audit names them for another map that shares a file the audit's fixes changed (#410).
151
+ #: (Array[String]) -> Array[MemberState]
152
+ def changed_or_broken_in(paths)
153
+ return [] if @unreadable
154
+
155
+ @members.in_files(paths).map { |member| MemberState.new(member.id, entry(member).fetch("state")) }
156
+ .select(&:changed_or_broken?)
157
+ end
158
+
159
+ # Each member no audit has judged as it is now, by id with its state: changed or broken since what this tool
160
+ # last knew of it, or never assessed, having only its mapping commit or no baseline at all (D71, D44). A map
161
+ # this tool cannot read is one entry, with no id, whose state is unreadable_map.
162
+ #: () -> Array[Hash[String, untyped]]
163
+ def unjudged = entries.reject { it.fetch("state") == "unchanged" }.map { it.slice("id", "state") }
164
+
165
+ #: () -> String
166
+ def principle_id = @members.principle_id
167
+
168
+ # The ids of the members whose own text changed and whose change a one-member audit may judge on its own, so
169
+ # list-member-problems fills that audit's command for each (D56, #728).
170
+ #: () -> Array[String]
171
+ def acknowledgeable_member_ids
172
+ changed_members.select { |member| @baselines.acknowledgeable?(member, @now[member.key]) }.map(&:id)
173
+ end
174
+
175
+ # How many members found in their files were compared with each baseline, audit or mapping_commit, or had
176
+ # none (no_baseline), so an unchanged member compared only with the commit that mapped it is not read as
177
+ # unchanged since an audit.
178
+ #: () -> Hash[String, Integer]
179
+ def compared_with
180
+ return {} if @unreadable
181
+
182
+ found = @members.reject { |member| @now[member.key].is_a?(Hash) }
183
+ found.map { |member| baseline(member.key).last&.fetch("baseline") || "no_baseline" }.tally.sort.to_h
184
+ end
185
+
186
+ # Whether the principle awaits its first full audit: none has completed and a member is found, in git one a
187
+ # commit or an open audit gives a baseline, since until a commit holds its map committing gives its members
188
+ # one (D58, #731 R7).
189
+ #: () -> bool
190
+ def awaits_first_audit?
191
+ return false unless @baselines.no_completed_audit?
192
+
193
+ bases = compared_with.keys
194
+ @baselines.in_git ? bases.any? { it != "no_baseline" } : !bases.empty?
195
+ end
196
+
197
+ # Why members found in their files have no baseline, or nil when every one has one. Outside git a principle
198
+ # awaiting its first full audit has none either: that audit is the one remedy, which awaits_first_audit? names.
199
+ #: () -> String?
200
+ def no_baseline_because
201
+ return if no_baseline_paths.empty? || (awaits_first_audit? && !@baselines.in_git)
202
+
203
+ @baselines.none_because
204
+ end
205
+
206
+ # Each file holding a member found with no baseline, once.
207
+ #: () -> Array[String]
208
+ def no_baseline_paths
209
+ return [] if @unreadable
210
+
211
+ @members.select { |member| !@now[member.key].is_a?(Hash) && baseline(member.key).last.nil? }
212
+ .map(&:path).uniq
213
+ end
214
+
215
+ private
216
+
217
+ # The members whose own text changed since this tool last knew it; none in a map that cannot be read.
218
+ #: () -> Array[Member]
219
+ def changed_members
220
+ return [] if @unreadable
221
+
222
+ @members.select { |member| entry(member).fetch("state") == "changed" }
223
+ end
224
+
225
+ #: (Member) -> bool
226
+ def assessed?(member) = baseline(member.key).last&.fetch("baseline") == "audit"
227
+
228
+ #: (Member) -> Hash[String, untyped]
229
+ def entry(member)
230
+ listed = { "principle" => principle_id, "id" => member.id }
231
+ now = @now[member.key]
232
+ return compared(listed, member.key, now.to_s) unless now.is_a?(Hash)
233
+
234
+ listed.merge("state" => LocateRefusal.new(now).member_state).merge(now.except("status"))
235
+ end
236
+
237
+ # listed, compared with the last audit's hash for key, or else with its hash at the commit that mapped it.
238
+ # An unchanged member compared only with the commit that mapped it has never been assessed, so it is
239
+ # counted apart from one an audit assessed: counts is read first, and "unchanged" alone reads as audited (#569).
240
+ #: (Hash[String, untyped], String, String) -> Hash[String, untyped]
241
+ def compared(listed, key, sha)
242
+ then_sha, since = baseline(key)
243
+ return listed.merge("state" => "no_baseline") unless since
244
+ return listed.merge("state" => "changed", "since" => since) unless then_sha == sha
245
+
246
+ listed.merge("state" => since["baseline"] == "mapping_commit" ? "unchanged_since_mapping" : "unchanged")
247
+ end
248
+
249
+ #: (String) -> [String?, Hash[String, untyped]?]
250
+ def baseline(key) = @baselines.of(key)
251
+ end
252
+ end
253
+ end