ssot-cli 0.1.11.dev1__tar.gz → 0.1.12.dev9__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 (33) hide show
  1. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/PKG-INFO +11 -5
  2. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/README.md +4 -0
  3. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/pyproject.toml +7 -5
  4. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/claim_cmd.py +20 -5
  5. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/common.py +11 -1
  6. ssot_cli-0.1.12.dev9/src/ssot_cli/config_cmd.py +53 -0
  7. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/conformance_cmd.py +34 -0
  8. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/evidence_cmd.py +8 -2
  9. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/feature_cmd.py +8 -2
  10. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/main.py +49 -2
  11. ssot_cli-0.1.12.dev9/src/ssot_cli/pack_cmd.py +92 -0
  12. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/registry_cmd.py +17 -1
  13. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/release_cmd.py +23 -0
  14. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/test_cmd.py +8 -2
  15. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli.egg-info/PKG-INFO +11 -5
  16. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli.egg-info/SOURCES.txt +2 -0
  17. ssot_cli-0.1.12.dev9/src/ssot_cli.egg-info/requires.txt +7 -0
  18. ssot_cli-0.1.11.dev1/src/ssot_cli.egg-info/requires.txt +0 -6
  19. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/setup.cfg +0 -0
  20. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/__init__.py +0 -0
  21. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/adr_cmd.py +0 -0
  22. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/boundary_cmd.py +0 -0
  23. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/graph_cmd.py +0 -0
  24. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/init_cmd.py +0 -0
  25. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/issue_cmd.py +0 -0
  26. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/profile_cmd.py +0 -0
  27. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/risk_cmd.py +0 -0
  28. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/spec_cmd.py +0 -0
  29. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/upgrade_cmd.py +0 -0
  30. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli/validate_cmd.py +0 -0
  31. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli.egg-info/dependency_links.txt +0 -0
  32. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli.egg-info/entry_points.txt +0 -0
  33. {ssot_cli-0.1.11.dev1 → ssot_cli-0.1.12.dev9}/src/ssot_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssot-cli
3
- Version: 0.1.11.dev1
3
+ Version: 0.1.12.dev9
4
4
  Summary: Primary CLI distribution for ssot-registry.
5
5
  Author-email: Jacob Stewart <jacob@swarmauri.com>
6
6
  License-Expression: Apache-2.0
@@ -19,14 +19,16 @@ Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
22
23
  Classifier: Topic :: Software Development :: Documentation
23
24
  Classifier: Topic :: Software Development :: Quality Assurance
24
25
  Classifier: Topic :: Utilities
25
- Requires-Python: <3.14,>=3.10
26
+ Requires-Python: <3.15,>=3.10
26
27
  Description-Content-Type: text/markdown
27
- Requires-Dist: ssot-contracts<0.3.0,>=0.2.17.dev1
28
- Requires-Dist: ssot-core<0.3.0,>=0.2.17.dev1
29
- Requires-Dist: ssot-conformance<0.3.0,>=0.2.17.dev1
28
+ Requires-Dist: ssot-contracts<0.3.0,>=0.2.18.dev9
29
+ Requires-Dist: ssot-core<0.3.0,>=0.2.18.dev9
30
+ Requires-Dist: ssot-conformance<0.3.0,>=0.2.18.dev9
31
+ Requires-Dist: ssot-pack-contracts<0.3.0,>=0.2.18.dev9
30
32
  Requires-Dist: tomli>=2.0.1; python_version < "3.11"
31
33
 
32
34
  <div align="center">
@@ -39,6 +41,10 @@ Requires-Dist: tomli>=2.0.1; python_version < "3.11"
39
41
  <a href="https://pypi.org/project/ssot-cli/"><img src="https://img.shields.io/pypi/pyversions/ssot-cli?label=Python" alt="Supported Python versions" /></a>
40
42
  <a href="https://pepy.tech/project/ssot-cli"><img src="https://static.pepy.tech/badge/ssot-cli" alt="Downloads" /></a>
41
43
  <a href="https://hits.sh/github.com/groupsum/ssot-registry/"><img src="https://hits.sh/github.com/groupsum/ssot-registry.svg?style=flat-square" alt="Repository hits" /></a>
44
+ <!-- ssot-schema-badges:start -->
45
+ <img src="https://img.shields.io/badge/schema_version-0.5.0-blue" alt="schema_version 0.5.0" />
46
+ <img src="https://img.shields.io/badge/migration%20coverage-12%2F12-brightgreen" alt="Migration coverage 12/12" />
47
+ <!-- ssot-schema-badges:end -->
42
48
  </div>
43
49
 
44
50
  `ssot-cli` is the primary command-line distribution for SSOT.
@@ -8,6 +8,10 @@
8
8
  <a href="https://pypi.org/project/ssot-cli/"><img src="https://img.shields.io/pypi/pyversions/ssot-cli?label=Python" alt="Supported Python versions" /></a>
9
9
  <a href="https://pepy.tech/project/ssot-cli"><img src="https://static.pepy.tech/badge/ssot-cli" alt="Downloads" /></a>
10
10
  <a href="https://hits.sh/github.com/groupsum/ssot-registry/"><img src="https://hits.sh/github.com/groupsum/ssot-registry.svg?style=flat-square" alt="Repository hits" /></a>
11
+ <!-- ssot-schema-badges:start -->
12
+ <img src="https://img.shields.io/badge/schema_version-0.5.0-blue" alt="schema_version 0.5.0" />
13
+ <img src="https://img.shields.io/badge/migration%20coverage-12%2F12-brightgreen" alt="Migration coverage 12/12" />
14
+ <!-- ssot-schema-badges:end -->
11
15
  </div>
12
16
 
13
17
  `ssot-cli` is the primary command-line distribution for SSOT.
@@ -4,16 +4,17 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ssot-cli"
7
- version = "0.1.11.dev1"
7
+ version = "0.1.12.dev9"
8
8
  description = "Primary CLI distribution for ssot-registry."
9
9
  readme = "README.md"
10
- requires-python = ">=3.10,<3.14"
10
+ requires-python = ">=3.10,<3.15"
11
11
  license = "Apache-2.0"
12
12
  authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
