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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 51ce6acd3b9d638e2948da0d6e41e0eb4b7656dff90a976bf976f4232020c431
|
|
4
|
+
data.tar.gz: 803b232afd27b6050db0197f2d77accc7afe852545d17000f2c768a4138aa402
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bb6296b076b2b9ce37d5ad707d1b8312a95edd7e0341da94ee9f7da1587471536db19d1fb3afe072de8f297100fe02da308a7f553b13e99608509413eadcdd0a
|
|
7
|
+
data.tar.gz: 89b4e1074966cc96af2769263ac1ed9210dd0175c3d0c4490ff681dbfc41da09157a20189e1380e06ae61c5fea7cc261b479c1d1bd1783265163ebd167baafa9
|
data/exe/principles
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# typed: strict
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# A Ruby this tool cannot run on is refused, or handed to one it can, before lib/ loads.
|
|
6
|
+
require_relative "../lib/principles/supported_ruby"
|
|
7
|
+
Principles::SupportedRuby.new(RUBY_VERSION, ENV.to_h).run_here_or_on_another(__FILE__, ARGV)
|
|
8
|
+
|
|
9
|
+
require_relative "../lib/principles"
|
|
10
|
+
exit Principles::CLI.run(ARGV)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "invocation_options"
|
|
5
|
+
|
|
6
|
+
module Principles
|
|
7
|
+
# The options the plugin's after-commit hook gives list-member-problems, --commits-after and --hook-event (D73),
|
|
8
|
+
# and what is wrong with them as given, which CommandLine refuses as a usage error.
|
|
9
|
+
class AfterCommitHookOptions
|
|
10
|
+
# The events Claude Code runs the after-commit hook on, which --hook-event echoes back to it.
|
|
11
|
+
EVENTS = %w[PostToolUse PostToolUseFailure].freeze #: Array[String]
|
|
12
|
+
# A commit's full sha, SHA-1 or SHA-256, which the hook resolves HEAD@{1} to before passing it.
|
|
13
|
+
FULL_SHA = /\A\h{40}(?:\h{24})?\z/ #: Regexp
|
|
14
|
+
|
|
15
|
+
#: (InvocationOptions) -> void
|
|
16
|
+
def initialize(options)
|
|
17
|
+
@after = options.value("commits_after") #: String?
|
|
18
|
+
@event = options.value("hook_event") #: String?
|
|
19
|
+
@short = !options.value("short").nil? #: bool
|
|
20
|
+
freeze
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# What is wrong with the options as given, or nil when they fit or neither is given.
|
|
24
|
+
#: () -> String?
|
|
25
|
+
def fault
|
|
26
|
+
after = @after
|
|
27
|
+
event = @event
|
|
28
|
+
return if after.nil? && event.nil?
|
|
29
|
+
return "--commits-after and --hook-event are given together, for the after-commit hook" unless after && event
|
|
30
|
+
|
|
31
|
+
value_fault(after, event) || ("--commits-after prints the hook's JSON, not the --short lines" if @short)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# What is wrong with either value, or nil when both are ones the hook passes.
|
|
37
|
+
#: (String, String) -> String?
|
|
38
|
+
def value_fault(after, event)
|
|
39
|
+
return "--hook-event takes PostToolUse or PostToolUseFailure, not #{event}" unless EVENTS.include?(event)
|
|
40
|
+
|
|
41
|
+
"--commits-after takes a commit's full sha, not #{after}" unless FULL_SHA.match?(after)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "data/running_version"
|
|
6
|
+
require_relative "models/project"
|
|
7
|
+
require_relative "command_routes"
|
|
8
|
+
require_relative "controllers/help"
|
|
9
|
+
require_relative "command_line"
|
|
10
|
+
|
|
11
|
+
module Principles
|
|
12
|
+
# Parses one invocation, runs its step and prints the JSON result. Every refusal is one JSON object
|
|
13
|
+
# naming its code and repair, because an agent's shell may merge stdout and stderr.
|
|
14
|
+
class CLI
|
|
15
|
+
# The repair for a shape error raised from this tool's own code. Such an error cannot say which file was at
|
|
16
|
+
# fault, so the repair names every file a command reads, and blames none (D29).
|
|
17
|
+
SHAPE_REPAIR = "A file this command read is not in the shape this tool writes: the map in " \
|
|
18
|
+
".principles/maps/ID.json, its audit record in .principles/audits/ID.json, or the answer given " \
|
|
19
|
+
"with --file. Compare it with git, or with the request's response_template."
|
|
20
|
+
|
|
21
|
+
# Runs one invocation and returns its exit status: 0 for a result, 1 for a refusal, 2 for a usage error, and 3
|
|
22
|
+
# when list-member-problems --short finds a member an audit assessed changed, or 4 when it finds only edits to
|
|
23
|
+
# members no audit has assessed (D44). executable is the command every printed command
|
|
24
|
+
# names: the entry point says how it is run.
|
|
25
|
+
#: (Array[String], ?IO, ?IO, ?IO, ?executable: String) -> Integer
|
|
26
|
+
def self.run(argv, stdout = $stdout, stderr = $stderr, stdin = $stdin,
|
|
27
|
+
executable: Controllers::Command::INVOCATION)
|
|
28
|
+
new(stdout, stderr, stdin, executable: executable).run(argv)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#: (IO, IO, IO, executable: String) -> void
|
|
32
|
+
def initialize(stdout, stderr, stdin, executable:)
|
|
33
|
+
@stdout = stdout
|
|
34
|
+
@stderr = stderr
|
|
35
|
+
@stdin = stdin
|
|
36
|
+
@executable = executable
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#: (Array[String]) -> Integer
|
|
40
|
+
def run(argv)
|
|
41
|
+
dispatch(CommandLine.new(argv, positional: CommandRoutes::POSITIONAL, executable: @executable))
|
|
42
|
+
rescue Error => e
|
|
43
|
+
refused(e.payload, 1)
|
|
44
|
+
rescue OptionParser::ParseError, KeyError, TypeError, NoMethodError, ArgumentError, SystemCallError => e
|
|
45
|
+
refused(refusal(e), e.is_a?(OptionParser::ParseError) ? 2 : 1)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
#: (CommandLine) -> Integer
|
|
51
|
+
def dispatch(line)
|
|
52
|
+
return printed(running_version) if line.option("version")
|
|
53
|
+
|
|
54
|
+
command = line.command or return printed(help.overview)
|
|
55
|
+
return printed(help_for(command)) if line.option("help")
|
|
56
|
+
|
|
57
|
+
line.check
|
|
58
|
+
printed_result(result(command, line))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The command's result, from the controller for its group.
|
|
62
|
+
#: (String, CommandLine) -> untyped
|
|
63
|
+
def result(command, line)
|
|
64
|
+
options = line.command_options
|
|
65
|
+
# One byte past the limit is enough for the answer's own check to refuse it as too large.
|
|
66
|
+
options["stdin"] = @stdin.read(Data::BoundedFile::LIMIT + 1).to_s if options["file"] == "-"
|
|
67
|
+
project = Models::Project.at(options.fetch("project"))
|
|
68
|
+
CommandRoutes::CONTROLLERS.fetch(command).new(project, tool: running_tool)
|
|
69
|
+
.call(command, line.id, options)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The JSON object an agent reads for a failure that is not one of this tool's refusals.
|
|
73
|
+
#: (StandardError) -> Hash[untyped, untyped]
|
|
74
|
+
def refusal(error)
|
|
75
|
+
case error
|
|
76
|
+
when OptionParser::ParseError
|
|
77
|
+
failure(error, status: "usage_error", code: "invalid_invocation", repair: usage_repair(error))
|
|
78
|
+
when SystemCallError
|
|
79
|
+
failure(error, status: "refused", code: "filesystem_error",
|
|
80
|
+
repair: "Check the project path and file permissions, then retry.")
|
|
81
|
+
else
|
|
82
|
+
failure(error, status: "refused", code: "invalid_shape", repair: SHAPE_REPAIR)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The repair a refused command line carries for the command it names, or the advice to see every command.
|
|
87
|
+
#: (OptionParser::ParseError) -> String
|
|
88
|
+
def usage_repair(error)
|
|
89
|
+
error.is_a?(CommandLine::UsageRefusal) ? error.repair : "To see every command: #{@executable} --help"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Prints the refusal for the agent and returns the exit status.
|
|
93
|
+
#: (Hash[untyped, untyped], Integer) -> Integer
|
|
94
|
+
def refused(payload, status)
|
|
95
|
+
@stderr.puts(visible(JSON.generate(payload)))
|
|
96
|
+
status
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#: (StandardError, status: String, code: String, repair: String) -> Hash[Symbol, String]
|
|
100
|
+
def failure(error, status:, code:, repair:)
|
|
101
|
+
{ status: status, code: code, message: error.message, repair: repair }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# json with every invisible formatting character and line or paragraph separator escaped, as JSON allows.
|
|
105
|
+
# Text from project files can hold a right-to-left override or a zero-width space, which makes a printed
|
|
106
|
+
# check or id look like something it is not (#227).
|
|
107
|
+
#: (String) -> String
|
|
108
|
+
def visible(json)
|
|
109
|
+
json.gsub(/[\p{Cf}\p{Zl}\p{Zp}]/) do |character|
|
|
110
|
+
character.encode("UTF-16BE").unpack("n*").map { |unit| format("\\u%04x", unit) }.join
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# A result prints as JSON, or as lines with their own exit status, as list-member-problems --short does.
|
|
115
|
+
#: (untyped) -> Integer
|
|
116
|
+
def printed_result(result)
|
|
117
|
+
return printed(visible(JSON.pretty_generate(result))) unless result.is_a?(Views::Lines)
|
|
118
|
+
|
|
119
|
+
@stdout.print(result.stdout)
|
|
120
|
+
@stderr.print(result.stderr)
|
|
121
|
+
result.status
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
#: (String) -> Integer
|
|
125
|
+
def printed(text)
|
|
126
|
+
@stdout.puts(text)
|
|
127
|
+
0
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
#: () -> Controllers::Help
|
|
131
|
+
def help
|
|
132
|
+
Controllers::Help.new(tool: running_tool)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# This run of the tool: the executable it was invoked as, and its version, read only when a command needs it.
|
|
136
|
+
#: () -> Controllers::RunningTool
|
|
137
|
+
def running_tool = Controllers::RunningTool.new(executable: @executable, version: -> { running_version })
|
|
138
|
+
|
|
139
|
+
# The version of this tool that is running, as a build stamped it or a checkout's commit gives it.
|
|
140
|
+
#: () -> String
|
|
141
|
+
def running_version = Data::RunningVersion.new.to_s
|
|
142
|
+
|
|
143
|
+
# A command's --help, from the steps command-steps.json lists for it.
|
|
144
|
+
#: (String) -> String
|
|
145
|
+
def help_for(command)
|
|
146
|
+
help.command(command) || raise(OptionParser::InvalidArgument, "#{command} has no help")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "optparse"
|
|
5
|
+
require_relative "invocation_options"
|
|
6
|
+
require_relative "after_commit_hook_options"
|
|
7
|
+
require_relative "models/command_steps"
|
|
8
|
+
|
|
9
|
+
module Principles
|
|
10
|
+
# One invocation's words: the command, its positional arguments and its options, refused with an
|
|
11
|
+
# OptionParser error when they do not fit what the command takes. CLI decides what to run from it.
|
|
12
|
+
class CommandLine
|
|
13
|
+
# The sources Claude Code's SessionStart hook names, which list-open-findings --short --session-start takes (#717).
|
|
14
|
+
SESSION_STARTS = %w[startup clear compact].freeze #: Array[String]
|
|
15
|
+
|
|
16
|
+
# argv is parsed at once; positional names each command's positional arguments, in order; executable is the
|
|
17
|
+
# invocation a refusal tells the agent to run --help with (#562).
|
|
18
|
+
#: (Array[String], positional: Hash[String, Array[String]], executable: String) -> void
|
|
19
|
+
def initialize(argv, positional:, executable:)
|
|
20
|
+
@positional = positional
|
|
21
|
+
@executable = executable
|
|
22
|
+
@options = InvocationOptions.new #: InvocationOptions
|
|
23
|
+
@arguments = argv.dup #: Array[String]
|
|
24
|
+
# The words as typed: parsing takes the options out of @arguments, and a value option is checked against
|
|
25
|
+
# the word that followed it (#750).
|
|
26
|
+
@words = argv.dup.freeze #: Array[String]
|
|
27
|
+
parse(argv)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A usage refusal that carries the repair for the command it names, which CLI prints in place of its
|
|
31
|
+
# general advice (#576). It stays an OptionParser::ParseError, so it exits 2 like every usage refusal.
|
|
32
|
+
class UsageRefusal < OptionParser::ParseError
|
|
33
|
+
#: String
|
|
34
|
+
attr_reader :repair
|
|
35
|
+
|
|
36
|
+
#: (String, String) -> void
|
|
37
|
+
def initialize(fault, repair)
|
|
38
|
+
super()
|
|
39
|
+
@fault = fault
|
|
40
|
+
@repair = repair
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#: () -> String
|
|
44
|
+
def message = @fault
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# A parsed option's value, or true for a flag that was given.
|
|
48
|
+
#: (String) -> untyped
|
|
49
|
+
def option(name)
|
|
50
|
+
@options.value(name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The command named first; nil when none is, and refused when this tool has no such command.
|
|
54
|
+
#: () -> String?
|
|
55
|
+
def command
|
|
56
|
+
name = @arguments.first or return nil
|
|
57
|
+
return name if @positional.key?(name)
|
|
58
|
+
|
|
59
|
+
raise OptionParser::InvalidArgument, "unknown command #{name}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Refuses the wrong number of arguments, an option the command does not take or given an option as its value,
|
|
63
|
+
# and an answer command without --file.
|
|
64
|
+
#: () -> void
|
|
65
|
+
def check
|
|
66
|
+
name = command.to_s
|
|
67
|
+
check_arguments(name)
|
|
68
|
+
check_options(name)
|
|
69
|
+
check_values_are_not_options(name)
|
|
70
|
+
check_session_start(name)
|
|
71
|
+
check_hook_report(name)
|
|
72
|
+
check_audit_kind(name)
|
|
73
|
+
return unless %w[submit-answer show-answer-check].include?(name) && !@options.value("file")
|
|
74
|
+
|
|
75
|
+
raise OptionParser::MissingArgument, "#{name} needs --file FILE, or --file - for stdin"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The principle id, for a command whose first argument is one.
|
|
79
|
+
#: () -> String?
|
|
80
|
+
def id
|
|
81
|
+
%w[id id?].include?(names.first) ? values.first : nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The options, with every positional argument but the id under its name.
|
|
85
|
+
#: () -> Hash[String, untyped]
|
|
86
|
+
def command_options
|
|
87
|
+
names.zip(values).each_with_object(@options.to_h) do |(name, value), all|
|
|
88
|
+
all[name] = value unless %w[id id?].include?(name)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
#: () -> Array[String]
|
|
95
|
+
def names
|
|
96
|
+
@positional.fetch(command.to_s)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#: () -> Array[String]
|
|
100
|
+
def values
|
|
101
|
+
@arguments.drop(1)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
#: (String) -> void
|
|
105
|
+
def check_arguments(name)
|
|
106
|
+
return if values.length.between?(names.count { |each| !each.end_with?("?") }, names.length)
|
|
107
|
+
|
|
108
|
+
takes = "#{name} takes #{shown_arguments}".strip.sub(/ takes\z/, " takes no arguments")
|
|
109
|
+
raise OptionParser::InvalidArgument, takes
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The positional arguments as usage shows them; a name ending in ? is optional and shown in brackets.
|
|
113
|
+
#: () -> String
|
|
114
|
+
def shown_arguments
|
|
115
|
+
names.map { |each| each.end_with?("?") ? "[#{each.chomp("?").upcase}]" : each.upcase }.join(" ")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# An option a command does not take is refused, so a mistyped command never runs as something else.
|
|
119
|
+
#: (String) -> void
|
|
120
|
+
def check_options(name)
|
|
121
|
+
taken = Models::CommandSteps.load.command(name)&.options || []
|
|
122
|
+
unknown = (@options.given - taken - ["project"]).map { |option| "--#{option}" }
|
|
123
|
+
return if unknown.empty?
|
|
124
|
+
|
|
125
|
+
raise UsageRefusal.new("#{name} does not take #{unknown.join(", ")}", help_repair(name))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# An option that takes a value takes the next word, whatever it is, so --member --help took --help as the member
|
|
129
|
+
# (#750). Only this tool's own option words are refused there, since a quote may start with dashes; such a word is
|
|
130
|
+
# given as a value after =, as --starting-at=--role is.
|
|
131
|
+
#: (String) -> void
|
|
132
|
+
def check_values_are_not_options(name)
|
|
133
|
+
@words.each_cons(2) do |pair|
|
|
134
|
+
option = pair.fetch(0)
|
|
135
|
+
value = pair.fetch(1)
|
|
136
|
+
next unless InvocationOptions.takes_a_value?(option) && InvocationOptions::WORDS.include?(value)
|
|
137
|
+
|
|
138
|
+
raise UsageRefusal.new("#{option} takes a value, not the option #{value}; to give that word as its value, " \
|
|
139
|
+
"write #{option}=#{value}", help_repair(name))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# --session-start names one of the hook's sources, and words only the --short line.
|
|
144
|
+
#: (String) -> void
|
|
145
|
+
def check_session_start(name)
|
|
146
|
+
start = @options.value("session_start") or return
|
|
147
|
+
unless SESSION_STARTS.include?(start)
|
|
148
|
+
raise UsageRefusal.new("--session-start takes startup, clear or compact, not #{start}", help_repair(name))
|
|
149
|
+
end
|
|
150
|
+
return if @options.value("short")
|
|
151
|
+
|
|
152
|
+
raise UsageRefusal.new("--session-start words only the --short line", help_repair(name))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# --commits-after and --hook-event ask together for the after-commit hook's JSON, which --short's lines are not.
|
|
156
|
+
#: (String) -> void
|
|
157
|
+
def check_hook_report(name)
|
|
158
|
+
fault = AfterCommitHookOptions.new(@options).fault or return
|
|
159
|
+
raise UsageRefusal.new(fault, help_repair(name))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# --member and --write-ups each ask start-audit for a different audit, so only one may be given (#760).
|
|
163
|
+
#: (String) -> void
|
|
164
|
+
def check_audit_kind(name)
|
|
165
|
+
return unless @options.value("member") && @options.value("write_ups")
|
|
166
|
+
|
|
167
|
+
raise UsageRefusal.new("--member and --write-ups each start a different audit; give one", help_repair(name))
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Parses argv's options; an option no command defines fails here, before the command is checked, and is
|
|
171
|
+
# repaired with the help of the command argv names, or left to CLI's general advice when it names none.
|
|
172
|
+
#: (Array[String]) -> void
|
|
173
|
+
def parse(argv)
|
|
174
|
+
@options.parse!(@arguments)
|
|
175
|
+
rescue OptionParser::InvalidOption => e
|
|
176
|
+
name = named_command(argv) or raise
|
|
177
|
+
raise UsageRefusal.new(e.message, help_repair(name))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# The first word that names a command. The command comes first among the positional words, but an option
|
|
181
|
+
# and its value may come before it; a value that is itself a command's name leads to that command's help.
|
|
182
|
+
#: (Array[String]) -> String?
|
|
183
|
+
def named_command(argv) = argv.find { |word| @positional.key?(word) }
|
|
184
|
+
|
|
185
|
+
#: (String) -> String
|
|
186
|
+
def help_repair(name) = "To see what it takes: #{@executable} #{name} --help"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "controllers/inventory"
|
|
5
|
+
require_relative "controllers/mapping"
|
|
6
|
+
require_relative "controllers/meta_principle_setup"
|
|
7
|
+
require_relative "controllers/member_entries"
|
|
8
|
+
require_relative "controllers/audit"
|
|
9
|
+
require_relative "controllers/answer"
|
|
10
|
+
|
|
11
|
+
module Principles
|
|
12
|
+
# Every command this tool runs, each with its route: the positional words it takes, which CommandLine parses, and
|
|
13
|
+
# the controller that runs it, which CLI hands the parsed line to.
|
|
14
|
+
module CommandRoutes
|
|
15
|
+
# One command's route.
|
|
16
|
+
class Route
|
|
17
|
+
#: Array[String]
|
|
18
|
+
attr_reader :positional
|
|
19
|
+
#: singleton(Controllers::Command)
|
|
20
|
+
attr_reader :controller
|
|
21
|
+
|
|
22
|
+
# positional names each positional argument in order, a trailing ? marking one the command may leave out; the
|
|
23
|
+
# rest are named options.
|
|
24
|
+
#: (singleton(Controllers::Command), Array[String]) -> void
|
|
25
|
+
def initialize(controller, positional)
|
|
26
|
+
@controller = controller
|
|
27
|
+
@positional = positional.freeze #: Array[String]
|
|
28
|
+
freeze
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
ROUTES = {
|
|
33
|
+
"list-candidate-files" => Route.new(Controllers::Inventory, []),
|
|
34
|
+
"list-principles" => Route.new(Controllers::Inventory, []),
|
|
35
|
+
"list-member-problems" => Route.new(Controllers::Inventory, %w[id?]),
|
|
36
|
+
"show-principle" => Route.new(Controllers::Inventory, %w[id]),
|
|
37
|
+
"show-audit" => Route.new(Controllers::Audit, %w[id]),
|
|
38
|
+
"list-open-findings" => Route.new(Controllers::Audit, %w[id?]),
|
|
39
|
+
"show-meta-principle-passage" => Route.new(Controllers::MetaPrincipleSetup, []),
|
|
40
|
+
"create-principle" => Route.new(Controllers::Mapping, %w[id]),
|
|
41
|
+
"change-description" => Route.new(Controllers::Mapping, %w[id]),
|
|
42
|
+
"change-activation" => Route.new(Controllers::Mapping, %w[id]),
|
|
43
|
+
"add-member" => Route.new(Controllers::MemberEntries, %w[id path]),
|
|
44
|
+
"move-member" => Route.new(Controllers::MemberEntries, %w[id member]),
|
|
45
|
+
"change-member-role" => Route.new(Controllers::MemberEntries, %w[id member]),
|
|
46
|
+
"change-member-check" => Route.new(Controllers::MemberEntries, %w[id member]),
|
|
47
|
+
"remove-member-check" => Route.new(Controllers::MemberEntries, %w[id member]),
|
|
48
|
+
"remove-member" => Route.new(Controllers::MemberEntries, %w[id member]),
|
|
49
|
+
"start-audit" => Route.new(Controllers::Audit, %w[id]),
|
|
50
|
+
"submit-answer" => Route.new(Controllers::Answer, %w[id]),
|
|
51
|
+
"show-answer-check" => Route.new(Controllers::Answer, %w[id])
|
|
52
|
+
}.freeze #: Hash[String, Route]
|
|
53
|
+
|
|
54
|
+
# Each command's positional arguments, in order, as CommandLine takes them.
|
|
55
|
+
POSITIONAL = ROUTES.transform_values(&:positional).freeze #: Hash[String, Array[String]]
|
|
56
|
+
|
|
57
|
+
# The controller that runs each command: one per set of commands that change for one reason.
|
|
58
|
+
CONTROLLERS = ROUTES.transform_values(&:controller).freeze #: Hash[String, singleton(Controllers::Command)]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "time"
|
|
5
|
+
require_relative "command"
|
|
6
|
+
require_relative "../models/acknowledgement"
|
|
7
|
+
require_relative "../views/acknowledgement_result"
|
|
8
|
+
|
|
9
|
+
module Principles
|
|
10
|
+
# Each runs one command: reads models, applies their rules and prints through a view.
|
|
11
|
+
# controllers/CLAUDE.md owns the boundary.
|
|
12
|
+
module Controllers
|
|
13
|
+
# A one-member audit (D33): start-audit ID --member MEMBER asks whether a committed change keeps that member's
|
|
14
|
+
# meaning, and submit-answer moves its baseline in the audit record when the answer holds.
|
|
15
|
+
class Acknowledgement < Command
|
|
16
|
+
# The request for principle id's member.
|
|
17
|
+
#: (String, String) -> Hash[String, untyped]
|
|
18
|
+
def start(id, member_id)
|
|
19
|
+
naming_the_executable(rerun: one_member_audit(id)) do
|
|
20
|
+
acknowledgement, member, base = prepared(id, member_id)
|
|
21
|
+
baseline, lines = since_baseline(id, acknowledgement, member)
|
|
22
|
+
change = Views::MemberChangeSinceBaseline.new(
|
|
23
|
+
member: shown(acknowledgement, member, baseline), member_lines: lines.shown,
|
|
24
|
+
commits: commits(acknowledgement, baseline, base), paths: acknowledgement.paths_since(baseline)
|
|
25
|
+
)
|
|
26
|
+
view.request(request_id: acknowledgement.request_id.to_s, principle_id: id, change: change,
|
|
27
|
+
open_findings: acknowledgement.open_findings).merge("wrote" => [])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The result of answering request_id with response, and the record it changes, not yet written.
|
|
32
|
+
#: (Models::RequestId, untyped) -> [Hash[String, untyped], Array[Models::AuditRecord]]
|
|
33
|
+
def answered(request_id, response)
|
|
34
|
+
id = request_id.principle_id
|
|
35
|
+
acknowledgement, member, = prepared(id, response.fetch("basis").fetch("member_id"))
|
|
36
|
+
stale(id, member.id) unless acknowledgement.request_id.to_s == request_id.to_s
|
|
37
|
+
[view.acknowledged(principle_id: id, member_id: member.id, file: @audit_records.file(id)),
|
|
38
|
+
[accepted(id, acknowledgement, member, response)]]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# The one-member audit of principle id's member, as a refusal naming a near miss leads its repair with (D56).
|
|
44
|
+
#: (String) -> ^(String) -> String
|
|
45
|
+
def one_member_audit(id)
|
|
46
|
+
->(member) { invocation.in_project("start-audit", id, options: ["--member", member]) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The record with the acknowledgement in it, once its quotes are found at the baseline and now.
|
|
50
|
+
#: (String, Models::Acknowledgement, Models::Member, untyped) -> Models::AuditRecord
|
|
51
|
+
def accepted(id, acknowledgement, member, response)
|
|
52
|
+
_baseline, lines = since_baseline(id, acknowledgement, member)
|
|
53
|
+
acknowledgement.accepted(response, before: lines.at_baseline, now: lines.now, at: Time.now.utc.iso8601)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The member as the request shows it, with where it was at its baseline when it has moved since (#547).
|
|
57
|
+
#: (Models::Acknowledgement, Models::Member, Models::Placement) -> Hash[String, untyped]
|
|
58
|
+
def shown(acknowledgement, member, baseline)
|
|
59
|
+
moved_from = acknowledgement.moved_from(baseline)
|
|
60
|
+
{ "id" => member.id, "role" => member.role, "evidence" => [member.reference],
|
|
61
|
+
**(moved_from ? { "evidence_at_baseline" => [moved_from] } : {}) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The commits the request names: the baseline's, HEAD, and the audit's base when the baseline is later (#550).
|
|
65
|
+
#: (Models::Acknowledgement, Models::Placement, String?) -> Hash[String, String]
|
|
66
|
+
def commits(acknowledgement, baseline, base)
|
|
67
|
+
audit_base = acknowledgement.audit_base(baseline)
|
|
68
|
+
{ "baseline" => baseline.commit, "base" => base.to_s, **(audit_base ? { "audit_base" => audit_base } : {}) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The member's baseline, the oldest commit from the one it was last judged at that holds the text the audit
|
|
72
|
+
# record vouches for, placed as that commit's map placed it or as it is placed now (#547, #550), and its own
|
|
73
|
+
# lines there and now, which the answer's quotes are checked against (#379).
|
|
74
|
+
#: (String, Models::Acknowledgement, Models::Member) -> [Models::Placement, Models::MemberLines]
|
|
75
|
+
def since_baseline(id, acknowledgement, member)
|
|
76
|
+
placements = map_history(id).placements_since(acknowledgement.last_judged_commit, member.key,
|
|
77
|
+
@member_maps.find(id).member(member.id))
|
|
78
|
+
baseline = acknowledgement.baseline(placements)
|
|
79
|
+
lines = @project.member_lines(member.locator, since: baseline.commit, then_locator: baseline.locator)
|
|
80
|
+
[baseline, acknowledgement.compared(lines)]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#: (String, String) -> [Models::Acknowledgement, Models::Member, String?]
|
|
84
|
+
def prepared(id, member_id)
|
|
85
|
+
member = Models::Member.new(@member_maps.find(id).member(member_id))
|
|
86
|
+
base = @history.base_commit(id, [@member_maps.file(id), member.path])
|
|
87
|
+
now = @project.locate(member.locator).fetch("sha256")
|
|
88
|
+
[Models::Acknowledgement.new(principle_id: id, member: member, record: @audit_records.find(id), base: base,
|
|
89
|
+
now_sha256: now), member, base]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
#: (String, String) -> bot
|
|
93
|
+
def stale(id, member_id)
|
|
94
|
+
raise Error.new("stale_request", "This request no longer matches #{id}: the member, its baseline or the " \
|
|
95
|
+
"commit it was based on has changed since it was printed",
|
|
96
|
+
"Start it again and answer the new request: " \
|
|
97
|
+
"#{invocation.in_project("start-audit", id, options: ["--member", member_id])}")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#: () -> Views::AcknowledgementResult
|
|
101
|
+
def view
|
|
102
|
+
Views::AcknowledgementResult.new(invocation)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|