modus-operandi 0.1.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 (111) hide show
  1. modus_operandi-0.1.0/LICENSE +21 -0
  2. modus_operandi-0.1.0/PKG-INFO +140 -0
  3. modus_operandi-0.1.0/README.md +116 -0
  4. modus_operandi-0.1.0/pyproject.toml +73 -0
  5. modus_operandi-0.1.0/pyproject.toml.orig +69 -0
  6. modus_operandi-0.1.0/src/modus_operandi/__init__.py +24 -0
  7. modus_operandi-0.1.0/src/modus_operandi/bootstrap.py +52 -0
  8. modus_operandi-0.1.0/src/modus_operandi/cli.py +313 -0
  9. modus_operandi-0.1.0/src/modus_operandi/config.py +199 -0
  10. modus_operandi-0.1.0/src/modus_operandi/data/config.example.yml +37 -0
  11. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/adr-task-id.sh +32 -0
  12. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/adr_utils.py +74 -0
  13. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/agent-step.sh +38 -0
  14. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/agent_call.py +34 -0
  15. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/agent_log_tailer.py +121 -0
  16. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/buffered_emitter.py +156 -0
  17. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/check_implementation.py +109 -0
  18. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/check_plan_deviation.py +43 -0
  19. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/check_questions.py +55 -0
  20. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/check_review.py +149 -0
  21. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/clear-feedback.sh +12 -0
  22. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/config_invocation.py +197 -0
  23. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/determine-scope.sh +57 -0
  24. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/display.py +89 -0
  25. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/editor.py +97 -0
  26. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/engine_output.py +95 -0
  27. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/feedback_editor.py +136 -0
  28. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/feedback_gate.py +86 -0
  29. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/gate_state.py +89 -0
  30. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/implement-pass-check.sh +23 -0
  31. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/implement-retry.sh +25 -0
  32. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/latency_table.py +223 -0
  33. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/live_lines.py +339 -0
  34. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/live_monitor.py +201 -0
  35. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/name-task.sh +90 -0
  36. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/notify.py +55 -0
  37. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/pass-check.sh +27 -0
  38. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/prompt_subst.sh +20 -0
  39. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/pty_spawn.py +155 -0
  40. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/review-check.sh +83 -0
  41. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/review-task-id.sh +22 -0
  42. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/run-agent-cursor.sh +208 -0
  43. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/run-agent.sh +268 -0
  44. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/run_finish.py +102 -0
  45. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/run_id_discoverer.py +30 -0
  46. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/run_pipeline.py +283 -0
  47. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/run_state.py +33 -0
  48. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/run_statistics.py +274 -0
  49. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/save_adr.py +130 -0
  50. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/session_store.sh +137 -0
  51. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/show-file.sh +38 -0
  52. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/stdout_reader.py +117 -0
  53. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/step_result_poller.py +62 -0
  54. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/table_format.py +103 -0
  55. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/task_utils.py +25 -0
  56. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/usage_parser.py +142 -0
  57. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/validate_inputs.py +114 -0
  58. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/warm-planner.sh +47 -0
  59. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/workflow_info.py +89 -0
  60. modus_operandi-0.1.0/src/modus_operandi/data/pipeline_scripts/wrapper_cli.py +57 -0
  61. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/bug-review.md +3 -0
  62. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/comment-fix.md +1 -0
  63. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/comment-review.md +3 -0
  64. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/executor-questions.md +1 -0
  65. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/implement-continue.md +1 -0
  66. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/implement-retry.md +1 -0
  67. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/implement.md +1 -0
  68. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/planner-agreement.md +1 -0
  69. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/planner-answers.md +1 -0
  70. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/review.md +3 -0
  71. modus_operandi-0.1.0/src/modus_operandi/data/prompts/adr/srp-review.md +40 -0
  72. modus_operandi-0.1.0/src/modus_operandi/data/prompts/bug-fix.md +1 -0
  73. modus_operandi-0.1.0/src/modus_operandi/data/prompts/fix-all.md +7 -0
  74. modus_operandi-0.1.0/src/modus_operandi/data/prompts/fix.md +1 -0
  75. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/bug-rereview.md +1 -0
  76. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/bug-review.md +1 -0
  77. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/comment-fix.md +1 -0
  78. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/comment-rereview.md +1 -0
  79. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/comment-review.md +1 -0
  80. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/review-rereview.md +1 -0
  81. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/review.md +1 -0
  82. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/srp-rereview.md +25 -0
  83. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/srp-review.md +42 -0
  84. modus_operandi-0.1.0/src/modus_operandi/data/prompts/review/warmup.md +12 -0
  85. modus_operandi-0.1.0/src/modus_operandi/data/prompts/srp-fix.md +1 -0
  86. modus_operandi-0.1.0/src/modus_operandi/data/prompts/task/research.md +1 -0
  87. modus_operandi-0.1.0/src/modus_operandi/data/prompts/task/study-revise.md +1 -0
  88. modus_operandi-0.1.0/src/modus_operandi/data/prompts/task/study.md +1 -0
  89. modus_operandi-0.1.0/src/modus_operandi/data/prompts/task/write-adr.md +99 -0
  90. modus_operandi-0.1.0/src/modus_operandi/data/prompts/task/write-plan.md +1 -0
  91. modus_operandi-0.1.0/src/modus_operandi/data/roles/executor.md +14 -0
  92. modus_operandi-0.1.0/src/modus_operandi/data/roles/planner.md +13 -0
  93. modus_operandi-0.1.0/src/modus_operandi/data/victory.wav +0 -0
  94. modus_operandi-0.1.0/src/modus_operandi/data/workflows/review-pipeline.yml +107 -0
  95. modus_operandi-0.1.0/src/modus_operandi/data/workflows/task-pipeline.yml +341 -0
  96. modus_operandi-0.1.0/src/modus_operandi/edit_command.py +113 -0
  97. modus_operandi-0.1.0/src/modus_operandi/editor.py +97 -0
  98. modus_operandi-0.1.0/src/modus_operandi/exceptions/__init__.py +17 -0
  99. modus_operandi-0.1.0/src/modus_operandi/exceptions/edit_requested.py +6 -0
  100. modus_operandi-0.1.0/src/modus_operandi/exceptions/help_requested.py +5 -0
  101. modus_operandi-0.1.0/src/modus_operandi/exceptions/invalid_invocation.py +6 -0
  102. modus_operandi-0.1.0/src/modus_operandi/exceptions/uninstall_requested.py +12 -0
  103. modus_operandi-0.1.0/src/modus_operandi/installer.py +74 -0
  104. modus_operandi-0.1.0/src/modus_operandi/installer_cli.py +165 -0
  105. modus_operandi-0.1.0/src/modus_operandi/paths.py +117 -0
  106. modus_operandi-0.1.0/src/modus_operandi/proc.py +25 -0
  107. modus_operandi-0.1.0/src/modus_operandi/prompt.py +12 -0
  108. modus_operandi-0.1.0/src/modus_operandi/render.py +303 -0
  109. modus_operandi-0.1.0/src/modus_operandi/tool_discovery.py +11 -0
  110. modus_operandi-0.1.0/src/modus_operandi/uninstall.py +184 -0
  111. modus_operandi-0.1.0/src/modus_operandi/verify.py +230 -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,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: modus-operandi
