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