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,149 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "command"
|
|
5
|
+
|
|
6
|
+
module Principles
|
|
7
|
+
# Each runs one command: reads models, applies their rules and prints through a view.
|
|
8
|
+
# controllers/CLAUDE.md owns the boundary.
|
|
9
|
+
module Controllers
|
|
10
|
+
# The writes to one member's entry in the committed member map: where the member is, the part it plays
|
|
11
|
+
# and the check that enforces it. This tool asks, the agent answers, and this tool writes the map and
|
|
12
|
+
# nothing else.
|
|
13
|
+
class MemberEntries < Command
|
|
14
|
+
# Each command and the method that runs it.
|
|
15
|
+
COMMANDS = {
|
|
16
|
+
"add-member" => :add_member, "move-member" => :move_member,
|
|
17
|
+
"change-member-role" => :change_member_role, "change-member-check" => :change_member_check,
|
|
18
|
+
"remove-member-check" => :remove_member_check, "remove-member" => :remove_member
|
|
19
|
+
}.freeze #: Hash[String, Symbol]
|
|
20
|
+
|
|
21
|
+
# Each location option and the name the agent typed it as.
|
|
22
|
+
LOCATION_OPTIONS = { "section" => "--section", "starting_at" => "--starting-at",
|
|
23
|
+
"ending_at" => "--ending-at", "query" => "--query" }.freeze #: Hash[String, String]
|
|
24
|
+
|
|
25
|
+
#: (String, String?, Hash[String, untyped]) -> untyped
|
|
26
|
+
def call(command, id, options)
|
|
27
|
+
naming_the_executable(rerun: ->(member) { rerun(command, id.to_s, member, options) }) do
|
|
28
|
+
method = COMMANDS[command] or unknown(command)
|
|
29
|
+
send(method, required_id(id), options)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# command as the agent ran it on principle id, with member in place of the member id it gave, and its options
|
|
36
|
+
# as given, each quoted for a shell and named as the agent typed it: a location by LOCATION_OPTIONS, any other,
|
|
37
|
+
# such as --path or --role, by its own one-word name (D56).
|
|
38
|
+
#: (String, String, String, Hash[String, untyped]) -> String
|
|
39
|
+
def rerun(command, id, member, options)
|
|
40
|
+
flags = options.except("project", "member").flat_map do |name, value|
|
|
41
|
+
[LOCATION_OPTIONS.fetch(name) { "--#{name}" }, value.to_s]
|
|
42
|
+
end
|
|
43
|
+
invocation.in_project(command, id, member, options: flags)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#: (String, Hash[String, untyped]) -> untyped
|
|
47
|
+
def add_member(id, options)
|
|
48
|
+
started = !@member_maps.mapped?(id)
|
|
49
|
+
map = @member_maps.addable(id)
|
|
50
|
+
member = located(options, { "path" => text(options["path"], "PATH") }).merge(played(options))
|
|
51
|
+
write_map(map.adding(member)) do |printed|
|
|
52
|
+
added = member_added(printed, map, member)
|
|
53
|
+
started ? added.merge(map_result.map_created(printed)) : added
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# What add-member reports of the member it added to map: its id, lines and check, and the members, in
|
|
58
|
+
# this map or another, that share a line with it, so the agent sees an edit there shows on each (#383).
|
|
59
|
+
#: (Views::PrintedPrinciple, Models::MemberMap, Hash[String, untyped]) -> Hash[String, untyped]
|
|
60
|
+
def member_added(printed, map, member)
|
|
61
|
+
added = Models::Member.new(member)
|
|
62
|
+
map_result.member_added(printed, added: span(member).merge(member.slice("check")),
|
|
63
|
+
shared: lines_shared(added, map))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The part a new member plays, and the check that enforces it when one is named (D30).
|
|
67
|
+
#: (Hash[String, untyped]) -> Hash[String, untyped]
|
|
68
|
+
def played(options)
|
|
69
|
+
role = { "role" => text(options["role"], "--role") }
|
|
70
|
+
options["check"] ? role.merge("check" => text(options["check"], "--check")) : role
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The member's id and the lines it spans in its file now, so the agent sees what it located.
|
|
74
|
+
#: (Hash[String, untyped]) -> Hash[String, untyped]
|
|
75
|
+
def span(member)
|
|
76
|
+
{ "id" => Models::Member.new(member).id }
|
|
77
|
+
.merge(snapshot(Models::Member.new(member).locator).slice("first_line", "last_line"))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Points an existing member at a new place (a narrower passage, a renamed heading, an anchor that was
|
|
81
|
+
# lost, or with --path a file that was renamed) without taking it out of the map, so it keeps its key and
|
|
82
|
+
# its audit (#268).
|
|
83
|
+
#: (String, Hash[String, untyped]) -> untyped
|
|
84
|
+
def move_member(id, options)
|
|
85
|
+
map = @member_maps.find(id)
|
|
86
|
+
old = map.member(text(options["member"], "MEMBER"))
|
|
87
|
+
member = located(options, { "path" => new_path(options, old) })
|
|
88
|
+
moved = span(member)
|
|
89
|
+
write_map(map.moving(Models::Member.new(old).id, member)) do |printed|
|
|
90
|
+
map_result.member_moved(printed, from: Models::Member.new(old).id, moved: moved)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# The path --path names, or else the member's own.
|
|
95
|
+
#: (Hash[String, untyped], Hash[String, untyped]) -> String
|
|
96
|
+
def new_path(options, old)
|
|
97
|
+
options["path"] ? text(options["path"], "--path") : old.fetch("path")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#: (String, Hash[String, untyped]) -> untyped
|
|
101
|
+
def change_member_role(id, options)
|
|
102
|
+
map = @member_maps.find(id)
|
|
103
|
+
target = Models::Member.new(map.member(text(options["member"], "MEMBER"))).id
|
|
104
|
+
write_map(map.changing_role(target, text(options["role"], "--role"))) do |printed|
|
|
105
|
+
map_result.member_replaced(printed, member: target, done: "Changed the role of #{target}.")
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Names the command that enforces a member, which the audit asks the agent to run (D30).
|
|
110
|
+
#: (String, Hash[String, untyped]) -> untyped
|
|
111
|
+
def change_member_check(id, options)
|
|
112
|
+
map = @member_maps.find(id)
|
|
113
|
+
target = Models::Member.new(map.member(text(options["member"], "MEMBER"))).id
|
|
114
|
+
check = text(options["check"], "--check")
|
|
115
|
+
changed = map.changing_check(target, check)
|
|
116
|
+
hidden = Models::Member.new(changed.member(target)).check_hidden_characters
|
|
117
|
+
write_map(changed) { |printed| map_result.check_changed(printed, member: target, hidden: hidden) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
#: (String, Hash[String, untyped]) -> untyped
|
|
121
|
+
def remove_member_check(id, options)
|
|
122
|
+
map = @member_maps.find(id)
|
|
123
|
+
target = Models::Member.new(map.member(text(options["member"], "MEMBER"))).id
|
|
124
|
+
write_map(map.removing_check(target)) do |printed|
|
|
125
|
+
map_result.member_replaced(printed, member: target,
|
|
126
|
+
done: "Removed the check of #{target}; the audit no longer asks for " \
|
|
127
|
+
"its result.")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
#: (String, Hash[String, untyped]) -> untyped
|
|
132
|
+
def remove_member(id, options)
|
|
133
|
+
map = @member_maps.find(id)
|
|
134
|
+
target = text(options["member"], "MEMBER")
|
|
135
|
+
write_map(map.removing(target)) { |printed| map_result.member_removed(printed, removed: target) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The member's place in its file from --section, --starting-at and --ending-at, checked to resolve.
|
|
139
|
+
#: (Hash[String, untyped], Hash[String, untyped]) -> Hash[String, untyped]
|
|
140
|
+
def located(options, member)
|
|
141
|
+
LOCATION_OPTIONS.each { |key, flag| member[key] = text(options[key], flag) if options[key] }
|
|
142
|
+
place = Models::Member.new(member)
|
|
143
|
+
place.check_location
|
|
144
|
+
snapshot(place.locator)
|
|
145
|
+
member
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "command"
|
|
5
|
+
require_relative "../views/members_a_commit_changed"
|
|
6
|
+
|
|
7
|
+
module Principles
|
|
8
|
+
# Each runs one command: reads models, applies their rules and prints through a view.
|
|
9
|
+
# controllers/CLAUDE.md owns the boundary.
|
|
10
|
+
module Controllers
|
|
11
|
+
# list-member-problems --commits-after COMMIT --hook-event EVENT, which the plugin's after-commit hook runs once a
|
|
12
|
+
# Bash call moved HEAD (D73): the members the commits after COMMIT changed, as the hook's JSON, or nothing. It
|
|
13
|
+
# exits 0 whatever it finds, since the host shows the user an error for any other status, and reports a refusal
|
|
14
|
+
# only when those commits could have caused it, so a broken setup does not repeat at every commit (review R8).
|
|
15
|
+
class MembersACommitChanged < Command
|
|
16
|
+
# report gives list-member-problems' result for every map, as Inventory checks it, or raises its refusal.
|
|
17
|
+
#: (Models::Project, tool: RunningTool, report: ^() -> Hash[String, untyped]) -> void
|
|
18
|
+
def initialize(project, tool:, report:)
|
|
19
|
+
super(project, tool: tool)
|
|
20
|
+
@report = report
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The hook's output for the commits after `after`, run for event.
|
|
24
|
+
#: (after: String, event: String) -> Views::Lines
|
|
25
|
+
def printed(after:, event:)
|
|
26
|
+
members = @report.call.fetch("members")
|
|
27
|
+
changed = @history.commits_brought_in(after).changed_by(members)
|
|
28
|
+
return Views::MembersACommitChanged::NOTHING if changed.empty?
|
|
29
|
+
|
|
30
|
+
view.changed(event, changed, others: members.size - changed.size)
|
|
31
|
+
rescue Error => e
|
|
32
|
+
return Views::MembersACommitChanged::NOTHING unless @history.touched_since?(after, @member_maps.readable_paths)
|
|
33
|
+
|
|
34
|
+
view.refused(event, e.payload.fetch("code"))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
#: () -> Views::MembersACommitChanged
|
|
40
|
+
def view = Views::MembersACommitChanged.new(invocation)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "command"
|
|
5
|
+
require_relative "../models/check_since_last_audit"
|
|
6
|
+
require_relative "../models/open_findings_elsewhere"
|
|
7
|
+
require_relative "../views/acknowledged_since_last_audit"
|
|
8
|
+
require_relative "../views/diff_since_last_audit"
|
|
9
|
+
|
|
10
|
+
module Principles
|
|
11
|
+
# Each runs one command: reads models, applies their rules and prints through a view.
|
|
12
|
+
# controllers/CLAUDE.md owns the boundary.
|
|
13
|
+
module Controllers
|
|
14
|
+
# The members a full audit's request lists, which Audit hands over for start-audit: each as the agent assesses
|
|
15
|
+
# it, beside what the principle's audit record says of it since the last full audit.
|
|
16
|
+
class MembersToAssess < Command
|
|
17
|
+
# The members a full audit's request lists, each as the agent assesses it, and the findings other principles
|
|
18
|
+
# leave open on members sharing their lines, each once, which each member's mapped_elsewhere names by id (D62).
|
|
19
|
+
class Listing
|
|
20
|
+
#: Array[Hash[String, untyped]]
|
|
21
|
+
attr_reader :members
|
|
22
|
+
|
|
23
|
+
#: Array[Hash[String, untyped]]
|
|
24
|
+
attr_reader :open_findings_elsewhere
|
|
25
|
+
|
|
26
|
+
#: (members: Array[Hash[String, untyped]], open_findings_elsewhere: Array[Hash[String, untyped]]) -> void
|
|
27
|
+
def initialize(members:, open_findings_elsewhere:)
|
|
28
|
+
@members = members
|
|
29
|
+
@open_findings_elsewhere = open_findings_elsewhere
|
|
30
|
+
freeze
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# principle_id names the principle whose members are listed, against its audit record now.
|
|
35
|
+
#: (Models::Project, tool: RunningTool, principle_id: String) -> void
|
|
36
|
+
def initialize(project, tool:, principle_id:)
|
|
37
|
+
super(project, tool: tool)
|
|
38
|
+
@principle_id = principle_id
|
|
39
|
+
@record = @audit_records.find(principle_id) #: Models::AuditRecord?
|
|
40
|
+
@completed = @record&.last_completed #: Models::AuditRecord?
|
|
41
|
+
@checks = Models::CheckSinceLastAudit.new(id: principle_id, history: @history, member_maps: @member_maps,
|
|
42
|
+
record: @record) #: Models::CheckSinceLastAudit
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Each member of audit's map as the assessment request shows it, located as current has it: id, role, lines,
|
|
46
|
+
# the evidence that locates it and the check that enforces it, if any, with whether the last audit already had
|
|
47
|
+
# it run, the edits to it acknowledged since that audit, its last assessment carried forward by audit, or why
|
|
48
|
+
# it is judged again (D49) with the command that shows its file's edits since that audit (#771), and the other
|
|
49
|
+
# principles' members sharing its lines (#719), beside the findings their principles leave open on them (D62).
|
|
50
|
+
#: (Models::Audit, Models::MemberSnapshots) -> Listing
|
|
51
|
+
def listed(audit, current)
|
|
52
|
+
map = audit.map
|
|
53
|
+
carried = carried_assessments(audit)
|
|
54
|
+
elsewhere = elsewhere(map, current)
|
|
55
|
+
members = map.members_shown.map do |fields|
|
|
56
|
+
member = Models::Member.new(fields)
|
|
57
|
+
judged = carried(member, carried)
|
|
58
|
+
located(member, current).merge(checked(member), acknowledged(fields), judged,
|
|
59
|
+
changed_since(fields, judged), shared_lines(member, elsewhere))
|
|
60
|
+
end
|
|
61
|
+
Listing.new(members: members, open_findings_elsewhere: elsewhere_listed(elsewhere, current))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Each finding elsewhere names, once, with each evidence item's lines beside this audit's members, where current
|
|
67
|
+
# locates them; the project locates each item now (D64).
|
|
68
|
+
#: (Models::OpenFindingsElsewhere, Models::MemberSnapshots) -> Array[Hash[String, untyped]]
|
|
69
|
+
def elsewhere_listed(elsewhere, current)
|
|
70
|
+
elsewhere.listed(current.audited_lines) { |locator| @project.located_text(locator) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The other principles' members sharing a line with each member of map where current locates it, with the
|
|
74
|
+
# findings their records leave open on them, each record read once (#772, D62). The request id hashes this
|
|
75
|
+
# principle's members, not other maps or their records: another principle changing after the request is its
|
|
76
|
+
# own audit to judge, and does not make this one stale.
|
|
77
|
+
#: (Models::MemberMap, Models::MemberSnapshots) -> Models::OpenFindingsElsewhere
|
|
78
|
+
def elsewhere(map, current)
|
|
79
|
+
sharing = map.members.ids.to_h do |id|
|
|
80
|
+
[id, Models::MembersSharingLines.new(@project, current.of(id), id: id).elsewhere(except: @principle_id)]
|
|
81
|
+
end
|
|
82
|
+
Models::OpenFindingsElsewhere.of(sharing, @audit_records)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The other principles' members sharing a line with member, each with its role there, so a contradiction
|
|
86
|
+
# between the two maps shows in this audit (#719), naming the findings that principle leaves open on it, so one
|
|
87
|
+
# problem is not raised under both (#772, D62); nothing when there are none.
|
|
88
|
+
#: (Models::Member, Models::OpenFindingsElsewhere) -> Hash[String, untyped]
|
|
89
|
+
def shared_lines(member, elsewhere)
|
|
90
|
+
Views::LinesSharedWithMember.new(mapped_elsewhere: elsewhere.beside(member.id)).to_h
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The member's last assessment carried forward, with whether any evidence it cited changed since and, in git,
|
|
94
|
+
# whether its file changed since the last full audit's base commit, outside the member, whose own text did not;
|
|
95
|
+
# or why it is judged again. These are facts: the agent may re-judge any carried member (D49).
|
|
96
|
+
#: (Models::Member, Models::CarriedAssessments) -> Hash[String, untyped]
|
|
97
|
+
def carried(member, carried)
|
|
98
|
+
shown = carried.shown(member.id) { |locator, sha| evidence_changed?(locator, sha) }
|
|
99
|
+
kept = shown["carried_forward"]
|
|
100
|
+
base = @completed&.base_commit
|
|
101
|
+
return shown unless kept && base
|
|
102
|
+
|
|
103
|
+
{ "carried_forward" => kept.merge("file_changed_outside_member" =>
|
|
104
|
+
@history.commits_since(base, member.path).any?) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Whether the evidence at locator no longer hashes to sha, or can no longer be found.
|
|
108
|
+
#: (Models::Locator, String) -> bool
|
|
109
|
+
def evidence_changed?(locator, sha)
|
|
110
|
+
snapshot(locator).fetch("sha256") != sha
|
|
111
|
+
rescue Error
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# The member as the request names and locates it: id, role, evidence and its lines now.
|
|
116
|
+
#: (Models::Member, Models::MemberSnapshots) -> Hash[String, untyped]
|
|
117
|
+
def located(member, current)
|
|
118
|
+
{ "id" => member.id, "role" => member.role, "evidence" => [member.reference] }
|
|
119
|
+
.merge(current.of(member.id).lines)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# The command that shows the edits to a member judged again to its file since the last full audit's base
|
|
123
|
+
# commit, over its path then and now (#557, #771); nothing for a member carried forward, or with no such
|
|
124
|
+
# commit, as outside git or before any full audit.
|
|
125
|
+
#: (Hash[String, untyped], Hash[String, untyped]) -> Hash[String, untyped]
|
|
126
|
+
def changed_since(fields, judged)
|
|
127
|
+
base = @completed&.base_commit
|
|
128
|
+
return {} unless judged["judge_because"] && base
|
|
129
|
+
|
|
130
|
+
paths = map_history(@principle_id).paths_from(base, Models::Member.new(fields).key, fields)
|
|
131
|
+
{ "read_the_change" => Views::DiffSinceLastAudit.new(last_audited_commit: base, paths: paths).to_s }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The edits to member acknowledged since the last full audit, which this audit judges together (D33, #312), with
|
|
135
|
+
# its lines then and now; its read_the_change shows the whole change (#771).
|
|
136
|
+
#: (Hash[String, untyped]) -> Hash[String, untyped]
|
|
137
|
+
def acknowledged(fields)
|
|
138
|
+
last = @completed or return {}
|
|
139
|
+
|
|
140
|
+
member = Models::Member.new(fields)
|
|
141
|
+
made = last.members.acknowledgements_for(member.key)
|
|
142
|
+
return {} if made.empty?
|
|
143
|
+
|
|
144
|
+
base = last.base_commit.to_s
|
|
145
|
+
lines = @project.member_lines_since_last_audit(member.locator, since: base).shown
|
|
146
|
+
since = Views::AcknowledgedSinceLastAudit.new(last_audited_commit: base, member_lines: lines,
|
|
147
|
+
acknowledgements: made)
|
|
148
|
+
{ "acknowledged_since_last_audit" => since.to_h }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
#: (Models::Member) -> Hash[String, untyped]
|
|
152
|
+
def checked(member)
|
|
153
|
+
check = member.check or return {}
|
|
154
|
+
hidden = member.check_hidden_characters
|
|
155
|
+
{ "check" => check, "check_since_last_audit" => @checks.of(member, check) }
|
|
156
|
+
.merge(hidden.empty? ? {} : { "check_hidden_characters" => hidden })
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "command"
|
|
5
|
+
require_relative "inventory"
|
|
6
|
+
require_relative "../models/entry_files"
|
|
7
|
+
require_relative "../models/runs_from"
|
|
8
|
+
require_relative "../models/meta_principle"
|
|
9
|
+
require_relative "../models/text_size"
|
|
10
|
+
require_relative "../views/meta_principle_result"
|
|
11
|
+
|
|
12
|
+
module Principles
|
|
13
|
+
# Each runs one command: reads models, applies their rules and prints through a view.
|
|
14
|
+
# controllers/CLAUDE.md owns the boundary.
|
|
15
|
+
module Controllers
|
|
16
|
+
# Setting principles up: shows the map of the principle that keeps every other one held, and proposes its passage
|
|
17
|
+
# for the agent to place with the user, since this tool writes none of the project's guidance (D67, D68); the
|
|
18
|
+
# add-member it prints writes the map (T74). It takes no id, since the principle is the product's own.
|
|
19
|
+
class MetaPrincipleSetup < Command
|
|
20
|
+
#: (String, String?, Hash[String, untyped]) -> untyped
|
|
21
|
+
def call(command, _id, _options)
|
|
22
|
+
naming_the_executable do
|
|
23
|
+
command == "show-meta-principle-passage" ? show_meta_principle_passage : unknown(command)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Writes nothing: with no map, it shows the one the printed add-member creates with the passage as its first
|
|
30
|
+
# member, so no memberless map waits while placement waits on an owner (T74). A map already there is shown as
|
|
31
|
+
# the project holds it, since the id is its identity, and its members in an entry file are named for the agent
|
|
32
|
+
# to judge whether one is the passage.
|
|
33
|
+
#: () -> untyped
|
|
34
|
+
def show_meta_principle_passage
|
|
35
|
+
map = @member_maps.addable(Models::MetaPrinciple::ID)
|
|
36
|
+
view = meta_principle_result
|
|
37
|
+
view.proposal(printed_principle(map), placement(view), map_in_project(map),
|
|
38
|
+
existing_principles: existing_principles).merge("wrote" => [])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# map as the project holds it: whether it was there before this run, its members already in an entry file, and
|
|
42
|
+
# whether principles is set up, its map having a member (D75).
|
|
43
|
+
#: (Models::MemberMap) -> Views::MetaPrincipleResult::MapInProject
|
|
44
|
+
def map_in_project(map)
|
|
45
|
+
Views::MetaPrincipleResult::MapInProject.new(
|
|
46
|
+
already_mapped: @member_maps.mapped?(map.id), members_in_entry_files: map.members_in(Models::EntryFiles::PATHS),
|
|
47
|
+
set_up: @member_maps.set_up?
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The project's other principles, as list-principles lists them, which this principle's first audit keeps held.
|
|
52
|
+
#: () -> Array[Hash[String, untyped]]
|
|
53
|
+
def existing_principles = handed(Inventory).rows(@member_maps.ids - [Models::MetaPrinciple::ID])
|
|
54
|
+
|
|
55
|
+
# Where the passage can go: how this run was started and whether the hook runs in this session, the project's
|
|
56
|
+
# entry files, and the passage.
|
|
57
|
+
#: (Views::MetaPrincipleResult) -> Views::MetaPrincipleResult::Placement
|
|
58
|
+
def placement(view)
|
|
59
|
+
runs_from = Models::RunsFrom.new(@tool.executable)
|
|
60
|
+
Views::MetaPrincipleResult::Placement.new(
|
|
61
|
+
runs_from: runs_from.to_s, session_start_hook_in_this_session: runs_from.session_start_hook_in_this_session?,
|
|
62
|
+
entry_files: @project.entry_files.to_a, passage: passage(view)
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The view of show-meta-principle-passage's result, with how its printed commands map the passage.
|
|
67
|
+
#: () -> Views::MetaPrincipleResult
|
|
68
|
+
def meta_principle_result
|
|
69
|
+
Views::MetaPrincipleResult.new(invocation, passage_mapping, in_git: @history.inside_git?)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The passage member's heading and role, the role of a passage the project has that carries one of its steps,
|
|
73
|
+
# and the role of any other place that keeps the principles held (D75).
|
|
74
|
+
#: () -> Views::MetaPrincipleResult::PassageMapping
|
|
75
|
+
def passage_mapping
|
|
76
|
+
Views::MetaPrincipleResult::PassageMapping.new(heading: Models::MetaPrinciple::PASSAGE_HEADING,
|
|
77
|
+
role: Models::MetaPrinciple::PASSAGE_ROLE,
|
|
78
|
+
step_role: Models::MetaPrinciple::STEP_ROLE,
|
|
79
|
+
place_role: Models::MetaPrinciple::KEEPING_PLACE_ROLE)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The passage's text, as the agent pastes it, with its size, which loads in every session (F9), and what it
|
|
83
|
+
# presumes of a teammate without principles (D71).
|
|
84
|
+
#: (Views::MetaPrincipleResult) -> Hash[String, untyped]
|
|
85
|
+
def passage(view)
|
|
86
|
+
text = view.passage
|
|
87
|
+
size = Models::TextSize.new(text).under(%w[bytes estimated_tokens])
|
|
88
|
+
{ "text" => text, **size, "presumes" => view.presumes(size.fetch("estimated_tokens")) }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "time"
|
|
5
|
+
require_relative "command"
|
|
6
|
+
require_relative "../models/finding_write_ups"
|
|
7
|
+
require_relative "../models/last_completed_revision"
|
|
8
|
+
require_relative "../models/repoint"
|
|
9
|
+
require_relative "../views/repoint_result"
|
|
10
|
+
|
|
11
|
+
module Principles
|
|
12
|
+
# Each runs one command: reads models, applies their rules and prints through a view.
|
|
13
|
+
# controllers/CLAUDE.md owns the boundary.
|
|
14
|
+
module Controllers
|
|
15
|
+
# A repoint of a principle's open findings' write-ups (D59): start-audit ID --write-ups lists each open finding
|
|
16
|
+
# with where its write-up is now, and submit-answer points the recorded_at the agent gives in the audit record.
|
|
17
|
+
class Repoint < Command
|
|
18
|
+
# The request for principle id's open findings.
|
|
19
|
+
#: (String) -> Hash[String, untyped]
|
|
20
|
+
def start(id)
|
|
21
|
+
naming_the_executable do
|
|
22
|
+
record = @audit_records.find(id)
|
|
23
|
+
repoint = Models::Repoint.new(principle_id: id, record: record, history: @history)
|
|
24
|
+
findings = Models::FindingWriteUps.of(@project, [record].compact).listed
|
|
25
|
+
view.request(request_id: repoint.request_id.to_s, principle_id: id, revision: repoint.revision,
|
|
26
|
+
findings: findings).merge("wrote" => [])
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The result of answering request_id with response, and the record it changes, not yet written. It is
|
|
31
|
+
# refused once the last completed record changed after the request was printed, unless it writes nothing.
|
|
32
|
+
#: (Models::RequestId, untyped) -> [Hash[String, untyped], Array[Models::AuditRecord]]
|
|
33
|
+
def answered(request_id, response)
|
|
34
|
+
id = request_id.principle_id
|
|
35
|
+
record = @audit_records.find(id)
|
|
36
|
+
repoint = Models::Repoint.new(principle_id: id, record: record, history: @history)
|
|
37
|
+
repointed = repoint.accepted(response, at: Time.now.utc.iso8601) { |locator| snapshot(locator) }
|
|
38
|
+
changes = writable_changes(record, repointed, response.dig("basis", "revision"))
|
|
39
|
+
[view.repointed(principle_id: id, file: @audit_records.file(id), outcome: outcome(repoint, repointed)), changes]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# What the repoint did, and each other principle's open finding whose write-up cannot be found (T16).
|
|
45
|
+
#: (Models::Repoint, Models::AuditRecord) -> Views::RepointResult::Outcome
|
|
46
|
+
def outcome(repoint, repointed)
|
|
47
|
+
others = @audit_records.of(@member_maps.ids - [repointed.principle_id])
|
|
48
|
+
Views::RepointResult::Outcome.new(repointed: repoint.moved_ids(repointed),
|
|
49
|
+
unchanged: repoint.unchanged_ids(repointed),
|
|
50
|
+
not_found_elsewhere: Models::FindingWriteUps.of(@project, others).not_found)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The records repointed changes from record. Refused once record changed after the request, whose revision the
|
|
54
|
+
# answer quotes back, was printed, unless there are none.
|
|
55
|
+
#: (Models::AuditRecord?, Models::AuditRecord, String?) -> Array[Models::AuditRecord]
|
|
56
|
+
def writable_changes(record, repointed, revision)
|
|
57
|
+
changes = @audit_records.changes(repointed)
|
|
58
|
+
Models::LastCompletedRevision.new(revision).confirm_writable(repointed.principle_id, record, changes)
|
|
59
|
+
changes
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#: () -> Views::RepointResult
|
|
63
|
+
def view
|
|
64
|
+
Views::RepointResult.new(invocation)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../models/meta_principle"
|
|
5
|
+
require_relative "../models/principles_package"
|
|
6
|
+
require_relative "../views/meta_principle_passage"
|
|
7
|
+
|
|
8
|
+
module Principles
|
|
9
|
+
# Each runs one command: reads models, applies their rules and prints through a view.
|
|
10
|
+
# controllers/CLAUDE.md owns the boundary.
|
|
11
|
+
module Controllers
|
|
12
|
+
# This run of the tool, as its entry point was invoked: the executable every printed command names, and the
|
|
13
|
+
# version running, which only the command line can read; and so the release of principles it comes from.
|
|
14
|
+
class RunningTool
|
|
15
|
+
#: String
|
|
16
|
+
attr_reader :executable
|
|
17
|
+
|
|
18
|
+
# version gives the version running, read only when a command needs it, since in a checkout reading it
|
|
19
|
+
# asks git.
|
|
20
|
+
#: (executable: String, version: ^() -> String) -> void
|
|
21
|
+
def initialize(executable:, version:)
|
|
22
|
+
@executable = executable
|
|
23
|
+
@version = version
|
|
24
|
+
freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#: () -> String
|
|
28
|
+
def version = @version.call
|
|
29
|
+
|
|
30
|
+
# The release of principles this run comes from, as the audit of map's principle keeps it: the version running
|
|
31
|
+
# and the passage as setup prints it, which only a view holds; nil for a principle whose audit keeps none (D68).
|
|
32
|
+
#: (Models::MemberMap) -> Models::PrinciplesPackage?
|
|
33
|
+
def release_kept_by(map)
|
|
34
|
+
return unless map.keeps_package?
|
|
35
|
+
|
|
36
|
+
passage = Views::MetaPrinciplePassage.new(heading: Models::MetaPrinciple::PASSAGE_HEADING).text
|
|
37
|
+
Models::PrinciplesPackage.of(version: version, passage: passage)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "../error"
|
|
6
|
+
require_relative "schema_check"
|
|
7
|
+
require_relative "principles_subfolder"
|
|
8
|
+
require_relative "json_text"
|
|
9
|
+
|
|
10
|
+
module Principles
|
|
11
|
+
# Every file and process this tool reads or writes. data/CLAUDE.md owns the boundary.
|
|
12
|
+
module Data
|
|
13
|
+
# The audit records: one JSON file per principle under .principles/audits/, committed in the audited
|
|
14
|
+
# repository beside its map. A record holds the last audit's base commit, each member's hash and each
|
|
15
|
+
# finding's id, members and action, never the agent's prose (D24, D25).
|
|
16
|
+
class AuditFiles
|
|
17
|
+
# The key a record named its principle under before the rename to principles (#536).
|
|
18
|
+
EARLIER_ID_KEY = "memeplex_id"
|
|
19
|
+
|
|
20
|
+
#: (PrinciplesSubfolder) -> void
|
|
21
|
+
def initialize(directory)
|
|
22
|
+
@directory = directory
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Which of the folder and its earlier name hold this subfolder (#536).
|
|
26
|
+
#: () -> Array[String]
|
|
27
|
+
def held_in
|
|
28
|
+
@directory.held_in
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#: (String) -> bool
|
|
32
|
+
def exist?(id)
|
|
33
|
+
@directory.exist?(id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The record of id's last audit, or nil when it has none, checked against its schema (D29).
|
|
37
|
+
#: (String) -> Hash[String, untyped]?
|
|
38
|
+
def read(id)
|
|
39
|
+
return unless exist?(id)
|
|
40
|
+
|
|
41
|
+
parse(id, @directory.read(id, shown_as: relative(id)))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The record of id from its text, with an older version's key upgraded, checked against its schema (D29).
|
|
45
|
+
#: (String, String) -> Hash[String, untyped]
|
|
46
|
+
def parse(id, text)
|
|
47
|
+
record = upgraded(JsonText.new(text).parsed)
|
|
48
|
+
problems = SchemaCheck.new("audit-record").problems(record)
|
|
49
|
+
return record if problems.empty?
|
|
50
|
+
|
|
51
|
+
raise Error.new("invalid_audit_record", "#{relative(id)} is not in the shape this tool writes",
|
|
52
|
+
"Repair each field listed, or restore the record from version control.", relative(id),
|
|
53
|
+
fields: problems)
|
|
54
|
+
rescue JSON::ParserError => e
|
|
55
|
+
raise Error.new("unparseable_audit", e.message,
|
|
56
|
+
"Repair this audit record or restore it from version control.", relative(id))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
#: (String, Hash[String, untyped]) -> void
|
|
60
|
+
def write(id, record)
|
|
61
|
+
@directory.write(id, "#{JSON.pretty_generate(record)}\n")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#: (String) -> String
|
|
65
|
+
def relative(id)
|
|
66
|
+
File.join(PrinciplesSubfolder::FOLDER, "audits", "#{id}.json")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Where record id was kept before the folder took its name now, so it can be read at earlier commits (#536).
|
|
70
|
+
#: (String) -> String
|
|
71
|
+
def earlier_relative(id)
|
|
72
|
+
File.join(PrinciplesSubfolder::EARLIER_FOLDER, "audits", "#{id}.json")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# A record an older version wrote names its principle under EARLIER_ID_KEY, in the record and in the last
|
|
78
|
+
# completed record an open one keeps. It is read under principle_id, in the same place, so the next save
|
|
79
|
+
# writes the new key only; when both are present, principle_id is kept.
|
|
80
|
+
#: (untyped) -> untyped
|
|
81
|
+
def upgraded(record)
|
|
82
|
+
return record unless record.is_a?(Hash)
|
|
83
|
+
|
|
84
|
+
renamed = renamed_id(record)
|
|
85
|
+
kept = renamed["last_completed"]
|
|
86
|
+
kept.is_a?(Hash) ? renamed.merge("last_completed" => renamed_id(kept)) : renamed
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
#: (Hash[String, untyped]) -> Hash[String, untyped]
|
|
90
|
+
def renamed_id(record)
|
|
91
|
+
current = record.key?("principle_id") ? record.except(EARLIER_ID_KEY) : record
|
|
92
|
+
current.to_h { |key, value| [key == EARLIER_ID_KEY ? "principle_id" : key, value] }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|