3
+ Version: 0.1.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
+ Automate spec-driven "planner -> executor" workflows in LLM clients (opencode
28
+ and cursor): task planning, task implementing and code review.
29
+
30
+ ## Quick start
31
+
32
+ ```shell
33
+ pip install modus-operandi
34
+ modus-operandi task "<task-description>"
35
+ ```
36
+
37
+ No configuration needed: the defaults use the free OpenCode Zen models
38
+ (`opencode/big-pickle` for planning and review, `opencode/deepseek-v4-flash-free`
39
+ for implementation), which work out of the box. To switch models or backends,
40
+ run `modus-operandi edit`.
41
+
42
+ ## Install
43
+
44
+ `modus-operandi` works right after install: the first run renders the pipeline
45
+ files (agents, scripts, workflows, prompts) into `~/.config/modus-operandi/` and
46
+ `~/.config/opencode/`, and creates `~/.config/modus-operandi/config.yml` from the
47
+ example. Updates arrive through PyPI (`pip install -U modus-operandi`); the render
48
+ is re-applied automatically on the next run.
49
+
50
+ Requirements: Python >= 3.12, and one backend CLI on PATH — `opencode`
51
+ (default) or `cursor-agent` (see `--backend cursor` below).
52
+
53
+ The dev flow (`python3 install.py` from a checkout) additionally installs a
54
+ checkout-based `modus-operandi` command into `~/.local/bin` and keeps that
55
+ directory on PATH in your existing shell rc files (`~/.bashrc`, `~/.zshrc`,
56
+ `~/.profile`), so the command is available in every newly opened terminal.
57
+
58
+ ## Usage
59
+
60
+ ### Task
61
+
62
+ If you have a workflow like this
63
+
64
+ ```
65
+ 1. Studying the motivation of the task with a planner-agent
66
+ 2. Creating an ADR
67
+ 3. Writing an implementation plan for the executor
68
+ 4. The executor-agent asking clarifying questions about the task
69
+ 5. The planner-agent answering these questions
70
+ 6. Implementing the task
71
+ 7. Reviewing for SRP violations
72
+ 8. Reviewing for bugs
73
+ 9. General review (correctness and quality)
74
+ 10. Comment review (readability)
75
+ 11. Fixing the issues found
76
+ ```
77
+
78
+ The ADR records the decision (what and why) and is published to the
79
+ `architecture/` directory at the end of the run in its final version; the
80
+ implementation plan stays internal to the executor.
81
+
82
+ then you can use this command
83
+
84
+ ```shell
85
+ modus-operandi task "<task-description>"
86
+ ```
87
+
88
+ ### Review
89
+
90
+ If you have a workflow like this
91
+
92
+ ```
93
+ 1. Reviewing the whole project for SRP violations
94
+ 2. Reviewing the whole project for bugs
95
+ 3. Comment review (readability)
96
+ 4. Fixing the issues found
97
+ ```
98
+
99
+ then you can use this command
100
+
101
+ ```shell
102
+ modus-operandi review
103
+ ```
104
+
105
+ To review only the changes between the current branch and the default branch:
106
+
107
+ ```shell
108
+ modus-operandi review --branch-diff
109
+ ```
110
+
111
+ ### Backend
112
+
113
+ By default, opencode is used. If you need the cursor backend, use the
114
+ `--backend cursor` flag:
115
+
116
+ ```shell
117
+ modus-operandi --backend cursor task "<task-description>"
118
+ ```
119
+
120
+ ### Config
121
+
122
+ ```shell
123
+ modus-operandi edit
124
+ ```
125
+
126
+ opens `~/.config/modus-operandi/config.yml` in your terminal editor. On save and
127
+ close it validates the file: a valid config is applied, an invalid one is
128
+ rolled back and the error is reported with its line number; a config left
129
+ unchanged is not re-applied.
130
+
131
+ ## Uninstall
132
+
133
+ ```shell
134
+ modus-operandi uninstall
135
+ pip uninstall modus-operandi
136
+ ```
137
+
138
+ removes the rendered pipeline files (`~/.config/modus-operandi` and the modus-operandi
139
+ files under `~/.config/opencode`) — run `pip uninstall modus-operandi` afterwards
140
+ to remove the package itself.
@@ -0,0 +1,116 @@
1
+ # Modus Operandi 🤌
2
+
3
+ Automate spec-driven "planner -> executor" workflows in LLM clients (opencode
4
+ and cursor): task planning, task implementing and code review.
5
+
6
+ ## Quick start
7
+
8
+ ```shell
9
+ pip install modus-operandi
10
+ modus-operandi task "<task-description>"
11
+ ```
12
+
13
+ No configuration needed: the defaults use the free OpenCode Zen models
14
+ (`opencode/big-pickle` for planning and review, `opencode/deepseek-v4-flash-free`
15
+ for implementation), which work out of the box. To switch models or backends,
16
+ run `modus-operandi edit`.
17
+
18
+ ## Install
19
+
20
+ `modus-operandi` works right after install: the first run renders the pipeline
21
+ files (agents, scripts, workflows, prompts) into `~/.config/modus-operandi/` and
22
+ `~/.config/opencode/`, and creates `~/.config/modus-operandi/config.yml` from the
23
+ example. Updates arrive through PyPI (`pip install -U modus-operandi`); the render
24
+ is re-applied automatically on the next run.
25
+
26
+ Requirements: Python >= 3.12, and one backend CLI on PATH — `opencode`
27
+ (default) or `cursor-agent` (see `--backend cursor` below).
28
+
29
+ The dev flow (`python3 install.py` from a checkout) additionally installs a
30
+ checkout-based `modus-operandi` command into `~/.local/bin` and keeps that
31
+ directory on PATH in your existing shell rc files (`~/.bashrc`, `~/.zshrc`,
32
+ `~/.profile`), so the command is available in every newly opened terminal.
33
+
34
+ ## Usage
35
+
36
+ ### Task
37
+
38
+ If you have a workflow like this
39
+
40
+ ```
41
+ 1. Studying the motivation of the task with a planner-agent
42
+ 2. Creating an ADR
43
+ 3. Writing an implementation plan for the executor
44
+ 4. The executor-agent asking clarifying questions about the task
45
+ 5. The planner-agent answering these questions
46
+ 6. Implementing the task
47
+ 7. Reviewing for SRP violations
48
+ 8. Reviewing for bugs
49
+ 9. General review (correctness and quality)
50
+ 10. Comment review (readability)
51
+ 11. Fixing the issues found
52
+ ```
53
+
54
+ The ADR records the decision (what and why) and is published to the
55
+ `architecture/` directory at the end of the run in its final version; the
56
+ implementation plan stays internal to the executor.
57
+
58
+ then you can use this command
59
+
60
+ ```shell
61
+ modus-operandi task "<task-description>"
62
+ ```
63
+
64
+ ### Review
65
+
66
+ If you have a workflow like this
67
+
68
+ ```
69
+ 1. Reviewing the whole project for SRP violations
70
+ 2. Reviewing the whole project for bugs
71
+ 3. Comment review (readability)
72
+ 4. Fixing the issues found
73
+ ```
74
+
75
+ then you can use this command
76
+
77
+ ```shell
78
+ modus-operandi review
79
+ ```
80
+
81
+ To review only the changes between the current branch and the default branch:
82
+
83
+ ```shell
84
+ modus-operandi review --branch-diff
85
+ ```
86
+
87
+ ### Backend
88
+
89
+ By default, opencode is used. If you need the cursor backend, use the
90
+ `--backend cursor` flag:
91
+
92
+ ```shell
93
+ modus-operandi --backend cursor task "<task-description>"
94
+ ```
95
+
96
+ ### Config
97
+
98
+ ```shell
99
+ modus-operandi edit
100
+ ```
101
+
102
+ opens `~/.config/modus-operandi/config.yml` in your terminal editor. On save and
103
+ close it validates the file: a valid config is applied, an invalid one is
104
+ rolled back and the error is reported with its line number; a config left
105
+ unchanged is not re-applied.
106
+
107
+ ## Uninstall
108
+
109
+ ```shell
110
+ modus-operandi uninstall
111
+ pip uninstall modus-operandi
112
+ ```
113
+
114
+ removes the rendered pipeline files (`~/.config/modus-operandi` and the modus-operandi
115
+ files under `~/.config/opencode`) — run `pip uninstall modus-operandi` afterwards
116
+ to remove the package itself.
@@ -0,0 +1,73 @@
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.1.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
+ "types-PyYAML==6.0.12.20260815",
46
+ ]
47
+
48
+ [tool.ruff]
49
+ target-version = "py312"
50
+ line-length = 100
51
+ extend-include = [".github/scripts/*.py"]
52
+
53
+ [tool.ruff.lint]
54
+ select = [
55
+ "E",
56
+ "F",
57
+ "I",
58
+ "W",
59
+ "B",
60
+ "UP",
61
+ "SIM",
62
+ ]
63
+
64
+ [tool.mypy]
65
+ python_version = "3.12"
66
+ strict = true
67
+ explicit_package_bases = true
68
+ files = ["."]
69
+ mypy_path = [
70
+ "src",
71
+ "src/modus_operandi/data/pipeline_scripts",
72
+ ".",
73
+ ]
@@ -0,0 +1,69 @@
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.1.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
+ # types-PyYAML does not follow semver: the version is the targeted
42
+ # PyYAML version + a typeshed calver (date) suffix, and any stub update
43
+ # can change type-checking results. Pin exactly.
44
+ "types-PyYAML==6.0.12.20260815",
45
+ ]
46
+
47
+ [tool.ruff]
48
+ target-version = "py312"
49
+ line-length = 100
50
+ lint.select = ["E", "F", "I", "W", "B", "UP", "SIM"]
51
+ # CI scripts live in the hidden .github/ tree, which ruff skips by default;
52
+ # they are first-class code and must be linted like everything else.
53
+ extend-include = [".github/scripts/*.py"]
54
+
55
+ [tool.mypy]
56
+ python_version = "3.12"
57
+ strict = true
58
+ # The standalone data scripts are not part of the modus_operandi package (no
59
+ # __init__.py under data/): with explicit package bases mypy maps them only
60
+ # through mypy_path, so their sibling imports resolve to the same module
61
+ # names the scripts use at runtime.
62
+ explicit_package_bases = true
63
+ # Everything is checked: the whole repo tree, no whitelist, no exclude.
64
+ files = ["."]
65
+ # The standalone scripts import each other from the same directory (e.g.
66
+ # save_adr.py imports adr_utils/agent_call/task_utils); point mypy at it so
67
+ # those sibling imports resolve. "src" is the explicit package base of the
68
+ # modus_operandi package.
69
+ mypy_path = ["src", "src/modus_operandi/data/pipeline_scripts", "."]
@@ -0,0 +1,24 @@
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.
8
+ """
9
+
10
+ from .paths import Paths
11
+
12
+ # The package's public surface: the install layout type and the
13
+ # user-facing install failure. (The explicit __all__ marks Paths as a
14
+ # re-export of the paths.py alias; InstallError and __version__ are
15
+ # defined in this module.)
16
+ __all__ = ["InstallError", "Paths", "__version__"]
17
+
18
+ __version__ = "0.1.0"
19
+
20
+
21
+ class InstallError(Exception):
22
+ """User-facing failure of the install/bootstrap flow."""
23
+
24
+ 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