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,45 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../error"
5
+
6
+ module Principles
7
+ # Every file and process this tool reads or writes. data/CLAUDE.md owns the boundary.
8
+ module Data
9
+ # A file this tool reads whole, refused unless it is a regular file no larger than LIMIT. Without the check,
10
+ # a special file such as /dev/zero, or a file of gigabytes, would hang a run or exhaust its memory.
11
+ class BoundedFile
12
+ # Far above any instruction file, map, audit record or answer, and far below what exhausts memory.
13
+ LIMIT = 4 * 1024 * 1024 #: Integer
14
+
15
+ #: (String, ?shown_as: String) -> void
16
+ def initialize(path, shown_as: path)
17
+ @path = path
18
+ @shown_as = shown_as
19
+ freeze
20
+ end
21
+
22
+ # The file's text, read once it is known to be a regular file within LIMIT.
23
+ #: (?encoding: String) -> String
24
+ def read(encoding: "UTF-8")
25
+ unless File.file?(@path)
26
+ raise Error.new("not_a_file", "#{@shown_as} is not a regular file", "Name a regular file.", @shown_as)
27
+ end
28
+
29
+ refuse_larger(File.size(@path))
30
+
31
+ File.read(@path, encoding: encoding)
32
+ end
33
+
34
+ # Refuses bytes of text, such as stdin's, that are more than this tool reads from one file.
35
+ #: (Integer) -> void
36
+ def refuse_larger(bytes)
37
+ return if bytes <= LIMIT
38
+
39
+ raise Error.new("file_too_large", "#{@shown_as} holds #{bytes} bytes, more than the #{LIMIT} bytes " \
40
+ "read at most",
41
+ "Name a smaller file, or map a smaller part of it.", @shown_as)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,105 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "find"
5
+ require_relative "../error"
6
+ require_relative "ignored_by_git"
7
+
8
+ module Principles
9
+ # Every file and process this tool reads or writes. data/CLAUDE.md owns the boundary.
10
+ module Data
11
+ # The instruction files an agent host loads by convention, as list-candidate-files finds them in the project: each
12
+ # readable one with its length, those that cannot be read, those in a folder that often holds test data, and in
13
+ # git those git ignores.
14
+ class CandidateFilesFound
15
+ # Folders the inventory never enters: version control, this tool's own records, and installed packages.
16
+ PRUNED_FOLDERS = %w[.git .principles .memeplex .coord node_modules vendor .bundle].freeze
17
+
18
+ # The instruction files an agent host loads by convention. The inventory lists only these.
19
+ CANDIDATE_NAMES = %w[CLAUDE.md AGENTS.md].freeze
20
+ CANDIDATE_PATH = %r{(?:\A|/)\.claude/(?:rules/|skills/|agents/|settings[^/]*\.json\z)}
21
+
22
+ # Folder names that often hold test data shaped like guidance, such as a sample CLAUDE.md under
23
+ # spec/fixtures/. A name is a hint, not a fact: the agent decides, so these files stay listed and are
24
+ # named here with the folder that matched (alpha 009 friction 1).
25
+ TEST_DATA_FOLDER = %r{(?:\A|/)(fixtures?|examples?|samples?)/}
26
+
27
+ # One readable candidate: its path, its length in characters, and for a link the file it leads to (#695).
28
+ # The stand-in project lists by it too, so the two cannot disagree on the shape.
29
+ #: (String, text: String, leads_to: String) -> Hash[String, untyped]
30
+ def self.listed(path, text:, leads_to:)
31
+ entry = { "path" => path, "characters" => text.length }
32
+ leads_to == path ? entry : entry.merge("symlink_to" => leads_to)
33
+ end
34
+
35
+ #: (ProjectFiles) -> void
36
+ def initialize(files)
37
+ @files = files
38
+ end
39
+
40
+ #: () -> Hash[String, untyped]
41
+ def to_h
42
+ candidates = candidate_paths
43
+ ignored = IgnoredByGit.new(@files.root, candidates).facts
44
+ entries, skipped = readable(candidates - (ignored&.fetch("ignored") || []))
45
+ entries = entries.sort_by { |entry| entry.fetch("path") }
46
+ listed = { "files" => entries, "unreadable" => skipped,
47
+ "possibly_test_data" => entries.filter_map { |entry| test_data_hint(entry.fetch("path")) } }
48
+ ignored ? listed.merge(ignored) : listed
49
+ end
50
+
51
+ private
52
+
53
+ # Each candidate's path relative to the root, in the order the walk finds them.
54
+ #: () -> Array[String]
55
+ def candidate_paths
56
+ paths = [] #: Array[String]
57
+ Find.find(@files.root) do |absolute|
58
+ if File.directory?(absolute)
59
+ Find.prune if PRUNED_FOLDERS.include?(File.basename(absolute)) || nested_repository?(absolute)
60
+ next
61
+ end
62
+ relative = absolute.delete_prefix("#{@files.root}/")
63
+ paths << relative if candidate?(relative)
64
+ end
65
+ paths
66
+ end
67
+
68
+ # A folder below the root holding its own .git, a worktree's file or a clone's folder, is another project,
69
+ # as git treats it (#376). The root may hold one too: this tool runs in a worktree.
70
+ #: (String) -> bool
71
+ def nested_repository?(absolute)
72
+ absolute != @files.root && File.exist?(File.join(absolute, ".git"))
73
+ end
74
+
75
+ # Each path with its length in characters, and the refusal for each that cannot be read.
76
+ #: (Array[String]) -> [Array[Hash[String, untyped]], Array[Hash[String, untyped]]]
77
+ def readable(paths)
78
+ entries = [] #: Array[Hash[String, untyped]]
79
+ skipped = [] #: Array[Hash[String, untyped]]
80
+ paths.each do |relative|
81
+ entries << listed(relative)
82
+ rescue Error => e
83
+ skipped << e.payload
84
+ end
85
+ [entries, skipped]
86
+ end
87
+
88
+ #: (String) -> Hash[String, untyped]
89
+ def listed(relative)
90
+ CandidateFilesFound.listed(relative, text: @files.read_text(relative), leads_to: @files.leads_to(relative))
91
+ end
92
+
93
+ #: (String) -> Hash[String, String]?
94
+ def test_data_hint(relative)
95
+ folder = relative[TEST_DATA_FOLDER, 1]
96
+ { "path" => relative, "folder" => folder } if folder
97
+ end
98
+
99
+ #: (String) -> bool
100
+ def candidate?(relative)
101
+ CANDIDATE_NAMES.include?(File.basename(relative)) || relative.match?(CANDIDATE_PATH)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,305 @@
1
+ {
2
+ "groups": [
3
+ { "id": "read", "heading": "Read; these change nothing on disk",
4
+ "writes": "Writes nothing." },
5
+ { "id": "write-map", "heading": "Write the member map, `.principles/maps/ID.json`, which the project commits",
6
+ "writes": "Writes `.principles/maps/ID.json`; commit it as it changes. Never a member's file." },
7
+ { "id": "write-audit-record", "heading": "Audit; these write only the audit record, `.principles/audits/ID.json`, which the project commits",
8
+ "writes": "Writes `.principles/audits/ID.json`, listed under `wrote` when it does; commit it. Never the map or a member's file." }
9
+ ],
10
+ "refusal_groups": {
11
+ "any-command": ["file_too_large", "invalid_invocation", "invalid_shape", "filesystem_error", "unknown_command", "missing_id", "invalid_command_steps"],
12
+ "read-map": ["principles_folder_not_moved", "both_principles_folders", "symlinked_directory", "unknown_principle", "unparseable_map", "invalid_map", "newer_format", "duplicate_member"],
13
+ "read-audit": ["principles_folder_not_moved", "both_principles_folders", "symlinked_directory", "unparseable_audit", "invalid_audit_record"],
14
+ "locate-text": ["outside_project", "not_a_file", "unreadable_file", "invalid_encoding", "section_needs_markdown", "query_needs_frontmatter", "query_with_other_locators", "unsupported_query", "unsupported_frontmatter", "duplicate_frontmatter_key", "field_not_found", "section_not_found",
15
+ "ambiguous_heading", "passage_not_found", "ambiguous_passage", "passage_end_not_found",
16
+ "broken_members"]
17
+ },
18
+ "commands": [
19
+ {
20
+ "name": "list-candidate-files", "usage": "list-candidate-files", "group": "read", "options": [],
21
+ "summary": "Ask what in this project looks like a principle.",
22
+ "help": "learn-what-a-command-does/list-candidate-files",
23
+ "steps": [
24
+ { "id": "list-instruction-files", "actor": "cli",
25
+ "does": "Lists the instruction files agent hosts load by convention, naming git-ignored ones unread rather than listing them, each folder git ignores counted once, leaving out folders that hold their own git repository, and names any under a fixtures, examples or samples folder and, in git, any git does not track." },
26
+ { "id": "choose-one-principle", "actor": "agent",
27
+ "does": "Choose one principle several listed files reinforce. Decide whether each file named in possibly_test_data is guidance or a sample.",
28
+ "prompt": "find-a-principle-to-map/choose-one-principle",
29
+ "cannot_check": "whether a file is guidance or test data; ask the project's owner when you cannot tell.",
30
+ "answer_with": "create-principle" }
31
+ ]
32
+ },
33
+ {
34
+ "name": "list-principles", "usage": "list-principles", "group": "read", "options": [],
35
+ "summary": "Every mapped principle and whether its members are current.",
36
+ "help": "learn-what-a-command-does/list-principles",
37
+ "steps": [
38
+ { "id": "count-members-by-freshness", "actor": "cli",
39
+ "does": "Reads every map and the audit record of each, and counts its members as current, changed, unassessed, locator_lost or unavailable.",
40
+ "refuses_like": ["read-map", "read-audit"] }
41
+ ]
42
+ },
43
+ {
44
+ "name": "list-member-problems", "usage": "list-member-problems [ID] [--short | --commits-after COMMIT --hook-event EVENT]", "group": "read", "options": ["short", "commits-after", "hook-event"],
45
+ "summary": "Members whose reference is broken, or whose own text changed since their baseline.",
46
+ "help": "learn-what-a-command-does/list-member-problems",
47
+ "steps": [
48
+ { "id": "compare-members-with-their-baselines", "actor": "cli",
49
+ "does": "Finds each member of map ID, or of every map, and compares its own text with the last audit's hash for it, or else with its text at the commit that mapped it where it is. Refuses while any member cannot be found or read, or a map cannot be read, so a gate can stop on the exit status; otherwise lists each changed member with what it was compared with, and counts every state. With --short, prints one tab-separated line per changed member and exits 3 when any changed.",
50
+ "refuses": ["broken_members"], "refuses_like": ["read-map", "read-audit"] },
51
+ { "id": "repair-or-audit", "actor": "agent",
52
+ "does": "For a refusal, say whether each listed member moved or is gone, and repair it. For a changed member, audit its principle when the change was meant to alter the principle.",
53
+ "cannot_check": "whether a change alters what a member means; this tool compares text, not meaning.",
54
+ "answer_with": "move-member" }
55
+ ]
56
+ },
57
+ {
58
+ "name": "show-principle", "usage": "show-principle ID", "group": "read", "options": [],
59
+ "summary": "Each member, where it is and whether it changed.",
60
+ "help": "learn-what-a-command-does/show-principle",
61
+ "steps": [
62
+ { "id": "compare-members-with-last-audit", "actor": "cli",
63
+ "does": "Compares each member's text with the audit record: current, changed, unassessed, locator_lost when its passage no longer matches, or unavailable. Adds last_audit: its status, time and commit, and whether the map changed since.",
64
+ "refuses_like": ["read-map", "read-audit"] }
65
+ ]
66
+ },
67
+ {
68
+ "name": "show-audit", "usage": "show-audit ID", "group": "read", "options": [],
69
+ "summary": "The record of a principle's last audit.",
70
+ "help": "learn-what-a-command-does/show-audit",
71
+ "steps": [
72
+ { "id": "show-record", "actor": "cli",
73
+ "does": "Prints the audit record: its status, base commit, each member's hash and each finding's members and disposition.",
74
+ "refuses": ["unknown_principle", "unknown_audit"], "refuses_like": ["read-audit"] }
75
+ ]
76
+ },
77
+ {
78
+ "name": "list-open-findings", "usage": "list-open-findings [ID] [--short [--session-start SOURCE]]", "group": "read", "options": ["short", "session-start"],
79
+ "summary": "The findings the last audit of each principle deferred or accepted, and where each is written up.",
80
+ "help": "learn-what-a-command-does/list-open-findings",
81
+ "steps": [
82
+ { "id": "read-open-findings", "actor": "cli",
83
+ "does": "Reads the last audit record of principle ID, or of every principle, and lists each deferred or accepted finding with its action, its members, its recorded_at pointer and when the audit was made. Says that an earlier audit's open findings are only in git. With --short, prints one line saying how many are open, how long the oldest has waited and what it waits on, and nothing when none is open; --session-start clear or compact words it for a context just cleared or compacted.",
84
+ "refuses_like": ["read-map", "read-audit"] },
85
+ { "id": "follow-up-each-finding", "actor": "agent",
86
+ "does": "Read each finding where its recorded_at points, and decide each one at its principle's next full audit.",
87
+ "cannot_check": "whether a deferred or accepted finding still holds; this tool keeps only its members and action.",
88
+ "answer_with": "start-audit" }
89
+ ]
90
+ },
91
+ {
92
+ "name": "show-answer-check", "usage": "show-answer-check REQUEST --file FILE|-", "group": "read", "options": ["file"],
93
+ "summary": "Check an answer without submitting it.",
94
+ "help": "learn-what-a-command-does/show-answer-check",
95
+ "steps": [
96
+ { "id": "check-without-writing", "actor": "cli",
97
+ "does": "Runs every check submit-answer would and prints the same refusal, or names the files submitting would write. Writes nothing.",
98
+ "refuses": ["unparseable_json", "unreadable_file", "request_mismatch", "unknown_request", "stale_request", "stale_map", "undeclared_move", "moves_need_git", "map_entries_need_git",
99
+ "incomplete_coverage", "invalid_delivery", "observation_repeats_last_audit", "invalid_check_result", "invalid_answer", "unknown_member", "duplicate_finding",
100
+ "incomplete_dispositions", "invalid_disposition", "claimed_change_missing", "unassessed_changes"],
101
+ "refuses_like": ["read-map", "read-audit", "locate-text"] },
102
+ { "id": "submit-the-checked-answer", "actor": "agent",
103
+ "does": "Fix what a refusal names and check again, or submit the answer unchanged.",
104
+ "answer_with": "submit-answer" }
105
+ ]
106
+ },
107
+ {
108
+ "name": "show-meta-principle-passage", "usage": "show-meta-principle-passage", "group": "read", "options": [],
109
+ "summary": "Set principles up: propose the passage that keeps the others held between audits, and the add-member that maps it.",
110
+ "help": "learn-what-a-command-does/show-meta-principle-passage",
111
+ "steps": [
112
+ { "id": "propose-meta-principle-passage", "actor": "cli",
113
+ "does": "Writes nothing. Shows the sticking-to-your-principles map that the printed add-member creates, its description and an activation for all work, or, where that map is already there, shows it and names its members already in an entry file; says which of CLAUDE.md and AGENTS.md exist and whether one links the other, and proposes the passage each would take, with its size.",
114
+ "refuses_like": ["read-map"] },
115
+ { "id": "place-the-passage", "actor": "agent",
116
+ "does": "With no member mapped, show the user the passage, agree where it goes, write it there, and map it as the principle's member, or map another file or setting that keeps the principles held, such as a timer. With a member mapped, agree with the user which step each member lacks, and add any that is missing.",
117
+ "cannot_check": "where the passage belongs, or whether a passage or member already in the project does its job; agree that with the user.",
118
+ "answer_with": "add-member" }
119
+ ]
120
+ },
121
+ {
122
+ "name": "create-principle", "usage": "create-principle ID --description TEXT", "group": "write-map", "options": ["description"],
123
+ "summary": "Map a new principle and say what it is.",
124
+ "help": "learn-what-a-command-does/create-principle",
125
+ "steps": [
126
+ { "id": "write-new-map", "actor": "cli",
127
+ "does": "Checks that ID is lower-kebab-case, not yet mapped and not sticking-to-your-principles, which setup starts, then writes a map with the description and no members.",
128
+ "refuses": ["invalid_id", "principle_exists", "meta_principle_id", "invalid_answer"] },
129
+ { "id": "choose-members", "actor": "agent",
130
+ "does": "Choose the files, sections or passages that reinforce the principle, and the part each plays.",
131
+ "cannot_check": "whether the members you choose are the right ones.",
132
+ "answer_with": "add-member" }
133
+ ]
134
+ },
135
+ {
136
+ "name": "change-description", "usage": "change-description ID --description TEXT", "group": "write-map", "options": ["description"],
137
+ "summary": "Reword a mapped principle.",
138
+ "help": "learn-what-a-command-does/change-description",
139
+ "steps": [
140
+ { "id": "write-description", "actor": "cli", "does": "Replaces the map's description.",
141
+ "refuses": ["invalid_answer"], "refuses_like": ["read-map"] }
142
+ ]
143
+ },
144
+ {
145
+ "name": "change-activation", "usage": "change-activation ID (--when-files GLOB [--when-files GLOB ...] | --when-invoked | --always)", "group": "write-map", "options": ["when-files", "when-invoked", "always"],
146
+ "summary": "Record when a principle's work is in hand.",
147
+ "help": "learn-what-a-command-does/change-activation",
148
+ "steps": [
149
+ { "id": "write-activation", "actor": "cli",
150
+ "does": "Checks that exactly one form is given and no glob is empty, then replaces the map's activation.",
151
+ "refuses": ["invalid_activation", "invalid_answer"], "refuses_like": ["read-map"] }
152
+ ]
153
+ },
154
+ {
155
+ "name": "add-member", "usage": "add-member ID PATH [--section HEADING] [--starting-at TEXT [--ending-at TEXT]] [--query QUERY] --role ROLE [--check COMMAND]", "group": "write-map", "options": ["section", "starting-at", "ending-at", "query", "role", "check"],
156
+ "summary": "Add a file, a Markdown section or a passage.",
157
+ "help": "learn-what-a-command-does/add-member",
158
+ "steps": [
159
+ { "id": "locate-new-member", "actor": "cli",
160
+ "does": "Finds the member's text, so a locator that matches nothing is refused now rather than at the audit.",
161
+ "refuses": ["invalid_answer", "ending_without_start"], "refuses_like": ["read-map", "locate-text"] },
162
+ { "id": "write-member", "actor": "cli",
163
+ "does": "Refuses a member already in the map, then adds it with its role; the first member of sticking-to-your-principles creates its map.",
164
+ "refuses": ["member_exists"] },
165
+ { "id": "add-next-or-audit", "actor": "agent",
166
+ "does": "Add the next member the same way. Once the map lists every member, commit it: in git, that commit is the members' baseline until their first full audit, which judges them and may come later; outside git, the first full audit gives them one.",
167
+ "answer_with": "start-audit", "answer_usage": "start-audit ID",
168
+ "answer_when": "When you choose to start the first full audit" }
169
+ ]
170
+ },
171
+ {
172
+ "name": "move-member", "usage": "move-member ID MEMBER [--path PATH] [--section HEADING] [--starting-at TEXT [--ending-at TEXT]] [--query QUERY]", "group": "write-map", "options": ["path", "section", "starting-at", "ending-at", "query"],
173
+ "summary": "Point a member at a new place in its file, or in another file: the one its file was renamed to, or one its text moved into.",
174
+ "help": "learn-what-a-command-does/move-member",
175
+ "steps": [
176
+ { "id": "find-member", "actor": "cli", "does": "Finds MEMBER in the map.",
177
+ "refuses": ["unknown_member", "invalid_answer"], "refuses_like": ["read-map"] },
178
+ { "id": "locate-new-place", "actor": "cli",
179
+ "does": "Finds the text at the new locator, in PATH when --path names the file the member's file was renamed to or its text moved into, or else in its own file. The locator replaces the old one whole, and the member keeps its role and the key its audit is filed under.",
180
+ "refuses": ["invalid_answer", "ending_without_start", "member_exists"], "refuses_like": ["locate-text"] }
181
+ ]
182
+ },
183
+ {
184
+ "name": "change-member-role", "usage": "change-member-role ID MEMBER --role ROLE", "group": "write-map", "options": ["role"],
185
+ "summary": "Reword a member's role.",
186
+ "help": "learn-what-a-command-does/change-member-role",
187
+ "steps": [
188
+ { "id": "write-role", "actor": "cli", "does": "Finds MEMBER in the map and replaces its role.",
189
+ "refuses": ["unknown_member", "invalid_answer"], "refuses_like": ["read-map"] }
190
+ ]
191
+ },
192
+ {
193
+ "name": "change-member-check", "usage": "change-member-check ID MEMBER --check COMMAND", "group": "write-map", "options": ["check"],
194
+ "summary": "Name the command that enforces a member, which each audit asks you to run.",
195
+ "help": "learn-what-a-command-does/change-member-check",
196
+ "steps": [
197
+ { "id": "write-check", "actor": "cli", "does": "Finds MEMBER in the map and stores COMMAND as its check. This tool never runs it.",
198
+ "refuses": ["unknown_member", "invalid_answer"], "refuses_like": ["read-map"] }
199
+ ]
200
+ },
201
+ {
202
+ "name": "remove-member-check", "usage": "remove-member-check ID MEMBER", "group": "write-map", "options": [],
203
+ "summary": "Stop naming a check for a member.",
204
+ "help": "learn-what-a-command-does/remove-member-check",
205
+ "steps": [
206
+ { "id": "drop-check", "actor": "cli", "does": "Finds MEMBER in the map and removes its check, refusing a member that has none.",
207
+ "refuses": ["unknown_member", "no_check", "invalid_answer"], "refuses_like": ["read-map"] }
208
+ ]
209
+ },
210
+ {
211
+ "name": "remove-member", "usage": "remove-member ID MEMBER", "group": "write-map", "options": [],
212
+ "summary": "Take a member out of the map.",
213
+ "help": "learn-what-a-command-does/remove-member",
214
+ "steps": [
215
+ { "id": "drop-member", "actor": "cli", "does": "Finds MEMBER in the map and removes it.",
216
+ "refuses": ["unknown_member", "invalid_answer"], "refuses_like": ["read-map"] }
217
+ ]
218
+ },
219
+ {
220
+ "name": "start-audit", "usage": "start-audit ID [--member MEMBER | --write-ups]", "group": "write-audit-record", "options": ["member", "write-ups"],
221
+ "summary": "Ask for an assessment of every member and the set.",
222
+ "help": "learn-what-a-command-does/start-audit",
223
+ "steps": [
224
+ { "id": "snapshot-members", "actor": "cli",
225
+ "does": "Refuses an empty map or, in git, an uncommitted map or member file; bases the audit on HEAD. Writes nothing.",
226
+ "refuses": ["map_has_no_members", "uncommitted_files"], "refuses_like": ["read-map", "locate-text"],
227
+ "next": [{ "to": "assess-every-member", "when": "you passed neither --member nor --write-ups" },
228
+ { "to": "start-acknowledgement", "when": "you passed --member" },
229
+ { "to": "start-repointing", "when": "you passed --write-ups" }] },
230
+ { "id": "assess-every-member", "actor": "agent",
231
+ "does": "Assess each member not carried forward, and any carried one whose sighting is too old to stand behind, the set as a whole and one decision you made recently under it, with a finding for each problem. Decide each prior finding and member to check against the activation the request lists. The request says how to answer each part.",
232
+ "prompt": "assess-every-member/assess-each-member-and-the-set",
233
+ "checks": "every member is assessed or carried forward once, a marker only where the request carries the member, and each piece of evidence resolves to project text that has not changed since the request.",
234
+ "cannot_check": "whether your assessment is true, a member reached your context, or your findings are the right ones.",
235
+ "answer_with": "submit-answer" },
236
+ { "id": "start-acknowledgement", "actor": "cli",
237
+ "does": "With --member, starts a one-member audit instead: in git, for a member the last completed audit assessed whose text changed since, names its baseline commit and the change to read. Writes nothing.",
238
+ "refuses": ["cannot_acknowledge"] },
239
+ { "id": "judge-the-change", "actor": "agent",
240
+ "does": "Say whether the change keeps the member's principle, with a one-line reason and a quote from then and now.",
241
+ "prompt": "acknowledge-a-change/say-whether-the-change-keeps-the-meaning",
242
+ "checks": "the meaning is said to be kept, the reason is one line, and both quotes are the member's.",
243
+ "cannot_check": "whether the change keeps the meaning, or whether you read it.",
244
+ "answer_with": "submit-answer" },
245
+ { "id": "start-repointing", "actor": "cli",
246
+ "does": "With --write-ups, lists instead each finding the last completed audit left open, its recorded_at and what list-open-findings says of it, and asks where you found each moved write-up, which it cannot check. Writes nothing.",
247
+ "refuses": ["cannot_repoint"] }
248
+ ]
249
+ },
250
+ {
251
+ "name": "submit-answer", "usage": "submit-answer REQUEST --file FILE|-", "group": "write-audit-record", "options": ["file"],
252
+ "summary": "Give the answer a request asked for.",
253
+ "help": "learn-what-a-command-does/submit-answer",
254
+ "steps": [
255
+ { "id": "match-request", "actor": "cli",
256
+ "does": "Reads FILE, or stdin for -, and checks that its request_id is REQUEST, an id this tool prints.",
257
+ "refuses": ["unparseable_json", "unreadable_file", "request_mismatch", "unknown_request"],
258
+ "next": [{ "to": "acknowledge-the-change", "when": "the request was a one-member audit" },
259
+ { "to": "record-the-repoints", "when": "the request was a repoint of write-ups" },
260
+ { "to": "recheck-members", "when": "the request was an assessment or a correction" }] },
261
+ { "id": "acknowledge-the-change", "actor": "cli",
262
+ "does": "For a one-member audit, checks that the answer says the meaning is kept, with a one-line reason and a quote of the member then and now, then moves its hash in the audit record and keeps the acknowledgement beside it.",
263
+ "refuses": ["stale_request", "cannot_acknowledge", "quote_not_found"] },
264
+ { "id": "commit-the-acknowledgement", "actor": "agent",
265
+ "does": "Commit the audit record, then check that list-member-problems --short no longer reports the member.",
266
+ "cannot_check": "that you committed, or that the change keeps the meaning.",
267
+ "answer_with": "list-member-problems" },
268
+ { "id": "record-the-repoints", "actor": "cli",
269
+ "does": "Points each finding at its new recorded_at, once found, in the last completed record, keeping the old one, when and why; names other principles' lost write-ups; refuses once that record changed, unless it writes nothing.",
270
+ "refuses": ["cannot_repoint", "audit_completed_since_request", "unknown_finding", "duplicate_finding", "invalid_answer", "uncommitted_files"], "refuses_like": ["locate-text"] },
271
+ { "id": "commit-the-repoint", "actor": "agent",
272
+ "does": "Commit the audit record, then check that list-open-findings --short no longer names those write-ups.",
273
+ "cannot_check": "that you committed, or that the new places hold the write-ups.",
274
+ "answer_with": "list-open-findings" },
275
+ { "id": "recheck-members", "actor": "cli",
276
+ "does": "Re-reads the map and every member's text, and the record outside git.",
277
+ "refuses_like": ["read-map", "read-audit", "locate-text"],
278
+ "next": [{ "to": "check-assessment", "when": "the request was an assessment" },
279
+ { "to": "check-dispositions", "when": "the request was a correction" }] },
280
+ { "id": "check-assessment", "actor": "cli",
281
+ "does": "Refuses an answer when the map, a member, its base commit or the listed loads changed since the request. Then checks every member is covered once, every field and piece of evidence resolves, and each listed loading and prior finding is decided once. Prints a correction request for findings; outside git, writes the record as open.",
282
+ "refuses": ["stale_request", "audit_completed_since_request", "incomplete_prior_findings", "invalid_decision", "raised_in_not_found", "raised_in_unreadable", "raised_in_lacks_finding", "raised_in_time_differs", "first_raised_not_earlier", "incomplete_loading_decisions", "invalid_loading_decision", "incomplete_coverage", "invalid_delivery", "observation_repeats_last_audit", "invalid_check_result", "invalid_answer", "unknown_member", "duplicate_finding"],
283
+ "refuses_like": ["locate-text"],
284
+ "next": [{ "to": "complete-audit", "when": "there are no findings" },
285
+ { "to": "dispose-of-each-finding", "when": "there are findings" }] },
286
+ { "id": "dispose-of-each-finding", "actor": "agent",
287
+ "does": "Fix each finding and reassess what you changed, or record why it waits: deferred, accepted or disagreed.",
288
+ "prompt": "dispose-of-each-finding/fix-or-record-each-finding",
289
+ "checks": "each finding has one disposition, and each fixed finding changed the members it names.",
290
+ "cannot_check": "whether a fix is right or a deferral justified.",
291
+ "answer_with": "submit-answer" },
292
+ { "id": "check-dispositions", "actor": "cli",
293
+ "does": "Refuses a correction when the map changed since the assessment beyond moved_members. Then checks one disposition per finding, that only fixed findings' members changed since the base, and that each recorded_at resolves.",
294
+ "refuses": ["stale_map", "undeclared_move", "moves_need_git", "map_entries_need_git", "stale_request", "audit_completed_since_request", "incomplete_dispositions", "invalid_disposition", "invalid_answer", "claimed_change_missing", "unassessed_changes"],
295
+ "refuses_like": ["locate-text"] },
296
+ { "id": "complete-audit", "actor": "cli",
297
+ "does": "Writes the record unless it already says the same, and prints the open and resolved findings and the files to commit, in git only those differing from HEAD, with the commits since the base touching each changed member.",
298
+ "prompt": "report-the-completed-audit/what-completion-means" },
299
+ { "id": "report-the-outcome", "actor": "agent",
300
+ "does": "Commit the record with your fixes, and report the outcome, open findings included, to the project's owner.",
301
+ "cannot_check": "that the owner was told, or that you committed." }
302
+ ]
303
+ }
304
+ ]
305
+ }
@@ -0,0 +1,50 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require_relative "../error"
6
+
7
+ module Principles
8
+ # Every file and process this tool reads or writes. data/CLAUDE.md owns the boundary.
9
+ module Data
10
+ # command-steps.json, which ships in the gem beside this file and lists every command's steps.
11
+ #
12
+ # A run reads it without its schema. The file is part of the gem, and `rake ci` checks it against the
13
+ # schema before every commit (spec/unit/principles/data/command_steps_file_spec.rb). So --version and
14
+ # --help need nothing beyond the standard library, even on a Ruby where json_schemer is not installed.
15
+ class CommandStepsFile
16
+ PATH = File.join(__dir__, "command-steps.json").freeze #: String
17
+ SCHEMA = File.join(__dir__, "schemas", "command-steps.schema.json").freeze #: String
18
+
19
+ #: String
20
+ attr_reader :path
21
+
22
+ # path defaults to the file shipped in the gem; a spec passes a changed copy.
23
+ #: (?String) -> void
24
+ def initialize(path = PATH)
25
+ @path = path
26
+ freeze
27
+ end
28
+
29
+ #: () -> untyped
30
+ def read
31
+ JSON.parse(File.read(path))
32
+ rescue JSON::ParserError => e
33
+ raise Error.new("invalid_command_steps", e.message, "Repair the JSON of this file.", path)
34
+ end
35
+
36
+ # The file's content once it passes its schema, or a refusal naming each failing field.
37
+ #: () -> untyped
38
+ def read_checked
39
+ require "json_schemer"
40
+ require "pathname"
41
+ data = read
42
+ errors = JSONSchemer.schema(Pathname.new(SCHEMA)).validate(data).map { |error| error.fetch("error") }
43
+ return data if errors.empty?
44
+
45
+ raise Error.new("invalid_command_steps", errors.join("; "),
46
+ "Repair the file against schemas/command-steps.schema.json.", path)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,71 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../prose_list"
5
+
6
+ module Principles
7
+ # Every file and process this tool reads or writes. data/CLAUDE.md owns the boundary.
8
+ module Data
9
+ # The type one schema asks for, from its type keyword: whether a value has it, and how a refusal names it,
10
+ # with the keys an object takes, so an agent that sent another type learns the shape from the refusal (#737).
11
+ class ExpectedType
12
+ TYPES = {
13
+ "object" => Hash, "array" => Array, "string" => String, "integer" => Integer, "boolean" => [true, false],
14
+ "null" => NilClass
15
+ }.freeze #: Hash[String, untyped]
16
+ # This tool's own schema keyword: what a refusal of the value's type adds, such as when to point inside a
17
+ # file, which the keys alone do not say (#758), as {"keys", "says"}. The refusal says it after the required
18
+ # keys, in place of the optional keys it names, so it names each key once (#765).
19
+ ADVICE = "x-type-refusal-advice"
20
+
21
+ #: (Hash[String, untyped]) -> void
22
+ def initialize(schema)
23
+ advice = schema.fetch(ADVICE, {}) #: Hash[String, untyped]
24
+ @names = Array(schema["type"]) #: Array[String]
25
+ @required = schema.fetch("required", []) #: Array[String]
26
+ @optional = schema.fetch("properties", {}).keys - @required - advice.fetch("keys", []) #: Array[String]
27
+ @advice = advice["says"] #: String?
28
+ freeze
29
+ end
30
+
31
+ # Whether value has the type; a schema that names no type allows any.
32
+ #: (untyped) -> bool
33
+ def matches?(value)
34
+ @names.empty? || @names.any? do |name|
35
+ kind = TYPES.fetch(name)
36
+ kind.is_a?(Array) ? kind.include?(value) : value.is_a?(kind)
37
+ end
38
+ end
39
+
40
+ # The type as a refusal says it, such as "an object or null, with path and optionally heading", with any
41
+ # advice the schema gives for it.
42
+ #: () -> String
43
+ def to_s
44
+ @names.map { |name| %w[object array integer].include?(name) ? "an #{name}" : "a #{name}" }
45
+ .join(" or ").sub("a null", "null") + keys
46
+ end
47
+
48
+ private
49
+
50
+ # The keys an object takes: its required ones, then the advice, then the others it may take.
51
+ #: () -> String
52
+ def keys
53
+ return any_of if @required.empty?
54
+
55
+ text = [", with #{ProseList.new(@required)}", *@advice].join(", ")
56
+ return text if @optional.empty?
57
+
58
+ "#{text}#{"," if @advice} and optionally #{optional}"
59
+ end
60
+
61
+ # The keys an object that requires none may take, then the advice; nothing when there are neither.
62
+ #: () -> String
63
+ def any_of
64
+ [*("with any of #{optional}" unless @optional.empty?), *@advice].sum("") { |part| ", #{part}" }
65
+ end
66
+
67
+ #: () -> ProseList
68
+ def optional = ProseList.new(@optional, conjunction: "or")
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,54 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "open3"
5
+
6
+ module Principles
7
+ # Every file and process this tool reads or writes. data/CLAUDE.md owns the boundary.
8
+ module Data
9
+ # What git ignores among a project's candidate files: the files, by every ignore file git reads, a global one
10
+ # included, so build output and personal settings are named rather than read (#397), and each folder above
11
+ # them that git ignores, so a build folder's many copies can be counted once (#406).
12
+ class IgnoredByGit
13
+ #: (String, Array[String]) -> void
14
+ def initialize(root, candidates)
15
+ @root = root
16
+ @candidates = candidates
17
+ freeze
18
+ end
19
+
20
+ # The ignored candidates and the ignored folders above them, or nil when git gave no answer to either
21
+ # question: outside a repository, and when git refuses one, as one of doubtful ownership. nil means not
22
+ # checked, not that nothing is ignored, so it must not become empty lists; and both or neither, since a
23
+ # folder list git did not give would make every file look ignored on its own.
24
+ #: () -> Hash[String, Array[String]]?
25
+ def facts
26
+ paths = asked(@candidates) or return nil
27
+ folders = paths.empty? ? [] : asked(ancestor_folders(paths))
28
+ { "ignored" => paths.sort, "ignored_ancestor_folders" => folders.sort } if folders
29
+ end
30
+
31
+ private
32
+
33
+ # Every folder above each path, as "a/" and "a/b/", once each: the trailing "/" lets git match a folder
34
+ # pattern such as build/.
35
+ #: (Array[String]) -> Array[String]
36
+ def ancestor_folders(paths)
37
+ paths.flat_map do |path|
38
+ folders = path.split("/").first(path.count("/"))
39
+ (1..folders.length).map { |depth| "#{folders.first(depth).join("/")}/" }
40
+ end.uniq
41
+ end
42
+
43
+ # The paths git ignores, or nil when it gave no answer.
44
+ #: (Array[String]) -> Array[String]?
45
+ def asked(paths)
46
+ output, _, status = Open3.capture3("git", "-C", @root, "check-ignore", "-z", "--stdin",
47
+ stdin_data: paths.map { |path| "#{path}\0" }.join)
48
+ output.split("\0") if status.exitstatus.to_i <= 1
49
+ rescue SystemCallError
50
+ nil
51
+ end
52
+ end
53
+ end
54
+ end