prodc 0.4.6__tar.gz → 0.4.7__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 (68) hide show
  1. prodc-0.4.7/PKG-INFO +200 -0
  2. prodc-0.4.7/README.md +173 -0
  3. {prodc-0.4.6 → prodc-0.4.7}/examples/quickstart/needs/jordan.yaml +0 -1
  4. {prodc-0.4.6 → prodc-0.4.7}/pyproject.toml +5 -4
  5. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/cli/commands.py +4 -0
  6. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/config.py +22 -0
  7. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/loader.py +22 -0
  8. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/status.py +45 -0
  9. prodc-0.4.7/tests/test_flowsteps.py +172 -0
  10. prodc-0.4.6/PKG-INFO +0 -82
  11. prodc-0.4.6/README.md +0 -56
  12. {prodc-0.4.6 → prodc-0.4.7}/.gitignore +0 -0
  13. {prodc-0.4.6 → prodc-0.4.7}/CHANGELOG.md +0 -0
  14. {prodc-0.4.6 → prodc-0.4.7}/LICENSE +0 -0
  15. {prodc-0.4.6 → prodc-0.4.7}/examples/quickstart/features/orders.feature +0 -0
  16. {prodc-0.4.6 → prodc-0.4.7}/examples/quickstart/prodc.toml +0 -0
  17. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/__init__.py +0 -0
  18. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/adapters.py +0 -0
  19. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/agent.md +0 -0
  20. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/board.py +0 -0
  21. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/callbacks.py +0 -0
  22. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/cli/__init__.py +0 -0
  23. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/cli/app.py +0 -0
  24. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/cli/common.py +0 -0
  25. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/cli/render.py +0 -0
  26. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/cli/serialize.py +0 -0
  27. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/dedup.py +0 -0
  28. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/guide.md +0 -0
  29. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/model.py +0 -0
  30. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/py.typed +0 -0
  31. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/report.py +0 -0
  32. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/report_html.py +0 -0
  33. {prodc-0.4.6 → prodc-0.4.7}/src/prodc/writer.py +0 -0
  34. {prodc-0.4.6 → prodc-0.4.7}/tests/test_adapters.py +0 -0
  35. {prodc-0.4.6 → prodc-0.4.7}/tests/test_answer.py +0 -0
  36. {prodc-0.4.6 → prodc-0.4.7}/tests/test_callbacks.py +0 -0
  37. {prodc-0.4.6 → prodc-0.4.7}/tests/test_citation.py +0 -0
  38. {prodc-0.4.6 → prodc-0.4.7}/tests/test_claim_gating.py +0 -0
  39. {prodc-0.4.6 → prodc-0.4.7}/tests/test_cli.py +0 -0
  40. {prodc-0.4.6 → prodc-0.4.7}/tests/test_command_hook.py +0 -0
  41. {prodc-0.4.6 → prodc-0.4.7}/tests/test_contradicts.py +0 -0
  42. {prodc-0.4.6 → prodc-0.4.7}/tests/test_decisions.py +0 -0
  43. {prodc-0.4.6 → prodc-0.4.7}/tests/test_dedup.py +0 -0
  44. {prodc-0.4.6 → prodc-0.4.7}/tests/test_description.py +0 -0
  45. {prodc-0.4.6 → prodc-0.4.7}/tests/test_drafts.py +0 -0
  46. {prodc-0.4.6 → prodc-0.4.7}/tests/test_evalgate.py +0 -0
  47. {prodc-0.4.6 → prodc-0.4.7}/tests/test_external_adapter.py +0 -0
  48. {prodc-0.4.6 → prodc-0.4.7}/tests/test_gitlab_source.py +0 -0
  49. {prodc-0.4.6 → prodc-0.4.7}/tests/test_importance.py +0 -0
  50. {prodc-0.4.6 → prodc-0.4.7}/tests/test_loader.py +0 -0
  51. {prodc-0.4.6 → prodc-0.4.7}/tests/test_models.py +0 -0
  52. {prodc-0.4.6 → prodc-0.4.7}/tests/test_needref.py +0 -0
  53. {prodc-0.4.6 → prodc-0.4.7}/tests/test_principles.py +0 -0
  54. {prodc-0.4.6 → prodc-0.4.7}/tests/test_relink.py +0 -0
  55. {prodc-0.4.6 → prodc-0.4.7}/tests/test_report.py +0 -0
  56. {prodc-0.4.6 → prodc-0.4.7}/tests/test_report_html.py +0 -0
  57. {prodc-0.4.6 → prodc-0.4.7}/tests/test_review.py +0 -0
  58. {prodc-0.4.6 → prodc-0.4.7}/tests/test_roles.py +0 -0
  59. {prodc-0.4.6 → prodc-0.4.7}/tests/test_source_role.py +0 -0
  60. {prodc-0.4.6 → prodc-0.4.7}/tests/test_sources.py +0 -0
  61. {prodc-0.4.6 → prodc-0.4.7}/tests/test_staleness.py +0 -0
  62. {prodc-0.4.6 → prodc-0.4.7}/tests/test_status.py +0 -0
  63. {prodc-0.4.6 → prodc-0.4.7}/tests/test_suggest.py +0 -0
  64. {prodc-0.4.6 → prodc-0.4.7}/tests/test_variants.py +0 -0
  65. {prodc-0.4.6 → prodc-0.4.7}/tests/test_who_variants.py +0 -0
  66. {prodc-0.4.6 → prodc-0.4.7}/tests/test_withdrawn.py +0 -0
  67. {prodc-0.4.6 → prodc-0.4.7}/tests/test_writer.py +0 -0
  68. {prodc-0.4.6 → prodc-0.4.7}/tests/test_writer_comments.py +0 -0
