prodc 0.3.82__tar.gz → 0.3.83__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.
- {prodc-0.3.82 → prodc-0.3.83}/PKG-INFO +1 -1
- {prodc-0.3.82 → prodc-0.3.83}/pyproject.toml +1 -1
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/cli/app.py +13 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/cli/commands.py +21 -0
- prodc-0.3.83/src/prodc/guide.md +95 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_cli.py +20 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_models.py +91 -3
- {prodc-0.3.82 → prodc-0.3.83}/.gitignore +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/CHANGELOG.md +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/LICENSE +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/README.md +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/examples/quickstart/features/orders.feature +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/examples/quickstart/needs/jordan.yaml +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/examples/quickstart/prodc.toml +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/__init__.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/adapters.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/agent.md +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/board.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/callbacks.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/cli/__init__.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/cli/common.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/cli/render.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/cli/serialize.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/config.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/dedup.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/loader.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/model.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/py.typed +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/report.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/report_html.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/status.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/src/prodc/writer.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_adapters.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_answer.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_callbacks.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_citation.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_claim_gating.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_command_hook.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_contradicts.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_decisions.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_dedup.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_description.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_drafts.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_evalgate.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_external_adapter.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_gitlab_source.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_importance.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_loader.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_needref.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_principles.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_relink.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_report.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_report_html.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_review.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_roles.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_source_role.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_sources.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_staleness.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_status.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_suggest.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_variants.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_who_variants.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_withdrawn.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_writer.py +0 -0
- {prodc-0.3.82 → prodc-0.3.83}/tests/test_writer_comments.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: prodc
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.83
|
|
4
4
|
Summary: Product management as code: roles, flows, and features traced to evidence.
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/jorgeecardona/prodc
|
|
6
6
|
Project-URL: Repository, https://gitlab.com/jorgeecardona/prodc
|
|
@@ -22,6 +22,7 @@ from .commands import (
|
|
|
22
22
|
cmd_capture,
|
|
23
23
|
cmd_diff,
|
|
24
24
|
cmd_dupes,
|
|
25
|
+
cmd_guide,
|
|
25
26
|
cmd_lint,
|
|
26
27
|
cmd_plan,
|
|
27
28
|
cmd_portfolio,
|
|
@@ -606,6 +607,18 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
606
607
|
ag.add_argument("--force", action="store_true", help="overwrite PATH if it already exists")
|
|
607
608
|
ag.set_defaults(func=cmd_agent)
|
|
608
609
|
|
|
610
|
+
gd = sub.add_parser(
|
|
611
|
+
"guide", help="print prodc's human CLI guide (a short, scannable onboarding path)"
|
|
612
|
+
)
|
|
613
|
+
gd.add_argument(
|
|
614
|
+
"--write",
|
|
615
|
+
default=None,
|
|
616
|
+
metavar="PATH",
|
|
617
|
+
help="write the guide to PATH instead of stdout",
|
|
618
|
+
)
|
|
619
|
+
gd.add_argument("--force", action="store_true", help="overwrite PATH if it already exists")
|
|
620
|
+
gd.set_defaults(func=cmd_guide)
|
|
621
|
+
|
|
609
622
|
df = sub.add_parser(
|
|
610
623
|
"diff", help="diff the current board against a prior status --json snapshot"
|
|
611
624
|
)
|
|
@@ -1146,6 +1146,27 @@ def cmd_agent(args: argparse.Namespace) -> int:
|
|
|
1146
1146
|
return 0
|
|
1147
1147
|
|
|
1148
1148
|
|
|
1149
|
+
def cmd_guide(args: argparse.Namespace) -> int:
|
|
1150
|
+
"""Print prodc's human CLI guide (a short onboarding path), or write it to a file.
|
|
1151
|
+
|
|
1152
|
+
The human sibling of :func:`cmd_agent`: where `agent` ships an instruction set for a coding
|
|
1153
|
+
agent, `guide` is the 60-second path a person reads to try prodc. Ships versioned with prodc;
|
|
1154
|
+
`--write` drops it into a repo.
|
|
1155
|
+
"""
|
|
1156
|
+
text = importlib.resources.files("prodc").joinpath("guide.md").read_text(encoding="utf-8")
|
|
1157
|
+
if args.write:
|
|
1158
|
+
dest = Path(args.write)
|
|
1159
|
+
if dest.exists() and not args.force:
|
|
1160
|
+
print(f"prodc: {dest} already exists (use --force to overwrite)", file=sys.stderr)
|
|
1161
|
+
return 2
|
|
1162
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
1163
|
+
dest.write_text(text, encoding="utf-8")
|
|
1164
|
+
print(f"wrote prodc guide to {dest}")
|
|
1165
|
+
return 0
|
|
1166
|
+
print(text, end="")
|
|
1167
|
+
return 0
|
|
1168
|
+
|
|
1169
|
+
|
|
1149
1170
|
def cmd_suggest(args: argparse.Namespace) -> int:
|
|
1150
1171
|
"""Propose candidate proof locators for HOLE needs (or relink BROKEN ones) from the inventories (#54)."""
|
|
1151
1172
|
loaded = load_or_report(args)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# prodc in 60 seconds
|
|
2
|
+
|
|
3
|
+
prodc keeps product work in the repo, next to the code: who needs what, why, and the test that proves it is done. Plain YAML files, reviewed in pull requests like code.
|
|
4
|
+
|
|
5
|
+
Try it now: `prodc status`
|
|
6
|
+
|
|
7
|
+
## The two rules
|
|
8
|
+
|
|
9
|
+
- **No evidence means it is an opinion.** A need backed only by your own guess still loads, but prodc marks it an opinion until a real user has said something. Example: "users want dark mode" with nobody quoted is an opinion.
|
|
10
|
+
- **"Done" is a test, not a claim.** You do not mark a need done; you attach a proof that points into the real test suite. `prodc status` reads DELIVERED, HOLE, or BROKEN from it. Example: `proofs: ["pytest:tests/test_checkout.py::test_pays"]`.
|
|
11
|
+
|
|
12
|
+
## Your first board
|
|
13
|
+
|
|
14
|
+
Three files make a board.
|
|
15
|
+
|
|
16
|
+
`prodc.toml`:
|
|
17
|
+
|
|
18
|
+
```toml
|
|
19
|
+
[prodc]
|
|
20
|
+
needs = ["needs/*.yaml"]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`needs/needs.yaml`:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
needs:
|
|
27
|
+
- id: N-1
|
|
28
|
+
text: a buyer can pay for a cart
|
|
29
|
+
who: Dana
|
|
30
|
+
sources:
|
|
31
|
+
- kind: primary
|
|
32
|
+
who: Dana
|
|
33
|
+
quote: I could not check out on mobile
|
|
34
|
+
proofs: ["pytest:tests/test_checkout.py::test_pays"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then run `prodc status`. You get one board line per need, with its state and coverage.
|
|
38
|
+
|
|
39
|
+
## Read the board
|
|
40
|
+
|
|
41
|
+
| Command | Shows |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `prodc status` | the whole board and coverage |
|
|
44
|
+
| `prodc show <id>` | one need as a citable answer |
|
|
45
|
+
| `prodc plan` | the backlog, ranked |
|
|
46
|
+
| `prodc brief <client>` | what to ask a client before a call |
|
|
47
|
+
|
|
48
|
+
## Record a need
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
prodc add need N-2 --text "a buyer can save a cart" --who you --kind assumption --file needs/needs.yaml
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Warning: never record a `primary` source you invented. `primary` means a real user actually said it. Your own reasoning is `assumption`.
|
|
55
|
+
|
|
56
|
+
## Prove a need
|
|
57
|
+
|
|
58
|
+
A need moves HOLE, then PENDING, then DELIVERED as its proof comes true. For a HOLE, let prodc find the test:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
prodc suggest N-1 --apply
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Gate it in CI
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
prodc lint --strict
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Exit codes are the contract: 0 is clean, 1 is a gate failed (a broken proof or a must-have opinion), 2 is a usage or config error.
|
|
71
|
+
|
|
72
|
+
## Stuck? A gap is a valid answer
|
|
73
|
+
|
|
74
|
+
A HOLE (no proof yet) or an opinion (no evidence yet) is honest output, not a failure. Leave it visible. Never attach a fake proof to turn a row green.
|
|
75
|
+
|
|
76
|
+
## Cheat sheet
|
|
77
|
+
|
|
78
|
+
| To | Run |
|
|
79
|
+
|---|---|
|
|
80
|
+
| see the board | `prodc status` |
|
|
81
|
+
| read one need | `prodc show <id>` |
|
|
82
|
+
| rank the backlog | `prodc plan` |
|
|
83
|
+
| prep a client call | `prodc brief <client>` |
|
|
84
|
+
| add a need | `prodc add need <id> --text "..." --who you --kind assumption --file <f>` |
|
|
85
|
+
| attach evidence | `prodc capture <id> --kind primary --who <person> --date <ISO> --quote "..."` |
|
|
86
|
+
| attach a proof | `prodc suggest <id> --apply` |
|
|
87
|
+
| decide against a need | `prodc withdraw <id> --reason "..."` |
|
|
88
|
+
| gate in CI | `prodc lint --strict` |
|
|
89
|
+
|
|
90
|
+
## Going further
|
|
91
|
+
|
|
92
|
+
- The vision, held as given: `prodc add principle <id> --text "..."`
|
|
93
|
+
- How a need gets built (the chosen solution): `prodc add decision <id> --text "..." --needs <ids>`
|
|
94
|
+
- Full command list: `docs/commands.md`. The nouns: `docs/model.md`. Config: `docs/config.md`.
|
|
95
|
+
- Teaching an AI agent to run prodc for you: `prodc agent`.
|
|
@@ -513,3 +513,23 @@ def test_agent_write_creates_file_and_refuses_overwrite(
|
|
|
513
513
|
assert main(["agent", "--write", str(dest)]) == 2
|
|
514
514
|
assert "already exists" in capsys.readouterr().err
|
|
515
515
|
assert main(["agent", "--write", str(dest), "--force"]) == 0
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def test_guide_prints_human_guide(capsys: pytest.CaptureFixture[str]) -> None:
|
|
519
|
+
code = main(["guide"])
|
|
520
|
+
out = capsys.readouterr().out
|
|
521
|
+
assert code == 0
|
|
522
|
+
assert "prodc in 60 seconds" in out
|
|
523
|
+
assert "The two rules" in out # the human onboarding path is present
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def test_guide_write_creates_file_and_refuses_overwrite(
|
|
527
|
+
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
528
|
+
) -> None:
|
|
529
|
+
dest = tmp_path / "sub" / "GUIDE.md"
|
|
530
|
+
assert main(["guide", "--write", str(dest)]) == 0
|
|
531
|
+
assert dest.exists() and "prodc" in dest.read_text(encoding="utf-8")
|
|
532
|
+
capsys.readouterr()
|
|
533
|
+
assert main(["guide", "--write", str(dest)]) == 2
|
|
534
|
+
assert "already exists" in capsys.readouterr().err
|
|
535
|
+
assert main(["guide", "--write", str(dest), "--force"]) == 0
|
|
@@ -11,11 +11,11 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
from dataclasses import dataclass, replace
|
|
13
13
|
|
|
14
|
-
from musil import Action, Model, check
|
|
14
|
+
from musil import Action, Model, check, explore, reaches
|
|
15
15
|
|
|
16
16
|
from prodc.adapters import Adapter, ProofResult
|
|
17
|
-
from prodc.model import Need, NeedState, ProofState
|
|
18
|
-
from prodc.status import Resolver
|
|
17
|
+
from prodc.model import Decision, Need, NeedState, ProofState, Withdrawal, board_needs
|
|
18
|
+
from prodc.status import Resolver, active_decisions
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class _Stub:
|
|
@@ -211,6 +211,79 @@ FLOW_MODEL = Model(
|
|
|
211
211
|
)
|
|
212
212
|
|
|
213
213
|
|
|
214
|
+
# --- draft / withdrawn lifecycle (shared by Need and Decision) -----------------------
|
|
215
|
+
# The board-membership state machine the writer verbs move an item through: a thing is staged
|
|
216
|
+
# (draft), promoted (draft cleared), withdrawn (a decision against — off the board, record kept),
|
|
217
|
+
# or reinstated. Every invariant derives from prodc's REAL rules (counts_toward_coverage /
|
|
218
|
+
# board_needs / active_decisions), so a drift between this model and those rules is a failed check.
|
|
219
|
+
# Note the asymmetry it pins down: coverage/board exclude a *withdrawn* item unconditionally, but a
|
|
220
|
+
# *draft* only until promoted — and active_decisions excludes both.
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass(frozen=True)
|
|
224
|
+
class Life:
|
|
225
|
+
draft: bool = True # starts staged (proposed); promote clears it
|
|
226
|
+
withdrawn: bool = False
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _life_actions() -> list[Action[Life]]:
|
|
230
|
+
return [
|
|
231
|
+
Action("promote", lambda s: s.draft and not s.withdrawn, lambda s: replace(s, draft=False)),
|
|
232
|
+
Action("withdraw", lambda s: not s.withdrawn, lambda s: replace(s, withdrawn=True)),
|
|
233
|
+
Action("reinstate", lambda s: s.withdrawn, lambda s: replace(s, withdrawn=False)),
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _w(s: Life) -> Withdrawal | None:
|
|
238
|
+
return Withdrawal(reason="r", date="2026-01-01") if s.withdrawn else None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _life_need(s: Life) -> Need:
|
|
242
|
+
return Need(id="n", text="x", draft=s.draft, withdrawn=_w(s))
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _life_decision(s: Life) -> Decision:
|
|
246
|
+
return Decision(id="D", text="x", needs=("n",), draft=s.draft, withdrawn=_w(s))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _on_board(s: Life) -> bool:
|
|
250
|
+
return not s.draft and not s.withdrawn
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _life_coverage_counts_iff_not_withdrawn(s: Life) -> bool:
|
|
254
|
+
"""A leaf product need counts toward coverage exactly when not withdrawn — draft is orthogonal."""
|
|
255
|
+
return _life_need(s).counts_toward_coverage == (not s.withdrawn)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _life_board_default_excludes_draft_and_withdrawn(s: Life) -> bool:
|
|
259
|
+
"""The counted board (no --include-drafts) holds the item iff it is neither draft nor withdrawn."""
|
|
260
|
+
on = _life_need(s).id in {n.id for n in board_needs([_life_need(s)], include_drafts=False)}
|
|
261
|
+
return on == _on_board(s)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _life_board_with_drafts_excludes_only_withdrawn(s: Life) -> bool:
|
|
265
|
+
"""--include-drafts holds the item iff it is not withdrawn (a draft is kept, withdrawn never)."""
|
|
266
|
+
on = _life_need(s).id in {n.id for n in board_needs([_life_need(s)], include_drafts=True)}
|
|
267
|
+
return on == (not s.withdrawn)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _life_decision_active_iff_on_board(s: Life) -> bool:
|
|
271
|
+
"""A decision is in force iff neither a draft nor withdrawn — stricter than need coverage."""
|
|
272
|
+
return (active_decisions([_life_decision(s)]) == [_life_decision(s)]) == _on_board(s)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
LIFE_MODEL = Model(
|
|
276
|
+
init=Life(),
|
|
277
|
+
actions=_life_actions(),
|
|
278
|
+
invariants={
|
|
279
|
+
"coverage-counts-iff-not-withdrawn": _life_coverage_counts_iff_not_withdrawn,
|
|
280
|
+
"board-default-excludes-draft-and-withdrawn": _life_board_default_excludes_draft_and_withdrawn,
|
|
281
|
+
"board-with-drafts-excludes-only-withdrawn": _life_board_with_drafts_excludes_only_withdrawn,
|
|
282
|
+
"decision-active-iff-on-board": _life_decision_active_iff_on_board,
|
|
283
|
+
},
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
214
287
|
# --- the gate ------------------------------------------------------------------------
|
|
215
288
|
def test_leaf_lifecycle_safety() -> None:
|
|
216
289
|
r = check(LEAF_MODEL)
|
|
@@ -222,6 +295,21 @@ def test_flow_rollup_safety() -> None:
|
|
|
222
295
|
assert r.ok, f"{r.invariant}: {r.trace}"
|
|
223
296
|
|
|
224
297
|
|
|
298
|
+
def test_lifecycle_safety() -> None:
|
|
299
|
+
r = check(LIFE_MODEL)
|
|
300
|
+
assert r.ok, f"{r.invariant}: {r.trace}"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def test_lifecycle_board_always_recoverable() -> None:
|
|
304
|
+
"""No reachable state is permanently stuck off the board: reinstate + promote always lead back.
|
|
305
|
+
|
|
306
|
+
The liveness counterpart to the safety invariants — it proves a withdrawn or staged item can
|
|
307
|
+
never get trapped off the active board (an item is never a one-way trip).
|
|
308
|
+
"""
|
|
309
|
+
g = explore(LIFE_MODEL)
|
|
310
|
+
assert reaches(g, _on_board) == set(g.states)
|
|
311
|
+
|
|
312
|
+
|
|
225
313
|
def test_checker_bites() -> None:
|
|
226
314
|
"""Guard against a vacuous suite: a knowingly false invariant must be caught."""
|
|
227
315
|
bad = Model(
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|