cuff-cli 0.1.0__tar.gz → 0.2.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.
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/PKG-INFO +29 -12
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/README.md +28 -11
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff/__init__.py +1 -1
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff/cli.py +29 -7
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff/gate.py +39 -11
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff/ledger.py +5 -3
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff/subject.py +3 -2
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff/workspace.py +13 -7
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff_cli.egg-info/PKG-INFO +29 -12
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/pyproject.toml +1 -1
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/LICENSE +0 -0
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff/__main__.py +0 -0
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff/errors.py +0 -0
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff/git.py +0 -0
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff_cli.egg-info/SOURCES.txt +0 -0
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff_cli.egg-info/dependency_links.txt +0 -0
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff_cli.egg-info/entry_points.txt +0 -0
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/core/cuff_cli.egg-info/top_level.txt +0 -0
- {cuff_cli-0.1.0 → cuff_cli-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cuff-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Cuff subject-bound claim, evidence, and verification gate.
|
|
5
5
|
Author: Fab7
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -38,7 +38,7 @@ worktree, or stages, commits, fetches, pushes, releases, or deploys anything.
|
|
|
38
38
|
Install a released version as a standard uv-managed tool:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
uv tool install cuff-cli==0.
|
|
41
|
+
uv tool install cuff-cli==0.2.0
|
|
42
42
|
cuff --version
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -57,12 +57,12 @@ Run initialization at the exact Git worktree root:
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
cuff init --json
|
|
60
|
-
git add .cuff/project.json
|
|
60
|
+
git add .fab7/cuff/project.json
|
|
61
61
|
git commit -m "Initialize Cuff"
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
The marker is exactly `{"schema":1}` and records live under
|
|
65
|
-
`.cuff/records/`. An incompatible marker is never rewritten or migrated.
|
|
65
|
+
`.fab7/cuff/records/`. An incompatible marker is never rewritten or migrated.
|
|
66
66
|
|
|
67
67
|
The preferred path atomically appends a claim and its observed evidence:
|
|
68
68
|
|
|
@@ -123,19 +123,36 @@ spend, or residual-risk authority.
|
|
|
123
123
|
|
|
124
124
|
## Static host integrations
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
uv-managed `cuff` executable on `PATH
|
|
126
|
+
One native payload lives in [`plugins/cuff`](plugins/cuff) and contains both
|
|
127
|
+
host manifests, Claude Code commands, and Codex skills. The shared
|
|
128
|
+
`fab7hq/fab7` marketplace owns registration; this repository owns the payload.
|
|
129
|
+
The assets require the uv-managed `cuff` executable on `PATH`.
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
132
|
# Codex
|
|
133
|
-
codex plugin marketplace add fab7hq/
|
|
134
|
-
codex plugin add cuff@
|
|
133
|
+
codex plugin marketplace add fab7hq/fab7
|
|
134
|
+
codex plugin add cuff@fab7
|
|
135
135
|
|
|
136
136
|
# Claude Code
|
|
137
|
-
claude plugin marketplace add fab7hq/
|
|
138
|
-
claude plugin install cuff@
|
|
137
|
+
claude plugin marketplace add fab7hq/fab7
|
|
138
|
+
claude plugin install cuff@fab7 --scope user
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Validate the built candidate and both host payloads without touching the
|
|
142
|
+
normal host configuration:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv build --out-dir ../sandbox/cuff-01/dist
|
|
146
|
+
uv run python tools/local_release_check.py --host all
|
|
147
|
+
uv run python tools/local_release_check.py --host all --prepare-auth \
|
|
148
|
+
--candidate-commit COMMIT \
|
|
149
|
+
--evidence-dir ../sandbox/cuff-01/e2e
|
|
150
|
+
# Log each CLI in using the isolated home paths printed by the prepare phase.
|
|
151
|
+
uv run python tools/local_release_check.py --host all --live --reuse-prepared \
|
|
152
|
+
--candidate-commit COMMIT \
|
|
153
|
+
--evidence-dir ../sandbox/cuff-01/e2e \
|
|
154
|
+
--codex-model MODEL \
|
|
155
|
+
--claude-model MODEL
|
|
139
156
|
```
|
|
140
157
|
|
|
141
158
|
See [RUNBOOK.md](RUNBOOK.md) for operations, [the architecture overview](docs/architecture/overview.md)
|
|
@@ -27,7 +27,7 @@ worktree, or stages, commits, fetches, pushes, releases, or deploys anything.
|
|
|
27
27
|
Install a released version as a standard uv-managed tool:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
uv tool install cuff-cli==0.
|
|
30
|
+
uv tool install cuff-cli==0.2.0
|
|
31
31
|
cuff --version
|
|
32
32
|
```
|
|
33
33
|
|
|
@@ -46,12 +46,12 @@ Run initialization at the exact Git worktree root:
|
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
cuff init --json
|
|
49
|
-
git add .cuff/project.json
|
|
49
|
+
git add .fab7/cuff/project.json
|
|
50
50
|
git commit -m "Initialize Cuff"
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
The marker is exactly `{"schema":1}` and records live under
|
|
54
|
-
`.cuff/records/`. An incompatible marker is never rewritten or migrated.
|
|
54
|
+
`.fab7/cuff/records/`. An incompatible marker is never rewritten or migrated.
|
|
55
55
|
|
|
56
56
|
The preferred path atomically appends a claim and its observed evidence:
|
|
57
57
|
|
|
@@ -112,19 +112,36 @@ spend, or residual-risk authority.
|
|
|
112
112
|
|
|
113
113
|
## Static host integrations
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
uv-managed `cuff` executable on `PATH
|
|
115
|
+
One native payload lives in [`plugins/cuff`](plugins/cuff) and contains both
|
|
116
|
+
host manifests, Claude Code commands, and Codex skills. The shared
|
|
117
|
+
`fab7hq/fab7` marketplace owns registration; this repository owns the payload.
|
|
118
|
+
The assets require the uv-managed `cuff` executable on `PATH`.
|
|
119
119
|
|
|
120
120
|
```bash
|
|
121
121
|
# Codex
|
|
122
|
-
codex plugin marketplace add fab7hq/
|
|
123
|
-
codex plugin add cuff@
|
|
122
|
+
codex plugin marketplace add fab7hq/fab7
|
|
123
|
+
codex plugin add cuff@fab7
|
|
124
124
|
|
|
125
125
|
# Claude Code
|
|
126
|
-
claude plugin marketplace add fab7hq/
|
|
127
|
-
claude plugin install cuff@
|
|
126
|
+
claude plugin marketplace add fab7hq/fab7
|
|
127
|
+
claude plugin install cuff@fab7 --scope user
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Validate the built candidate and both host payloads without touching the
|
|
131
|
+
normal host configuration:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv build --out-dir ../sandbox/cuff-01/dist
|
|
135
|
+
uv run python tools/local_release_check.py --host all
|
|
136
|
+
uv run python tools/local_release_check.py --host all --prepare-auth \
|
|
137
|
+
--candidate-commit COMMIT \
|
|
138
|
+
--evidence-dir ../sandbox/cuff-01/e2e
|
|
139
|
+
# Log each CLI in using the isolated home paths printed by the prepare phase.
|
|
140
|
+
uv run python tools/local_release_check.py --host all --live --reuse-prepared \
|
|
141
|
+
--candidate-commit COMMIT \
|
|
142
|
+
--evidence-dir ../sandbox/cuff-01/e2e \
|
|
143
|
+
--codex-model MODEL \
|
|
144
|
+
--claude-model MODEL
|
|
128
145
|
```
|
|
129
146
|
|
|
130
147
|
See [RUNBOOK.md](RUNBOOK.md) for operations, [the architecture overview](docs/architecture/overview.md)
|
|
@@ -11,7 +11,7 @@ from typing import Any
|
|
|
11
11
|
from . import __version__
|
|
12
12
|
from .errors import CuffError
|
|
13
13
|
from .gate import check
|
|
14
|
-
from .ledger import append, create_claim, seal, verify
|
|
14
|
+
from .ledger import append, create_claim, normalize_work_item, seal, verify
|
|
15
15
|
from .subject import declared_subject, filesystem_subject
|
|
16
16
|
from .workspace import find_workspace, initialize_workspace
|
|
17
17
|
|
|
@@ -137,13 +137,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
137
137
|
except KeyboardInterrupt:
|
|
138
138
|
return 130
|
|
139
139
|
except Exception as exc:
|
|
140
|
+
error = {"code": "CUFF_UNEXPECTED", "message": f"{type(exc).__name__}: {exc}"}
|
|
141
|
+
if getattr(args, "command", None) == "check":
|
|
142
|
+
data = _check_error(args, error)
|
|
143
|
+
else:
|
|
144
|
+
data = {"ok": False, "errors": [error]}
|
|
140
145
|
if getattr(args, "json", False):
|
|
141
|
-
print(json.dumps(
|
|
142
|
-
"ok": False,
|
|
143
|
-
"errors": [{"code": "CUFF_UNEXPECTED", "message": f"{type(exc).__name__}: {exc}"}],
|
|
144
|
-
}, sort_keys=True, indent=2))
|
|
146
|
+
print(json.dumps(data, sort_keys=True, indent=2))
|
|
145
147
|
else:
|
|
146
|
-
print(f"
|
|
148
|
+
print(f"{error['code']}: {error['message']}", file=sys.stderr)
|
|
147
149
|
return 3
|
|
148
150
|
return 2
|
|
149
151
|
|
|
@@ -211,7 +213,11 @@ def _finish_result(args: argparse.Namespace, data: dict[str, Any], label: str) -
|
|
|
211
213
|
|
|
212
214
|
|
|
213
215
|
def _finish_error(args: argparse.Namespace, error: CuffError) -> int:
|
|
214
|
-
data =
|
|
216
|
+
data = (
|
|
217
|
+
_check_error(args, error.to_dict())
|
|
218
|
+
if getattr(args, "command", None) == "check"
|
|
219
|
+
else {"ok": False, "errors": [error.to_dict()]}
|
|
220
|
+
)
|
|
215
221
|
if getattr(args, "json", False):
|
|
216
222
|
print(json.dumps(data, sort_keys=True, indent=2))
|
|
217
223
|
else:
|
|
@@ -219,6 +225,22 @@ def _finish_error(args: argparse.Namespace, error: CuffError) -> int:
|
|
|
219
225
|
return 1
|
|
220
226
|
|
|
221
227
|
|
|
228
|
+
def _check_error(args: argparse.Namespace, error: dict[str, Any]) -> dict[str, Any]:
|
|
229
|
+
work_item = getattr(args, "work_item", None)
|
|
230
|
+
try:
|
|
231
|
+
work_item = normalize_work_item(work_item)
|
|
232
|
+
except CuffError:
|
|
233
|
+
pass
|
|
234
|
+
return {
|
|
235
|
+
"ok": False,
|
|
236
|
+
"errors": [error],
|
|
237
|
+
"work_item": work_item,
|
|
238
|
+
"latest_claim": None,
|
|
239
|
+
"selected_evidence": None,
|
|
240
|
+
"record_count": 0,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
222
244
|
def _replay(content: bytes, stream: Any) -> None:
|
|
223
245
|
if content:
|
|
224
246
|
stream.write(content.decode(errors="replace"))
|
|
@@ -43,26 +43,54 @@ def check(
|
|
|
43
43
|
_fail(result["errors"], "CUFF_CLAIM_MISSING", "No completion claim exists", work_item=normalized)
|
|
44
44
|
return result
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
linked = [
|
|
47
47
|
record
|
|
48
48
|
for record in records
|
|
49
49
|
if record["type"] == "evidence"
|
|
50
50
|
and record["claim"] == claim["id"]
|
|
51
|
-
and record["exit_code"] == 0
|
|
52
51
|
]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
if not linked:
|
|
53
|
+
_fail(
|
|
54
|
+
result["errors"],
|
|
55
|
+
"CUFF_EVIDENCE_MISSING",
|
|
56
|
+
"Latest claim has no linked evidence",
|
|
57
|
+
work_item=normalized,
|
|
58
|
+
record_id=claim["id"],
|
|
59
|
+
)
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
passing = [record for record in linked if record["exit_code"] == 0]
|
|
63
|
+
if not passing:
|
|
64
|
+
_fail(
|
|
65
|
+
result["errors"],
|
|
66
|
+
"CUFF_EVIDENCE_FAILED",
|
|
67
|
+
"Latest claim has linked evidence, but none passed",
|
|
68
|
+
work_item=normalized,
|
|
69
|
+
record_id=claim["id"],
|
|
70
|
+
)
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
if not _subject_fresh(root, claim):
|
|
74
|
+
_fail(
|
|
75
|
+
result["errors"],
|
|
76
|
+
"CUFF_SUBJECT_STALE",
|
|
77
|
+
"Latest claim subject changed or cannot be recomputed",
|
|
78
|
+
work_item=normalized,
|
|
79
|
+
record_id=claim["id"],
|
|
80
|
+
)
|
|
60
81
|
return result
|
|
61
82
|
|
|
83
|
+
if git_context is not None and not result["errors"]:
|
|
84
|
+
for evidence in reversed(passing):
|
|
85
|
+
if _evidence_fresh(root, evidence, head, git_context):
|
|
86
|
+
result["selected_evidence"] = evidence
|
|
87
|
+
result["ok"] = True
|
|
88
|
+
return result
|
|
89
|
+
|
|
62
90
|
_fail(
|
|
63
91
|
result["errors"],
|
|
64
|
-
"
|
|
65
|
-
"
|
|
92
|
+
"CUFF_EVIDENCE_STALE",
|
|
93
|
+
"Passing evidence no longer applies to the selected Git state",
|
|
66
94
|
work_item=normalized,
|
|
67
95
|
record_id=claim["id"],
|
|
68
96
|
)
|
|
@@ -21,7 +21,8 @@ from .errors import CuffError
|
|
|
21
21
|
from .subject import SHA256_RE, current_subject, validate_subject
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
STATE_DIR = ".fab7/cuff"
|
|
25
|
+
RECORDS_DIR = ".fab7/cuff/records"
|
|
25
26
|
WORK_ITEM_RE = re.compile(r"^[a-z0-9_.-]{1,120}$")
|
|
26
27
|
RECORD_ID_RE = re.compile(r"^rec_[0-9a-f]{32}$")
|
|
27
28
|
GIT_OID_RE = re.compile(r"^[0-9a-f]{40}(?:[0-9a-f]{24})?$")
|
|
@@ -91,7 +92,8 @@ def resolve_actor(explicit: str | None) -> str:
|
|
|
91
92
|
|
|
92
93
|
def init(root: Path) -> Path:
|
|
93
94
|
path = root / RECORDS_DIR
|
|
94
|
-
|
|
95
|
+
state = root / STATE_DIR
|
|
96
|
+
if state.parent.is_symlink() or state.is_symlink() or path.is_symlink():
|
|
95
97
|
raise CuffError("CUFF_PATH_INVALID", "Cuff workspace directories must not be symlinks")
|
|
96
98
|
path.mkdir(parents=True, exist_ok=True)
|
|
97
99
|
read_all(root)
|
|
@@ -315,7 +317,7 @@ def validate_record(record: Any) -> None:
|
|
|
315
317
|
if "git_ref" in record:
|
|
316
318
|
raise CuffError(
|
|
317
319
|
"CUFF_LEDGER_INVALID",
|
|
318
|
-
"Ledger schema is incompatible; archive or remove .cuff and run cuff init",
|
|
320
|
+
"Ledger schema is incompatible; archive or remove .fab7/cuff and run cuff init",
|
|
319
321
|
)
|
|
320
322
|
record_type = record.get("type")
|
|
321
323
|
allowed = (
|
|
@@ -22,6 +22,7 @@ MAX_FILES = 10_000
|
|
|
22
22
|
MAX_BYTES = 256 * 1024 * 1024
|
|
23
23
|
MAX_DEPTH = 64
|
|
24
24
|
MAX_NAME_BYTES = 255
|
|
25
|
+
STATE_PARTS = (".fab7", "cuff")
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def declared_subject(kind: str, ref: str, digest: str) -> dict[str, str]:
|
|
@@ -107,7 +108,7 @@ def _bounded_target(workspace: Path, path: Path) -> tuple[Path, str]:
|
|
|
107
108
|
relative = lexical.relative_to(workspace)
|
|
108
109
|
except ValueError as exc:
|
|
109
110
|
raise CuffError("CUFF_SUBJECT_PATH_INVALID", "Subject path escapes the workspace") from exc
|
|
110
|
-
if relative.parts[:
|
|
111
|
+
if relative.parts[:len(STATE_PARTS)] == STATE_PARTS:
|
|
111
112
|
raise CuffError("CUFF_SUBJECT_PATH_INVALID", "Cuff controller state cannot be a subject")
|
|
112
113
|
current = workspace
|
|
113
114
|
for part in relative.parts:
|
|
@@ -143,7 +144,7 @@ def _tree_entries(workspace: Path, target: Path) -> list[dict[str, Any]]:
|
|
|
143
144
|
for name in names:
|
|
144
145
|
candidate = directory_path / name
|
|
145
146
|
workspace_relative = candidate.relative_to(workspace)
|
|
146
|
-
if workspace_relative.parts[:
|
|
147
|
+
if workspace_relative.parts[:len(STATE_PARTS)] == STATE_PARTS:
|
|
147
148
|
continue
|
|
148
149
|
directory_mode = candidate.stat(follow_symlinks=False).st_mode
|
|
149
150
|
if stat.S_ISLNK(directory_mode):
|
|
@@ -10,11 +10,11 @@ from typing import Any
|
|
|
10
10
|
|
|
11
11
|
from . import git
|
|
12
12
|
from .errors import CuffError
|
|
13
|
-
from .ledger import RECORDS_DIR, init as init_records
|
|
13
|
+
from .ledger import RECORDS_DIR, STATE_DIR, init as init_records
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
MAX_PARENT_WALK = 64
|
|
17
|
-
PROJECT_PATH = Path(".cuff/project.json")
|
|
17
|
+
PROJECT_PATH = Path(".fab7/cuff/project.json")
|
|
18
18
|
PROJECT_MARKER = {"schema": 1}
|
|
19
19
|
PROJECT_MARKER_BYTES = b'{"schema":1}\n'
|
|
20
20
|
|
|
@@ -28,7 +28,7 @@ def initialize_workspace(
|
|
|
28
28
|
start = (cwd or Path.cwd()).resolve()
|
|
29
29
|
root = _existing_directory(explicit or start, start)
|
|
30
30
|
_require_git_root(root)
|
|
31
|
-
state = root /
|
|
31
|
+
state = root / STATE_DIR
|
|
32
32
|
marker = root / PROJECT_PATH
|
|
33
33
|
_require_safe_state_paths(state, marker)
|
|
34
34
|
_require_safe_records_path(root / RECORDS_DIR)
|
|
@@ -37,7 +37,7 @@ def initialize_workspace(
|
|
|
37
37
|
if existed:
|
|
38
38
|
_read_marker(marker)
|
|
39
39
|
else:
|
|
40
|
-
state.mkdir(mode=0o755, exist_ok=True)
|
|
40
|
+
state.mkdir(mode=0o755, parents=True, exist_ok=True)
|
|
41
41
|
init_records(root)
|
|
42
42
|
if not existed:
|
|
43
43
|
_create_marker(marker)
|
|
@@ -63,9 +63,9 @@ def find_workspace(
|
|
|
63
63
|
|
|
64
64
|
current = start
|
|
65
65
|
for _ in range(MAX_PARENT_WALK):
|
|
66
|
-
state = current /
|
|
66
|
+
state = current / STATE_DIR
|
|
67
67
|
marker = current / PROJECT_PATH
|
|
68
|
-
if state.is_symlink() or (state.exists() and not state.is_dir()):
|
|
68
|
+
if state.parent.is_symlink() or state.is_symlink() or (state.exists() and not state.is_dir()):
|
|
69
69
|
raise CuffError(
|
|
70
70
|
"CUFF_PATH_INVALID",
|
|
71
71
|
"Cuff state path in workspace discovery is unsafe",
|
|
@@ -82,7 +82,7 @@ def find_workspace(
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def _validate_project(root: Path) -> None:
|
|
85
|
-
state = root /
|
|
85
|
+
state = root / STATE_DIR
|
|
86
86
|
marker = root / PROJECT_PATH
|
|
87
87
|
if not marker.exists() and not marker.is_symlink():
|
|
88
88
|
raise _not_initialized(root)
|
|
@@ -123,6 +123,12 @@ def _existing_directory(path: Path, start: Path) -> Path:
|
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
def _require_safe_state_paths(state: Path, marker: Path) -> None:
|
|
126
|
+
if state.parent.is_symlink() or (state.parent.exists() and not state.parent.is_dir()):
|
|
127
|
+
raise CuffError(
|
|
128
|
+
"CUFF_PATH_INVALID",
|
|
129
|
+
"Cuff state directory must be a non-symlink directory",
|
|
130
|
+
{"path": str(state.parent)},
|
|
131
|
+
)
|
|
126
132
|
if state.is_symlink() or (state.exists() and not state.is_dir()):
|
|
127
133
|
raise CuffError(
|
|
128
134
|
"CUFF_PATH_INVALID",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cuff-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Cuff subject-bound claim, evidence, and verification gate.
|
|
5
5
|
Author: Fab7
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -38,7 +38,7 @@ worktree, or stages, commits, fetches, pushes, releases, or deploys anything.
|
|
|
38
38
|
Install a released version as a standard uv-managed tool:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
uv tool install cuff-cli==0.
|
|
41
|
+
uv tool install cuff-cli==0.2.0
|
|
42
42
|
cuff --version
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -57,12 +57,12 @@ Run initialization at the exact Git worktree root:
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
cuff init --json
|
|
60
|
-
git add .cuff/project.json
|
|
60
|
+
git add .fab7/cuff/project.json
|
|
61
61
|
git commit -m "Initialize Cuff"
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
The marker is exactly `{"schema":1}` and records live under
|
|
65
|
-
`.cuff/records/`. An incompatible marker is never rewritten or migrated.
|
|
65
|
+
`.fab7/cuff/records/`. An incompatible marker is never rewritten or migrated.
|
|
66
66
|
|
|
67
67
|
The preferred path atomically appends a claim and its observed evidence:
|
|
68
68
|
|
|
@@ -123,19 +123,36 @@ spend, or residual-risk authority.
|
|
|
123
123
|
|
|
124
124
|
## Static host integrations
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
uv-managed `cuff` executable on `PATH
|
|
126
|
+
One native payload lives in [`plugins/cuff`](plugins/cuff) and contains both
|
|
127
|
+
host manifests, Claude Code commands, and Codex skills. The shared
|
|
128
|
+
`fab7hq/fab7` marketplace owns registration; this repository owns the payload.
|
|
129
|
+
The assets require the uv-managed `cuff` executable on `PATH`.
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
132
|
# Codex
|
|
133
|
-
codex plugin marketplace add fab7hq/
|
|
134
|
-
codex plugin add cuff@
|
|
133
|
+
codex plugin marketplace add fab7hq/fab7
|
|
134
|
+
codex plugin add cuff@fab7
|
|
135
135
|
|
|
136
136
|
# Claude Code
|
|
137
|
-
claude plugin marketplace add fab7hq/
|
|
138
|
-
claude plugin install cuff@
|
|
137
|
+
claude plugin marketplace add fab7hq/fab7
|
|
138
|
+
claude plugin install cuff@fab7 --scope user
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Validate the built candidate and both host payloads without touching the
|
|
142
|
+
normal host configuration:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv build --out-dir ../sandbox/cuff-01/dist
|
|
146
|
+
uv run python tools/local_release_check.py --host all
|
|
147
|
+
uv run python tools/local_release_check.py --host all --prepare-auth \
|
|
148
|
+
--candidate-commit COMMIT \
|
|
149
|
+
--evidence-dir ../sandbox/cuff-01/e2e
|
|
150
|
+
# Log each CLI in using the isolated home paths printed by the prepare phase.
|
|
151
|
+
uv run python tools/local_release_check.py --host all --live --reuse-prepared \
|
|
152
|
+
--candidate-commit COMMIT \
|
|
153
|
+
--evidence-dir ../sandbox/cuff-01/e2e \
|
|
154
|
+
--codex-model MODEL \
|
|
155
|
+
--claude-model MODEL
|
|
139
156
|
```
|
|
140
157
|
|
|
141
158
|
See [RUNBOOK.md](RUNBOOK.md) for operations, [the architecture overview](docs/architecture/overview.md)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|