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,165 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Principles
5
+ # Every file and process this tool reads or writes. data/CLAUDE.md owns the boundary.
6
+ module Data
7
+ # The git repository holding the project, if there is one. This tool only reads it: the commit at HEAD,
8
+ # which paths differ from it, and a file's text at a commit. git's own messages are kept off this tool's
9
+ # output.
10
+ class Repository
11
+ # The only revisions this tool hands git: HEAD or a full commit sha (SHA-1 or SHA-256), optionally its first
12
+ # parent. None can be read as an option or a wider revision expression.
13
+ REVISION = /\A(?:HEAD|\h{40}(?:\h{24})?)\^?\z/ #: Regexp
14
+
15
+ # git reads each path this tool passes as itself, never as a glob or a :(magic) pathspec, so a member's
16
+ # file name cannot take in other files.
17
+ LITERAL = { "GIT_LITERAL_PATHSPECS" => "1" }.freeze #: Hash[String, String]
18
+
19
+ #: (String) -> void
20
+ def initialize(root)
21
+ @root = root
22
+ end
23
+
24
+ # Whether the project is inside a git work tree.
25
+ #: () -> bool
26
+ def inside?
27
+ output, ok = git(["rev-parse", "--is-inside-work-tree"])
28
+ ok && output.strip == "true"
29
+ end
30
+
31
+ # The full sha of the last commit that changed path, or nil outside git or when no commit has.
32
+ #: (String) -> String?
33
+ def last_commit(path)
34
+ sha, ok = git(["log", "-1", "--format=%H", "--", path])
35
+ ok && !sha.strip.empty? ? sha.strip : nil
36
+ end
37
+
38
+ # Whether path, relative to the project, is absent from the working tree.
39
+ #: (String) -> bool
40
+ def absent?(path)
41
+ !File.exist?(File.join(@root, path))
42
+ end
43
+
44
+ # Each rename git detects between commit and the working tree, as {"from", "to", "similarity"}, with paths
45
+ # relative to the project; none outside git or when commit is not there, as in a shallow clone.
46
+ #: (String) -> Array[Hash[String, untyped]]
47
+ def renames_since(commit)
48
+ output, ok = git(["diff", "-M", "-z", "--name-status", "--relative", "--diff-filter=R", revision(commit), "--"])
49
+ return [] unless ok
50
+
51
+ output.force_encoding("UTF-8").split("\0").each_slice(3).filter_map do |status, from, to|
52
+ next unless status.to_s.start_with?("R") && from && to
53
+
54
+ { "from" => from, "to" => to, "similarity" => status.to_s.delete_prefix("R").to_i }
55
+ end
56
+ end
57
+
58
+ # The full sha at HEAD, or nil outside git or before the first commit.
59
+ #: () -> String?
60
+ def head
61
+ sha, ok = git(["rev-parse", "--verify", "--quiet", "HEAD"])
62
+ ok && !sha.strip.empty? ? sha.strip : nil
63
+ end
64
+
65
+ # Which of paths, relative to the project, are untracked or ignored, or differ from HEAD in the index
66
+ # or the working tree. Before the first commit, every path does.
67
+ #: (Array[String]) -> Array[String]
68
+ def uncommitted(paths)
69
+ return [] if paths.empty?
70
+ return paths.uniq.sort unless head
71
+
72
+ changed, = git(["diff", "--name-only", "--relative", "-z", "HEAD", "--", *paths])
73
+ untracked, = git(["ls-files", "--others", "-z", "--", *paths])
74
+ (changed.split("\0") + untracked.split("\0")).uniq.sort
75
+ end
76
+
77
+ # Which of paths, relative to the project, git does not track and does not ignore. Before the first
78
+ # commit, every path not staged is one.
79
+ #: (Array[String]) -> Array[String]
80
+ def untracked(paths)
81
+ return [] if paths.empty?
82
+
83
+ output, = git(["ls-files", "--others", "--exclude-standard", "-z", "--", *paths])
84
+ output.split("\0").sort
85
+ end
86
+
87
+ # Which of paths, relative to the project, git does not track, ignored or not. Unlike untracked, it names an
88
+ # ignored file too, since git holds it no more than an untracked one (#896).
89
+ #: (Array[String]) -> Array[String]
90
+ def not_tracked(paths)
91
+ return [] if paths.empty?
92
+
93
+ output, = git(["ls-files", "--others", "-z", "--", *paths])
94
+ output.split("\0").sort
95
+ end
96
+
97
+ # Every file in the project that differs from HEAD, is gone since it, or is untracked, sorted. Unlike
98
+ # uncommitted, it leaves ignored files out: it is asked about the whole project, where an ignored file is
99
+ # one the owner chose to keep out of git, while uncommitted is asked about named members, which an audit
100
+ # needs committed (#390, #409). Before the first commit, every file not ignored is one.
101
+ #: () -> Array[String]
102
+ def changed_files
103
+ changed, = head ? git(["diff", "--name-only", "--relative", "-z", "HEAD"]) : [""]
104
+ untracked, = git(["ls-files", "--others", "--exclude-standard", "-z"])
105
+ (changed.split("\0") + untracked.split("\0")).uniq.sort
106
+ end
107
+
108
+ # path's text at commit, or nil when the commit does not hold it.
109
+ #: (String, String) -> String?
110
+ def text_at(commit, path)
111
+ text, ok = git(["show", "--end-of-options", "#{revision(commit)}:./#{path}"])
112
+ ok ? text.force_encoding("UTF-8") : nil
113
+ end
114
+
115
+ # The commits after commit, up to HEAD, that changed any of paths, newest first, each as its short sha and
116
+ # subject line.
117
+ #: (String, Array[String]) -> Array[Hash[String, String]]
118
+ def commits_since(commit, paths)
119
+ output, ok = git(["log", "--format=%h%x00%s", "--end-of-options", "#{revision(commit)}..HEAD", "--", *paths])
120
+ return [] unless ok
121
+
122
+ output.force_encoding("UTF-8").lines.map do |line|
123
+ sha, subject = line.chomp.split("\0", 2)
124
+ { "commit" => sha.to_s, "subject" => subject.to_s }
125
+ end
126
+ end
127
+
128
+ # Every commit on HEAD's first-parent line that changed any of paths, newest first, each as its full and
129
+ # short sha, subject line, author date and whether it is a root commit ("true" or "false"); none outside git.
130
+ # since leaves out since and every commit before it.
131
+ #: (Array[String], ?since: String?) -> Array[Hash[String, String]]
132
+ def commits_touching(paths, since: nil)
133
+ range = since ? "#{revision(since)}..HEAD" : "HEAD"
134
+ output, ok = git(["log", "--first-parent", "--format=%H%x00%h%x00%s%x00%aI%x00%P", "--end-of-options", range,
135
+ "--", *paths])
136
+ return [] unless ok
137
+
138
+ output.force_encoding("UTF-8").lines.map do |line|
139
+ sha, short, subject, authored_at, parents = line.chomp.split("\0", 5)
140
+ { "sha" => sha.to_s, "commit" => short.to_s, "subject" => subject.to_s, "authored_at" => authored_at.to_s,
141
+ "root" => parents.to_s.empty?.to_s }
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ # commit, once it is known to be a revision this tool hands git. Answers are checked against their schema
148
+ # first, so reaching here with anything else is a defect in this tool, not a refusal for the agent.
149
+ #: (String) -> String
150
+ def revision(commit)
151
+ raise ArgumentError, "not a revision this tool reads: #{commit.inspect}" unless REVISION.match?(commit)
152
+
153
+ commit
154
+ end
155
+
156
+ #: (Array[String]) -> [String, bool]
157
+ def git(arguments)
158
+ output = IO.popen(LITERAL, ["git", "-C", @root, *arguments], err: File::NULL, &:read)
159
+ [output, Process.last_status&.success? || false]
160
+ rescue SystemCallError
161
+ ["", false]
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,44 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "English"
5
+ require_relative "../version"
6
+
7
+ module Principles
8
+ # Every file and process this tool reads or writes. data/CLAUDE.md owns the boundary.
9
+ module Data
10
+ # The version that is running: what a build stamped, or, in a checkout, the release
11
+ # with the commit it runs from, as RELEASE.g<short sha>. The g keeps a development version a RubyGems
12
+ # prerelease even when every character of the sha is a digit.
13
+ class RunningVersion
14
+ # git archive writes the short sha of the commit it archives here, since .gitattributes marks this file
15
+ # export-subst; a checkout keeps the placeholder as written (#898).
16
+ ARCHIVED_COMMIT = "$Format:%h$" #: String
17
+
18
+ #: (?String, ?archived_commit: String) -> void
19
+ def initialize(checkout = File.expand_path("../../..", __dir__), archived_commit: ARCHIVED_COMMIT)
20
+ @checkout = checkout
21
+ @archived_commit = archived_commit
22
+ end
23
+
24
+ #: () -> String
25
+ def to_s
26
+ BUILT_VERSION || "#{RELEASE}.g#{commit}"
27
+ end
28
+
29
+ private
30
+
31
+ # The short sha an archive wrote, which names its commit even inside another repository; else the commit the
32
+ # checkout is at, or "unknown" outside git.
33
+ #: () -> String
34
+ def commit
35
+ return @archived_commit if @archived_commit.match?(/\A\h+\z/)
36
+
37
+ sha = IO.popen(["git", "-C", @checkout, "rev-parse", "--short", "HEAD"], err: File::NULL, &:read).to_s.strip
38
+ $CHILD_STATUS&.success? && !sha.empty? ? sha : "unknown"
39
+ rescue SystemCallError
40
+ "unknown"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,139 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require_relative "../error"
6
+ require_relative "expected_type"
7
+ require_relative "known_fields"
8
+
9
+ module Principles
10
+ # Every file and process this tool reads or writes. data/CLAUDE.md owns the boundary.
11
+ module Data
12
+ # Checks parsed JSON against one of the schemas in schemas/, with the standard library alone, so a run
13
+ # needs no gem (D29). It supports only KEYWORDS, which is what this tool's own schemas use; a spec fails on
14
+ # a schema that uses another, and a conformance spec compares its verdicts with json_schemer's.
15
+ class SchemaCheck
16
+ # The keywords a schema may use, besides annotations such as title and description.
17
+ KEYWORDS = %w[type properties required additionalProperties items minLength pattern enum const].freeze
18
+ # This tool's own schema keyword, on an object that refuses fields it does not name: for a field an agent
19
+ # may put there by mistake, where it belongs instead, which its refusal adds (#769), as {field => text}.
20
+ UNREAD_FIELD_ADVICE = "x-unread-field-advice"
21
+ # Keywords that only describe, and check nothing; the refusal advice is worded into a refusal.
22
+ ANNOTATIONS = %W[$schema $id title description #{ExpectedType::ADVICE} #{UNREAD_FIELD_ADVICE}].freeze
23
+
24
+ #: (String) -> void
25
+ def initialize(name)
26
+ @schema = JSON.parse(File.read(File.join(__dir__, "schemas", "#{name}.schema.json"))) #: Hash[String, untyped]
27
+ end
28
+
29
+ # Each place data breaks the schema, as a JSON pointer and what is wrong there, sorted by pointer.
30
+ #: (untyped) -> Array[Hash[String, String]]
31
+ def problems(data)
32
+ found = [] #: Array[Hash[String, String]]
33
+ check(@schema, data, "", found)
34
+ found.sort_by { |problem| problem.fetch("pointer") }
35
+ end
36
+
37
+ # data with only the fields the schema names, for printing; KnownFields owns the rule.
38
+ #: (untyped) -> untyped
39
+ def known(data)
40
+ KnownFields.new(@schema).of(data)
41
+ end
42
+
43
+ # data when it fits the schema; otherwise a refusal with code, naming source and each failing field.
44
+ #: (untyped, code: String, source: String, repair: String) -> untyped
45
+ def checked(data, code:, source:, repair:)
46
+ found = problems(data)
47
+ return data if found.empty?
48
+
49
+ raise Error.new(code, "#{source} is not in the shape this tool reads", repair, source, fields: found)
50
+ end
51
+
52
+ private
53
+
54
+ #: (Hash[String, untyped], untyped, String, Array[Hash[String, String]]) -> void
55
+ def check(schema, value, pointer, found)
56
+ expected = ExpectedType.new(schema)
57
+ return found << problem(pointer, "must be #{expected}") unless expected.matches?(value)
58
+
59
+ check_value(schema, value, pointer, found)
60
+ check_object(schema, value, pointer, found) if value.is_a?(Hash)
61
+ check_items(schema, value, pointer, found) if value.is_a?(Array)
62
+ end
63
+
64
+ #: (Hash[String, untyped], untyped, String, Array[Hash[String, String]]) -> void
65
+ def check_value(schema, value, pointer, found)
66
+ found << problem(pointer, "must be #{JSON.generate(schema["const"])}") if mismatched_const?(schema, value)
67
+ found << problem(pointer, "must be one of #{choices(schema)}") if schema["enum"]&.include?(value) == false
68
+ check_string(schema, value, pointer, found) if value.is_a?(String)
69
+ end
70
+
71
+ #: (Hash[String, untyped], String, String, Array[Hash[String, String]]) -> void
72
+ def check_string(schema, value, pointer, found)
73
+ found << problem(pointer, "must not be empty") if too_short?(schema, value)
74
+ found << problem(pointer, "must match #{schema["pattern"]}") if unmatched?(schema, value)
75
+ end
76
+
77
+ #: (Hash[String, untyped], String) -> bool
78
+ def unmatched?(schema, value)
79
+ pattern = schema["pattern"]
80
+ pattern ? !Regexp.new(pattern).match?(value) : false
81
+ end
82
+
83
+ #: (Hash[String, untyped], untyped) -> bool
84
+ def mismatched_const?(schema, value)
85
+ schema.key?("const") && value != schema["const"]
86
+ end
87
+
88
+ #: (Hash[String, untyped]) -> String
89
+ def choices(schema)
90
+ schema.fetch("enum").map { |each| JSON.generate(each) }.join(", ")
91
+ end
92
+
93
+ #: (Hash[String, untyped], untyped) -> bool
94
+ def too_short?(schema, value)
95
+ minimum = schema["minLength"]
96
+ return false unless minimum && value.is_a?(String)
97
+
98
+ value.length < minimum
99
+ end
100
+
101
+ #: (Hash[String, untyped], Hash[String, untyped], String, Array[Hash[String, String]]) -> void
102
+ def check_object(schema, value, pointer, found)
103
+ schema.fetch("required", []).each do |key|
104
+ found << problem(child(pointer, key), "is required") unless value.key?(key)
105
+ end
106
+ value.each { |key, each| check_property(schema, key, each, pointer, found) }
107
+ end
108
+
109
+ #: (Hash[String, untyped], String, untyped, String, Array[Hash[String, String]]) -> void
110
+ def check_property(schema, key, value, pointer, found)
111
+ # A field the schema names, else additionalProperties: false refuses it, or a schema checks it.
112
+ property = schema.fetch("properties", {})[key] || schema["additionalProperties"]
113
+ if property.is_a?(Hash)
114
+ check(property, value, child(pointer, key), found)
115
+ elsif property == false
116
+ advice = schema.fetch(UNREAD_FIELD_ADVICE, {})[key]
117
+ found << problem(child(pointer, key), ["is not a field this tool reads here", *advice].join("; "))
118
+ end
119
+ end
120
+
121
+ #: (Hash[String, untyped], Array[untyped], String, Array[Hash[String, String]]) -> void
122
+ def check_items(schema, value, pointer, found)
123
+ items = schema["items"] or return
124
+ value.each_with_index { |each, index| check(items, each, child(pointer, index.to_s), found) }
125
+ end
126
+
127
+ # The pointer to key inside pointer, escaped as RFC 6901 asks.
128
+ #: (String, String) -> String
129
+ def child(pointer, key)
130
+ "#{pointer}/#{key.gsub("~", "~0").gsub("/", "~1")}"
131
+ end
132
+
133
+ #: (String, String) -> Hash[String, String]
134
+ def problem(pointer, text)
135
+ { "pointer" => pointer, "problem" => text }
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "acknowledgement-answer.schema.json",
4
+ "title": "Acknowledgement answer",
5
+ "description": "What an agent answers a one-member audit with (D33): the request's id and basis, whether the change keeps the member's meaning, a one-line reason, and a line quoted from the member's text before the change and one from after it. Fields this tool does not read are refused (D29). Models::Acknowledgement checks the quotes and the reason.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["request_id", "basis", "meaning_kept", "reason", "old_line", "new_line"],
9
+ "properties": {
10
+ "request_id": { "type": "string" },
11
+ "basis": {
12
+ "type": "object",
13
+ "additionalProperties": false,
14
+ "required": ["base_commit", "member_id"],
15
+ "properties": {
16
+ "base_commit": { "type": "string" },
17
+ "member_id": { "type": "string" }
18
+ }
19
+ },
20
+ "meaning_kept": { "type": "boolean" },
21
+ "reason": { "type": "string", "minLength": 1 },
22
+ "old_line": { "type": "string", "minLength": 1 },
23
+ "new_line": { "type": "string", "minLength": 1 }
24
+ }
25
+ }
@@ -0,0 +1,99 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "answer-guide.schema.json",
4
+ "title": "Answer guide",
5
+ "description": "Example shapes the assessment request shows for the parts of an answer its template cannot show by example.",
6
+ "type": "object",
7
+ "required": ["finding", "member_delivery", "recent_decision", "reassessment", "check_result"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "finding": {
11
+ "type": "object",
12
+ "required": ["id", "member_ids", "problem", "consequence", "proposed_action", "evidence"],
13
+ "additionalProperties": false,
14
+ "properties": {
15
+ "id": { "type": "string" },
16
+ "member_ids": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
17
+ "problem": { "type": "string" },
18
+ "consequence": { "type": "string" },
19
+ "proposed_action": { "type": "string" },
20
+ "evidence": {
21
+ "type": "array",
22
+ "minItems": 1,
23
+ "items": {
24
+ "type": "object",
25
+ "required": ["path"],
26
+ "properties": {
27
+ "path": { "type": "string" },
28
+ "heading": { "type": "string" },
29
+ "starting_at": { "type": "string" },
30
+ "ending_at": { "type": "string" },
31
+ "query": { "type": "string" }
32
+ },
33
+ "additionalProperties": false
34
+ }
35
+ }
36
+ }
37
+ },
38
+ "member_delivery": {
39
+ "type": "object",
40
+ "required": ["not_observed", "observed"],
41
+ "additionalProperties": false,
42
+ "properties": {
43
+ "not_observed": {
44
+ "type": "object",
45
+ "required": ["delivery"],
46
+ "properties": { "delivery": { "const": "not_observed" } },
47
+ "additionalProperties": false
48
+ },
49
+ "observed": {
50
+ "type": "object",
51
+ "required": ["delivery", "observation"],
52
+ "properties": { "delivery": { "const": "observed" }, "observation": { "type": "string" },
53
+ "observed_at": { "type": "string" } },
54
+ "additionalProperties": false
55
+ }
56
+ }
57
+ },
58
+ "recent_decision": {
59
+ "type": "object",
60
+ "required": ["assessed", "unavailable"],
61
+ "additionalProperties": false,
62
+ "properties": {
63
+ "assessed": {
64
+ "type": "object",
65
+ "required": ["assessment", "evidence"],
66
+ "additionalProperties": false,
67
+ "properties": {
68
+ "assessment": { "type": "string" },
69
+ "evidence": { "$ref": "#/properties/finding/properties/evidence" }
70
+ }
71
+ },
72
+ "unavailable": {
73
+ "type": "object",
74
+ "required": ["unavailable_reason"],
75
+ "properties": { "unavailable_reason": { "type": "string" } },
76
+ "additionalProperties": false
77
+ }
78
+ }
79
+ },
80
+ "reassessment": { "type": "string" },
81
+ "check_result": {
82
+ "type": "object",
83
+ "required": ["ran", "not_run", "limits"],
84
+ "additionalProperties": false,
85
+ "properties": {
86
+ "ran": {
87
+ "type": "object", "required": ["command", "exit_status"], "additionalProperties": false,
88
+ "properties": { "command": { "type": "string" }, "exit_status": { "type": "integer" },
89
+ "review_basis": { "type": "string" } }
90
+ },
91
+ "not_run": {
92
+ "type": "object", "required": ["command", "not_run_reason"], "additionalProperties": false,
93
+ "properties": { "command": { "type": "string" }, "not_run_reason": { "type": "string" } }
94
+ },
95
+ "limits": { "type": "string" }
96
+ }
97
+ }
98
+ }
99
+ }