measuring-autonomy 1.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.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2022, emmanuel-a-otchere
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: measuring-autonomy
3
+ Version: 1.1.0
4
+ Summary: Measure and manage autonomy: safe-level scoring, Autonomy Ledger health checks, and framework tooling.
5
+ Author: Emmanuel A. Otchere
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy
8
+ Project-URL: Changelog, https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy/blob/main/CHANGELOG.md
9
+ Keywords: autonomy,governance,maturity-model,management,org-design
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Information Technology
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Office/Business
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: pyyaml>=5.4
28
+ Dynamic: license-file
29
+
30
+ # Measuring and Managing Autonomy
31
+
32
+ Autonomy from a business and systems point of view can be challenging to
33
+ manage. This project defines a framework by which managing and measuring
34
+ autonomy can be designed and implemented.
35
+
36
+ Most organizations manage autonomy implicitly — it emerges from trust, tenure,
37
+ and accidents of structure, and is therefore felt but not measured, expanded
38
+ but never designed, and revoked arbitrarily. This framework makes autonomy an
39
+ **explicit, designed, measurable property** of the organization.
40
+
41
+ ## The framework in one diagram
42
+
43
+ ```mermaid
44
+ flowchart LR
45
+ A[Design<br>decision + blast radius] --> B[Grant<br>autonomy contract]
46
+ B --> C[Measure<br>ledger + telemetry]
47
+ C --> D[Manage<br>expand / contract / re-shape]
48
+ D -->|re-grant, next horizon| B
49
+ ```
50
+
51
+ ## What's here
52
+
53
+ | Document | Purpose |
54
+ |---|---|
55
+ | [Framework](docs/framework.md) | Definitions, the three axes, the five levels (L0–L4), autonomy contracts |
56
+ | [Maturity model](docs/maturity-model.md) | Six dimensions × five levels grid, scoring rules, the Autonomy Ledger |
57
+ | [Assessment](docs/assessment.md) | The questionnaire and scoring procedure — the practical instrument |
58
+ | [Governance loop](docs/governance-loop.md) | Grant → Instrument → Observe → Review, with cadence and anti-patterns |
59
+ | [Positioning](docs/positioning.md) | Related work, lineage, open questions, BibTeX |
60
+ | [Worked example](examples/README.md) | One team, scored end to end, producing a governance move |
61
+ | [AI agents as units](docs/agents-as-units.md) | First-pass extension: scoring agent units with the same instrument |
62
+ | [Ledger format](docs/ledger-format.md) | Machine-readable Autonomy Ledger (YAML v1) + health checks |
63
+ | [Contract template](docs/contract-template.md) | Autonomy contract + review agenda, ready to copy |
64
+ | [Versioning](docs/versioning.md) | Ruleset/ledger versioning policy — how assessments stay comparable |
65
+ | [Changelog](CHANGELOG.md) | Every change, classified by comparability impact |
66
+
67
+ ## Install
68
+
69
+ **No Python needed** — standalone binaries for macOS, Linux, and Windows are
70
+ attached to each [GitHub Release](https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy/releases):
71
+ download `autonomy-score-<os>-<arch>` and `ledger-health-<os>-<arch>`, mark
72
+ them executable, run.
73
+
74
+ **With Python 3.9+** (installs the `autonomy-score` and `ledger-health`
75
+ commands):
76
+
77
+ ```bash
78
+ pipx install measuring-autonomy # or: pip install measuring-autonomy
79
+ ```
80
+
81
+ **From a clone**, the zero-install form keeps working with no packaging step:
82
+ `python3 tools/autonomy_score.py ...` (the `tools/` scripts are shims over
83
+ `src/measuring_autonomy/`).
84
+
85
+ ## Quick start: score a team
86
+
87
+ ```bash
88
+ python3 tools/autonomy_score.py 1 3 2 2 1 1 --current 1
89
+ ```
90
+
91
+ Scores are D1 clarity, D2 capability, D3 guardrails, D4 telemetry,
92
+ D5 transparency, D6 accountability (0–4 each). The tool applies the
93
+ weakest-link rule — `min(guardrails, telemetry, accountability)` — and
94
+ outputs the safe grant level and recommended move.
95
+
96
+ ```bash
97
+ python3 tools/autonomy_score.py --self-test # verify the scoring rules
98
+ ```
99
+
100
+ Track granted autonomy in a machine-readable **Autonomy Ledger** (v1 YAML
101
+ format, [`examples/ledger.yaml`](examples/ledger.yaml)) and check its health:
102
+
103
+ ```bash
104
+ python3 tools/ledger_health.py examples/ledger.yaml # requires PyYAML (pip install pyyaml)
105
+ ```
106
+
107
+ ## Try it without a terminal
108
+
109
+ A dependency-free web version of the assessment lives in [`site/`](site/)
110
+ (and is published to GitHub Pages when enabled — repo **Settings → Pages →
111
+ Source: GitHub Actions**). Open `site/index.html` locally, tick the items
112
+ with evidence, and print the scorecard.
113
+
114
+ ## The three questions
115
+
116
+ 1. **Design** — What autonomy should this unit have, and why? →
117
+ [contracts](docs/framework.md#5-autonomy-contracts)
118
+ 2. **Measurement** — How much autonomy exists, and how well is it used? →
119
+ [maturity model](docs/maturity-model.md) + [assessment](docs/assessment.md)
120
+ 3. **Management** — When should autonomy expand, contract, or re-shape? →
121
+ [governance loop](docs/governance-loop.md)
122
+
123
+ ## Roadmap
124
+
125
+ See [docs/roadmap.md](docs/roadmap.md). Current status: **v0.1 draft** — the
126
+ written framework, maturity model, and assessment instrument are complete;
127
+ next up are validation against real teams and optional tooling.
128
+
129
+ ## Contributing
130
+
131
+ Feedback, corrections, and real-world applications are very welcome — see
132
+ [CONTRIBUTING.md](CONTRIBUTING.md). Use the issue templates to report
133
+ framework feedback or submit a case study.
134
+
135
+ ## License
136
+
137
+ [MIT](LICENSE)
@@ -0,0 +1,108 @@
1
+ # Measuring and Managing Autonomy
2
+
3
+ Autonomy from a business and systems point of view can be challenging to
4
+ manage. This project defines a framework by which managing and measuring
5
+ autonomy can be designed and implemented.
6
+
7
+ Most organizations manage autonomy implicitly — it emerges from trust, tenure,
8
+ and accidents of structure, and is therefore felt but not measured, expanded
9
+ but never designed, and revoked arbitrarily. This framework makes autonomy an
10
+ **explicit, designed, measurable property** of the organization.
11
+
12
+ ## The framework in one diagram
13
+
14
+ ```mermaid
15
+ flowchart LR
16
+ A[Design<br>decision + blast radius] --> B[Grant<br>autonomy contract]
17
+ B --> C[Measure<br>ledger + telemetry]
18
+ C --> D[Manage<br>expand / contract / re-shape]
19
+ D -->|re-grant, next horizon| B
20
+ ```
21
+
22
+ ## What's here
23
+
24
+ | Document | Purpose |
25
+ |---|---|
26
+ | [Framework](docs/framework.md) | Definitions, the three axes, the five levels (L0–L4), autonomy contracts |
27
+ | [Maturity model](docs/maturity-model.md) | Six dimensions × five levels grid, scoring rules, the Autonomy Ledger |
28
+ | [Assessment](docs/assessment.md) | The questionnaire and scoring procedure — the practical instrument |
29
+ | [Governance loop](docs/governance-loop.md) | Grant → Instrument → Observe → Review, with cadence and anti-patterns |
30
+ | [Positioning](docs/positioning.md) | Related work, lineage, open questions, BibTeX |
31
+ | [Worked example](examples/README.md) | One team, scored end to end, producing a governance move |
32
+ | [AI agents as units](docs/agents-as-units.md) | First-pass extension: scoring agent units with the same instrument |
33
+ | [Ledger format](docs/ledger-format.md) | Machine-readable Autonomy Ledger (YAML v1) + health checks |
34
+ | [Contract template](docs/contract-template.md) | Autonomy contract + review agenda, ready to copy |
35
+ | [Versioning](docs/versioning.md) | Ruleset/ledger versioning policy — how assessments stay comparable |
36
+ | [Changelog](CHANGELOG.md) | Every change, classified by comparability impact |
37
+
38
+ ## Install
39
+
40
+ **No Python needed** — standalone binaries for macOS, Linux, and Windows are
41
+ attached to each [GitHub Release](https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy/releases):
42
+ download `autonomy-score-<os>-<arch>` and `ledger-health-<os>-<arch>`, mark
43
+ them executable, run.
44
+
45
+ **With Python 3.9+** (installs the `autonomy-score` and `ledger-health`
46
+ commands):
47
+
48
+ ```bash
49
+ pipx install measuring-autonomy # or: pip install measuring-autonomy
50
+ ```
51
+
52
+ **From a clone**, the zero-install form keeps working with no packaging step:
53
+ `python3 tools/autonomy_score.py ...` (the `tools/` scripts are shims over
54
+ `src/measuring_autonomy/`).
55
+
56
+ ## Quick start: score a team
57
+
58
+ ```bash
59
+ python3 tools/autonomy_score.py 1 3 2 2 1 1 --current 1
60
+ ```
61
+
62
+ Scores are D1 clarity, D2 capability, D3 guardrails, D4 telemetry,
63
+ D5 transparency, D6 accountability (0–4 each). The tool applies the
64
+ weakest-link rule — `min(guardrails, telemetry, accountability)` — and
65
+ outputs the safe grant level and recommended move.
66
+
67
+ ```bash
68
+ python3 tools/autonomy_score.py --self-test # verify the scoring rules
69
+ ```
70
+
71
+ Track granted autonomy in a machine-readable **Autonomy Ledger** (v1 YAML
72
+ format, [`examples/ledger.yaml`](examples/ledger.yaml)) and check its health:
73
+
74
+ ```bash
75
+ python3 tools/ledger_health.py examples/ledger.yaml # requires PyYAML (pip install pyyaml)
76
+ ```
77
+
78
+ ## Try it without a terminal
79
+
80
+ A dependency-free web version of the assessment lives in [`site/`](site/)
81
+ (and is published to GitHub Pages when enabled — repo **Settings → Pages →
82
+ Source: GitHub Actions**). Open `site/index.html` locally, tick the items
83
+ with evidence, and print the scorecard.
84
+
85
+ ## The three questions
86
+
87
+ 1. **Design** — What autonomy should this unit have, and why? →
88
+ [contracts](docs/framework.md#5-autonomy-contracts)
89
+ 2. **Measurement** — How much autonomy exists, and how well is it used? →
90
+ [maturity model](docs/maturity-model.md) + [assessment](docs/assessment.md)
91
+ 3. **Management** — When should autonomy expand, contract, or re-shape? →
92
+ [governance loop](docs/governance-loop.md)
93
+
94
+ ## Roadmap
95
+
96
+ See [docs/roadmap.md](docs/roadmap.md). Current status: **v0.1 draft** — the
97
+ written framework, maturity model, and assessment instrument are complete;
98
+ next up are validation against real teams and optional tooling.
99
+
100
+ ## Contributing
101
+
102
+ Feedback, corrections, and real-world applications are very welcome — see
103
+ [CONTRIBUTING.md](CONTRIBUTING.md). Use the issue templates to report
104
+ framework feedback or submit a case study.
105
+
106
+ ## License
107
+
108
+ [MIT](LICENSE)
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "measuring-autonomy"
7
+ version = "1.1.0"
8
+ description = "Measure and manage autonomy: safe-level scoring, Autonomy Ledger health checks, and framework tooling."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Emmanuel A. Otchere" }]
13
+ keywords = ["autonomy", "governance", "maturity-model", "management", "org-design"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: Information Technology",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Office/Business",
28
+ "Topic :: Software Development :: Quality Assurance",
29
+ ]
30
+ dependencies = ["pyyaml>=5.4"]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy"
34
+ Changelog = "https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy/blob/main/CHANGELOG.md"
35
+
36
+ [project.scripts]
37
+ autonomy-score = "measuring_autonomy.score:main"
38
+ ledger-health = "measuring_autonomy.ledger:main"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,33 @@
1
+ """Measuring & Managing Autonomy toolkit.
2
+
3
+ Scoring engine and Autonomy Ledger health checker for the framework
4
+ (https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy).
5
+
6
+ The ledger module is imported lazily by its console command only, so
7
+ `import measuring_autonomy` stays dependency-free; PyYAML is required
8
+ solely by `ledger-health`.
9
+ """
10
+ from .score import (
11
+ DIMENSIONS,
12
+ WEAKEST_LINK,
13
+ RULESET_VERSION,
14
+ assess,
15
+ gap_analysis,
16
+ parse_scores,
17
+ recommended_move,
18
+ safe_level,
19
+ )
20
+
21
+ __version__ = "1.1.0"
22
+
23
+ __all__ = [
24
+ "__version__",
25
+ "DIMENSIONS",
26
+ "RULESET_VERSION",
27
+ "WEAKEST_LINK",
28
+ "assess",
29
+ "gap_analysis",
30
+ "parse_scores",
31
+ "recommended_move",
32
+ "safe_level",
33
+ ]
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env python3
2
+ """Autonomy Ledger health checker for the Measuring & Managing Autonomy framework.
3
+
4
+ Validates a ledger (YAML, format described in docs/ledger-format.md) against
5
+ the framework's rules and prints an org-level health report:
6
+
7
+ - validity: schema errors (bad level, missing fields, date inconsistencies)
8
+ - coverage: fraction of contracts with all required fields
9
+ - staleness: contracts whose last_review is older than their cadence
10
+ - expiry: active contracts past their horizon (autonomy with no live review)
11
+ - guardrails: contracts with empty guardrail/telemetry/trigger lists
12
+
13
+ Exit code 0 = healthy ledger; 1 = findings; 2 = unreadable/invalid file.
14
+
15
+ Usage:
16
+ ledger-health examples/ledger.yaml
17
+ ledger-health ledger.yaml --today 2025-12-31
18
+ ledger-health --self-test
19
+ python3 tools/ledger_health.py ... # zero-install form from a checkout
20
+
21
+ Requires PyYAML (a declared dependency of this package; the zero-install
22
+ form needs `pip install pyyaml`).
23
+ License: MIT (see LICENSE).
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import datetime as dt
29
+ import json
30
+ import sys
31
+
32
+ try:
33
+ import yaml
34
+ except ImportError: # pragma: no cover
35
+ yaml = None
36
+
37
+ REQUIRED_LISTS = ("guardrails", "telemetry", "revocation_triggers")
38
+ REQUIRED_FIELDS = (
39
+ "id",
40
+ "unit",
41
+ "domain",
42
+ "level",
43
+ "scope",
44
+ "guardrails",
45
+ "telemetry",
46
+ "revocation_triggers",
47
+ "grantor",
48
+ "horizon",
49
+ "last_review",
50
+ )
51
+ STATUSES = ("active", "expired", "revoked", "draft")
52
+ DEFAULT_CADENCE_DAYS = 90
53
+
54
+ # Ledger format version handled by this tool (docs/versioning.md, docs/ledger-format.md).
55
+ # Ledgers without a top-level `schema:` field are treated as schema 1
56
+ # (backward compatibility with pre-versioning files). A newer schema than
57
+ # supported is a warning, not an error: report on what is understood.
58
+ SUPPORTED_SCHEMA_VERSION = 1
59
+
60
+
61
+ def parse_date(value) -> dt.date | None:
62
+ if isinstance(value, dt.date):
63
+ return value
64
+ try:
65
+ return dt.date.fromisoformat(str(value))
66
+ except ValueError:
67
+ return None
68
+
69
+
70
+ def check_contract(c: dict, today: dt.date, idx: int) -> tuple[list[str], dict]:
71
+ """Return (errors, metrics) for one contract. Errors are fatal findings;
72
+ metrics feed the health report."""
73
+ errors: list[str] = []
74
+ label = c.get("id") or f"contract[{idx}]"
75
+ status = c.get("status", "active")
76
+
77
+ # required fields
78
+ for field in REQUIRED_FIELDS:
79
+ if field not in c or c[field] in (None, ""):
80
+ errors.append(f"{label}: missing required field '{field}'")
81
+ # non-empty lists
82
+ for field in REQUIRED_LISTS:
83
+ value = c.get(field)
84
+ if value is not None and not (isinstance(value, list) and len(value) > 0):
85
+ errors.append(f"{label}: '{field}' must be a non-empty list")
86
+ # level
87
+ if "level" in c and (not isinstance(c["level"], int) or not 0 <= c["level"] <= 4):
88
+ errors.append(f"{label}: level must be an integer 0..4")
89
+ # status
90
+ if status not in STATUSES:
91
+ errors.append(f"{label}: status must be one of {STATUSES}")
92
+ # dates
93
+ horizon = parse_date(c.get("horizon")) if "horizon" in c else None
94
+ last_review = parse_date(c.get("last_review")) if "last_review" in c else None
95
+ if "horizon" in c and horizon is None:
96
+ errors.append(f"{label}: horizon is not an ISO date")
97
+ if "last_review" in c and last_review is None:
98
+ errors.append(f"{label}: last_review is not an ISO date")
99
+ if horizon and last_review and last_review > horizon:
100
+ errors.append(f"{label}: last_review is after horizon")
101
+
102
+ # health metrics (only meaningful for live contracts)
103
+ cadence = c.get("review_cadence_days", DEFAULT_CADENCE_DAYS)
104
+ stale = expired = False
105
+ if status == "active":
106
+ if last_review and (today - last_review).days > cadence:
107
+ stale = True
108
+ if horizon and today > horizon:
109
+ expired = True
110
+ metrics = {
111
+ "id": c.get("id"),
112
+ "unit": c.get("unit"),
113
+ "domain": c.get("domain"),
114
+ "level": c.get("level"),
115
+ "status": status,
116
+ "stale": stale,
117
+ "expired": expired,
118
+ }
119
+ return errors, metrics
120
+
121
+
122
+ def health_report(ledger: dict, today: dt.date) -> dict:
123
+ schema = ledger.get("schema", SUPPORTED_SCHEMA_VERSION)
124
+ if not isinstance(schema, int) or schema < 1:
125
+ raise ValueError(f"ledger 'schema' must be a positive integer, got {schema!r}")
126
+ warnings: list[str] = []
127
+ if schema > SUPPORTED_SCHEMA_VERSION:
128
+ warnings.append(
129
+ f"ledger declares schema v{schema}; this tool supports v{SUPPORTED_SCHEMA_VERSION} — "
130
+ "validating the v1 fields it understands; upgrade the tool for full support"
131
+ )
132
+
133
+ contracts = ledger.get("contracts")
134
+ if not isinstance(contracts, list):
135
+ raise ValueError("ledger must contain a 'contracts' list")
136
+
137
+ errors: list[str] = []
138
+ metrics: list[dict] = []
139
+ ids: set[str] = set()
140
+ for idx, c in enumerate(contracts):
141
+ if not isinstance(c, dict):
142
+ errors.append(f"contract[{idx}]: must be a mapping")
143
+ continue
144
+ c_errors, c_metrics = check_contract(c, today, idx)
145
+ errors += c_errors
146
+ if c_metrics["id"]:
147
+ if c_metrics["id"] in ids:
148
+ errors.append(f"{c_metrics['id']}: duplicate id")
149
+ ids.add(c_metrics["id"])
150
+ metrics.append(c_metrics)
151
+
152
+ active = [m for m in metrics if m["status"] == "active"]
153
+ valid = [m for m in metrics if m["id"] and m["status"] in ("active", "draft")]
154
+ report = {
155
+ "schema_version": schema,
156
+ "schema_warnings": warnings,
157
+ "total_contracts": len(metrics),
158
+ "active": len(active),
159
+ "valid": len(valid),
160
+ "validity_errors": errors,
161
+ "coverage": round(len(valid) / len(metrics), 2) if metrics else 1.0,
162
+ "stale_reviews": [m["id"] for m in active if m["stale"]],
163
+ "expired_horizons": [m["id"] for m in active if m["expired"]],
164
+ "unhygienic": len(errors),
165
+ "today": today.isoformat(),
166
+ }
167
+ return report
168
+
169
+
170
+ def render(report: dict) -> str:
171
+ lines = ["Autonomy Ledger health", "=" * 40]
172
+ lines.append(
173
+ f"Contracts: {report['total_contracts']} total, {report['active']} active"
174
+ )
175
+ lines.append(f"Coverage: {int(report['coverage'] * 100)}% valid contracts")
176
+ lines.append(f"Ledger schema: v{report['schema_version']}")
177
+ for w in report.get("schema_warnings", []):
178
+ lines.append(f"warning: {w}")
179
+ if report["stale_reviews"]:
180
+ lines.append(f"STALE reviews (past cadence): {', '.join(report['stale_reviews'])}")
181
+ else:
182
+ lines.append("Stale reviews: none")
183
+ if report["expired_horizons"]:
184
+ lines.append(
185
+ f"EXPIRED horizons (active past horizon): {', '.join(report['expired_horizons'])}"
186
+ )
187
+ else:
188
+ lines.append("Expired horizons: none")
189
+ for err in report["validity_errors"]:
190
+ lines.append(f"error: {err}")
191
+ verdict = "HEALTHY" if not (report["validity_errors"] or report["stale_reviews"] or report["expired_horizons"]) else "NEEDS ATTENTION"
192
+ lines.append(f"Verdict: {verdict}")
193
+ return "\n".join(lines)
194
+
195
+
196
+ def _self_test() -> None:
197
+ good = {
198
+ "contracts": [
199
+ {
200
+ "id": "c1", "unit": "u", "domain": "d", "level": 2, "scope": "s",
201
+ "guardrails": ["g"], "telemetry": ["t"],
202
+ "revocation_triggers": ["r"], "grantor": "G",
203
+ "horizon": "2025-12-31", "last_review": "2025-09-30",
204
+ "status": "active",
205
+ }
206
+ ]
207
+ }
208
+ today = dt.date(2025, 10, 1)
209
+ r = health_report(good, today)
210
+ assert not r["validity_errors"] and not r["stale_reviews"], r
211
+ assert r["coverage"] == 1.0
212
+
213
+ # staleness: review 200 days ago, cadence 90
214
+ stale = {"contracts": [dict(good["contracts"][0], last_review="2025-01-01")]}
215
+ r = health_report(stale, today)
216
+ assert r["stale_reviews"] == ["c1"], r
217
+
218
+ # expiry: active contract past horizon
219
+ expired = {"contracts": [dict(good["contracts"][0], horizon="2025-09-30", last_review="2025-09-01")]}
220
+ r = health_report(expired, dt.date(2025, 10, 2))
221
+ assert r["expired_horizons"] == ["c1"], r
222
+
223
+ # schema violations
224
+ bad = {"contracts": [{"id": "c", "level": 7, "guardrails": [], "status": "weird"}]}
225
+ r = health_report(bad, today)
226
+ errs = " ".join(r["validity_errors"])
227
+ assert "level" in errs and "status" in errs and "guardrails" in errs, r["validity_errors"]
228
+
229
+ # duplicate ids
230
+ dup = {"contracts": [good["contracts"][0], dict(good["contracts"][0])]}
231
+ r = health_report(dup, today)
232
+ assert any("duplicate" in e for e in r["validity_errors"]), r["validity_errors"]
233
+
234
+ # last_review after horizon
235
+ inv = {"contracts": [dict(good["contracts"][0], horizon="2025-06-30", last_review="2025-09-30")]}
236
+ r = health_report(inv, today)
237
+ assert any("after horizon" in e for e in r["validity_errors"]), r["validity_errors"]
238
+
239
+ # schema handling: missing field = schema 1 (backward compatible)
240
+ r = health_report({"contracts": [good["contracts"][0]]}, today)
241
+ assert r["schema_version"] == 1 and not r["schema_warnings"], r
242
+
243
+ # explicit schema 1 is accepted
244
+ r = health_report({"schema": 1, "contracts": [good["contracts"][0]]}, today)
245
+ assert r["schema_version"] == 1 and not r["schema_warnings"], r
246
+
247
+ # future schema: warns but still validates (forward compatibility)
248
+ r = health_report({"schema": 2, "contracts": [good["contracts"][0]]}, today)
249
+ assert r["schema_version"] == 2 and r["schema_warnings"], r
250
+ assert not r["validity_errors"], r
251
+
252
+ # invalid schema value
253
+ try:
254
+ health_report({"schema": "one", "contracts": []}, today)
255
+ except ValueError:
256
+ pass
257
+ else:
258
+ raise AssertionError("expected ValueError for non-integer schema")
259
+
260
+ # not a contracts list
261
+ for bad_ledger in ({}, {"contracts": "nope"}):
262
+ try:
263
+ health_report(bad_ledger, today)
264
+ except ValueError:
265
+ pass
266
+ else:
267
+ raise AssertionError("expected ValueError for malformed ledger")
268
+
269
+ print("self-test: OK")
270
+
271
+
272
+ def main(argv: list[str] | None = None) -> int:
273
+ parser = argparse.ArgumentParser(description="Check Autonomy Ledger health (v1 format).")
274
+ parser.add_argument("ledger", nargs="?", help="path to ledger.yaml")
275
+ parser.add_argument("--today", help="override today's date (ISO) for testing")
276
+ parser.add_argument("--json", action="store_true", help="emit JSON")
277
+ parser.add_argument("--self-test", action="store_true", help="run built-in tests and exit")
278
+ args = parser.parse_args(argv)
279
+
280
+ if args.self_test:
281
+ _self_test()
282
+ return 0
283
+
284
+ if not args.ledger:
285
+ parser.error("a ledger file is required (or --self-test)")
286
+
287
+ if yaml is None:
288
+ print("error: PyYAML is required (pip install pyyaml)", file=sys.stderr)
289
+ return 2
290
+
291
+ try:
292
+ with open(args.ledger, encoding="utf-8") as fh:
293
+ ledger = yaml.safe_load(fh)
294
+ except (OSError, yaml.YAMLError) as exc:
295
+ print(f"error: cannot read ledger: {exc}", file=sys.stderr)
296
+ return 2
297
+
298
+ today = parse_date(args.today) if args.today else dt.date.today()
299
+ if today is None:
300
+ print("error: --today must be an ISO date", file=sys.stderr)
301
+ return 2
302
+
303
+ try:
304
+ report = health_report(ledger or {}, today)
305
+ except ValueError as exc:
306
+ print(f"error: {exc}", file=sys.stderr)
307
+ return 2
308
+
309
+ print(json.dumps(report, indent=2) if args.json else render(report))
310
+ bad = report["validity_errors"] or report["stale_reviews"] or report["expired_horizons"]
311
+ return 1 if bad else 0
312
+
313
+
314
+ if __name__ == "__main__":
315
+ sys.exit(main())
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env python3
2
+ """Autonomy score calculator for the Measuring & Managing Autonomy framework.
3
+
4
+ Implements the scoring rules from docs/maturity-model.md and
5
+ docs/assessment.md:
6
+
7
+ - Each dimension (D1..D6) is scored 0..4 (autonomy levels L0..L4).
8
+ - Safe grant level per domain = min(D3, D4, D6) ("weakest-link rule").
9
+ - D1/D2 are coaching signals; they cap expansion pace, not current grants.
10
+ - Recommended move comes from the decision table in the maturity model.
11
+
12
+ Usage:
13
+ autonomy-score 3 3 2 2 1 1 # scores for D1..D6
14
+ autonomy-score --current 1 3 3 2 2 1
15
+ autonomy-score --self-test # run the built-in tests
16
+ python3 tools/autonomy_score.py ... # zero-install form from a checkout
17
+
18
+ Scores are given in order: D1_clarity D2_capability D3_guardrails
19
+ D4_telemetry D5_transparency D6_accountability.
20
+
21
+ License: MIT (see LICENSE).
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import json
27
+ import os
28
+ import re
29
+ import sys
30
+
31
+ DIMENSIONS = [
32
+ ("D1", "decision_clarity"),
33
+ ("D2", "capability"),
34
+ ("D3", "guardrails"),
35
+ ("D4", "telemetry"),
36
+ ("D5", "transparency"),
37
+ ("D6", "accountability"),
38
+ ]
39
+
40
+ WEAKEST_LINK = ("D3", "D4", "D6") # guardrails, telemetry, accountability
41
+ CLEAN_CYCLES_TO_EXPAND = 2 # consecutive clean reviews required to expand
42
+
43
+ # Semantic version of the scoring ruleset. See docs/versioning.md.
44
+ # MAJOR = re-scoring (scores not comparable); MINOR = additive/clarification;
45
+ # PATCH = no change to scored outputs. Kept identical to site/scoring.js;
46
+ # parity is asserted by tools/site_selftest.js.
47
+ RULESET_VERSION = "1.0.0"
48
+
49
+
50
+ def parse_scores(values: list[str]) -> dict[str, int]:
51
+ """Parse and validate six dimension scores (ints 0..4)."""
52
+ if len(values) != 6:
53
+ raise SystemExit(
54
+ f"error: expected exactly 6 scores (D1..D6), got {len(values)}\n"
55
+ "usage: autonomy-score D1 D2 D3 D4 D5 D6"
56
+ )
57
+ scores: dict[str, int] = {}
58
+ for (code, _name), raw in zip(DIMENSIONS, values):
59
+ try:
60
+ score = int(raw)
61
+ except ValueError:
62
+ raise SystemExit(f"error: score for {code} must be an integer, got {raw!r}")
63
+ if not 0 <= score <= 4:
64
+ raise SystemExit(f"error: score for {code} must be in 0..4, got {score}")
65
+ scores[code] = score
66
+ return scores
67
+
68
+
69
+ def safe_level(scores: dict[str, int]) -> int:
70
+ """Weakest-link rule: min over D3, D4, D6."""
71
+ return min(scores[d] for d in WEAKEST_LINK)
72
+
73
+
74
+ def gap_analysis(scores: dict[str, int], current: int | None) -> list[str]:
75
+ """Notes on granted-vs-exercised and coaching signals."""
76
+ notes: list[str] = []
77
+ weakest = min(scores[d] for d in WEAKEST_LINK)
78
+ coaching = [d for d in ("D1", "D2") if scores[d] < weakest]
79
+ if coaching:
80
+ notes.append(
81
+ f"coaching signal: {', '.join(coaching)} below the safety floor — "
82
+ "invest in clarity/capability; do not revoke for this"
83
+ )
84
+ if current is not None:
85
+ headroom = safe_level(scores) - current
86
+ if headroom > 0:
87
+ notes.append(
88
+ f"headroom: safe level exceeds granted level by {headroom}; "
89
+ f"expand after {CLEAN_CYCLES_TO_EXPAND} clean review cycles"
90
+ )
91
+ elif headroom < 0:
92
+ notes.append(
93
+ f"over-grant: granted level exceeds safe level by {-headroom}; "
94
+ "fix guardrails/telemetry/accountability or step down"
95
+ )
96
+ else:
97
+ notes.append("granted level matches safe level: hold and review")
98
+ return notes
99
+
100
+
101
+ def recommended_move(scores: dict[str, int], current: int | None) -> str:
102
+ """Map a score vector to the management move (maturity model §4)."""
103
+ weakest = safe_level(scores)
104
+ if current is None:
105
+ return "formalize: write a contract at the safe level before any change"
106
+ if weakest < current:
107
+ return "contract or re-shape: safety dimensions lag the grant"
108
+ if weakest > current:
109
+ return (
110
+ f"expand (after {CLEAN_CYCLES_TO_EXPAND} clean cycles): "
111
+ "raise one level or widen scope in the strongest dimension"
112
+ )
113
+ if min(scores["D1"], scores["D2"]) < weakest:
114
+ return "hold and coach: capability/clarity lag; keep the grant"
115
+ return "hold: stable at the current level; re-review next horizon"
116
+
117
+
118
+ def assess(scores: dict[str, int], current: int | None) -> dict:
119
+ return {
120
+ "ruleset_version": RULESET_VERSION,
121
+ "scores": {code: scores[code] for code, _ in DIMENSIONS},
122
+ "safe_grant_level": safe_level(scores),
123
+ "granted_level": current,
124
+ "notes": gap_analysis(scores, current),
125
+ "recommended_move": recommended_move(scores, current),
126
+ }
127
+
128
+
129
+ def render(result: dict) -> str:
130
+ lines = ["Autonomy assessment", "=" * 40]
131
+ lines.append(f"Ruleset version: {RULESET_VERSION}")
132
+ for (code, name) in DIMENSIONS:
133
+ lines.append(f"{code} ({name}): {result['scores'][code]}")
134
+ lines += ["", f"Safe grant level (min D3,D4,D6): L{result['safe_grant_level']}"]
135
+ if result["granted_level"] is not None:
136
+ lines.append(f"Currently granted level: L{result['granted_level']}")
137
+ lines.append(f"Recommended move: {result['recommended_move']}")
138
+ for note in result["notes"]:
139
+ lines.append(f"note: {note}")
140
+ return "\n".join(lines)
141
+
142
+
143
+ def _self_test() -> None:
144
+ # Version stamp present in outputs, and identical to the JS engine
145
+ # (parity: docs/versioning.md makes cross-implementation drift a blocker).
146
+ # Enforced when the JS engine sits next to this package (repository
147
+ # checkout, CI); skipped for installed wheels and standalone binaries,
148
+ # which do not ship site/scoring.js — CI keeps enforcing it on push.
149
+ assert RULESET_VERSION == "1.0.0"
150
+ assert assess({d: 1 for d, _ in DIMENSIONS}, None)["ruleset_version"] == RULESET_VERSION
151
+ _repo_root = os.path.join(
152
+ os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir
153
+ )
154
+ _js_path = os.path.join(_repo_root, "site", "scoring.js")
155
+ if os.path.exists(_js_path):
156
+ with open(_js_path, encoding="utf-8") as _fh:
157
+ _js = _fh.read()
158
+ _m = re.search(r'RULESET_VERSION\s*=\s*"([^"]+)"', _js)
159
+ assert _m and _m.group(1) == RULESET_VERSION, (
160
+ f"version drift: py={RULESET_VERSION} js={_m and _m.group(1)}"
161
+ )
162
+ else:
163
+ print("note: JS parity check skipped (site/scoring.js not present; standalone install)")
164
+ # Weakest-link rule
165
+ assert safe_level({d: 4 for d, _ in DIMENSIONS}) == 4
166
+ assert safe_level({"D3": 2, "D4": 3, "D6": 1, "D1": 4, "D2": 4, "D5": 4}) == 1
167
+ # Weakest link ignores D1/D2/D5
168
+ assert safe_level({"D3": 3, "D4": 3, "D6": 3, "D1": 0, "D2": 0, "D5": 0}) == 3
169
+ # Worked example from examples/README.md: scores 1 3 2 2 1 1, no formal grant
170
+ ex = assess({"D1": 1, "D2": 3, "D3": 2, "D4": 2, "D5": 1, "D6": 1}, current=None)
171
+ assert ex["safe_grant_level"] == 1
172
+ assert ex["recommended_move"].startswith("formalize")
173
+ # Coaching signal fires when D1/D2 lag the safety floor
174
+ coach = assess({"D1": 0, "D2": 1, "D3": 3, "D4": 3, "D6": 3, "D5": 3}, current=3)
175
+ assert any("coaching" in n for n in coach["notes"])
176
+ assert coach["recommended_move"].startswith("hold and coach")
177
+ # Over-grant detection
178
+ over = assess({"D1": 4, "D2": 4, "D3": 1, "D4": 4, "D5": 4, "D6": 4}, current=3)
179
+ assert any("over-grant" in n for n in over["notes"])
180
+ assert over["recommended_move"].startswith("contract")
181
+ # Validation errors
182
+ for bad in (["1", "2", "3", "4", "5"], ["1", "2", "3", "4", "5", "x"], ["9"] * 6):
183
+ try:
184
+ parse_scores(bad)
185
+ except SystemExit:
186
+ pass
187
+ else:
188
+ raise AssertionError(f"expected validation failure for {bad}")
189
+ print("self-test: OK")
190
+
191
+
192
+ def main(argv: list[str] | None = None) -> int:
193
+ parser = argparse.ArgumentParser(
194
+ description="Compute safe autonomy level and recommended move from six dimension scores."
195
+ )
196
+ parser.add_argument("scores", nargs="*", help="six scores 0..4 for D1..D6")
197
+ parser.add_argument("--current", type=int, metavar="L", help="currently granted level 0..4")
198
+ parser.add_argument("--json", action="store_true", help="emit JSON")
199
+ parser.add_argument("--self-test", action="store_true", help="run built-in tests and exit")
200
+ args = parser.parse_args(argv)
201
+
202
+ if args.self_test:
203
+ _self_test()
204
+ return 0
205
+
206
+ if args.current is not None and not 0 <= args.current <= 4:
207
+ raise SystemExit("error: --current must be in 0..4")
208
+
209
+ scores = parse_scores(args.scores)
210
+ result = assess(scores, args.current)
211
+ print(json.dumps(result, indent=2) if args.json else render(result))
212
+ return 0
213
+
214
+
215
+ if __name__ == "__main__":
216
+ sys.exit(main())
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: measuring-autonomy
3
+ Version: 1.1.0
4
+ Summary: Measure and manage autonomy: safe-level scoring, Autonomy Ledger health checks, and framework tooling.
5
+ Author: Emmanuel A. Otchere
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy
8
+ Project-URL: Changelog, https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy/blob/main/CHANGELOG.md
9
+ Keywords: autonomy,governance,maturity-model,management,org-design
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Information Technology
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Office/Business
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: pyyaml>=5.4
28
+ Dynamic: license-file
29
+
30
+ # Measuring and Managing Autonomy
31
+
32
+ Autonomy from a business and systems point of view can be challenging to
33
+ manage. This project defines a framework by which managing and measuring
34
+ autonomy can be designed and implemented.
35
+
36
+ Most organizations manage autonomy implicitly — it emerges from trust, tenure,
37
+ and accidents of structure, and is therefore felt but not measured, expanded
38
+ but never designed, and revoked arbitrarily. This framework makes autonomy an
39
+ **explicit, designed, measurable property** of the organization.
40
+
41
+ ## The framework in one diagram
42
+
43
+ ```mermaid
44
+ flowchart LR
45
+ A[Design<br>decision + blast radius] --> B[Grant<br>autonomy contract]
46
+ B --> C[Measure<br>ledger + telemetry]
47
+ C --> D[Manage<br>expand / contract / re-shape]
48
+ D -->|re-grant, next horizon| B
49
+ ```
50
+
51
+ ## What's here
52
+
53
+ | Document | Purpose |
54
+ |---|---|
55
+ | [Framework](docs/framework.md) | Definitions, the three axes, the five levels (L0–L4), autonomy contracts |
56
+ | [Maturity model](docs/maturity-model.md) | Six dimensions × five levels grid, scoring rules, the Autonomy Ledger |
57
+ | [Assessment](docs/assessment.md) | The questionnaire and scoring procedure — the practical instrument |
58
+ | [Governance loop](docs/governance-loop.md) | Grant → Instrument → Observe → Review, with cadence and anti-patterns |
59
+ | [Positioning](docs/positioning.md) | Related work, lineage, open questions, BibTeX |
60
+ | [Worked example](examples/README.md) | One team, scored end to end, producing a governance move |
61
+ | [AI agents as units](docs/agents-as-units.md) | First-pass extension: scoring agent units with the same instrument |
62
+ | [Ledger format](docs/ledger-format.md) | Machine-readable Autonomy Ledger (YAML v1) + health checks |
63
+ | [Contract template](docs/contract-template.md) | Autonomy contract + review agenda, ready to copy |
64
+ | [Versioning](docs/versioning.md) | Ruleset/ledger versioning policy — how assessments stay comparable |
65
+ | [Changelog](CHANGELOG.md) | Every change, classified by comparability impact |
66
+
67
+ ## Install
68
+
69
+ **No Python needed** — standalone binaries for macOS, Linux, and Windows are
70
+ attached to each [GitHub Release](https://github.com/emmanuel-a-otchere/MeasuringAndManagingAutonomy/releases):
71
+ download `autonomy-score-<os>-<arch>` and `ledger-health-<os>-<arch>`, mark
72
+ them executable, run.
73
+
74
+ **With Python 3.9+** (installs the `autonomy-score` and `ledger-health`
75
+ commands):
76
+
77
+ ```bash
78
+ pipx install measuring-autonomy # or: pip install measuring-autonomy
79
+ ```
80
+
81
+ **From a clone**, the zero-install form keeps working with no packaging step:
82
+ `python3 tools/autonomy_score.py ...` (the `tools/` scripts are shims over
83
+ `src/measuring_autonomy/`).
84
+
85
+ ## Quick start: score a team
86
+
87
+ ```bash
88
+ python3 tools/autonomy_score.py 1 3 2 2 1 1 --current 1
89
+ ```
90
+
91
+ Scores are D1 clarity, D2 capability, D3 guardrails, D4 telemetry,
92
+ D5 transparency, D6 accountability (0–4 each). The tool applies the
93
+ weakest-link rule — `min(guardrails, telemetry, accountability)` — and
94
+ outputs the safe grant level and recommended move.
95
+
96
+ ```bash
97
+ python3 tools/autonomy_score.py --self-test # verify the scoring rules
98
+ ```
99
+
100
+ Track granted autonomy in a machine-readable **Autonomy Ledger** (v1 YAML
101
+ format, [`examples/ledger.yaml`](examples/ledger.yaml)) and check its health:
102
+
103
+ ```bash
104
+ python3 tools/ledger_health.py examples/ledger.yaml # requires PyYAML (pip install pyyaml)
105
+ ```
106
+
107
+ ## Try it without a terminal
108
+
109
+ A dependency-free web version of the assessment lives in [`site/`](site/)
110
+ (and is published to GitHub Pages when enabled — repo **Settings → Pages →
111
+ Source: GitHub Actions**). Open `site/index.html` locally, tick the items
112
+ with evidence, and print the scorecard.
113
+
114
+ ## The three questions
115
+
116
+ 1. **Design** — What autonomy should this unit have, and why? →
117
+ [contracts](docs/framework.md#5-autonomy-contracts)
118
+ 2. **Measurement** — How much autonomy exists, and how well is it used? →
119
+ [maturity model](docs/maturity-model.md) + [assessment](docs/assessment.md)
120
+ 3. **Management** — When should autonomy expand, contract, or re-shape? →
121
+ [governance loop](docs/governance-loop.md)
122
+
123
+ ## Roadmap
124
+
125
+ See [docs/roadmap.md](docs/roadmap.md). Current status: **v0.1 draft** — the
126
+ written framework, maturity model, and assessment instrument are complete;
127
+ next up are validation against real teams and optional tooling.
128
+
129
+ ## Contributing
130
+
131
+ Feedback, corrections, and real-world applications are very welcome — see
132
+ [CONTRIBUTING.md](CONTRIBUTING.md). Use the issue templates to report
133
+ framework feedback or submit a case study.
134
+
135
+ ## License
136
+
137
+ [MIT](LICENSE)
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/measuring_autonomy/__init__.py
5
+ src/measuring_autonomy/ledger.py
6
+ src/measuring_autonomy/score.py
7
+ src/measuring_autonomy.egg-info/PKG-INFO
8
+ src/measuring_autonomy.egg-info/SOURCES.txt
9
+ src/measuring_autonomy.egg-info/dependency_links.txt
10
+ src/measuring_autonomy.egg-info/entry_points.txt
11
+ src/measuring_autonomy.egg-info/requires.txt
12
+ src/measuring_autonomy.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ autonomy-score = measuring_autonomy.score:main
3
+ ledger-health = measuring_autonomy.ledger:main
@@ -0,0 +1 @@
1
+ measuring_autonomy