13
13
  dependencies = [
14
- "ssot-contracts>=0.2.17.dev1,<0.3.0",
15
- "ssot-core>=0.2.17.dev1,<0.3.0",
16
- "ssot-conformance>=0.2.17.dev1,<0.3.0",
14
+ "ssot-contracts>=0.2.18.dev9,<0.3.0",
15
+ "ssot-core>=0.2.18.dev9,<0.3.0",
16
+ "ssot-conformance>=0.2.18.dev9,<0.3.0",
17
+ "ssot-pack-contracts>=0.2.18.dev9,<0.3.0",
17
18
  "tomli>=2.0.1; python_version < '3.11'",
18
19
  ]
19
20
  keywords = ["ssot", "cli", "registry", "governance", "release-management", "validation", "developer-tools"]
@@ -29,6 +30,7 @@ classifiers = [
29
30
  "Programming Language :: Python :: 3.11",
30
31
  "Programming Language :: Python :: 3.12",
31
32
  "Programming Language :: Python :: 3.13",
33
+ "Programming Language :: Python :: 3.14",
32
34
  "Topic :: Software Development :: Documentation",
33
35
  "Topic :: Software Development :: Quality Assurance",
34
36
  "Topic :: Utilities",
@@ -2,6 +2,7 @@
2
2
 
3
3
  import argparse
4
4
 
5
+ from ssot_contracts.generated.python.enums import ASSURANCE_ORIGINS
5
6
  from ssot_registry.api import (
6
7
  create_entity,
7
8
  delete_entity,
@@ -14,13 +15,14 @@ from ssot_registry.api import (
14
15
  unlink_entities,
15
16
  update_entity,
16
17
  )
17
- from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
18
+ from ssot_cli.common import add_ids_argument, add_origin_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
18
19
 
19
20
 
20
21
  _LINK_MAPPING = {
21
22
  "feature_ids": "feature_ids",
22
23
  "test_ids": "test_ids",
23
24
  "evidence_ids": "evidence_ids",
25
+ "depends_on_claim_ids": "depends_on_claim_ids",
24
26
  }
25
27
 
26
28
 
@@ -42,9 +44,11 @@ def register_claim(subparsers: argparse._SubParsersAction) -> None:
42
44
  create.add_argument("--description", default="", help="What the claim asserts and why it matters.")
43
45
  create.add_argument("--body", default=None, help="Optional longer-form narrative for the claim.")
44
46
  create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the claim body.")
47
+ add_origin_argument(create, choices=sorted(ASSURANCE_ORIGINS), default="repo-local")
45
48
  create.add_argument("--feature-ids", nargs="*", default=[], help="Feature ids the claim is about.")
46
49
  create.add_argument("--test-ids", nargs="*", default=[], help="Test ids that support the claim.")
47
50
  create.add_argument("--evidence-ids", nargs="*", default=[], help="Evidence ids that substantiate the claim.")
51
+ create.add_argument("--depends-on-claim-ids", nargs="*", default=[], help="Lower-tier claim ids this claim builds on.")
48
52
  create.set_defaults(func=run_create)
49
53
 
50
54
  get = claim_sub.add_parser("get", help="Show one claim.", description="Fetch a single claim record by id.")
@@ -55,6 +59,7 @@ def register_claim(subparsers: argparse._SubParsersAction) -> None:
55
59
  list_cmd = claim_sub.add_parser("list", help="List claims.", description="List claim records currently known to the registry.")
56
60
  add_path_argument(list_cmd)
57
61
  add_ids_argument(list_cmd, help_text="Claim ids to include in the list output.")
62
+ add_origin_argument(list_cmd, choices=sorted(ASSURANCE_ORIGINS), default=None)
58
63
  list_cmd.set_defaults(func=run_list)
59
64
 
60
65
  update = claim_sub.add_parser("update", help="Edit claim metadata.", description="Update mutable claim fields without changing its linked support graph.")
@@ -65,6 +70,7 @@ def register_claim(subparsers: argparse._SubParsersAction) -> None:
65
70
  update.add_argument("--description", default=None, help="Replacement claim description.")
66
71
  update.add_argument("--body", default=None, help="Replacement longer-form claim narrative.")
67
72
  update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement claim body.")
73
+ add_origin_argument(update, choices=sorted(ASSURANCE_ORIGINS), default=None)
68
74
  update.set_defaults(func=run_update)
69
75
 
70
76
  delete = claim_sub.add_parser("delete", help="Delete a claim.", description="Remove a claim record from the registry.")
@@ -78,6 +84,7 @@ def register_claim(subparsers: argparse._SubParsersAction) -> None:
78
84
  link.add_argument("--feature-ids", nargs="*", help="Feature ids to attach.")
79
85
  link.add_argument("--test-ids", nargs="*", help="Test ids to attach.")
80
86
  link.add_argument("--evidence-ids", nargs="*", help="Evidence ids to attach.")
87
+ link.add_argument("--depends-on-claim-ids", nargs="*", help="Lower-tier claim ids this claim builds on.")
81
88
  link.set_defaults(func=run_link)
82
89
 
83
90
  unlink = claim_sub.add_parser("unlink", help="Remove related records from a claim.", description="Remove links from a claim to features, tests, or evidence.")
@@ -86,11 +93,13 @@ def register_claim(subparsers: argparse._SubParsersAction) -> None:
86
93
  unlink.add_argument("--feature-ids", nargs="*", help="Feature ids to detach.")
87
94
  unlink.add_argument("--test-ids", nargs="*", help="Test ids to detach.")
88
95
  unlink.add_argument("--evidence-ids", nargs="*", help="Evidence ids to detach.")
96
+ unlink.add_argument("--depends-on-claim-ids", nargs="*", help="Lower-tier claim ids to detach.")
89
97
  unlink.set_defaults(func=run_unlink)
90
98
 
91
99
  evaluate = claim_sub.add_parser("evaluate", help="Evaluate claim support.", description="Recompute claim support and readiness for one claim or the entire registry.")
92
100
  add_path_argument(evaluate)
93
101
  evaluate.add_argument("--claim-id", default=None, help="Claim id to evaluate. Omit to evaluate every claim in the registry.")
102
+ evaluate.add_argument("--include-tier-gate", action="store_true", help="Include machine-actionable claim tier gate evaluation.")
94
103
  evaluate.set_defaults(func=run_evaluate)
95
104
 
96
105
  set_status = claim_sub.add_parser("set-status", help="Advance or revise claim status.", description="Change the lifecycle status of a claim without editing other fields.")
@@ -99,7 +108,11 @@ def register_claim(subparsers: argparse._SubParsersAction) -> None:
99
108
  set_status.add_argument("--status", required=True, choices=["proposed", "declared", "implemented", "asserted", "evidenced", "certified", "promoted", "published", "blocked", "retired"], help="Target lifecycle or publication state.")
100
109
  set_status.set_defaults(func=run_set_status)
101
110
 
102
- set_tier = claim_sub.add_parser("set-tier", help="Change claim tier.", description="Set the assurance tier expected for a claim.")
111
+ set_tier = claim_sub.add_parser(
112
+ "set-tier",
113
+ help="Check claim tier immutability.",
114
+ description="Reject in-place claim tier changes; create a new claim row and use depends_on_claim_ids for append-only tier promotion.",
115
+ )
103
116
  add_path_argument(set_tier)
104
117
  set_tier.add_argument("--id", required=True, help="Claim id whose tier should change.")
105
118
  set_tier.add_argument("--tier", required=True, choices=["T0", "T1", "T2", "T3", "T4"], help="Target assurance tier to assign.")
@@ -123,9 +136,11 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
123
136
  "kind": args.kind,
124
137
  "description": args.description,
125
138
  "body": body,
139
+ "origin": args.origin,
126
140
  "feature_ids": args.feature_ids,
127
141
  "test_ids": args.test_ids,
128
142
  "evidence_ids": args.evidence_ids,
143
+ "depends_on_claim_ids": args.depends_on_claim_ids,
129
144
  }
130
145
  return create_entity(args.path, "claims", row)
131
146
 
@@ -135,12 +150,12 @@ def run_get(args: argparse.Namespace) -> dict[str, object]:
135
150
 
136
151
 
137
152
  def run_list(args: argparse.Namespace) -> dict[str, object]:
138
- return list_entities(args.path, "claims", ids=args.ids)
153
+ return list_entities(args.path, "claims", ids=args.ids, origin=args.origin)
139
154
 
140
155
 
141
156
  def run_update(args: argparse.Namespace) -> dict[str, object]:
142
157
  body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="claim")
143
- changes = compact_dict({"title": args.title, "kind": args.kind, "description": args.description, "body": body})
158
+ changes = compact_dict({"title": args.title, "kind": args.kind, "description": args.description, "body": body, "origin": args.origin})
144
159
  if not changes:
145
160
  raise ValueError("At least one update field is required")
146
161
  return update_entity(args.path, "claims", args.id, changes)
@@ -159,7 +174,7 @@ def run_unlink(args: argparse.Namespace) -> dict[str, object]:
159
174
 
160
175
 
161
176
  def run_evaluate(args: argparse.Namespace) -> dict[str, object]:
162
- return evaluate_claims(path=args.path, claim_id=args.claim_id)
177
+ return evaluate_claims(path=args.path, claim_id=args.claim_id, include_tier_gate=args.include_tier_gate)
163
178
 
164
179
 
165
180
  def run_set_status(args: argparse.Namespace) -> dict[str, object]:
@@ -23,6 +23,16 @@ def add_ids_argument(parser: argparse.ArgumentParser, flag: str = "--ids", *, de
23
23
  parser.add_argument(flag, dest=dest, nargs="+", default=None, help=help_text)
24
24
 
25
25
 
26
+ def add_origin_argument(parser: argparse.ArgumentParser, *, choices: list[str], required: bool = False, default: str | None = None) -> None:
27
+ parser.add_argument(
28
+ "--origin",
29
+ choices=choices,
30
+ required=required,
31
+ default=default,
32
+ help="Assurance row origin owner.",
33
+ )
34
+
35
+
26
36
  def add_optional_bool_argument(
27
37
  parser: argparse.ArgumentParser,
28
38
  name: str,
@@ -81,5 +91,5 @@ def load_text_argument(
81
91
  if inline_value is not None and file_value is not None:
82
92
  raise ValueError(f"{label} accepts only one of body or body_file")
83
93
  if file_value is not None:
84
- return Path(file_value).read_text(encoding="utf-8")
94
+ return Path(file_value).read_text(encoding="utf-8-sig")
85
95
  return inline_value
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from ssot_registry.api import ensure_repo_config, load_repo_config, validate_repo_config
6
+
7
+ from .common import add_path_argument
8
+
9
+
10
+ def register_config(subparsers: argparse._SubParsersAction) -> None:
11
+ config = subparsers.add_parser(
12
+ "config",
13
+ help="Manage repo-local SSOT policy configuration.",
14
+ description="Create, inspect, and validate the repo-local `.ssot/ssot.toml` policy file.",
15
+ )
16
+ config_sub = config.add_subparsers(dest="config_command", required=True)
17
+
18
+ init = config_sub.add_parser(
19
+ "init",
20
+ help="Create or refresh the repo-local config file.",
21
+ description="Ensure that `.ssot/ssot.toml` exists and contains a valid default template.",
22
+ )
23
+ add_path_argument(init)
24
+ init.add_argument("--force", action="store_true", help="Overwrite an existing repo-local config file.")
25
+ init.set_defaults(func=run_init)
26
+
27
+ show = config_sub.add_parser(
28
+ "show",
29
+ help="Show the effective repo-local config.",
30
+ description="Load and display the validated repo-local SSOT config.",
31
+ )
32
+ add_path_argument(show)
33
+ show.set_defaults(func=run_show)
34
+
35
+ validate = config_sub.add_parser(
36
+ "validate",
37
+ help="Validate the repo-local config.",
38
+ description="Parse and validate the repo-local SSOT config file without mutating the repository.",
39
+ )
40
+ add_path_argument(validate)
41
+ validate.set_defaults(func=run_validate)
42
+
43
+
44
+ def run_init(args: argparse.Namespace) -> dict[str, object]:
45
+ return ensure_repo_config(args.path, overwrite=args.force)
46
+
47
+
48
+ def run_show(args: argparse.Namespace) -> dict[str, object]:
49
+ return load_repo_config(args.path)
50
+
51
+
52
+ def run_validate(args: argparse.Namespace) -> dict[str, object]:
53
+ return validate_repo_config(args.path)
@@ -44,6 +44,20 @@ def register_conformance(subparsers: argparse._SubParsersAction) -> None:
44
44
  run.add_argument("--evidence-output", default=None, help="Optional JSON output path for machine-readable evidence.")
45
45
  run.set_defaults(func=run_run)
46
46
 
47
+ origin = conformance_sub.add_parser(
48
+ "origin",
49
+ help="Generate downstream ssot-origin compliance tests.",
50
+ description="Plan or apply generated downstream conformance tests and SSOT rows for synchronized ssot-origin ADR and SPEC obligations.",
51
+ )
52
+ add_path_argument(origin)
53
+ origin.add_argument("--kinds", nargs="*", choices=["adr", "spec"], default=None, help="Limit generation to ADR or SPEC origin obligations.")
54
+ origin.add_argument("--apply", action="store_true", help="Write generated tests and registry rows instead of only reporting the plan.")
55
+ origin.add_argument("--include-claims", action="store_true", help="Generate claim rows and links.")
56
+ origin.add_argument("--include-evidence", action="store_true", help="Generate evidence rows and a machine-readable report.")
57
+ origin.add_argument("--overwrite", action="store_true", help="Allow replacing previously generated files.")
58
+ origin.add_argument("--report-output", default=None, help="Optional output path for the generated origin conformance report.")
59
+ origin.set_defaults(func=run_origin)
60
+
47
61
 
48
62
  def run_profile_list(args: argparse.Namespace) -> dict[str, object]:
49
63
  from ssot_conformance import list_profiles
@@ -86,3 +100,23 @@ def run_run(args: argparse.Namespace) -> dict[str, object]:
86
100
  evidence_output=args.evidence_output,
87
101
  dry_run=args.dry_run,
88
102
  )
103
+
104
+
105
+ def run_origin(args: argparse.Namespace) -> dict[str, object]:
106
+ from ssot_conformance import apply_origin_conformance, plan_origin_conformance
107
+
108
+ if args.apply:
109
+ return apply_origin_conformance(
110
+ args.path,
111
+ kinds=args.kinds,
112
+ include_claims=args.include_claims,
113
+ include_evidence=args.include_evidence,
114
+ overwrite=args.overwrite,
115
+ report_output=args.report_output,
116
+ )
117
+ return plan_origin_conformance(
118
+ args.path,
119
+ kinds=args.kinds,
120
+ include_claims=args.include_claims,
121
+ include_evidence=args.include_evidence,
122
+ )
@@ -2,6 +2,7 @@
2
2
 
3
3
  import argparse
4
4
 
5
+ from ssot_contracts.generated.python.enums import ASSURANCE_ORIGINS
5
6
  from ssot_registry.api import (
6
7
  create_entity,
7
8
  delete_entity,
@@ -12,7 +13,7 @@ from ssot_registry.api import (
12
13
  update_entity,
13
14
  verify_evidence_rows,
14
15
  )
15
- from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
16
+ from ssot_cli.common import add_ids_argument, add_origin_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
16
17
 
17
18
 
18
19
  _LINK_MAPPING = {
@@ -38,6 +39,7 @@ def register_evidence(subparsers: argparse._SubParsersAction) -> None:
38
39
  create.add_argument("--tier", choices=["T0", "T1", "T2", "T3", "T4"], default="T0", help="Assurance tier the evidence contributes toward.")
39
40
  create.add_argument("--body", default=None, help="Optional longer-form narrative for the evidence row.")
40
41
  create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the evidence body.")
42
+ add_origin_argument(create, choices=sorted(ASSURANCE_ORIGINS), default="repo-local")
41
43
  create.add_argument("--evidence-path", dest="evidence_path", required=True, help="Repository-relative location of the evidence artifact.")
42
44
  create.add_argument("--claim-ids", nargs="*", default=[], help="Claim ids supported by the evidence.")
43
45
  create.add_argument("--test-ids", nargs="*", default=[], help="Test ids associated with the evidence.")
@@ -51,6 +53,7 @@ def register_evidence(subparsers: argparse._SubParsersAction) -> None:
51
53
  list_cmd = evidence_sub.add_parser("list", help="List evidence rows.", description="List evidence artifacts currently known to the registry.")
52
54
  add_path_argument(list_cmd)
53
55
  add_ids_argument(list_cmd, help_text="Evidence ids to include in the list output.")
56
+ add_origin_argument(list_cmd, choices=sorted(ASSURANCE_ORIGINS), default=None)
54
57
  list_cmd.set_defaults(func=run_list)
55
58
 
56
59
  update = evidence_sub.add_parser("update", help="Edit evidence metadata.", description="Update mutable evidence fields without changing its link graph.")
@@ -62,6 +65,7 @@ def register_evidence(subparsers: argparse._SubParsersAction) -> None:
62
65
  update.add_argument("--tier", choices=["T0", "T1", "T2", "T3", "T4"], default=None, help="Updated assurance tier contribution.")
63
66
  update.add_argument("--body", default=None, help="Replacement longer-form evidence narrative.")
64
67
  update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement evidence body.")
68
+ add_origin_argument(update, choices=sorted(ASSURANCE_ORIGINS), default=None)
65
69
  update.add_argument("--evidence-path", dest="evidence_path", default=None, help="Updated repository-relative path to the artifact.")
66
70
  update.set_defaults(func=run_update)
67
71
 
@@ -106,6 +110,7 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
106
110
  "kind": args.kind,
107
111
  "tier": args.tier,
108
112
  "body": body,
113
+ "origin": args.origin,
109
114
  "path": args.evidence_path,
110
115
  "claim_ids": args.claim_ids,
111
116
  "test_ids": args.test_ids,
@@ -118,7 +123,7 @@ def run_get(args: argparse.Namespace) -> dict[str, object]:
118
123
 
119
124
 
120
125
  def run_list(args: argparse.Namespace) -> dict[str, object]:
121
- return list_entities(args.path, "evidence", ids=args.ids)
126
+ return list_entities(args.path, "evidence", ids=args.ids, origin=args.origin)
122
127
 
123
128
 
124
129
  def run_update(args: argparse.Namespace) -> dict[str, object]:
@@ -130,6 +135,7 @@ def run_update(args: argparse.Namespace) -> dict[str, object]:
130
135
  "kind": args.kind,
131
136
  "tier": args.tier,
132
137
  "body": body,
138
+ "origin": args.origin,
133
139
  "path": args.evidence_path,
134
140
  }
135
141
  )
@@ -3,6 +3,7 @@
3
3
  import argparse
4
4
 
5
5
  from ssot_contracts.generated.python.enums import (
6
+ ASSURANCE_ORIGINS,
6
7
  CLAIM_TIERS,
7
8
  FEATURE_IMPLEMENTATION_STATUSES,
8
9
  FEATURE_LIFECYCLE_STAGES,
@@ -20,7 +21,7 @@ from ssot_registry.api import (
20
21
  unlink_entities,
21
22
  update_entity,
22
23
  )
23
- from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
24
+ from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_origin_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
24
25
 
25
26
 
26
27
  _LINK_MAPPING = {
@@ -50,6 +51,7 @@ def register_feature(subparsers: argparse._SubParsersAction) -> None:
50
51
  create.add_argument("--description", default="", help="Operator-facing summary of the feature's purpose.")
51
52
  create.add_argument("--body", default=None, help="Optional longer-form narrative for the feature.")
52
53
  create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the feature body.")
54
+ add_origin_argument(create, choices=sorted(ASSURANCE_ORIGINS), default="repo-local")
53
55
  create.add_argument("--implementation-status", choices=sorted(FEATURE_IMPLEMENTATION_STATUSES), default="absent", help="Current implementation state in the codebase.")
54
56
  create.add_argument("--lifecycle-stage", choices=sorted(FEATURE_LIFECYCLE_STAGES), default="active", help="Actual lifecycle state of the feature today.")
55
57
  create.add_argument("--replacement-feature-id", nargs="*", default=[], help="Replacement feature ids if this feature is being deprecated or removed.")
@@ -73,6 +75,7 @@ def register_feature(subparsers: argparse._SubParsersAction) -> None:
73
75
  list_cmd = feature_sub.add_parser("list", help="List features.", description="List feature records currently known to the registry.")
74
76
  add_path_argument(list_cmd)
75
77
  add_ids_argument(list_cmd, help_text="Feature ids to include in the list output.")
78
+ add_origin_argument(list_cmd, choices=sorted(ASSURANCE_ORIGINS), default=None)
76
79
  list_cmd.set_defaults(func=run_list)
77
80
 
78
81
  update = feature_sub.add_parser("update", help="Edit feature metadata.", description="Update mutable feature fields without changing links or planning state.")
@@ -82,6 +85,7 @@ def register_feature(subparsers: argparse._SubParsersAction) -> None:
82
85
  update.add_argument("--description", default=None, help="Replacement feature description.")
83
86
  update.add_argument("--body", default=None, help="Replacement longer-form feature narrative.")
84
87
  update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement feature body.")
88
+ add_origin_argument(update, choices=sorted(ASSURANCE_ORIGINS), default=None)
85
89
  update.add_argument("--implementation-status", choices=sorted(FEATURE_IMPLEMENTATION_STATUSES), default=None, help="Updated implementation state in the codebase.")
86
90
  update.set_defaults(func=run_update)
87
91
 
@@ -168,6 +172,7 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
168
172
  "title": args.title,
169
173
  "description": args.description,
170
174
  "body": body,
175
+ "origin": args.origin,
171
176
  "implementation_status": args.implementation_status,
172
177
  "lifecycle": {
173
178
  "stage": args.lifecycle_stage,
@@ -188,7 +193,7 @@ def run_get(args: argparse.Namespace) -> dict[str, object]:
188
193
 
189
194
 
190
195
  def run_list(args: argparse.Namespace) -> dict[str, object]:
191
- return list_entities(args.path, "features", ids=args.ids)
196
+ return list_entities(args.path, "features", ids=args.ids, origin=args.origin)
192
197
 
193
198
 
194
199
  def run_update(args: argparse.Namespace) -> dict[str, object]:
@@ -198,6 +203,7 @@ def run_update(args: argparse.Namespace) -> dict[str, object]:
198
203
  "title": args.title,
199
204
  "description": args.description,
200
205
  "body": body,
206
+ "origin": args.origin,
201
207
  "implementation_status": args.implementation_status,
202
208
  }
203
209
  )
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+ import re
4
5
  import sys
6
+ from importlib.metadata import PackageNotFoundError, version as package_version
5
7
  from pathlib import Path
6
8
  from typing import Any
7
9
 
@@ -13,11 +15,13 @@ from .adr_cmd import register_adr
13
15
  from .boundary_cmd import register_boundary
14
16
  from .claim_cmd import register_claim
15
17
  from .conformance_cmd import register_conformance
18
+ from .config_cmd import register_config
16
19
  from .evidence_cmd import register_evidence
17
20
  from .feature_cmd import register_feature
18
21
  from .graph_cmd import register_graph
19
22
  from .init_cmd import register_init
20
23
  from .issue_cmd import register_issue
24
+ from .pack_cmd import register_pack
21
25
  from .profile_cmd import register_profile
22
26
  from .registry_cmd import register_registry
23
27
  from .release_cmd import register_release
@@ -27,16 +31,51 @@ from .test_cmd import register_test
27
31
  from .upgrade_cmd import register_upgrade
28
32
  from .validate_cmd import register_validate
29
33
 
34
+ _PACKAGE_NAME = "ssot-cli"
35
+ _PYPROJECT_PATH = Path(__file__).resolve().parents[2] / "pyproject.toml"
36
+ _VERSION_PATTERN = re.compile(r'^version\s*=\s*"(?P<version>[^"]+)"\s*$')
37
+
30
38
 
31
39
  def _default_prog() -> str:
32
40
  executable = Path(sys.argv[0]).name
33
- if executable.endswith(".exe"):
34
- executable = executable[:-4]
41
+ for suffix in (".exe", ".py"):
42
+ if executable.endswith(suffix):
43
+ executable = executable[: -len(suffix)]
44
+ break
35
45
  if executable in {"", "__main__", "-m"}:
36
46
  return "ssot-registry"
37
47
  return executable
38
48
 
39
49
 
50
+ def _read_version_from_pyproject(pyproject_path: Path = _PYPROJECT_PATH) -> str:
51
+ in_project_section = False
52
+
53
+ for line in pyproject_path.read_text(encoding="utf-8").splitlines():
54
+ stripped = line.strip()
55
+
56
+ if stripped.startswith("[") and stripped.endswith("]"):
57
+ in_project_section = stripped == "[project]"
58
+ continue
59
+
60
+ if not in_project_section:
61
+ continue
62
+
63
+ match = _VERSION_PATTERN.match(stripped)
64
+ if match is not None:
65
+ return match.group("version")
66
+
67
+ raise RuntimeError(f"Unable to locate [project].version in {pyproject_path}")
68
+
69
+
70
+ def _cli_version() -> str:
71
+ if _PYPROJECT_PATH.exists():
72
+ return _read_version_from_pyproject()
73
+ try:
74
+ return package_version(_PACKAGE_NAME)
75
+ except PackageNotFoundError:
76
+ return _read_version_from_pyproject()
77
+
78
+
40
79
  def build_parser(*, prog: str | None = None) -> argparse.ArgumentParser:
41
80
  parser = argparse.ArgumentParser(
42
81
  prog=prog or _default_prog(),
@@ -46,6 +85,12 @@ def build_parser(*, prog: str | None = None) -> argparse.ArgumentParser:
46
85
  "implementation features, verification artifacts, and publication-ready releases."
47
86
  ),
48
87
  )
88
+ parser.add_argument(
89
+ "--version",
90
+ action="version",
91
+ version=f"%(prog)s {_cli_version()}",
92
+ help="Print the installed CLI package version and exit.",
93
+ )
49
94
  parser.add_argument(
50
95
  "--output-format",
51
96
  default="json",
@@ -62,12 +107,14 @@ def build_parser(*, prog: str | None = None) -> argparse.ArgumentParser:
62
107
  register_init(subparsers)
63
108
  register_validate(subparsers)
64
109
  register_upgrade(subparsers)
110
+ register_config(subparsers)
65
111
  register_adr(subparsers)
66
112
  register_spec(subparsers)
67
113
  register_feature(subparsers)
68
114
  register_profile(subparsers)
69
115
  register_test(subparsers)
70
116
  register_issue(subparsers)
117
+ register_pack(subparsers)
71
118
  register_claim(subparsers)
72
119
  register_conformance(subparsers)
73
120
  register_evidence(subparsers)
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from ssot_registry.api import inspect_pack, preflight_pack, sync_pack
6
+
7
+ from .common import add_path_argument
8
+
9
+
10
+ def register_pack(subparsers: argparse._SubParsersAction) -> None:
11
+ pack = subparsers.add_parser(
12
+ "pack",
13
+ help="Inspect, preflight, and sync governance packs.",
14
+ description="Operate on self-describing SSOT governance packs that expose the shared pack contract.",
15
+ )
16
+ pack_sub = pack.add_subparsers(dest="pack_command", required=True)
17
+
18
+ inspect = pack_sub.add_parser("inspect", help="Inspect packaged governance-pack metadata and manifests.")
19
+ inspect.add_argument("package", help="Import package name for the governance pack.")
20
+ inspect.add_argument("--manifest", action="store_true", help="Include the full packaged manifest in the response.")
21
+ inspect.set_defaults(func=run_inspect)
22
+
23
+ preflight = pack_sub.add_parser("preflight", help="Validate a governance pack before repository mutation.")
24
+ add_path_argument(preflight)
25
+ preflight.add_argument("package", help="Import package name for the governance pack.")
26
+ preflight.add_argument("--kind", choices=["adr", "adrs", "spec", "specs"], default=None)
27
+ preflight.add_argument("--all", action="store_true", help="Check all document kinds declared by the pack.")
28
+ preflight.add_argument("--manifest", action="store_true", help="Include the full packaged manifest in the response.")
29
+ preflight.add_argument("--pin", default=None, help="Require the pack version to match this exact value.")
30
+ preflight.add_argument("--resolved", action="store_true", help="Include resolved manifest document entries in the response.")
31
+ preflight.add_argument("--trusted-only", action="store_true", help="Require the pack to be trusted by default.")
32
+ preflight.set_defaults(func=run_preflight)
33
+
34
+ sync = pack_sub.add_parser("sync", help="Sync declared governance-pack documents into a repository.")
35
+ add_path_argument(sync)
36
+ sync.add_argument("package", help="Import package name for the governance pack.")
37
+ sync.add_argument("--kind", choices=["adr", "adrs", "spec", "specs"], default=None)
38
+ sync.add_argument("--all", action="store_true", help="Sync all document kinds declared by the pack.")
39
+ sync.add_argument("--trusted-only", action="store_true", help="Require the pack to be trusted by default.")
40
+ sync.add_argument("--trust", action="store_true", help="Record explicit operator approval for syncing the selected pack.")
41
+ sync.add_argument("--dry-run", action="store_true", help="Report sync changes without writing files or registry rows.")
42
+ sync.add_argument("--manifest", action="store_true", help="Include the full packaged manifest in the response.")
43
+ sync.add_argument("--no-sync", action="store_true", help="Run preflight and return without writing files or registry rows.")
44
+ sync.add_argument("--pin", default=None, help="Require the pack version to match this exact value.")
45
+ sync.add_argument("--preflight-only", action="store_true", help="Run only preflight checks through the sync command surface.")
46
+ sync.add_argument("--prune-stale", action="store_true", help="Remove stale extension-pack rows for this pack after successful sync.")
47
+ sync.add_argument("--reservations", action="store_true", help="Include reservation changes in the response.")
48
+ sync.add_argument("--resolved", action="store_true", help="Include resolved manifest document entries in the response.")
49
+ sync.add_argument("--yes", action="store_true", help="Acknowledge noninteractive operator approval for the requested sync.")
50
+ sync.set_defaults(func=run_sync)
51
+
52
+
53
+ def run_inspect(args: argparse.Namespace) -> dict[str, object]:
54
+ payload = inspect_pack(args.package)
55
+ if not args.manifest:
56
+ payload.pop("documents", None)
57
+ return payload
58
+
59
+
60
+ def run_preflight(args: argparse.Namespace) -> dict[str, object]:
61
+ if args.all and args.kind is not None:
62
+ raise ValueError("--all cannot be combined with --kind")
63
+ return preflight_pack(
64
+ args.path,
65
+ args.package,
66
+ kind=None if args.all else args.kind,
67
+ trusted_only=args.trusted_only,
68
+ pin=args.pin,
69
+ include_manifest=args.manifest,
70
+ include_resolved=args.resolved,
71
+ )
72
+
73
+
74
+ def run_sync(args: argparse.Namespace) -> dict[str, object]:
75
+ if args.all and args.kind is not None:
76
+ raise ValueError("--all cannot be combined with --kind")
77
+ return sync_pack(
78
+ args.path,
79
+ args.package,
80
+ kind=None if args.all else args.kind,
81
+ trusted_only=args.trusted_only,
82
+ dry_run=args.dry_run,
83
+ pin=args.pin,
84
+ preflight_only=args.preflight_only,
85
+ no_sync=args.no_sync,
86
+ prune_stale=args.prune_stale,
87
+ include_manifest=args.manifest,
88
+ include_resolved=args.resolved,
89
+ include_reservations=args.reservations,
90
+ trust=args.trust,
91
+ yes=args.yes,
92
+ )
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
 
5
- from ssot_registry.api import export_registry, sync_automated_statuses
5
+ from ssot_registry.api import export_registry, repair_document_hashes, sync_automated_statuses
6
6
 
7
7
 
8
8
  def register_registry(subparsers: argparse._SubParsersAction) -> None:
@@ -32,6 +32,18 @@ def register_registry(subparsers: argparse._SubParsersAction) -> None:
32
32
  sync_statuses.add_argument("--dry-run", action="store_true", help="Report proposed status changes without saving them.")
33
33
  sync_statuses.set_defaults(func=run_sync_statuses)
34
34
 
35
+ repair_doc_hashes = registry_sub.add_parser(
36
+ "repair-doc-hashes",
37
+ help="Refresh mutable repo-local ADR/SPEC content hashes.",
38
+ description=(
39
+ "Repair explicitly selected repo-local ADR and SPEC content_sha256 values after validating each "
40
+ "document at its registered path, then run full registry validation before saving."
41
+ ),
42
+ )
43
+ repair_doc_hashes.add_argument("path", nargs="?", default=".", help="Repository root or registry file to repair.")
44
+ repair_doc_hashes.add_argument("--ids", nargs="+", required=True, help="ADR and SPEC ids whose document hashes should be repaired.")
45
+ repair_doc_hashes.set_defaults(func=run_repair_doc_hashes)
46
+
35
47
 
36
48
  def run_export(args: argparse.Namespace) -> dict[str, object]:
37
49
  return export_registry(path=args.path, output_format=args.format, output=args.output)
@@ -39,3 +51,7 @@ def run_export(args: argparse.Namespace) -> dict[str, object]:
39
51
 
40
52
  def run_sync_statuses(args: argparse.Namespace) -> dict[str, object]:
41
53
  return sync_automated_statuses(path=args.path, dry_run=args.dry_run)
54
+
55
+
56
+ def run_repair_doc_hashes(args: argparse.Namespace) -> dict[str, object]:
57
+ return repair_document_hashes(path=args.path, ids=args.ids)
@@ -18,6 +18,7 @@ from ssot_registry.api import (
18
18
  remove_release_evidence,
19
19
  revoke_release,
20
20
  update_entity,
21
+ verify_local_release,
21
22
  )
22
23
  from ssot_cli.common import add_ids_argument, add_path_argument, compact_dict, load_text_argument
23
24
 
@@ -121,6 +122,18 @@ def register_release(subparsers: argparse._SubParsersAction) -> None:
121
122
  publish.add_argument("--release-id", default=None, help="Release id to publish. Omit to use the active release.")
122
123
  publish.set_defaults(func=run_publish)
123
124
 
125
+ verify_local = release_sub.add_parser(
126
+ "verify-local",
127
+ help="Run local release assurance verification.",
128
+ description="Generate governed source snapshots, artifact manifests, evidence bundles, and a local verification report for a release.",
129
+ )
130
+ add_path_argument(verify_local)
131
+ verify_local.add_argument("--release-id", default=None, help="Release id to verify. Omit to use the active release.")
132
+ verify_local.add_argument("--path-policy", choices=["ssot-only", "declared", "full-repo"], default="ssot-only", help="Source snapshot path policy.")
133
+ verify_local.add_argument("--no-write-artifacts", action="store_true", help="Return the report without writing the verification report artifact.")
134
+ verify_local.add_argument("--blocking", action="store_true", help="Mark the report as intended for blocking release gates.")
135
+ verify_local.set_defaults(func=run_verify_local)
136
+
124
137
  revoke = release_sub.add_parser("revoke", help="Revoke a release.", description="Mark a release revoked and record the reason for operators and auditors.")
125
138
  add_path_argument(revoke)
126
139
  revoke.add_argument("--release-id", required=True, help="Release id to revoke.")
@@ -214,6 +227,16 @@ def run_publish(args: argparse.Namespace) -> dict[str, object]:
214
227
  return publish_release(path=args.path, release_id=args.release_id)
215
228
 
216
229
 
230
+ def run_verify_local(args: argparse.Namespace) -> dict[str, object]:
231
+ return verify_local_release(
232
+ path=args.path,
233
+ release_id=args.release_id,
234
+ path_policy=args.path_policy,
235
+ write_artifacts=not args.no_write_artifacts,
236
+ blocking=args.blocking,
237
+ )
238
+
239
+
217
240
  def run_revoke(args: argparse.Namespace) -> dict[str, object]:
218
241
  return revoke_release(path=args.path, release_id=args.release_id, reason=args.reason)
219
242
 
@@ -2,8 +2,9 @@
2
2
 
3
3
  import argparse
4
4
 
5
+ from ssot_contracts.generated.python.enums import ASSURANCE_ORIGINS
5
6
  from ssot_registry.api import create_entity, delete_entity, get_entity, link_entities, list_entities, run_tests, unlink_entities, update_entity
6
- from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict, load_json_object_argument, load_text_argument
7
+ from ssot_cli.common import add_ids_argument, add_origin_argument, add_path_argument, collect_list_fields, compact_dict, load_json_object_argument, load_text_argument
7
8
 
8
9
 
9
10
  _LINK_MAPPING = {
@@ -27,6 +28,7 @@ def register_test(subparsers: argparse._SubParsersAction) -> None:
27
28
  create.add_argument("--title", required=True, help="Human-readable test title.")
28
29
  create.add_argument("--body", default=None, help="Optional longer-form narrative for the test.")
29
30
  create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the test body.")
31
+ add_origin_argument(create, choices=sorted(ASSURANCE_ORIGINS), default="repo-local")
30
32
  create.add_argument("--status", choices=["planned", "passing", "failing", "blocked", "skipped"], default="planned", help="Current execution or readiness state of the test.")
31
33
  create.add_argument("--kind", required=True, help="Operator-defined test category such as unit, integration, or manual.")
32
34
  create.add_argument("--test-path", dest="test_path", required=True, help="Repository-relative location of the executable test or test specification.")
@@ -45,6 +47,7 @@ def register_test(subparsers: argparse._SubParsersAction) -> None:
45
47
  list_cmd = test_sub.add_parser("list", help="List tests.", description="List test records currently known to the registry.")
46
48
  add_path_argument(list_cmd)
47
49
  add_ids_argument(list_cmd, help_text="Test ids to include in the list output.")
50
+ add_origin_argument(list_cmd, choices=sorted(ASSURANCE_ORIGINS), default=None)
48
51
  list_cmd.set_defaults(func=run_list)
49
52
 
50
53
  update = test_sub.add_parser("update", help="Edit test metadata.", description="Update mutable test fields without changing link relationships.")
@@ -53,6 +56,7 @@ def register_test(subparsers: argparse._SubParsersAction) -> None:
53
56
  update.add_argument("--title", default=None, help="Replacement test title.")
54
57
  update.add_argument("--body", default=None, help="Replacement longer-form test narrative.")
55
58
  update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement test body.")
59
+ add_origin_argument(update, choices=sorted(ASSURANCE_ORIGINS), default=None)
56
60
  update.add_argument("--status", choices=["planned", "passing", "failing", "blocked", "skipped"], default=None, help="Updated execution or readiness state.")
57
61
  update.add_argument("--kind", default=None, help="Updated test category.")
58
62
  update.add_argument("--test-path", dest="test_path", default=None, help="Updated repository-relative path to the test or procedure.")
@@ -113,6 +117,7 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
113
117
  "id": args.id,
114
118
  "title": args.title,
115
119
  "body": body,
120
+ "origin": args.origin,
116
121
  "status": args.status,
117
122
  "kind": args.kind,
118
123
  "path": args.test_path,
@@ -129,7 +134,7 @@ def run_get(args: argparse.Namespace) -> dict[str, object]:
129
134
 
130
135
 
131
136
  def run_list(args: argparse.Namespace) -> dict[str, object]:
132
- return list_entities(args.path, "tests", ids=args.ids)
137
+ return list_entities(args.path, "tests", ids=args.ids, origin=args.origin)
133
138
 
134
139
 
135
140
  def run_update(args: argparse.Namespace) -> dict[str, object]:
@@ -143,6 +148,7 @@ def run_update(args: argparse.Namespace) -> dict[str, object]:
143
148
  {
144
149
  "title": args.title,
145
150
  "body": body,
151
+ "origin": args.origin,
146
152
  "status": args.status,
147
153
  "kind": args.kind,
148
154
  "path": args.test_path,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssot-cli
3
- Version: 0.1.11.dev1
3
+ Version: 0.1.12.dev9
4
4
  Summary: Primary CLI distribution for ssot-registry.
5
5
  Author-email: Jacob Stewart <jacob@swarmauri.com>
6
6
  License-Expression: Apache-2.0
@@ -19,14 +19,16 @@ Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
22
23
  Classifier: Topic :: Software Development :: Documentation
23
24
  Classifier: Topic :: Software Development :: Quality Assurance
24
25
  Classifier: Topic :: Utilities
25
- Requires-Python: <3.14,>=3.10
26
+ Requires-Python: <3.15,>=3.10
26
27
  Description-Content-Type: text/markdown
27
- Requires-Dist: ssot-contracts<0.3.0,>=0.2.17.dev1
28
- Requires-Dist: ssot-core<0.3.0,>=0.2.17.dev1
29
- Requires-Dist: ssot-conformance<0.3.0,>=0.2.17.dev1
28
+ Requires-Dist: ssot-contracts<0.3.0,>=0.2.18.dev9
29
+ Requires-Dist: ssot-core<0.3.0,>=0.2.18.dev9
30
+ Requires-Dist: ssot-conformance<0.3.0,>=0.2.18.dev9
31
+ Requires-Dist: ssot-pack-contracts<0.3.0,>=0.2.18.dev9
30
32
  Requires-Dist: tomli>=2.0.1; python_version < "3.11"
31
33
 
32
34
  <div align="center">
@@ -39,6 +41,10 @@ Requires-Dist: tomli>=2.0.1; python_version < "3.11"
39
41
  <a href="https://pypi.org/project/ssot-cli/"><img src="https://img.shields.io/pypi/pyversions/ssot-cli?label=Python" alt="Supported Python versions" /></a>
40
42
  <a href="https://pepy.tech/project/ssot-cli"><img src="https://static.pepy.tech/badge/ssot-cli" alt="Downloads" /></a>
41
43
  <a href="https://hits.sh/github.com/groupsum/ssot-registry/"><img src="https://hits.sh/github.com/groupsum/ssot-registry.svg?style=flat-square" alt="Repository hits" /></a>
44
+ <!-- ssot-schema-badges:start -->
45
+ <img src="https://img.shields.io/badge/schema_version-0.5.0-blue" alt="schema_version 0.5.0" />
46
+ <img src="https://img.shields.io/badge/migration%20coverage-12%2F12-brightgreen" alt="Migration coverage 12/12" />
47
+ <!-- ssot-schema-badges:end -->
42
48
  </div>
43
49
 
44
50
  `ssot-cli` is the primary command-line distribution for SSOT.
@@ -5,6 +5,7 @@ src/ssot_cli/adr_cmd.py
5
5
  src/ssot_cli/boundary_cmd.py
6
6
  src/ssot_cli/claim_cmd.py
7
7
  src/ssot_cli/common.py
8
+ src/ssot_cli/config_cmd.py
8
9
  src/ssot_cli/conformance_cmd.py
9
10
  src/ssot_cli/evidence_cmd.py
10
11
  src/ssot_cli/feature_cmd.py
@@ -12,6 +13,7 @@ src/ssot_cli/graph_cmd.py
12
13
  src/ssot_cli/init_cmd.py
13
14
  src/ssot_cli/issue_cmd.py
14
15
  src/ssot_cli/main.py
16
+ src/ssot_cli/pack_cmd.py
15
17
  src/ssot_cli/profile_cmd.py
16
18
  src/ssot_cli/registry_cmd.py
17
19
  src/ssot_cli/release_cmd.py
@@ -0,0 +1,7 @@
1
+ ssot-contracts<0.3.0,>=0.2.18.dev9
2
+ ssot-core<0.3.0,>=0.2.18.dev9
3
+ ssot-conformance<0.3.0,>=0.2.18.dev9
4
+ ssot-pack-contracts<0.3.0,>=0.2.18.dev9
5
+
6
+ [:python_version < "3.11"]
7
+ tomli>=2.0.1
@@ -1,6 +0,0 @@
1
- ssot-contracts<0.3.0,>=0.2.17.dev1
2
- ssot-core<0.3.0,>=0.2.17.dev1
3
- ssot-conformance<0.3.0,>=0.2.17.dev1
4
-
5
- [:python_version < "3.11"]
6
- tomli>=2.0.1
File without changes