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.
- checksums.yaml +7 -0
- data/exe/principles +10 -0
- data/lib/principles/after_commit_hook_options.rb +44 -0
- data/lib/principles/cli.rb +149 -0
- data/lib/principles/command_line.rb +188 -0
- data/lib/principles/command_routes.rb +60 -0
- data/lib/principles/controllers/acknowledgement.rb +106 -0
- data/lib/principles/controllers/answer.rb +172 -0
- data/lib/principles/controllers/audit.rb +203 -0
- data/lib/principles/controllers/audit_completion.rb +113 -0
- data/lib/principles/controllers/command.rb +201 -0
- data/lib/principles/controllers/correction.rb +99 -0
- data/lib/principles/controllers/help.rb +107 -0
- data/lib/principles/controllers/inventory.rb +181 -0
- data/lib/principles/controllers/mapping.rb +56 -0
- data/lib/principles/controllers/member_entries.rb +149 -0
- data/lib/principles/controllers/members_a_commit_changed.rb +43 -0
- data/lib/principles/controllers/members_to_assess.rb +160 -0
- data/lib/principles/controllers/meta_principle_setup.rb +92 -0
- data/lib/principles/controllers/repoint.rb +68 -0
- data/lib/principles/controllers/running_tool.rb +41 -0
- data/lib/principles/data/audit_files.rb +96 -0
- data/lib/principles/data/bounded_file.rb +45 -0
- data/lib/principles/data/candidate_files_found.rb +105 -0
- data/lib/principles/data/command-steps.json +305 -0
- data/lib/principles/data/command_steps_file.rb +50 -0
- data/lib/principles/data/expected_type.rb +71 -0
- data/lib/principles/data/ignored_by_git.rb +54 -0
- data/lib/principles/data/json_text.rb +57 -0
- data/lib/principles/data/known_fields.rb +42 -0
- data/lib/principles/data/linked_path.rb +102 -0
- data/lib/principles/data/map_files.rb +104 -0
- data/lib/principles/data/principle_id.rb +32 -0
- data/lib/principles/data/principles_subfolder.rb +93 -0
- data/lib/principles/data/project_files.rb +109 -0
- data/lib/principles/data/repository.rb +165 -0
- data/lib/principles/data/running_version.rb +44 -0
- data/lib/principles/data/schema_check.rb +139 -0
- data/lib/principles/data/schemas/acknowledgement-answer.schema.json +25 -0
- data/lib/principles/data/schemas/answer-guide.schema.json +99 -0
- data/lib/principles/data/schemas/assessment-answer.schema.json +332 -0
- data/lib/principles/data/schemas/audit-record.schema.json +277 -0
- data/lib/principles/data/schemas/command-steps.schema.json +83 -0
- data/lib/principles/data/schemas/correction-answer-guide.schema.json +68 -0
- data/lib/principles/data/schemas/correction-answer.schema.json +306 -0
- data/lib/principles/data/schemas/member-map.schema.json +39 -0
- data/lib/principles/data/schemas/repointing-answer.schema.json +44 -0
- data/lib/principles/error.rb +102 -0
- data/lib/principles/invocation_options.rb +77 -0
- data/lib/principles/models/accepted_loading.rb +36 -0
- data/lib/principles/models/acknowledgement.rb +203 -0
- data/lib/principles/models/activation.rb +64 -0
- data/lib/principles/models/agent_loads.rb +64 -0
- data/lib/principles/models/answer_fields.rb +67 -0
- data/lib/principles/models/answer_place.rb +53 -0
- data/lib/principles/models/answer_text.rb +37 -0
- data/lib/principles/models/answered_time.rb +65 -0
- data/lib/principles/models/assessment_answer.rb +131 -0
- data/lib/principles/models/assessment_basis.rb +47 -0
- data/lib/principles/models/assessment_report.rb +62 -0
- data/lib/principles/models/audit.rb +200 -0
- data/lib/principles/models/audit_record.rb +249 -0
- data/lib/principles/models/audit_records.rb +103 -0
- data/lib/principles/models/audit_start.rb +96 -0
- data/lib/principles/models/audited_member_lines.rb +45 -0
- data/lib/principles/models/candidate_files.rb +66 -0
- data/lib/principles/models/candidate_inventory.rb +67 -0
- data/lib/principles/models/candidate_listing.rb +32 -0
- data/lib/principles/models/carried_assessments.rb +76 -0
- data/lib/principles/models/check_since_last_audit.rb +61 -0
- data/lib/principles/models/cited_files.rb +39 -0
- data/lib/principles/models/command_steps.rb +270 -0
- data/lib/principles/models/commits_brought_in.rb +27 -0
- data/lib/principles/models/commits_since_base.rb +32 -0
- data/lib/principles/models/correction_answer.rb +66 -0
- data/lib/principles/models/correction_basis.rb +70 -0
- data/lib/principles/models/dated_line.rb +29 -0
- data/lib/principles/models/decided_prior_findings.rb +106 -0
- data/lib/principles/models/declared_map_changes.rb +156 -0
- data/lib/principles/models/disposition.rb +233 -0
- data/lib/principles/models/entry_file.rb +65 -0
- data/lib/principles/models/entry_files.rb +46 -0
- data/lib/principles/models/evidence_item.rb +48 -0
- data/lib/principles/models/evidence_lines_since.rb +57 -0
- data/lib/principles/models/evidence_replacements.rb +99 -0
- data/lib/principles/models/finding_write_ups.rb +108 -0
- data/lib/principles/models/findings_left_open.rb +34 -0
- data/lib/principles/models/first_raised_claim.rb +128 -0
- data/lib/principles/models/frontmatter_fields.rb +128 -0
- data/lib/principles/models/hook_commands.rb +100 -0
- data/lib/principles/models/ignored_candidates.rb +46 -0
- data/lib/principles/models/json_digest.rb +35 -0
- data/lib/principles/models/json_path_query.rb +131 -0
- data/lib/principles/models/json_path_selectors.rb +64 -0
- data/lib/principles/models/json_path_string.rb +76 -0
- data/lib/principles/models/kept_prior_finding.rb +70 -0
- data/lib/principles/models/last_completed_revision.rb +70 -0
- data/lib/principles/models/line_span.rb +49 -0
- data/lib/principles/models/loaded_files.rb +60 -0
- data/lib/principles/models/loading_decision.rb +45 -0
- data/lib/principles/models/loading_facts.rb +45 -0
- data/lib/principles/models/loads_to_check.rb +93 -0
- data/lib/principles/models/locate_refusal.rb +23 -0
- data/lib/principles/models/located_text.rb +43 -0
- data/lib/principles/models/locator.rb +52 -0
- data/lib/principles/models/map_members.rb +171 -0
- data/lib/principles/models/markdown_file.rb +92 -0
- data/lib/principles/models/member.rb +171 -0
- data/lib/principles/models/member_assessments.rb +144 -0
- data/lib/principles/models/member_elsewhere.rb +74 -0
- data/lib/principles/models/member_file.rb +156 -0
- data/lib/principles/models/member_grounds.rb +32 -0
- data/lib/principles/models/member_history.rb +68 -0
- data/lib/principles/models/member_lines.rb +95 -0
- data/lib/principles/models/member_loads.rb +197 -0
- data/lib/principles/models/member_map.rb +219 -0
- data/lib/principles/models/member_map_history.rb +143 -0
- data/lib/principles/models/member_maps.rb +185 -0
- data/lib/principles/models/member_moves.rb +95 -0
- data/lib/principles/models/member_problems.rb +253 -0
- data/lib/principles/models/member_sighting.rb +176 -0
- data/lib/principles/models/member_snapshots.rb +36 -0
- data/lib/principles/models/member_state.rb +35 -0
- data/lib/principles/models/members_elsewhere.rb +46 -0
- data/lib/principles/models/members_sharing_lines.rb +116 -0
- data/lib/principles/models/meta_principle.rb +51 -0
- data/lib/principles/models/missing_files.rb +85 -0
- data/lib/principles/models/misspelt_member_id.rb +45 -0
- data/lib/principles/models/open_finding.rb +221 -0
- data/lib/principles/models/open_findings_elsewhere.rb +45 -0
- data/lib/principles/models/other_principles.rb +47 -0
- data/lib/principles/models/placement.rb +42 -0
- data/lib/principles/models/plain_text_file.rb +32 -0
- data/lib/principles/models/principles_folder_move.rb +59 -0
- data/lib/principles/models/principles_package.rb +53 -0
- data/lib/principles/models/prior_finding_decision.rb +162 -0
- data/lib/principles/models/prior_finding_outcomes.rb +60 -0
- data/lib/principles/models/prior_findings.rb +109 -0
- data/lib/principles/models/problem_report.rb +110 -0
- data/lib/principles/models/project.rb +214 -0
- data/lib/principles/models/project_history.rb +203 -0
- data/lib/principles/models/record_judged_against.rb +42 -0
- data/lib/principles/models/recorded_entry.rb +93 -0
- data/lib/principles/models/recorded_finding.rb +97 -0
- data/lib/principles/models/recorded_findings.rb +58 -0
- data/lib/principles/models/recorded_members.rb +149 -0
- data/lib/principles/models/repoint.rb +112 -0
- data/lib/principles/models/reported_check.rb +130 -0
- data/lib/principles/models/request_id.rb +70 -0
- data/lib/principles/models/runs_from.rb +36 -0
- data/lib/principles/models/skill_loads.rb +123 -0
- data/lib/principles/models/still_holds_restatement.rb +87 -0
- data/lib/principles/models/text_size.rb +26 -0
- data/lib/principles/models/uncommitted_outside_the_map.rb +30 -0
- data/lib/principles/models/waits_on.rb +58 -0
- data/lib/principles/models/write_up_opening.rb +93 -0
- data/lib/principles/models/write_up_pointer.rb +50 -0
- data/lib/principles/models/write_up_repoint.rb +39 -0
- data/lib/principles/prose_list.rb +23 -0
- data/lib/principles/supported_ruby.rb +93 -0
- data/lib/principles/version.rb +11 -0
- data/lib/principles/views/acknowledged_since_last_audit.rb +34 -0
- data/lib/principles/views/acknowledgement_result.rb +44 -0
- data/lib/principles/views/activation_in_hand.rb +47 -0
- data/lib/principles/views/assessment_request.rb +131 -0
- data/lib/principles/views/audit_changes.rb +79 -0
- data/lib/principles/views/audit_commit.rb +55 -0
- data/lib/principles/views/audit_result.rb +147 -0
- data/lib/principles/views/candidate_files_result.rb +95 -0
- data/lib/principles/views/check_result_template.rb +75 -0
- data/lib/principles/views/diff_since_last_audit.rb +29 -0
- data/lib/principles/views/findings_result.rb +45 -0
- data/lib/principles/views/help.rb +119 -0
- data/lib/principles/views/invocation.rb +66 -0
- data/lib/principles/views/landed_since_base.rb +27 -0
- data/lib/principles/views/lines_shared_with_member.rb +111 -0
- data/lib/principles/views/loads_to_check_result.rb +45 -0
- data/lib/principles/views/map_result.rb +169 -0
- data/lib/principles/views/member_change_since_baseline.rb +58 -0
- data/lib/principles/views/member_repairs.rb +82 -0
- data/lib/principles/views/members_a_commit_changed.rb +66 -0
- data/lib/principles/views/members_with_no_baseline.rb +56 -0
- data/lib/principles/views/meta_principle_passage.rb +23 -0
- data/lib/principles/views/meta_principle_result.rb +170 -0
- data/lib/principles/views/open_findings_elsewhere_result.rb +32 -0
- data/lib/principles/views/open_findings_line.rb +125 -0
- data/lib/principles/views/other_principles_unjudged_result.rb +32 -0
- data/lib/principles/views/outside_the_map_result.rb +31 -0
- data/lib/principles/views/package_since_last_audit_result.rb +34 -0
- data/lib/principles/views/principle_read_result.rb +86 -0
- data/lib/principles/views/principle_with_changed_members.rb +26 -0
- data/lib/principles/views/principles_with_no_completed_audit.rb +30 -0
- data/lib/principles/views/printed_principle.rb +45 -0
- data/lib/principles/views/prior_finding_decisions_result.rb +35 -0
- data/lib/principles/views/prior_findings_result.rb +80 -0
- data/lib/principles/views/problem_lines.rb +84 -0
- data/lib/principles/views/problem_result.rb +107 -0
- data/lib/principles/views/prompts/acknowledge-a-change/say-whether-the-change-keeps-the-meaning.md +12 -0
- data/lib/principles/views/prompts/answer-any-judgement-request/what-a-self-audit-cannot-show.md +6 -0
- data/lib/principles/views/prompts/assess-every-member/answer-guide.json +63 -0
- data/lib/principles/views/prompts/assess-every-member/assess-each-member-and-the-set.md +12 -0
- data/lib/principles/views/prompts/assess-every-member/check-roles-other-principles-give-these-lines.md +7 -0
- data/lib/principles/views/prompts/assess-every-member/decide-each-member-loading.md +17 -0
- data/lib/principles/views/prompts/assess-every-member/decide-each-prior-finding.md +38 -0
- data/lib/principles/views/prompts/assess-every-member/give-problem-evidence-at-still-holds.md +2 -0
- data/lib/principles/views/prompts/assess-every-member/give-the-evidence-a-prior-finding-lacks.md +7 -0
- data/lib/principles/views/prompts/assess-every-member/have-a-changed-check-reviewed-before-running-it.md +4 -0
- data/lib/principles/views/prompts/assess-every-member/have-a-new-check-reviewed-before-running-it.md +3 -0
- data/lib/principles/views/prompts/assess-every-member/have-hidden-characters-in-a-check-explained.md +4 -0
- data/lib/principles/views/prompts/assess-every-member/judge-acknowledged-edits-together.md +6 -0
- data/lib/principles/views/prompts/assess-every-member/judge-the-keeping-against-the-other-principles.md +7 -0
- data/lib/principles/views/prompts/assess-every-member/judge-the-keeping-under-the-release-since-the-last-audit.md +10 -0
- data/lib/principles/views/prompts/assess-every-member/judge-the-set-with-carried-assessments.md +10 -0
- data/lib/principles/views/prompts/assess-every-member/keep-the-last-review-basis-unless-you-give-a-new-one.md +1 -0
- data/lib/principles/views/prompts/assess-every-member/name-another-principles-open-finding-rather-than-raise-it.md +9 -0
- data/lib/principles/views/prompts/assess-every-member/read-whether-each-prior-finding-s-evidence-changed.md +12 -0
- data/lib/principles/views/prompts/assess-every-member/run-a-named-check-at-every-full-audit.md +2 -0
- data/lib/principles/views/prompts/assess-every-member/run-the-check-then-report-it.md +2 -0
- data/lib/principles/views/prompts/assess-every-member/run-the-reviewed-check-and-say-what-its-review-rested-on.md +6 -0
- data/lib/principles/views/prompts/assess-every-member/uncommitted-files-outside-the-map.md +4 -0
- data/lib/principles/views/prompts/assess-every-member/why-each-member-is-judged-again.md +24 -0
- data/lib/principles/views/prompts/dispose-of-each-finding/answer-guide.json +34 -0
- data/lib/principles/views/prompts/dispose-of-each-finding/fix-or-record-each-finding.md +24 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/ask-the-owner-before-mapping.md +1 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/choose-one-principle.md +5 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-ignored-folders.md +2 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-ignored.md +3 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-possibly-test-data.md +2 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-untracked.md +2 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/what-each-command-writes.md +4 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/where-the-inventory-looks.md +10 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/add-member.md +45 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/change-activation.md +21 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/change-description.md +2 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/change-member-check.md +17 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/change-member-role.md +9 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/create-principle.md +6 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/every-command-and-how-they-chain.md +28 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/list-candidate-files.md +17 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/list-member-problems.md +73 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/list-open-findings.md +71 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/list-principles.md +7 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/move-member.md +11 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/remove-member-check.md +10 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/remove-member.md +6 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/show-answer-check.md +16 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/show-audit.md +20 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/show-meta-principle-passage.md +52 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/show-principle.md +86 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/start-audit.md +68 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/submit-answer.md +56 -0
- data/lib/principles/views/prompts/repoint-write-ups/point-each-moved-write-up-at-its-new-place.md +10 -0
- data/lib/principles/views/prompts/report-after-a-commit/it-refused-after-the-commits-touched-the-maps.md +2 -0
- data/lib/principles/views/prompts/report-after-a-commit/others-are-still-listed.md +1 -0
- data/lib/principles/views/prompts/report-after-a-commit/the-commits-changed-these-members.md +2 -0
- data/lib/principles/views/prompts/report-after-a-commit/this-command-shows-each-one.md +1 -0
- data/lib/principles/views/prompts/report-the-completed-audit/commit-only-what-the-audit-changed.md +2 -0
- data/lib/principles/views/prompts/report-the-completed-audit/replace-the-default-commit-message.md +4 -0
- data/lib/principles/views/prompts/report-the-completed-audit/what-completion-means.md +2 -0
- data/lib/principles/views/prompts/set-principles-up/entry-file-passage.md +3 -0
- data/lib/principles/views/prompts/set-principles-up/name-the-step-each-member-lacks.md +15 -0
- data/lib/principles/views/prompts/set-principles-up/place-the-passage-with-the-user.md +31 -0
- data/lib/principles/views/prompts/set-principles-up/set-up-first.md +2 -0
- data/lib/principles/views/prompts/set-principles-up/what-the-passage-presumes.md +4 -0
- data/lib/principles/views/prompts/start-a-session/act-on-the-open-findings.md +5 -0
- data/lib/principles/views/prompts/start-a-session/it-returns-after-each-clear-or-compaction-too.md +1 -0
- data/lib/principles/views/prompts/start-a-session/it-returns-at-each-session-start.md +1 -0
- data/lib/principles/views/prompts/start-a-session/raise-them-at-the-next-pause.md +1 -0
- data/lib/principles/views/prompts/start-a-session/raise-them-before-the-first-task.md +1 -0
- data/lib/principles/views/prompts.rb +71 -0
- data/lib/principles/views/repoint_result.rb +80 -0
- data/lib/principles/views/request_part.rb +26 -0
- data/lib/principles/views/request_parts.rb +26 -0
- data/lib/principles/views/shell_word.rb +29 -0
- data/lib/principles/views/visible_text.rb +26 -0
- data/lib/principles/views/workflow_diagram.rb +77 -0
- data/lib/principles.rb +5 -0
- 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
|
+
}
|