modus-operandi 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. modus_operandi-0.6.0/LICENSE +21 -0
  2. modus_operandi-0.6.0/PKG-INFO +154 -0
  3. modus_operandi-0.6.0/README.md +130 -0
  4. modus_operandi-0.6.0/pyproject.toml +82 -0
  5. modus_operandi-0.6.0/pyproject.toml.orig +86 -0
  6. modus_operandi-0.6.0/src/modus_operandi/__init__.py +26 -0
  7. modus_operandi-0.6.0/src/modus_operandi/bootstrap.py +52 -0
  8. modus_operandi-0.6.0/src/modus_operandi/cli.py +381 -0
  9. modus_operandi-0.6.0/src/modus_operandi/config.py +301 -0
  10. modus_operandi-0.6.0/src/modus_operandi/data/config.example.yml +42 -0
  11. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/adr-task-id.sh +32 -0
  12. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/adr_utils.py +74 -0
  13. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/agent-step.sh +38 -0
  14. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/agent_call.py +34 -0
  15. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/agent_log_tailer.py +135 -0
  16. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/buffered_emitter.py +156 -0
  17. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/check_implementation.py +109 -0
  18. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/check_plan_deviation.py +43 -0
  19. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/check_questions.py +55 -0
  20. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/check_review.py +163 -0
  21. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/check_summary.py +50 -0
  22. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/clear-feedback.sh +12 -0
  23. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/config_invocation.py +288 -0
  24. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/determine-scope.sh +57 -0
  25. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/display.py +142 -0
  26. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/editor.py +101 -0
  27. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/engine_output.py +95 -0
  28. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/feedback_editor.py +136 -0
  29. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/feedback_gate.py +86 -0
  30. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/gate_state.py +89 -0
  31. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/implement-pass-check.sh +23 -0
  32. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/implement-retry.sh +25 -0
  33. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/latency_table.py +224 -0
  34. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/live_lines.py +339 -0
  35. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/live_monitor.py +201 -0
  36. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/name-task.sh +90 -0
  37. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/notify.py +74 -0
  38. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/pass-check.sh +27 -0
  39. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/prompt_subst.sh +20 -0
  40. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/pty_spawn.py +159 -0
  41. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/review-check.sh +87 -0
  42. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/review-task-id.sh +22 -0
  43. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/run-agent-cursor.sh +206 -0
  44. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/run-agent.sh +260 -0
  45. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/run_finish.py +102 -0
  46. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/run_id_discoverer.py +30 -0
  47. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/run_pipeline.py +307 -0
  48. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/run_state.py +33 -0
  49. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/run_statistics.py +293 -0
  50. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/save_adr.py +130 -0
  51. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/session_store.sh +128 -0
  52. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/show-file.sh +73 -0
  53. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/stdout_reader.py +117 -0
  54. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/step_result_poller.py +62 -0
  55. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/table_format.py +117 -0
  56. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/task_utils.py +25 -0
  57. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/usage_parser.py +142 -0
  58. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/validate_inputs.py +122 -0
  59. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/workflow_info.py +89 -0
  60. modus_operandi-0.6.0/src/modus_operandi/data/pipeline_scripts/wrapper_cli.py +57 -0
  61. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/bug-review.md +1 -0
  62. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/comment-fix.md +1 -0
  63. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/comment-review.md +1 -0
  64. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/executor-questions.md +1 -0
  65. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/implement-continue.md +1 -0
  66. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/implement-retry.md +1 -0
  67. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/implement.md +1 -0
  68. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/planner-agreement.md +1 -0
  69. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/planner-answers.md +1 -0
  70. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/review.md +1 -0
  71. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/srp-review.md +1 -0
  72. modus_operandi-0.6.0/src/modus_operandi/data/prompts/adr/tests-review.md +1 -0
  73. modus_operandi-0.6.0/src/modus_operandi/data/prompts/bug-fix.md +1 -0
  74. modus_operandi-0.6.0/src/modus_operandi/data/prompts/fix-all.md +13 -0
  75. modus_operandi-0.6.0/src/modus_operandi/data/prompts/fix.md +1 -0
  76. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/bug-rereview.md +1 -0
  77. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/bug-review.md +1 -0
  78. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/comment-fix.md +1 -0
  79. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/comment-rereview.md +1 -0
  80. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/comment-review.md +1 -0
  81. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/review-rereview.md +1 -0
  82. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/review.md +1 -0
  83. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/srp-rereview.md +1 -0
  84. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/srp-review.md +1 -0
  85. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/tests-rereview.md +1 -0
  86. modus_operandi-0.6.0/src/modus_operandi/data/prompts/review/tests-review.md +1 -0
  87. modus_operandi-0.6.0/src/modus_operandi/data/prompts/srp-fix.md +1 -0
  88. modus_operandi-0.6.0/src/modus_operandi/data/prompts/summary.md +10 -0
  89. modus_operandi-0.6.0/src/modus_operandi/data/prompts/task/research.md +1 -0
  90. modus_operandi-0.6.0/src/modus_operandi/data/prompts/task/study-revise.md +1 -0
  91. modus_operandi-0.6.0/src/modus_operandi/data/prompts/task/study.md +1 -0
  92. modus_operandi-0.6.0/src/modus_operandi/data/prompts/task/write-adr.md +99 -0
  93. modus_operandi-0.6.0/src/modus_operandi/data/prompts/task/write-plan.md +1 -0
  94. modus_operandi-0.6.0/src/modus_operandi/data/roles/executor.md +14 -0
  95. modus_operandi-0.6.0/src/modus_operandi/data/roles/planner.md +13 -0
  96. modus_operandi-0.6.0/src/modus_operandi/data/roles/reviewer-bugs.md +11 -0
  97. modus_operandi-0.6.0/src/modus_operandi/data/roles/reviewer-comment.md +16 -0
  98. modus_operandi-0.6.0/src/modus_operandi/data/roles/reviewer-review.md +11 -0
  99. modus_operandi-0.6.0/src/modus_operandi/data/roles/reviewer-srp.md +7 -0
  100. modus_operandi-0.6.0/src/modus_operandi/data/roles/reviewer-tests.md +132 -0
  101. modus_operandi-0.6.0/src/modus_operandi/data/victory.wav +0 -0
  102. modus_operandi-0.6.0/src/modus_operandi/data/workflows/review-pipeline.yml +133 -0
  103. modus_operandi-0.6.0/src/modus_operandi/data/workflows/task-pipeline.yml +374 -0
  104. modus_operandi-0.6.0/src/modus_operandi/edit_command.py +113 -0
  105. modus_operandi-0.6.0/src/modus_operandi/editor.py +101 -0
  106. modus_operandi-0.6.0/src/modus_operandi/exceptions/__init__.py +19 -0
  107. modus_operandi-0.6.0/src/modus_operandi/exceptions/edit_requested.py +6 -0
  108. modus_operandi-0.6.0/src/modus_operandi/exceptions/help_requested.py +5 -0
  109. modus_operandi-0.6.0/src/modus_operandi/exceptions/invalid_invocation.py +9 -0
  110. modus_operandi-0.6.0/src/modus_operandi/exceptions/uninstall_requested.py +12 -0
  111. modus_operandi-0.6.0/src/modus_operandi/exceptions/version_requested.py +5 -0
  112. modus_operandi-0.6.0/src/modus_operandi/installer.py +74 -0
  113. modus_operandi-0.6.0/src/modus_operandi/installer_cli.py +165 -0
  114. modus_operandi-0.6.0/src/modus_operandi/paths.py +123 -0
  115. modus_operandi-0.6.0/src/modus_operandi/proc.py +25 -0
  116. modus_operandi-0.6.0/src/modus_operandi/prompt.py +12 -0
  117. modus_operandi-0.6.0/src/modus_operandi/render.py +330 -0
  118. modus_operandi-0.6.0/src/modus_operandi/tool_discovery.py +11 -0
  119. modus_operandi-0.6.0/src/modus_operandi/uninstall.py +194 -0
  120. modus_operandi-0.6.0/src/modus_operandi/verify.py +258 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Denis Riazantsev (metacodeine@gmail.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: modus-operandi
3
+ Version: 0.6.0
4
+ Summary: Spec-driven planner -> executor pipelines for opencode and cursor
5
+ Author: Denis Riazantsev
6
+ Author-email: Denis Riazantsev <metacodeine@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: POSIX
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Build Tools
19
+ Requires-Dist: specify-cli>=0.16
20
+ Requires-Dist: pyyaml>=6.0
21
+ Requires-Python: >=3.12
22
+ Project-URL: repository, https://github.com/DenisRyazantsev/modus-operandi
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Modus Operandi 🤌
26
+
27
+ [![PyPI version](https://img.shields.io/pypi/v/modus-operandi.svg)](https://pypi.org/project/modus-operandi/)
28
+ [![Python versions](https://img.shields.io/pypi/pyversions/modus-operandi.svg)](https://pypi.org/project/modus-operandi/)
29
+
30
+ Automate spec-driven "planner -> executor" workflows in LLM clients (opencode
31
+ and cursor): task planning, task implementing and code review.
32
+
33
+ ## Quick start
34
+
35
+ ```shell
36
+ pip install modus-operandi
37
+ modus-operandi task "<task-description>"
38
+ ```
39
+
40
+ No configuration needed: the defaults use the free OpenCode Zen model
41
+ `opencode/big-pickle` for planning, review and implementation, which works
42
+ out of the box. To switch models or backends, run `modus-operandi edit`.
43
+
44
+ ## Install
45
+
46
+ `modus-operandi` works right after install: the first run renders the pipeline
47
+ files (agents, scripts, workflows, prompts) into `~/.config/modus-operandi/` and
48
+ `~/.config/opencode/`, and creates `~/.config/modus-operandi/config.yml` from the
49
+ example. Updates arrive through PyPI (`pip install -U modus-operandi`); the render
50
+ is re-applied automatically on the next run.
51
+
52
+ Requirements: Python >= 3.12, and one backend CLI on PATH — `opencode`
53
+ (default) or `cursor-agent` (see `--backend cursor` below).
54
+
55
+ The dev flow (`python3 install.py` from a checkout) additionally installs a
56
+ checkout-based `modus-operandi` command into `~/.local/bin` and keeps that
57
+ directory on PATH in your existing shell rc files (`~/.bashrc`, `~/.zshrc`,
58
+ `~/.profile`), so the command is available in every newly opened terminal.
59
+
60
+ ## Usage
61
+
62
+ ### Task
63
+
64
+ If you have a workflow like this
65
+
66
+ ```
67
+ 1. Studying the motivation of the task with a planner-agent
68
+ 2. Creating an ADR
69
+ 3. Writing an implementation plan for the executor
70
+ 4. The executor-agent asking clarifying questions about the task
71
+ 5. The planner-agent answering these questions
72
+ 6. Implementing the task
73
+ 7. Reviewing for SRP violations
74
+ 8. Reviewing for bugs
75
+ 9. General review (correctness and quality)
76
+ 10. Comment review (readability)
77
+ 11. Tests review (the tests for the change, judged against the test conventions on machine-generated coverage and latency reports)
78
+ 12. Fixing the issues found
79
+ 13. The executor writing a final summary of what was done
80
+ ```
81
+
82
+ The ADR records the decision (what and why) and is published to the
83
+ `architecture/` directory at the end of the run in its final version; the
84
+ implementation plan stays internal to the executor.
85
+
86
+ then you can use this command
87
+
88
+ ```shell
89
+ modus-operandi task "<task-description>"
90
+ ```
91
+
92
+ The description can also come from a file: `modus-operandi task path/to/description.md`
93
+ (a single argument naming an existing file is read as the description; file
94
+ and inline text input are mutually exclusive).
95
+
96
+ ### Review
97
+
98
+ If you have a workflow like this
99
+
100
+ ```
101
+ 1. Reviewing the whole project for SRP violations
102
+ 2. Reviewing the whole project for bugs
103
+ 3. Comment review (readability)
104
+ 4. Tests review (the tests for the change, judged against the test conventions on machine-generated coverage and latency reports)
105
+ 5. Fixing the issues found
106
+ 6. The executor writing a final summary of what was done
107
+ ```
108
+
109
+ then you can use this command
110
+
111
+ ```shell
112
+ modus-operandi review
113
+ ```
114
+
115
+ To review only the changes between the current branch and the default branch:
116
+
117
+ ```shell
118
+ modus-operandi review --branch-diff
119
+ ```
120
+
121
+ ### Backend
122
+
123
+ By default, opencode is used. If you need the cursor backend, use the
124
+ `--backend cursor` flag:
125
+
126
+ ```shell
127
+ modus-operandi --backend cursor task "<task-description>"
128
+ ```
129
+
130
+ Cursor runs on its default `composer-2` model for planning, review and
131
+ implementation when no `cursor:` section is configured yet; set your own
132
+ model slugs with `modus-operandi edit`.
133
+
134
+ ### Config
135
+
136
+ ```shell
137
+ modus-operandi edit
138
+ ```
139
+
140
+ opens `~/.config/modus-operandi/config.yml` in your terminal editor. On save and
141
+ close it validates the file: a valid config is applied, an invalid one is
142
+ rolled back and the error is reported with its line number; a config left
143
+ unchanged is not re-applied.
144
+
145
+ ## Uninstall
146
+
147
+ ```shell
148
+ modus-operandi uninstall
149
+ pip uninstall modus-operandi
150
+ ```
151
+
152
+ removes the rendered pipeline files (`~/.config/modus-operandi` and the modus-operandi
153
+ files under `~/.config/opencode`) — run `pip uninstall modus-operandi` afterwards
154
+ to remove the package itself.
@@ -0,0 +1,130 @@
1
+ # Modus Operandi 🤌
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/modus-operandi.svg)](https://pypi.org/project/modus-operandi/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/modus-operandi.svg)](https://pypi.org/project/modus-operandi/)
5
+
6
+ Automate spec-driven "planner -> executor" workflows in LLM clients (opencode
7
+ and cursor): task planning, task implementing and code review.
8
+
9
+ ## Quick start
10
+
11
+ ```shell
12
+ pip install modus-operandi
13
+ modus-operandi task "<task-description>"
14
+ ```
15
+
16
+ No configuration needed: the defaults use the free OpenCode Zen model
17
+ `opencode/big-pickle` for planning, review and implementation, which works
18
+ out of the box. To switch models or backends, run `modus-operandi edit`.
19
+
20
+ ## Install
21
+
22
+ `modus-operandi` works right after install: the first run renders the pipeline
23
+ files (agents, scripts, workflows, prompts) into `~/.config/modus-operandi/` and
24
+ `~/.config/opencode/`, and creates `~/.config/modus-operandi/config.yml` from the
25
+ example. Updates arrive through PyPI (`pip install -U modus-operandi`); the render
26
+ is re-applied automatically on the next run.
27
+
28
+ Requirements: Python >= 3.12, and one backend CLI on PATH — `opencode`
29
+ (default) or `cursor-agent` (see `--backend cursor` below).
30
+
31
+ The dev flow (`python3 install.py` from a checkout) additionally installs a
32
+ checkout-based `modus-operandi` command into `~/.local/bin` and keeps that
33
+ directory on PATH in your existing shell rc files (`~/.bashrc`, `~/.zshrc`,
34
+ `~/.profile`), so the command is available in every newly opened terminal.
35
+
36
+ ## Usage
37
+
38
+ ### Task
39
+
40
+ If you have a workflow like this
41
+
42
+ ```
43
+ 1. Studying the motivation of the task with a planner-agent
44
+ 2. Creating an ADR
45
+ 3. Writing an implementation plan for the executor
46
+ 4. The executor-agent asking clarifying questions about the task
47
+ 5. The planner-agent answering these questions
48
+ 6. Implementing the task
49
+ 7. Reviewing for SRP violations
50
+ 8. Reviewing for bugs
51
+ 9. General review (correctness and quality)
52
+ 10. Comment review (readability)
53
+ 11. Tests review (the tests for the change, judged against the test conventions on machine-generated coverage and latency reports)
54
+ 12. Fixing the issues found
55
+ 13. The executor writing a final summary of what was done
56
+ ```
57
+
58
+ The ADR records the decision (what and why) and is published to the
59
+ `architecture/` directory at the end of the run in its final version; the
60
+ implementation plan stays internal to the executor.
61
+
62
+ then you can use this command
63
+
64
+ ```shell
65
+ modus-operandi task "<task-description>"
66
+ ```
67
+
68
+ The description can also come from a file: `modus-operandi task path/to/description.md`
69
+ (a single argument naming an existing file is read as the description; file
70
+ and inline text input are mutually exclusive).
71
+
72
+ ### Review
73
+
74
+ If you have a workflow like this
75
+
76
+ ```
77
+ 1. Reviewing the whole project for SRP violations
78
+ 2. Reviewing the whole project for bugs
79
+ 3. Comment review (readability)
80
+ 4. Tests review (the tests for the change, judged against the test conventions on machine-generated coverage and latency reports)
81
+ 5. Fixing the issues found
82
+ 6. The executor writing a final summary of what was done
83
+ ```
84
+
85
+ then you can use this command
86
+
87
+ ```shell
88
+ modus-operandi review
89
+ ```
90
+
91
+ To review only the changes between the current branch and the default branch:
92
+
93
+ ```shell
94
+ modus-operandi review --branch-diff
95
+ ```
96
+
97
+ ### Backend
98
+
99
+ By default, opencode is used. If you need the cursor backend, use the
100
+ `--backend cursor` flag:
101
+
102
+ ```shell
103
+ modus-operandi --backend cursor task "<task-description>"
104
+ ```
105
+
106
+ Cursor runs on its default `composer-2` model for planning, review and
107
+ implementation when no `cursor:` section is configured yet; set your own
108
+ model slugs with `modus-operandi edit`.
109
+
110
+ ### Config
111
+
112
+ ```shell
113
+ modus-operandi edit
114
+ ```
115
+
116
+ opens `~/.config/modus-operandi/config.yml` in your terminal editor. On save and
117
+ close it validates the file: a valid config is applied, an invalid one is
118
+ rolled back and the error is reported with its line number; a config left
119
+ unchanged is not re-applied.
120
+
121
+ ## Uninstall
122
+
123
+ ```shell
124
+ modus-operandi uninstall
125
+ pip uninstall modus-operandi
126
+ ```
127
+
128
+ removes the rendered pipeline files (`~/.config/modus-operandi` and the modus-operandi
129
+ files under `~/.config/opencode`) — run `pip uninstall modus-operandi` afterwards
130
+ to remove the package itself.
@@ -0,0 +1,82 @@
1
+ [build-system]
2
+ requires = ["uv_build >= 0.12.1, <0.13.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "modus-operandi"
7
+ version = "0.6.0"
8
+ description = "Spec-driven planner -> executor pipelines for opencode and cursor"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: POSIX",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Software Development :: Build Tools",
24
+ ]
25
+ dependencies = [
26
+ "specify-cli>=0.16",
27
+ "pyyaml>=6.0",
28
+ ]
29
+
30
+ [[project.authors]]
31
+ name = "Denis Riazantsev"
32
+ email = "metacodeine@gmail.com"
33
+
34
+ [project.urls]
35
+ repository = "https://github.com/DenisRyazantsev/modus-operandi"
36
+
37
+ [project.scripts]
38
+ modus-operandi = "modus_operandi.cli:main"
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "pytest~=9.1.1",
43
+ "ruff~=0.16.4",
44
+ "mypy~=2.3.1",
45
+ "coverage~=7.6",
46
+ "types-PyYAML==6.0.12.20260815",
47
+ ]
48
+
49
+ [tool.ruff]
50
+ target-version = "py312"
51
+ line-length = 100
52
+ extend-include = [".github/scripts/*.py"]
53
+
54
+ [tool.ruff.lint]
55
+ select = [
56
+ "E",
57
+ "F",
58
+ "I",
59
+ "W",
60
+ "B",
61
+ "UP",
62
+ "SIM",
63
+ ]
64
+
65
+ [tool.pytest.ini_options]
66
+ python_files = [
67
+ "tests/test_*.py",
68
+ "tests/*/test_*.py",
69
+ "tests/*_test.py",
70
+ "tests/*/*_test.py",
71
+ ]
72
+
73
+ [tool.mypy]
74
+ python_version = "3.12"
75
+ strict = true
76
+ explicit_package_bases = true
77
+ files = ["."]
78
+ mypy_path = [
79
+ "src",
80
+ "src/modus_operandi/data/pipeline_scripts",
81
+ ".",
82
+ ]
@@ -0,0 +1,86 @@
1
+ [build-system]
2
+ requires = ["uv_build >= 0.12.1, <0.13.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "modus-operandi"
7
+ version = "0.6.0"
8
+ description = "Spec-driven planner -> executor pipelines for opencode and cursor"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "Denis Riazantsev", email = "metacodeine@gmail.com" },
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: POSIX",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Topic :: Software Development :: Build Tools",
27
+ ]
28
+ dependencies = ["specify-cli>=0.16", "pyyaml>=6.0"]
29
+
30
+ [project.urls]
31
+ repository = "https://github.com/DenisRyazantsev/modus-operandi"
32
+
33
+ [project.scripts]
34
+ modus-operandi = "modus_operandi.cli:main"
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "pytest~=9.1.1",
39
+ "ruff~=0.16.4",
40
+ "mypy~=2.3.1",
41
+ # coverage.py >= 7.6 carries per-function data in its JSON report
42
+ # (test_reports.py parses it for the tests review kind, ADR-0022). The
43
+ # 7.x line is pinned: a future 8.x could reshape that JSON.
44
+ "coverage~=7.6",
45
+ # types-PyYAML does not follow semver: the version is the targeted
46
+ # PyYAML version + a typeshed calver (date) suffix, and any stub update
47
+ # can change type-checking results. Pin exactly.
48
+ "types-PyYAML==6.0.12.20260815",
49
+ ]
50
+
51
+ [tool.ruff]
52
+ target-version = "py312"
53
+ line-length = 100
54
+ lint.select = ["E", "F", "I", "W", "B", "UP", "SIM"]
55
+ # CI scripts live in the hidden .github/ tree, which ruff skips by default;
56
+ # they are first-class code and must be linted like everything else.
57
+ extend-include = [".github/scripts/*.py"]
58
+
59
+ [tool.pytest.ini_options]
60
+ # The whole suite lives under tests/; the root-level report generator
61
+ # (test_reports.py) matches the default `test_*.py` pattern and must not be
62
+ # collected as a test module by an argument-less `uv run pytest`. The
63
+ # patterns are matched against the repo-root-relative path (a `*` crosses
64
+ # path separators, so the second pattern covers every nesting depth).
65
+ python_files = [
66
+ "tests/test_*.py",
67
+ "tests/*/test_*.py",
68
+ "tests/*_test.py",
69
+ "tests/*/*_test.py",
70
+ ]
71
+
72
+ [tool.mypy]
73
+ python_version = "3.12"
74
+ strict = true
75
+ # The standalone data scripts are not part of the modus_operandi package (no
76
+ # __init__.py under data/): with explicit package bases mypy maps them only
77
+ # through mypy_path, so their sibling imports resolve to the same module
78
+ # names the scripts use at runtime.
79
+ explicit_package_bases = true
80
+ # Everything is checked: the whole repo tree, no whitelist, no exclude.
81
+ files = ["."]
82
+ # The standalone scripts import each other from the same directory (e.g.
83
+ # save_adr.py imports adr_utils/agent_call/task_utils); point mypy at it so
84
+ # those sibling imports resolve. "src" is the explicit package base of the
85
+ # modus_operandi package.
86
+ mypy_path = ["src", "src/modus_operandi/data/pipeline_scripts", "."]
@@ -0,0 +1,26 @@
1
+ """modus-operandi package: the launcher and the install/bootstrap flow.
2
+
3
+ The package carries the pipeline artifacts as package data under
4
+ ``data/`` (pipeline scripts, prompts, workflows, the config example and the
5
+ victory sound) and renders them into the user config base on first run
6
+ (``modus_operandi.cli.ensure_installed``). ``__version__`` is mirrored in
7
+ ``pyproject.toml``; both are bumped together on release. The checkout
8
+ carries the 0.0.0.dev0 placeholder (ADR-0018); CI stamps the tag name at
9
+ release.
10
+ """
11
+
12
+ from .paths import Paths
13
+
14
+ # The package's public surface: the install layout type and the
15
+ # user-facing install failure. (The explicit __all__ marks Paths as a
16
+ # re-export of the paths.py alias; InstallError and __version__ are
17
+ # defined in this module.)
18
+ __all__ = ["InstallError", "Paths", "__version__"]
19
+
20
+ __version__ = "0.6.0"
21
+
22
+
23
+ class InstallError(Exception):
24
+ """User-facing failure of the install/bootstrap flow."""
25
+
26
+ pass
@@ -0,0 +1,52 @@
1
+ """Bootstrap: materialize the rendered installation on first run / update.
2
+
3
+ One responsibility: check the ``install-version.txt`` marker against the
4
+ package ``__version__`` and, when the rendered artifacts are missing or
5
+ stale, render them from the package data (prerequisites check + apply) and
6
+ report one status line. The launcher (cli.py) owns the argument parsing and
7
+ dispatch; this module owns the installation state — it changes together with
8
+ installer/verify, not with the command surface.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import sys
14
+
15
+ from . import InstallError, Paths, __version__, installer, verify
16
+
17
+
18
+ def ensure_installed(layout: Paths, check_prereqs: bool = True) -> int:
19
+ """Bootstrap the rendered installation from the package data.
20
+
21
+ Reads ``install-version.txt`` (config base /modus-operandi/install-version.txt):
22
+ when the file is missing or its version differs from the package
23
+ ``__version__``, checks the prerequisites and renders every artifact from
24
+ the package data (``apply()`` records the marker), then prints exactly
25
+ one status line. Returns 0 on success (including an up-to-date
26
+ installation) and 1 on error, with the reason on stderr.
27
+
28
+ ``check_prereqs=False`` skips the tool-presence checks: the `edit` flow
29
+ never runs the backend CLI, so a user who uninstalled it (or switched
30
+ configs) must still be able to edit the config and re-render the
31
+ artifacts.
32
+ """
33
+ marker = layout["install_version"]
34
+ if marker.exists() and marker.read_text(encoding="utf-8").strip() == __version__:
35
+ return 0
36
+ was_installed = marker.exists()
37
+ if check_prereqs:
38
+ try:
39
+ verify.check_prerequisites(layout)
40
+ except InstallError as exc:
41
+ print(f"error: {exc}", file=sys.stderr)
42
+ return 1
43
+ try:
44
+ installer.apply(layout)
45
+ except (InstallError, OSError, ValueError, KeyError) as exc:
46
+ print(f"error: {exc}", file=sys.stderr)
47
+ return 1
48
+ if was_installed:
49
+ print(f"modus-operandi: updated to {__version__}", flush=True)
50
+ else:
51
+ print(f"modus-operandi: installed to {layout['config_dir']}", flush=True)
52
+ return 0