prodc-0.4.7/PKG-INFO ADDED
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.5
2
+ Name: prodc
3
+ Version: 0.4.7
4
+ Summary: Product management as code: roles, flows, and features traced to evidence.
5
+ Project-URL: Homepage, https://gitlab.com/cardona-oss/prodc
6
+ Project-URL: Repository, https://gitlab.com/cardona-oss/prodc
7
+ Project-URL: Changelog, https://gitlab.com/cardona-oss/prodc/-/blob/main/CHANGELOG.md
8
+ Project-URL: Documentation, https://cardona-oss.gitlab.io/prodc/
9
+ Author-email: Jorge Cardona <jorgeecardona@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Classifier: Topic :: Software Development :: Testing
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: pyyaml>=6
22
+ Provides-Extra: docs
23
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
24
+ Requires-Dist: mkdocs>=1.6; extra == 'docs'
25
+ Requires-Dist: mkdocstrings[python]>=0.27; extra == 'docs'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # prodc
29
+
30
+ [![PyPI](https://img.shields.io/pypi/v/prodc)](https://pypi.org/project/prodc/)
31
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitlab.com/cardona-oss/prodc/-/blob/main/LICENSE)
32
+
33
+ Product management as code: the *needs* your product exists to meet, each traced to the evidence a real user gave for it and the test that proves it is delivered. For teams that keep product work in the repo, next to the code, and want a tool to check it holds together.
34
+
35
+ prodc reads plain YAML files and your test suite and shows you the gaps. It does not call a model, host a UI, or replace your tracker. Two rules carry it:
36
+
37
+ 1. **A need with no evidence is an opinion.** Write down who asked — an interview quote, a ticket, an email — or prodc flags the need as an opinion, because that is what it is until someone outside the room has asked.
38
+ 2. **"Done" is a claim your tests back.** You never mark a need delivered. You attach a *proof* (a locator into your test suite), and `prodc status` resolves it: DELIVERED, PENDING, HOLE (an honest gap), or BROKEN (points at nothing). The state is read from the repo, not remembered from a standup.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install prodc # or: uv add prodc
44
+ ```
45
+
46
+ Python 3.12+. The only runtime dependency is PyYAML.
47
+
48
+ ## Quickstart
49
+
50
+ Three files. The first tells prodc where to look, the second holds what people need, the third is a test you have probably already written.
51
+
52
+ `prodc.toml`
53
+
54
+ ```toml
55
+ [prodc]
56
+ needs = ["needs/*.yaml"]
57
+
58
+ [proofs.web]
59
+ kind = "gherkin"
60
+ paths = ["features/*.feature"]
61
+ delivered = ["@implemented"]
62
+ pending = ["@pending"]
63
+ ```
64
+
65
+ `needs/jordan.yaml`
66
+
67
+ ```yaml
68
+ who: jordan
69
+ needs:
70
+ - id: N-1
71
+ text: "see which orders are past due at a glance"
72
+ must: true
73
+ sources:
74
+ - kind: primary
75
+ who: Jordan
76
+ date: 2026-09-20
77
+ quote: "I lose the past-due ones in the list"
78
+ proofs:
79
+ - "web:orders.feature#Past due orders are highlighted"
80
+
81
+ - id: N-2
82
+ text: "export the day's delivery run sheet"
83
+ sources:
84
+ - kind: secondary
85
+ locator: issues.md#N-2
86
+ date: 2026-09-21
87
+
88
+ - id: N-3
89
+ text: "implement a settings page"
90
+ ```
91
+
92
+ `features/orders.feature`
93
+
94
+ ```gherkin
95
+ Feature: Orders board
96
+
97
+ @implemented
98
+ Scenario: Past due orders are highlighted
99
+ Given an order whose due date has passed
100
+ When I open the orders board
101
+ Then the order is highlighted as past due
102
+ ```
103
+
104
+ Run the board (output trimmed; the real one also carries a legend and phrasing hints):
105
+
106
+ ```text
107
+ $ prodc status
108
+ gaps: 2 HOLE · 1 opinion coverage 1/3 (33%)
109
+
110
+ jordan 1 DELIVERED 2 HOLE
111
+ provenance: 1 primary · 1 secondary · 0 assumption · 1 opinion
112
+
113
+ N-2 HOLE export the day's delivery run sheet secondary
114
+ N-3 HOLE implement a settings page opinion
115
+ ⚠ text may be a solution in disguise (starts with 'implement')
116
+ !! N-1 DELIVERED* 1/1 see which orders are past due at a glance primary:Jordan
117
+ ⚠ DELIVERED on an unattested proof link — warrant not reviewed
118
+ ```
119
+
120
+ N-1 is DELIVERED because a tagged scenario proves it (the `*` means declared by tag, no test results loaded yet). N-2 has evidence but no proof: a HOLE. N-3 has neither: an opinion.
121
+
122
+ Now close the loop from the command line. Each write command rewrites one YAML file, re-validates the whole model, and prints the diff.
123
+
124
+ ```bash
125
+ # trace the opinion to evidence
126
+ prodc capture N-3 --kind primary --who Jordan --date 2026-10-01 \
127
+ --quote "I keep changing the printer in three places"
128
+
129
+ # add the scenario to your suite, tagged @pending until it is green:
130
+ # @pending
131
+ # Scenario: Export the run sheet
132
+ # then point the need at it:
133
+ prodc set N-2 proofs+="web:orders.feature#Export the run sheet"
134
+
135
+ prodc status # gaps: 1 PENDING · 1 HOLE — the scenario exists, so N-2 is PENDING, not BROKEN
136
+ # (skip writing the scenario and the proof points at nothing: BROKEN, lint exits 1)
137
+ prodc lint # CI gate: exit 1 on a BROKEN proof or a must-have with no evidence
138
+ ```
139
+
140
+ Change a scenario title without updating the need and the proof turns BROKEN; `prodc lint` exits 1. That is the whole idea: the board cannot drift from the code.
141
+
142
+ ## Why prodc
143
+
144
+ **Versus Jira, Linear, and other trackers.** A tracker records work items and a status someone set by hand. prodc records *needs* and computes their status from the tests. It has no assignees, sprints, comments, or notifications, and it is not trying to. Keep the tracker for the work; keep prodc for the question "did a real user ask for this, and does a test prove it?" The two connect at the edges: a need's source can be a ticket locator (checked read-only against GitLab with `[sources.<name>] kind = "gitlab"`), a proof can be resolved by any script you point at a tracker (`kind = "command"`), and `prodc sync` fires a hook when a need changes state.
145
+
146
+ **Versus a PRD in Notion or a wiki.** A document says what should be true; nothing checks it later. prodc's files are validated on every load (ids, cross-references, source hygiene), linted in CI, and reviewed in merge requests like code. "Is this done?" is answered by `prodc show N-1`, not by finding the right page.
147
+
148
+ **Versus plain YAML in the repo.** You could keep this structure by hand. prodc adds the schema, the proof resolution against real test suites, the evidence ladder, the lint, and the write-back commands that keep the files consistent.
149
+
150
+ **What it does not do.** It does not run your tests, call an LLM, or ping a tracker on its own. It does not model the full discovery loop (opportunity-solution-tree restructuring, parallel solution testing); it records needs, the evidence under them, and the decisions taken on them.
151
+
152
+ ## Key features
153
+
154
+ - **Evidence ladder.** A source is `primary` (the user's own words), `secondary` (a report, a
155
+ ticket, an internal doc), or `assumption` (a guess). With neither primary nor secondary, the need
156
+ is an opinion: `lint` fails a must-have opinion, `status --strict` fails on any.
157
+ - **Proofs resolve against the repo.** Adapters for Gherkin scenarios and tags, pytest node ids,
158
+ file existence, human sign-off, prototypes, citations, eval metric gates, external results files,
159
+ and a generic `command` hook. Feed it cucumber-json or junit XML and declared states become real.
160
+ - **Five states, honest semantics.** DELIVERED, PENDING, HOLE, BROKEN, UNRESOLVED. A HOLE is never
161
+ a failure; BROKEN always is; a proof in an absent sibling repo is UNRESOLVED, not BROKEN.
162
+ - **Flows.** A need with `children` rolls them up, with `seam` proofs for the behaviour between
163
+ steps. Any BROKEN child breaks the flow; a flow is DELIVERED only when every child and seam is
164
+ green.
165
+ - **The roll-up is machine-checked.** `tests/test_models.py` builds a
166
+ [musil](https://pypi.org/project/musil/) model of the lifecycle and explores every reachable
167
+ state against the real resolver, so the documented invariants cannot drift from the code.
168
+ - **Write-back that respects your files.** `set`, `capture`, `accept`, `attest`, `withdraw`, `add`
169
+ splice one need's block back into the original YAML, keeping other comments. All take `--dry-run`
170
+ and `--expect-sha`.
171
+ - **Boards for humans.** `status`, `show`, `brief`, `plan`, `report --format html` (a self-contained
172
+ interactive viewer), `portfolio`, and `diff` against a snapshot. The read commands (`status`,
173
+ `lint`, `show`, `brief`, `plan`, `diff`, `portfolio`, `dupes`, `recurrence`, `suggest`, `review`)
174
+ take `--json`; exit codes are fixed (0 ok, 1 gate failed, 2 config or load error).
175
+
176
+ prodc also models goals, releases, roles, principles, decisions, attestations, withdrawals and
177
+ settled answers, and ships hygiene commands (`dupes`, `recurrence`, `suggest`); the
178
+ [docs](https://cardona-oss.gitlab.io/prodc/) have the full surface.
179
+
180
+ ## Use it with a coding agent
181
+
182
+ prodc ships a playbook that teaches an agent to act as the project's product manager: read the board, capture evidence with honest provenance, find proofs, gate before a handoff. An agent may record `assumption` or `secondary` sources; `primary` is reserved for a real user's own words.
183
+
184
+ ```bash
185
+ prodc agent >> AGENTS.md # or: prodc agent --write AGENTS.md
186
+ prodc guide # the short human onboarding guide
187
+ ```
188
+
189
+ ## Links
190
+
191
+ - Repository: <https://gitlab.com/cardona-oss/prodc>
192
+ - PyPI: <https://pypi.org/project/prodc/>
193
+ - Docs: <https://cardona-oss.gitlab.io/prodc/> — [Quickstart](https://cardona-oss.gitlab.io/prodc/quickstart/), [Commands](https://cardona-oss.gitlab.io/prodc/commands/), [Model](https://cardona-oss.gitlab.io/prodc/model/), [Config](https://cardona-oss.gitlab.io/prodc/config/), [Lifecycle](https://cardona-oss.gitlab.io/prodc/lifecycle/)
194
+ - Changelog: [CHANGELOG.md](https://gitlab.com/cardona-oss/prodc/-/blob/main/CHANGELOG.md)
195
+
196
+ `AGENTS.md` holds build and test instructions for coding agents working on prodc itself.
197
+
198
+ ## License
199
+
200
+ MIT. See [LICENSE](https://gitlab.com/cardona-oss/prodc/-/blob/main/LICENSE).
prodc-0.4.7/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # prodc
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/prodc)](https://pypi.org/project/prodc/)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitlab.com/cardona-oss/prodc/-/blob/main/LICENSE)
5
+
6
+ Product management as code: the *needs* your product exists to meet, each traced to the evidence a real user gave for it and the test that proves it is delivered. For teams that keep product work in the repo, next to the code, and want a tool to check it holds together.
7
+
8
+ prodc reads plain YAML files and your test suite and shows you the gaps. It does not call a model, host a UI, or replace your tracker. Two rules carry it:
9
+
10
+ 1. **A need with no evidence is an opinion.** Write down who asked — an interview quote, a ticket, an email — or prodc flags the need as an opinion, because that is what it is until someone outside the room has asked.
11
+ 2. **"Done" is a claim your tests back.** You never mark a need delivered. You attach a *proof* (a locator into your test suite), and `prodc status` resolves it: DELIVERED, PENDING, HOLE (an honest gap), or BROKEN (points at nothing). The state is read from the repo, not remembered from a standup.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install prodc # or: uv add prodc
17
+ ```
18
+
19
+ Python 3.12+. The only runtime dependency is PyYAML.
20
+
21
+ ## Quickstart
22
+
23
+ Three files. The first tells prodc where to look, the second holds what people need, the third is a test you have probably already written.
24
+
25
+ `prodc.toml`
26
+
27
+ ```toml
28
+ [prodc]
29
+ needs = ["needs/*.yaml"]
30
+
31
+ [proofs.web]
32
+ kind = "gherkin"
33
+ paths = ["features/*.feature"]
34
+ delivered = ["@implemented"]
35
+ pending = ["@pending"]
36
+ ```
37
+
38
+ `needs/jordan.yaml`
39
+
40
+ ```yaml
41
+ who: jordan
42
+ needs:
43
+ - id: N-1
44
+ text: "see which orders are past due at a glance"
45
+ must: true
46
+ sources:
47
+ - kind: primary
48
+ who: Jordan
49
+ date: 2026-09-20
50
+ quote: "I lose the past-due ones in the list"
51
+ proofs:
52
+ - "web:orders.feature#Past due orders are highlighted"
53
+
54
+ - id: N-2
55
+ text: "export the day's delivery run sheet"
56
+ sources:
57
+ - kind: secondary
58
+ locator: issues.md#N-2
59
+ date: 2026-09-21
60
+
61
+ - id: N-3
62
+ text: "implement a settings page"
63
+ ```
64
+
65
+ `features/orders.feature`
66
+
67
+ ```gherkin
68
+ Feature: Orders board
69
+
70
+ @implemented
71
+ Scenario: Past due orders are highlighted
72
+ Given an order whose due date has passed
73
+ When I open the orders board
74
+ Then the order is highlighted as past due
75
+ ```
76
+
77
+ Run the board (output trimmed; the real one also carries a legend and phrasing hints):
78
+
79
+ ```text
80
+ $ prodc status
81
+ gaps: 2 HOLE · 1 opinion coverage 1/3 (33%)
82
+
83
+ jordan 1 DELIVERED 2 HOLE
84
+ provenance: 1 primary · 1 secondary · 0 assumption · 1 opinion
85
+
86
+ N-2 HOLE export the day's delivery run sheet secondary
87
+ N-3 HOLE implement a settings page opinion
88
+ ⚠ text may be a solution in disguise (starts with 'implement')
89
+ !! N-1 DELIVERED* 1/1 see which orders are past due at a glance primary:Jordan
90
+ ⚠ DELIVERED on an unattested proof link — warrant not reviewed
91
+ ```
92
+
93
+ N-1 is DELIVERED because a tagged scenario proves it (the `*` means declared by tag, no test results loaded yet). N-2 has evidence but no proof: a HOLE. N-3 has neither: an opinion.
94
+
95
+ Now close the loop from the command line. Each write command rewrites one YAML file, re-validates the whole model, and prints the diff.
96
+
97
+ ```bash
98
+ # trace the opinion to evidence
99
+ prodc capture N-3 --kind primary --who Jordan --date 2026-10-01 \
100
+ --quote "I keep changing the printer in three places"
101
+
102
+ # add the scenario to your suite, tagged @pending until it is green:
103
+ # @pending
104
+ # Scenario: Export the run sheet
105
+ # then point the need at it:
106
+ prodc set N-2 proofs+="web:orders.feature#Export the run sheet"
107
+
108
+ prodc status # gaps: 1 PENDING · 1 HOLE — the scenario exists, so N-2 is PENDING, not BROKEN
109
+ # (skip writing the scenario and the proof points at nothing: BROKEN, lint exits 1)
110
+ prodc lint # CI gate: exit 1 on a BROKEN proof or a must-have with no evidence
111
+ ```
112
+
113
+ Change a scenario title without updating the need and the proof turns BROKEN; `prodc lint` exits 1. That is the whole idea: the board cannot drift from the code.
114
+
115
+ ## Why prodc
116
+
117
+ **Versus Jira, Linear, and other trackers.** A tracker records work items and a status someone set by hand. prodc records *needs* and computes their status from the tests. It has no assignees, sprints, comments, or notifications, and it is not trying to. Keep the tracker for the work; keep prodc for the question "did a real user ask for this, and does a test prove it?" The two connect at the edges: a need's source can be a ticket locator (checked read-only against GitLab with `[sources.<name>] kind = "gitlab"`), a proof can be resolved by any script you point at a tracker (`kind = "command"`), and `prodc sync` fires a hook when a need changes state.
118
+
119
+ **Versus a PRD in Notion or a wiki.** A document says what should be true; nothing checks it later. prodc's files are validated on every load (ids, cross-references, source hygiene), linted in CI, and reviewed in merge requests like code. "Is this done?" is answered by `prodc show N-1`, not by finding the right page.
120
+
121
+ **Versus plain YAML in the repo.** You could keep this structure by hand. prodc adds the schema, the proof resolution against real test suites, the evidence ladder, the lint, and the write-back commands that keep the files consistent.
122
+
123
+ **What it does not do.** It does not run your tests, call an LLM, or ping a tracker on its own. It does not model the full discovery loop (opportunity-solution-tree restructuring, parallel solution testing); it records needs, the evidence under them, and the decisions taken on them.
124
+
125
+ ## Key features
126
+
127
+ - **Evidence ladder.** A source is `primary` (the user's own words), `secondary` (a report, a
128
+ ticket, an internal doc), or `assumption` (a guess). With neither primary nor secondary, the need
129
+ is an opinion: `lint` fails a must-have opinion, `status --strict` fails on any.
130
+ - **Proofs resolve against the repo.** Adapters for Gherkin scenarios and tags, pytest node ids,
131
+ file existence, human sign-off, prototypes, citations, eval metric gates, external results files,
132
+ and a generic `command` hook. Feed it cucumber-json or junit XML and declared states become real.
133
+ - **Five states, honest semantics.** DELIVERED, PENDING, HOLE, BROKEN, UNRESOLVED. A HOLE is never
134
+ a failure; BROKEN always is; a proof in an absent sibling repo is UNRESOLVED, not BROKEN.
135
+ - **Flows.** A need with `children` rolls them up, with `seam` proofs for the behaviour between
136
+ steps. Any BROKEN child breaks the flow; a flow is DELIVERED only when every child and seam is
137
+ green.
138
+ - **The roll-up is machine-checked.** `tests/test_models.py` builds a
139
+ [musil](https://pypi.org/project/musil/) model of the lifecycle and explores every reachable
140
+ state against the real resolver, so the documented invariants cannot drift from the code.
141
+ - **Write-back that respects your files.** `set`, `capture`, `accept`, `attest`, `withdraw`, `add`
142
+ splice one need's block back into the original YAML, keeping other comments. All take `--dry-run`
143
+ and `--expect-sha`.
144
+ - **Boards for humans.** `status`, `show`, `brief`, `plan`, `report --format html` (a self-contained
145
+ interactive viewer), `portfolio`, and `diff` against a snapshot. The read commands (`status`,
146
+ `lint`, `show`, `brief`, `plan`, `diff`, `portfolio`, `dupes`, `recurrence`, `suggest`, `review`)
147
+ take `--json`; exit codes are fixed (0 ok, 1 gate failed, 2 config or load error).
148
+
149
+ prodc also models goals, releases, roles, principles, decisions, attestations, withdrawals and
150
+ settled answers, and ships hygiene commands (`dupes`, `recurrence`, `suggest`); the
151
+ [docs](https://cardona-oss.gitlab.io/prodc/) have the full surface.
152
+
153
+ ## Use it with a coding agent
154
+
155
+ prodc ships a playbook that teaches an agent to act as the project's product manager: read the board, capture evidence with honest provenance, find proofs, gate before a handoff. An agent may record `assumption` or `secondary` sources; `primary` is reserved for a real user's own words.
156
+
157
+ ```bash
158
+ prodc agent >> AGENTS.md # or: prodc agent --write AGENTS.md
159
+ prodc guide # the short human onboarding guide
160
+ ```
161
+
162
+ ## Links
163
+
164
+ - Repository: <https://gitlab.com/cardona-oss/prodc>
165
+ - PyPI: <https://pypi.org/project/prodc/>
166
+ - Docs: <https://cardona-oss.gitlab.io/prodc/> — [Quickstart](https://cardona-oss.gitlab.io/prodc/quickstart/), [Commands](https://cardona-oss.gitlab.io/prodc/commands/), [Model](https://cardona-oss.gitlab.io/prodc/model/), [Config](https://cardona-oss.gitlab.io/prodc/config/), [Lifecycle](https://cardona-oss.gitlab.io/prodc/lifecycle/)
167
+ - Changelog: [CHANGELOG.md](https://gitlab.com/cardona-oss/prodc/-/blob/main/CHANGELOG.md)
168
+
169
+ `AGENTS.md` holds build and test instructions for coding agents working on prodc itself.
170
+
171
+ ## License
172
+
173
+ MIT. See [LICENSE](https://gitlab.com/cardona-oss/prodc/-/blob/main/LICENSE).
@@ -1,5 +1,4 @@
1
1
  who: jordan
2
- label: "design partner"
3
2
  needs:
4
3
  - id: N-1
5
4
  text: "see which orders are past due at a glance"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "prodc"
3
- version = "0.4.6"
3
+ version = "0.4.7"
4
4
  description = "Product management as code: roles, flows, and features traced to evidence."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -32,9 +32,10 @@ docs = [
32
32
  ]
33
33
 
34
34
  [project.urls]
35
- Homepage = "https://gitlab.com/jorgeecardona/prodc"
36
- Repository = "https://gitlab.com/jorgeecardona/prodc"
37
- Changelog = "https://gitlab.com/jorgeecardona/prodc/-/blob/main/CHANGELOG.md"
35
+ Homepage = "https://gitlab.com/cardona-oss/prodc"
36
+ Repository = "https://gitlab.com/cardona-oss/prodc"
37
+ Changelog = "https://gitlab.com/cardona-oss/prodc/-/blob/main/CHANGELOG.md"
38
+ Documentation = "https://cardona-oss.gitlab.io/prodc/"
38
39
 
39
40
  [build-system]
40
41
  requires = ["hatchling"]
@@ -39,6 +39,7 @@ from ..status import (
39
39
  contradictions,
40
40
  decisions_for,
41
41
  evidence_age_days,
42
+ flow_step_warnings,
42
43
  importance_revisit_due,
43
44
  orphaned_decisions,
44
45
  principle_report,
@@ -298,6 +299,9 @@ def cmd_lint(args: argparse.Namespace) -> int:
298
299
  | {n.client for n in needs if n.client is not None}
299
300
  )
300
301
  warnings += tag_warnings(needs, structural_ids, cfg.duplicate_threshold)
302
+ # Flow step hygiene (#90): once [flows].steps declares a vocabulary, grade each journey's
303
+ # shape — never-terminal, out-of-order, mixed bare/stepped. Silent when no vocab is declared.
304
+ warnings += flow_step_warnings(needs, cfg.flow_steps)
301
305
  if args.json:
302
306
  print(
303
307
  json.dumps(
@@ -120,6 +120,16 @@ class Config:
120
120
  callbacks: tuple[Callback, ...] = () # [[callbacks.hooks]] — lifecycle callbacks (#71)
121
121
  callback_state_file: str = ".prodc/state.json" # [callbacks].state_file — the state ledger
122
122
  principle_scopes: tuple[str, ...] = ("product",) # [principles].scopes — allowed scopes (#88)
123
+ flow_steps: tuple[str, ...] = () # [flows].steps — ordered step vocab; () = undeclared (#90)
124
+
125
+ @property
126
+ def terminal_step(self) -> str | None:
127
+ """The step that ends a journey: the last declared step, or None when no vocab (#90).
128
+
129
+ The single definition of 'terminal' — status/lint read it here, never recompute
130
+ ``flow_steps[-1]`` elsewhere. Order in ``[flows].steps`` *is* the journey.
131
+ """
132
+ return self.flow_steps[-1] if self.flow_steps else None
123
133
 
124
134
  def importance_label(self, level: int | None) -> str | None:
125
135
  """The declared label for an importance level, or None if unset/undeclared (#59)."""
@@ -312,6 +322,17 @@ class Config:
312
322
  else:
313
323
  principle_scopes = ("product",)
314
324
 
325
+ flows_tbl = _as_dict(raw.get("flows", {}), "[flows]")
326
+ if "steps" in flows_tbl:
327
+ steps = _as_str_list(flows_tbl["steps"], "[flows].steps")
328
+ if not steps:
329
+ raise ConfigError("[flows].steps must list at least one step")
330
+ if len(set(steps)) != len(steps):
331
+ raise ConfigError("[flows].steps must not repeat a step (order is its rank)")
332
+ flow_steps = tuple(steps)
333
+ else:
334
+ flow_steps = ()
335
+
315
336
  return cls(
316
337
  root=root,
317
338
  need_globs=need_globs,
@@ -326,4 +347,5 @@ class Config:
326
347
  callbacks=callbacks,
327
348
  callback_state_file=callback_state_file,
328
349
  principle_scopes=principle_scopes,
350
+ flow_steps=flow_steps,
329
351
  )
@@ -554,6 +554,7 @@ def load(config: Config) -> Model:
554
554
  _check_source_roles(acc.needs, acc.principles, {p.id for p in acc.roles})
555
555
  _check_decisions(acc.decisions, {n.id for n in acc.needs}, {p.id for p in acc.principles})
556
556
  _check_principle_scopes(acc.principles, config.principle_scopes)
557
+ _check_flow_steps(acc.needs, config.flow_steps)
557
558
  acc.needs = _symmetrize_contradicts(acc.needs)
558
559
  return Model(
559
560
  needs=tuple(acc.needs),
@@ -618,6 +619,27 @@ def _check_principle_scopes(principles: list[Principle], allowed: tuple[str, ...
618
619
  )
619
620
 
620
621
 
622
+ def _check_flow_steps(needs: list[Need], allowed: tuple[str, ...]) -> None:
623
+ """Every flow edge's ``step`` must be one declared in ``[flows].steps`` (#90).
624
+
625
+ Membership is integrity, not judgment — a mis-spelled step (``conlcude``) is a typo, the
626
+ same severity as an undeclared principle scope, so it is a hard load error. Because the
627
+ writer re-loads and reverts on ``LoaderError``, this also refuses to *write* a bad step.
628
+ Skipped entirely when no vocabulary is declared: a bare step stays free text, as it was
629
+ before this. Order and completeness are the lint's job, not the loader's.
630
+ """
631
+ if not allowed:
632
+ return
633
+ allowed_set = set(allowed)
634
+ for need in needs:
635
+ for c in need.children:
636
+ if c.step is not None and c.step not in allowed_set:
637
+ raise LoaderError(
638
+ f"need {need.id!r}: child {c.id!r} step {c.step!r} is not declared in "
639
+ f"[flows].steps ({', '.join(allowed)})"
640
+ )
641
+
642
+
621
643
  def _check_withdrawal_of(needs: list[Need]) -> None:
622
644
  """A withdrawal's ``of`` must name a known need (never itself); required when kind is duplicate.
623
645
 
@@ -911,6 +911,51 @@ def flow_orphan_warnings(needs: list[Need]) -> set[str]:
911
911
  return {n.id for n in needs if not n.children and n.id not in in_a_flow}
912
912
 
913
913
 
914
+ def flow_step_warnings(needs: list[Need], steps: tuple[str, ...]) -> list[tuple[str, str]]:
915
+ """Judgment-level hygiene on a flow's steps, once a ``[flows].steps`` vocabulary exists (#90).
916
+
917
+ Opt-in: silent when no vocabulary is declared — a board with no steps has no journey to
918
+ grade. Membership (a step word that isn't in the vocab) is the loader's hard error; this
919
+ catches the *shape* of the journey, keyed by flow id, as warnings:
920
+
921
+ - **never terminal**: a flow with stepped edges but none carrying the terminal step — the
922
+ journey never ends (Patton: the story must end). The message names the configured word.
923
+ - **out of order**: stepped edges whose rank decreases along ``children`` — the map runs
924
+ backwards. Warned once per flow.
925
+ - **mixed bare/stepped**: a flow where some edges carry a step and some don't — a use case
926
+ with a hole, which would otherwise pass clean.
927
+
928
+ A flow with zero stepped edges is exempt: it's a plain flow, not a use case (#90), and
929
+ warning it would punish every existing board the moment a vocabulary is declared.
930
+ """
931
+ if not steps:
932
+ return []
933
+ terminal = steps[-1]
934
+ rank = {s: i for i, s in enumerate(steps)}
935
+ out: list[tuple[str, str]] = []
936
+ for flow in needs:
937
+ if not flow.children:
938
+ continue
939
+ stepped = [c for c in flow.children if c.step is not None]
940
+ if not stepped:
941
+ continue # a plain flow, not a use case — exempt
942
+ if len(stepped) != len(flow.children):
943
+ bare = next(c.id for c in flow.children if c.step is None)
944
+ out.append((flow.id, f"edge {bare!r} has no step while siblings do — a use case with a hole"))
945
+ if not any(c.step == terminal for c in stepped):
946
+ out.append(
947
+ (
948
+ flow.id,
949
+ f"no edge carries the terminal step {terminal!r} — the journey never ends; "
950
+ "add it or drop the steps",
951
+ )
952
+ )
953
+ ranks = [rank[c.step] for c in stepped if c.step in rank]
954
+ if any(b < a for a, b in pairwise(ranks)):
955
+ out.append((flow.id, "steps run out of order — reorder children or re-step the edges"))
956
+ return out
957
+
958
+
914
959
  def shared_proof_warnings(results: list[NeedResult]) -> dict[str, str]:
915
960
  """Flag a need whose *every* proof is also claimed by another need (#45).
916
961
 
@@ -0,0 +1,172 @@
1
+ """Flow step vocabulary (#90 follow-up): [flows].steps config, the load-time membership
2
+ error, and the flow_step_warnings hygiene lint (never-terminal / out-of-order / mixed)."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ from prodc.config import Config, ConfigError
11
+ from prodc.loader import LoaderError, load
12
+ from prodc.model import Child, Need
13
+ from prodc.status import flow_step_warnings
14
+
15
+ _TOML = """
16
+ [prodc]
17
+ needs = ["needs/*.yaml"]
18
+
19
+ [flows]
20
+ steps = ["trigger", "act", "conclude"]
21
+ """
22
+
23
+
24
+ def _project(tmp_path: Path, toml: str = _TOML, needs: str = "needs: []\n") -> Path:
25
+ (tmp_path / "needs").mkdir(parents=True, exist_ok=True)
26
+ (tmp_path / "prodc.toml").write_text(toml, encoding="utf-8")
27
+ (tmp_path / "needs" / "a.yaml").write_text(needs, encoding="utf-8")
28
+ return tmp_path / "prodc.toml"
29
+
30
+
31
+ # --- config (#90) -----------------------------------------------------------
32
+
33
+
34
+ def test_flow_steps_default_is_empty_and_terminal_none(tmp_path: Path) -> None:
35
+ cfg = Config.load(_project(tmp_path, toml="[prodc]\nneeds = [\"needs/*.yaml\"]\n"))
36
+ assert cfg.flow_steps == ()
37
+ assert cfg.terminal_step is None # no vocab → nothing is terminal
38
+
39
+
40
+ def test_flow_steps_parsed_ordered_and_terminal_is_last(tmp_path: Path) -> None:
41
+ cfg = Config.load(_project(tmp_path))
42
+ assert cfg.flow_steps == ("trigger", "act", "conclude")
43
+ assert cfg.terminal_step == "conclude" # last entry, the single definition of terminal
44
+
45
+
46
+ def test_flow_steps_empty_list_rejected(tmp_path: Path) -> None:
47
+ toml = "[prodc]\nneeds = [\"needs/*.yaml\"]\n\n[flows]\nsteps = []\n"
48
+ with pytest.raises(ConfigError, match=r"\[flows\].steps must list at least one step"):
49
+ Config.load(_project(tmp_path, toml=toml))
50
+
51
+
52
+ def test_flow_steps_duplicate_rejected(tmp_path: Path) -> None:
53
+ toml = "[prodc]\nneeds = [\"needs/*.yaml\"]\n\n[flows]\nsteps = [\"act\", \"act\"]\n"
54
+ with pytest.raises(ConfigError, match="must not repeat a step"):
55
+ Config.load(_project(tmp_path, toml=toml))
56
+
57
+
58
+ # --- loader membership = hard error (#90) -----------------------------------
59
+
60
+
61
+ def test_undeclared_step_is_load_error(tmp_path: Path) -> None:
62
+ needs = """
63
+ needs:
64
+ - id: F-1
65
+ text: a flow
66
+ children: [{id: US-1, step: conlcude}]
67
+ - id: US-1
68
+ text: a step
69
+ """
70
+ with pytest.raises(LoaderError, match="step 'conlcude' is not declared in"):
71
+ load(Config.load(_project(tmp_path, needs=needs)))
72
+
73
+
74
+ def test_declared_step_loads(tmp_path: Path) -> None:
75
+ needs = """
76
+ needs:
77
+ - id: F-1
78
+ text: a flow
79
+ children: [{id: US-1, step: trigger}, {id: US-2, step: conclude}]
80
+ - id: US-1
81
+ text: a
82
+ - id: US-2
83
+ text: b
84
+ """
85
+ model = load(Config.load(_project(tmp_path, needs=needs)))
86
+ flow = next(n for n in model.needs if n.id == "F-1")
87
+ assert [c.step for c in flow.children] == ["trigger", "conclude"]
88
+
89
+
90
+ def test_no_vocab_lets_any_step_pass(tmp_path: Path) -> None:
91
+ toml = "[prodc]\nneeds = [\"needs/*.yaml\"]\n"
92
+ needs = """
93
+ needs:
94
+ - id: F-1
95
+ text: a flow
96
+ children: [{id: US-1, step: whatever}]
97
+ - id: US-1
98
+ text: a
99
+ """
100
+ model = load(Config.load(_project(tmp_path, toml=toml, needs=needs)))
101
+ assert model.needs[0].children[0].step == "whatever" # free text, unchecked
102
+
103
+
104
+ # --- lint: shape of the journey (#90) ---------------------------------------
105
+
106
+ _STEPS = ("trigger", "act", "conclude")
107
+
108
+
109
+ def test_lint_silent_without_vocabulary() -> None:
110
+ needs = [Need(id="F-1", text="f", children=(Child("A", "trigger"),)), Need(id="A", text="a")]
111
+ assert flow_step_warnings(needs, ()) == []
112
+
113
+
114
+ def test_lint_exempts_flow_with_no_stepped_edges() -> None:
115
+ needs = [Need(id="F-1", text="f", children=(Child("A"), Child("B")))]
116
+ assert flow_step_warnings(needs, _STEPS) == [] # a plain flow, not a use case
117
+
118
+
119
+ def test_lint_warns_when_no_terminal_step() -> None:
120
+ needs = [Need(id="F-1", text="f", children=(Child("A", "trigger"), Child("B", "act")))]
121
+ warns = flow_step_warnings(needs, _STEPS)
122
+ assert any(nid == "F-1" and "terminal step 'conclude'" in m for nid, m in warns)
123
+
124
+
125
+ def test_lint_terminal_step_names_the_configured_word() -> None:
126
+ needs = [Need(id="F-1", text="f", children=(Child("A", "start"),))]
127
+ warns = flow_step_warnings(needs, ("start", "done"))
128
+ assert any("terminal step 'done'" in m for _, m in warns) # not hardcoded "conclude"
129
+
130
+
131
+ def test_lint_warns_on_out_of_order_steps() -> None:
132
+ needs = [
133
+ Need(id="F-1", text="f", children=(Child("A", "act"), Child("B", "trigger"), Child("C", "conclude")))
134
+ ]
135
+ warns = flow_step_warnings(needs, _STEPS)
136
+ assert any("out of order" in m for _, m in warns)
137
+
138
+
139
+ def test_lint_warns_on_mixed_bare_and_stepped() -> None:
140
+ needs = [
141
+ Need(id="F-1", text="f", children=(Child("A", "trigger"), Child("B"), Child("C", "conclude")))
142
+ ]
143
+ warns = flow_step_warnings(needs, _STEPS)
144
+ assert any("has no step while siblings do" in m for _, m in warns)
145
+
146
+
147
+ def test_lint_clean_journey_is_silent() -> None:
148
+ needs = [
149
+ Need(id="F-1", text="f", children=(Child("A", "trigger"), Child("B", "act"), Child("C", "conclude")))
150
+ ]
151
+ assert flow_step_warnings(needs, _STEPS) == []
152
+
153
+
154
+ def test_lint_command_surfaces_flow_step_warning(
155
+ tmp_path: Path, capsys: pytest.CaptureFixture[str]
156
+ ) -> None:
157
+ from prodc.cli import main
158
+
159
+ needs = """
160
+ needs:
161
+ - id: F-1
162
+ text: a flow that never ends
163
+ children: [{id: US-1, step: trigger}, {id: US-2, step: act}]
164
+ - id: US-1
165
+ text: a
166
+ - id: US-2
167
+ text: b
168
+ """
169
+ code = main(["lint", "--config", str(_project(tmp_path, needs=needs))])
170
+ out = capsys.readouterr().out
171
+ assert code == 0 # a shape warning is not a hard error
172
+ assert "terminal step 'conclude'" in out
prodc-0.4.6/PKG-INFO DELETED
@@ -1,82 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: prodc
3
- Version: 0.4.6
4
- Summary: Product management as code: roles, flows, and features traced to evidence.
5
- Project-URL: Homepage, https://gitlab.com/jorgeecardona/prodc
6
- Project-URL: Repository, https://gitlab.com/jorgeecardona/prodc
7
- Project-URL: Changelog, https://gitlab.com/jorgeecardona/prodc/-/blob/main/CHANGELOG.md
8
- Author-email: Jorge Cardona <jorgeecardona@gmail.com>
9
- License-Expression: MIT
10
- License-File: LICENSE
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Intended Audience :: Developers
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Topic :: Scientific/Engineering
17
- Classifier: Topic :: Software Development :: Testing
18
- Classifier: Typing :: Typed
19
- Requires-Python: >=3.12
20
- Requires-Dist: pyyaml>=6
21
- Provides-Extra: docs
22
- Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
23
- Requires-Dist: mkdocs>=1.6; extra == 'docs'
24
- Requires-Dist: mkdocstrings[python]>=0.27; extra == 'docs'
25
- Description-Content-Type: text/markdown
26
-
27
- # prodc
28
-
29
- Product management as code: roles, flows, and features, each traced to the evidence behind it and the test that proves it. The aim is to make product work something a whole team can share — kept in the repo, next to the code, read by the tools you already use — rather than a specialty locked in one person's head and one tracker.
30
-
31
- prodc records that structure and checks it holds together; it does not call a model or ping a tracker. Two ideas carry it, both about not fooling yourself:
32
-
33
- 1. **A need with no evidence is an opinion.** Write down who asked for something — an interview, a ticket, an email — or prodc flags it as an opinion, because that is what it is until someone outside the room has asked.
34
- 2. **"Done" is a claim your tests back.** You attach a proof (a locator into your test suite), and `prodc status` reports each need as DELIVERED, PENDING, HOLE (an honest gap), or BROKEN. The status is read from the repo, not remembered from a standup.
35
-
36
- ## Install
37
-
38
- ```bash
39
- pip install prodc # or: uv add prodc
40
- uv sync && make check # contributors: deps + lint + typecheck + test
41
- ```
42
-
43
- ## Use
44
-
45
- ```bash
46
- prodc status
47
- ```
48
-
49
- ```text
50
- prodc status — quickstart
51
- gaps: 2 HOLE · 1 opinion coverage 1/3 (33%)
52
- id GROUNDS need WARRANT
53
-
54
- jordan (design partner) 1 DELIVERED · 2 HOLE
55
- !! N-1 DELIVERED* 1/1 see which orders are past due at a glance primary:Jordan
56
- N-2 HOLE export the day's delivery run sheet secondary
57
- N-3 HOLE implement a settings page opinion
58
- ⚠ text may be a solution in disguise (starts with 'implement')
59
- ```
60
-
61
- GROUNDS = does a test prove it. WARRANT = did a user ask for it. The gap between them is the point.
62
-
63
- ## Use it with an agent
64
-
65
- prodc ships a playbook that teaches a coding agent to run it as the project's product manager —
66
- read the board, capture evidence, record needs with honest provenance, find proofs, gate before a
67
- handoff. Drop it where your agent reads project guidance:
68
-
69
- ```bash
70
- prodc agent >> AGENTS.md # or: prodc agent --write AGENTS.md
71
- ```
72
-
73
- This is how an engineer vibe-codes their way into product work: the agent does the product
74
- management from inside the repo, with prodc keeping it honest.
75
-
76
- ## Docs
77
-
78
- - [Quickstart](docs/quickstart.md) — 60 seconds to a board
79
- - [Commands](docs/commands.md) — every verb, one line each
80
- - [Model](docs/model.md) — the nouns (Need, Role, Goal, Release) and the Need fields
81
-
82
- `docs/issues/` holds follow-ups. `AGENTS.md` is for coding agents.
prodc-0.4.6/README.md DELETED
@@ -1,56 +0,0 @@
1
- # prodc
2
-
3
- Product management as code: roles, flows, and features, each traced to the evidence behind it and the test that proves it. The aim is to make product work something a whole team can share — kept in the repo, next to the code, read by the tools you already use — rather than a specialty locked in one person's head and one tracker.
4
-
5
- prodc records that structure and checks it holds together; it does not call a model or ping a tracker. Two ideas carry it, both about not fooling yourself:
6
-
7
- 1. **A need with no evidence is an opinion.** Write down who asked for something — an interview, a ticket, an email — or prodc flags it as an opinion, because that is what it is until someone outside the room has asked.
8
- 2. **"Done" is a claim your tests back.** You attach a proof (a locator into your test suite), and `prodc status` reports each need as DELIVERED, PENDING, HOLE (an honest gap), or BROKEN. The status is read from the repo, not remembered from a standup.
9
-
10
- ## Install
11
-
12
- ```bash
13
- pip install prodc # or: uv add prodc
14
- uv sync && make check # contributors: deps + lint + typecheck + test
15
- ```
16
-
17
- ## Use
18
-
19
- ```bash
20
- prodc status
21
- ```
22
-
23
- ```text
24
- prodc status — quickstart
25
- gaps: 2 HOLE · 1 opinion coverage 1/3 (33%)
26
- id GROUNDS need WARRANT
27
-
28
- jordan (design partner) 1 DELIVERED · 2 HOLE
29
- !! N-1 DELIVERED* 1/1 see which orders are past due at a glance primary:Jordan
30
- N-2 HOLE export the day's delivery run sheet secondary
31
- N-3 HOLE implement a settings page opinion
32
- ⚠ text may be a solution in disguise (starts with 'implement')
33
- ```
34
-
35
- GROUNDS = does a test prove it. WARRANT = did a user ask for it. The gap between them is the point.
36
-
37
- ## Use it with an agent
38
-
39
- prodc ships a playbook that teaches a coding agent to run it as the project's product manager —
40
- read the board, capture evidence, record needs with honest provenance, find proofs, gate before a
41
- handoff. Drop it where your agent reads project guidance:
42
-
43
- ```bash
44
- prodc agent >> AGENTS.md # or: prodc agent --write AGENTS.md
45
- ```
46
-
47
- This is how an engineer vibe-codes their way into product work: the agent does the product
48
- management from inside the repo, with prodc keeping it honest.
49
-
50
- ## Docs
51
-
52
- - [Quickstart](docs/quickstart.md) — 60 seconds to a board
53
- - [Commands](docs/commands.md) — every verb, one line each
54
- - [Model](docs/model.md) — the nouns (Need, Role, Goal, Release) and the Need fields
55
-
56
- `docs/issues/` holds follow-ups. `AGENTS.md` is for coding agents.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes