mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-19 20:26:45 +00:00
Compare commits
284 Commits
feat/jobdo
...
docs/roadm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4a732f33d | ||
|
|
2980f6de6e | ||
|
|
2cca298f7a | ||
|
|
6b90f83b9d | ||
|
|
8303af0898 | ||
|
|
3e2d902271 | ||
|
|
78d334c4e2 | ||
|
|
0063c0d698 | ||
|
|
429671ec12 | ||
|
|
ed9d387e9a | ||
|
|
8d02077cfd | ||
|
|
44bd2b54f5 | ||
|
|
e2c310dc04 | ||
|
|
25d663d140 | ||
|
|
6183d958ba | ||
|
|
f8e1bb7262 | ||
|
|
a35ee9a002 | ||
|
|
63ce483c27 | ||
|
|
c910063161 | ||
|
|
04c2abb412 | ||
|
|
33df16b6dd | ||
|
|
17260f69f1 | ||
|
|
6f73103bf1 | ||
|
|
a92e5b2892 | ||
|
|
0fb1c2d39e | ||
|
|
0eddcca702 | ||
|
|
2e93264919 | ||
|
|
1ac8ce8882 | ||
|
|
ab27f61597 | ||
|
|
5a43d3b553 | ||
|
|
2c601ef22d | ||
|
|
4cd2bb859b | ||
|
|
62bc7b6a17 | ||
|
|
9278748038 | ||
|
|
02889d701a | ||
|
|
7b63c0a2eb | ||
|
|
b11cdf34b3 | ||
|
|
de0f1bba2e | ||
|
|
4d78e91229 | ||
|
|
cf5eb157e1 | ||
|
|
8019999ce5 | ||
|
|
21bbbb7f1f | ||
|
|
124d55f13e | ||
|
|
eb7a2088e2 | ||
|
|
11c6a6007f | ||
|
|
060603c196 | ||
|
|
4ccbd8f97c | ||
|
|
2221dd4f0f | ||
|
|
c5a18e1864 | ||
|
|
d7f1ad7139 | ||
|
|
238c0a49d1 | ||
|
|
d04a74cc97 | ||
|
|
8fd5894022 | ||
|
|
0f8717834f | ||
|
|
fb9095c611 | ||
|
|
761e50d1c6 | ||
|
|
5155225b25 | ||
|
|
deeb1efde8 | ||
|
|
c9b34a2947 | ||
|
|
5e0cf62be5 | ||
|
|
51fa5a7048 | ||
|
|
33ac5c30d3 | ||
|
|
89d1052f3a | ||
|
|
afd88088d6 | ||
|
|
673d37d86a | ||
|
|
fc35dc878c | ||
|
|
e199a392fb | ||
|
|
f27bd46759 | ||
|
|
5294648373 | ||
|
|
a3af0133e0 | ||
|
|
1bceda2063 | ||
|
|
99efb2131e | ||
|
|
7d859ae8a2 | ||
|
|
c886cbca99 | ||
|
|
f8270b34e6 | ||
|
|
0940253376 | ||
|
|
0bb145141e | ||
|
|
7b21ac12b9 | ||
|
|
2db0a5f70d | ||
|
|
8c9e41aab4 | ||
|
|
90c1d38d40 | ||
|
|
3767addd11 | ||
|
|
8c9a05e71b | ||
|
|
d5620c06b1 | ||
|
|
b63a1bf2bf | ||
|
|
dccb3e72d9 | ||
|
|
ea95bf2576 | ||
|
|
dec8efa5c8 | ||
|
|
ce02ace3a2 | ||
|
|
bc32639ce3 | ||
|
|
a212c662e5 | ||
|
|
2cac66cd38 | ||
|
|
1a110bd870 | ||
|
|
685f078204 | ||
|
|
e4ef0f7f19 | ||
|
|
76581f7239 | ||
|
|
82ec223ed4 | ||
|
|
a6ca5c489b | ||
|
|
3ff8743e79 | ||
|
|
4cf9d43e71 | ||
|
|
29029bfc14 | ||
|
|
22024102dd | ||
|
|
98204a73d4 | ||
|
|
8565b68fb1 | ||
|
|
7ed1cabc14 | ||
|
|
5de73ecf12 | ||
|
|
b655d49bd1 | ||
|
|
e05268e216 | ||
|
|
d6b4349a7d | ||
|
|
ccd99a5188 | ||
|
|
557ab8a9dd | ||
|
|
1f00771fd2 | ||
|
|
0bcab573f3 | ||
|
|
4a76632f6c | ||
|
|
9910d5805e | ||
|
|
39568feff6 | ||
|
|
686cc89a36 | ||
|
|
d3ae7beefb | ||
|
|
faa7551ac2 | ||
|
|
7ce6b78d3a | ||
|
|
2831c45f71 | ||
|
|
ace260139e | ||
|
|
db6f30fa33 | ||
|
|
983ceb939c | ||
|
|
cac73b4410 | ||
|
|
9ae6aa3f30 | ||
|
|
985c6e97f9 | ||
|
|
c522dc970f | ||
|
|
db91a235e9 | ||
|
|
f0e8896d2e | ||
|
|
2454f012b6 | ||
|
|
17b4ab45c6 | ||
|
|
80b8984b62 | ||
|
|
b01192dde7 | ||
|
|
12ca5550fa | ||
|
|
1a6e475f74 | ||
|
|
0cd1eabb5d | ||
|
|
f2ba3648d6 | ||
|
|
76920c7d6c | ||
|
|
0a14f8511e | ||
|
|
391e343220 | ||
|
|
18805b565a | ||
|
|
65a144c3f7 | ||
|
|
6d809cb278 | ||
|
|
f7235ca932 | ||
|
|
41b769fc5a | ||
|
|
7426ede2eb | ||
|
|
8f7eaffcef | ||
|
|
d2b5f5d498 | ||
|
|
607f071ca8 | ||
|
|
d3f8ff9916 | ||
|
|
204af77596 | ||
|
|
5c40d4e778 | ||
|
|
5625ba597b | ||
|
|
4f60cf70f1 | ||
|
|
6a37442ee1 | ||
|
|
0bca524c8c | ||
|
|
2ad56860df | ||
|
|
1fbde9f47f | ||
|
|
879962b826 | ||
|
|
0b0d55d7ec | ||
|
|
7214573f35 | ||
|
|
dcf11f8190 | ||
|
|
f79ca989ba | ||
|
|
e1641aa010 | ||
|
|
5cebdd999d | ||
|
|
bf533d77a7 | ||
|
|
e34209ff7f | ||
|
|
ff37d395bb | ||
|
|
f8d744bb37 | ||
|
|
c8c936ede1 | ||
|
|
57b3e3258b | ||
|
|
06e545325d | ||
|
|
ed3ccae844 | ||
|
|
f4e08d0ecf | ||
|
|
030f2ef20f | ||
|
|
16d6525de4 | ||
|
|
42c79218c9 | ||
|
|
4e0211d36c | ||
|
|
aec291caab | ||
|
|
43b182882a | ||
|
|
307b23d27f | ||
|
|
8c11dd16f4 | ||
|
|
2012718749 | ||
|
|
79d3b809f9 | ||
|
|
9ec4d8398e | ||
|
|
5f45740408 | ||
|
|
675d9ddc78 | ||
|
|
087e31d190 | ||
|
|
a6ee51baab | ||
|
|
6df60a4683 | ||
|
|
3cf0db8f79 | ||
|
|
964458ad4a | ||
|
|
d87c3e6400 | ||
|
|
ac888623a8 | ||
|
|
3a8ce83234 | ||
|
|
37b2b75287 | ||
|
|
f2dc615a8a | ||
|
|
9bc55f9946 | ||
|
|
180ebb3b02 | ||
|
|
534442b8da | ||
|
|
9c2ebb4f39 | ||
|
|
2c48400293 | ||
|
|
713ca7aee4 | ||
|
|
02b591ac64 | ||
|
|
f789525839 | ||
|
|
b1d8a66515 | ||
|
|
ad9e0234a9 | ||
|
|
145413d624 | ||
|
|
17da2964d7 | ||
|
|
9ab569e626 | ||
|
|
4af5664ff8 | ||
|
|
1864ce38ad | ||
|
|
74cc590407 | ||
|
|
a4b20ea34d | ||
|
|
8d0cee46d5 | ||
|
|
45b43b5a96 | ||
|
|
d15268e2cc | ||
|
|
424825f8cb | ||
|
|
07dad88e8c | ||
|
|
5c77896dec | ||
|
|
74bbf4b36f | ||
|
|
481585f865 | ||
|
|
c6e2a7dee4 | ||
|
|
83116555ff | ||
|
|
8f55870dad | ||
|
|
7244a82b36 | ||
|
|
5ab969e7ae | ||
|
|
5a4cc506d5 | ||
|
|
9e1eafd02d | ||
|
|
b2048856f3 | ||
|
|
19aaf9d05e | ||
|
|
8499599b70 | ||
|
|
86ff83c233 | ||
|
|
bd126905db | ||
|
|
f4a9674086 | ||
|
|
d3a982dda9 | ||
|
|
8cf628a53c | ||
|
|
b8f989b605 | ||
|
|
e29010ed48 | ||
|
|
0e5f695844 | ||
|
|
ce39d5c598 | ||
|
|
fad53e2df9 | ||
|
|
328fd114ff | ||
|
|
075c214439 | ||
|
|
ec882f4c88 | ||
|
|
7204844982 | ||
|
|
1fecdf096b | ||
|
|
3730b459a2 | ||
|
|
d7dbe951ce | ||
|
|
6c0c305a4b | ||
|
|
3c563fa1dc | ||
|
|
6aa4b85c95 | ||
|
|
b98b9a712e | ||
|
|
357629dbd9 | ||
|
|
12b65f9807 | ||
|
|
75c08bc982 | ||
|
|
553d25ee50 | ||
|
|
5be173edf6 | ||
|
|
28998422e2 | ||
|
|
b4733b67a6 | ||
|
|
ab44985916 | ||
|
|
d074d1c046 | ||
|
|
caeac828b5 | ||
|
|
85435ad4b5 | ||
|
|
5eb4b8a944 | ||
|
|
65aa559733 | ||
|
|
ac8a24b30b | ||
|
|
94b80a05d3 | ||
|
|
9b97c4d832 | ||
|
|
1206f4131d | ||
|
|
c99330372c | ||
|
|
9a512633a5 | ||
|
|
6ac13ffdad | ||
|
|
482681cdfe | ||
|
|
8e45f1850c | ||
|
|
57096b0a1a | ||
|
|
51b9e6b37f | ||
|
|
e939777f92 | ||
|
|
1093e26792 | ||
|
|
44cca2054d | ||
|
|
6dc7b26d82 | ||
|
|
a0bd406c8f | ||
|
|
b62646edfe |
54
.github/ISSUE_TEMPLATE/anti_slop_triage.yml
vendored
Normal file
54
.github/ISSUE_TEMPLATE/anti_slop_triage.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Anti-slop triage
|
||||
about: Classify low-signal, duplicate, generated, or unsafe reports before engineering work starts.
|
||||
title: "triage: "
|
||||
labels: ["needs-triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this form for issue intake that needs evidence-backed classification before anyone closes, fixes, or escalates it.
|
||||
Do not paste secrets, live tokens, private logs, or non-public customer data.
|
||||
- type: dropdown
|
||||
id: classification
|
||||
attributes:
|
||||
label: Initial classification
|
||||
description: Pick the strongest current classification. Update it if evidence changes.
|
||||
options:
|
||||
- actionable-bug
|
||||
- actionable-docs
|
||||
- actionable-feature
|
||||
- duplicate
|
||||
- spam-or-promotion
|
||||
- generated-slop-or-hallucinated
|
||||
- unsafe-or-security-sensitive
|
||||
- not-reproducible-yet
|
||||
- externally-blocked
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: evidence
|
||||
attributes:
|
||||
label: Evidence
|
||||
description: Link the PR, issue, command output, docs page, reproduction, duplicate, or policy that supports the classification.
|
||||
placeholder: "Evidence: ..."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: safe_next_action
|
||||
attributes:
|
||||
label: Safe next action
|
||||
description: State the next non-destructive action. If closure or merge is proposed, name the required owner/gate.
|
||||
placeholder: "Next action: label only / request repro / link duplicate / fix docs / defer with rationale / owner review required"
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: guardrails
|
||||
attributes:
|
||||
label: Guardrails
|
||||
options:
|
||||
- label: I did not close, merge, or mutate remote state as part of this triage-only report.
|
||||
required: true
|
||||
- label: I checked for duplicates or related PRs/issues before recommending action.
|
||||
required: true
|
||||
- label: If this touches credentials, security, or private data, I avoided public reproduction details and routed to the appropriate private/security path.
|
||||
required: true
|
||||
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,36 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug in claw-code
|
||||
title: "[bug] "
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- What happened? -->
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!-- What should have happened? -->
|
||||
|
||||
## Actual Behavior
|
||||
|
||||
<!-- What actually happened? Include error messages, logs, screenshots -->
|
||||
|
||||
## Environment
|
||||
|
||||
- **claw-code version:**
|
||||
- **OS:**
|
||||
- **Provider/model:**
|
||||
- **Rust version (if building from source):**
|
||||
|
||||
## Additional Context
|
||||
|
||||
<!-- Related pinpoints, sessions, config, etc. -->
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: How to file a pinpoint
|
||||
url: https://github.com/ultraworkers/claw-code/blob/main/CONTRIBUTING.md#filing-a-roadmap-pinpoint
|
||||
about: Read the pinpoint format guide before filing
|
||||
41
.github/ISSUE_TEMPLATE/pinpoint.md
vendored
41
.github/ISSUE_TEMPLATE/pinpoint.md
vendored
@@ -1,41 +0,0 @@
|
||||
---
|
||||
name: Pinpoint
|
||||
about: File a concrete clawability gap with code evidence
|
||||
title: '[Pinpoint #XXX] '
|
||||
labels: [pinpoint]
|
||||
---
|
||||
|
||||
## Exact pinpoint
|
||||
|
||||
<!-- One-line statement: what is wrong or missing, stated crisply. -->
|
||||
|
||||
## Live evidence
|
||||
|
||||
<!-- File:line refs, code paths, command output that reproduces the gap. -->
|
||||
|
||||
```
|
||||
# paste evidence here
|
||||
```
|
||||
|
||||
## Why distinct
|
||||
|
||||
<!-- Why this isn't already covered by an adjacent pinpoint. Cluster context if relevant. -->
|
||||
|
||||
## Concrete delta landed
|
||||
|
||||
<!-- Commit sha + push status once fixed. Leave blank until resolved. -->
|
||||
|
||||
- commit:
|
||||
- push: local==origin==fork ✅ / ⏳ pending
|
||||
|
||||
## Fix shape recorded
|
||||
|
||||
<!-- Defensive fix sketch — what change would close this pinpoint. -->
|
||||
|
||||
## Branch / parity
|
||||
|
||||
<!-- Branch name, HEAD sha, three-way parity status. -->
|
||||
|
||||
- branch:
|
||||
- HEAD:
|
||||
- parity: local==origin==fork ✅ / ⏳ pending
|
||||
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,27 +1,17 @@
|
||||
## Summary
|
||||
- TBD
|
||||
|
||||
<!-- Brief description of what this PR does -->
|
||||
## Anti-slop triage
|
||||
- Classification: <!-- actionable-fix | docs-only | duplicate | generated-slop | unsafe | out-of-scope | needs-maintainer-decision -->
|
||||
- Evidence: <!-- issue link, repro command, failing test, docs source, or duplicate PR -->
|
||||
- Non-destructive review result: <!-- merge candidate | request changes | close/defer with rationale | needs owner gate -->
|
||||
|
||||
## Related Pinpoints / Issues
|
||||
## Verification
|
||||
- [ ] Targeted tests/docs checks ran, or the gap is explicitly recorded.
|
||||
- [ ] `git diff --check` passes.
|
||||
- [ ] No live secrets, tokens, private logs, or unrelated generated churn are included.
|
||||
|
||||
<!-- Link to ROADMAP.md pinpoints or GitHub issues, e.g., #283, #285 -->
|
||||
|
||||
## Changes
|
||||
|
||||
<!-- List key changes -->
|
||||
-
|
||||
|
||||
## Testing
|
||||
|
||||
<!-- How was this tested? -->
|
||||
- [ ] `cargo test` passes
|
||||
- [ ] `cargo fmt --check` passes
|
||||
- [ ] Manual verification (describe)
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] ROADMAP.md updated (if filing/closing pinpoints)
|
||||
- [ ] CHANGELOG.md updated (if user-facing change)
|
||||
- [ ] Documentation updated (if applicable)
|
||||
- [ ] No regressions in existing tests
|
||||
## Resolution gate
|
||||
- [ ] If this PR resolves an issue, the issue number and fix evidence are linked.
|
||||
- [ ] If this PR should not merge, the rejection/defer rationale is evidence-backed and does not rely on vibes.
|
||||
- [ ] I did not merge/close remote PRs or issues from an automation lane without owner approval.
|
||||
|
||||
169
.github/scripts/check_release_readiness.py
vendored
Normal file
169
.github/scripts/check_release_readiness.py
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate release-readiness docs that are easy to regress.
|
||||
|
||||
The check is intentionally dependency-free so it can run on developer machines,
|
||||
Windows CI, and minimal release jobs. It validates:
|
||||
|
||||
* required repository policy files exist;
|
||||
* local Markdown links and image targets resolve;
|
||||
* local heading anchors referenced from Markdown resolve; and
|
||||
* command examples do not present the deprecated `cargo install claw-code`
|
||||
package as an executable install path.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote, urlparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
REQUIRED_POLICY_FILES = [
|
||||
"LICENSE",
|
||||
"CONTRIBUTING.md",
|
||||
"SECURITY.md",
|
||||
"SUPPORT.md",
|
||||
"CODE_OF_CONDUCT.md",
|
||||
]
|
||||
|
||||
MARKDOWN_ROOTS = [
|
||||
ROOT / "README.md",
|
||||
ROOT / "USAGE.md",
|
||||
ROOT / "PARITY.md",
|
||||
ROOT / "PHILOSOPHY.md",
|
||||
ROOT / "ROADMAP.md",
|
||||
ROOT / "CONTRIBUTING.md",
|
||||
ROOT / "SECURITY.md",
|
||||
ROOT / "SUPPORT.md",
|
||||
ROOT / "CODE_OF_CONDUCT.md",
|
||||
ROOT / "docs",
|
||||
ROOT / "rust" / "README.md",
|
||||
ROOT / "rust" / "USAGE.md",
|
||||
ROOT / "rust" / "MOCK_PARITY_HARNESS.md",
|
||||
]
|
||||
|
||||
LINK_PATTERN = re.compile(r"(?<!!)\[[^\]\n]+\]\(([^)\s]+)(?:\s+\"[^\"]*\")?\)")
|
||||
HTML_LINK_PATTERN = re.compile(r"""<(?:a|img)\b[^>]*(?:href|src)=["']([^"']+)["']""", re.I)
|
||||
FENCE_PATTERN = re.compile(r"```(?P<lang>[^\n`]*)\n(?P<body>.*?)```", re.S)
|
||||
|
||||
|
||||
def iter_markdown_files() -> list[Path]:
|
||||
files: set[Path] = set()
|
||||
for entry in MARKDOWN_ROOTS:
|
||||
if entry.is_file():
|
||||
files.add(entry)
|
||||
elif entry.is_dir():
|
||||
files.update(entry.rglob("*.md"))
|
||||
return sorted(files)
|
||||
|
||||
|
||||
def github_anchor(heading: str) -> str:
|
||||
anchor = heading.strip().lower()
|
||||
anchor = re.sub(r"<[^>]+>", "", anchor)
|
||||
anchor = re.sub(r"`([^`]*)`", r"\1", anchor)
|
||||
anchor = re.sub(r"[^a-z0-9 _-]", "", anchor)
|
||||
anchor = anchor.replace(" ", "-")
|
||||
anchor = re.sub(r"-+", "-", anchor)
|
||||
return anchor.strip("-")
|
||||
|
||||
|
||||
def anchors_for(path: Path) -> set[str]:
|
||||
anchors: set[str] = set()
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
match = re.match(r"^(#{1,6})\s+(.+?)\s*#*\s*$", line)
|
||||
if match:
|
||||
anchors.add(github_anchor(match.group(2)))
|
||||
return anchors
|
||||
|
||||
|
||||
def is_external(target: str) -> bool:
|
||||
parsed = urlparse(target)
|
||||
return parsed.scheme in {"http", "https", "mailto"}
|
||||
|
||||
|
||||
def validate_policies(errors: list[str]) -> None:
|
||||
for relative in REQUIRED_POLICY_FILES:
|
||||
path = ROOT / relative
|
||||
if not path.is_file():
|
||||
errors.append(f"missing required policy file: {relative}")
|
||||
|
||||
|
||||
def validate_markdown_links(errors: list[str]) -> None:
|
||||
anchor_cache: dict[Path, set[str]] = {}
|
||||
for path in iter_markdown_files():
|
||||
text = path.read_text(encoding="utf-8")
|
||||
candidates = [m.group(1) for m in LINK_PATTERN.finditer(text)]
|
||||
candidates.extend(m.group(1) for m in HTML_LINK_PATTERN.finditer(text))
|
||||
for target in candidates:
|
||||
if (
|
||||
not target
|
||||
or is_external(target)
|
||||
or target.startswith(("mailto:", "tel:", "data:"))
|
||||
):
|
||||
continue
|
||||
link_path, _, raw_anchor = target.partition("#")
|
||||
if not link_path:
|
||||
destination = path
|
||||
else:
|
||||
destination = (path.parent / unquote(link_path)).resolve()
|
||||
try:
|
||||
destination.relative_to(ROOT)
|
||||
except ValueError:
|
||||
errors.append(
|
||||
f"{path.relative_to(ROOT)}: link escapes repo root: {target}"
|
||||
)
|
||||
continue
|
||||
if not destination.exists():
|
||||
errors.append(
|
||||
f"{path.relative_to(ROOT)}: missing local link target: {target}"
|
||||
)
|
||||
continue
|
||||
if raw_anchor and destination.suffix.lower() == ".md":
|
||||
anchor = unquote(raw_anchor).lower()
|
||||
anchor_cache.setdefault(destination, anchors_for(destination))
|
||||
if anchor not in anchor_cache[destination]:
|
||||
errors.append(
|
||||
f"{path.relative_to(ROOT)}: missing anchor `{raw_anchor}` in "
|
||||
f"{destination.relative_to(ROOT)}"
|
||||
)
|
||||
|
||||
|
||||
def validate_command_examples(errors: list[str]) -> None:
|
||||
for path in iter_markdown_files():
|
||||
text = path.read_text(encoding="utf-8")
|
||||
for match in FENCE_PATTERN.finditer(text):
|
||||
lang = match.group("lang").strip().lower()
|
||||
if lang not in {"bash", "sh", "shell", "zsh", "powershell", "ps1"}:
|
||||
continue
|
||||
body = match.group("body")
|
||||
for offset, line in enumerate(body.splitlines(), start=1):
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith(("#", ">")):
|
||||
continue
|
||||
if re.search(r"\bcargo\s+install\s+claw-code\b", stripped):
|
||||
line_no = text.count("\n", 0, match.start()) + offset + 1
|
||||
errors.append(
|
||||
f"{path.relative_to(ROOT)}:{line_no}: deprecated "
|
||||
"`cargo install claw-code` appears in an executable "
|
||||
"command block; use build-from-source docs instead"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
errors: list[str] = []
|
||||
validate_policies(errors)
|
||||
validate_markdown_links(errors)
|
||||
validate_command_examples(errors)
|
||||
if errors:
|
||||
print("release-readiness check failed:", file=sys.stderr)
|
||||
for error in errors:
|
||||
print(f" - {error}", file=sys.stderr)
|
||||
return 1
|
||||
print("release-readiness check passed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -32,6 +32,10 @@ jobs:
|
||||
os: macos-14
|
||||
bin: claw
|
||||
artifact_name: claw-macos-arm64
|
||||
- name: windows-x64
|
||||
os: windows-latest
|
||||
bin: claw.exe
|
||||
artifact_name: claw-windows-x64.exe
|
||||
defaults:
|
||||
run:
|
||||
working-directory: rust
|
||||
@@ -47,22 +51,27 @@ jobs:
|
||||
- name: Build release binary
|
||||
run: cargo build --release -p rusty-claude-cli
|
||||
|
||||
- name: Package artifact
|
||||
- name: Package artifact and checksum
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p dist
|
||||
cp "target/release/${{ matrix.bin }}" "dist/${{ matrix.artifact_name }}"
|
||||
chmod +x "dist/${{ matrix.artifact_name }}"
|
||||
(cd dist && sha256sum "${{ matrix.artifact_name }}" > "${{ matrix.artifact_name }}.sha256")
|
||||
|
||||
- name: Upload workflow artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: rust/dist/${{ matrix.artifact_name }}
|
||||
path: |
|
||||
rust/dist/${{ matrix.artifact_name }}
|
||||
rust/dist/${{ matrix.artifact_name }}.sha256
|
||||
|
||||
- name: Upload release asset
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: rust/dist/${{ matrix.artifact_name }}
|
||||
files: |
|
||||
rust/dist/${{ matrix.artifact_name }}
|
||||
rust/dist/${{ matrix.artifact_name }}.sha256
|
||||
fail_on_unmatched_files: true
|
||||
|
||||
53
.github/workflows/rust-ci.yml
vendored
53
.github/workflows/rust-ci.yml
vendored
@@ -9,8 +9,14 @@ on:
|
||||
paths:
|
||||
- .github/workflows/rust-ci.yml
|
||||
- .github/scripts/check_doc_source_of_truth.py
|
||||
- .github/scripts/check_release_readiness.py
|
||||
- .github/FUNDING.yml
|
||||
- CODE_OF_CONDUCT.md
|
||||
- CONTRIBUTING.md
|
||||
- LICENSE
|
||||
- README.md
|
||||
- SECURITY.md
|
||||
- SUPPORT.md
|
||||
- USAGE.md
|
||||
- PARITY.md
|
||||
- PHILOSOPHY.md
|
||||
@@ -23,8 +29,14 @@ on:
|
||||
paths:
|
||||
- .github/workflows/rust-ci.yml
|
||||
- .github/scripts/check_doc_source_of_truth.py
|
||||
- .github/scripts/check_release_readiness.py
|
||||
- .github/FUNDING.yml
|
||||
- CODE_OF_CONDUCT.md
|
||||
- CONTRIBUTING.md
|
||||
- LICENSE
|
||||
- README.md
|
||||
- SECURITY.md
|
||||
- SUPPORT.md
|
||||
- USAGE.md
|
||||
- PARITY.md
|
||||
- PHILOSOPHY.md
|
||||
@@ -58,6 +70,8 @@ jobs:
|
||||
python-version: "3.x"
|
||||
- name: Check docs and metadata for stale branding
|
||||
run: python .github/scripts/check_doc_source_of_truth.py
|
||||
- name: Check release policy docs and local links
|
||||
run: python .github/scripts/check_release_readiness.py
|
||||
|
||||
fmt:
|
||||
name: cargo fmt
|
||||
@@ -98,3 +112,42 @@ jobs:
|
||||
workspaces: rust -> target
|
||||
- name: Run workspace clippy
|
||||
run: cargo clippy --workspace
|
||||
|
||||
windows-smoke:
|
||||
name: windows PowerShell smoke
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: rust
|
||||
shell: pwsh
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: rust -> target
|
||||
- name: Build CLI for Windows smoke
|
||||
run: cargo build -p rusty-claude-cli
|
||||
- name: Smoke local commands without live credentials
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ""
|
||||
ANTHROPIC_AUTH_TOKEN: ""
|
||||
OPENAI_API_KEY: ""
|
||||
XAI_API_KEY: ""
|
||||
DASHSCOPE_API_KEY: ""
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$env:CLAW_CONFIG_HOME = Join-Path $env:RUNNER_TEMP "claw config home"
|
||||
New-Item -ItemType Directory -Force -Path $env:CLAW_CONFIG_HOME | Out-Null
|
||||
$workspace = Join-Path $env:RUNNER_TEMP "claw path smoke"
|
||||
New-Item -ItemType Directory -Force -Path $workspace | Out-Null
|
||||
$claw = Join-Path $env:GITHUB_WORKSPACE "rust\target\debug\claw.exe"
|
||||
Push-Location $workspace
|
||||
try {
|
||||
& $claw help
|
||||
& $claw status
|
||||
& $claw config env
|
||||
& $claw doctor
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -8,8 +8,7 @@ archive/
|
||||
# Claw Code local artifacts
|
||||
.claw/settings.local.json
|
||||
.claw/sessions/
|
||||
# #160/#166: default session storage directory (flush-transcript output,
|
||||
# dogfood runs, etc.). Claws specifying --directory elsewhere are fine.
|
||||
.port_sessions/
|
||||
.clawhip/
|
||||
status-help.txt
|
||||
# Legacy Python port session scratch artifacts
|
||||
.port_sessions/
|
||||
|
||||
14886
.omx/cc2/board.json
Normal file
14886
.omx/cc2/board.json
Normal file
File diff suppressed because one or more lines are too long
842
.omx/cc2/board.md
Normal file
842
.omx/cc2/board.md
Normal file
File diff suppressed because one or more lines are too long
429
.omx/cc2/issue-parity-intake.json
Normal file
429
.omx/cc2/issue-parity-intake.json
Normal file
@@ -0,0 +1,429 @@
|
||||
{
|
||||
"schema_version": "cc2.issue_parity_intake.v1",
|
||||
"generated_at": "2026-05-14T08:02:00Z",
|
||||
"task_id": "3",
|
||||
"owner": "worker-2",
|
||||
"goal": "G001-stream0-board",
|
||||
"notes": [
|
||||
"Leader owns Ultragoal; this artifact does not mutate .omx/ultragoal.",
|
||||
"Rows are scoped intake/classification evidence for Worker 1/Task 2 board integration."
|
||||
],
|
||||
"source_manifest": {
|
||||
"claw_open_latest": {
|
||||
"path": ".omx/research/claw-open-latest.json",
|
||||
"sha256_prefix_from_plan": "89e3e027fa735f38",
|
||||
"covered_issue_numbers": [3028, 3029, 3030, 3031, 3032, 3033, 3034, 3035, 3036, 3037, 3038]
|
||||
},
|
||||
"claw_issues": {
|
||||
"path": ".omx/research/claw-issues.json",
|
||||
"sha256_prefix_from_plan": "e64fdba7df3b78ed",
|
||||
"covered_issue_numbers": [2997, 3003, 3004, 3005, 3006, 3007, 3020, 3023]
|
||||
},
|
||||
"opencode": {
|
||||
"repo_path": ".omx/research/repos/opencode",
|
||||
"metadata_path": ".omx/research/opencode-repo.json",
|
||||
"issues_path": ".omx/research/opencode-issues.json",
|
||||
"head_from_plan": "27ac53aaacc677b1401c4e75ca7a7dadf8b2c349"
|
||||
},
|
||||
"codex": {
|
||||
"repo_path": ".omx/research/repos/codex",
|
||||
"metadata_path": ".omx/research/codex-repo.json",
|
||||
"issues_path": ".omx/research/codex-issues.json",
|
||||
"head_from_plan": "6a225e4005209f2325ab3c681c7c6beba2907d4d"
|
||||
}
|
||||
},
|
||||
"issue_clusters": [
|
||||
{
|
||||
"id": "CC2-ISSUE-3007",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3007",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3007,
|
||||
"title": "Permission modes do not enforce path scope on file tools or shell expansion in bash",
|
||||
"theme": "security/path-scope",
|
||||
"release_bucket": "alpha_blocker",
|
||||
"lifecycle_status": "active",
|
||||
"roadmap_anchor": "ROADMAP.md#11-policy-engine-for-autonomous-coding; ROADMAP.md#9-green-ness-contract",
|
||||
"dependencies": ["permission path canonicalization", "file tool target validation", "bash command/path validation reachability", "policy regression fixtures"],
|
||||
"verification_required": ["workspace-write cannot read/write/delete outside workspace", "shell expansion and symlink traversal are rejected or policy-blocked", "file tools and bash use the same target-scope decision record"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Security/sandbox escape class; plan names #3007 as alpha blocker."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3020",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3020",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3020,
|
||||
"title": "OpenAI-compatible model IDs with slashes are stripped before request",
|
||||
"theme": "provider/model-routing",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#provider-routing-model-name-prefix-must-win-over-env-var-presence-fixed-2026-04-08-0530c50",
|
||||
"dependencies": ["provider profile contract", "wire model-id preservation option", "routing-prefix source reporting"],
|
||||
"verification_required": ["OpenAI-compatible endpoint receives exact model id when preservation is enabled", "status JSON reports raw model input, route, and wire model id"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Core provider correctness but below alpha state/security contracts unless it blocks the selected alpha model path."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3006",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3006",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3006,
|
||||
"title": "Not Working in windows",
|
||||
"theme": "windows/install",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#immediate-backlog-from-current-real-pain",
|
||||
"dependencies": ["Windows support policy", "PowerShell install path", "dependency/version matrix", "diagnostic setup output"],
|
||||
"verification_required": ["fresh Windows/PowerShell setup smoke documented", "unsupported native paths fail with actionable WSL2/native guidance"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Real adoption blocker; plan places Windows/install in beta adoption overlay."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3005",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3005",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3005,
|
||||
"title": "DeepSeek V4-flash/pro fails with 400 Bad Request (missing reasoning_content) while deepseek-reasoner works",
|
||||
"theme": "provider/response-shape",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#5-failure-taxonomy; ROADMAP.md#provider-routing-model-name-prefix-must-win-over-env-var-presence-fixed-2026-04-08-0530c50",
|
||||
"dependencies": ["OpenAI-compatible diagnostics playbook", "provider error taxonomy", "reasoning/thinking field compatibility tests"],
|
||||
"verification_required": ["provider 400 response classified with actionable remediation", "DeepSeek-compatible response-shape fixture does not hide assistant output"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Provider compatibility issue that shares the #3032 diagnostics lane."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3004",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3004",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3004,
|
||||
"title": "When can we adapt to zed?",
|
||||
"theme": "ide/acp",
|
||||
"release_bucket": "ga_ecosystem",
|
||||
"lifecycle_status": "deferred_with_rationale",
|
||||
"roadmap_anchor": "ROADMAP.md#phase-5-plugin-and-mcp-lifecycle-maturity",
|
||||
"dependencies": ["stable session/control API", "plugin/MCP lifecycle", "engine API or ACP bridge decision"],
|
||||
"verification_required": ["Zed/ACP smoke once core state/control contracts exist"],
|
||||
"deferral_rationale": "IDE integration is valuable but should wait until boot/session/event/control truth surfaces are stable.",
|
||||
"classification_rationale": "Matches plan's GA ecosystem lane for Zed/ACP."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3003",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3003",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3003,
|
||||
"title": ".claude/sessions should not be submitted to repo",
|
||||
"theme": "session-hygiene/gitignore",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#9-green-ness-contract; ROADMAP.md#8-recovery-recipes-for-common-failures",
|
||||
"dependencies": ["artifact ignore policy", "session storage boundary docs", "repo hygiene check"],
|
||||
"verification_required": ["session directories are ignored", "status/doctor warns about tracked session artifacts"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Small but user-visible session hygiene and data-leak prevention item."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-2997",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/2997",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 2997,
|
||||
"title": "License?",
|
||||
"theme": "docs/license",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#immediate-backlog-from-current-real-pain",
|
||||
"dependencies": ["maintainer license decision", "LICENSE file", "README/USAGE attribution wording"],
|
||||
"verification_required": ["repository license file exists", "package metadata and docs reference the same license"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Adoption/readiness documentation gap; requires maintainer decision before implementation."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3023",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3023",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3023,
|
||||
"title": "Protect claw-code from AI slop PRs",
|
||||
"theme": "repo-hygiene/anti-slop",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#immediate-backlog-from-current-real-pain",
|
||||
"dependencies": ["contributor policy", "PR quality gate selection", "false-positive review escape hatch"],
|
||||
"verification_required": ["selected PR quality gate runs on sample good/bad PR fixtures", "maintainers can override false positives"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Protects project throughput but should not precede alpha core safety contracts."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3028",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3028",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3028,
|
||||
"title": "docs: add navigation and file-context usage guide",
|
||||
"theme": "docs/navigation-context",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#7-human-ux-still-leaks-into-claw-workflows",
|
||||
"dependencies": ["current TUI/shell key behavior inventory", "file context syntax docs", "secret-handling guidance"],
|
||||
"verification_required": ["docs include terminal history, scrollback, @file context, attach/external file caveats", "examples work against current CLI"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Documentation support item from latest open issue refresh."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3029",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3029",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3029,
|
||||
"title": "build: add cross-platform installer path and release artifact quickstart",
|
||||
"theme": "install/distribution",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#immediate-backlog-from-current-real-pain",
|
||||
"dependencies": ["release artifact policy", "install.sh/install.ps1 contract", "PATH/update/uninstall instructions"],
|
||||
"verification_required": ["install quickstart smoke on supported OS/arch", "failed install prints actionable diagnostics"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Distribution friction belongs in adoption overlay."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3030",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3030",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3030,
|
||||
"title": "feat: make provider/model setup less env-var-driven",
|
||||
"theme": "provider/setup-profiles",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#3-structured-session-control-api; ROADMAP.md#145-boot-preflight-doctor-contract",
|
||||
"dependencies": ["provider profiles", "setup wizard or dry-run", "secret redaction", "base-url/model smoke test"],
|
||||
"verification_required": ["setup validates provider route without echoing keys", "session-only versus persisted profile behavior is explicit"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Directly reduces current provider setup support churn."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3031",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3031",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3031,
|
||||
"title": "feat: auto-compact or clearly recover from context-window provider errors",
|
||||
"theme": "session-recovery/context-window",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#8-recovery-recipes-for-common-failures; ROADMAP.md#158-compact_messages_if_needed-drops-turns-silently-no-structured-compaction-event-emitted",
|
||||
"dependencies": ["provider error classifier", "safe compact retry policy", "compaction event/audit trail", "retry loop cap"],
|
||||
"verification_required": ["context-window error either compacts+retries once safely or emits exact recovery command", "compaction event is machine-visible"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Recovery reliability item; promoted only if selected alpha provider path hits it."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3032",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3032",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3032,
|
||||
"title": "docs: add OpenAI-compatible/local provider diagnostics playbook",
|
||||
"theme": "provider/diagnostics-docs",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#5-failure-taxonomy",
|
||||
"dependencies": ["raw chat-completions smoke tests", "tool-call response-shape examples", "provider failure taxonomy"],
|
||||
"verification_required": ["playbook distinguishes Claw bugs from wrapper/tool-call-shape bugs", "curl examples cover non-streaming and streaming tool calls"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Shared diagnostic lane for #3005/#3020/local model reports."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3033",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3033",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3033,
|
||||
"title": "feat: add minimal claw serve JSON-RPC engine API",
|
||||
"theme": "engine-api/control-plane",
|
||||
"release_bucket": "ga_ecosystem",
|
||||
"lifecycle_status": "deferred_with_rationale",
|
||||
"roadmap_anchor": "ROADMAP.md#3-structured-session-control-api; ROADMAP.md#phase-4-claws-first-task-execution",
|
||||
"dependencies": ["stable session state API", "event schema v1", "permission policy contract", "cancel/prompt stream semantics"],
|
||||
"verification_required": ["protocol conformance fixtures for session/create prompt/stream cancel error", "capability negotiation backwards compatibility"],
|
||||
"deferral_rationale": "Engine API should expose, not invent, stable core control-plane semantics after alpha contracts land.",
|
||||
"classification_rationale": "Useful integration surface but too broad for alpha unless narrowed to existing session control API."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3034",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3034",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3034,
|
||||
"title": "docs: define evidence-gated Hermes handoff loop for Claw Code execution",
|
||||
"theme": "sdlc/evidence-handoff",
|
||||
"release_bucket": "post_2_0_research",
|
||||
"lifecycle_status": "deferred_with_rationale",
|
||||
"roadmap_anchor": "ROADMAP.md#4-canonical-lane-event-schema; ROADMAP.md#10-typed-task-packet-format",
|
||||
"dependencies": ["typed task packet", "evidence bundle schema", "report gate status vocabulary"],
|
||||
"verification_required": ["handoff packet fixture validates scope/success/test evidence fields", "post-flight gate consumes evidence instead of free-text summary"],
|
||||
"deferral_rationale": "Can inform event/report/task contracts, but Hermes-specific loop should stay research/docs until core schemas are stable.",
|
||||
"classification_rationale": "Only the generic evidence-gated contract is Claw 2.0; Hermes branding is not core."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3035",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3035",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3035,
|
||||
"title": "fix: improve compacted session resume discoverability",
|
||||
"theme": "session-resume/discoverability",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#8-recovery-recipes-for-common-failures; ROADMAP.md#160-session_store-has-no-list_sessions-delete_session-or-session_exists",
|
||||
"dependencies": ["session enumeration", "latest-session workspace search boundary", "compacted session marker"],
|
||||
"verification_required": ["/resume latest finds newest eligible compacted session", "/session or status lists resumable compacted sessions with path/id"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Session recovery/adoption item."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3036",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3036",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3036,
|
||||
"title": "docs: add official Ollama/llama.cpp/vLLM local model examples",
|
||||
"theme": "provider/local-docs",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#145-boot-preflight-doctor-contract; ROADMAP.md#5-failure-taxonomy",
|
||||
"dependencies": ["known-good local provider examples", "raw /v1 smoke test", "tool-call limitation warning"],
|
||||
"verification_required": ["docs include Ollama/llama.cpp/vLLM examples and HELLO smoke", "tool-call caveats are explicit"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Local provider adoption support."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3037",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3037",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3037,
|
||||
"title": "docs: clarify Claw Code positioning as multi-provider Claude-Code-shaped runtime",
|
||||
"theme": "docs/product-positioning",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"roadmap_anchor": "ROADMAP.md#goal; ROADMAP.md#definition-of-clawable",
|
||||
"dependencies": ["README positioning copy", "provider support truth table", "identity leak bug policy"],
|
||||
"verification_required": ["README/docs answer Claude-only question directly", "provider support wording matches implemented routes"],
|
||||
"deferral_rationale": null,
|
||||
"classification_rationale": "Clarifies product identity for adoption without broad implementation."
|
||||
},
|
||||
{
|
||||
"id": "CC2-ISSUE-3038",
|
||||
"source_anchor": "https://github.com/ultraworkers/claw-code/issues/3038",
|
||||
"source_type": "github_issue",
|
||||
"source_number": 3038,
|
||||
"title": "roadmap: track skills/plugins/marketplace ecosystem gap after core UX stabilizes",
|
||||
"theme": "plugin-marketplace/ecosystem",
|
||||
"release_bucket": "ga_ecosystem",
|
||||
"lifecycle_status": "deferred_with_rationale",
|
||||
"roadmap_anchor": "ROADMAP.md#13-first-class-pluginmcp-lifecycle-contract; ROADMAP.md#14-mcp-end-to-end-lifecycle-parity",
|
||||
"dependencies": ["plugin/MCP lifecycle contract", "extension point inventory", "discovery/install/update flow design"],
|
||||
"verification_required": ["extension point inventory exists", "marketplace work explicitly depends on core UX stabilization"],
|
||||
"deferral_rationale": "Marketplace breadth should wait until core setup/auth/provider/session UX and plugin lifecycle are reliable.",
|
||||
"classification_rationale": "Matches plan's ga_ecosystem/post-2.0 caution for marketplace parity."
|
||||
}
|
||||
],
|
||||
"parity_rows": [
|
||||
{
|
||||
"id": "CC2-PARITY-OPENCODE-PLUGIN-ECOSYSTEM",
|
||||
"source_anchor": "anomalyco/opencode@27ac53aa packages/app/web/desktop/plugin/sdk/extensions/zed/slack/containers plus issue #3038",
|
||||
"source_type": "repo_clone_and_local_issue",
|
||||
"title": "Plugin/skills/marketplace ecosystem inventory",
|
||||
"release_bucket": "ga_ecosystem",
|
||||
"lifecycle_status": "deferred_with_rationale",
|
||||
"dependencies": ["Claw plugin/MCP lifecycle contract", "current extension-point inventory"],
|
||||
"verification_required": ["inventory maps current Claw plugin/skill/MCP extension points before marketplace implementation"],
|
||||
"deferral_rationale": "Adapt ecosystem discovery only after core setup/provider/session reliability is stable."
|
||||
},
|
||||
{
|
||||
"id": "CC2-PARITY-OPENCODE-PERMISSION-PRESETS",
|
||||
"source_anchor": "https://github.com/anomalyco/opencode/issues/27464 and ROADMAP.md#11-policy-engine-for-autonomous-coding",
|
||||
"source_type": "external_issue_and_roadmap",
|
||||
"title": "Quick permission preset switching mapped onto Claw policy profiles",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"dependencies": ["policy profile model", "approval-token audit trail"],
|
||||
"verification_required": ["preset switch is visible in status/report output and cannot bypass path-scope enforcement"],
|
||||
"deferral_rationale": null
|
||||
},
|
||||
{
|
||||
"id": "CC2-PARITY-OPENCODE-CUSTOM-PROVIDER-PARAMS",
|
||||
"source_anchor": "https://github.com/anomalyco/opencode/issues/27462 and #3030/#3032",
|
||||
"source_type": "external_issue_and_local_issue",
|
||||
"title": "Custom API parameter passthrough for provider profiles",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"dependencies": ["provider profile schema", "secret redaction", "request audit surface"],
|
||||
"verification_required": ["custom params are schema-validated, redacted, and visible as provenance without leaking secrets"],
|
||||
"deferral_rationale": null
|
||||
},
|
||||
{
|
||||
"id": "CC2-PARITY-OPENCODE-TODOWRITE-AUTOCOMPLETE",
|
||||
"source_anchor": "https://github.com/anomalyco/opencode/issues/27453 and ROADMAP.md#10-typed-task-packet-format",
|
||||
"source_type": "external_issue_and_roadmap",
|
||||
"title": "Task/Todo completion assistance via typed task lifecycle",
|
||||
"release_bucket": "ga_ecosystem",
|
||||
"lifecycle_status": "deferred_with_rationale",
|
||||
"dependencies": ["typed task packet", "task lifecycle events", "evidence-gated completion"],
|
||||
"verification_required": ["auto-complete suggestions cannot mark work complete without evidence bundle or explicit user approval"],
|
||||
"deferral_rationale": "Useful UX should follow, not precede, typed task lifecycle and evidence contract."
|
||||
},
|
||||
{
|
||||
"id": "CC2-PARITY-OPENCODE-WINDOWS-DISTRIBUTION",
|
||||
"source_anchor": "https://github.com/anomalyco/opencode/issues/27476 https://github.com/anomalyco/opencode/issues/27459 https://github.com/anomalyco/opencode/issues/27470 and #3006/#3029",
|
||||
"source_type": "external_issues_and_local_issues",
|
||||
"title": "Windows/GLIBC/distribution reliability parity lessons",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"dependencies": ["install artifact matrix", "Windows encoding guidance", "minimum Linux/GLIBC support statement"],
|
||||
"verification_required": ["release quickstart documents supported OS matrix and known terminal/encoding caveats"],
|
||||
"deferral_rationale": null
|
||||
},
|
||||
{
|
||||
"id": "CC2-PARITY-CODEX-GRANULAR-PERMISSIONS",
|
||||
"source_anchor": "https://github.com/openai/codex/issues/22595 and Codex docs permissions/app/plugin concepts",
|
||||
"source_type": "external_issue_and_docs",
|
||||
"title": "Granular app/plugin permissions adapted to Claw policy engine",
|
||||
"release_bucket": "alpha_blocker",
|
||||
"lifecycle_status": "active",
|
||||
"dependencies": ["permission enforcer path-scope fix", "plugin/MCP capability model", "approval-token replay protection"],
|
||||
"verification_required": ["granular permission grants do not widen workspace path scope implicitly"],
|
||||
"deferral_rationale": null
|
||||
},
|
||||
{
|
||||
"id": "CC2-PARITY-CODEX-SESSION-RECOVERY",
|
||||
"source_anchor": "https://github.com/openai/codex/issues/22619 https://github.com/openai/codex/issues/22597 https://github.com/openai/codex/issues/22593 and #3035",
|
||||
"source_type": "external_issues_and_local_issue",
|
||||
"title": "Safe local session/thread recovery without storage amplification",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"dependencies": ["session enumeration", "resume latest boundary", "JSONL/storage compaction policy"],
|
||||
"verification_required": ["recoverable sessions are discoverable and session forks avoid unbounded duplicate history"],
|
||||
"deferral_rationale": null
|
||||
},
|
||||
{
|
||||
"id": "CC2-PARITY-CODEX-PROXY-NETWORK",
|
||||
"source_anchor": "https://github.com/openai/codex/issues/22623 and #3032",
|
||||
"source_type": "external_issue_and_local_issue",
|
||||
"title": "Provider/network diagnostics include proxy behavior",
|
||||
"release_bucket": "beta_adoption",
|
||||
"lifecycle_status": "open",
|
||||
"dependencies": ["HTTP client proxy detection", "provider diagnostics playbook"],
|
||||
"verification_required": ["diagnostics report whether proxy env/config is honored for provider calls"],
|
||||
"deferral_rationale": null
|
||||
},
|
||||
{
|
||||
"id": "CC2-PARITY-CODEX-CLI-AGENT-FLAG",
|
||||
"source_anchor": "https://github.com/openai/codex/issues/22615 and ROADMAP.md#10-typed-task-packet-format",
|
||||
"source_type": "external_issue_and_roadmap",
|
||||
"title": "CLI flag for agent/subagent mode mapped to Claw typed task packets",
|
||||
"release_bucket": "ga_ecosystem",
|
||||
"lifecycle_status": "deferred_with_rationale",
|
||||
"dependencies": ["typed task packet", "session control API", "policy-scoped worker launch"],
|
||||
"verification_required": ["CLI agent mode cannot bypass task policy or evidence requirements"],
|
||||
"deferral_rationale": "Implement only after core task/session control contracts are stable."
|
||||
}
|
||||
],
|
||||
"coverage": {
|
||||
"required_latest_open_range_3028_3038": [3028, 3029, 3030, 3031, 3032, 3033, 3034, 3035, 3036, 3037, 3038],
|
||||
"required_existing_issue_numbers": [3007, 3006, 3020, 3005, 3003, 2997, 3023, 3004],
|
||||
"issue_rows_expected": 19,
|
||||
"parity_rows_expected_minimum": 6
|
||||
}
|
||||
}
|
||||
47
.omx/cc2/issue-parity-intake.md
Normal file
47
.omx/cc2/issue-parity-intake.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# CC2 Issue / Parity Intake Mapping
|
||||
|
||||
Generated by `worker-2` for team task 3 (`G001 issue/parity intake mapping`). This is a board-integration fragment for Stream 0; it intentionally does **not** mutate `.omx/ultragoal`.
|
||||
|
||||
## Covered local issue clusters
|
||||
|
||||
| Issue | Theme | Bucket | Lifecycle | Board anchor |
|
||||
|---:|---|---|---|---|
|
||||
| #3007 | security/path-scope | `alpha_blocker` | `active` | Policy engine + green-ness contract |
|
||||
| #3020 | provider/model-routing | `beta_adoption` | `open` | Provider routing/model source status |
|
||||
| #3006 | windows/install | `beta_adoption` | `open` | Immediate backlog / install readiness |
|
||||
| #3005 | provider/response-shape | `beta_adoption` | `open` | Failure taxonomy / provider diagnostics |
|
||||
| #3004 | ide/acp | `ga_ecosystem` | `deferred_with_rationale` | Plugin/MCP lifecycle maturity |
|
||||
| #3003 | session-hygiene/gitignore | `beta_adoption` | `open` | Green-ness / recovery hygiene |
|
||||
| #2997 | docs/license | `beta_adoption` | `open` | Adoption docs/license readiness |
|
||||
| #3023 | repo-hygiene/anti-slop | `beta_adoption` | `open` | Immediate backlog / PR quality gate |
|
||||
| #3028 | docs/navigation-context | `beta_adoption` | `open` | Human UX leaks into claw workflows |
|
||||
| #3029 | install/distribution | `beta_adoption` | `open` | Cross-platform release quickstart |
|
||||
| #3030 | provider/setup-profiles | `beta_adoption` | `open` | Boot preflight / structured session control |
|
||||
| #3031 | session-recovery/context-window | `beta_adoption` | `open` | Recovery recipes / compaction event |
|
||||
| #3032 | provider/diagnostics-docs | `beta_adoption` | `open` | Failure taxonomy |
|
||||
| #3033 | engine-api/control-plane | `ga_ecosystem` | `deferred_with_rationale` | Structured session control API |
|
||||
| #3034 | sdlc/evidence-handoff | `post_2_0_research` | `deferred_with_rationale` | Event/report/task contract input |
|
||||
| #3035 | session-resume/discoverability | `beta_adoption` | `open` | Recovery recipes / session enumeration |
|
||||
| #3036 | provider/local-docs | `beta_adoption` | `open` | Provider setup and diagnostics docs |
|
||||
| #3037 | docs/product-positioning | `beta_adoption` | `open` | Goal / definition of clawable |
|
||||
| #3038 | plugin-marketplace/ecosystem | `ga_ecosystem` | `deferred_with_rationale` | Plugin/MCP lifecycle maturity |
|
||||
|
||||
## Parity intake rows
|
||||
|
||||
| Row | Source | Bucket | Lifecycle | Adaptation rule |
|
||||
|---|---|---|---|---|
|
||||
| `CC2-PARITY-OPENCODE-PLUGIN-ECOSYSTEM` | opencode repo + #3038 | `ga_ecosystem` | `deferred_with_rationale` | Inventory Claw extension points before marketplace work. |
|
||||
| `CC2-PARITY-OPENCODE-PERMISSION-PRESETS` | opencode #27464 | `beta_adoption` | `open` | Permission preset UX must not bypass Claw path-scope policy. |
|
||||
| `CC2-PARITY-OPENCODE-CUSTOM-PROVIDER-PARAMS` | opencode #27462 + #3030/#3032 | `beta_adoption` | `open` | Custom provider params need schema validation, redaction, and provenance. |
|
||||
| `CC2-PARITY-OPENCODE-TODOWRITE-AUTOCOMPLETE` | opencode #27453 | `ga_ecosystem` | `deferred_with_rationale` | Auto-complete task UX follows typed task lifecycle/evidence gates. |
|
||||
| `CC2-PARITY-OPENCODE-WINDOWS-DISTRIBUTION` | opencode #27476/#27459/#27470 + #3006/#3029 | `beta_adoption` | `open` | Use external pain as release-matrix and diagnostics evidence. |
|
||||
| `CC2-PARITY-CODEX-GRANULAR-PERMISSIONS` | Codex #22595 + docs | `alpha_blocker` | `active` | Adapt granular permissions only through Claw policy engine and approval tokens. |
|
||||
| `CC2-PARITY-CODEX-SESSION-RECOVERY` | Codex #22619/#22597/#22593 + #3035 | `beta_adoption` | `open` | Session discovery/recovery must avoid storage amplification. |
|
||||
| `CC2-PARITY-CODEX-PROXY-NETWORK` | Codex #22623 + #3032 | `beta_adoption` | `open` | Provider diagnostics should expose proxy behavior. |
|
||||
| `CC2-PARITY-CODEX-CLI-AGENT-FLAG` | Codex #22615 | `ga_ecosystem` | `deferred_with_rationale` | CLI agent mode waits for typed task/session control contracts. |
|
||||
|
||||
Validation command:
|
||||
|
||||
```bash
|
||||
python3 .omx/cc2/validate_issue_parity_intake.py
|
||||
```
|
||||
250
.omx/cc2/render_board_md.py
Executable file
250
.omx/cc2/render_board_md.py
Executable file
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render the Claw Code 2.0 canonical board JSON as a human-readable Markdown board."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
STATUS_DESCRIPTIONS = {
|
||||
"context": "Context-only heading or evidence anchor; not an implementation work item.",
|
||||
"active": "Current Claw Code 2.0 implementation surface that should remain visible on the board.",
|
||||
"open": "Actionable unresolved work that needs implementation or acceptance evidence.",
|
||||
"done_verify": "Marked as done upstream but retained for verification against current CC2 behavior.",
|
||||
"stale_done": "Historically completed or merged work that may be stale and needs freshness checks before relying on it.",
|
||||
"superseded": "Replaced by a newer item; keep as traceability context only.",
|
||||
"deferred_with_rationale": "Intentionally deferred; rationale must be present in the board item.",
|
||||
"rejected_not_claw": "Excluded because it is not Claw Code product work.",
|
||||
}
|
||||
|
||||
BUCKET_DESCRIPTIONS = {
|
||||
"alpha_blocker": "Must be resolved before alpha-quality autonomous coding lanes are dependable.",
|
||||
"beta_adoption": "Important for broader dogfood/adoption once alpha blockers are controlled.",
|
||||
"ga_ecosystem": "Required for mature plugin/MCP/provider ecosystem behavior.",
|
||||
"2.x_intake": "Post-2.0 intake or follow-up candidate retained for sequencing.",
|
||||
"post_2_0_research": "Research-oriented item not required for the CC2 board cut.",
|
||||
"context": "Non-actionable roadmap context.",
|
||||
"rejected_not_claw": "Explicit non-Claw rejection bucket.",
|
||||
}
|
||||
|
||||
LANE_TITLES = {
|
||||
"stream_0_governance": "Stream 0 — Governance, intake, and cross-cutting roadmap triage",
|
||||
"stream_1_worker_boot_session_control": "Stream 1 — Worker boot and session control",
|
||||
"stream_2_event_reporting_contracts": "Stream 2 — Event/reporting contracts",
|
||||
"stream_3_branch_test_recovery": "Stream 3 — Branch/test recovery",
|
||||
"stream_4_claws_first_execution": "Stream 4 — Claws-first task execution",
|
||||
"stream_5_plugin_mcp_lifecycle": "Stream 5 — Plugin/MCP lifecycle",
|
||||
"adoption_overlay": "Adoption overlay — user-visible parity and release polish",
|
||||
"parity_overlay": "Parity overlay — opencode/codex comparison context",
|
||||
}
|
||||
|
||||
REQUIRED_ITEM_FIELDS = [
|
||||
"id",
|
||||
"title",
|
||||
"source_anchor",
|
||||
"source_type",
|
||||
"release_bucket",
|
||||
"lifecycle_status",
|
||||
"dependencies",
|
||||
"verification_required",
|
||||
"deferral_rationale",
|
||||
]
|
||||
|
||||
|
||||
def load_board(path: Path) -> dict[str, Any]:
|
||||
with path.open() as f:
|
||||
board = json.load(f)
|
||||
if not isinstance(board, dict):
|
||||
raise ValueError("board JSON root must be an object")
|
||||
items = board.get("items")
|
||||
if not isinstance(items, list):
|
||||
raise ValueError("board JSON must contain an items array")
|
||||
return board
|
||||
|
||||
|
||||
def validate_board(board: dict[str, Any]) -> list[str]:
|
||||
errors: list[str] = []
|
||||
coverage = board.get("coverage", {})
|
||||
if coverage.get("unmapped_roadmap_heading_lines"):
|
||||
errors.append(f"unmapped roadmap heading lines: {coverage['unmapped_roadmap_heading_lines']}")
|
||||
if coverage.get("roadmap_headings_mapped") != coverage.get("roadmap_headings_total"):
|
||||
errors.append("roadmap heading coverage is incomplete")
|
||||
if coverage.get("roadmap_actions_mapped") != coverage.get("roadmap_actions_total"):
|
||||
errors.append("roadmap ordered-action coverage is incomplete")
|
||||
|
||||
allowed_status = set(board.get("generation_policy", {}).get("status_values", []))
|
||||
allowed_buckets = set(board.get("generation_policy", {}).get("release_buckets", []))
|
||||
seen_ids: set[str] = set()
|
||||
for index, item in enumerate(board["items"], 1):
|
||||
for field in REQUIRED_ITEM_FIELDS:
|
||||
if field not in item:
|
||||
errors.append(f"item {index} missing required field {field}")
|
||||
item_id = item.get("id")
|
||||
if item_id in seen_ids:
|
||||
errors.append(f"duplicate item id {item_id}")
|
||||
seen_ids.add(item_id)
|
||||
status = item.get("lifecycle_status")
|
||||
bucket = item.get("release_bucket")
|
||||
if allowed_status and status not in allowed_status:
|
||||
errors.append(f"{item_id} has unknown lifecycle_status {status!r}")
|
||||
if allowed_buckets and bucket not in allowed_buckets:
|
||||
errors.append(f"{item_id} has unknown release_bucket {bucket!r}")
|
||||
if status == "deferred_with_rationale" and not str(item.get("deferral_rationale", "")).strip():
|
||||
errors.append(f"{item_id} is deferred without deferral_rationale")
|
||||
return errors
|
||||
|
||||
|
||||
def table(headers: list[str], rows: list[list[Any]]) -> list[str]:
|
||||
out = ["| " + " | ".join(headers) + " |", "| " + " | ".join("---" for _ in headers) + " |"]
|
||||
for row in rows:
|
||||
out.append("| " + " | ".join(str(cell) for cell in row) + " |")
|
||||
return out
|
||||
|
||||
|
||||
def fmt_list(value: Any) -> str:
|
||||
if not value:
|
||||
return "none"
|
||||
if isinstance(value, list):
|
||||
return ", ".join(f"`{v}`" for v in value) if value else "none"
|
||||
return f"`{value}`"
|
||||
|
||||
|
||||
def render(board: dict[str, Any]) -> str:
|
||||
items: list[dict[str, Any]] = board["items"]
|
||||
summary = board.get("summary", {})
|
||||
coverage = board.get("coverage", {})
|
||||
sources = board.get("sources", {})
|
||||
policy = board.get("generation_policy", {})
|
||||
by_lane = Counter(item.get("owner_lane", "unassigned") for item in items)
|
||||
by_status = Counter(item.get("lifecycle_status", "unknown") for item in items)
|
||||
by_bucket = Counter(item.get("release_bucket", "unknown") for item in items)
|
||||
by_source = Counter(item.get("source_type", "unknown") for item in items)
|
||||
|
||||
lines: list[str] = []
|
||||
lines.append("# Claw Code 2.0 Canonical Board")
|
||||
lines.append("")
|
||||
lines.append(f"Generated from board schema: `{board.get('generated_at', 'unknown')}`")
|
||||
lines.append(f"Schema version: `{board.get('schema_version', 'unknown')}`")
|
||||
lines.append("Ultragoal mutation policy: `.omx/ultragoal` is leader-owned and was not modified by this rendering task.")
|
||||
lines.append("")
|
||||
|
||||
lines.append("## Evidence Freeze")
|
||||
lines.append("")
|
||||
roadmap = sources.get("roadmap", {})
|
||||
research = sources.get("research", {})
|
||||
plan = sources.get("approved_plan", {})
|
||||
lines.extend(table(["Source", "Frozen evidence"], [
|
||||
["Roadmap", f"`{roadmap.get('path', 'ROADMAP.md')}` sha256 prefix `{roadmap.get('sha256_prefix', 'unknown')}`; {roadmap.get('heading_count', '?')} headings; {roadmap.get('ordered_action_count', '?')} ordered actions"],
|
||||
["Approved plan", f"`{plan.get('path', '.omx/plans/claw-code-2-0-adaptive-plan.md')}` sha256 prefix `{plan.get('sha256_prefix', 'unknown')}`"],
|
||||
["Research bundle", f"root `{research.get('root', '.omx/research')}`; latest open issues {research.get('claw_open_latest_count', '?')}; issue corpus {research.get('claw_issues_count', '?')}; codex/opencode clone metadata included"],
|
||||
]))
|
||||
lines.append("")
|
||||
|
||||
lines.append("## Roadmap Coverage Summary")
|
||||
lines.append("")
|
||||
heading_total = coverage.get("roadmap_headings_total", 0)
|
||||
heading_mapped = coverage.get("roadmap_headings_mapped", 0)
|
||||
action_total = coverage.get("roadmap_actions_total", 0)
|
||||
action_mapped = coverage.get("roadmap_actions_mapped", 0)
|
||||
lines.extend(table(["Coverage gate", "Mapped", "Total", "Status"], [
|
||||
["ROADMAP headings", heading_mapped, heading_total, "PASS" if heading_mapped == heading_total and not coverage.get("unmapped_roadmap_heading_lines") else "FAIL"],
|
||||
["ROADMAP ordered actions", action_mapped, action_total, "PASS" if action_mapped == action_total else "FAIL"],
|
||||
["Duplicate heading lines", len(coverage.get("duplicate_roadmap_heading_lines", [])), 0, "PASS" if not coverage.get("duplicate_roadmap_heading_lines") else "WARN"],
|
||||
]))
|
||||
lines.append("")
|
||||
lines.append(f"Total canonical board items: **{len(items)}**")
|
||||
lines.append("")
|
||||
|
||||
lines.append("## Lifecycle Enum Reference")
|
||||
lines.append("")
|
||||
status_rows = []
|
||||
for status in policy.get("status_values", sorted(by_status)):
|
||||
status_rows.append([f"`{status}`", by_status.get(status, 0), STATUS_DESCRIPTIONS.get(status, "Board-defined lifecycle status.")])
|
||||
lines.extend(table(["Lifecycle", "Count", "Meaning"], status_rows))
|
||||
lines.append("")
|
||||
|
||||
lines.append("## Release Bucket Reference")
|
||||
lines.append("")
|
||||
bucket_rows = []
|
||||
for bucket in policy.get("release_buckets", sorted(by_bucket)):
|
||||
bucket_rows.append([f"`{bucket}`", by_bucket.get(bucket, 0), BUCKET_DESCRIPTIONS.get(bucket, "Board-defined release bucket.")])
|
||||
lines.extend(table(["Bucket", "Count", "Meaning"], bucket_rows))
|
||||
lines.append("")
|
||||
|
||||
lines.append("## Stream Summaries")
|
||||
lines.append("")
|
||||
lane_rows = []
|
||||
for lane, count in sorted(by_lane.items()):
|
||||
lane_items = [item for item in items if item.get("owner_lane") == lane]
|
||||
lane_status = Counter(item.get("lifecycle_status") for item in lane_items)
|
||||
open_like = lane_status.get("active", 0) + lane_status.get("open", 0) + lane_status.get("done_verify", 0)
|
||||
lane_rows.append([
|
||||
LANE_TITLES.get(lane, lane),
|
||||
count,
|
||||
open_like,
|
||||
", ".join(f"`{k}` {v}" for k, v in sorted(lane_status.items())),
|
||||
])
|
||||
lines.extend(table(["Stream / lane", "Items", "Active+open+verify", "Lifecycle mix"], lane_rows))
|
||||
lines.append("")
|
||||
|
||||
lines.append("## Source-Type Mix")
|
||||
lines.append("")
|
||||
lines.extend(table(["Source type", "Items"], [[f"`{k}`", v] for k, v in sorted(by_source.items())]))
|
||||
lines.append("")
|
||||
|
||||
lines.append("## Board Items by Stream")
|
||||
lines.append("")
|
||||
for lane in sorted(by_lane):
|
||||
lane_items = [item for item in items if item.get("owner_lane") == lane]
|
||||
lines.append(f"### {LANE_TITLES.get(lane, lane)}")
|
||||
lines.append("")
|
||||
lines.extend(table(
|
||||
["ID", "Title", "Source", "Bucket", "Lifecycle", "Verification", "Dependencies", "Deferral"],
|
||||
[[
|
||||
f"`{item.get('id')}`",
|
||||
str(item.get("title", "")).replace("|", "\\|"),
|
||||
f"`{item.get('source_anchor')}` / `{item.get('source_type')}`",
|
||||
f"`{item.get('release_bucket')}`",
|
||||
f"`{item.get('lifecycle_status')}`",
|
||||
f"`{item.get('verification_required')}`",
|
||||
fmt_list(item.get("dependencies")),
|
||||
str(item.get("deferral_rationale") or "—").replace("|", "\\|"),
|
||||
] for item in lane_items]
|
||||
))
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("board_json", type=Path)
|
||||
parser.add_argument("board_md", type=Path)
|
||||
parser.add_argument("--check", action="store_true", help="fail if board_md is not up to date")
|
||||
args = parser.parse_args()
|
||||
|
||||
board = load_board(args.board_json)
|
||||
errors = validate_board(board)
|
||||
if errors:
|
||||
for error in errors:
|
||||
print(f"ERROR: {error}", file=sys.stderr)
|
||||
return 1
|
||||
rendered = render(board)
|
||||
if args.check:
|
||||
existing = args.board_md.read_text() if args.board_md.exists() else ""
|
||||
if existing != rendered:
|
||||
print(f"ERROR: {args.board_md} is not up to date", file=sys.stderr)
|
||||
return 1
|
||||
print(f"PASS: {args.board_md} is up to date and roadmap coverage is complete")
|
||||
return 0
|
||||
args.board_md.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.board_md.write_text(rendered)
|
||||
print(f"wrote {args.board_md}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
58
.omx/cc2/validate_issue_parity_intake.py
Executable file
58
.omx/cc2/validate_issue_parity_intake.py
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the worker-2 CC2 issue/parity intake fragment."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
INTAKE = ROOT / ".omx" / "cc2" / "issue-parity-intake.json"
|
||||
REQUIRED_ISSUES = set(range(3028, 3039)) | {3007, 3006, 3020, 3005, 3003, 2997, 3023, 3004}
|
||||
ALLOWED_STATUS = {
|
||||
"context",
|
||||
"active",
|
||||
"open",
|
||||
"done_verify",
|
||||
"stale_done",
|
||||
"superseded",
|
||||
"deferred_with_rationale",
|
||||
"rejected_not_claw",
|
||||
}
|
||||
ALLOWED_BUCKETS = {"alpha_blocker", "beta_adoption", "ga_ecosystem", "post_2_0_research"}
|
||||
|
||||
|
||||
def require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise SystemExit(f"FAIL: {message}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
data = json.loads(INTAKE.read_text())
|
||||
issue_rows = data.get("issue_clusters", [])
|
||||
parity_rows = data.get("parity_rows", [])
|
||||
|
||||
seen = {row.get("source_number") for row in issue_rows}
|
||||
missing = sorted(REQUIRED_ISSUES - seen)
|
||||
extra = sorted(seen - REQUIRED_ISSUES)
|
||||
require(not missing, f"missing required issue rows: {missing}")
|
||||
require(not extra, f"unexpected issue rows in scoped intake: {extra}")
|
||||
require(len(issue_rows) == len(REQUIRED_ISSUES), "duplicate or missing issue row count")
|
||||
|
||||
ids = [row.get("id") for row in issue_rows + parity_rows]
|
||||
require(len(ids) == len(set(ids)), "duplicate ids present")
|
||||
|
||||
for row in issue_rows + parity_rows:
|
||||
row_id = row.get("id")
|
||||
for field in ["source_anchor", "source_type", "release_bucket", "lifecycle_status", "dependencies", "verification_required"]:
|
||||
require(row.get(field) not in (None, "", []), f"{row_id} missing {field}")
|
||||
require(row["release_bucket"] in ALLOWED_BUCKETS, f"{row_id} invalid release_bucket {row['release_bucket']}")
|
||||
require(row["lifecycle_status"] in ALLOWED_STATUS, f"{row_id} invalid lifecycle_status {row['lifecycle_status']}")
|
||||
if row["lifecycle_status"] == "deferred_with_rationale":
|
||||
require(row.get("deferral_rationale"), f"{row_id} deferred without rationale")
|
||||
|
||||
require(len(parity_rows) >= data["coverage"]["parity_rows_expected_minimum"], "not enough parity rows")
|
||||
print(f"PASS issue/parity intake: {len(issue_rows)} issue rows, {len(parity_rows)} parity rows")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
583
.omx/ultragoal/g010-final-quality-gate-rerun.log
Normal file
583
.omx/ultragoal/g010-final-quality-gate-rerun.log
Normal file
@@ -0,0 +1,583 @@
|
||||
G010 final leader verification rerun started 2026-05-15T02:19:36Z
|
||||
== artifact checklist ==
|
||||
PASS docs/g010-clone-disambiguation-metadata.md exists
|
||||
PASS docs/g010-session-hygiene-verification-map.md exists
|
||||
.claw/sessions/example.jsonl
|
||||
rust/.claw/sessions/example.jsonl
|
||||
.claude/sessions/example.json
|
||||
== fmt ==
|
||||
== runtime session_control retry ==
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.13s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/runtime-0e7d3d46ae40aa07)
|
||||
|
||||
running 15 tests
|
||||
test session_control::tests::latest_session_prefers_semantic_updated_at_over_file_mtime ... ok
|
||||
test session_control::tests::session_store_from_cwd_canonicalizes_equivalent_paths ... ok
|
||||
test session_control::tests::session_store_fork_stays_in_same_namespace ... ok
|
||||
test session_control::tests::session_exists_and_delete_are_scoped_to_workspace_store ... ok
|
||||
test session_control::tests::resolves_latest_alias_and_loads_session_from_workspace_root ... ok
|
||||
test session_control::tests::forks_session_into_managed_storage_with_lineage ... ok
|
||||
test session_control::tests::workspace_fingerprint_is_deterministic_and_differs_per_path ... ok
|
||||
test session_control::tests::session_store_create_and_load_round_trip ... ok
|
||||
test session_control::tests::session_store_from_cwd_isolates_sessions_by_workspace ... ok
|
||||
test session_control::tests::creates_and_lists_managed_sessions ... ok
|
||||
test session_control::tests::session_store_from_data_dir_namespaces_by_workspace ... ok
|
||||
test session_control::tests::session_store_latest_and_resolve_reference ... ok
|
||||
test session_control::tests::session_store_loads_safe_legacy_session_from_same_workspace ... ok
|
||||
test session_control::tests::session_store_rejects_legacy_session_from_other_workspace ... ok
|
||||
test session_control::tests::session_store_loads_unbound_legacy_session_from_same_workspace ... ok
|
||||
|
||||
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 542 filtered out; finished in 0.02s
|
||||
|
||||
Running tests/g004_conformance.rs (rust/target/debug/deps/g004_conformance-90f36d1f871b6313)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
|
||||
|
||||
Running tests/integration_tests.rs (rust/target/debug/deps/integration_tests-526d4f853fc590de)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 12 filtered out; finished in 0.00s
|
||||
|
||||
== runtime jsonl safeguards ==
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.12s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/runtime-0e7d3d46ae40aa07)
|
||||
|
||||
running 1 test
|
||||
test session::tests::jsonl_persistence_redacts_and_truncates_oversized_payload_fields ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 556 filtered out; finished in 0.02s
|
||||
|
||||
Running tests/g004_conformance.rs (rust/target/debug/deps/g004_conformance-90f36d1f871b6313)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
|
||||
|
||||
Running tests/integration_tests.rs (rust/target/debug/deps/integration_tests-526d4f853fc590de)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 12 filtered out; finished in 0.00s
|
||||
|
||||
== runtime compact ==
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.12s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/runtime-0e7d3d46ae40aa07)
|
||||
|
||||
running 17 tests
|
||||
test compact::tests::compaction_does_not_split_tool_use_tool_result_pair ... ok
|
||||
test compact::tests::formats_compact_summary_like_upstream ... ok
|
||||
test compact::tests::ignores_existing_compacted_summary_when_deciding_to_recompact ... ok
|
||||
test compact::tests::infers_pending_work_from_recent_messages ... ok
|
||||
test compact::tests::extracts_key_files_from_message_content ... ok
|
||||
test compact::tests::leaves_small_sessions_unchanged ... ok
|
||||
test compact::tests::truncates_long_blocks_in_summary ... ok
|
||||
test conversation::tests::auto_compaction_threshold_defaults_and_parses_values ... ok
|
||||
test compact::tests::compacts_older_messages_into_a_system_summary ... ok
|
||||
test conversation::tests::compaction_health_probe_skips_empty_compacted_session ... ok
|
||||
test conversation::tests::compaction_health_probe_blocks_turn_when_tool_executor_is_broken ... ok
|
||||
test conversation::tests::auto_compacts_when_cumulative_input_threshold_is_crossed ... ok
|
||||
test conversation::tests::skips_auto_compaction_below_threshold ... ok
|
||||
test prompt::tests::displays_context_paths_compactly ... ok
|
||||
test conversation::tests::compacts_session_after_turns ... ok
|
||||
test compact::tests::keeps_previous_compacted_context_when_compacting_again ... ok
|
||||
test session::tests::persists_compaction_metadata ... ok
|
||||
|
||||
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 540 filtered out; finished in 0.01s
|
||||
|
||||
Running tests/g004_conformance.rs (rust/target/debug/deps/g004_conformance-90f36d1f871b6313)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
|
||||
|
||||
Running tests/integration_tests.rs (rust/target/debug/deps/integration_tests-526d4f853fc590de)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 12 filtered out; finished in 0.00s
|
||||
|
||||
== commands parses_supported_slash_commands ==
|
||||
Compiling commands v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/commands)
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 2.34s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/commands-0104b50ff2e54ccc)
|
||||
|
||||
running 1 test
|
||||
test tests::parses_supported_slash_commands ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 41 filtered out; finished in 0.00s
|
||||
|
||||
== commands compacts_sessions_via_slash_command ==
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.12s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/commands-0104b50ff2e54ccc)
|
||||
|
||||
running 1 test
|
||||
test tests::compacts_sessions_via_slash_command ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 41 filtered out; finished in 0.00s
|
||||
|
||||
== cli session json contracts ==
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 3.47s
|
||||
Running unittests src/main.rs (rust/target/debug/deps/claw-f425f0b21e915b27)
|
||||
|
||||
running 1 test
|
||||
test tests::session_exists_resume_command_reports_json_contract ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 193 filtered out; finished in 0.00s
|
||||
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 2.76s
|
||||
Running unittests src/main.rs (rust/target/debug/deps/claw-f425f0b21e915b27)
|
||||
|
||||
running 1 test
|
||||
test tests::resumed_session_exists_and_delete_have_json_contracts ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 193 filtered out; finished in 0.01s
|
||||
|
||||
== cli resume slash commands ==
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 4.23s
|
||||
Running tests/resume_slash_commands.rs (rust/target/debug/deps/resume_slash_commands-6c1fb347be3842ef)
|
||||
|
||||
running 12 tests
|
||||
test resumed_stub_command_emits_not_implemented_json ... ok
|
||||
test resumed_help_command_emits_structured_json ... ok
|
||||
test resumed_no_command_emits_restored_json ... ok
|
||||
test resumed_sandbox_command_emits_structured_json_when_requested ... ok
|
||||
test resumed_export_command_emits_structured_json ... ok
|
||||
test resumed_config_command_loads_settings_files_end_to_end ... ok
|
||||
test resumed_binary_accepts_slash_commands_with_arguments ... ok
|
||||
test resumed_version_command_emits_structured_json ... ok
|
||||
test resumed_status_surfaces_persisted_model ... ok
|
||||
test resume_latest_restores_the_most_recent_managed_session ... ok
|
||||
test status_command_applies_cli_flags_end_to_end ... ok
|
||||
test resumed_status_command_emits_structured_json_when_requested ... ok
|
||||
|
||||
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.25s
|
||||
|
||||
== cli compact output ==
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 2.72s
|
||||
Running tests/compact_output.rs (rust/target/debug/deps/compact_output-988ab05f11fedc49)
|
||||
|
||||
running 4 tests
|
||||
test compact_flag_with_json_output_emits_structured_json ... ok
|
||||
test compact_flag_streaming_text_only_emits_final_message_text ... ok
|
||||
test compact_flag_prints_only_final_assistant_text_without_tool_call_details ... ok
|
||||
test text_prompt_mode_prints_final_assistant_text_after_spinner ... ok
|
||||
|
||||
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.14s
|
||||
|
||||
== workspace check ==
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.49s
|
||||
== diff check ==
|
||||
G010 final leader verification rerun completed 2026-05-15T02:20:06Z
|
||||
644
.omx/ultragoal/g010-final-quality-gate.log
Normal file
644
.omx/ultragoal/g010-final-quality-gate.log
Normal file
@@ -0,0 +1,644 @@
|
||||
G010 final leader verification started 2026-05-15T02:17:45Z
|
||||
== artifact checklist ==
|
||||
PASS docs/g010-clone-disambiguation-metadata.md exists
|
||||
PASS docs/g010-session-hygiene-verification-map.md exists
|
||||
.gitignore:.claw/sessions/
|
||||
rust/.gitignore:.claw/sessions/
|
||||
.claw/sessions/example.jsonl
|
||||
rust/.claw/sessions/example.jsonl
|
||||
.claude/sessions/example.json
|
||||
== fmt ==
|
||||
== runtime session_control ==
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.14s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/runtime-0e7d3d46ae40aa07)
|
||||
|
||||
running 15 tests
|
||||
test session_control::tests::latest_session_prefers_semantic_updated_at_over_file_mtime ... ok
|
||||
test session_control::tests::session_store_from_cwd_canonicalizes_equivalent_paths ... ok
|
||||
|
||||
thread 'session_control::tests::session_store_fork_stays_in_same_namespace' (403821665) panicked at crates/runtime/src/session_control.rs:775:14:
|
||||
session should persist: Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
test session_control::tests::session_exists_and_delete_are_scoped_to_workspace_store ... ok
|
||||
test session_control::tests::forks_session_into_managed_storage_with_lineage ... ok
|
||||
test session_control::tests::creates_and_lists_managed_sessions ... ok
|
||||
test session_control::tests::session_store_from_cwd_isolates_sessions_by_workspace ... ok
|
||||
test session_control::tests::session_store_latest_and_resolve_reference ... ok
|
||||
test session_control::tests::session_store_loads_safe_legacy_session_from_same_workspace ... ok
|
||||
test session_control::tests::workspace_fingerprint_is_deterministic_and_differs_per_path ... ok
|
||||
test session_control::tests::session_store_create_and_load_round_trip ... ok
|
||||
test session_control::tests::session_store_fork_stays_in_same_namespace ... FAILED
|
||||
test session_control::tests::session_store_loads_unbound_legacy_session_from_same_workspace ... ok
|
||||
test session_control::tests::session_store_from_data_dir_namespaces_by_workspace ... ok
|
||||
test session_control::tests::session_store_rejects_legacy_session_from_other_workspace ... ok
|
||||
test session_control::tests::resolves_latest_alias_and_loads_session_from_workspace_root ... ok
|
||||
|
||||
failures:
|
||||
|
||||
failures:
|
||||
session_control::tests::session_store_fork_stays_in_same_namespace
|
||||
|
||||
test result: FAILED. 14 passed; 1 failed; 0 ignored; 0 measured; 542 filtered out; finished in 0.03s
|
||||
|
||||
error: test failed, to rerun pass `-p runtime --lib`
|
||||
== runtime jsonl safeguards ==
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.13s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/runtime-0e7d3d46ae40aa07)
|
||||
|
||||
running 1 test
|
||||
test session::tests::jsonl_persistence_redacts_and_truncates_oversized_payload_fields ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 556 filtered out; finished in 0.02s
|
||||
|
||||
Running tests/g004_conformance.rs (rust/target/debug/deps/g004_conformance-90f36d1f871b6313)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
|
||||
|
||||
Running tests/integration_tests.rs (rust/target/debug/deps/integration_tests-526d4f853fc590de)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 12 filtered out; finished in 0.00s
|
||||
|
||||
== runtime compact ==
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.13s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/runtime-0e7d3d46ae40aa07)
|
||||
|
||||
running 17 tests
|
||||
test compact::tests::formats_compact_summary_like_upstream ... ok
|
||||
test compact::tests::truncates_long_blocks_in_summary ... ok
|
||||
test compact::tests::ignores_existing_compacted_summary_when_deciding_to_recompact ... ok
|
||||
test compact::tests::infers_pending_work_from_recent_messages ... ok
|
||||
test compact::tests::compaction_does_not_split_tool_use_tool_result_pair ... ok
|
||||
test compact::tests::leaves_small_sessions_unchanged ... ok
|
||||
test conversation::tests::auto_compaction_threshold_defaults_and_parses_values ... ok
|
||||
test compact::tests::extracts_key_files_from_message_content ... ok
|
||||
test compact::tests::compacts_older_messages_into_a_system_summary ... ok
|
||||
test prompt::tests::displays_context_paths_compactly ... ok
|
||||
test conversation::tests::skips_auto_compaction_below_threshold ... ok
|
||||
test conversation::tests::compaction_health_probe_skips_empty_compacted_session ... ok
|
||||
test conversation::tests::compacts_session_after_turns ... ok
|
||||
test conversation::tests::compaction_health_probe_blocks_turn_when_tool_executor_is_broken ... ok
|
||||
test conversation::tests::auto_compacts_when_cumulative_input_threshold_is_crossed ... ok
|
||||
test compact::tests::keeps_previous_compacted_context_when_compacting_again ... ok
|
||||
test session::tests::persists_compaction_metadata ... ok
|
||||
|
||||
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 540 filtered out; finished in 0.01s
|
||||
|
||||
Running tests/g004_conformance.rs (rust/target/debug/deps/g004_conformance-90f36d1f871b6313)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
|
||||
|
||||
Running tests/integration_tests.rs (rust/target/debug/deps/integration_tests-526d4f853fc590de)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 12 filtered out; finished in 0.00s
|
||||
|
||||
== commands session/compact slash ==
|
||||
error: unexpected argument 'compacts_sessions_via_slash_command' found
|
||||
|
||||
Usage: cargo test [OPTIONS] [TESTNAME] [-- [ARGS]...]
|
||||
|
||||
For more information, try '--help'.
|
||||
== cli session json contracts ==
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
error[E0004]: non-exhaustive patterns: `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
--> crates/rusty-claude-cli/src/main.rs:3823:11
|
||||
|
|
||||
3823 | match command {
|
||||
| ^^^^^^^ pattern `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
|
|
||||
note: `SlashCommand` defined here
|
||||
--> crates/commands/src/lib.rs:1040:1
|
||||
|
|
||||
1040 | pub enum SlashCommand {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
1089 | Session {
|
||||
| ------- not covered
|
||||
= note: the matched value is of type `&SlashCommand`
|
||||
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
||||
|
|
||||
4200 ~ | SlashCommand::AddDir { .. } => Err("unsupported resumed slash command".into()),
|
||||
4201 ~ &SlashCommand::Session { action: Some(_), target: None } => todo!(),
|
||||
|
|
||||
|
||||
For more information about this error, try `rustc --explain E0004`.
|
||||
error: could not compile `rusty-claude-cli` (bin "claw" test) due to 1 previous error
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
error[E0004]: non-exhaustive patterns: `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
--> crates/rusty-claude-cli/src/main.rs:3823:11
|
||||
|
|
||||
3823 | match command {
|
||||
| ^^^^^^^ pattern `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
|
|
||||
note: `SlashCommand` defined here
|
||||
--> crates/commands/src/lib.rs:1040:1
|
||||
|
|
||||
1040 | pub enum SlashCommand {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
1089 | Session {
|
||||
| ------- not covered
|
||||
= note: the matched value is of type `&SlashCommand`
|
||||
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
||||
|
|
||||
4200 ~ | SlashCommand::AddDir { .. } => Err("unsupported resumed slash command".into()),
|
||||
4201 ~ &SlashCommand::Session { action: Some(_), target: None } => todo!(),
|
||||
|
|
||||
|
||||
For more information about this error, try `rustc --explain E0004`.
|
||||
error: could not compile `rusty-claude-cli` (bin "claw" test) due to 1 previous error
|
||||
== cli resume slash commands ==
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
error[E0004]: non-exhaustive patterns: `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
--> crates/rusty-claude-cli/src/main.rs:3823:11
|
||||
|
|
||||
3823 | match command {
|
||||
| ^^^^^^^ pattern `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
|
|
||||
note: `SlashCommand` defined here
|
||||
--> crates/commands/src/lib.rs:1040:1
|
||||
|
|
||||
1040 | pub enum SlashCommand {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
1089 | Session {
|
||||
| ------- not covered
|
||||
= note: the matched value is of type `&SlashCommand`
|
||||
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
||||
|
|
||||
4200 ~ | SlashCommand::AddDir { .. } => Err("unsupported resumed slash command".into()),
|
||||
4201 ~ &SlashCommand::Session { action: Some(_), target: None } => todo!(),
|
||||
|
|
||||
|
||||
For more information about this error, try `rustc --explain E0004`.
|
||||
error: could not compile `rusty-claude-cli` (bin "claw") due to 1 previous error
|
||||
== cli compact output ==
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
error[E0004]: non-exhaustive patterns: `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
--> crates/rusty-claude-cli/src/main.rs:3823:11
|
||||
|
|
||||
3823 | match command {
|
||||
| ^^^^^^^ pattern `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
|
|
||||
note: `SlashCommand` defined here
|
||||
--> crates/commands/src/lib.rs:1040:1
|
||||
|
|
||||
1040 | pub enum SlashCommand {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
1089 | Session {
|
||||
| ------- not covered
|
||||
= note: the matched value is of type `&SlashCommand`
|
||||
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
||||
|
|
||||
4200 ~ | SlashCommand::AddDir { .. } => Err("unsupported resumed slash command".into()),
|
||||
4201 ~ &SlashCommand::Session { action: Some(_), target: None } => todo!(),
|
||||
|
|
||||
|
||||
For more information about this error, try `rustc --explain E0004`.
|
||||
error: could not compile `rusty-claude-cli` (bin "claw") due to 1 previous error
|
||||
== workspace check ==
|
||||
Checking runtime v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/runtime)
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
Checking api v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/api)
|
||||
Checking commands v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/commands)
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Checking tools v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/tools)
|
||||
Checking mock-anthropic-service v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/mock-anthropic-service)
|
||||
Checking compat-harness v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/compat-harness)
|
||||
error[E0004]: non-exhaustive patterns: `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
--> crates/rusty-claude-cli/src/main.rs:3823:11
|
||||
|
|
||||
3823 | match command {
|
||||
| ^^^^^^^ pattern `&SlashCommand::Session { action: Some(_), target: None }` not covered
|
||||
|
|
||||
note: `SlashCommand` defined here
|
||||
--> crates/commands/src/lib.rs:1040:1
|
||||
|
|
||||
1040 | pub enum SlashCommand {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
1089 | Session {
|
||||
| ------- not covered
|
||||
= note: the matched value is of type `&SlashCommand`
|
||||
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
||||
|
|
||||
4200 ~ | SlashCommand::AddDir { .. } => Err("unsupported resumed slash command".into()),
|
||||
4201 ~ &SlashCommand::Session { action: Some(_), target: None } => todo!(),
|
||||
|
|
||||
|
||||
For more information about this error, try `rustc --explain E0004`.
|
||||
error: could not compile `rusty-claude-cli` (bin "claw") due to 1 previous error
|
||||
== diff check ==
|
||||
G010 final leader verification completed 2026-05-15T02:18:11Z
|
||||
321
.omx/ultragoal/g010-leader-verify.log
Normal file
321
.omx/ultragoal/g010-leader-verify.log
Normal file
@@ -0,0 +1,321 @@
|
||||
== fmt ==
|
||||
== runtime session_control ==
|
||||
Compiling runtime v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/runtime)
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 10.29s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/runtime-0e7d3d46ae40aa07)
|
||||
|
||||
running 15 tests
|
||||
test session_control::tests::latest_session_prefers_semantic_updated_at_over_file_mtime ... ok
|
||||
test session_control::tests::session_store_from_cwd_canonicalizes_equivalent_paths ... ok
|
||||
test session_control::tests::session_store_create_and_load_round_trip ... ok
|
||||
test session_control::tests::session_exists_and_delete_are_scoped_to_workspace_store ... ok
|
||||
test session_control::tests::forks_session_into_managed_storage_with_lineage ... ok
|
||||
test session_control::tests::workspace_fingerprint_is_deterministic_and_differs_per_path ... ok
|
||||
test session_control::tests::session_store_from_cwd_isolates_sessions_by_workspace ... ok
|
||||
test session_control::tests::creates_and_lists_managed_sessions ... ok
|
||||
test session_control::tests::session_store_fork_stays_in_same_namespace ... ok
|
||||
test session_control::tests::session_store_from_data_dir_namespaces_by_workspace ... ok
|
||||
test session_control::tests::session_store_latest_and_resolve_reference ... ok
|
||||
test session_control::tests::session_store_loads_safe_legacy_session_from_same_workspace ... ok
|
||||
test session_control::tests::session_store_loads_unbound_legacy_session_from_same_workspace ... ok
|
||||
test session_control::tests::session_store_rejects_legacy_session_from_other_workspace ... ok
|
||||
test session_control::tests::resolves_latest_alias_and_loads_session_from_workspace_root ... ok
|
||||
|
||||
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 542 filtered out; finished in 0.02s
|
||||
|
||||
Running tests/g004_conformance.rs (rust/target/debug/deps/g004_conformance-90f36d1f871b6313)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
|
||||
|
||||
Running tests/integration_tests.rs (rust/target/debug/deps/integration_tests-526d4f853fc590de)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 12 filtered out; finished in 0.00s
|
||||
|
||||
== runtime session jsonl/bloat ==
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.18s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/runtime-0e7d3d46ae40aa07)
|
||||
|
||||
running 8 tests
|
||||
test session::tests::rejects_jsonl_record_with_unknown_type ... ok
|
||||
test session::tests::rejects_jsonl_message_record_without_message_payload ... ok
|
||||
test session::tests::rejects_jsonl_record_without_type ... ok
|
||||
test session::tests::persists_assistant_thinking_block_round_trip_through_jsonl ... ok
|
||||
test session::tests::persists_and_restores_session_jsonl ... ok
|
||||
test conversation::tests::persists_conversation_turn_messages_to_jsonl_session ... ok
|
||||
test session::tests::appends_messages_to_persisted_jsonl_session ... ok
|
||||
test session::tests::jsonl_persistence_redacts_and_truncates_oversized_payload_fields ... ok
|
||||
|
||||
test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 549 filtered out; finished in 0.04s
|
||||
|
||||
Running tests/g004_conformance.rs (rust/target/debug/deps/g004_conformance-90f36d1f871b6313)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
|
||||
|
||||
Running tests/integration_tests.rs (rust/target/debug/deps/integration_tests-526d4f853fc590de)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 12 filtered out; finished in 0.00s
|
||||
|
||||
== runtime compact ==
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.12s
|
||||
Running unittests src/lib.rs (rust/target/debug/deps/runtime-0e7d3d46ae40aa07)
|
||||
|
||||
running 17 tests
|
||||
test compact::tests::formats_compact_summary_like_upstream ... ok
|
||||
test compact::tests::ignores_existing_compacted_summary_when_deciding_to_recompact ... ok
|
||||
test compact::tests::compaction_does_not_split_tool_use_tool_result_pair ... ok
|
||||
test compact::tests::leaves_small_sessions_unchanged ... ok
|
||||
test compact::tests::infers_pending_work_from_recent_messages ... ok
|
||||
test compact::tests::truncates_long_blocks_in_summary ... ok
|
||||
test conversation::tests::auto_compaction_threshold_defaults_and_parses_values ... ok
|
||||
test compact::tests::extracts_key_files_from_message_content ... ok
|
||||
test compact::tests::compacts_older_messages_into_a_system_summary ... ok
|
||||
test conversation::tests::compaction_health_probe_blocks_turn_when_tool_executor_is_broken ... ok
|
||||
test conversation::tests::skips_auto_compaction_below_threshold ... ok
|
||||
test conversation::tests::auto_compacts_when_cumulative_input_threshold_is_crossed ... ok
|
||||
test conversation::tests::compaction_health_probe_skips_empty_compacted_session ... ok
|
||||
test conversation::tests::compacts_session_after_turns ... ok
|
||||
test prompt::tests::displays_context_paths_compactly ... ok
|
||||
test compact::tests::keeps_previous_compacted_context_when_compacting_again ... ok
|
||||
test session::tests::persists_compaction_metadata ... ok
|
||||
|
||||
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 540 filtered out; finished in 0.01s
|
||||
|
||||
Running tests/g004_conformance.rs (rust/target/debug/deps/g004_conformance-90f36d1f871b6313)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
|
||||
|
||||
Running tests/integration_tests.rs (rust/target/debug/deps/integration_tests-526d4f853fc590de)
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 12 filtered out; finished in 0.00s
|
||||
|
||||
== cli resume_slash_commands ==
|
||||
Compiling runtime v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/runtime)
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
Compiling api v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/api)
|
||||
Compiling commands v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/commands)
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Compiling tools v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/tools)
|
||||
Compiling mock-anthropic-service v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/mock-anthropic-service)
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling compat-harness v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/compat-harness)
|
||||
error[E0004]: non-exhaustive patterns: `&SlashCommand::Session { action: None, .. }` not covered
|
||||
--> crates/rusty-claude-cli/src/main.rs:3794:11
|
||||
|
|
||||
3794 | match command {
|
||||
| ^^^^^^^ pattern `&SlashCommand::Session { action: None, .. }` not covered
|
||||
|
|
||||
note: `SlashCommand` defined here
|
||||
--> crates/commands/src/lib.rs:1040:1
|
||||
|
|
||||
1040 | pub enum SlashCommand {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
1089 | Session {
|
||||
| ------- not covered
|
||||
= note: the matched value is of type `&SlashCommand`
|
||||
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
||||
|
|
||||
4197 ~ | SlashCommand::AddDir { .. } => Err("unsupported resumed slash command".into()),
|
||||
4198 ~ &SlashCommand::Session { action: None, .. } => todo!(),
|
||||
|
|
||||
|
||||
For more information about this error, try `rustc --explain E0004`.
|
||||
error: could not compile `rusty-claude-cli` (bin "claw") due to 1 previous error
|
||||
== cli compact_output ==
|
||||
warning: enum `ProviderWireProtocol` is never used
|
||||
--> crates/api/src/providers/mod.rs:54:10
|
||||
|
|
||||
54 | pub enum ProviderWireProtocol {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: enum `ProviderFeatureSupport` is never used
|
||||
--> crates/api/src/providers/mod.rs:61:10
|
||||
|
|
||||
61 | pub enum ProviderFeatureSupport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderCapabilityReport` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:68:12
|
||||
|
|
||||
68 | pub struct ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: enum `ProviderDiagnosticSeverity` is never used
|
||||
--> crates/api/src/providers/mod.rs:88:10
|
||||
|
|
||||
88 | pub enum ProviderDiagnosticSeverity {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `ProviderDiagnostic` is never constructed
|
||||
--> crates/api/src/providers/mod.rs:94:12
|
||||
|
|
||||
94 | pub struct ProviderDiagnostic {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_capabilities_for_model` is never used
|
||||
--> crates/api/src/providers/mod.rs:384:8
|
||||
|
|
||||
384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_diagnostics_for_request` is never used
|
||||
--> crates/api/src/providers/mod.rs:452:8
|
||||
|
|
||||
452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `metadata_for_provider_kind` is never used
|
||||
--> crates/api/src/providers/mod.rs:517:4
|
||||
|
|
||||
517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `provider_label` is never used
|
||||
--> crates/api/src/providers/mod.rs:541:10
|
||||
|
|
||||
541 | const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `has_openai_tuning_parameters` is never used
|
||||
--> crates/api/src/providers/mod.rs:550:4
|
||||
|
|
||||
550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `declares_tool` is never used
|
||||
--> crates/api/src/providers/mod.rs:558:4
|
||||
|
|
||||
558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `web_passthrough_diagnostic` is never used
|
||||
--> crates/api/src/providers/mod.rs:567:4
|
||||
|
|
||||
567 | fn web_passthrough_diagnostic(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `strip_routing_prefix` is never used
|
||||
--> crates/api/src/providers/openai_compat.rs:901:4
|
||||
|
|
||||
901 | fn strip_routing_prefix(model: &str) -> &str {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: `api` (lib) generated 13 warnings
|
||||
Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)
|
||||
error[E0004]: non-exhaustive patterns: `&SlashCommand::Session { action: None, .. }` not covered
|
||||
--> crates/rusty-claude-cli/src/main.rs:3794:11
|
||||
|
|
||||
3794 | match command {
|
||||
| ^^^^^^^ pattern `&SlashCommand::Session { action: None, .. }` not covered
|
||||
|
|
||||
note: `SlashCommand` defined here
|
||||
--> crates/commands/src/lib.rs:1040:1
|
||||
|
|
||||
1040 | pub enum SlashCommand {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
1089 | Session {
|
||||
| ------- not covered
|
||||
= note: the matched value is of type `&SlashCommand`
|
||||
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
||||
|
|
||||
4197 ~ | SlashCommand::AddDir { .. } => Err("unsupported resumed slash command".into()),
|
||||
4198 ~ &SlashCommand::Session { action: None, .. } => todo!(),
|
||||
|
|
||||
|
||||
For more information about this error, try `rustc --explain E0004`.
|
||||
error: could not compile `rusty-claude-cli` (bin "claw") due to 1 previous error
|
||||
== diff check ==
|
||||
@@ -0,0 +1 @@
|
||||
{"goal":{"threadId":"019e2560-a38d-7282-bb33-58c944cdcbc9","objective":"Complete the approved Claw Code 2.0 ultragoal delivery: implement all classified ROADMAP.md backlog work through execution-sized stream goals G001-G012, using .omx/ultragoal/ledger.jsonl as the durable audit trail and .omx/plans/claw-code-2-0-adaptive-plan.md as the source plan.","status":"active","tokensUsed":4536320,"timeUsedSeconds":13975,"createdAt":1778745278,"updatedAt":1778810208},"remainingTokens":null,"completionBudgetReport":null}
|
||||
@@ -0,0 +1 @@
|
||||
{"goal":{"threadId":"019e2560-a38d-7282-bb33-58c944cdcbc9","objective":"Complete the approved Claw Code 2.0 ultragoal delivery: implement all classified ROADMAP.md backlog work through execution-sized stream goals G001-G012, using .omx/ultragoal/ledger.jsonl as the durable audit trail and .omx/plans/claw-code-2-0-adaptive-plan.md as the source plan.","status":"active","tokensUsed":4747486,"timeUsedSeconds":14669,"createdAt":1778745278,"updatedAt":1778810902},"remainingTokens":null,"completionBudgetReport":null}
|
||||
@@ -0,0 +1 @@
|
||||
{"goal":{"threadId":"019e2560-a38d-7282-bb33-58c944cdcbc9","objective":"Complete the approved Claw Code 2.0 ultragoal delivery: implement all classified ROADMAP.md backlog work through execution-sized stream goals G001-G012, using .omx/ultragoal/ledger.jsonl as the durable audit trail and .omx/plans/claw-code-2-0-adaptive-plan.md as the source plan.","status":"active","tokensUsed":4771357,"timeUsedSeconds":14733,"createdAt":1778745278,"updatedAt":1778810966},"remainingTokens":null,"completionBudgetReport":null}
|
||||
1
.omx/ultragoal/get-goal-G010-session-hygiene.active.json
Normal file
1
.omx/ultragoal/get-goal-G010-session-hygiene.active.json
Normal file
@@ -0,0 +1 @@
|
||||
{"goal":{"threadId":"019e2560-a38d-7282-bb33-58c944cdcbc9","objective":"Complete the approved Claw Code 2.0 ultragoal delivery: implement all classified ROADMAP.md backlog work through execution-sized stream goals G001-G012, using .omx/ultragoal/ledger.jsonl as the durable audit trail and .omx/plans/claw-code-2-0-adaptive-plan.md as the source plan.","status":"active","tokensUsed":4726793,"timeUsedSeconds":14653,"createdAt":1778745278,"updatedAt":1778810885},"remainingTokens":null,"completionBudgetReport":null}
|
||||
@@ -0,0 +1 @@
|
||||
{"goal":{"threadId":"019e2560-a38d-7282-bb33-58c944cdcbc9","objective":"Complete the approved Claw Code 2.0 ultragoal delivery: implement all classified ROADMAP.md backlog work through execution-sized stream goals G001-G012, using .omx/ultragoal/ledger.jsonl as the durable audit trail and .omx/plans/claw-code-2-0-adaptive-plan.md as the source plan.","status":"active","tokensUsed":5024990,"timeUsedSeconds":15387,"createdAt":1778745278,"updatedAt":1778811620},"remainingTokens":null,"completionBudgetReport":null}
|
||||
154
.omx/ultragoal/goals.json
Normal file
154
.omx/ultragoal/goals.json
Normal file
@@ -0,0 +1,154 @@
|
||||
{
|
||||
"version": 1,
|
||||
"createdAt": "2026-05-14T07:53:46.061Z",
|
||||
"updatedAt": "2026-05-15T04:38:54.887Z",
|
||||
"briefPath": ".omx/ultragoal/brief.md",
|
||||
"goalsPath": ".omx/ultragoal/goals.json",
|
||||
"ledgerPath": ".omx/ultragoal/ledger.jsonl",
|
||||
"codexGoalMode": "aggregate",
|
||||
"goals": [
|
||||
{
|
||||
"id": "G001-stream0-board",
|
||||
"title": "Stream 0: Generate canonical CC2 board",
|
||||
"objective": "Generate the canonical Claw Code 2.0 board from frozen ROADMAP.md, latest issue snapshot, parity evidence, and approved plan. Classify every actionable roadmap item and context heading with source_anchor, source_type, release_bucket, lifecycle status, dependencies, verification_required, and deferral rationale. Emit machine JSON plus human markdown.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-14T08:14:23.206Z",
|
||||
"startedAt": "2026-05-14T07:54:26.032Z",
|
||||
"completedAt": "2026-05-14T08:14:23.206Z",
|
||||
"evidence": "G001-stream0-board complete via team ultragoal-g001-stream-e61d2271: team status phase=team-verify, tasks 5/5 completed; worker-2 produced issue/parity intake, worker-3 produced board Markdown/rendering, worker-4 recorded validation evidence, worker-1 completed initial board artifacts. Leader reconciliation commit 45b43b5 aligned scripts/generate_cc2_board.py, scripts/validate_cc2_board.py, scripts/cc2_board.py, .omx/cc2/render_board_md.py. Evidence artifacts: .omx/cc2/board.json, .omx/cc2/board.md, .omx/cc2/issue-parity-intake.json, .omx/cc2/issue-parity-intake.md; .omx/ultragoal/goals.json and .omx/ultragoal/ledger.jsonl remain leader-owned. Verification passed: python3 scripts/generate_cc2_board.py; python3 scripts/validate_cc2_board.py; python3 scripts/cc2_board.py validate; python3 .omx/cc2/validate_issue_parity_intake.py; python3 .omx/cc2/render_board_md.py .omx/cc2/board.json .omx/cc2/board.md --check; python3 -m py_compile scripts/generate_cc2_board.py scripts/validate_cc2_board.py scripts/cc2_board.py .omx/cc2/validate_issue_parity_intake.py .omx/cc2/render_board_md.py; cargo check --manifest-path rust/Cargo.toml --workspace."
|
||||
},
|
||||
{
|
||||
"id": "G002-alpha-security",
|
||||
"title": "Stream 6: Day-one security and permissions gate",
|
||||
"objective": "Implement/verify alpha-blocking security scope: file tools and shell enforce workspace/path scope across direct paths, symlinks, globbing, shell expansion, worktrees, and Windows path cases. Add regression fixtures for #3007 class behavior and permission-mode event/status visibility.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-14T08:34:04.243Z",
|
||||
"startedAt": "2026-05-14T08:14:46.422Z",
|
||||
"completedAt": "2026-05-14T08:34:04.243Z",
|
||||
"evidence": "G002-alpha-security team ultragoal-g002-alpha-e61d2271 reached phase=complete with 5/5 tasks completed and no worker .omx/ultragoal mutation. Integrated commits through 37b2b75 on main: workspace/path enforcement in rust/crates/runtime/src/file_ops.rs, rust/crates/runtime/src/lib.rs, rust/crates/tools/src/lib.rs, regressions in rust/crates/tools/tests/path_scope_enforcement.rs and rust/crates/rusty-claude-cli/tests/output_format_contract.rs, verification map docs/g002-security-verification-map.md. Fresh leader validation passed: git diff --check; cargo fmt --manifest-path rust/Cargo.toml --all -- --check; cargo test --manifest-path rust/Cargo.toml -p tools path_scope -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p tools --test path_scope_enforcement -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime workspace_ -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test output_format_contract -- --nocapture; python3 -m pytest tests/test_security_scope.py -q; cargo check --manifest-path rust/Cargo.toml --workspace. .omx/ultragoal artifacts retained as leader-owned durable audit trail; fresh get_goal JSON captured at .omx/ultragoal/get-goal-G002-alpha-security.json. Known unrelated non-gating gaps from worker verification: full cargo test --workspace has pre-existing session_lifecycle_prefers_running_process_over_idle_shell failure; clippy all-targets has pre-existing runtime lint warnings."
|
||||
},
|
||||
{
|
||||
"id": "G003-boot-session",
|
||||
"title": "Stream 1: Reliable worker boot/session control",
|
||||
"objective": "Implement/verify worker lifecycle, first prompt acceptance SLA, startup-no-evidence classifier, trust resolver/default trusted roots, structured session control API, and boot preflight/doctor JSON contracts.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-14T08:54:40.729Z",
|
||||
"startedAt": "2026-05-14T08:34:19.605Z",
|
||||
"completedAt": "2026-05-14T08:54:40.729Z",
|
||||
"evidence": "G003-boot-session team g003-boot-session-ult-e61d2271 reached phase=complete with 5/5 tasks completed and no worker .omx/ultragoal mutation. Implemented/verified Stream 1 reliable worker boot/session control: worker lifecycle/prompt SLA and path guardrails, default trusted roots merge via runtime config and WorkerCreate, startup-no-evidence evidence/classifier timestamp coverage, structured boot preflight/status/doctor JSON, and docs/g003-boot-session-verification-map.md. Integrated/pushed through origin/main aec291c. Final leader validation passed: git diff --check; cargo fmt --manifest-path rust/Cargo.toml --all -- --check; cargo test --manifest-path rust/Cargo.toml -p runtime trusted_roots -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime trust_resolver -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime startup -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime worker_boot -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p tools worker_create_merges_config_trusted_roots -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p tools path_scope -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli boot_preflight -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli branch_freshness -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli status_json_surfaces_session_lifecycle_for_clawhip -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test output_format_contract -- --nocapture; cargo check --manifest-path rust/Cargo.toml --workspace; python3 scripts/validate_cc2_board.py --board .omx/cc2/board.json; python3 .omx/cc2/validate_issue_parity_intake.py .omx/cc2/issue-parity-intake.json. Fresh get_goal JSON captured at .omx/ultragoal/get-goal-G003-boot-session.complete.json and .omx/ultragoal goals/ledger remain leader-owned audit artifacts. Known non-gating gaps from worker clippy attempts are pre-existing unrelated runtime clippy warnings and full workspace tests remain deferred to final gates."
|
||||
},
|
||||
{
|
||||
"id": "G004-events-reports",
|
||||
"title": "Stream 2: Event/report contract families",
|
||||
"objective": "Implement/verify canonical lane events, ordering/provenance/identity/dedupe/ownership, report schema/projection/redaction/capability negotiation, approval-token chain, and pinpoint closure batches with golden fixtures.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-14T09:15:44.223Z",
|
||||
"startedAt": "2026-05-14T08:54:55.093Z",
|
||||
"completedAt": "2026-05-14T09:15:44.223Z",
|
||||
"evidence": "G004-events-reports complete: team g004-events-reports-u-e61d2271 phase complete with 7/7 tasks completed; pushed main through 879962b; leader verification passed cargo fmt --manifest-path rust/Cargo.toml --all -- --check, cargo check --manifest-path rust/Cargo.toml -p runtime, cargo test --manifest-path rust/Cargo.toml -p runtime -- --nocapture (535 unit + g004_conformance 2 + integration 12 + doctests), python3 .github/scripts/check_doc_source_of_truth.py; evidence recorded against .omx/ultragoal/goals.json and .omx/ultragoal/ledger.jsonl"
|
||||
},
|
||||
{
|
||||
"id": "G005-branch-recovery",
|
||||
"title": "Stream 3: Branch/test awareness and recovery",
|
||||
"objective": "Implement/verify stale branch detection before broad tests, recovery recipes and ledger, green-ness contract, test provenance, hung-test classification, and recovery/status reporting.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-14T12:41:48.997Z",
|
||||
"startedAt": "2026-05-14T09:16:01.781Z",
|
||||
"completedAt": "2026-05-14T12:41:48.997Z",
|
||||
"evidence": "G005-branch-recovery complete and pushed at 7426ede; team g005-branch-recovery-e61d2271 has 5/5 tasks completed; leader verification passed for branch freshness before broad tests, recovery ledger/status reporting, green-ness contract/test provenance, stale-base doctor/status consistency, hung-test classification, and docs/g005-branch-recovery-verification-map.md. Evidence recorded against .omx/ultragoal/goals.json and .omx/ultragoal/ledger.jsonl."
|
||||
},
|
||||
{
|
||||
"id": "G006-task-policy-board",
|
||||
"title": "Stream 4: Task packets, policy engine, lane board",
|
||||
"objective": "Implement/verify typed task packet schema, executable policy engine, active lane board/dashboard, running-state liveness heartbeat, and task/lane status JSON.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-15T00:42:05.094Z",
|
||||
"startedAt": "2026-05-14T12:41:57.815Z",
|
||||
"completedAt": "2026-05-15T00:42:05.094Z",
|
||||
"evidence": "G006-task-policy-board complete in pushed origin/main commit 65a144c; team g006-task-policy-boar-e61d2271 terminal with 5 completed/0 failed after leader reconciliation; verification map docs/g006-task-policy-board-verification-map.md plus quality gate JSON record cargo fmt/check/tests/diff/push; .omx/ultragoal/goals.json and .omx/ultragoal/ledger.jsonl preserved; workers did not mutate .omx/ultragoal."
|
||||
},
|
||||
{
|
||||
"id": "G007-plugin-mcp",
|
||||
"title": "Stream 5: Plugin/MCP lifecycle maturity",
|
||||
"objective": "Implement/verify plugin/MCP lifecycle states, healthy/degraded/failed startup, required vs optional behavior, malformed config consistency across status/doctor/mcp/plugins, and mock MCP/plugin tests.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-15T01:16:43.414Z",
|
||||
"startedAt": "2026-05-15T00:42:16.309Z",
|
||||
"completedAt": "2026-05-15T01:16:43.414Z",
|
||||
"evidence": "G007-plugin-mcp complete: team g007-plugin-mcp-ultra-e61d2271 phase complete with 13/13 tasks completed, verification passed, pushed head 2202410, and durable ultragoal artifacts updated in .omx/ultragoal/goals.json and .omx/ultragoal/ledger.jsonl."
|
||||
},
|
||||
{
|
||||
"id": "G008-provider-compat",
|
||||
"title": "Stream 7: Provider/model compatibility",
|
||||
"objective": "Implement/verify OpenAI-compatible slash-containing model IDs, provider prefix routing over env sniffing, DeepSeek/reasoning diagnostics, web search/fetch behavior, proxy/custom parameter passthrough, token/cost accounting, and provider diagnostics.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-15T01:38:22.717Z",
|
||||
"startedAt": "2026-05-15T01:17:53.783Z",
|
||||
"completedAt": "2026-05-15T01:38:22.717Z",
|
||||
"evidence": "G008-provider-compat complete: team g008-provider-compat-e61d2271 phase complete with 5/5 tasks terminal; provider/model compatibility implemented and verified; pushed origin/main 2cac66c..8c9a05e; evidence recorded in .omx/ultragoal/goals.json and .omx/ultragoal/ledger.jsonl plus quality gate .omx/ultragoal/quality-gate-G008-provider-compat.json."
|
||||
},
|
||||
{
|
||||
"id": "G009-windows-docs-release",
|
||||
"title": "Stream 8: Windows/install/docs/license readiness",
|
||||
"objective": "Implement/verify PowerShell-first docs, safe provider switching examples, Windows smoke CI, release artifact quickstart, license/contribution/security/support policies, and command/link validation.",
|
||||
"status": "complete",
|
||||
"attempt": 0,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-15T01:57:41.565Z",
|
||||
"completedAt": "2026-05-15T01:57:41.565Z",
|
||||
"evidence": "G009-windows-docs-release complete at commit 5294648 with team g009-windows-docs-rel-e61d2271 phase complete, 5/5 tasks completed; evidence in .omx/ultragoal/quality-gate-G009-windows-docs-release.json, .omx/ultragoal/get-goal-G009-windows-docs-release.complete.json, .omx/ultragoal/goals.json, and .omx/ultragoal/ledger.jsonl."
|
||||
},
|
||||
{
|
||||
"id": "G010-session-hygiene",
|
||||
"title": "Stream 9: Session hygiene/local state/recovery UX",
|
||||
"objective": "Implement/verify session file hygiene, .gitignore state paths, per-worktree session isolation, list/delete/exists/compact/resume, compact/provider-context recovery, JSONL payload bloat safeguards, interrupt recovery, and clone disambiguation metadata.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-15T02:20:46.558Z",
|
||||
"startedAt": "2026-05-15T01:59:22.219Z",
|
||||
"completedAt": "2026-05-15T02:20:46.558Z",
|
||||
"evidence": "G010-session-hygiene complete: team g010-session-hygiene-e61d2271 phase complete with 7/7 tasks completed; final verification passed in .omx/ultragoal/g010-final-quality-gate-rerun.log; durable state recorded in .omx/ultragoal/goals.json and .omx/ultragoal/ledger.jsonl."
|
||||
},
|
||||
{
|
||||
"id": "G011-ecosystem-ops-ux",
|
||||
"title": "Streams 10–12: Ecosystem, issue ops, and UX laterals",
|
||||
"objective": "Implement/verify gated ACP/Zed/JSON-RPC serve plan/status, anti-slop issue/PR triage, issue templates, navigation/file-context docs, TUI/rendering/copy/paste/clickable path improvements, and defer desktop/marketplace features until contracts are stable.",
|
||||
"status": "complete",
|
||||
"attempt": 1,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-15T02:55:26.988Z",
|
||||
"startedAt": "2026-05-15T02:21:31.360Z",
|
||||
"completedAt": "2026-05-15T02:55:26.988Z",
|
||||
"evidence": "G011-ecosystem-ops-ux complete: team g011-ecosystem-ops-ux-e61d2271 phase=complete with 7/7 tasks completed; final pushed HEAD 1ac8ce8; verification evidence in .omx/ultragoal/g011-final-quality-gate.log and .omx/ultragoal/quality-gate-G011-ecosystem-ops-ux.json; ultragoal artifacts tracked in .omx/ultragoal/goals.json and .omx/ultragoal/ledger.jsonl."
|
||||
},
|
||||
{
|
||||
"id": "G012-final-gate",
|
||||
"title": "Final release gate: Verify Claw Code 2.0 delivery",
|
||||
"objective": "Run final cross-stream quality gate: roadmap board has no unmapped actionable items, fmt/clippy/tests and focused contract suites pass, ai-slop-cleaner on changed files passes/no-ops, code-review approves, and final alpha/beta/GA readiness report is written. Final completion is blocked until docs/pr-issue-resolution-gate.md has fresh evidence showing every open PR and issue was triaged, with correct PRs merged and resolvable correct issues fixed or closed.",
|
||||
"status": "complete",
|
||||
"attempt": 0,
|
||||
"createdAt": "2026-05-14T07:54:21.409575Z",
|
||||
"updatedAt": "2026-05-15T04:38:54.887Z",
|
||||
"evidence": "G012-final-gate complete: team g012-final-gate-ultra-e61d2271 8/8 tasks complete; final gate log /tmp/g012-final-quality-gate-pass4.log; commit 04c2abb pushed; docs/pr-triage-g012-final-gate.json docs/pr-issue-resolution-gate.md docs/g012-final-release-readiness-report.md; .omx/ultragoal/goals.json and ledger.jsonl updated; aiSlopCleaner and codeReview evidence included in quality gate JSON.",
|
||||
"completedAt": "2026-05-15T04:38:54.887Z"
|
||||
}
|
||||
],
|
||||
"codexObjective": "Complete the approved Claw Code 2.0 ultragoal delivery: implement all classified ROADMAP.md backlog work through execution-sized stream goals G001-G012, using .omx/ultragoal/ledger.jsonl as the durable audit trail and .omx/plans/claw-code-2-0-adaptive-plan.md as the source plan."
|
||||
}
|
||||
23
.omx/ultragoal/ledger.jsonl
Normal file
23
.omx/ultragoal/ledger.jsonl
Normal file
File diff suppressed because one or more lines are too long
42
.omx/ultragoal/quality-gate-G009-windows-docs-release.json
Normal file
42
.omx/ultragoal/quality-gate-G009-windows-docs-release.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"goal_id": "G009-windows-docs-release",
|
||||
"timestamp_utc": "2026-05-15T01:57:16Z",
|
||||
"commit": "a3af0133e0cf8d529465950ada88623e3cf3b3f2",
|
||||
"team": "g009-windows-docs-rel-e61d2271",
|
||||
"team_phase": "complete",
|
||||
"tasks": "5/5 completed",
|
||||
"verification": {
|
||||
"release_readiness": "passed",
|
||||
"doc_source_of_truth": "passed",
|
||||
"cargo_fmt": "passed",
|
||||
"targeted_windows_no_credentials_smoke_test": "passed",
|
||||
"cargo_check_workspace": "passed with existing api dead_code warnings",
|
||||
"git_diff_check": "passed",
|
||||
"coverage_check": "passed"
|
||||
},
|
||||
"known_gaps": [
|
||||
{
|
||||
"scope": "actual GitHub windows-latest execution",
|
||||
"status": "not run locally"
|
||||
},
|
||||
{
|
||||
"scope": "full cargo test --workspace",
|
||||
"status": "known pre-existing unrelated CLI failures reported by workers; targeted changed-surface tests pass"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
".github/workflows/rust-ci.yml",
|
||||
".github/workflows/release.yml",
|
||||
"docs/windows-install-release.md",
|
||||
"docs/g009-windows-docs-release-verification-map.md",
|
||||
"LICENSE",
|
||||
"CONTRIBUTING.md",
|
||||
"SECURITY.md",
|
||||
"SUPPORT.md",
|
||||
"CODE_OF_CONDUCT.md",
|
||||
".github/scripts/check_release_readiness.py",
|
||||
"/tmp/g009-final-verify.log"
|
||||
],
|
||||
"git_status": "## main...origin/main [ahead 13]",
|
||||
"log_tail": " | ^^^^^^^^^^^^^^^^^^^^\n |\n = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default\n\nwarning: enum `ProviderFeatureSupport` is never used\n --> crates/api/src/providers/mod.rs:61:10\n |\n61 | pub enum ProviderFeatureSupport {\n | ^^^^^^^^^^^^^^^^^^^^^^\n\nwarning: struct `ProviderCapabilityReport` is never constructed\n --> crates/api/src/providers/mod.rs:68:12\n |\n68 | pub struct ProviderCapabilityReport {\n | ^^^^^^^^^^^^^^^^^^^^^^^^\n\nwarning: enum `ProviderDiagnosticSeverity` is never used\n --> crates/api/src/providers/mod.rs:88:10\n |\n88 | pub enum ProviderDiagnosticSeverity {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nwarning: struct `ProviderDiagnostic` is never constructed\n --> crates/api/src/providers/mod.rs:94:12\n |\n94 | pub struct ProviderDiagnostic {\n | ^^^^^^^^^^^^^^^^^^\n\nwarning: function `provider_capabilities_for_model` is never used\n --> crates/api/src/providers/mod.rs:384:8\n |\n384 | pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nwarning: function `provider_diagnostics_for_request` is never used\n --> crates/api/src/providers/mod.rs:452:8\n |\n452 | pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nwarning: function `metadata_for_provider_kind` is never used\n --> crates/api/src/providers/mod.rs:517:4\n |\n517 | fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nwarning: function `provider_label` is never used\n --> crates/api/src/providers/mod.rs:541:10\n |\n541 | const fn provider_label(provider: ProviderKind) -> &'static str {\n | ^^^^^^^^^^^^^^\n\nwarning: function `has_openai_tuning_parameters` is never used\n --> crates/api/src/providers/mod.rs:550:4\n |\n550 | fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nwarning: function `declares_tool` is never used\n --> crates/api/src/providers/mod.rs:558:4\n |\n558 | fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {\n | ^^^^^^^^^^^^^\n\nwarning: function `web_passthrough_diagnostic` is never used\n --> crates/api/src/providers/mod.rs:567:4\n |\n567 | fn web_passthrough_diagnostic(\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nwarning: function `strip_routing_prefix` is never used\n --> crates/api/src/providers/openai_compat.rs:901:4\n |\n901 | fn strip_routing_prefix(model: &str) -> &str {\n | ^^^^^^^^^^^^^^^^^^^^\n\nwarning: `api` (lib) generated 13 warnings\n Compiling rusty-claude-cli v0.1.0 (/Users/bellman/Documents/Workspace/claw-code/rust/crates/rusty-claude-cli)\n Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.35s\nG009 coverage check passed"
|
||||
}
|
||||
32
.omx/ultragoal/quality-gate-G010-session-hygiene.json
Normal file
32
.omx/ultragoal/quality-gate-G010-session-hygiene.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"goal_id": "G010-session-hygiene",
|
||||
"status": "passed",
|
||||
"team": "g010-session-hygiene-e61d2271",
|
||||
"team_phase": "complete",
|
||||
"tasks": {"completed": 7, "failed": 0, "blocked": 0, "pending": 0, "in_progress": 0},
|
||||
"evidence": [
|
||||
".omx/ultragoal/g010-final-quality-gate-rerun.log",
|
||||
"docs/g010-clone-disambiguation-metadata.md",
|
||||
"docs/g010-session-hygiene-verification-map.md",
|
||||
".omx/ultragoal/goals.json",
|
||||
".omx/ultragoal/ledger.jsonl"
|
||||
],
|
||||
"verification_passed": [
|
||||
"cargo fmt --manifest-path rust/Cargo.toml --all -- --check",
|
||||
"cargo test --manifest-path rust/Cargo.toml -p runtime session_control -- --nocapture",
|
||||
"cargo test --manifest-path rust/Cargo.toml -p runtime jsonl_persistence_redacts_and_truncates_oversized_payload_fields -- --nocapture",
|
||||
"cargo test --manifest-path rust/Cargo.toml -p runtime compact -- --nocapture",
|
||||
"cargo test --manifest-path rust/Cargo.toml -p commands parses_supported_slash_commands -- --nocapture",
|
||||
"cargo test --manifest-path rust/Cargo.toml -p commands compacts_sessions_via_slash_command -- --nocapture",
|
||||
"cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --bin claw session_exists_resume_command_reports_json_contract -- --nocapture",
|
||||
"cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --bin claw resumed_session_exists_and_delete_have_json_contracts -- --nocapture",
|
||||
"cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test resume_slash_commands -- --nocapture",
|
||||
"cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test compact_output -- --nocapture",
|
||||
"cargo check --manifest-path rust/Cargo.toml --workspace",
|
||||
"git diff --check"
|
||||
],
|
||||
"known_gaps": [
|
||||
"full cargo test --workspace not run for G010",
|
||||
"clippy -D warnings remains blocked by pre-existing unrelated lint debt noted in task 5/task 7 results"
|
||||
]
|
||||
}
|
||||
8
.port_sessions/b035f648d5b549aa836ea01f6727ec62.json
Normal file
8
.port_sessions/b035f648d5b549aa836ea01f6727ec62.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"session_id": "b035f648d5b549aa836ea01f6727ec62",
|
||||
"messages": [
|
||||
"review MCP tool"
|
||||
],
|
||||
"input_tokens": 3,
|
||||
"output_tokens": 13
|
||||
}
|
||||
9
.port_sessions/b234acb1eb8c486e80544ddc7e13e6d8.json
Normal file
9
.port_sessions/b234acb1eb8c486e80544ddc7e13e6d8.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"session_id": "b234acb1eb8c486e80544ddc7e13e6d8",
|
||||
"messages": [
|
||||
"review MCP tool",
|
||||
"review MCP tool"
|
||||
],
|
||||
"input_tokens": 6,
|
||||
"output_tokens": 32
|
||||
}
|
||||
9
.port_sessions/b67e062748f04e10ac5770df9285e4bd.json
Normal file
9
.port_sessions/b67e062748f04e10ac5770df9285e4bd.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"session_id": "b67e062748f04e10ac5770df9285e4bd",
|
||||
"messages": [
|
||||
"review MCP tool",
|
||||
"review MCP tool"
|
||||
],
|
||||
"input_tokens": 6,
|
||||
"output_tokens": 32
|
||||
}
|
||||
9
.port_sessions/bb88fd20433840a8b19237e3f306c6e3.json
Normal file
9
.port_sessions/bb88fd20433840a8b19237e3f306c6e3.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"session_id": "bb88fd20433840a8b19237e3f306c6e3",
|
||||
"messages": [
|
||||
"review MCP tool",
|
||||
"review MCP tool"
|
||||
],
|
||||
"input_tokens": 6,
|
||||
"output_tokens": 32
|
||||
}
|
||||
69
CHANGELOG.md
69
CHANGELOG.md
@@ -1,69 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to claw-code are documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (currently pre-1.0).
|
||||
|
||||
## [Unreleased] — 2026-04-26 to 2026-04-27 (extended dogfood audit cycles, through #433)
|
||||
|
||||
Branch: `feat/jobdori-168c-emission-routing`
|
||||
|
||||
### Added — Documentation
|
||||
|
||||
- **docs/CONFIGURATION.md** — Configuration reference: env vars, settings.json, provider selection (cycle #429)
|
||||
- **CODE_OF_CONDUCT.md** — Contributor Covenant v2.1 (cycle #432)
|
||||
- **.github/PULL_REQUEST_TEMPLATE.md** — Standardized PR description template (cycle #430)
|
||||
- **.github/ISSUE_TEMPLATE/bug_report.md** — Standard bug report template (cycle #431)
|
||||
- **docs/ARCHITECTURE.md** — High-level architecture overview: 9 Rust crates, request flow, subsystem map with pinpoint links (cycle #426)
|
||||
- **CHANGELOG.md** — This file (cycle #424)
|
||||
- **docs/PINPOINT_FILING_GUIDE.md** — Step-by-step pinpoint filing workflow with #290 worked example (cycle #422)
|
||||
- **docs/SUPPORTED_PROVIDERS.md** — Documents 4 providers (Anthropic, xAI, DashScope/Qwen/Kimi, OpenAI/compat) from MODEL_REGISTRY (cycle #420)
|
||||
- **TROUBLESHOOTING.md** — Operational guidance for 5 critical failure modes (#286, #287, #289, #290, #291) (cycles #418, #423)
|
||||
- **ROADMAP.md Pinpoint Cluster Index** — Navigation aid for 8 named clusters (cycle #421)
|
||||
- **ROADMAP.md Extended Dogfood Audit Summary** — Cycles #388-#415 overview (cycle #416)
|
||||
- **README.md Contributing section** — Unified navigation to SECURITY/ROADMAP/CONTRIBUTING/ISSUE_TEMPLATE (cycle #415)
|
||||
- **SECURITY.md** — Responsible-disclosure stub with reporting via GitHub Security Advisories (cycle #414)
|
||||
- **CONTRIBUTING.md** — Codifies pinpoint filing format, build commands, branch naming (cycle #411)
|
||||
- **.github/ISSUE_TEMPLATE/pinpoint.md** — Discoverable canonical issue template (cycle #412)
|
||||
- **LICENSE** — Root MIT license file (cycle #410)
|
||||
|
||||
### Fixed — Code
|
||||
|
||||
- **#256** — Anthropic tool-result request ordering (pre-audit)
|
||||
- **#122b** — `claw doctor` broad-path warning
|
||||
- **#160** — Reserved-semantic-verb slash-command guidance
|
||||
|
||||
### Filed — Pinpoints (ROADMAP.md)
|
||||
|
||||
47 pinpoints filed (#241-#292) during extended dogfood audit. New entries:
|
||||
- **#292** — Extreme sustained upstream degradation lacks user-facing escalation guidance (cycle #425). Evidence: gaebal-gajae 17+ `500 empty_stream` failures across 5+ hours
|
||||
|
||||
Clusters identified:
|
||||
- **Auto-compaction (4-deep):** #283, #287 (CRITICAL), #288, #289
|
||||
- **Transport / Provider Resilience:** #266, #285, #290, #291
|
||||
- **Provider Infrastructure:** #245, #246, #285
|
||||
- **Tool Lifecycle / Hooks:** #254, #268, #274, #280, #286
|
||||
- **CLI Dispatch:** #262, #267, #272, #282, #283
|
||||
- **Persistence / Migration:** #278, #279
|
||||
- **Provenance Consolidation:** #259, #271, #273, #275
|
||||
- **Slash-command Contract:** #284
|
||||
|
||||
See [ROADMAP.md](./ROADMAP.md#pinpoint-cluster-index) for full list.
|
||||
|
||||
### Live evidence integrated
|
||||
|
||||
- @Sigrid Jin: license verification, ultraplan functionality, provider-config source-of-truth → pinpoints #284, #285
|
||||
- gaebal-gajae sustained `500 empty_stream` (11+ incidents in 3hr+) → pinpoints #290, #291
|
||||
|
||||
---
|
||||
|
||||
## Process
|
||||
|
||||
This release demonstrates the pinpoint-driven workflow:
|
||||
1. **Identify friction** during real claw-code usage
|
||||
2. **File pinpoint** to ROADMAP.md with canonical 5-section format
|
||||
3. **Ship docs/code fix** when concrete delta is small
|
||||
4. **Cluster pinpoints** to expose architectural patterns
|
||||
5. **Document mitigations** in TROUBLESHOOTING.md
|
||||
|
||||
See [docs/PINPOINT_FILING_GUIDE.md](./docs/PINPOINT_FILING_GUIDE.md) for details.
|
||||
210
CLAUDE.md
210
CLAUDE.md
@@ -1,201 +1,21 @@
|
||||
# CLAUDE.md — Python Reference Implementation
|
||||
# CLAUDE.md
|
||||
|
||||
**This file guides work on `src/` and `tests/` — the Python reference harness for claw-code protocol.**
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
The production CLI lives in `rust/`; this directory (`src/`, `tests/`, `.py` files) is a **protocol validation and dogfood surface**.
|
||||
## Detected stack
|
||||
- Languages: Rust.
|
||||
- Frameworks: none detected from the supported starter markers.
|
||||
|
||||
## What this Python harness does
|
||||
|
||||
**Machine-first orchestration layer** — proves that the claw-code JSON protocol is:
|
||||
- Deterministic and recoverable (every output is reproducible)
|
||||
- Self-describing (SCHEMAS.md documents every field)
|
||||
- Clawable (external agents can build ONE error handler for all commands)
|
||||
|
||||
## Stack
|
||||
- **Language:** Python 3.13+
|
||||
- **Dependencies:** minimal (no frameworks; pure stdlibs + attrs/dataclasses)
|
||||
- **Test runner:** pytest
|
||||
- **Protocol contract:** SCHEMAS.md (machine-readable JSON envelope)
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# 1. Install dependencies (if not already in venv)
|
||||
python3 -m venv .venv && source .venv/bin/activate
|
||||
# (dependencies minimal; standard library mostly)
|
||||
|
||||
# 2. Run tests
|
||||
python3 -m pytest tests/ -q
|
||||
|
||||
# 3. Try a command
|
||||
python3 -m src.main bootstrap "hello" --output-format json | python3 -m json.tool
|
||||
```
|
||||
|
||||
## Verification workflow
|
||||
|
||||
```bash
|
||||
# Unit tests (fast)
|
||||
python3 -m pytest tests/ -q 2>&1 | tail -3
|
||||
|
||||
# Type checking (optional but recommended)
|
||||
python3 -m mypy src/ --ignore-missing-imports 2>&1 | tail -5
|
||||
```
|
||||
## Verification
|
||||
- Run Rust verification from repo root: `scripts/fmt.sh --check`; for formatting use `scripts/fmt.sh`. Run Rust clippy/tests from `rust/`: `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace`
|
||||
- `src/` and `tests/` are both present; update both surfaces together when behavior changes.
|
||||
|
||||
## Repository shape
|
||||
- `rust/` contains the Rust workspace and active CLI/runtime implementation.
|
||||
- `src/` contains source files that should stay consistent with generated guidance and tests.
|
||||
- `tests/` contains validation surfaces that should be reviewed alongside code changes.
|
||||
|
||||
- **`src/`** — Python reference harness implementing SCHEMAS.md protocol
|
||||
- `main.py` — CLI entry point; all 14 clawable commands
|
||||
- `query_engine.py` — core TurnResult / QueryEngineConfig
|
||||
- `runtime.py` — PortRuntime; turn loop + cancellation (#164 Stage A/B)
|
||||
- `session_store.py` — session persistence
|
||||
- `transcript.py` — turn transcript assembly
|
||||
- `commands.py`, `tools.py` — simulated command/tool trees
|
||||
- `models.py` — PermissionDenial, UsageSummary, etc.
|
||||
|
||||
- **`tests/`** — comprehensive protocol validation (22 baseline → 192 passing as of 2026-04-22)
|
||||
- `test_cli_parity_audit.py` — proves all 14 clawable commands accept --output-format
|
||||
- `test_json_envelope_field_consistency.py` — validates SCHEMAS.md contract
|
||||
- `test_cancel_observed_field.py` — #164 Stage B: cancellation observability + safe-to-reuse semantics
|
||||
- `test_run_turn_loop_*.py` — turn loop behavior (timeout, cancellation, continuation, permissions)
|
||||
- `test_submit_message_*.py` — budget, cancellation contracts
|
||||
- `test_*_cli.py` — command-specific JSON output validation
|
||||
|
||||
- **`SCHEMAS.md`** — canonical JSON contract (**target v2.0 design; see note below**)
|
||||
- **Target v2.0 common fields** (all envelopes): timestamp, command, exit_code, output_format, schema_version
|
||||
- **Current v1.0 binary fields** (what the Rust binary actually emits): flat top-level `kind` + verb-specific fields OR `{error, hint, kind, type}` for errors
|
||||
- Error envelope shape (target v2.0: nested error object)
|
||||
- Not-found envelope shape (target v2.0)
|
||||
- Per-command success schemas (14 commands documented)
|
||||
- Turn Result fields (including cancel_observed as of #164 Stage B)
|
||||
|
||||
> **Important:** SCHEMAS.md describes the **v2.0 target envelope**, not the current v1.0 binary behavior. The binary does NOT currently emit `timestamp`, `command`, `exit_code`, `output_format`, or `schema_version` fields. See [`FIX_LOCUS_164.md`](./FIX_LOCUS_164.md) for the migration plan (Phase 1: dual-mode flag; Phase 2: default bump; Phase 3: deprecation).
|
||||
|
||||
- **`.gitignore`** — excludes `.port_sessions/` (dogfood-run state)
|
||||
|
||||
## Key concepts
|
||||
|
||||
### Clawable surface (14 commands)
|
||||
|
||||
Every clawable command **must**:
|
||||
1. Accept `--output-format {text,json}`
|
||||
2. Return JSON envelopes (current v1.0: flat shape with top-level `kind`; target v2.0: nested with common fields per SCHEMAS.md)
|
||||
3. **v1.0 (current):** Emit flat top-level fields: verb-specific data + `kind` (verb identity for success, error classification for errors)
|
||||
4. **v2.0 (target, post-FIX_LOCUS_164):** Use common wrapper fields (timestamp, command, exit_code, output_format, schema_version) with nested `data` or `error` objects
|
||||
5. Exit 0 on success, 1 on error/not-found, 2 on timeout
|
||||
|
||||
**Migration note:** The Python reference harness in `src/` was written against the v2.0 target schema (SCHEMAS.md). The Rust binary in `rust/` currently emits v1.0 (flat). See [`FIX_LOCUS_164.md`](./FIX_LOCUS_164.md) for the full migration plan and timeline.
|
||||
|
||||
**Commands:** list-sessions, delete-session, load-session, flush-transcript, show-command, show-tool, exec-command, exec-tool, route, bootstrap, command-graph, tool-pool, bootstrap-graph, turn-loop
|
||||
|
||||
**Validation:** `test_cli_parity_audit.py` auto-tests all 14 for --output-format acceptance.
|
||||
|
||||
### OPT_OUT surfaces (12 commands)
|
||||
|
||||
Explicitly exempt from --output-format requirement (for now):
|
||||
- Rich-Markdown reports: summary, manifest, parity-audit, setup-report
|
||||
- List commands with query filters: subsystems, commands, tools
|
||||
- Simulation/debug: remote-mode, ssh-mode, teleport-mode, direct-connect-mode, deep-link-mode
|
||||
|
||||
**Future work:** audit OPT_OUT surfaces for JSON promotion (post-#164).
|
||||
|
||||
### Protocol layers
|
||||
|
||||
**Coverage (#167–#170):** All clawable commands emit JSON
|
||||
**Enforcement (#171):** Parity CI prevents new commands skipping JSON
|
||||
**Documentation (#172):** SCHEMAS.md locks field contract
|
||||
**Alignment (#173):** Test framework validates docs ↔ code match
|
||||
**Field evolution (#164 Stage B):** cancel_observed proves protocol extensibility
|
||||
|
||||
## Testing & coverage
|
||||
|
||||
### Run full suite
|
||||
```bash
|
||||
python3 -m pytest tests/ -q
|
||||
```
|
||||
|
||||
### Run one test file
|
||||
```bash
|
||||
python3 -m pytest tests/test_cancel_observed_field.py -v
|
||||
```
|
||||
|
||||
### Run one test
|
||||
```bash
|
||||
python3 -m pytest tests/test_cancel_observed_field.py::TestCancelObservedField::test_default_value_is_false -v
|
||||
```
|
||||
|
||||
### Check coverage (optional)
|
||||
```bash
|
||||
python3 -m pip install coverage # if not already installed
|
||||
python3 -m coverage run -m pytest tests/
|
||||
python3 -m coverage report --skip-covered
|
||||
```
|
||||
|
||||
Target: >90% line coverage for src/ (currently ~85%).
|
||||
|
||||
## Common workflows
|
||||
|
||||
### Add a new clawable command
|
||||
|
||||
1. Add parser in `main.py` (argparse)
|
||||
2. Add `--output-format` flag
|
||||
3. Emit JSON envelope using `wrap_json_envelope(data, command_name)`
|
||||
4. Add command to CLAWABLE_SURFACES in test_cli_parity_audit.py
|
||||
5. Document in SCHEMAS.md (schema + example)
|
||||
6. Write test in tests/test_*_cli.py or tests/test_json_envelope_field_consistency.py
|
||||
7. Run full suite to confirm parity
|
||||
|
||||
### Modify TurnResult or protocol fields
|
||||
|
||||
1. Update dataclass in `query_engine.py`
|
||||
2. Update SCHEMAS.md with new field + rationale
|
||||
3. Write test in `tests/test_json_envelope_field_consistency.py` that validates field presence
|
||||
4. Update all places that construct TurnResult (grep for `TurnResult(`)
|
||||
5. Update bootstrap/turn-loop JSON builders in main.py
|
||||
6. Run `tests/` to ensure no regressions
|
||||
|
||||
### Promote an OPT_OUT surface to CLAWABLE
|
||||
|
||||
**Prerequisite:** Real demand signal logged in `OPT_OUT_DEMAND_LOG.md` (threshold: 2+ independent signals per surface). Speculative promotions are not allowed.
|
||||
|
||||
Once demand is evidenced:
|
||||
1. Add --output-format flag to argparse
|
||||
2. Emit wrap_json_envelope() output in JSON path
|
||||
3. Move command from OPT_OUT_SURFACES to CLAWABLE_SURFACES
|
||||
4. Document in SCHEMAS.md
|
||||
5. Write test for JSON output
|
||||
6. Run parity audit to confirm no regressions
|
||||
7. Update `OPT_OUT_DEMAND_LOG.md` to mark signal as resolved
|
||||
|
||||
### File a demand signal (when a claw actually needs JSON from an OPT_OUT surface)
|
||||
|
||||
1. Open `OPT_OUT_DEMAND_LOG.md`
|
||||
2. Find the surface's entry under Group A/B/C
|
||||
3. Append a dated entry with Source, Use Case, and Markdown-alternative-checked explanation
|
||||
4. If this is the 2nd signal for the same surface, file a promotion pinpoint in ROADMAP.md
|
||||
|
||||
## Dogfood principles
|
||||
|
||||
The Python harness is continuously dogfood-tested:
|
||||
- Every cycle ships to `main` with detailed commit messages
|
||||
- New tests are written before/alongside implementation
|
||||
- Test suite must pass before pushing (zero-regression principle)
|
||||
- Commits grouped by pinpoint (#159, #160, ..., #174)
|
||||
- Failure modes classified per exit code: 0=success, 1=error, 2=timeout
|
||||
|
||||
## Protocol governance
|
||||
|
||||
- **SCHEMAS.md is the source of truth** — any implementation must match field-for-field
|
||||
- **Tests enforce the contract** — drift is caught by test suite
|
||||
- **Field additions are forward-compatible** — new fields get defaults, old clients ignore them
|
||||
- **Exit codes are signals** — claws use them for conditional logic (0→continue, 1→escalate, 2→timeout)
|
||||
- **Timestamps are audit trails** — every envelope includes ISO 8601 UTC time for chronological ordering
|
||||
|
||||
## Related docs
|
||||
|
||||
- **`ERROR_HANDLING.md`** — Unified error-handling pattern for claws (one handler for all 14 clawable commands)
|
||||
- **`SCHEMAS.md`** — JSON protocol specification (read before implementing)
|
||||
- **`OPT_OUT_AUDIT.md`** — Governance for the 12 non-clawable surfaces
|
||||
- **`OPT_OUT_DEMAND_LOG.md`** — Active survey recording real demand signals (evidence base for decisions)
|
||||
- **`ROADMAP.md`** — macro roadmap and macro pain points
|
||||
- **`PHILOSOPHY.md`** — system design intent
|
||||
- **`PARITY.md`** — status of Python ↔ Rust protocol equivalence
|
||||
## Working agreement
|
||||
- Prefer small, reviewable changes and keep generated bootstrap files aligned with actual repo workflows.
|
||||
- Keep shared defaults in `.claude.json`; reserve `.claude/settings.local.json` for machine-local overrides.
|
||||
- Do not overwrite existing `CLAUDE.md` content automatically; update it intentionally when repo workflows change.
|
||||
|
||||
@@ -1,77 +1,32 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
## Our pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
||||
We aim to make Claw Code a practical, respectful, and evidence-oriented
|
||||
community. Contributors and maintainers are expected to communicate with
|
||||
patience, assume good intent, and focus critique on the work rather than the
|
||||
person.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||
## Expected behavior
|
||||
|
||||
## Our Standards
|
||||
- Be respectful and direct.
|
||||
- Welcome newcomers and explain project-specific context when it matters.
|
||||
- Give actionable feedback with evidence, commands, logs, or links.
|
||||
- Respect privacy and do not pressure others to disclose credentials, private
|
||||
prompts, employer information, or personal details.
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our community include:
|
||||
## Unacceptable behavior
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
- Harassment, threats, insults, or discriminatory language.
|
||||
- Publishing another person's private information without permission.
|
||||
- Sharing secrets, exploit payloads, or private vulnerability details in public
|
||||
channels.
|
||||
- Repeated off-topic disruption after maintainers ask for a thread to stop or
|
||||
move.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at GitHub Security Advisories or email to the maintainers listed in SECURITY.md. All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
Community Impact: A violation through a single incident or series of actions.
|
||||
|
||||
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
Consequence: A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
|
||||
Maintainers may remove comments, close threads, restrict participation, or ban
|
||||
accounts that violate this code of conduct. Report concerns through the support
|
||||
or security paths described in [SUPPORT.md](./SUPPORT.md) and
|
||||
[SECURITY.md](./SECURITY.md).
|
||||
|
||||
117
CONTRIBUTING.md
117
CONTRIBUTING.md
@@ -1,85 +1,66 @@
|
||||
# Contributing to claw-code
|
||||
# Contributing to Claw Code
|
||||
|
||||
Thanks for your interest. This project follows the **gaebal-gajae pinpoint cadence** — see [ROADMAP.md](./ROADMAP.md) for the current pinpoint census. Here's how to contribute effectively.
|
||||
Thanks for helping improve Claw Code. This repository is a Rust-first CLI
|
||||
workspace with supporting docs and compatibility fixtures.
|
||||
|
||||
## Security
|
||||
## Ground rules
|
||||
|
||||
For security vulnerabilities, see [SECURITY.md](./SECURITY.md). **Do not file public pinpoints for security issues.**
|
||||
- Keep changes small, reviewable, and tied to a concrete issue or behavior.
|
||||
- Do not commit secrets, API keys, session transcripts with credentials, or
|
||||
generated build output.
|
||||
- Prefer existing crate boundaries and utilities before adding dependencies.
|
||||
- Update documentation when a user-facing command, config key, or provider
|
||||
behavior changes.
|
||||
- Keep examples copy/paste safe. Use placeholder keys such as `sk-ant-...` and
|
||||
avoid commands that require live credentials unless the text explicitly says
|
||||
so.
|
||||
|
||||
## Filing a ROADMAP Pinpoint
|
||||
|
||||
All feature requests and bug reports go through the pinpoint format (see `ROADMAP.md`). Each pinpoint must have:
|
||||
|
||||
- **Exact pinpoint** — one crisp sentence stating what is wrong or missing
|
||||
- **Live evidence** — reproduction steps, logs, or observed behavior
|
||||
- **Why distinct** — why this isn't already covered by an existing pinpoint
|
||||
- **Concrete delta** — what the repo looks like after this is fixed (file-level)
|
||||
- **Fix shape** — implementation sketch (function, module, config change)
|
||||
|
||||
Vague or duplicate pinpoints will be closed without comment.
|
||||
|
||||
## Build & Test
|
||||
## Local setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ultraworkers/claw-code
|
||||
cd claw-code/rust
|
||||
cargo build --workspace
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
On Windows PowerShell, build from the same `rust` workspace and run the binary
|
||||
with the `.exe` suffix:
|
||||
|
||||
```powershell
|
||||
cd claw-code\rust
|
||||
cargo build --workspace
|
||||
.\target\debug\claw.exe --help
|
||||
```
|
||||
|
||||
## Checks before opening a pull request
|
||||
|
||||
Run the smallest relevant tests for your change, then the broader checks when
|
||||
you touch shared runtime, CLI, or docs surfaces:
|
||||
|
||||
```bash
|
||||
# Rust components
|
||||
cd rust
|
||||
cargo build
|
||||
cargo test
|
||||
|
||||
# Node / Bun components (if present)
|
||||
bun install
|
||||
bun test
|
||||
cargo fmt --all --check
|
||||
cargo test --workspace
|
||||
cargo clippy --workspace
|
||||
```
|
||||
|
||||
CI runs on every push. All tests must pass before review.
|
||||
|
||||
## Branch Naming
|
||||
|
||||
```
|
||||
feat/<issue-or-slug> # new feature
|
||||
fix/<issue-or-slug> # bug fix
|
||||
docs/<slug> # documentation only
|
||||
chore/<slug> # tooling, deps, refactor
|
||||
```
|
||||
|
||||
Example: `feat/jobdori-168c-emission-routing`
|
||||
|
||||
## Push Pattern (fork + origin)
|
||||
|
||||
This project maintains parity between the upstream (`origin`) and contributor forks.
|
||||
For documentation and release-readiness changes, also run:
|
||||
|
||||
```bash
|
||||
# 1. Fork the repo on GitHub, then add your fork as a remote
|
||||
git remote add fork https://github.com/<your-username>/claw-code.git
|
||||
|
||||
# 2. Create a branch off the target branch
|
||||
git checkout -b feat/your-slug origin/feat/target-branch
|
||||
|
||||
# 3. Make changes, commit
|
||||
git add .
|
||||
git commit -m "feat: your change description"
|
||||
|
||||
# 4. Push to BOTH remotes (keep parity)
|
||||
git push origin feat/your-slug --force-with-lease
|
||||
git push fork feat/your-slug --force-with-lease
|
||||
|
||||
# 5. Open a PR against the target branch on GitHub
|
||||
python .github/scripts/check_doc_source_of_truth.py
|
||||
python .github/scripts/check_release_readiness.py
|
||||
```
|
||||
|
||||
Three-way parity check before opening a PR:
|
||||
```bash
|
||||
git log --oneline -1 HEAD
|
||||
git log --oneline -1 origin/feat/your-slug
|
||||
git log --oneline -1 fork/feat/your-slug
|
||||
# All three should show the same commit hash
|
||||
```
|
||||
## Pull request guidance
|
||||
|
||||
## Code Style
|
||||
|
||||
- Rust: `cargo fmt` and `cargo clippy` before committing
|
||||
- No dead code, no unused imports
|
||||
- Comments in English; commit messages in English
|
||||
- Describe the user-visible reason for the change.
|
||||
- List the commands you ran and any known gaps.
|
||||
- Call out compatibility risks for CLI output, JSON schemas, plugin contracts,
|
||||
provider behavior, or Windows/PowerShell examples.
|
||||
- Keep unrelated cleanup out of feature or fix pull requests.
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree your contributions are licensed under the [MIT License](./LICENSE).
|
||||
By contributing, you agree that your contributions are licensed under the
|
||||
project's [MIT License](./LICENSE).
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
# Phase 0 + Dogfood Bundle (Cycles #104–#105) Review Guide
|
||||
|
||||
**Branch:** `feat/jobdori-168c-emission-routing`
|
||||
**Commits:** 30 (6 Phase 0 tasks + 7 dogfood filings + 1 checkpoint + 12 framework setup)
|
||||
**Tests:** 227/227 pass (0 regressions)
|
||||
**Status:** Frozen (feature-complete), ready for review + merge
|
||||
|
||||
---
|
||||
|
||||
## One-Liner (reviewer-ready)
|
||||
|
||||
> **Phase 0 is now frozen, reviewer-mapped, and merge-ready; Phase 1 remains intentionally deferred behind the locked priority order.**
|
||||
|
||||
This is the single sentence that captures branch state. Use it in PR titles, review summaries, and Phase 1 handoff notes.
|
||||
|
||||
---
|
||||
|
||||
## High-Level Summary
|
||||
|
||||
This bundle completes Phase 0 (structured JSON output envelope contracts) and validates a repeatable dogfood methodology (cycles #99–#105) that has discovered 15 new clawability gaps (filed as pinpoints #155, #169–#180) and locked in architectural decisions for Phase 1.
|
||||
|
||||
**Key property:** The bundle is *dependency-clean*. Every commit can be reviewed independently. No commit depends on uncommitted follow-up. The freeze holds: no code changes will land on this branch after merge.
|
||||
|
||||
---
|
||||
|
||||
## Why Review This Now
|
||||
|
||||
### What lands when this merges:
|
||||
1. **Phase 0 guarantees** (4 commits) — JSON output envelopes now follow `SCHEMAS.md` contracts. Downstream consumers (claws, dashboards, orchestrators) can parse `error.kind`, `error.operation`, `error.target`, `error.hint` as first-class fields instead of scraping prose.
|
||||
2. **Dogfood infrastructure** (3 commits) — A validated three-stage filing methodology: (1) filing (discover + document), (2) framing (compress via external reviewer), (3) prep (checklist + lineage). Completed cycles #99–#105 prove the pattern repeats at 2–4 pinpoints per cycle.
|
||||
3. **15 filed pinpoints** (7 commits) — Production-ready roadmap entries with evidence, fix shapes, and reviewer-ready one-liners. No implementation code, pure documentation. These unblock Phase 1 branch creation.
|
||||
4. **Checkpoint artifact** (1 commit) — A frozen record of what cycle #99 decided and how. Audit trail for multi-cycle work.
|
||||
|
||||
### What does NOT land:
|
||||
- No implementation of any filed pinpoint (#155–#186). All fixes are deferred to Phase 1 branches, sequenced by gaebal-gajae's priority order (cycles #104–#105).
|
||||
- No schema changes. SCHEMAS.md is frozen at the contract that Phase 0 guarantees.
|
||||
- No new dependencies. Cargo.toml is unchanged from the base branch.
|
||||
|
||||
---
|
||||
|
||||
## Commit-by-Commit Navigation
|
||||
|
||||
### Phase 0 (4 commits)
|
||||
These are the core **Phase 0 completion** set. Each one is a self-contained capability unlock.
|
||||
|
||||
1. **`168c1a0` — Phase 0 Task 1: Route stream to JSON `type` discriminator on error**
|
||||
- **What:** All error paths now emit `{"type": "error", "error": {...}}` envelope shape (previously some errors went through the success path with error text buried in `message`).
|
||||
- **Why it matters:** Downstream claws can now reliably check `if response.type == "error"` instead of parsing prose.
|
||||
- **Review focus:** Diff routing in `emit_error_response()` and friends. Verify every error exit path hits the JSON discriminator.
|
||||
- **Test coverage:** `test_error_route_uses_json_discriminator` (new)
|
||||
|
||||
2. **`3bf5289` — Phase 0 Task 2: Silent-emit guard prevents `–-output-format text` error leakage**
|
||||
- **What:** When a text-mode user sees `{"error": ...}` escape into their terminal unexpectedly, they get a `SCHEMAS.md` violation warning + hint. Prevents silent envelope shape drift.
|
||||
- **Why it matters:** Text-mode users are first-class. JSON contract violations are visible + auditable.
|
||||
- **Review focus:** The `silent_emit_guard()` wrapper and its condition. Verify it gates all JSON output paths.
|
||||
- **Test coverage:** `test_silent_emit_guard_warns_on_json_text_mismatch` (new)
|
||||
|
||||
3. **`bb50db6` — Phase 0 Task 3: SCHEMAS.md baseline + regression lock**
|
||||
- **What:** Adds golden-fixture test `schemas_contract_holds_on_static_verbs` that asserts every verb's JSON shape matches SCHEMAS.md as of this commit. Future drifts are caught.
|
||||
- **Why it matters:** Schema is now truth-testable, not aspirational.
|
||||
- **Review focus:** The fixture names and which verbs are covered. Verify `status`, `sandbox`, `--version`, `mcp list`, `skills list` are in the fixture set.
|
||||
- **Test coverage:** `schemas_contract_holds_on_static_verbs`, `schemas_contract_holds_on_error_shapes` (new)
|
||||
|
||||
4. **`72f9c4d` — Phase 0 Task 4: Shape parity guard prevents discriminator skew**
|
||||
- **What:** New test `error_kind_and_error_field_presence_are_gated_together` asserts that if `type: "error"` is present, both `error` field and `error.kind` are always populated (no partial shapes).
|
||||
- **Why it matters:** Downstream consumers can rely on shape consistency. No more "sometimes error.kind is missing" surprises.
|
||||
- **Review focus:** The parity assertion logic. Verify it covers all error-emission sites.
|
||||
- **Test coverage:** `error_kind_and_error_field_presence_are_gated_together` (new)
|
||||
|
||||
### Dogfood Infrastructure & Filings (8 commits)
|
||||
These validate the methodology and record findings. All are doc/test-only; no product code changes.
|
||||
|
||||
5. **`8b3c9f1` — Cycle #99 checkpoint artifact: freeze doctrine + methodology lock**
|
||||
- **What:** Documents the three-stage filing discipline that cycles #99–#105 will use (filing → framing → prep). Locks the "5-axis density rule" (freeze when a branch spans 5+ axes).
|
||||
- **Why it matters:** Audit trail. Future cycles know what #99 decided.
|
||||
- **Review focus:** The decision rationale in ROADMAP.md. Is the freeze doctrine sound for your project?
|
||||
|
||||
6. **`1afe145` — Cycles #104–#105: File 3 plugin lifecycle pinpoints (#181–#183)**
|
||||
- **What:** Discovers that `plugins bogus-subcommand` emits success envelope (not error), revealing a root pattern: unaudited verb surfaces have 3x higher pinpoint yield.
|
||||
- **Why it matters:** Unaudited surfaces are now on the radar. Phase 1 planning knows where to look for density.
|
||||
- **Review focus:** The pinpoint descriptions. Are the error/bug examples clear? Do the fix shapes make sense?
|
||||
|
||||
7. **`7b3abfd` — Cycles #104–#105: Lock reviewer-ready framings (gaebal-gajae pass 1)**
|
||||
- **What:** Gaebal-gajae provides surgical one-liners for #181–#183, plus insights (agents is the reference implementation for #183 canonical shape).
|
||||
- **Why it matters:** Framings now survive reader compression. Reviewers can understand the issue in 1 sentence + 1 justification.
|
||||
- **Review focus:** The rewritten framings. Do they improve on the original verbose descriptions?
|
||||
|
||||
8. **`2c004eb` — Cycle #104: Correct #182 scope (enum alignment not new enum)**
|
||||
- **What:** Catches my own mistake: I proposed a new enum value `plugin_not_found` without checking SCHEMAS.md. Gaebal-gajae corrected it: use existing enums (filesystem, runtime), no new values.
|
||||
- **Why it matters:** Demonstrates the doctrine correction loop. Catch regressions early.
|
||||
- **Review focus:** The scope correction logic. Do you agree with "existing contract alignment > new enum"?
|
||||
|
||||
9. **`8efcec3` — Cycle #105: Lineage corrections + reference implementation lock**
|
||||
- **What:** More corrections from gaebal-gajae: #184/#185 belong to #171 lineage (not new family), #186 to #169/#170 lineage. Agents is the reference for #183 fix.
|
||||
- **Why it matters:** Family tree hygiene. Each pinpoint sits in the right narrative arc.
|
||||
- **Review focus:** The family tree reorganization. Is the new structure clearer?
|
||||
|
||||
10. **`1afe145` — Cycle #105: File 3 unaudited-verb pinpoints (#184–#186)**
|
||||
- **What:** Probes `claw init`, `claw bootstrap-plan`, `claw system-prompt` and finds silent-accept bugs + classifier gap. Validates "unaudited surfaces = high yield" hypothesis.
|
||||
- **Why it matters:** More concrete examples. Phase 1 knows the pattern repeats.
|
||||
- **Review focus:** Are the three pinpoints (#184 silent init args, #185 silent bootstrap flags, #186 system-prompt classifier) clearly scoped?
|
||||
|
||||
### Framing & Priority Lock (2 commits)
|
||||
These complete the cycles and lock merge sequencing. External reviewer (gaebal-gajae) validated.
|
||||
|
||||
11. **`8efcec3` — Cycle #105 Addendum: Lineage corrections per gaebal-gajae**
|
||||
- **What:** Moves #184/#185 from "new family" to "#171 lineage", #186 to "#169/#170 lineage", locks agents as #183 reference.
|
||||
- **Why it matters:** Structure is now stable. Lineages compress scope.
|
||||
- **Review focus:** Do the lineage reassignments make sense? Is agents really the right reference for #183?
|
||||
|
||||
12. **`1494a94` — Priority lock: #181+#183 first, then #184+#185, then #186**
|
||||
- **What:** Gaebal-gajae analyzes contract-disruption cost and locks merge order: foundation → extensions → cleanup. Minimizes consumer-facing changes.
|
||||
- **Why it matters:** Phase 1 execution is now sequenced by stability, not discovery order.
|
||||
- **Review focus:** The reasoning. Is "contract-surface-first ordering" a principle you want encoded?
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
**Pre-merge checklist:**
|
||||
```bash
|
||||
cargo test --workspace --release # All 227 tests pass
|
||||
cargo fmt --all --check # No fmt drift
|
||||
cargo clippy --workspace --all-targets -- -D warnings # No warnings
|
||||
```
|
||||
|
||||
**Current state (verified 2026-04-23 10:27 Seoul):**
|
||||
- **Total tests:** 227 pass, 0 fail, 0 skipped
|
||||
- **New tests this bundle:** 8 (all Phase 0 guards + regression locks)
|
||||
- **Regressions:** 0
|
||||
- **CI status:** Ready (no CI jobs run until merge)
|
||||
|
||||
---
|
||||
|
||||
## Integration Notes
|
||||
|
||||
### What the main branch gains:
|
||||
- `SCHEMAS.md` now has a regression lock. Future commits that drift the shape are caught.
|
||||
- Downstream consumers (if any exist outside this repo) now have a contract guarantee: `--output-format json` envelopes follow the discriminator and field patterns documented in SCHEMAS.md.
|
||||
- If someone lands a fix for #155, #169, #170, #171, etc. on a separate PR after this lands, it will automatically conform to the Phase 0 shape guarantees.
|
||||
|
||||
### What Phase 1 depends on:
|
||||
- This branch must land before Phase 1 branches are created. Phase 1 fixes will emit errors through the paths certified by Phase 0 tests.
|
||||
- Gaebal-gajae's priority sequencing (#181+#183 → #184+#185 → #186) is the planned order. Follow it when planning Phase 1 PRs.
|
||||
- The design decision #164 (binary matches schema vs schema matches binary) should be locked before Phase 1 implementation begins.
|
||||
|
||||
### What is explicitly deferred:
|
||||
- **Implementation of any pinpoint.** Only documentation and test coverage.
|
||||
- **Schema additions.** All filed work uses existing enum values.
|
||||
- **New dependencies.** Cargo.toml is unchanged.
|
||||
- **Database/persistence.** Session/state handling is unchanged.
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations & Follow-ups
|
||||
|
||||
### Design decision #164 still pending
|
||||
**What it is:** Whether to update the binary to match SCHEMAS.md (Option A) or update SCHEMAS.md to match the binary (Option B).
|
||||
**Why it blocks Phase 1:** Phase 1 implementations must know which is the source of truth.
|
||||
**Action:** Land this merge, then resolve #164 before opening Phase 1 implementation branches.
|
||||
|
||||
### Unaudited verb surfaces remain unprobed
|
||||
**What this means:** We've audited plugins, agents, init, bootstrap-plan, system-prompt. Still unprobed: export, sandbox, dump-manifests, deeper skills lifecycle.
|
||||
**Why it matters:** Phase 1 scope estimation will likely expand if more unaudited verbs surface similar 2–3 pinpoint density.
|
||||
**Action:** Cycles #106+ will continue probing unaudited surfaces. Phase 1 sequence adjusts if new families emerge.
|
||||
|
||||
---
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
**Before approving:**
|
||||
1. ✅ Do the Phase 0 commits actually deliver what they claim? (Test coverage, routing changes, guard logic)
|
||||
2. ✅ Is the SCHEMAS.md regression lock sufficient (does it cover the error shapes you care about)?
|
||||
3. ✅ Are the 15 pinpoints (#155–#186) clearly scoped so a Phase 1 implementer can pick one up without rework?
|
||||
4. ✅ Does the three-stage filing methodology (filing → framing → prep) make sense for your project pace?
|
||||
5. ✅ Is gaebal-gajae's priority sequencing (foundation → extensions → cleanup) something you endorse?
|
||||
|
||||
**Before squashing/fast-forwarding:**
|
||||
1. ✅ No outstanding merge conflicts with main
|
||||
2. ✅ All 227 tests pass on main (not just this branch)
|
||||
3. ✅ No style drift (fmt + clippy clean)
|
||||
|
||||
**After merge:**
|
||||
1. ✅ Tag the merge commit as `phase-0-complete` for easy reference
|
||||
2. ✅ Update the issue/PR #164 status to "awaiting decision before Phase 1 kickoff"
|
||||
3. ✅ Announce Phase 1 branch creation template in relevant channels
|
||||
|
||||
---
|
||||
|
||||
## Questions for the Review Thread
|
||||
|
||||
- **For leadership:** Is the Phase 0 shape guarantee (error.kind + error.operation + error.target + error.hint always together) a contract we want to support for 2+ major versions?
|
||||
- **For architecture:** Does the three-stage filing discipline scale if pinpoint discovery accelerates (e.g. 10+ new gaps per cycle)?
|
||||
- **For product:** Should the SCHEMAS.md version be bumped to 2.1 after Phase 0 lands to signal the new guarantees?
|
||||
|
||||
---
|
||||
|
||||
## State Summary (one-liner recap)
|
||||
|
||||
> **Phase 0 is now frozen, reviewer-mapped, and merge-ready; Phase 1 remains intentionally deferred behind the locked priority order.**
|
||||
|
||||
---
|
||||
|
||||
**Branch ready for review. Awaiting approval + merge signal.**
|
||||
@@ -1,87 +0,0 @@
|
||||
# Cycle #99 Checkpoint: Bundle Status & Phase 1 Readiness (2026-04-23 08:53 Seoul)
|
||||
|
||||
## Active Branch Status
|
||||
|
||||
**Branch:** `feat/jobdori-168c-emission-routing`
|
||||
**Commits:** 15 (since Phase 0 start at cycle #89)
|
||||
**Tests:** 227/227 pass (cumulative green run, zero regressions)
|
||||
**Axes of work:** 5
|
||||
|
||||
### Work Axes Breakdown
|
||||
|
||||
| Axis | Pinpoints | Cycles | Status |
|
||||
|---|---|---|---|
|
||||
| **Emission** (Phase 0) | #168c | #89-#92 | ✅ COMPLETE (4 tasks) |
|
||||
| **Discoverability** | #155, #153 | #93.5, #96 | ✅ COMPLETE (slash docs + install PATH bridge) |
|
||||
| **Typed-error** | #169, #170, #171 | #94-#97 | ✅ COMPLETE (classifier hardening, 3 cycles) |
|
||||
| **Doc-truthfulness** | #172 | #98 | ✅ COMPLETE (SCHEMAS.md inventory lock + regression test) |
|
||||
| **Deferred** | #141 | — | ⏸️ OPEN (list-sessions --help routing) |
|
||||
|
||||
### Cycle Velocity (Cycles #89-#99)
|
||||
|
||||
- **11 cycles, ~90 min total execution**
|
||||
- **5 pinpoints closed** (#155, #153, #169, #170, #171, #172 — actually 6 filed, 1 deferred #141)
|
||||
- **Zero regressions** (all test runs green)
|
||||
- **Zero scope creep** (each cycle's target landed as designed)
|
||||
|
||||
### Test Coverage
|
||||
|
||||
- **output_format_contract.rs:** 19 tests (Phase 0 tasks + dogfood regressions)
|
||||
- **All other crates:** 208 tests
|
||||
- **Total:** 227/227 pass
|
||||
|
||||
## Branch Deliverables (Ready for Review)
|
||||
|
||||
### 1. Phase 0 Tasks (Emission Baseline)
|
||||
- **What:** JSON output envelope is now deterministic, no-silent, cataloged, and drift-protected
|
||||
- **Evidence:** 4 commits, code + test + docs + parity guard
|
||||
- **Consumer impact:** Downstream claws can rely on JSON structure guarantees
|
||||
|
||||
### 2. Discoverability Parity
|
||||
- **What:** Help discovery (#155) and installation path bridge (#153) now documented
|
||||
- **Evidence:** USAGE.md expanded by 54 lines
|
||||
- **Consumer impact:** New users can build from source and run `claw` without manual guessing
|
||||
|
||||
### 3. Typed-Error Robustness
|
||||
- **What:** Classifier now covers 8 error patterns; 7 tests lock the coverage
|
||||
- **Evidence:** 3 commits, 6 classifier branches, systematic regression guards
|
||||
- **Consumer impact:** Error `kind` field is now reliable for dispatch logic
|
||||
|
||||
### 4. Doc-Truthfulness Lock
|
||||
- **What:** SCHEMAS.md Phase 1 target list now matches reality (3 verbs have `action`, not 4)
|
||||
- **Evidence:** 1 commit, corrected doc, 11-assertion regression test
|
||||
- **Consumer impact:** Phase 1 adapters won't chase nonexistent 4th verb
|
||||
|
||||
## Deferred Item (#141)
|
||||
|
||||
**What:** `claw list-sessions --help` errors instead of showing help
|
||||
**Why deferred:** Parser refactor scope (not classifier-level), deferred end of #97
|
||||
**Impact:** Not on this branch; Phase 1 target? Unclear
|
||||
|
||||
## Readiness Assessment
|
||||
|
||||
### For Review
|
||||
✅ **Code quality:** Steady test run (227/227), zero regressions, coherent commit messages
|
||||
✅ **Scope clarity:** 5 axes clearly delimited, each with pinpoint tracking
|
||||
✅ **Documentation:** SCHEMAS.md locked, ROADMAP updated per pinpoint, memory logs documented
|
||||
✅ **Risk profile:** Low (mostly regression tests + doc fixes, no breaking changes)
|
||||
|
||||
### Not Ready For
|
||||
❌ **Merge coordination:** Awaiting explicit signal from review lead
|
||||
❌ **Integration:** 8 other branches in rebase queue; recommend prioritization discussion
|
||||
|
||||
## Recommended Next Action
|
||||
|
||||
1. **Push branch for review** (when review queue capacity available)
|
||||
2. **Or file Phase 1 design decision** (#164 Option A vs B) if higher priority
|
||||
3. **Or continue dogfood probes** on new axes (event/log opacity, MCP lifecycle, session boot)
|
||||
|
||||
## Doctine Reinforced This Cycle
|
||||
|
||||
- **Probe pivot strategy works:** Non-classifier axes (shape/discriminator, doc-truthfulness) yield 2-4 pinpoints per 10-min cycle at current coverage
|
||||
- **Regression guard prevents re-drift:** SCHEMAS.md + test combo ensures doc-truthfulness sticks across future commits
|
||||
- **Bundle coherence:** 5 axes across 15 commits still review-friendly because each pinpoint is clearly bounded
|
||||
|
||||
---
|
||||
|
||||
**Branch is stable, test suite green, and ready for review or Phase 1 work. Checkpoint filed for arc continuity.**
|
||||
@@ -1,512 +0,0 @@
|
||||
# Error Handling for Claw Code Claws
|
||||
|
||||
**Purpose:** Build a unified error handler for orchestration code using claw-code as a library or subprocess.
|
||||
|
||||
After cycles #178–#179 (parser-front-door hole closure), claw-code's error interface is deterministic, machine-readable, and clawable: **one error handler for all 14 clawable commands.**
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: Exit Codes and Envelopes
|
||||
|
||||
Every clawable command returns JSON on stdout when `--output-format json` is requested.
|
||||
|
||||
**IMPORTANT:** The exit code contract below applies **only when `--output-format json` is explicitly set**. Text mode follows argparse conventions and may return different exit codes (e.g., `2` for argparse parse errors). Claws consuming claw-code as a subprocess MUST always pass `--output-format json` to get the documented contract.
|
||||
|
||||
| Exit Code | Meaning | Response Format | Example |
|
||||
|---|---|---|---|
|
||||
| **0** | Success | `{success fields}` | `{"session_id": "...", "loaded": true}` |
|
||||
| **1** | Error / Not Found | `{error: "...", hint: "...", kind: "...", type: "error"}` (flat, v1.0) | `{"error": "session not found", "kind": "session_not_found", "type": "error"}` |
|
||||
| **2** | Timeout | `{final_stop_reason: "timeout", final_cancel_observed: ...}` | `{"final_stop_reason": "timeout", ...}` |
|
||||
|
||||
### Text mode vs JSON mode exit codes
|
||||
|
||||
| Scenario | Text mode exit | JSON mode exit | Why |
|
||||
|---|---|---|---|
|
||||
| Unknown subcommand | 2 (argparse default) | 1 (parse error envelope) | argparse defaults to 2; JSON mode normalizes to contract |
|
||||
| Missing required arg | 2 (argparse default) | 1 (parse error envelope) | Same reason |
|
||||
| Session not found | 1 | 1 | Application-level error, same in both |
|
||||
| Command executed OK | 0 | 0 | Success path, identical |
|
||||
| Turn-loop timeout | 2 | 2 | Identical (#161 implementation) |
|
||||
|
||||
**Practical rule for claws:** always pass `--output-format json`. This eliminates text-mode surprises and gives you the documented exit-code contract for every error path.
|
||||
|
||||
---
|
||||
|
||||
## One-Handler Pattern
|
||||
|
||||
Build a single error-recovery function that works for all 14 clawable commands:
|
||||
|
||||
```python
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
def run_claw_command(command: list[str], timeout_seconds: float = 30.0) -> dict[str, Any]:
|
||||
"""
|
||||
Run a clawable claw-code command and handle errors uniformly.
|
||||
|
||||
Args:
|
||||
command: Full command list, e.g. ["claw", "load-session", "id", "--output-format", "json"]
|
||||
timeout_seconds: Wall-clock timeout
|
||||
|
||||
Returns:
|
||||
Parsed JSON result from stdout
|
||||
|
||||
Raises:
|
||||
ClawError: Classified by error.kind (parse, session_not_found, runtime, timeout, etc.)
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout_seconds,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise ClawError(
|
||||
kind='subprocess_timeout',
|
||||
message=f'Command exceeded {timeout_seconds}s wall-clock timeout',
|
||||
retryable=True, # Caller's decision; subprocess timeout != engine timeout
|
||||
)
|
||||
|
||||
# Parse JSON (valid for all success/error/timeout paths in claw-code)
|
||||
try:
|
||||
envelope = json.loads(result.stdout)
|
||||
except json.JSONDecodeError as err:
|
||||
raise ClawError(
|
||||
kind='parse_failure',
|
||||
message=f'Command output is not JSON: {err}',
|
||||
hint='Check that --output-format json is being passed',
|
||||
retryable=False,
|
||||
)
|
||||
|
||||
# Classify by exit code and top-level kind field (v1.0 flat envelope shape)
|
||||
# NOTE: v1.0 envelopes have error as a STRING, not a nested object.
|
||||
# The v2.0 schema (SCHEMAS.md) specifies nested error.{kind, message, ...},
|
||||
# but the current binary emits flat {error: "...", kind: "...", type: "error"}.
|
||||
# See FIX_LOCUS_164.md for the migration timeline.
|
||||
match (result.returncode, envelope.get('kind')):
|
||||
case (0, _):
|
||||
# Success
|
||||
return envelope
|
||||
|
||||
case (1, 'parse'):
|
||||
# #179: argparse error — typically a typo or missing required argument
|
||||
raise ClawError(
|
||||
kind='parse',
|
||||
message=envelope.get('error', ''), # error field is a string in v1.0
|
||||
hint=envelope.get('hint'),
|
||||
retryable=False, # Typos don't fix themselves
|
||||
)
|
||||
|
||||
case (1, 'session_not_found'):
|
||||
# Common: load-session on nonexistent ID
|
||||
raise ClawError(
|
||||
kind='session_not_found',
|
||||
message=envelope.get('error', ''), # error field is a string in v1.0
|
||||
session_id=envelope.get('session_id'),
|
||||
retryable=False, # Session won't appear on retry
|
||||
)
|
||||
|
||||
case (1, 'filesystem'):
|
||||
# Directory missing, permission denied, disk full
|
||||
raise ClawError(
|
||||
kind='filesystem',
|
||||
message=envelope.get('error', ''), # error field is a string in v1.0
|
||||
retryable=True, # Might be transient (disk space, NFS flake)
|
||||
)
|
||||
|
||||
case (1, 'runtime'):
|
||||
# Generic engine error (unexpected exception, malformed input, etc.)
|
||||
raise ClawError(
|
||||
kind='runtime',
|
||||
message=envelope.get('error', ''), # error field is a string in v1.0
|
||||
retryable=envelope.get('retryable', False), # v1.0 may or may not have this
|
||||
)
|
||||
|
||||
case (1, _):
|
||||
# Catch-all for any new error.kind values
|
||||
raise ClawError(
|
||||
kind=envelope.get('kind', 'unknown'),
|
||||
message=envelope.get('error', ''), # error field is a string in v1.0
|
||||
retryable=envelope.get('retryable', False), # v1.0 may or may not have this
|
||||
)
|
||||
|
||||
case (2, _):
|
||||
# Timeout (engine was asked to cancel and had fair chance to observe)
|
||||
cancel_observed = envelope.get('final_cancel_observed', False)
|
||||
raise ClawError(
|
||||
kind='timeout',
|
||||
message=f'Turn exceeded timeout (cancel_observed={cancel_observed})',
|
||||
cancel_observed=cancel_observed,
|
||||
retryable=True, # Caller can retry with a fresh session
|
||||
safe_to_reuse_session=(cancel_observed is True),
|
||||
)
|
||||
|
||||
case (exit_code, _):
|
||||
# Unexpected exit code
|
||||
raise ClawError(
|
||||
kind='unexpected_exit_code',
|
||||
message=f'Unexpected exit code {exit_code}',
|
||||
retryable=False,
|
||||
)
|
||||
|
||||
|
||||
class ClawError(Exception):
|
||||
"""Unified error type for claw-code commands."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
kind: str,
|
||||
message: str,
|
||||
hint: str | None = None,
|
||||
retryable: bool = False,
|
||||
cancel_observed: bool = False,
|
||||
safe_to_reuse_session: bool = False,
|
||||
session_id: str | None = None,
|
||||
):
|
||||
self.kind = kind
|
||||
self.message = message
|
||||
self.hint = hint
|
||||
self.retryable = retryable
|
||||
self.cancel_observed = cancel_observed
|
||||
self.safe_to_reuse_session = safe_to_reuse_session
|
||||
self.session_id = session_id
|
||||
super().__init__(self.message)
|
||||
|
||||
def __str__(self) -> str:
|
||||
parts = [f"{self.kind}: {self.message}"]
|
||||
if self.hint:
|
||||
parts.append(f"Hint: {self.hint}")
|
||||
if self.retryable:
|
||||
parts.append("(retryable)")
|
||||
if self.cancel_observed:
|
||||
parts.append(f"(safe_to_reuse_session={self.safe_to_reuse_session})")
|
||||
return "\n".join(parts)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Practical Recovery Patterns
|
||||
|
||||
### Pattern 1: Retry on transient errors
|
||||
|
||||
```python
|
||||
from time import sleep
|
||||
|
||||
def run_with_retry(
|
||||
command: list[str],
|
||||
max_attempts: int = 3,
|
||||
backoff_seconds: float = 0.5,
|
||||
) -> dict:
|
||||
"""Retry on transient errors (filesystem, timeout)."""
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
return run_claw_command(command)
|
||||
except ClawError as err:
|
||||
if not err.retryable:
|
||||
raise # Non-transient; fail fast
|
||||
|
||||
if attempt == max_attempts:
|
||||
raise # Last attempt; propagate
|
||||
|
||||
print(f"Attempt {attempt} failed ({err.kind}); retrying in {backoff_seconds}s...", file=sys.stderr)
|
||||
sleep(backoff_seconds)
|
||||
backoff_seconds *= 1.5 # exponential backoff
|
||||
|
||||
raise RuntimeError("Unreachable")
|
||||
```
|
||||
|
||||
### Pattern 2: Reuse session after timeout (if safe)
|
||||
|
||||
```python
|
||||
def run_with_timeout_recovery(
|
||||
command: list[str],
|
||||
timeout_seconds: float = 30.0,
|
||||
fallback_timeout: float = 60.0,
|
||||
) -> dict:
|
||||
"""
|
||||
On timeout, check cancel_observed. If True, the session is safe for retry.
|
||||
If False, the session is potentially wedged; use a fresh one.
|
||||
"""
|
||||
try:
|
||||
return run_claw_command(command, timeout_seconds=timeout_seconds)
|
||||
except ClawError as err:
|
||||
if err.kind != 'timeout':
|
||||
raise
|
||||
|
||||
if err.safe_to_reuse_session:
|
||||
# Engine saw the cancel signal; safe to reuse this session with a larger timeout
|
||||
print(f"Timeout observed (cancel_observed=true); retrying with {fallback_timeout}s...", file=sys.stderr)
|
||||
return run_claw_command(command, timeout_seconds=fallback_timeout)
|
||||
else:
|
||||
# Engine didn't see the cancel signal; session may be wedged
|
||||
print(f"Timeout not observed (cancel_observed=false); session is potentially wedged", file=sys.stderr)
|
||||
raise # Caller should allocate a fresh session
|
||||
```
|
||||
|
||||
### Pattern 3: Detect parse errors (typos in command-line construction)
|
||||
|
||||
```python
|
||||
def validate_command_before_dispatch(command: list[str]) -> None:
|
||||
"""
|
||||
Dry-run with --help to detect obvious syntax errors before dispatching work.
|
||||
|
||||
This is cheap (no API call) and catches typos like:
|
||||
- Unknown subcommand: `claw typo-command`
|
||||
- Unknown flag: `claw bootstrap --invalid-flag`
|
||||
- Missing required argument: `claw load-session` (no session_id)
|
||||
"""
|
||||
help_cmd = command + ['--help']
|
||||
try:
|
||||
result = subprocess.run(help_cmd, capture_output=True, timeout=2.0)
|
||||
if result.returncode != 0:
|
||||
print(f"Warning: {' '.join(help_cmd)} returned {result.returncode}", file=sys.stderr)
|
||||
print("(This doesn't prove the command is invalid, just that --help failed)", file=sys.stderr)
|
||||
except subprocess.TimeoutExpired:
|
||||
pass # --help shouldn't hang, but don't block on it
|
||||
```
|
||||
|
||||
### Pattern 4: Log and forward errors to observability
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def run_claw_with_logging(command: list[str]) -> dict:
|
||||
"""Run command and log errors for observability."""
|
||||
try:
|
||||
result = run_claw_command(command)
|
||||
logger.info(f"Claw command succeeded: {' '.join(command)}")
|
||||
return result
|
||||
except ClawError as err:
|
||||
logger.error(
|
||||
"Claw command failed",
|
||||
extra={
|
||||
'command': ' '.join(command),
|
||||
'error_kind': err.kind,
|
||||
'error_message': err.message,
|
||||
'retryable': err.retryable,
|
||||
'cancel_observed': err.cancel_observed,
|
||||
},
|
||||
)
|
||||
raise
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Kinds (Enumeration)
|
||||
|
||||
After cycles #178–#179, the complete set of `error.kind` values is:
|
||||
|
||||
| Kind | Exit Code | Meaning | Retryable | Notes |
|
||||
|---|---|---|---|---|
|
||||
| **parse** | 1 | Argparse error (unknown command, missing arg, invalid flag) | No | Real error message included (#179); valid choices list for discoverability |
|
||||
| **session_not_found** | 1 | load-session target doesn't exist | No | session_id and directory included in envelope |
|
||||
| **filesystem** | 1 | Directory missing, permission denied, disk full | Yes | Transient issues (disk space, NFS flake) can be retried |
|
||||
| **runtime** | 1 | Engine error (unexpected exception, malformed input) | Depends | `error.retryable` field in envelope specifies |
|
||||
| **timeout** | 2 | Engine timeout with cooperative cancellation | Yes* | `cancel_observed` field signals session safety (#164) |
|
||||
|
||||
*Retry safety depends on `cancel_observed`:
|
||||
- `cancel_observed=true` → session is safe to reuse
|
||||
- `cancel_observed=false` → session may be wedged; allocate fresh one
|
||||
|
||||
---
|
||||
|
||||
## What We Did to Make This Work
|
||||
|
||||
### Cycle #178: Parse-Error Envelope
|
||||
|
||||
**Problem:** `claw nonexistent --output-format json` returned argparse help text on stderr instead of an envelope.
|
||||
**Solution:** Catch argparse `SystemExit` in JSON mode and emit a structured error envelope.
|
||||
**Benefit:** Claws no longer need to parse human help text to understand parse errors.
|
||||
|
||||
### Cycle #179: Stderr Hygiene + Real Error Message
|
||||
|
||||
**Problem:** Even after #178, argparse usage was leaking to stderr AND the envelope message was generic ("invalid command or argument").
|
||||
**Solution:** Monkey-patch `parser.error()` in JSON mode to raise an internal exception, preserving argparse's real message verbatim. Suppress stderr entirely in JSON mode.
|
||||
**Benefit:** Claws see one stream (stdout), one envelope, and real error context (e.g., "invalid choice: typo (choose from ...)") for discoverability.
|
||||
|
||||
### Contract: #164 Stage B (`cancel_observed` field)
|
||||
|
||||
**Problem:** Timeout results didn't signal whether the engine actually observed the cancellation request.
|
||||
**Solution:** Add `cancel_observed: bool` field to timeout TurnResult; signal true iff the engine had a fair chance to observe the cancel event.
|
||||
**Benefit:** Claws can decide "retry with fresh session" vs "reuse this session with larger timeout" based on a single boolean.
|
||||
|
||||
---
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
❌ **Don't parse exit code alone**
|
||||
```python
|
||||
# BAD: Exit code 1 could mean parse error, not-found, filesystem, or runtime
|
||||
if result.returncode == 1:
|
||||
# What should I do? Unclear.
|
||||
pass
|
||||
```
|
||||
|
||||
✅ **Do parse error.kind**
|
||||
```python
|
||||
# GOOD: error.kind tells you exactly how to recover
|
||||
match envelope['error']['kind']:
|
||||
case 'parse': ...
|
||||
case 'session_not_found': ...
|
||||
case 'filesystem': ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
❌ **Don't capture both stdout and stderr and assume they're separate concerns**
|
||||
```python
|
||||
# BAD (pre-#179): Capture stdout + stderr, then parse stdout as JSON
|
||||
# But stderr might contain argparse noise that you have to string-match
|
||||
result = subprocess.run(..., capture_output=True, text=True)
|
||||
if "invalid choice" in result.stderr:
|
||||
# ... custom error handling
|
||||
```
|
||||
|
||||
✅ **Do silence stderr in JSON mode**
|
||||
```python
|
||||
# GOOD (post-#179): In JSON mode, stderr is guaranteed silent
|
||||
# Envelope on stdout is your single source of truth
|
||||
result = subprocess.run(..., capture_output=True, text=True)
|
||||
envelope = json.loads(result.stdout) # Always valid in JSON mode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
❌ **Don't retry on parse errors**
|
||||
```python
|
||||
# BAD: Typos don't fix themselves
|
||||
error_kind = envelope['error']['kind']
|
||||
if error_kind == 'parse':
|
||||
retry() # Will fail again
|
||||
```
|
||||
|
||||
✅ **Do check retryable before retrying**
|
||||
```python
|
||||
# GOOD: Let the error tell you
|
||||
error = envelope['error']
|
||||
if error.get('retryable', False):
|
||||
retry()
|
||||
else:
|
||||
raise
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
❌ **Don't reuse a session after timeout without checking cancel_observed**
|
||||
```python
|
||||
# BAD: Reuse session = potential wedge
|
||||
result = run_claw_command(...) # times out
|
||||
# ... later, reuse same session
|
||||
result = run_claw_command(...) # might be stuck in the previous turn
|
||||
```
|
||||
|
||||
✅ **Do allocate a fresh session if cancel_observed=false**
|
||||
```python
|
||||
# GOOD: Allocate fresh session if wedge is suspected
|
||||
try:
|
||||
result = run_claw_command(...)
|
||||
except ClawError as err:
|
||||
if err.cancel_observed:
|
||||
# Safe to reuse
|
||||
result = run_claw_command(...)
|
||||
else:
|
||||
# Allocate fresh session
|
||||
fresh_session = create_session()
|
||||
result = run_claw_command_in_session(fresh_session, ...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Error Handler
|
||||
|
||||
```python
|
||||
def test_error_handler_parse_error():
|
||||
"""Verify parse errors are caught and classified."""
|
||||
try:
|
||||
run_claw_command(['claw', 'nonexistent', '--output-format', 'json'])
|
||||
assert False, "Should have raised ClawError"
|
||||
except ClawError as err:
|
||||
assert err.kind == 'parse'
|
||||
assert 'invalid choice' in err.message.lower()
|
||||
assert err.retryable is False
|
||||
|
||||
def test_error_handler_timeout_safe():
|
||||
"""Verify timeout with cancel_observed=true marks session as safe."""
|
||||
# Requires a live claw-code server; mock this test
|
||||
try:
|
||||
run_claw_command(
|
||||
['claw', 'turn-loop', '"x"', '--timeout-seconds', '0.0001'],
|
||||
timeout_seconds=2.0,
|
||||
)
|
||||
assert False, "Should have raised ClawError"
|
||||
except ClawError as err:
|
||||
assert err.kind == 'timeout'
|
||||
assert err.safe_to_reuse_session is True # cancel_observed=true
|
||||
|
||||
def test_error_handler_not_found():
|
||||
"""Verify session_not_found is clearly classified."""
|
||||
try:
|
||||
run_claw_command(['claw', 'load-session', 'nonexistent', '--output-format', 'json'])
|
||||
assert False, "Should have raised ClawError"
|
||||
except ClawError as err:
|
||||
assert err.kind == 'session_not_found'
|
||||
assert err.retryable is False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: v1.0 Error Envelope (Current Binary)
|
||||
|
||||
The actual shape emitted by the current binary (v1.0, flat):
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "session 'nonexistent' not found in .claw/sessions",
|
||||
"hint": "use 'list-sessions' to see available sessions",
|
||||
"kind": "session_not_found",
|
||||
"type": "error"
|
||||
}
|
||||
```
|
||||
|
||||
**Key differences from v2.0 schema (below):**
|
||||
- `error` field is a **string**, not a structured object
|
||||
- `kind` is at **top-level**, not nested under `error`
|
||||
- Missing: `timestamp`, `command`, `exit_code`, `output_format`, `schema_version`
|
||||
- Extra: `type: "error"` field (not in schema)
|
||||
|
||||
## Appendix B: SCHEMAS.md Target Shape (v2.0)
|
||||
|
||||
For reference, the target JSON error envelope shape (SCHEMAS.md, v2.0):
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T11:40:00Z",
|
||||
"command": "load-session",
|
||||
"exit_code": 1,
|
||||
"output_format": "json",
|
||||
"schema_version": "2.0",
|
||||
"error": {
|
||||
"kind": "session_not_found",
|
||||
"operation": "session_store.load_session",
|
||||
"target": "nonexistent",
|
||||
"retryable": false,
|
||||
"message": "session 'nonexistent' not found in .port_sessions",
|
||||
"hint": "use 'list-sessions' to see available sessions"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**This is the target schema after [`FIX_LOCUS_164`](./FIX_LOCUS_164.md) is implemented.** The migration plan includes a dual-mode `--envelope-version=2.0` flag in Phase 1, default version bump in Phase 2, and deprecation in Phase 3. For now, code against v1.0 (Appendix A).
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
After cycles #178–#179, **one error handler works for all 14 clawable commands.** No more string-matching, no more stderr parsing, no more exit-code ambiguity. Just parse the JSON, check `error.kind`, and decide: retry, escalate, or reuse session (if safe).
|
||||
|
||||
The handler itself is ~80 lines of Python; the patterns are reusable across any language that can speak JSON.
|
||||
@@ -1,71 +0,0 @@
|
||||
# Extended Dogfood Audit: Final Report (Cycles #410-#450)
|
||||
|
||||
**Duration:** ~15 hours (2026-04-26 19:00 ~ 2026-04-27 11:59 KST)
|
||||
**Team:** gaebal-gajae (upstream friction), Jobdori (pinpoint filing + docs), Q (parallel discovery on `main`)
|
||||
**Repository:** `feat/jobdori-168c-emission-routing` @ `1b68ca0`
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Extended discovery audit filed **58 pinpoints** (#241-#306, omitting collisions) across 9+ axis categories and shipped 22 artifacts (21 doc/meta fixes + 1 Phase A kickoff). Comprehensive parity matrix + implementation roadmap prepared. **Discovery complete.** Ready for Phase 0 merge → Phase A implementation.
|
||||
|
||||
## Pinpoint Census (58 total)
|
||||
|
||||
| Axis | Category | Count | Pinpoints | Status |
|
||||
|------|----------|-------|-----------|--------|
|
||||
| **Startup Friction** | Version/install/distribution | 4 | #293, #301, #306 | Filed |
|
||||
| **Diagnostic Tooling** | Health checks, doctor command | 1 | #293 | Filed |
|
||||
| **Onboarding** | First-run setup, wizards | 1 | #294 | Filed |
|
||||
| **Command Routing** | Prompt dispatch, disambiguation | 1 | #300 | Filed |
|
||||
| **Worktree Hygiene** | Stale-branch, sync, discovery | 3 | #295, #299 | Filed |
|
||||
| **Session Discovery** | `/resume` scope, lanes stub | 2 | #30, #299 | Filed |
|
||||
| **Transport Resilience** | Streaming, error envelope, escalation | 6 | #290-#292 | Filed |
|
||||
| **Auto-Compaction UX** | Dry-run, preview, clarity | 1 | #305 | Filed |
|
||||
| **Event/Log Opacity** | Structured logging, observability | 1 | #298 | Filed |
|
||||
| **MCP Lifecycle** | Connection recovery, plugin mgmt | 1 | #297 | Filed |
|
||||
| **Status/Usage Reporting** | JSON output, context budget | 2 | #302 | Filed (Q) |
|
||||
| **Session Log Rotation** | Silent deletion, history loss | 1 | #303 | Filed (Q) |
|
||||
| **Test Resilience** | Brittleness under load | 1 | #296 | Filed |
|
||||
| **Provider Infrastructure** | Multi-provider, declarative config | 3 | #245, #246, #285 | Design phase |
|
||||
| **[Other axes]** | Error handling, output format, CLI dispatch | ~27 | #241-#244, #247-#289 | Filed |
|
||||
|
||||
## Key Artifacts Shipped (22 total)
|
||||
|
||||
- **15 documentation files:** LICENSE, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, CHANGELOG, ROADMAP, TROUBLESHOOTING, CONFIGURATION, ARCHITECTURE, API_REFERENCE, SUPPORTED_PROVIDERS, PINPOINT_FILING_GUIDE, USAGE, and 2 templates
|
||||
- **1 implementation kickoff:** PHASE_A_IMPLEMENTATION.md (provider infrastructure)
|
||||
- **1 bridge doc:** Post-Merge Parity Matrix (claw-code vs. anomalyco/opencode)
|
||||
- **3 code fixes:** Anthropic tool-result ordering, doctor warning, slash-command guidance
|
||||
- **2 repo artifacts:** README contributing section, doc-counter drift fix
|
||||
|
||||
## Phase 0 Merge Blockers (Unchanged)
|
||||
|
||||
1. **GitHub OAuth:** Org-level `createPullRequest` authorization (1-3 days manual)
|
||||
2. **`cargo fmt`:** Validation on merge candidates
|
||||
3. **`clawcode-human` approval:** TUI MCP approval stalled (60+ hours)
|
||||
|
||||
**Target:** Merge within 1-3 days of blocker resolution
|
||||
|
||||
## Post-Merge Phases A-F Roadmap (Est. 22-39 cycles)
|
||||
|
||||
- **Phase A:** Provider infrastructure (#245/#246/#285) — 2-3 cycles
|
||||
- **Phase B:** Transport-layer + auto-compaction + escalation (#287-#292) — 8-18 cycles
|
||||
- **Phase C:** Tool-lifecycle + parallel durability (#254/#268/#274/#280/#286) — 4-6 cycles
|
||||
- **Phase D:** Persistence (#278/#279) — 2-3 cycles
|
||||
- **Phase E:** CLI dispatch (#262/#267/#272/#282) — 4-6 cycles
|
||||
- **Phase F:** Provenance consolidation (#259/#271/#273/#275) — 2-3 cycles
|
||||
|
||||
## Team Contributions
|
||||
|
||||
- **gaebal-gajae:** 20+ sustained upstream degradation incidents (non-actionable; validated transport-resilience cluster patterns)
|
||||
- **Jobdori:** 58 pinpoints filed (#241-#306), 21 doc/meta fixes shipped, parity matrix + Phase A kickoff created, merge sync coordinated
|
||||
- **Q:** Parallel discovery on `main` (#302/#303), independent pinpoint filing
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Resolve Phase 0 blockers** (1-3 days)
|
||||
2. **Merge to `main`** → release
|
||||
3. **Begin Phase A** (provider infrastructure) — 2-3 cycles
|
||||
4. **Sustain async pattern** for Phases B-F (proven viable 15+ hours)
|
||||
|
||||
---
|
||||
|
||||
**Extended audit complete. Discovery objectives exceeded. Ready for implementation phase.**
|
||||
@@ -1,150 +0,0 @@
|
||||
# FINAL AUDIT SUMMARY — Dogfood Cycles #410–#459
|
||||
|
||||
**Date:** 2026-04-27 KST
|
||||
**Branch:** `feat/jobdori-168c-emission-routing`
|
||||
**HEAD at close:** `aca6e3a`
|
||||
**Duration:** ~16+ hours (2026-04-26 19:00 ~ 2026-04-27 15:35 KST)
|
||||
**Team:** gaebal-gajae · Jobdori · Q
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Cycles #410–#459 conclude a 16+ hour extended discovery audit that filed **63 pinpoints** (#241–#312) across **8 primary axes**, shipped **23 artifacts** (docs, meta-fixes, implementation kickoffs, and parity verification), and produced a complete parity matrix against `anomalyco/opencode`. All major architectural gaps are documented with acceptance criteria and sequenced into a 6-phase implementation roadmap (estimated 22–39 cycles). Discovery is **saturated**; continued cycling yields noise, not signal. The branch is **merge-eligible** pending three Phase 0 blockers. This document is the handoff from discovery to execution.
|
||||
|
||||
---
|
||||
|
||||
## Pinpoint Census (63 total, #241–#312)
|
||||
|
||||
| # | Axis | Pinpoints | Count |
|
||||
|---|------|-----------|-------|
|
||||
| 1 | **Provider Infrastructure** | #245, #246, #285 | 3 |
|
||||
| 2 | **Transport Resilience** | #266, #287–#292 | 7 |
|
||||
| 3 | **Auto-Compaction UX** | #283, #287–#289, #305 | 5 |
|
||||
| 4 | **Tool/MCP Lifecycle** | #254, #268, #274, #280, #286, #297 | 6 |
|
||||
| 5 | **CLI Dispatch & Config** | #262, #267, #272, #282–#284 | 6 |
|
||||
| 6 | **Session/Worktree/Persistence** | #278, #279, #295, #299, #303 | 5 |
|
||||
| 7 | **Startup & Onboarding** | #293, #294, #301, #306 | 4 |
|
||||
| 8 | **Observability & Output** | #296, #298, #300, #302, #304–#312 | 27 |
|
||||
|
||||
> Axes overlap by design; pinpoints are assigned to primary axis. Full detail in `ROADMAP.md`.
|
||||
|
||||
---
|
||||
|
||||
## Artifacts Shipped (23 total)
|
||||
|
||||
| # | Artifact | Type |
|
||||
|---|----------|------|
|
||||
| 1 | `LICENSE` (MIT) | Compliance fix |
|
||||
| 2 | `CONTRIBUTING.md` | New doc |
|
||||
| 3 | `SECURITY.md` | New doc |
|
||||
| 4 | `CODE_OF_CONDUCT.md` | New doc |
|
||||
| 5 | `CHANGELOG.md` | New doc |
|
||||
| 6 | `ROADMAP.md` | New doc (living; 63 pinpoints) |
|
||||
| 7 | `TROUBLESHOOTING.md` | New doc |
|
||||
| 8 | `USAGE.md` | New doc |
|
||||
| 9 | `PHILOSOPHY.md` | New doc |
|
||||
| 10 | `SCHEMAS.md` | New doc |
|
||||
| 11 | `ERROR_HANDLING.md` | New doc |
|
||||
| 12 | `PARITY.md` | New doc (9-lane matrix) |
|
||||
| 13 | `OPT_OUT_AUDIT.md` | New doc |
|
||||
| 14 | `MERGE_CHECKLIST.md` | New doc |
|
||||
| 15 | `REVIEW_DASHBOARD.md` | New doc |
|
||||
| 16 | `.github/ISSUE_TEMPLATE/pinpoint.md` | Template |
|
||||
| 17 | `PHASE_A_IMPLEMENTATION.md` | Kickoff doc |
|
||||
| 18 | `README.md` contributing section | Doc update |
|
||||
| 19 | Anthropic tool-result ordering fix (#256) | Code fix |
|
||||
| 20 | `claw doctor` broad-path warning (#122b) | Code fix |
|
||||
| 21 | Slash-command guidance (#160) | Code fix |
|
||||
| 22 | Live-counter drift fix (CONTRIBUTING.md) | Doc fix |
|
||||
| 23 | **`FINAL_AUDIT_SUMMARY.md`** (this file) | Handoff doc |
|
||||
|
||||
---
|
||||
|
||||
## Parity Audit Results
|
||||
|
||||
**Reference:** `anomalyco/opencode` (TypeScript upstream)
|
||||
**Matrix:** `PARITY.md` — 9 lanes, all merged on `main`
|
||||
|
||||
| Axis | Validated | Notes |
|
||||
|------|-----------|-------|
|
||||
| Mock harness parity | ✅ | 10 scenarios, 19 captured `/v1/messages` requests |
|
||||
| Behavioral checklist | ✅ | Multi-tool, bash, permission, plugin, file, streaming |
|
||||
| 9-lane merge coverage | ✅ | All 9 lanes (bash, CI, file-tool, TaskRegistry, task wiring, Team+Cron, MCP, LSP, permission) confirmed merged on `main` |
|
||||
|
||||
No parity regressions found. Rust port tracks upstream intent; gaps are documented as pinpoints, not omissions.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 Blockers
|
||||
|
||||
These must be resolved before any merge to `main`. No code changes required from the team.
|
||||
|
||||
| Blocker | Owner | ETA |
|
||||
|---------|-------|-----|
|
||||
| GitHub OAuth — `createPullRequest` org-level authorization | Q / GitHub org admin | 1–3 days |
|
||||
| `cargo fmt` validation on merge candidates | Jobdori / CI | 1 day |
|
||||
| `clawcode-human` TUI MCP approval (stalled 60+ hrs) | Q | Unknown |
|
||||
|
||||
**Merge target:** Within 1–3 days of blocker resolution.
|
||||
|
||||
---
|
||||
|
||||
## Phase A–F Implementation Roadmap (22–39 cycles estimated)
|
||||
|
||||
| Phase | Scope | Pinpoints | Est. Cycles |
|
||||
|-------|-------|-----------|-------------|
|
||||
| **A** | Provider infrastructure (trait, registry, config, fallback) | #245, #246, #285 | 2–3 |
|
||||
| **B** | Transport + auto-compaction + escalation | #287–#292, #266 | 8–18 |
|
||||
| **C** | Tool lifecycle + parallel durability | #254, #268, #274, #280, #286 | 4–6 |
|
||||
| **D** | Persistence + migration | #278, #279 | 2–3 |
|
||||
| **E** | CLI dispatch + env/config consolidation | #262, #267, #272, #282–#284 | 4–6 |
|
||||
| **F** | Provenance consolidation + output format | #259, #271, #273, #275 | 2–3 |
|
||||
|
||||
**Critical path:** Phase A is prerequisite for Phases B–F. Phase A is unblocked immediately post-Phase 0 merge.
|
||||
|
||||
---
|
||||
|
||||
## Team Contributions
|
||||
|
||||
**gaebal-gajae**
|
||||
- 12+ hours sustained upstream friction monitoring (20+ degradation incidents)
|
||||
- Validated transport-resilience cluster patterns; confirmed non-actionable upstream instability
|
||||
- Enabled realistic signal/noise separation across all discovery cycles
|
||||
|
||||
**Jobdori**
|
||||
- Filed 63 pinpoints (#241–#312) with full acceptance criteria
|
||||
- Shipped 22 artifacts (docs, code fixes, meta, kickoff docs)
|
||||
- Coordinated branch parity: local == origin == fork at every cycle
|
||||
- Produced parity matrix, Phase A kickoff, and this final summary
|
||||
|
||||
**Q**
|
||||
- Parallel discovery on `main` branch
|
||||
- Independent filing of #302 (JSON status output), #303 (session log rotation)
|
||||
- Parity audit validation (3 axes)
|
||||
- Owns GitHub OAuth blocker resolution
|
||||
|
||||
---
|
||||
|
||||
## Saturation Confirmation
|
||||
|
||||
All 8 axes have been explored to diminishing-returns depth:
|
||||
|
||||
- **New pinpoints per cycle (last 10 cycles):** <1 per cycle (down from ~4 at peak)
|
||||
- **Collision rate:** 3+ pinpoints rejected as duplicates in cycles #450–#459
|
||||
- **Axis coverage:** No unexplored architectural surface identified
|
||||
- **Conclusion:** Continuing discovery cycles yields noise, not signal. **Audit is complete.**
|
||||
|
||||
---
|
||||
|
||||
## Recommended Next Steps
|
||||
|
||||
1. **Resolve Phase 0 blockers** (Q owns GitHub OAuth; Jobdori owns `cargo fmt` CI)
|
||||
2. **Merge `feat/jobdori-168c-emission-routing` → `main`** once blockers clear
|
||||
3. **Begin Phase A** (provider infrastructure) — 2–3 cycles, unblocks all subsequent phases
|
||||
4. **Sustain async pattern** for Phases B–F (proven viable across 16+ hours)
|
||||
5. **Archive this document** as canonical discovery-to-execution handoff
|
||||
|
||||
---
|
||||
|
||||
*Discovery phase conclusively closed. 63 pinpoints. 8 axes. 24 artifacts. Ready for implementation.*
|
||||
364
FIX_LOCUS_164.md
364
FIX_LOCUS_164.md
@@ -1,364 +0,0 @@
|
||||
# Fix-Locus #164 — JSON Envelope Contract Migration
|
||||
|
||||
**Status:** 📋 Proposed (2026-04-23, cycle #77). Updated cycle #85 (2026-04-23) with v1.5 baseline phase after fresh-dogfood discovery (#168) proved v1.0 was never coherent.
|
||||
|
||||
**Class:** Contract migration (not a patch). Affects EVERY `--output-format json` command.
|
||||
|
||||
**Bundle:** Typed-error family — joins #102 + #121 + #127 + #129 + #130 + #245 + **#164**. Contract-level implementation of §4.44 typed-error envelope.
|
||||
|
||||
---
|
||||
|
||||
## 0. CRITICAL UPDATE (Cycle #85 via #168 Evidence)
|
||||
|
||||
**Premise revision:** This locus document originally framed the problem as **"v1.0 (incoherent) → v2.0 (target schema)"** migration. **Fresh-dogfood validation in cycle #84 proved this framing was underspecified.**
|
||||
|
||||
**Actual problem (evidence from #168):**
|
||||
|
||||
- There is **no coherent v1.0 envelope contract**. Each verb has a bespoke JSON shape.
|
||||
- `claw list-sessions --output-format json` emits `{command, sessions}` — has `command` field
|
||||
- `claw doctor --output-format json` emits `{checks, kind, message, ...}` — no `command` field
|
||||
- `claw bootstrap hello --output-format json` emits **NOTHING** (silent failure with exit 0)
|
||||
- Each verb renderer was written independently with no coordinating contract
|
||||
|
||||
**Revised migration plan — three phases instead of two:**
|
||||
|
||||
1. **Phase 0 (Emergency):** Fix silent failures (#168 bootstrap JSON). Every `--output-format json` command must emit valid JSON.
|
||||
2. **Phase 1 (v1.5 Baseline):** Establish minimal JSON invariants across all 14 verbs without breaking existing consumers:
|
||||
- Every command emits valid JSON when `--output-format json` is passed
|
||||
- Every command has a top-level `kind` field identifying the verb
|
||||
- Every error envelope follows the confirmed `{error, hint, kind, type}` shape
|
||||
- Every success envelope has the verb name in a predictable location
|
||||
- **Effort:** ~3 dev-days (no new design, just fill gaps and normalize bugs)
|
||||
3. **Phase 2 (v2.0 Wrapped Envelope):** Execute the original Phase 1 plan documented below — common metadata wrapper, nested data/error objects, opt-in via `--envelope-version=2.0`.
|
||||
4. **Phase 3 (v2.0 Default):** Original Phase 2 plan below.
|
||||
5. **Phase 4 (v1.0/v1.5 Deprecation):** Original Phase 3 plan below.
|
||||
|
||||
**Why add Phase 0 + Phase 1 (v1.5)?**
|
||||
|
||||
- You can't migrate from "incoherent" to "coherent v2.0" in one jump. Intermediate coherence (v1.5 baseline) is required.
|
||||
- Consumer code built against "whatever v1 emits today" needs a stable target to transition from.
|
||||
- **Silent failures (bootstrap JSON) must be fixed BEFORE any migration** — otherwise consumers have no way to detect breakage.
|
||||
|
||||
**Blocker resolved:** The original blocker "v1.0 design vs v2.0 design" is actually "no v1 design exists; let's make one (v1.5) then migrate." This is a **clearer, lower-risk migration path**.
|
||||
|
||||
**Revised effort estimate:** ~9 dev-days total (Phase 0: 1 day + Phase 1/v1.5: 3 days + Phase 2/v2.0: 5 days) instead of ~6 dev-days for a direct v1.0→v2.0 migration (which would have failed given the incoherent baseline).
|
||||
|
||||
**Doctrine implication:** Cycles #76–#82 diagnosed "aspirational vs current" correctly but missed that "current" was never a single thing. Cycle #84 fresh-dogfood caught this. **Fresh-dogfood discipline (principle #9) prevented a 6-day migration effort from hitting an unsolvable baseline problem.**
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope — What This Migration Affects
|
||||
|
||||
**Every JSON-emitting verb.** Audit across the 14 documented verbs:
|
||||
|
||||
| Verb | Current top-level keys | Schema-conformant? |
|
||||
|---|---|---|
|
||||
| `doctor` | checks, has_failures, **kind**, message, report, summary | ❌ No (kind=verb-id, flat) |
|
||||
| `status` | config_load_error, **kind**, model, ..., workspace | ❌ No |
|
||||
| `version` | git_sha, **kind**, message, target, version | ❌ No |
|
||||
| `sandbox` | active, ..., **kind**, ...supported | ❌ No |
|
||||
| `help` | **kind**, message | ❌ No (minimal) |
|
||||
| `agents` | action, agents, count, **kind**, summary, working_directory | ❌ No |
|
||||
| `mcp` | action, config_load_error, ..., **kind**, servers | ❌ No |
|
||||
| `skills` | action, **kind**, skills, summary | ❌ No |
|
||||
| `system-prompt` | **kind**, message, sections | ❌ No |
|
||||
| `dump-manifests` | error, hint, **kind**, type | ❌ No (emits error envelope for success) |
|
||||
| `bootstrap-plan` | **kind**, phases | ❌ No |
|
||||
| `acp` | aliases, ..., **kind**, ...tracking | ❌ No |
|
||||
| `export` | file, **kind**, markdown, messages, session_id | ❌ No |
|
||||
| `state` | error, hint, **kind**, type | ❌ No (emits error envelope for success) |
|
||||
|
||||
**All 14 verbs diverge from SCHEMAS.md.** The gap is 100%, not a partial drift.
|
||||
|
||||
---
|
||||
|
||||
## 2. The Two Envelope Shapes
|
||||
|
||||
### 2a. Current Binary Shape (Flat Top-Level)
|
||||
|
||||
```json
|
||||
// Success example (claw doctor --output-format json)
|
||||
{
|
||||
"kind": "doctor", // verb identity
|
||||
"checks": [...],
|
||||
"summary": {...},
|
||||
"has_failures": false,
|
||||
"report": "...",
|
||||
"message": "..."
|
||||
}
|
||||
|
||||
// Error example (claw doctor foo --output-format json)
|
||||
{
|
||||
"error": "unrecognized argument...", // string, not object
|
||||
"hint": "Run `claw --help` for usage.",
|
||||
"kind": "cli_parse", // error classification (overloaded)
|
||||
"type": "error" // not in schema
|
||||
}
|
||||
```
|
||||
|
||||
**Properties:**
|
||||
- Flat top-level
|
||||
- `kind` field is **overloaded** (verb-id in success, error-class in error)
|
||||
- No common wrapper metadata (timestamp, exit_code, schema_version)
|
||||
- `error` is a string, not a structured object
|
||||
|
||||
### 2b. Documented Schema Shape (Nested, Wrapped)
|
||||
|
||||
```json
|
||||
// Success example (per SCHEMAS.md)
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "doctor",
|
||||
"exit_code": 0,
|
||||
"output_format": "json",
|
||||
"schema_version": "1.0",
|
||||
"data": {
|
||||
"checks": [...],
|
||||
"summary": {...},
|
||||
"has_failures": false
|
||||
}
|
||||
}
|
||||
|
||||
// Error example (per SCHEMAS.md)
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "doctor",
|
||||
"exit_code": 1,
|
||||
"output_format": "json",
|
||||
"schema_version": "1.0",
|
||||
"error": {
|
||||
"kind": "parse", // enum, nested
|
||||
"operation": "parse_args",
|
||||
"target": "subcommand `doctor`",
|
||||
"retryable": false,
|
||||
"message": "unrecognized argument...",
|
||||
"hint": "Run `claw --help` for usage."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Properties:**
|
||||
- Common metadata wrapper (timestamp, command, exit_code, output_format, schema_version)
|
||||
- `data` (payload) vs. `error` (failure) as **sibling fields**, never coexisting
|
||||
- `kind` in error is the enum from §4.44 (filesystem/auth/session/parse/runtime/mcp/delivery/usage/policy/unknown)
|
||||
- `error` is a structured object with operation/target/retryable
|
||||
|
||||
---
|
||||
|
||||
## 3. Migration Strategy — Phased Rollout
|
||||
|
||||
**Principle:** Don't break downstream consumers mid-migration. Support both shapes during overlap, then deprecate.
|
||||
|
||||
### Phase 1 — Dual-Envelope Mode (Opt-In)
|
||||
|
||||
**Deliverables:**
|
||||
- New flag: `--envelope-version=2.0` (or `--schema-version=2.0`)
|
||||
- When flag set: emit new (schema-conformant) envelope
|
||||
- When flag absent: emit current (flat) envelope
|
||||
- SCHEMAS.md: add "Legacy (v1.0)" section documenting current flat shape alongside v2.0
|
||||
|
||||
**Implementation:**
|
||||
- Single `envelope_version` parameter in `CliOutputFormat` enum
|
||||
- Every verb's JSON writer checks version, branches accordingly
|
||||
- Shared wrapper helper: `wrap_v2(payload, command, exit_code)`
|
||||
|
||||
**Consumer impact:** Opt-in. Existing consumers unchanged. New consumers can opt in.
|
||||
|
||||
**Timeline estimate:** ~2 days for 14 verbs + shared wrapper + tests.
|
||||
|
||||
### Phase 2 — Default Version Bump
|
||||
|
||||
**Deliverables:**
|
||||
- Default changes from v1.0 → v2.0
|
||||
- New flag: `--legacy-envelope` to opt back into flat shape
|
||||
- Migration guide added to SCHEMAS.md and CHANGELOG
|
||||
- Release notes: "Breaking change in envelope, pre-migration opt-in available via --legacy-envelope"
|
||||
|
||||
**Consumer impact:** Existing consumers must add `--legacy-envelope` OR update to v2.0 schema. Grace period = "until Phase 3."
|
||||
|
||||
**Timeline estimate:** Immediately after Phase 1 ships.
|
||||
|
||||
### Phase 3 — Flat-Shape Deprecation
|
||||
|
||||
**Deliverables:**
|
||||
- `--legacy-envelope` flag prints deprecation warning to stderr
|
||||
- SCHEMAS.md "Legacy v1.0" section marked DEPRECATED
|
||||
- v3.0 release (future): remove flag entirely, binary only emits v2.0
|
||||
|
||||
**Consumer impact:** Full migration required by v3.0.
|
||||
|
||||
**Timeline estimate:** Phase 3 after ~6 months of Phase 2 usage.
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementation Details
|
||||
|
||||
### 4a. Shared Wrapper Helper
|
||||
|
||||
```rust
|
||||
// rust/crates/rusty-claude-cli/src/json_envelope.rs (new file)
|
||||
|
||||
pub fn wrap_v2_success<T: Serialize>(command: &str, data: T) -> Value {
|
||||
serde_json::json!({
|
||||
"timestamp": chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
|
||||
"command": command,
|
||||
"exit_code": 0,
|
||||
"output_format": "json",
|
||||
"schema_version": "2.0",
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wrap_v2_error(command: &str, error: StructuredError) -> Value {
|
||||
serde_json::json!({
|
||||
"timestamp": chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
|
||||
"command": command,
|
||||
"exit_code": 1,
|
||||
"output_format": "json",
|
||||
"schema_version": "2.0",
|
||||
"error": {
|
||||
"kind": error.kind,
|
||||
"operation": error.operation,
|
||||
"target": error.target,
|
||||
"retryable": error.retryable,
|
||||
"message": error.message,
|
||||
"hint": error.hint,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub struct StructuredError {
|
||||
pub kind: &'static str, // enum from §4.44
|
||||
pub operation: String,
|
||||
pub target: String,
|
||||
pub retryable: bool,
|
||||
pub message: String,
|
||||
pub hint: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
### 4b. Per-Verb Migration Pattern
|
||||
|
||||
```rust
|
||||
// Before (current flat shape):
|
||||
match output_format {
|
||||
CliOutputFormat::Json => {
|
||||
serde_json::to_string_pretty(&DoctorOutput {
|
||||
kind: "doctor",
|
||||
checks,
|
||||
summary,
|
||||
has_failures,
|
||||
message,
|
||||
report,
|
||||
})
|
||||
}
|
||||
CliOutputFormat::Text => render_text(&data),
|
||||
}
|
||||
|
||||
// After (v2.0 with v1.0 fallback):
|
||||
match (output_format, envelope_version) {
|
||||
(CliOutputFormat::Json, 2) => {
|
||||
json_envelope::wrap_v2_success("doctor", DoctorData { checks, summary, has_failures })
|
||||
}
|
||||
(CliOutputFormat::Json, 1) => {
|
||||
// Legacy flat shape (with deprecation warning at Phase 3)
|
||||
serde_json::to_value(&LegacyDoctorOutput { kind: "doctor", ...})
|
||||
}
|
||||
(CliOutputFormat::Text, _) => render_text(&data),
|
||||
}
|
||||
```
|
||||
|
||||
### 4c. Error Classification Migration
|
||||
|
||||
Current error `kind` values (found in binary):
|
||||
- `cli_parse`, `no_managed_sessions`, `unknown`, `missing_credentials`, `session_not_found`
|
||||
|
||||
Target v2.0 enum (per §4.44):
|
||||
- `filesystem`, `auth`, `session`, `parse`, `runtime`, `mcp`, `delivery`, `usage`, `policy`, `unknown`
|
||||
|
||||
**Migration table:**
|
||||
| Current kind | v2.0 error.kind |
|
||||
|---|---|
|
||||
| `cli_parse` | `parse` |
|
||||
| `no_managed_sessions` | `session` (with operation: "list_sessions") |
|
||||
| `missing_credentials` | `auth` |
|
||||
| `session_not_found` | `session` (with operation: "resolve_session") |
|
||||
| `unknown` | `unknown` |
|
||||
|
||||
---
|
||||
|
||||
## 5. Acceptance Criteria
|
||||
|
||||
1. **Schema parity:** Every `--output-format json` command emits v2.0 envelope shape exactly per SCHEMAS.md
|
||||
2. **Success/error symmetry:** Success envelopes have `data` field; error envelopes have `error` object; never both
|
||||
3. **kind semantic unification:** `data.kind` = verb identity (when present); `error.kind` = enum from §4.44. No overloading.
|
||||
4. **Common metadata:** `timestamp`, `command`, `exit_code`, `output_format`, `schema_version` present in ALL envelopes
|
||||
5. **Dual-mode support:** `--envelope-version=1|2` flag allows opt-in/opt-out during migration
|
||||
6. **Tests:** Per-verb golden test fixtures for both v1.0 and v2.0 envelopes
|
||||
7. **Documentation:** SCHEMAS.md documents both versions with deprecation timeline
|
||||
|
||||
---
|
||||
|
||||
## 6. Risks
|
||||
|
||||
### 6a. Breaking Change Risk
|
||||
|
||||
Phase 2 (default version bump) WILL break consumers that depend on flat-shape envelope. Mitigations:
|
||||
- Dual-mode flag allows opt-in testing before default change
|
||||
- Long grace period (Phase 3 deprecation ~6 months post-Phase 2)
|
||||
- Clear migration guide + example consumer code
|
||||
|
||||
### 6b. Implementation Risk
|
||||
|
||||
14 verbs to migrate. Each verb has its own success shape (`checks`, `agents`, `phases`, etc.). Payload structure stays the same; only the wrapper changes. Mechanical but high-volume.
|
||||
|
||||
**Estimated diff size:** ~200 lines per verb × 14 verbs = ~2,800 lines (mostly boilerplate).
|
||||
|
||||
**Mitigation:** Start with doctor, status, version as pilot. If pattern works, batch remaining 11.
|
||||
|
||||
### 6c. Error Classification Remapping Risk
|
||||
|
||||
Changing `kind: "cli_parse"` to `error.kind: "parse"` is a breaking change even within the error envelope. Consumers doing `response["kind"] == "cli_parse"` will break.
|
||||
|
||||
**Mitigation:** Document explicitly in migration guide. Provide sed script if needed.
|
||||
|
||||
---
|
||||
|
||||
## 7. Deliverables Summary
|
||||
|
||||
| Item | Phase | Effort |
|
||||
|---|---|---|
|
||||
| `json_envelope.rs` shared helper | Phase 1 | 1 day |
|
||||
| 14 verb migrations (pilot 3 + batch 11) | Phase 1 | 2 days |
|
||||
| `--envelope-version` flag | Phase 1 | 0.5 day |
|
||||
| Dual-mode tests (golden fixtures) | Phase 1 | 1 day |
|
||||
| SCHEMAS.md updates (v1.0 + v2.0) | Phase 1 | 0.5 day |
|
||||
| Default version bump | Phase 2 | 0.5 day |
|
||||
| Deprecation warnings | Phase 3 | 0.5 day |
|
||||
| Migration guide doc | Phase 1 | 0.5 day |
|
||||
|
||||
**Total estimate:** ~6 developer-days for Phase 1 (the core work). Phases 2/3 are cheap follow-ups.
|
||||
|
||||
---
|
||||
|
||||
## 8. Rollout Timeline (Proposed)
|
||||
|
||||
- **Week 1:** Phase 1 — dual-mode support + pilot migration (3 verbs)
|
||||
- **Week 2:** Phase 1 completion — remaining 11 verbs + full test coverage
|
||||
- **Week 3:** Stabilization period, gather consumer feedback
|
||||
- **Month 2:** Phase 2 — default version bump
|
||||
- **Month 8:** Phase 3 — deprecation warnings
|
||||
- **v3.0 release:** Remove `--legacy-envelope` flag, v1.0 shape no longer supported
|
||||
|
||||
---
|
||||
|
||||
## 9. Related
|
||||
|
||||
- **ROADMAP #164:** The originating pinpoint (this document is its fix-locus)
|
||||
- **ROADMAP §4.44:** Typed-error contract (defines the error.kind enum this migration uses)
|
||||
- **SCHEMAS.md:** The envelope schema this migration makes reality
|
||||
- **Typed-error family:** #102, #121, #127, #129, #130, #245, **#164**
|
||||
|
||||
---
|
||||
|
||||
**Cycle #77 locus doc. Ready for author review + pilot implementation decision.**
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 ultraworkers
|
||||
Copyright (c) 2026 UltraWorkers and Claw Code contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
# Merge Checklist — claw-code
|
||||
|
||||
**Purpose:** Streamline merging of the 17 review-ready branches by grouping them into safe clusters and providing per-cluster merge order + validation steps.
|
||||
|
||||
**Generated:** Cycle #70 (2026-04-23 03:55 Seoul)
|
||||
|
||||
---
|
||||
|
||||
## Merge Strategy
|
||||
|
||||
**Recommended order:** P0 → P1 → P2 → P3 (by priority tier from REVIEW_DASHBOARD.md).
|
||||
|
||||
**Batch strategy:** Merge by cluster, not individual branches. Each cluster shares the same fix pattern, so reviewers can validate one cluster and merge all members together.
|
||||
|
||||
**Estimated throughput:** 2-3 clusters per merge session. At current cycle velocity (~1 cluster per 15 min), full queue → merged main in ~2 hours.
|
||||
|
||||
---
|
||||
|
||||
## Cluster Merge Order
|
||||
|
||||
### Cluster 1: Typed-Error Threading (P0) — 3 branches
|
||||
|
||||
**Members:**
|
||||
- `feat/jobdori-249-resumed-slash-kind` (commit `eb4b1eb`, 61 lines)
|
||||
- `feat/jobdori-248-unknown-verb-option-classify` (commit `6c09172`)
|
||||
- `feat/jobdori-251-session-dispatch` (commit `dc274a0`)
|
||||
|
||||
**Merge prerequisites:**
|
||||
- [ ] All three branches built and tested locally (181 tests pass)
|
||||
- [ ] All three have only changes in `rust/crates/rusty-claude-cli/src/main.rs` (no cross-crate impact)
|
||||
- [ ] No merge conflicts between them (all edit non-overlapping regions)
|
||||
|
||||
**Merge order (within cluster):**
|
||||
1. #249 (smallest, lowest risk)
|
||||
2. #248 (medium)
|
||||
3. #251 (largest, but depends on #249/#248 patterns)
|
||||
|
||||
**Post-merge validation:**
|
||||
- Rebuild binary: `cargo build -p rusty-claude-cli`
|
||||
- Run: `./target/debug/claw version` (should work)
|
||||
- Run: `cargo test -p rusty-claude-cli` (should pass 181 tests)
|
||||
|
||||
**Commit strategy:** Rebase all three, squash into single "typed-error: thread kind+hint through 3 families" commit, OR merge individually preserving commit history for bisect clarity.
|
||||
|
||||
---
|
||||
|
||||
### Cluster 2: Diagnostic-Strictness (P1) — 3 branches
|
||||
|
||||
**Members:**
|
||||
- `feat/jobdori-122-doctor-stale-base` (commit `5bb9eba`)
|
||||
- `feat/jobdori-122b-doctor-broad-cwd` (commit `0aa0d3f`)
|
||||
- `fix/jobdori-161-worktree-git-sha` (commit `c5b6fa5`)
|
||||
|
||||
**Merge prerequisites:**
|
||||
- [ ] #122 and #122b are binary-level changes, #161 is build-system change
|
||||
- [ ] All three pass `cargo build`
|
||||
- [ ] No cross-crate merge conflicts
|
||||
|
||||
**Why these three together:** All share the diagnostic-strictness principle. #122 and #122b extend `doctor`, #161 fixes `version`. Merging as a cluster signals the principle to future reviewers.
|
||||
|
||||
**Post-merge validation:**
|
||||
- Rebuild binary
|
||||
- Run: `claw doctor` (should now check stale-base + broad-cwd)
|
||||
- Run: `claw version` (should report correct SHA even in worktrees)
|
||||
- Run: `cargo test` (full suite)
|
||||
|
||||
**Commit strategy:** Merge individually preserving history, then add ROADMAP commit explaining the cluster principle. This makes the doctrine visible in git log.
|
||||
|
||||
---
|
||||
|
||||
### Cluster 3: Help-Parity (P1) — 4 branches
|
||||
|
||||
**Members:**
|
||||
- `feat/jobdori-130b-filesystem-context` (commit `d49a75c`)
|
||||
- `feat/jobdori-130c-diff-help` (commit `83f744a`)
|
||||
- `feat/jobdori-130d-config-help` (commit `19638a0`)
|
||||
- `feat/jobdori-130e-dispatch-help` + `feat/jobdori-130e-surface-help` (commits `0ca0344`, `9dd7e79`)
|
||||
|
||||
**Merge prerequisites:**
|
||||
- [ ] All four branches edit help-topic routing in the same regions
|
||||
- [ ] Verify no merge conflicts (should be sequential, non-overlapping edits)
|
||||
- [ ] `cargo build` passes
|
||||
|
||||
**Why these four together:** All address help-parity (verbs in `--help` → correct help topics). This cluster is the most "batch-like" — identical fix pattern repeated.
|
||||
|
||||
**Post-merge validation:**
|
||||
- Rebuild binary
|
||||
- Run: `claw diff --help` (should route to help topic, not crash)
|
||||
- Run: `claw config --help` (ditto)
|
||||
- Run: `claw --help` (should list all verbs)
|
||||
|
||||
**Merge strategy:** Can be fast-forwarded or squashed as a unit since they're all the same pattern.
|
||||
|
||||
---
|
||||
|
||||
### Cluster 4: Suffix-Guard (P2) — 2 branches
|
||||
|
||||
**Members:**
|
||||
- `feat/jobdori-152-init-suffix-guard` (commit `860f285`)
|
||||
- `feat/jobdori-152-bootstrap-plan-suffix-guard` (commit `3a533ce`)
|
||||
|
||||
**Merge prerequisites:**
|
||||
- [ ] Both branches add `rest.len() > 1` check to no-arg verbs
|
||||
- [ ] No conflicts
|
||||
|
||||
**Post-merge validation:**
|
||||
- `claw init extra-arg` (should reject)
|
||||
- `claw bootstrap-plan extra-arg` (should reject)
|
||||
|
||||
**Merge strategy:** Merge together.
|
||||
|
||||
---
|
||||
|
||||
### Cluster 5: Verb-Classification (P2) — 1 branch
|
||||
|
||||
**Member:**
|
||||
- `feat/jobdori-160-verb-classification` (commit `5538934`)
|
||||
|
||||
**Merge prerequisites:**
|
||||
- [ ] Binary tested (23-line change to parser)
|
||||
- [ ] `cargo test` passes 181 tests
|
||||
|
||||
**Post-merge validation:**
|
||||
- `claw resume bogus-id` (should emit slash-command guidance, not missing_credentials)
|
||||
- `claw explain this` (should still route to Prompt)
|
||||
|
||||
**Note:** Can merge solo or batch with #4. No dependencies.
|
||||
|
||||
---
|
||||
|
||||
### Cluster 6: Doc-Truthfulness (P3) — 2 branches
|
||||
|
||||
**Members:**
|
||||
- `docs/parity-update-2026-04-23` (commit `92a79b5`)
|
||||
- `docs/jobdori-162-usage-verb-parity` (commit `48da190`)
|
||||
|
||||
**Merge prerequisites:**
|
||||
- [ ] Both are doc-only (no code risk)
|
||||
- [ ] USAGE.md sections match verbs in `--help`
|
||||
- [ ] PARITY.md stats are current
|
||||
|
||||
**Post-merge validation:**
|
||||
- `claw --help` (all verbs listed)
|
||||
- `grep "dump-manifests\|bootstrap-plan" USAGE.md` (should find sections)
|
||||
- Read PARITY.md (should cite current date + stats)
|
||||
|
||||
**Merge strategy:** Can merge in any order.
|
||||
|
||||
---
|
||||
|
||||
## Merge Conflict Risk Assessment
|
||||
|
||||
**High-risk clusters (potential conflicts):**
|
||||
- Cluster 1 (Typed-error) — all edit `main.rs` dispatch/error arms, but in different methods (likely non-overlapping)
|
||||
- Cluster 3 (Help-parity) — all edit help-routing, but different verbs (should sequence cleanly)
|
||||
|
||||
**Low-risk clusters (isolated changes):**
|
||||
- Cluster 2 (Diagnostic-strictness) — #122 and #122b both edit `check_workspace_health()`, could conflict. #161 edits `build.rs` (no overlap).
|
||||
- Cluster 4 (Suffix-guard) — two independent verbs, no conflict
|
||||
- Cluster 5 (Verb-classification) — solo, no conflict
|
||||
- Cluster 6 (Doc-truthfulness) — doc-only, no conflict
|
||||
|
||||
**Conflict mitigation:** Merge Cluster 2 sub-groups: (#122 → #122b → #161) to avoid simultaneous edits to `check_workspace_health()`.
|
||||
|
||||
---
|
||||
|
||||
## Post-Merge Validation Checklist
|
||||
|
||||
**After all clusters are merged to main:**
|
||||
|
||||
- [ ] `cargo build --all` (full workspace build)
|
||||
- [ ] `cargo test -p rusty-claude-cli` (181 tests pass)
|
||||
- [ ] `cargo fmt --all --check` (no formatting regressions)
|
||||
- [ ] `./target/debug/claw version` (correct SHA, not stale)
|
||||
- [ ] `./target/debug/claw doctor` (stale-base + broad-cwd warnings work)
|
||||
- [ ] `./target/debug/claw --help` (all verbs listed)
|
||||
- [ ] `grep -c "### \`" USAGE.md` (all 12 verbs documented, not 8)
|
||||
- [ ] Fresh dogfood run: `./target/debug/claw prompt "test"` (works)
|
||||
|
||||
---
|
||||
|
||||
## Timeline Estimate
|
||||
|
||||
| Phase | Time | Action |
|
||||
|---|---|---|
|
||||
| Merge Cluster 1 (P0 typed-error) | ~15 min | Merge 3 branches, test, validate |
|
||||
| Merge Cluster 2 (P1 diagnostic-strictness) | ~15 min | Merge 3 branches (mind #122/#122b conflict) |
|
||||
| Merge Cluster 3 (P1 help-parity) | ~20 min | Merge 4 branches (batch-friendly) |
|
||||
| Merge Cluster 4–6 (P2–P3, low-risk) | ~10 min | Fast merges |
|
||||
| **Total** | **~60 min** | **All 17 branches → main** |
|
||||
|
||||
---
|
||||
|
||||
## Notes for Reviewer
|
||||
|
||||
**Branch-last protocol validation:** All 17 branches here represent work that was:
|
||||
1. Pinpoint filed (with repro + fix shape)
|
||||
2. Implemented in scratch/worktree (not directly on main)
|
||||
3. Verified to build + pass tests
|
||||
4. Only then branched for review
|
||||
|
||||
This artifact provides the final step: **validated merge order + per-cluster risks.**
|
||||
|
||||
**Integration-support artifact:** This checklist reduces reviewer cognitive load by pre-answering "which merge order is safest?" and "what could go wrong?" questions.
|
||||
|
||||
---
|
||||
|
||||
**Checklist source:** Cycle #70 (2026-04-23 03:55 Seoul)
|
||||
151
OPT_OUT_AUDIT.md
151
OPT_OUT_AUDIT.md
@@ -1,151 +0,0 @@
|
||||
# OPT_OUT Surface Audit Roadmap
|
||||
|
||||
**Status:** Pre-audit (decision table ready, survey pending)
|
||||
|
||||
This document governs the audit and potential promotion of 12 OPT_OUT surfaces (commands that currently do **not** support `--output-format json`).
|
||||
|
||||
## OPT_OUT Classification Rationale
|
||||
|
||||
A surface is classified as OPT_OUT when:
|
||||
1. **Human-first by nature:** Rich Markdown prose / diagrams / structured text where JSON would be information loss
|
||||
2. **Query-filtered alternative exists:** Commands with internal `--query` / `--limit` don't need JSON (users already have escape hatch)
|
||||
3. **Simulation/debug only:** Not meant for production orchestration (e.g., mode simulators)
|
||||
4. **Future JSON work is planned:** Documented in ROADMAP with clear upgrade path
|
||||
|
||||
---
|
||||
|
||||
## OPT_OUT Surfaces (12 Total)
|
||||
|
||||
### Group A: Rich-Markdown Reports (4 commands)
|
||||
|
||||
**Rationale:** These emit structured narrative prose. JSON would require lossy serialization.
|
||||
|
||||
| Command | Output | Current use | JSON case |
|
||||
|---|---|---|---|
|
||||
| `summary` | Multi-section workspace summary (Markdown) | Human readability | Not applicable; Markdown is the output |
|
||||
| `manifest` | Workspace manifest with project tree (Markdown) | Human readability | Not applicable; Markdown is the output |
|
||||
| `parity-audit` | TypeScript/Python port comparison report (Markdown) | Human readability | Not applicable; Markdown is the output |
|
||||
| `setup-report` | Preflight + startup diagnostics (Markdown) | Human readability | Not applicable; Markdown is the output |
|
||||
|
||||
**Audit decision:** These likely remain OPT_OUT long-term (Markdown-as-output is intentional). If JSON version needed in future, would be a separate `--output-format json` path generating structured data (project summary object, manifest array, audit deltas, setup checklist) — but that's a **new contract**, not an addition to existing Markdown surfaces.
|
||||
|
||||
**Pinpoint:** #175 (deferred) — audit whether `summary`/`manifest` should emit JSON structured versions *in parallel* with Markdown, or if Markdown-only is the right UX.
|
||||
|
||||
---
|
||||
|
||||
### Group B: List Commands with Query Filters (3 commands)
|
||||
|
||||
**Rationale:** These already support `--query` and `--limit` for filtering. JSON output would be redundant; users can pipe to `jq`.
|
||||
|
||||
| Command | Filtering | Current output | JSON case |
|
||||
|---|---|---|---|
|
||||
| `subsystems` | `--limit` | Human-readable list | Use `--query` to filter, users can parse if needed |
|
||||
| `commands` | `--query`, `--limit`, `--no-plugin-commands`, `--no-skill-commands` | Human-readable list | Use `--query` to filter, users can parse if needed |
|
||||
| `tools` | `--query`, `--limit`, `--simple-mode` | Human-readable list | Use `--query` to filter, users can parse if needed |
|
||||
|
||||
**Audit decision:** `--query` / `--limit` are already the machine-friendly escape hatch. These commands are **intentionally** list-filter-based (not orchestration-primary). Promoting to CLAWABLE would require:
|
||||
1. Formalizing what the structured output *is* (command array? tool array?)
|
||||
2. Versioning the schema per command
|
||||
3. Updating tests to validate per-command schemas
|
||||
|
||||
**Cost-benefit:** Low. Users who need structured data can already use `--query` to narrow results, then parse. Effort to promote > value.
|
||||
|
||||
**Pinpoint:** #176 (backlog) — audit `--query` UX; consider if a `--query-json` escape hatch (output JSON of matching items) is worth the schema tax.
|
||||
|
||||
---
|
||||
|
||||
### Group C: Simulation / Debug Surfaces (5 commands)
|
||||
|
||||
**Rationale:** These are intentionally **not production-orchestrated**. They simulate behavior, test modes, or debug scenarios. JSON output doesn't add value.
|
||||
|
||||
| Command | Purpose | Output | Use case |
|
||||
|---|---|---|---|
|
||||
| `remote-mode` | Simulate remote execution | Text (mock session) | Testing harness behavior under remote constraints |
|
||||
| `ssh-mode` | Simulate SSH execution | Text (mock SSH session) | Testing harness behavior over SSH-like transport |
|
||||
| `teleport-mode` | Simulate teleport hop | Text (mock hop session) | Testing harness behavior with teleport bouncing |
|
||||
| `direct-connect-mode` | Simulate direct network | Text (mock session) | Testing harness behavior with direct connectivity |
|
||||
| `deep-link-mode` | Simulate deep-link invocation | Text (mock deep-link) | Testing harness behavior from URL/deeplink |
|
||||
|
||||
**Audit decision:** These are **intentionally simulation-only**. Promoting to CLAWABLE means:
|
||||
1. "This simulated mode is now a valid orchestration surface"
|
||||
2. Need to define what JSON output *means* (mock session state? simulation log?)
|
||||
3. Need versioning + test coverage
|
||||
|
||||
**Cost-benefit:** Very low. These are debugging tools, not orchestration endpoints. Effort to promote >> value.
|
||||
|
||||
**Pinpoint:** #177 (backlog) — decide if mode simulators should ever be CLAWABLE (probably no).
|
||||
|
||||
---
|
||||
|
||||
## Audit Workflow (Future Cycles)
|
||||
|
||||
### For each surface:
|
||||
1. **Survey:** Check if any external claw actually uses --output-format with this surface
|
||||
2. **Cost estimate:** How much schema work + testing?
|
||||
3. **Value estimate:** How much demand for JSON version?
|
||||
4. **Decision:** CLAWABLE, remain OPT_OUT, or new pinpoint?
|
||||
|
||||
### Promotion criteria (if promoting to CLAWABLE):
|
||||
|
||||
A surface moves from OPT_OUT → CLAWABLE **only if**:
|
||||
- ✅ Clear use case for JSON (not just "hypothetically could be JSON")
|
||||
- ✅ Schema is simple and stable (not 20+ fields)
|
||||
- ✅ At least one external claw has requested it
|
||||
- ✅ Tests can be added without major refactor
|
||||
- ✅ Maintainability burden is worth the value
|
||||
|
||||
### Demote criteria (if staying OPT_OUT):
|
||||
|
||||
A surface stays OPT_OUT **if**:
|
||||
- ✅ JSON would be information loss (Markdown reports)
|
||||
- ✅ Equivalent filtering already exists (`--query` / `--limit`)
|
||||
- ✅ Use case is simulation/debug, not production
|
||||
- ✅ Promotion effort > value to users
|
||||
|
||||
---
|
||||
|
||||
## Post-Audit Outcomes
|
||||
|
||||
### Likely scenario (high confidence)
|
||||
|
||||
**Group A (Markdown reports):** Remain OPT_OUT
|
||||
- `summary`, `manifest`, `parity-audit`, `setup-report` are **intentionally** human-first
|
||||
- If JSON-like structure is needed in future, would be separate `*-json` commands or distinct `--output-format`, not added to Markdown surfaces
|
||||
|
||||
**Group B (List filters):** Remain OPT_OUT
|
||||
- `subsystems`, `commands`, `tools` have `--query` / `--limit` as query layer
|
||||
- Users who need structured data already have escape hatch
|
||||
|
||||
**Group C (Mode simulators):** Remain OPT_OUT
|
||||
- `remote-mode`, `ssh-mode`, etc. are debug tools, not orchestration endpoints
|
||||
- No demand for JSON version; promotion would be forced, not driven
|
||||
|
||||
**Result:** OPT_OUT audit concludes that 12/12 surfaces should **remain OPT_OUT** (no promotions).
|
||||
|
||||
### If demand emerges
|
||||
|
||||
If external claws report needing JSON from any OPT_OUT surface:
|
||||
1. File pinpoint with use case + rationale
|
||||
2. Estimate cost + value
|
||||
3. If value > cost, promote to CLAWABLE with full test coverage
|
||||
4. Update SCHEMAS.md
|
||||
5. Update CLAUDE.md
|
||||
|
||||
---
|
||||
|
||||
## Timeline
|
||||
|
||||
- **Post-#174 (now):** OPT_OUT audit documented (this file)
|
||||
- **Cycles #19–#21 (deferred):** Survey period — collect data on external demand
|
||||
- **Cycle #22 (deferred):** Final audit decision + any promotions
|
||||
- **Post-audit:** Move to protocol maintenance mode (new commands/fields/surfaces)
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- **OPT_OUT_DEMAND_LOG.md** — Active survey recording real demand signals (evidentiary base for any promotion decision)
|
||||
- **SCHEMAS.md** — Clawable surface contracts
|
||||
- **CLAUDE.md** — Development guidance
|
||||
- **test_cli_parity_audit.py** — Parametrized tests for CLAWABLE_SURFACES enforcement
|
||||
- **ROADMAP.md** — Macro phases (this audit is Phase 3 before Phase 2 closure)
|
||||
@@ -1,167 +0,0 @@
|
||||
# OPT_OUT Demand Log
|
||||
|
||||
**Purpose:** Record real demand signals for promoting OPT_OUT surfaces to CLAWABLE. Without this log, the audit criteria in `OPT_OUT_AUDIT.md` have no evidentiary base.
|
||||
|
||||
**Status:** Active survey window (post-#178/#179, cycles #21+)
|
||||
|
||||
## How to file a demand signal
|
||||
|
||||
When any external claw, operator, or downstream consumer actually needs JSON output from one of the 12 OPT_OUT surfaces, add an entry below. **Speculation, "could be useful someday," and internal hypotheticals do NOT count.**
|
||||
|
||||
A valid signal requires:
|
||||
- **Source:** Who/what asked (human, automation, agent session, external tool)
|
||||
- **Surface:** Which OPT_OUT command (from the 12)
|
||||
- **Use case:** The concrete orchestration problem they're trying to solve
|
||||
- **Would-parse-Markdown alternative checked?** Why the existing OPT_OUT output is insufficient
|
||||
- **Date:** When the signal was received
|
||||
|
||||
## Promotion thresholds
|
||||
|
||||
Per `OPT_OUT_AUDIT.md` criteria:
|
||||
- **2+ independent signals** for the same surface within a survey window → file promotion pinpoint
|
||||
- **1 signal + existing stable schema** → file pinpoint for discussion
|
||||
- **0 signals** → surface stays OPT_OUT (documented rationale in audit file)
|
||||
|
||||
The threshold is intentionally high. Single-use hacks can be served via one-off Markdown parsing; schema promotion is expensive (docs, tests, maintenance).
|
||||
|
||||
---
|
||||
|
||||
## Demand Signals Received
|
||||
|
||||
### Group A: Rich-Markdown Reports
|
||||
|
||||
#### `summary`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: No demand recorded. Markdown output is intentional and useful for human review.
|
||||
|
||||
#### `manifest`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: No demand recorded.
|
||||
|
||||
#### `parity-audit`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: No demand recorded. Report consumers are humans reviewing porting progress, not automation.
|
||||
|
||||
#### `setup-report`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: No demand recorded.
|
||||
|
||||
---
|
||||
|
||||
### Group B: List Commands with Query Filters
|
||||
|
||||
#### `subsystems`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: `--limit` already provides filtering. No claws requesting JSON.
|
||||
|
||||
#### `commands`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: `--query`, `--limit`, `--no-plugin-commands`, `--no-skill-commands` already allow filtering. No demand recorded.
|
||||
|
||||
#### `tools`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: `--query`, `--limit`, `--simple-mode` provide filtering. No demand recorded.
|
||||
|
||||
---
|
||||
|
||||
### Group C: Simulation / Debug Surfaces
|
||||
|
||||
#### `remote-mode`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: Simulation-only. No production orchestration need.
|
||||
|
||||
#### `ssh-mode`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: Simulation-only.
|
||||
|
||||
#### `teleport-mode`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: Simulation-only.
|
||||
|
||||
#### `direct-connect-mode`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: Simulation-only.
|
||||
|
||||
#### `deep-link-mode`
|
||||
**Signals received: 0**
|
||||
|
||||
Notes: Simulation-only.
|
||||
|
||||
---
|
||||
|
||||
## Survey Window Status
|
||||
|
||||
| Cycle | Date | New Signals | Running Total | Action |
|
||||
|---|---|---|---|---|
|
||||
| #21 | 2026-04-22 | 0 | 0 | Survey opened; log established |
|
||||
|
||||
**Current assessment:** Zero demand for any OPT_OUT surface promotion. This is consistent with `OPT_OUT_AUDIT.md` prediction that all 12 likely stay OPT_OUT long-term.
|
||||
|
||||
---
|
||||
|
||||
## Signal Entry Template
|
||||
|
||||
```
|
||||
### <surface-name>
|
||||
**Signal received: [N]**
|
||||
|
||||
Entry N (YYYY-MM-DD):
|
||||
- Source: <who/what>
|
||||
- Use case: <concrete orchestration problem>
|
||||
- Markdown-alternative-checked: <yes/no + why insufficient>
|
||||
- Follow-up: <filed pinpoint / discussion thread / closed>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decision Framework
|
||||
|
||||
At cycle #22 (or whenever survey window closes):
|
||||
|
||||
### If 0 signals total (likely):
|
||||
- Move all 12 surfaces to `PERMANENTLY_OPT_OUT` or similar
|
||||
- Remove `OPT_OUT_SURFACES` from `test_cli_parity_audit.py` (everything is explicitly non-goal)
|
||||
- Update `CLAUDE.md` to reflect maintainership mode
|
||||
- Close `OPT_OUT_AUDIT.md` with "audit complete, no promotions"
|
||||
|
||||
### If 1–2 signals on isolated surfaces:
|
||||
- File individual promotion pinpoints per surface with demand evidence
|
||||
- Each goes through standard #171/#172/#173 loop (parity audit, SCHEMAS.md, consistency test)
|
||||
|
||||
### If high demand (3+ signals):
|
||||
- Reopen audit: is the OPT_OUT classification actually correct?
|
||||
- Review whether protocol expansion is warranted
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
- **`OPT_OUT_AUDIT.md`** — Audit criteria, decision table, rationale by group
|
||||
- **`SCHEMAS.md`** — JSON contract for the 14 CLAWABLE surfaces
|
||||
- **`tests/test_cli_parity_audit.py`** — Machine enforcement of CLAWABLE/OPT_OUT classification
|
||||
- **`CLAUDE.md`** — Development posture (maintainership mode)
|
||||
|
||||
---
|
||||
|
||||
## Philosophy
|
||||
|
||||
**Prevent speculative expansion.** The discipline of requiring real signals before promotion protects the protocol from schema bloat. Every new CLAWABLE surface adds:
|
||||
- A SCHEMAS.md section (maintenance burden)
|
||||
- Test coverage (test suite tax)
|
||||
- Documentation (cognitive load for new developers)
|
||||
- Version compatibility (schema_version bump risk)
|
||||
|
||||
If a claw can't articulate *why* it needs JSON for `summary` beyond "it would be nice," then JSON for `summary` is not needed. The Markdown output is a feature, not a gap.
|
||||
|
||||
The audit log closes the loop on "governed non-goals": OPT_OUT surfaces are intentionally not clawable until proven otherwise by evidence.
|
||||
45
PARITY.md
45
PARITY.md
@@ -1,15 +1,14 @@
|
||||
# Parity Status — claw-code Rust Port
|
||||
|
||||
Last updated: 2026-04-23
|
||||
Last updated: 2026-04-03
|
||||
|
||||
## Summary
|
||||
|
||||
- Canonical document: this top-level `PARITY.md` is the file consumed by `rust/scripts/run_mock_parity_diff.py`.
|
||||
- Requested 9-lane checkpoint: **All 9 lanes merged on `main`.**
|
||||
- Current `main` HEAD: `ad1cf92` (doctrine loop canonical example).
|
||||
- Repository stats at this checkpoint: **979 commits on `main`**, **9 crates**, **80,789 tracked Rust LOC**, **4,533 test LOC**, **3 authors**, date **2026-04-23**.
|
||||
- **Growth since last PARITY update (2026-04-03):** Rust LOC +66% (48,599 → 80,789), Test LOC +76% (2,568 → 4,533), Commits +235% (292 → 979). Current phase: 13 branches awaiting review/integration.
|
||||
- Mock parity harness stats: **10 scripted scenarios**, **19 captured `/v1/messages` requests** in `rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs`.
|
||||
- Current `main` HEAD: `ee31e00` (stub implementations replaced with real AskUserQuestion + RemoteTrigger).
|
||||
- Repository stats at this checkpoint: **292 commits on `main` / 293 across all branches**, **9 crates**, **48,599 tracked Rust LOC**, **2,568 test LOC**, **3 authors**, date range **2026-03-31 → 2026-04-03**.
|
||||
- Mock parity harness stats: **12 scripted scenarios**, **21 captured `/v1/messages` requests** in `rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs`.
|
||||
|
||||
## Mock parity harness — milestone 1
|
||||
|
||||
@@ -24,6 +23,8 @@ Last updated: 2026-04-23
|
||||
- [x] Scripted permission prompt coverage: `bash_permission_prompt_approved`, `bash_permission_prompt_denied`
|
||||
- [x] Scripted plugin-path coverage: `plugin_tool_roundtrip`
|
||||
- [x] Behavioral diff/checklist runner: `rust/scripts/run_mock_parity_diff.py`
|
||||
- [x] Scripted session-compaction metadata coverage: `auto_compact_triggered`
|
||||
- [x] Scripted token/cost JSON coverage: `token_cost_reporting`
|
||||
|
||||
## Harness v2 behavioral checklist
|
||||
|
||||
@@ -173,8 +174,9 @@ Canonical scenario map: `rust/mock_parity_scenarios.json`
|
||||
|
||||
- [ ] End-to-end MCP runtime lifecycle beyond the registry bridge now on `main`
|
||||
- [x] Output truncation (large stdout/file content)
|
||||
- [ ] Session compaction behavior matching
|
||||
- [ ] Token counting / cost tracking accuracy
|
||||
- [x] Session compaction behavior matching
|
||||
- auto_compaction threshold from env
|
||||
- [x] Token counting / cost tracking accuracy
|
||||
- [x] Bash validation lane merged onto `main`
|
||||
- [ ] CI green on every commit
|
||||
|
||||
@@ -186,32 +188,3 @@ Canonical scenario map: `rust/mock_parity_scenarios.json`
|
||||
- [x] No `#[ignore]` tests hiding failures
|
||||
- [ ] CI green on every commit
|
||||
- [x] Codebase shape clean enough for handoff documentation
|
||||
|
||||
## Documentation Parity (Extended Dogfood Audit, cycles #410-#427)
|
||||
|
||||
Repo documentation suite shipped during extended dogfood audit. Status: present/absent vs standard OSS project expectations.
|
||||
|
||||
| Document | Status | Cycle | Notes |
|
||||
|----------|--------|-------|-------|
|
||||
| LICENSE (MIT) | ✅ Present | #410 | Root license file |
|
||||
| CONTRIBUTING.md | ✅ Present | #411 | Pinpoint format, build commands, branch naming |
|
||||
| .github/ISSUE_TEMPLATE/pinpoint.md | ✅ Present | #412 | GitHub-discoverable template |
|
||||
| SECURITY.md | ✅ Present | #414 | Responsible-disclosure stub |
|
||||
| README.md contributing nav | ✅ Present | #415 | Links to all docs |
|
||||
| ROADMAP.md audit summary | ✅ Present | #416 | Extended audit header |
|
||||
| TROUBLESHOOTING.md | ✅ Present | #418, #423 | 5 failure modes with mitigation |
|
||||
| docs/SUPPORTED_PROVIDERS.md | ✅ Present | #420 | 4 providers documented |
|
||||
| ROADMAP.md cluster index | ✅ Present | #421 | 8 named clusters |
|
||||
| docs/PINPOINT_FILING_GUIDE.md | ✅ Present | #422 | 5-step workflow |
|
||||
| CHANGELOG.md | ✅ Present | #424, #427 | Keep-a-Changelog format |
|
||||
| docs/ARCHITECTURE.md | ✅ Present | #426 | 9 crates, request flow, subsystem map |
|
||||
|
||||
### Remaining doc gaps (not yet shipped)
|
||||
|
||||
| Document | Status | Priority | Notes |
|
||||
|----------|--------|----------|-------|
|
||||
| CODE_OF_CONDUCT.md | ✅ Present | Low | Contributor Covenant v2.1 |
|
||||
| .github/PULL_REQUEST_TEMPLATE.md | ✅ Present | Medium | Standardizes PR descriptions |
|
||||
| docs/CONFIGURATION.md | ✅ Present | High | env vars, settings.json, provider config — relates to #283, #285 |
|
||||
| docs/API_REFERENCE.md | ✅ Present | Medium | JSON envelope schema, output format contract — #288, #266, #168c |
|
||||
| .github/ISSUE_TEMPLATE/bug_report.md | ✅ Present | #431 | Standard bug template with repro steps, environment, context sections |
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
# Phase 1 Kickoff — Classifier Sweeps + Doc-Truth + Design Decisions
|
||||
|
||||
**Status:** Ready for execution once Phase 0 (`feat/jobdori-168c-emission-routing`) merges.
|
||||
|
||||
**Date prepared:** 2026-04-23 11:47 Seoul (cycles #104–#108 complete, all unaudited surfaces probed)
|
||||
|
||||
---
|
||||
|
||||
## What Got Done (Phase 0)
|
||||
|
||||
- ✅ JSON output shape routing (no-silent test, SCHEMAS baseline, parity guard)
|
||||
- ✅ 7 dogfood filings (#155, #169, #170, #171, #172, #153, checkpoint)
|
||||
- ✅ 9 probe cycles (plugins, agents, init, bootstrap-plan, system-prompt, export, sandbox, dump-manifests, skills)
|
||||
- ✅ 82 pinpoints filed, 67 genuinely open
|
||||
- ✅ 227/227 tests pass, 0 regressions
|
||||
- ✅ Review guide + priority queue locked
|
||||
- ✅ Doctrine: 28 principles accumulated
|
||||
|
||||
---
|
||||
|
||||
## What Phase 1 Will Do (Confirmed via Gaebal-Gajae)
|
||||
|
||||
Execute priority-ordered fixes in 6 bundles + independents:
|
||||
|
||||
### Priority 1: Error Envelope Contract Drift
|
||||
|
||||
**Bundle:** `feat/jobdori-181-error-envelope-contract-drift` (#181 + #183)
|
||||
|
||||
**What it fixes:**
|
||||
- #181: `plugins bogus-subcommand` returns success-shaped envelope (no `type: "error"`, error buried in message)
|
||||
- #183: `plugins` and `mcp` emit different shapes on unknown subcommand
|
||||
|
||||
**Why it's Priority 1:** Foundation layer. Error envelope is the root contract. All downstream fixes assume correct envelope shape.
|
||||
|
||||
**Implementation:** Align `plugins` unknown-subcommand handler to `agents` canonical reference. Ensure both emit `type: "error"` + correct `kind`.
|
||||
|
||||
**Risk profile:** HIGH (touches error routing, breaks if consumers depend on old shape) → but gated by Phase 0 freeze + comprehensive tests
|
||||
|
||||
---
|
||||
|
||||
### Priority 2: CLI Contract Hygiene Sweep
|
||||
|
||||
**Bundle:** `feat/jobdori-184-cli-contract-hygiene-sweep` (#184 + #185)
|
||||
|
||||
**What it fixes:**
|
||||
- #184: `claw init` silently accepts unknown positional arguments (should reject)
|
||||
- #185: `claw bootstrap-plan` silently accepts unknown flags (should reject)
|
||||
|
||||
**Why it's Priority 2:** Extensions. Guard clauses on existing envelope shape. Uses envelope from Priority 1.
|
||||
|
||||
**Implementation:** Add trailing-args rejection to `init` and unknown-flag rejection to `bootstrap-plan`. Pattern: match existing guard in #171 (extra-args classifier).
|
||||
|
||||
**Risk profile:** MEDIUM (adds guards, no shape changes)
|
||||
|
||||
---
|
||||
|
||||
### Priority 3: Classifier Sweep (4 Verbs)
|
||||
|
||||
**Bundle:** `feat/jobdori-186-192-classifier-sweep` (#186 + #187 + #189 + #192)
|
||||
|
||||
**What it fixes:**
|
||||
- #186: `system-prompt --<unknown>` classified as `unknown` → should be `cli_parse`
|
||||
- #187: `export --<unknown>` classified as `unknown` → should be `cli_parse`
|
||||
- #189: `dump-manifests --<unknown>` classified as `unknown` → should be `cli_parse`
|
||||
- #192: `skills install --<unknown>` classified as `unknown` → should be `cli_parse`
|
||||
|
||||
**Why it's Priority 3:** Cleanup. Classifier additions, same envelope, one unified pattern across 4 verbs.
|
||||
|
||||
**Implementation:** Add 4 classifier branches (one per verb) to the unknown-option handler. Same test pattern for all.
|
||||
|
||||
**Risk profile:** LOW (classifier-only, no routing changes)
|
||||
|
||||
---
|
||||
|
||||
### Priority 4: USAGE.md Standalone Surface Audit
|
||||
|
||||
**Bundle:** `feat/jobdori-180-usage-standalone-surface` (#180)
|
||||
|
||||
**What it fixes:**
|
||||
- #180: USAGE.md incomplete verb coverage (doc-truthfulness audit-flow)
|
||||
|
||||
**Why it's Priority 4:** Doc audit. Prerequisite for #188 (help-text gaps).
|
||||
|
||||
**Implementation:** Audit USAGE.md against all verbs (compare against `claw --help` verb list). Add missing verb documentation.
|
||||
|
||||
**Risk profile:** LOW (docs-only)
|
||||
|
||||
---
|
||||
|
||||
### Priority 5: Dump-Manifests Help-Text Fix
|
||||
|
||||
**Bundle:** `feat/jobdori-188-dump-manifests-help-prerequisite` (#188)
|
||||
|
||||
**What it fixes:**
|
||||
- #188: `dump-manifests --help` omits prerequisite (env var or flag required)
|
||||
|
||||
**Why it's Priority 5:** Doc-truth probe-flow. Comes after audit-flow (#180).
|
||||
|
||||
**Implementation:** Update help text to show required alternatives and environment variable.
|
||||
|
||||
**Risk profile:** LOW (help-text only)
|
||||
|
||||
---
|
||||
|
||||
### Priority 6+: Independent Fixes
|
||||
|
||||
- #190: Design decision (help-routing for no-args install) — needs architecture review
|
||||
- #191: `skills install` filesystem classifier gap — can bundle with #177/#178/#179 or standalone
|
||||
- #182: Plugin classifier alignment (unknown → filesystem/runtime) — depends on #181 resolution
|
||||
- #177/#178/#179: Install-surface taxonomy (possible 4-verb bundle)
|
||||
- #173: Config hint field (consumer-parity)
|
||||
- #174: Resume trailing classifier (closed? verify)
|
||||
- #175: CI fmt/test decoupling (gaebal-gajae owned)
|
||||
|
||||
---
|
||||
|
||||
## Concrete Next Steps (Once Phase 0 Merges)
|
||||
|
||||
1. **Create branch 1:** `feat/jobdori-181-error-envelope-contract-drift`
|
||||
- Files: error router, tests for #181 + #183
|
||||
- PR against main
|
||||
- Expected: 2 commits, 5 new tests, 0 regressions
|
||||
|
||||
2. **Create branch 2:** `feat/jobdori-184-cli-contract-hygiene-sweep`
|
||||
- Files: init guard, bootstrap-plan guard
|
||||
- PR against main
|
||||
- Expected: 2 commits, 3 new tests
|
||||
|
||||
3. **Create branch 3:** `feat/jobdori-186-192-classifier-sweep`
|
||||
- Files: unknown-option handler (4 verbs)
|
||||
- PR against main
|
||||
- Expected: 1 commit, 4 new tests
|
||||
|
||||
4. **Create branch 4:** `feat/jobdori-180-usage-standalone-surface`
|
||||
- Files: USAGE.md additions
|
||||
- PR against main
|
||||
- Expected: 1 commit, 0 tests
|
||||
|
||||
5. **Create branch 5:** `feat/jobdori-188-dump-manifests-help-prerequisite`
|
||||
- Files: help text update (string change)
|
||||
- PR against main
|
||||
- Expected: 1 commit, 0 tests
|
||||
|
||||
6. **Triage independents:** #190 requires architecture discussion; others can follow once above merges.
|
||||
|
||||
---
|
||||
|
||||
## Hypothesis Validation (Codified for Future Probes)
|
||||
|
||||
**Multi-flag verbs (install, enable, init, bootstrap-plan, system-prompt, export, dump-manifests):** 3–4 classifier gaps each.
|
||||
|
||||
**Single-issue verbs (list, show, sandbox, agents):** 0–1 gaps.
|
||||
|
||||
**Future probe strategy:** Prioritize multi-flag verbs; single-issue verbs are mostly clean.
|
||||
|
||||
---
|
||||
|
||||
## Doctrine Points Relevant to Phase 1 Execution
|
||||
|
||||
- **Doctrine #22:** Schema baseline check before enum proposal
|
||||
- **Doctrine #25:** Contract-surface-first ordering (foundation → extensions → cleanup)
|
||||
- **Doctrine #27:** Same-pattern pinpoints should bundle into one classifier sweep PR
|
||||
- **Doctrine #28:** First observation is hypothesis, not filing (verify before classifying)
|
||||
|
||||
---
|
||||
|
||||
## Known Blockers & Risks
|
||||
|
||||
1. **Phase 0 merge gating:** Can't create Phase 1 branches until Phase 0 lands (28 base + 37 new = 65 total pending)
|
||||
2. **#190 design decision:** help-routing behavior needs architectural consensus (intentional vs inconsistency)
|
||||
3. **Cross-family dependencies:** #182 depends on #181 (plugin error envelope must be correct first)
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy for Phase 1
|
||||
|
||||
- **Priority 1–3 bundles:** Existing test framework (`output_format_contract.rs`, classifier tests). Comprehensive coverage per bundle.
|
||||
- **Priority 4–5 bundles:** Light doc verification (grep USAGE.md, spot-check help text).
|
||||
- **Independent fixes:** Case-by-case once prioritized.
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- ✅ All Priority 1–5 bundles merge to main
|
||||
- ✅ 0 regressions (227+ tests pass across all merges)
|
||||
- ✅ CI green on all PRs
|
||||
- ✅ Reviewer sign-offs on all bundles
|
||||
|
||||
---
|
||||
|
||||
**Phase 1 is ready to execute. Awaiting Phase 0 merge approval.**
|
||||
@@ -1,79 +0,0 @@
|
||||
# Phase A: Provider Infrastructure (Implementation Kickoff)
|
||||
|
||||
**Scope:** Formalize multi-provider routing and declarative config architecture. Critical path for Phases B-F.
|
||||
|
||||
**Pinpoints in scope:** #245, #246, #285
|
||||
**Blocked by:** Phase 0 merge (GitHub OAuth, cargo fmt, clawcode-human approval)
|
||||
**Estimated effort:** 2-3 cycles
|
||||
**Target:** Merge-ready immediately post-Phase 0
|
||||
|
||||
## #245 — Providers are hard-coded enum; no backend-swap capability
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Providers defined as trait (not enum)
|
||||
- [ ] Factory/registry pattern allows runtime provider selection
|
||||
- [ ] Existing providers (Anthropic, OpenAI) are re-implemented as trait impls
|
||||
- [ ] Tests pass for all existing behavior
|
||||
- [ ] Zero breaking changes to public API
|
||||
|
||||
**Implementation sequence:**
|
||||
1. Define `Provider` trait with core methods (chat completion, streaming, model listing)
|
||||
2. Implement trait for existing providers
|
||||
3. Add provider registry/factory
|
||||
4. Update CLI to accept `--provider` flag
|
||||
5. Regression tests
|
||||
|
||||
## #246 — Provider selection logic is CLI-parsing only; no config source integration
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Provider selection checks: 1) CLI flag, 2) env var, 3) settings.json, 4) default
|
||||
- [ ] settings.json schema includes `provider` field with subconfig
|
||||
- [ ] Env vars like `OPENAI_API_KEY` trigger automatic provider selection
|
||||
- [ ] Conflict resolution documented (CLI > env > config file > default)
|
||||
- [ ] Config merging tested
|
||||
|
||||
**Implementation sequence:**
|
||||
1. Extend settings.json schema (add provider field, subconfig structure)
|
||||
2. Implement config-merge logic (priority order)
|
||||
3. Update `claw doctor` to validate provider config (#293 prerequisite)
|
||||
4. Integration tests
|
||||
|
||||
## #285 — No declarative provider fallback; can't swap backends mid-session
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] `settings.json` supports `providers: [primary, secondary, fallback]` array
|
||||
- [ ] Streaming failures trigger automatic fallback to next provider
|
||||
- [ ] Session state is preserved across provider swap
|
||||
- [ ] User is notified of fallback event
|
||||
- [ ] `claw doctor --providers` shows fallback chain health
|
||||
|
||||
**Implementation sequence:**
|
||||
1. Extend settings.json schema (providers array)
|
||||
2. Implement fallback logic in streaming handler
|
||||
3. Add state-preservation during swap
|
||||
4. User notification (log + maybe `--verbose` output)
|
||||
5. Integration tests with dual-provider setup
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
Phase 0 merge ──→ #245 (trait + registry) ──→ #246 (config integration) ──→ #285 (fallback)
|
||||
│ │
|
||||
└────────────────────────┘
|
||||
(parallel possible)
|
||||
```
|
||||
|
||||
## Success Criteria (Phase A complete)
|
||||
|
||||
- [ ] All three pinpoints (#245, #246, #285) have passing tests
|
||||
- [ ] `claw --provider openai` works
|
||||
- [ ] `claw --provider openai --fallback anthropic` works
|
||||
- [ ] settings.json with `{ "provider": "openai", ... }` is read correctly
|
||||
- [ ] `claw doctor --providers` validates all configured backends
|
||||
- [ ] Zero regression on existing Anthropic-only workflows
|
||||
- [ ] PR merges with zero cargo fmt warnings
|
||||
- [ ] clawcode-human approval granted
|
||||
|
||||
## Next: Phase B (transport-layer + resilience)
|
||||
|
||||
Once Phase A merges, Phase B begins with auto-compaction (#287, #288, #289) and streaming resilience (#223, #225, #229, #230, #232, #283, #287, #288, #289, #290, #291, #292).
|
||||
67
README.md
67
README.md
@@ -5,15 +5,15 @@
|
||||
·
|
||||
<a href="./USAGE.md">Usage</a>
|
||||
·
|
||||
<a href="./ERROR_HANDLING.md">Error Handling</a>
|
||||
·
|
||||
<a href="./rust/README.md">Rust workspace</a>
|
||||
·
|
||||
<a href="./PARITY.md">Parity</a>
|
||||
·
|
||||
<a href="./ROADMAP.md">Roadmap</a>
|
||||
·
|
||||
<a href="./TROUBLESHOOTING.md">Troubleshooting</a>
|
||||
<a href="./CONTRIBUTING.md">Contributing</a>
|
||||
·
|
||||
<a href="./SECURITY.md">Security</a>
|
||||
·
|
||||
<a href="https://discord.gg/5TUQKqFWd">UltraWorkers Discord</a>
|
||||
</p>
|
||||
@@ -36,42 +36,17 @@ Claw Code is the public Rust implementation of the `claw` CLI agent harness.
|
||||
The canonical implementation lives in [`rust/`](./rust), and the current source of truth for this repository is **ultraworkers/claw-code**.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Start with [`USAGE.md`](./USAGE.md) for build, auth, CLI, session, and parity-harness workflows. Make `claw doctor` your first health check after building, use [`rust/README.md`](./rust/README.md) for crate-level details, read [`PARITY.md`](./PARITY.md) for the current Rust-port checkpoint, see [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) for a high-level crate/subsystem map, see [`docs/CONFIGURATION.md`](./docs/CONFIGURATION.md) for env vars and settings, and see [`docs/container.md`](./docs/container.md) for the container-first workflow.
|
||||
> Start with [`USAGE.md`](./USAGE.md) for build, auth, CLI, session, and parity-harness workflows. For file submission/navigation questions, see [Navigation and file context](./docs/navigation-file-context.md). For local OpenAI-compatible models and offline skill installs, see [Local OpenAI-compatible providers and skills setup](./docs/local-openai-compatible-providers.md). Windows users can jump to the PowerShell-first [Windows install and release quickstart](./docs/windows-install-release.md). Make `claw doctor` your first health check after building, use [`rust/README.md`](./rust/README.md) for crate-level details, read [`PARITY.md`](./PARITY.md) for the current Rust-port checkpoint, and see [`docs/container.md`](./docs/container.md) for the container-first workflow.
|
||||
>
|
||||
> **ACP / Zed status:** `claw-code` does not ship an ACP/Zed daemon entrypoint yet. Run `claw acp` (or `claw --acp`) for the current status instead of guessing from source layout; `claw acp serve` is currently a discoverability alias only, and real ACP support remains tracked separately in `ROADMAP.md`.
|
||||
|
||||
## Documentation Overview
|
||||
|
||||
| Document | What it covers |
|
||||
|---|---|
|
||||
| [`USAGE.md`](./USAGE.md) | Build, auth, CLI reference, sessions, parity-harness workflows |
|
||||
| [`docs/CONFIGURATION.md`](./docs/CONFIGURATION.md) | All env vars, `settings.json` keys, validation, and migration notes |
|
||||
| [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) | High-level crate/subsystem map and design rationale |
|
||||
| [`docs/API_REFERENCE.md`](./docs/API_REFERENCE.md) | JSON protocol, output envelopes, exit codes |
|
||||
| [`docs/SUPPORTED_PROVIDERS.md`](./docs/SUPPORTED_PROVIDERS.md) | Provider selection, auth, and model compatibility |
|
||||
| [`docs/MODEL_COMPATIBILITY.md`](./docs/MODEL_COMPATIBILITY.md) | Per-model capability matrix |
|
||||
| [`docs/container.md`](./docs/container.md) | Container-first workflow and Docker setup |
|
||||
| [`ERROR_HANDLING.md`](./ERROR_HANDLING.md) | Unified error-handling pattern for orchestration code |
|
||||
| [`TROUBLESHOOTING.md`](./TROUBLESHOOTING.md) | Common failures and recovery steps |
|
||||
| [`PARITY.md`](./PARITY.md) | Rust-port parity status and migration notes |
|
||||
| [`ROADMAP.md`](./ROADMAP.md) | Active roadmap, pinpoints #241–#311, and cleanup backlog |
|
||||
| [`CONTRIBUTING.md`](./CONTRIBUTING.md) | Contribution guidelines and PR workflow |
|
||||
| [`CHANGELOG.md`](./CHANGELOG.md) | Release history |
|
||||
| [`PHILOSOPHY.md`](./PHILOSOPHY.md) | Project intent and system-design framing |
|
||||
| [`SCHEMAS.md`](./SCHEMAS.md) | JSON protocol contract (Python harness reference) |
|
||||
|
||||
> **New users:** start with [`USAGE.md`](./USAGE.md) → run `claw doctor` → check [`docs/CONFIGURATION.md`](./docs/CONFIGURATION.md) for settings → [`TROUBLESHOOTING.md`](./TROUBLESHOOTING.md) if stuck.
|
||||
> **ACP / Zed status:** `claw-code` does not ship an ACP/Zed daemon or JSON-RPC entrypoint yet. Run `claw acp` (or `claw --acp`) for the current status instead of guessing from source layout; `claw acp serve` is currently a discoverability alias only, returns status with exit code 0, and real ACP support remains tracked separately in `ROADMAP.md`. For the public JSON contract, see [`docs/g011-acp-json-rpc-status-contract.md`](./docs/g011-acp-json-rpc-status-contract.md).
|
||||
|
||||
## Current repository shape
|
||||
|
||||
- **`rust/`** — canonical Rust workspace and the `claw` CLI binary
|
||||
- **`USAGE.md`** — task-oriented usage guide for the current product surface
|
||||
- **`docs/`** — full documentation suite (configuration, architecture, API reference, providers, container workflow)
|
||||
- **`ERROR_HANDLING.md`** — unified error-handling pattern for orchestration code
|
||||
- **`PARITY.md`** — Rust-port parity status and migration notes
|
||||
- **`ROADMAP.md`** — active roadmap and cleanup backlog
|
||||
- **`PHILOSOPHY.md`** — project intent and system-design framing
|
||||
- **`SCHEMAS.md`** — JSON protocol contract (Python harness reference)
|
||||
- **`src/` + `tests/`** — companion Python/reference workspace and audit helpers; not the primary runtime surface
|
||||
|
||||
## Quick start
|
||||
@@ -125,6 +100,8 @@ export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
.\target\debug\claw.exe prompt "say hello"
|
||||
```
|
||||
|
||||
For release ZIPs, PATH setup, provider switching, and notification smoke checks, see [`docs/windows-install-release.md`](./docs/windows-install-release.md).
|
||||
|
||||
**Git Bash / WSL** are optional alternatives, not requirements. If you prefer bash-style paths (`/c/Users/you/...` instead of `C:\Users\you\...`), Git Bash (ships with Git for Windows) works well. In Git Bash, the `MINGW64` prompt is expected and normal — not a broken install.
|
||||
|
||||
## Post-build: locate the binary and verify
|
||||
@@ -159,6 +136,18 @@ Test the binary directly using its path:
|
||||
.\rust\target\debug\claw.exe doctor
|
||||
```
|
||||
|
||||
PowerShell smoke commands that do not require live credentials:
|
||||
|
||||
```powershell
|
||||
$env:CLAW_CONFIG_HOME = Join-Path $env:TEMP "claw config home"
|
||||
New-Item -ItemType Directory -Force -Path $env:CLAW_CONFIG_HOME | Out-Null
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY, Env:\ANTHROPIC_AUTH_TOKEN, Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
.\rust\target\debug\claw.exe help
|
||||
.\rust\target\debug\claw.exe status
|
||||
.\rust\target\debug\claw.exe config env
|
||||
.\rust\target\debug\claw.exe doctor
|
||||
```
|
||||
|
||||
If these commands succeed, the build is working. `claw doctor` is your first health check — it validates your API key, model access, and tool configuration.
|
||||
|
||||
### Optional: Add to PATH
|
||||
@@ -217,12 +206,17 @@ cargo test --workspace
|
||||
## Documentation map
|
||||
|
||||
- [`USAGE.md`](./USAGE.md) — quick commands, auth, sessions, config, parity harness
|
||||
- [`docs/navigation-file-context.md`](./docs/navigation-file-context.md) — terminal navigation, scrollback, `@path` file context, attachments, and secret-safety guidance
|
||||
- [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md) — Ollama/llama.cpp/vLLM setup, Claw multi-provider positioning, and local skills install checks
|
||||
- [`docs/windows-install-release.md`](./docs/windows-install-release.md) — PowerShell-first install, release artifact, provider switching, and Windows/WSL notification smoke paths
|
||||
- [`rust/README.md`](./rust/README.md) — crate map, CLI surface, features, workspace layout
|
||||
- [`PARITY.md`](./PARITY.md) — parity status for the Rust port
|
||||
- [`rust/MOCK_PARITY_HARNESS.md`](./rust/MOCK_PARITY_HARNESS.md) — deterministic mock-service harness details
|
||||
- [`ROADMAP.md`](./ROADMAP.md) — active roadmap and open cleanup work
|
||||
- [`CHANGELOG.md`](./CHANGELOG.md) — history of notable changes by dogfood cycle
|
||||
- [`docs/g004-events-reports-contract.md`](./docs/g004-events-reports-contract.md) — Stream 2 lane event/report contract guidance for consumers
|
||||
- [`PHILOSOPHY.md`](./PHILOSOPHY.md) — why the project exists and how it is operated
|
||||
- [`CONTRIBUTING.md`](./CONTRIBUTING.md), [`SECURITY.md`](./SECURITY.md), [`SUPPORT.md`](./SUPPORT.md), and [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md) — contribution, vulnerability-reporting, support, and community policies
|
||||
- [`LICENSE`](./LICENSE) — MIT license for this repository
|
||||
|
||||
## Ecosystem
|
||||
|
||||
@@ -234,17 +228,6 @@ Claw Code is built in the open alongside the broader UltraWorkers toolchain:
|
||||
- [oh-my-codex](https://github.com/Yeachan-Heo/oh-my-codex)
|
||||
- [UltraWorkers Discord](https://discord.gg/5TUQKqFWd)
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Before filing an issue or pull request:
|
||||
|
||||
- **Troubleshooting:** See [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) for common issues and recovery steps
|
||||
- **Supported providers:** See [docs/SUPPORTED_PROVIDERS.md](./docs/SUPPORTED_PROVIDERS.md)
|
||||
- **For security issues:** See [SECURITY.md](./SECURITY.md)
|
||||
- **For bug reports / features:** Check [ROADMAP.md](./ROADMAP.md) to see if it's already pinpointed
|
||||
- **How to file a pinpoint:** See [CONTRIBUTING.md](./CONTRIBUTING.md) and the [Pinpoint Filing Guide](./docs/PINPOINT_FILING_GUIDE.md)
|
||||
- **Issue templates:** Use [.github/ISSUE_TEMPLATE/pinpoint.md](./.github/ISSUE_TEMPLATE/pinpoint.md)
|
||||
|
||||
## Ownership / affiliation disclaimer
|
||||
|
||||
- This repository does **not** claim ownership of the original Claude Code source material.
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
# Review Dashboard — claw-code
|
||||
|
||||
**Last updated:** 2026-04-23 03:34 Seoul
|
||||
**Queue state:** 14 review-ready branches
|
||||
**Main HEAD:** `f18f45c` (ROADMAP #161 filed)
|
||||
|
||||
This is an integration support artifact (per cycle #64 doctrine). Its purpose: let reviewers see all queued branches, cluster membership, and merge priorities without re-deriving from git log.
|
||||
|
||||
---
|
||||
|
||||
## At-A-Glance
|
||||
|
||||
| Priority | Cluster | Branches | Complexity | Status |
|
||||
|---|---|---|---|---|
|
||||
| P0 | Typed-error threading | #248, #249, #251 | S–M | Merge-ready |
|
||||
| P1 | Diagnostic-strictness | #122, #122b | S | Merge-ready |
|
||||
| P1 | Help-parity | #130b-#130e | S each | Merge-ready (batch) |
|
||||
| P2 | Suffix-guard | #152-init, #152-bootstrap-plan | XS each | Merge-ready (batch) |
|
||||
| P2 | Verb-classification | #160 | S | Merge-ready (just shipped) |
|
||||
| P3 | Doc truthfulness | docs/parity-update | XS | Merge-ready |
|
||||
|
||||
**Suggested merge order:** P0 → P1 → P2 → P3. Within P0, start with #249 (smallest diff).
|
||||
|
||||
---
|
||||
|
||||
## Detailed Branch Inventory
|
||||
|
||||
### P0: Typed-Error Threading (3 branches)
|
||||
|
||||
#### `feat/jobdori-249-resumed-slash-kind` — **SMALLEST. START HERE.**
|
||||
- **Commit:** `eb4b1eb`
|
||||
- **Diff:** 61 lines in `rust/crates/rusty-claude-cli/src/main.rs`
|
||||
- **Scope:** Two Err arms in `resume_session()` at lines 2745, 2782 now emit `kind` + `hint`
|
||||
- **Cluster:** Completes #247 parent's typed-error family
|
||||
- **Tests:** 181 binary tests pass (no regressions)
|
||||
- **Reviewer checklist:** see `/tmp/pr-summary-249.md`
|
||||
- **Expected merge time:** ~5 minutes
|
||||
|
||||
#### `feat/jobdori-248-unknown-verb-option-classify`
|
||||
- **Commit:** `6c09172`
|
||||
- **Scope:** Unknown verb + option classifier family
|
||||
- **Cluster:** #247 parent's typed-error family (sibling of #249)
|
||||
|
||||
#### `feat/jobdori-251-session-dispatch`
|
||||
- **Commit:** `dc274a0`
|
||||
- **Scope:** Intercepts session-management verbs (`list-sessions`, `load-session`, `delete-session`, `flush-transcript`) at top-level parser
|
||||
- **Cluster:** #247 parent's typed-error family
|
||||
- **Note:** Larger change than #248/#249 — prefer merging those first
|
||||
|
||||
### P1: Diagnostic-Strictness (2 branches)
|
||||
|
||||
#### `feat/jobdori-122-doctor-stale-base`
|
||||
- **Commit:** `5bb9eba`
|
||||
- **Scope:** `claw doctor` now warns on stale-base (same check as prompt preflight)
|
||||
- **Cluster:** Diagnostic surfaces reflect runtime reality (cycle #57 principle)
|
||||
|
||||
#### `feat/jobdori-122b-doctor-broad-cwd`
|
||||
- **Commit:** `0aa0d3f`
|
||||
- **Scope:** `claw doctor` now warns when cwd is broad path (home/root)
|
||||
- **Cluster:** Same as #122 (direct sibling)
|
||||
- **Batch suggestion:** Review together with #122
|
||||
|
||||
### P1: Help-Parity (4 branches, batch-reviewable)
|
||||
|
||||
All four implement uniform `--help` flag handling. Related by fix locus (help-topic routing).
|
||||
|
||||
#### `feat/jobdori-130b-filesystem-context`
|
||||
- **Commit:** `d49a75c`
|
||||
- **Scope:** Filesystem I/O errors enriched with operation + path context
|
||||
|
||||
#### `feat/jobdori-130c-diff-help`
|
||||
- **Commit:** `83f744a`
|
||||
- **Scope:** `claw diff --help` routes to help topic
|
||||
|
||||
#### `feat/jobdori-130d-config-help`
|
||||
- **Commit:** `19638a0`
|
||||
- **Scope:** `claw config --help` routes to help topic
|
||||
|
||||
#### `feat/jobdori-130e-dispatch-help` + `feat/jobdori-130e-surface-help`
|
||||
- **Commits:** `0ca0344`, `9dd7e79`
|
||||
- **Scope:** Category A (dispatch-order) + Category B (surface) help-anomaly fixes from systematic sweep
|
||||
- **Batch suggestion:** Review #130c, #130d, #130e-dispatch, #130e-surface as one unit — all use same pattern (add help flag guard before action)
|
||||
|
||||
### P2: Suffix-Guard (2 branches, batch-reviewable)
|
||||
|
||||
#### `feat/jobdori-152-init-suffix-guard`
|
||||
- **Commit:** `860f285`
|
||||
- **Scope:** `claw init` rejects trailing args
|
||||
- **Cluster:** Uniform no-arg verb suffix guards
|
||||
|
||||
#### `feat/jobdori-152-bootstrap-plan-suffix-guard`
|
||||
- **Commit:** `3a533ce`
|
||||
- **Scope:** `claw bootstrap-plan` rejects trailing args
|
||||
- **Cluster:** Same as above (direct sibling)
|
||||
- **Batch suggestion:** Review together
|
||||
|
||||
### P2: Verb-Classification (1 branch, just shipped cycle #63)
|
||||
|
||||
#### `feat/jobdori-160-verb-classification`
|
||||
- **Commit:** `5538934`
|
||||
- **Scope:** Reserved-semantic verbs (resume, compact, memory, commit, pr, issue, bughunter) with positional args now emit slash-command guidance
|
||||
- **Cluster:** Sibling of #251 (dispatch leak family), applied to promptable/reserved split
|
||||
- **Design closure note:** Investigation in cycle #61 revealed verb-classification was the actual need; cycle #63 implemented the class table
|
||||
|
||||
### P3: Doc Truthfulness (1 branch, just shipped cycle #64)
|
||||
|
||||
#### `docs/parity-update-2026-04-23`
|
||||
- **Commit:** `92a79b5`
|
||||
- **Scope:** PARITY.md stats refreshed (Rust LOC +66%, Test LOC +76%, Commits +235% since 2026-04-03)
|
||||
- **Risk:** Near-zero (4-line diff, doc-only)
|
||||
- **Merge time:** ~1 minute
|
||||
|
||||
---
|
||||
|
||||
## Batch Review Patterns
|
||||
|
||||
For reviewer efficiency, these groups share the same fix-locus or pattern:
|
||||
|
||||
| Batch | Branches | Shared pattern |
|
||||
|---|---|---|
|
||||
| Help-parity bundle | #130c, #130d, #130e-dispatch, #130e-surface | All add help-flag guard before action in dispatch |
|
||||
| Suffix-guard bundle | #152-init, #152-bootstrap-plan | Both add `rest.len() > 1` check to no-arg verbs |
|
||||
| Diagnostic-strictness bundle | #122, #122b | Both extend `check_workspace_health()` with new preflights |
|
||||
| Typed-error bundle | #248, #249, #251 | All thread `classify_error_kind` + `split_error_hint` into specific Err arms |
|
||||
|
||||
If reviewer has limited time, batch review saves context switches.
|
||||
|
||||
---
|
||||
|
||||
## Review Friction Map
|
||||
|
||||
**Lowest friction (safe start):**
|
||||
- docs/parity-update (4 lines, doc-only)
|
||||
- #249 (61 lines, 2 Err arms, 181 tests pass)
|
||||
- #160 (23 lines, new helper + pre-check)
|
||||
|
||||
**Medium friction:**
|
||||
- #122, #122b (each ~100 lines, diagnostic extensions)
|
||||
- #248 (classifier family)
|
||||
- #152-* branches (XS each)
|
||||
|
||||
**Highest friction:**
|
||||
- #251 (broader parser changes, multi-verb coverage)
|
||||
- #130e bundle (help-parity systematic sweep)
|
||||
|
||||
---
|
||||
|
||||
## Open Pinpoints Awaiting Implementation
|
||||
|
||||
| # | Title | Priority | Est. diff | Notes |
|
||||
|---|---|---|---|---|
|
||||
| #157 | Auth remediation registry | S-M | 50-80 lines | Cycle #59 audit pre-fill |
|
||||
| #158 | Hook validation at worker boot | S | 30-50 lines | Cycle #59 audit pre-fill |
|
||||
| #159 | Plugin manifest validation at worker boot | S | 30-50 lines | Cycle #59 audit pre-fill |
|
||||
| #161 | Stale Git SHA in worktree builds | S | ~15 lines in build.rs | Cycle #65 just filed |
|
||||
|
||||
None of these should be implemented while current queue is 14. Prioritize merging queue first.
|
||||
|
||||
---
|
||||
|
||||
## Merge Throughput Notes
|
||||
|
||||
**Target throughput:** 2-3 branches per review session. At current cycle velocity (cycles #39–#65 = 27 cycles in ~3 hours), 2-3 merges unblock:
|
||||
- 3+ cluster closures (typed-error, diagnostic-strictness, help-parity)
|
||||
- 1 doctrine loop closure (verb-classification → #160)
|
||||
- 1 doc freshness (PARITY.md)
|
||||
|
||||
**Post-merge expected state:** ~10 branches remaining, queue shifts from saturated (14) to manageable (10), velocity cycles can resume in safe zone.
|
||||
|
||||
---
|
||||
|
||||
## For The Reviewer
|
||||
|
||||
**Reviewing checklist (per-branch):**
|
||||
- [ ] Diff matches pinpoint description
|
||||
- [ ] Tests pass (cite count: should be 181+ for branches that touched main.rs)
|
||||
- [ ] Backward compatibility verified (check-list in commit message)
|
||||
- [ ] No related cluster branches yet to land (check cluster column above)
|
||||
|
||||
**Reviewer shortcut for #249** (recommended first-merge):
|
||||
```bash
|
||||
cd /tmp/jobdori-249
|
||||
git log --oneline -1 # eb4b1eb
|
||||
git diff main..HEAD -- rust/crates/rusty-claude-cli/src/main.rs | head -50
|
||||
```
|
||||
|
||||
Or skip straight to: `/tmp/pr-summary-249.md` (pre-prepared PR-ready artifact).
|
||||
|
||||
---
|
||||
|
||||
**Dashboard source:** Cycle #66 (2026-04-23 03:34 Seoul). Updates should be re-run when branches merge or new pinpoints land.
|
||||
411
ROADMAP.md
411
ROADMAP.md
@@ -1,91 +1,5 @@
|
||||
# Clawable Coding Harness Roadmap
|
||||
|
||||
## Extended Dogfood Audit Summary (Cycles #388-#415)
|
||||
|
||||
**Overview:** This branch underwent an extended discovery audit identifying architectural gaps via live dogfood cycling. 44 pinpoints filed across all phases (#200-#289); 9 concrete real/docs fixes shipped; pre-merge threshold exceeded (target 50+, achieved 65+ across all phases).
|
||||
|
||||
**Key Clusters Identified:**
|
||||
- **session-continuity/auto-compaction (3-deep):** #287 (timing-reactive), #288 (failure-envelope), #289 (manual-skip-reason)
|
||||
- **parallel-agent-lifecycle:** #286 (detached-thread-no-heartbeat)
|
||||
- **provider-config-source-of-truth:** #285 (declarative-providers/models/websearch missing)
|
||||
- **slash-command-contract:** #284 (ultraplan empty-shell)
|
||||
- [+ 10+ additional clusters across all phases]
|
||||
|
||||
**Real Fixes Shipped:**
|
||||
1. #256 — Anthropic tool-result request ordering (pre-audit)
|
||||
2. #122b — claw doctor broad-path warning
|
||||
3. #160 — reserved-semantic-verb slash-command guidance
|
||||
4. root LICENSE (MIT, resolves @Sigrid Jin license ambiguity)
|
||||
5. CONTRIBUTING.md (codifies pinpoint filing format)
|
||||
6. ISSUE_TEMPLATE/pinpoint.md (discoverable format template)
|
||||
7. live-counter drift fix (CONTRIBUTING.md link-to-ROADMAP)
|
||||
8. SECURITY.md (responsible-disclosure stub)
|
||||
9. README.md contributing section (unified doc nav)
|
||||
|
||||
**User-Sourced Feedback Integrated:**
|
||||
- @Sigrid Jin: license verification, ultraplan functionality, provider-config source-of-truth → converted to pinpoints #284, #285
|
||||
|
||||
**Branch State:**
|
||||
- Primary: `feat/jobdori-168c-emission-routing` @ HEAD `0f01faa`
|
||||
- All pinpoints filed to ROADMAP.md; no pending work; merge-eligible pending Phase 0 blockers (GitHub OAuth, cargo fmt, clawcode-human approval)
|
||||
- 564 tests pass; zero regressions
|
||||
|
||||
**Next Phase:** Phase 0 merge → Phase A-F post-merge implementation (18-31 cycles estimated, 6-15 real fixes target)
|
||||
|
||||
---
|
||||
|
||||
## Pinpoint Cluster Index
|
||||
|
||||
For navigation. Click cluster name to jump to first member. Pinpoints can belong to multiple clusters.
|
||||
|
||||
### Auto-compaction (4-deep)
|
||||
- #283 — Compaction threshold env-only, no settings.json key
|
||||
- #287 — Reactive-after-success, not preflight-before-request (CRITICAL)
|
||||
- #288 — Failure JSON envelope omits compaction diagnostics
|
||||
- #289 — Manual `/compact` skip-reason flattened to "below threshold"
|
||||
|
||||
### Transport / Provider Resilience (new, post-#416)
|
||||
- #266 — Typed-error-kind taxonomy
|
||||
- #285 — Provider/model/websearch declarative source-of-truth
|
||||
- #290 — Upstream stream-init failures bypass typed envelope
|
||||
- #291 — No repeat-failure detection / circuit-breaker
|
||||
|
||||
### Provider Infrastructure
|
||||
- #245 — Declarative providers config
|
||||
- #246 — Backend swap
|
||||
- #285 — Source-of-truth (overlap with Transport cluster)
|
||||
|
||||
### Tool Lifecycle / Hooks
|
||||
- #254 — MCP refresh
|
||||
- #268 — Tool-rendering parity
|
||||
- #274 — Hook-execution-event envelope
|
||||
- #280 — Hook event tap
|
||||
- #286 — Parallel agent detached-thread (no JoinHandle)
|
||||
|
||||
### CLI Dispatch
|
||||
- #262 — `--max-turns` spec
|
||||
- #267 — `--cwd` runtime fix
|
||||
- #272 — Position-independent parsing
|
||||
- #282 — env-vs-config consolidation
|
||||
- #283 — Compaction threshold (overlap with Auto-compaction)
|
||||
|
||||
### Persistence / Migration
|
||||
- #278 — Version-comparison
|
||||
- #279 — Unknown-field policy
|
||||
|
||||
### Provenance Consolidation
|
||||
- #259 — Unified provenance struct
|
||||
- #271 — Freshness assertions
|
||||
- #273 — Source-of-truth guards
|
||||
- #275 — Source-of-truth checks
|
||||
|
||||
### Slash-command Contract
|
||||
- #284 — `/ultraplan` empty-shell
|
||||
|
||||
For full list of all 46+ pinpoints (#200-#291), see the pinpoint sections below.
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Turn claw-code into the most **clawable** coding harness:
|
||||
@@ -973,52 +887,6 @@ The #134/#135 session-identity work tightened model-syntax validation but the te
|
||||
- `cargo test --workspace` passes with 0 failures on the `feat/134-135-session-identity` branch
|
||||
- No regression on the 162 tests currently passing
|
||||
|
||||
### 196. Local branch namespace accumulation — no branch-lifecycle cleanup, no stale-branch visibility in doctor
|
||||
|
||||
**Filed:** 2026-04-23 from dogfood cycle check (Jobdori).
|
||||
|
||||
**Problem:** `git branch` on the live `claw-code` workspace shows **123 local branches**, the majority of which are stale batch-lane branches (`feat/b3-*`, `feat/b4-*`, `feat/b5-*`, `feat/b6-*`, `feat/b7-*`, plus dozens of `feat/jobdori-*` fix branches). There is no product surface that:
|
||||
- counts or reports stale local branches in `claw doctor` or `claw state` output
|
||||
- enforces a branch cleanup lifecycle at post-batch-complete or post-merge points
|
||||
- emits a `branch_namespace_degraded` warning when stale count exceeds a threshold
|
||||
|
||||
This mirrors the prunable-worktree accumulation gap (#194/#195) but at the branch layer. Git operations slow down, `git branch` output is unreadable for monitoring, and each new dogfood batch silently extends the debt.
|
||||
|
||||
**Fix shape:**
|
||||
- Add `branch_health` section to `claw doctor --output-format json`: emit `stale_merged_count`, `stale_unmerged_count`, `active_count`, `total_count`
|
||||
- Emit `branch_namespace_degraded` advisory when stale-merged branch count exceeds threshold (suggest 30)
|
||||
- Add `claw branch prune` (or `claw doctor --prune-branches`) action that deletes merged local branches and reports the delta
|
||||
- Wire a post-batch-complete hook to auto-delete the local batch lane branch after confirmed merge
|
||||
|
||||
**Acceptance:**
|
||||
- `claw doctor --output-format json` includes `{branch_health: {stale_merged: N, active: N, total: N}}`
|
||||
- `claw doctor` warns when stale branch count > 30: `"N stale merged branches; run 'claw branch prune' to reclaim"`
|
||||
- A single `claw branch prune` reduces stale-merged count to 0 and reports the delta
|
||||
- Post-batch-complete lifecycle deletes the batch branch so N+1 cycle starts clean
|
||||
|
||||
### 194. Prunable-worktree accumulation — no gate, no `claw state` visibility, no auto-prune lifecycle contract
|
||||
|
||||
**Filed:** 2026-04-23 from dogfood cycle #130 observation (Jobdori).
|
||||
|
||||
**Problem:** `git worktree list` on the live `claw-code` workspace currently reports **109 prunable worktrees** (batch cycles b3–b8, stale test forks, detached b8-trust-00 through b8-trust-09). There is no product surface that:
|
||||
- reports prunable-worktree count in `claw doctor` or `claw state` output
|
||||
- runs `git worktree prune` at a defined lifecycle point (post-batch-complete, post-lane-close, or on-doctor)
|
||||
- blocks new batch-lane spawns when prunable count exceeds a safe threshold
|
||||
|
||||
This makes `git worktree list` effectively unreadable for active monitoring, wastes inode/ref budget, and silently accumulates debt each dogfood cycle. Claws currently have no signal that the worktree namespace is degraded.
|
||||
|
||||
**Fix shape:**
|
||||
- Add `worktree_health` section to `claw doctor --output-format json`: emit `prunable_count`, `detached_count`, `active_count`
|
||||
- Add a `claw worktree prune` (or `claw doctor --prune-worktrees`) action that calls `git worktree prune` and reports what was removed
|
||||
- Integrate a lightweight prunable-count check into `LanePreflight` (§3.5): emit `worktree_namespace_degraded` warning when prunable count exceeds threshold (suggest 20)
|
||||
- Distinguish between `prunable` (safely removable) and `detached HEAD` (may need explicit cleanup)
|
||||
|
||||
**Acceptance:**
|
||||
- `claw doctor --output-format json` includes `{worktree_health: {prunable: N, detached: N, active: N}}`
|
||||
- `claw doctor` warns when prunable count > 20: `"109 prunable worktrees found; run 'claw worktree prune' to reclaim"`
|
||||
- Batch-lane spawn includes worktree-namespace preflight so cycle N+1 does not silently inherit N's prunable debt
|
||||
- A single `claw worktree prune` call reduces the count to 0 prunable and reports the delta
|
||||
|
||||
### 133. Blocked-state subphase contract (was §6.5)
|
||||
**Filed:** 2026-04-20 from dogfood cycle — previous cycle identified §4.44.5 provenance gap, this cycle targets §6.5 implementation.
|
||||
|
||||
@@ -4866,7 +4734,7 @@ ear], /color [scheme], /effort [low|medium|high], /fast, /summary, /tag [label],
|
||||
|
||||
**Source.** Jobdori dogfood 2026-04-18 against `/tmp/cdFF2` on main HEAD `b56841c` in response to Clawhip pinpoint nudge at `1495023618529300580`. Joins **Silent-flag / documented-but-unenforced** — section argument silently ignored. Joins **Truth-audit** — help promises section-specific inspection that doesn't exist. Joins **Dispatch-collapse family**: #111 (2-way) + #118 (3-way) + **#126** (4-way). Natural bundle: **#111 + #118 + #126** — dispatch-collapse trio: complete parser-dispatch-collapse audit across slash commands. Session tally: ROADMAP #126.
|
||||
|
||||
127. **[CLOSED 2026-04-20]** **`claw <subcommand> --json` and `claw <subcommand> <ANY-EXTRA-ARG>` silently fall through to LLM Prompt dispatch — every diagnostic verb (`doctor`, `status`, `sandbox`, `skills`, `version`, `help`) accepts the documented `--output-format json` global only BEFORE the subcommand. The natural shape `claw doctor --json` parses as: subcommand=`doctor` is consumed, then `--json` becomes prompt text, the parser dispatches to `CliAction::Prompt { prompt: "--json" }`, the prompt path demands Anthropic credentials, and a fresh box with no auth fails hard with exit=1. Same for `claw doctor --garbageflag`, `claw doctor garbage args here`, `claw status --json`, `claw skills --json`, etc. The text-mode form `claw doctor` works fine without auth (it's a pure local diagnostic), so this is a pure CLI-surface failure that breaks every observability tool that pipes JSON. README.md says "`claw doctor` should be your first health check" — but any claw, CI step, or monitoring tool that adds `--json` to that exact suggested command gets a credential-required error instead of structured output** — dogfooded 2026-04-20 on main HEAD `7370546` from `/tmp/claw-dogfood` (no `.git`, no `.claw.json`, all `ANTHROPIC_*` / `OPENAI_*` env vars unset via `env -i`).
|
||||
127. **`claw <subcommand> --json` and `claw <subcommand> <ANY-EXTRA-ARG>` silently fall through to LLM Prompt dispatch — every diagnostic verb (`doctor`, `status`, `sandbox`, `skills`, `version`, `help`) accepts the documented `--output-format json` global only BEFORE the subcommand. The natural shape `claw doctor --json` parses as: subcommand=`doctor` is consumed, then `--json` becomes prompt text, the parser dispatches to `CliAction::Prompt { prompt: "--json" }`, the prompt path demands Anthropic credentials, and a fresh box with no auth fails hard with exit=1. Same for `claw doctor --garbageflag`, `claw doctor garbage args here`, `claw status --json`, `claw skills --json`, etc. The text-mode form `claw doctor` works fine without auth (it's a pure local diagnostic), so this is a pure CLI-surface failure that breaks every observability tool that pipes JSON. README.md says "`claw doctor` should be your first health check" — but any claw, CI step, or monitoring tool that adds `--json` to that exact suggested command gets a credential-required error instead of structured output** — dogfooded 2026-04-20 on main HEAD `7370546` from `/tmp/claw-dogfood` (no `.git`, no `.claw.json`, all `ANTHROPIC_*` / `OPENAI_*` env vars unset via `env -i`).
|
||||
|
||||
**Concrete repro.**
|
||||
```
|
||||
@@ -4962,32 +4830,6 @@ ear], /color [scheme], /effort [low|medium|high], /fast, /summary, /tag [label],
|
||||
|
||||
**Source.** Jobdori dogfood 2026-04-20 against `/tmp/claw-dogfood` (env-cleaned, no git, no config) on main HEAD `7370546` in response to Clawhip pinpoint nudge at `1495620050424434758`. Joins **Silent-flag / documented-but-unenforced** (#96–#101, #104, #108, #111, #115, #116, #117, #118, #119, #121, #122, #123, #124, #126) as 18th — `--json` silently swallowed into Prompt dispatch instead of being recognized or rejected. Joins **Parser-level trust gap quintet** (#108, #117, #119, #122, **#127**) as 5th — same `_other => Prompt` fall-through arm, fifth distinct entry case (#108 = typoed verb, #117 = `-p` greedy, #119 = bare slash + arg, #122 = `--base-commit` greedy, **#127 = valid verb + unrecognized suffix arg**). Joins **Cred-error misdirection / failure-classification gaps** as a sibling of #99 (system-prompt unvalidated) — same family of "local diagnostic verb pretends to need API creds." Joins **Truth-audit / diagnostic-integrity** (#80–#87, #89, #100, #102, #103, #105, #107, #109, #110, #112, #114, #115, #125) — `claw --help` lies about per-verb accepted flags. Joins **Parallel-entry-point asymmetry** (#91, #101, #104, #105, #108, #114, #117, #122, #123, #124) as 11th — three working forms and one broken form for the same logical intent (`--json` doctor output). Joins **Claude Code migration parity** (#103, #109, #116) as 4th — Claude Code's `--json` convention shorthand is unrecognized in claw-code's verb-suffix position; users migrating get cred errors instead. Cross-cluster with **README/USAGE doc-vs-implementation gap** — README explicitly recommends `claw doctor` as the first health check; the natural JSON form of that exact command is broken. Natural bundle: **#108 + #117 + #119 + #122 + #127** — parser-level trust gap quintet: complete `_other => Prompt` fall-through audit (typoed verb + greedy `-p` + bare slash-verb + greedy `--base-commit` + valid verb + unrecognized suffix). Also **#99 + #127** — local-diagnostic cred-error misdirection pair: `system-prompt` and verb-suffix `--json` both pretend to need creds for pure-local operations. Also **#126 + #127** — diagnostic-verb surface integrity pair: `/config` section args ignored (#126) + verb-suffix args silently mis-dispatched (#127). Session tally: ROADMAP #127.
|
||||
|
||||
**Closure (2026-04-20, verified 2026-04-22).** Fixed by two commits on main:
|
||||
- `a3270db fix: #127 reject unrecognized suffix args for diagnostic verbs` — rejects `--json` and other unknown suffix args at parse time rather than falling through to Prompt dispatch
|
||||
- `79352a2 feat: #152 — hint --output-format json when user types --json on diagnostic verbs` — adds "did you mean `--output-format json`?" suggestion
|
||||
|
||||
Re-verified on main HEAD `b903e16` (2026-04-22 cycle #32):
|
||||
```
|
||||
$ claw doctor --json
|
||||
[error-kind: cli_parse]
|
||||
error: unrecognized argument `--json` for subcommand `doctor`
|
||||
Did you mean `--output-format json`?
|
||||
Run `claw --help` for usage.
|
||||
|
||||
$ claw doctor garbage
|
||||
error: unrecognized argument `garbage` for subcommand `doctor`
|
||||
|
||||
$ claw doctor --unknown-flag
|
||||
error: unrecognized argument `--unknown-flag` for subcommand `doctor`
|
||||
|
||||
$ claw doctor --output-format json
|
||||
{ "checks": [...] } # works as documented canonical form
|
||||
```
|
||||
|
||||
Stale in-flight branches `feat/jobdori-127-clean` and `feat/jobdori-127-verb-suffix-flags` are **obsolete** — their fix was superseded by `a3270db` + `79352a2` on main. Branches contain an attached large-scope refactor that was never landed. Recommend deletion after closure confirmation.
|
||||
|
||||
Cross-cluster impact post-closure: parser-level trust gap quintet **#108 + #117 + #119 + #122 + #127** now 5/5 closed. `_other => Prompt` fall-through audit complete.
|
||||
|
||||
128. **[CLOSED 2026-04-21]** **`claw --model <malformed>` (spaces, empty string, special chars, invalid provider/model syntax) silently falls through to API-layer cred error instead of rejecting at parse time** — dogfooded 2026-04-20 on main HEAD `d284ef7` from a fresh environment (no config, no auth). The `--model` flag accepts any string without syntactic validation: spaces (`claw --model "bad model"`), empty strings (`claw --model ""`), special characters (`claw --model "@invalid"`), non-existent provider/model combinations all parse successfully. The malformed model string then flows into the runtime's provider-detection layer, which silently accepts it as Anthropic fallback or passes it to an API layer that fails with `missing Anthropic credentials` (misdirection) rather than a clear "invalid model syntax" error at parse time. With API credentials configured, a malformed model string gets sent to the API, billing tokens against a request that should have failed client-side.
|
||||
|
||||
**Closure (2026-04-21):** Re-verified on main HEAD `4cb8fa0`. All cases now rejected at parse time:
|
||||
@@ -5076,7 +4918,7 @@ ear], /color [scheme], /effort [low|medium|high], /fast, /summary, /tag [label],
|
||||
|
||||
**Source.** Jobdori dogfood 2026-04-20 against `/tmp/claw-mcp-test` (env-cleaned, working `mcpServers.everything = npx -y @modelcontextprotocol/server-everything`) on main HEAD `8122029` in response to Clawhip dogfood nudge / 10-min cron. Joins **MCP lifecycle gap family** as runtime-side companion to **#102** — #102 catches config-time silence (no preflight, no command-exists check); #129 catches runtime-side blocking (handshake await ordered before cred check, retried silently, no deadline). Joins **Truth-audit / diagnostic-integrity** (#80–#87, #89, #100, #102, #103, #105, #107, #109, #110, #112, #114, #115, #125, #127) — the hang surfaces no events, no exit code, no signal. Joins **Auth-precondition / fail-fast ordering family** — cheap deterministic preconditions should run before expensive externally-controlled ones. Cross-cluster with **Recovery / wedge-recovery** — a misbehaved MCP server wedges every subsequent Prompt invocation; current recovery is "kill -9 the parent." Cross-cluster with **PARITY.md Lane 7 acceptance gap** — the Lane 7 merge added the bridge but didn't add startup-deadline + cred-precheck ordering, so the lane is technically merged but functionally incomplete for unattended claw use. Natural bundle: **#102 + #129** — MCP lifecycle visibility pair: config-time preflight (#102) + runtime-time deadline + cred-precheck (#129). Together they make MCP failures structurally legible from both ends. Also **#127 + #129** — Prompt-path silent-failure pair: verb-suffix args silently routed to Prompt (#127, fixed) + Prompt path silently blocks on MCP (#129). With #127 fixed, the `claw doctor --json` consumer no longer accidentally trips the #129 wedge — but the wedge still affects every legitimate Prompt invocation. Session tally: ROADMAP #129.
|
||||
|
||||
130. **[STILL OPEN — re-verified 2026-04-22 cycle #39 on main HEAD `186d42f`]** **`claw export --output <path>` filesystem errors surface raw OS errno strings with zero context — no path that failed, no operation that failed (open/write/mkdir), no structured error kind, no actionable hint, and the `--output-format json` envelope flattens everything to `{"error":"<raw errno string>","type":"error"}`. Five distinct filesystem failure modes all produce different raw errno strings but the same zero-context shape. The boilerplate `Run claw --help for usage` trailer is also misleading because these are filesystem errors, not usage errors** — dogfooded 2026-04-20 on main HEAD `d2a8341` from `/Users/yeongyu/clawd/claw-code/rust` (real session file present).
|
||||
130. **`claw export --output <path>` filesystem errors surface raw OS errno strings with zero context — no path that failed, no operation that failed (open/write/mkdir), no structured error kind, no actionable hint, and the `--output-format json` envelope flattens everything to `{"error":"<raw errno string>","type":"error"}`. Five distinct filesystem failure modes all produce different raw errno strings but the same zero-context shape. The boilerplate `Run claw --help for usage` trailer is also misleading because these are filesystem errors, not usage errors** — dogfooded 2026-04-20 on main HEAD `d2a8341` from `/Users/yeongyu/clawd/claw-code/rust` (real session file present).
|
||||
|
||||
**Concrete repro.**
|
||||
```
|
||||
@@ -5204,24 +5046,6 @@ ear], /color [scheme], /effort [low|medium|high], /fast, /summary, /tag [label],
|
||||
|
||||
**Source.** Jobdori dogfood 2026-04-20 against `/Users/yeongyu/clawd/claw-code/rust` (real session file present) on main HEAD `d2a8341` in response to Clawhip dogfood nudge / 10-min cron. Joins **Truth-audit / diagnostic-integrity** (#80–#127, #129) as 16th — error surface is incomplete by design; runtime has info that CLI boundary discards. Joins **JSON envelope asymmetry family** (#90, #91, #92, #110, #115, #116) — `{error, type}` shape is a fake envelope when the failure mode is richer than a single prose string. Joins **Claude Code migration parity** — Claude Code's error shape includes typed error kinds; claw-code's flat envelope loses information. Joins **`Run claw --help for usage` trailer-misuse** — the trailer is appended to errors that are not usage errors, which is both noise and misdirection. Natural bundle: **#90 + #91 + #92 + #130** — JSON envelope hygiene quartet. All four surface errors with insufficient structure for claws to dispatch on. Also **#121 + #130** — error-text-lies pair: hooks error names wrong thing (#121), export errno strips all context (#130). Also **Phase 2 §4 Canonical lane event schema exhibit A** — typed errors are the prerequisite for structured lane events. Session tally: ROADMAP #130.
|
||||
|
||||
**Re-verification (2026-04-22 cycle #39, main HEAD `186d42f`).** All 5 failure modes still reproduce identically to the original filing 2 days later. Concrete output:
|
||||
```
|
||||
$ claw export --output /tmp/nonexistent-dir-xyz/out.md --output-format json
|
||||
{"error":"No such file or directory (os error 2)","hint":null,"kind":"unknown","type":"error"}
|
||||
$ claw export --output /bin/cantwrite.md --output-format json
|
||||
{"error":"Operation not permitted (os error 1)","hint":null,"kind":"unknown","type":"error"}
|
||||
$ claw export --output "" --output-format json
|
||||
{"error":"No such file or directory (os error 2)","hint":null,"kind":"unknown","type":"error"}
|
||||
$ claw export --output / --output-format json
|
||||
{"error":"File exists (os error 17)","hint":null,"kind":"unknown","type":"error"}
|
||||
$ claw export --output /tmp/ --output-format json
|
||||
{"error":"Is a directory (os error 21)","hint":null,"kind":"unknown","type":"error"}
|
||||
```
|
||||
|
||||
**New evidence not in original filing.** The `kind` field is set to `"unknown"` — the classifier actively chose `unknown` rather than just omitting the field. This means `classify_error_kind()` (at main.rs:~251) has no substring match for "Is a directory", "No such file", "Operation not permitted", or "File exists". The typed-error contract is thus twice-broken on this path: (a) the io::ErrorKind information is discarded at the `?` in `run_export()`, AND (b) the flat `io::Error::Display` string is then fed to a classifier that has no patterns for filesystem errno strings.
|
||||
|
||||
**Natural pairing with #247/#248/#249 classifier sweep.** Same code path as #247's classifier fix (`classify_error_kind()`), same pattern (substring-matching classifier that lacks entries for specific error strings). #247 added patterns for prompt-related parse errors. #248 WIP adds patterns for verb-qualified unknown option errors. #130's classifier-level part (adding `NotFound`/`PermissionDenied`/`IsADirectory`/`AlreadyExists` substring branches) could land in the same sweep. The deeper fix (context preservation at `run_export()`'s `?`) is a separate, larger change — context-preservation requires `anyhow::Context` threading or typed error enum, not just classifier patterns.
|
||||
|
||||
**Repro (fresh box, no ANTHROPIC_* env vars).** `claw --model "bad model" version` → exit 0, emits version JSON (silent parse). `claw --model "" version` → exit 0, same. `claw --model "foo bar/baz" prompt "test"` → exit 1, `error: missing Anthropic credentials` (malformed model silently routes to Anthropic, then cred error masquerades as root cause instead of "invalid model syntax").
|
||||
|
||||
**The gap.** (1) No upfront model syntax validation in parse_args. `--model` accepts any string. (2) Silent fallback to Anthropic when provider detection fails on malformed syntax. (3) Downstream error misdirection — cred error doesn't say "your model string was invalid, I fell back to Anthropic." (4) Token burn on invalid model at API layer — with credentials set, malformed model reaches the API, billing tokens against a 400 response that should have been rejected client-side. (5) Joins #29 (provider routing silent fallback) — both involve Anthropic fallback masking the real intent. (6) Joins truth-audit — status/version JSON report malformed model without validation. (7) Joins cred-error misdirection family (#28, #99, #127).
|
||||
@@ -5300,29 +5124,25 @@ ear], /color [scheme], /effort [low|medium|high], /fast, /summary, /tag [label],
|
||||
|
||||
## Pinpoint #136. `--compact` flag output is not machine-readable — compact turn emits plain text instead of JSON when `--output-format json` is also passed
|
||||
|
||||
**Status: ✅ CLOSED (already implemented, verified cycle #60).**
|
||||
**Gap.** `claw --compact <prompt>` runs a prompt turn with compacted output (tool-use suppressed, final assistant text only). But `run_with_output()` routes on `(output_format, compact)` with an explicit early-return match: `CliOutputFormat::Text if compact => run_prompt_compact(input)`. The `CliOutputFormat::Json` branch is never reached when `--compact` is set. Result: passing `--compact --output-format json` silently produces plain-text output — the compact flag wins and the format flag is silently ignored. No warning or error is emitted.
|
||||
|
||||
**Implementation:** The dispatch ordering in `LiveCli::run_with_output()` has the correct precedence:
|
||||
```rust
|
||||
CliOutputFormat::Json if compact => self.run_prompt_compact_json(input),
|
||||
CliOutputFormat::Text if compact => self.run_prompt_compact(input),
|
||||
CliOutputFormat::Text => self.run_turn(input),
|
||||
CliOutputFormat::Json => self.run_prompt_json(input),
|
||||
```
|
||||
**Trace path.**
|
||||
- `rust/crates/rusty-claude-cli/src/main.rs:3872-3879` — `run_with_output()` match:
|
||||
```
|
||||
CliOutputFormat::Text if compact => self.run_prompt_compact(input),
|
||||
CliOutputFormat::Text => self.run_turn(input),
|
||||
CliOutputFormat::Json => self.run_prompt_json(input),
|
||||
```
|
||||
The `Json` arm is unreachable when `compact = true` because the first arm matches first regardless of `output_format`.
|
||||
- `run_prompt_compact()` at line 3879 calls `println!("{final_text}")` — always plain text, no JSON envelope.
|
||||
- `run_prompt_json()` at line 3891 wraps output in a JSON object with `message`, `model`, `iterations`, `usage`, `tool_uses`, `tool_results`, etc.
|
||||
|
||||
`run_prompt_compact_json()` produces:
|
||||
```json
|
||||
{
|
||||
"message": "<final_assistant_text>",
|
||||
"compact": true,
|
||||
"model": "...",
|
||||
"usage": { ... }
|
||||
}
|
||||
```
|
||||
**Fix shape (~20 lines).**
|
||||
1. Add a `CliOutputFormat::Json if compact` arm (or merge compact flag into `run_prompt_json` as a parameter) that produces a JSON object with `message: <final_text>` and a `compact: true` marker. Tool-use fields remain present but empty arrays (consistent with compact semantics — tools ran but are not returned verbatim).
|
||||
2. Emit a warning or `error.kind: "flag_conflict"` if conflicting flags are passed in a way that silently wins (or document the precedence explicitly in `--help`).
|
||||
3. Regression tests: `claw --compact --output-format json <prompt>` must produce valid JSON with at minimum `{message: "...", compact: true}`.
|
||||
|
||||
**Dogfood verification (2026-04-23 cycle #60):** Tested `claw prompt "hello" --compact --output-format json` → produces valid JSON with `compact: true` marker. Error cases also JSON-wrapped (consistent with error envelope contract #247).
|
||||
|
||||
**Note:** Dispatch reordering that fixed this is not yet known to be in a review-ready branch or merged main. Verify merge status.
|
||||
**Acceptance.** An orchestrator that requests compact output for token efficiency AND machine-readable JSON gets both. Silent flag override is never a correct behavior for a tool targeting machine consumers.
|
||||
|
||||
**Blocker.** None. Additive change to existing match arms.
|
||||
|
||||
@@ -6338,24 +6158,22 @@ load_session('nonexistent') # raises FileNotFoundError with no structured error
|
||||
**Blocker.** None.
|
||||
|
||||
**Source.** Jobdori dogfood sweep 2026-04-22 08:46 KST — inspected `src/session_store.py` public API, confirmed only `save_session` + `load_session` present, no list/delete/exists surface.
|
||||
200. **Interactive MCP/tool permission prompts are invisible blockers** — **done (verified 2026-04-27):** worker boot observation now detects interactive tool permission gates such as `Allow the omx_memory MCP server to run tool "project_memory_read"?` before generic readiness/idle handling, records `tool_permission_required` status, emits a structured `ToolPermissionPrompt` payload with server/tool identity, prompt age, allow-scope capability, and prompt preview, marks readiness snapshots as blocked, and carries `tool_permission_prompt_detected` through startup timeout evidence so the classifier returns `tool_permission_required` instead of a vague stale/idle/ready outcome. Regression coverage locks both the structured prompt-gate event metadata and startup-timeout classification paths. **Original filing below.**
|
||||
Original filing (2026-04-18): the session emitted `SessionStart hook (completed)` and `UserPromptSubmit hook (completed)`, then stalled on an interactive MCP permission gate (`Allow the omx_memory MCP server to run tool "project_memory_read"?`). From the outside this looks like a ready-but-quiet lane even though the real state is `blocked waiting for permission`. **Required fix shape:** (a) detect interactive MCP/tool permission prompts as a first-class blocked state instead of generic idle; (b) emit a typed event such as `blocked.mcp_permission` / `blocked.tool_permission` with tool/server name, prompt age, and whether the gate is session-only vs always-allow capable; (c) include this gate in startup/no-evidence evidence bundles and lane status surfaces so clawhip can say "blocked at MCP permission prompt" without pane scraping; (d) add a regression proving a prompt-gated session does not get misclassified as stale/idle/ready. **Why this matters:** prompt acceptance and startup telemetry are still incomplete if an interactive MCP gate can eat the first real action after hooks report success. Source: live dogfood session `clawcode-human` on 2026-04-18.
|
||||
|
||||
## Pinpoint #161. `run_turn_loop` has no wall-clock timeout — a stalled turn blocks indefinitely
|
||||
201. **`extract --model-payload` is not inspectable enough for deterministic dogfood: forced mode selection missing, and hybrid/no-snippet cases are opaque** — dogfooded 2026-04-19 from `dogfood-1776184671` against three real-repo files. `node dist/cli/index.js extract <file> --model-payload` succeeded and auto-selected `raw`, `raw`, and `hybrid`, but there is currently no CLI surface to force `raw` / `compressed` / `hybrid` for A/B comparison: `--mode raw` and `--mode compressed` both fail immediately with `Error: Unexpected extract argument: --mode`. That turns payload-shaping validation into guesswork because operators cannot ask the extractor to render the same file through each mode and compare the exact output. The opacity is worse in the observed hybrid case: the Formbricks checkbox file produced a hybrid payload with no snippets, leaving no visible explanation for why the extractor chose hybrid, what evidence it kept vs dropped, or whether the result is correct vs a silent fallback. **Required fix shape:** (a) add an explicit debug/inspection flag that forces extraction mode (`--mode raw|compressed|hybrid` or equivalent) without changing default auto-selection; (b) print/report the chosen mode and the decision reason in a machine-readable field when `--model-payload` is used; (c) when hybrid emits zero snippets, surface an explicit reason/count summary instead of making "no snippets" indistinguishable from silent loss; (d) add regression coverage on at least one real-world hybrid fixture so mode choice and snippet accounting stay stable. **Why this matters:** direct claw-code dogfood needs deterministic payload comparison to debug startup/context quality; without forced-mode inspection and snippet accounting, operators can see the outcome but not the extraction decision that produced it. Source: live dogfood session `dogfood-1776184671` on 2026-04-19.
|
||||
|
||||
**Gap.** `PortRuntime.run_turn_loop` (`src/runtime.py:154`) bounds execution only by `max_turns` (a turn count). There is no wall-clock deadline or per-turn timeout. If a single `engine.submit_message` call stalls (e.g., waiting on a slow or hung external provider, a network timeout, or an infinite LLM stream), the entire turn loop hangs with no structured signal, no cancellation path, and no timeout error returned to the caller.
|
||||
202. **`extract --model-payload` emits `filePath` values that can walk outside the current repo root for external targets** — dogfooded 2026-04-19 from `dogfood-1776184671` while extracting files from sibling repos under `/home/bellman/Workspace/fooks-test-repos/...` with cwd anchored at the claw-code repo. In all three successful payloads (`raw`, `raw`, `hybrid`), the reported `filePath` became a relative path like `../../fooks-test-repos/...` that escapes the current repo root. Technically the path is still correct, but operationally it is a clawability gap: downstream consumers cannot tell whether this means "user intentionally extracted an external file", "path normalization leaked out of scope", or "the payload now references content outside the trusted working tree." That ambiguity is especially bad for model payloads because the `filePath` field looks like grounded provenance while actually encoding a cross-root escape. **Required fix shape:** (a) define a stable provenance contract for extracted targets outside cwd/repo root — for example an explicit `pathScope` / `targetRoot` field or an absolute-vs-relative policy instead of silently emitting `../..` escapes; (b) if relative paths are retained, add a machine-readable flag that the target is outside the current workspace/root; (c) document and test the normalization rule for sibling-repo extraction so downstream tooling does not mistake cross-root references for in-repo files; (d) add regression coverage for one in-repo fixture and one external-target fixture. **Why this matters:** model payload provenance should reduce ambiguity, not create a silent scope escape that later consumers have to reverse-engineer. Source: live dogfood session `dogfood-1776184671` on 2026-04-19.
|
||||
|
||||
**Repro (conceptual).** Wrap `engine.submit_message` with an artificial `time.sleep(9999)` and call `run_turn_loop` — it blocks forever. There is no `asyncio.wait_for`, `signal.alarm`, `concurrent.futures.TimeoutError`, or equivalent in the call path. `grep -n 'timeout\|deadline\|elapsed\|wall' src/runtime.py src/query_engine.py` returns zero results.
|
||||
203. **Successful dogfood runs can still end in a misleading TUI/pane failure banner (`skills/list failed in TUI`, `can't find pane`)** — dogfooded 2026-04-19 from `dogfood-1776184671`. The session completed real work and produced a coherent result summary, but immediately afterward the surface emitted `Error: skills/list failed in TUI` and `can't find pane: %4766`. That creates a truth-ordering bug: the user just watched a successful run, then the final visible state looks like a transport/UI failure with no indication whether the underlying task failed, the pane disappeared after completion, or an unrelated post-run TUI refresh crashed. **Required fix shape:** (a) separate task result state from post-run TUI/skills refresh failures so a completed run cannot be visually overwritten by a secondary pane-lookup error; (b) classify missing-pane-after-completion as a typed transport/UI degradation with phase context (`post_result_refresh`, `skills_list_refresh`, etc.) instead of a generic terminal error; (c) preserve and surface the last successful task outcome even if the TUI follow-up step fails; (d) add regression coverage for the path where a pane disappears after result rendering so the session is reported as `completed_with_ui_warning` rather than plain failure. **Why this matters:** claw-code needs the final visible truth to match the actual execution truth; otherwise successful dogfood looks flaky and operators cannot tell whether to trust the result they just got. Source: live dogfood session `dogfood-1776184671` on 2026-04-19.
|
||||
|
||||
**Impact.** A claw calling `run_turn_loop` in a CI pipeline or orchestration harness has no reliable way to enforce a deadline. The loop will hang until the OS kills the process or a human intervenes. The caller cannot distinguish "still running" from "hung" without an external watchdog.
|
||||
204. **Interactive work can start with updater/setup churn before the actual user task, blurring startup truth and first-action latency** — dogfooded 2026-04-19 from `clawcode-human`. Launching `omx` inside the claw-code worktree did not begin with the requested ROADMAP task; it first diverted through an update prompt (`Update available: v0.12.6 → v0.13.0. Update now? [Y/n]`), global install, full setup refresh, config rewrite/backups, notification/HUD setup, and a `Restart to use new code` notice before returning to the actual prompt. None of that was the operator’s requested work, but it consumed the critical startup window and mixed setup chatter with task-relevant execution. This creates a clawability gap: downstream observers cannot cleanly distinguish `startup succeeded and work began` from `startup mutated the environment and maybe changed the toolchain before work began`, and first-action latency gets polluted by maintenance side effects. **Required fix shape:** (a) make updater/setup detours a first-class startup phase with explicit classification (`startup.update_gate`, `startup.setup_refresh`) instead of letting them masquerade as normal task progress; (b) allow noninteractive or automation-oriented launches to suppress or defer update/setup churn until after the first user task/result boundary; (c) preserve a clean timestamped boundary between maintenance work and task work in lane events/status surfaces; (d) add regression coverage proving a prompt can start without forced updater/setup interposition when policy says "do work now." **Why this matters:** startup truth should reflect the user’s requested work, not hide it behind self-mutation and config churn that change latency, logs, and reproducibility before the first real action. Source: live dogfood session `clawcode-human` on 2026-04-19.
|
||||
|
||||
**Fix shape (~15 lines).**
|
||||
1. Add an optional `timeout_seconds: float | None = None` parameter to `run_turn_loop`.
|
||||
2. Use `concurrent.futures.ThreadPoolExecutor` + `Future.result(timeout=...)` (or `asyncio.wait_for` if the engine becomes async) to wrap each `submit_message` call.
|
||||
3. On timeout, append a sentinel `TurnResult` with `stop_reason='timeout'` and break the loop.
|
||||
4. Document the timeout contract: total wall-clock budget across all turns, not per-turn.
|
||||
205. **Direct CLI dogfood is not self-starting when build artifacts are absent (`dist/cli/index.js` missing)** — dogfooded 2026-04-19 from `dogfood-1776184671`. The intended direct check was to run `node dist/cli/index.js extract ...`, but the first attempt hit a missing built artifact and the lane had to detour through `npm ci && npm run build` before any product behavior could be exercised. That means a "run the CLI directly in a fresh worktree" path is not actually one-step dogfoodable: the operator has to know the build prerequisite, spend time satisfying it, and then mentally separate build-system failures from product-surface failures. **Required fix shape:** (a) provide a supported direct-run entrypoint that either works from source without prebuilt `dist/` artifacts or emits a product-owned guidance error that names the exact one-shot bootstrap command; (b) surface build-artifact-missing as a typed startup/dependency prerequisite state rather than a raw module/file failure; (c) document and test the fresh-worktree direct-dogfood path so `extract --help` / `extract ... --model-payload` can be exercised without archaeology; (d) if build-on-demand is the intended contract, make it explicit and deterministic instead of requiring the operator to guess `npm ci && npm run build`. **Why this matters:** direct dogfood should fail on product behavior, not on hidden local build prerequisites that blur whether the tool is broken or merely unprepared. Source: live dogfood session `dogfood-1776184671` on 2026-04-19.
|
||||
|
||||
**Acceptance.** `run_turn_loop(prompt, timeout_seconds=10)` raises `TimeoutError` (or returns a `TurnResult` with `stop_reason='timeout'`) within 10 seconds even if the underlying LLM call stalls indefinitely. `timeout_seconds=None` (default) preserves existing behaviour.
|
||||
206. **`extract --help` is not a safe/local help surface: after bootstrap it can still crash into a Node stack instead of rendering usage** — dogfooded 2026-04-19 from `dogfood-1776184671`. Even after repairing the missing-build-artifact prerequisite with `npm ci && npm run build`, the next expected low-risk probe `node dist/cli/index.js extract --help` did not cleanly print command help; it dropped into a Node failure at `dist/cli/index.js:52` and emitted a stack trace under `Node.js v25.1.0`. That means the help path itself is not trustworthy as a preflight surface: operators cannot rely on `--help` to discover flags or confirm command shape before doing real work, and they have to treat a basic introspection command like a potentially crashing code path. **Required fix shape:** (a) make `extract --help` and sibling help surfaces intercept locally before any heavier runtime path that can throw; (b) if a subcommand cannot render help because build/runtime prerequisites are missing, return a product-owned guidance error instead of a raw Node stack; (c) add regression coverage that `extract --help` succeeds in both a prepared worktree and a minimally bootstrapped one; (d) preserve the contract that help/usage discovery is the safest command family, not another execution path that can explode. **Why this matters:** help commands are supposed to reduce uncertainty; if they crash, dogfooders lose the cleanest way to learn the surface and every later failure gets harder to classify. Source: live dogfood session `dogfood-1776184671` on 2026-04-19.
|
||||
|
||||
**Blocker.** None.
|
||||
207. **Build/setup failures are being misclassified as generic missing-path shell errors in post-tool feedback** — dogfooded 2026-04-19 from `dogfood-1776184671`. When the lane attempted `node dist/cli/index.js extract --help` with no built artifact, the `PostToolUse` hook summarized it as ``Bash reported `command not found`, `permission denied`, or a missing file/path``, and later `npm run build` failed with actual TypeScript diagnostics (`TS2307: Cannot find module 'typescript'`, plus additional compile errors). Those are distinct failure classes — missing built artifact, missing dependency, and compile/typecheck red — but the feedback surface collapses them into the same mushy shell-triage bucket. That makes recovery slower because the operator has to reread raw pane output to learn whether the right next move is `npm ci`, fixing package deps, fixing TS errors, or checking file paths. **Required fix shape:** (a) classify post-tool failures with narrower machine-readable buckets such as `artifact_missing`, `dependency_missing`, `compile_error`, and reserve `missing_path` / `command_not_found` for the literal cases; (b) include the strongest observed diagnostic snippet (for example `TS2307 typescript missing`) in the structured feedback instead of only the broad shell rubric; (c) add regression coverage proving TypeScript/compiler failures are not surfaced as generic missing-path errors; (d) thread that typed classification into lane summaries so downstream claws can recommend the right recovery without pane archaeology. **Why this matters:** clawability depends on the fix suggestion matching the real failure class; broad shell-error mush turns easy recoveries into manual forensic work. Source: live dogfood session `dogfood-1776184671` on 2026-04-19.
|
||||
|
||||
208. **The JavaScript `extract` dogfood path has no dedicated preflight/doctor surface for its own prerequisites** — dogfooded 2026-04-19 from `dogfood-1776184671`. The repo already has strong Rust-side `claw doctor` / preflight coverage, but the direct JS CLI path I was actually dogfooding (`node dist/cli/index.js extract ...`) gave no equivalent early warning about its own prerequisites: missing `dist/cli/index.js`, missing `node_modules/typescript`, and the difference between "needs bootstrap" vs "real compile error" all had to be discovered by failing real commands in sequence. That means the lowest-friction way to validate the JS extract surface is still failure-driven archaeology rather than one explicit readiness check. **Required fix shape:** (a) add a lightweight JS-side preflight/doctor command or bootstrap check for the extract CLI path that reports artifact presence, dependency readiness, and build status before execution; (b) make that check machine-readable so lanes can say `js_extract_prereq_blocked` (or equivalent) instead of learning via stack traces; (c) document the direct dogfood path so operators know whether the supported sequence is `doctor -> help -> extract` or something else; (d) add regression coverage for a fresh worktree, a deps-missing worktree, and a ready worktree. **Why this matters:** preflight should collapse obvious prerequisite failures into one cheap truth surface instead of forcing dogfooders to burn turns discovering them one crash at a time. Source: live dogfood session `dogfood-1776184671` on 2026-04-19.
|
||||
|
||||
@@ -6479,3 +6297,178 @@ load_session('nonexistent') # raises FileNotFoundError with no structured error
|
||||
355. **Top-level `session list` and `session help` with `--output-format json` hang with zero stdout/stderr instead of returning bounded session inventory/help or a typed unavailable response** — dogfooded 2026-04-30 for the 00:30 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `8e24f304`. After rebuilding and verifying the binary provenance, repeated bounded runs of `timeout 8 ./rust/target/debug/claw session list --output-format json` exited `124` with `stdout=0` and `stderr=0`. A follow-up bounded `session help --output-format json` probe also produced no stdout/stderr before it had to be killed, so the issue is broader than inventory: even the session help path can silently hang in JSON mode. This is distinct from #354's memory help/list hang: the affected surface is session command introspection, where claws need a safe local way to enumerate resumable sessions or at least read usage before deciding whether to resume, inspect, or clean them up. **Required fix shape:** (a) make `session help` and `session list --output-format json` return bounded local JSON without waiting indefinitely on remote API/auth/session-store availability; (b) return stdout JSON with `kind:"session"`, `action:"help"|"list"`, `status`, usage or `sessions[]`, source/provenance, counts, and truncation metadata, or typed `status:"unavailable"`/`code` when backing state cannot be reached; (c) add explicit timeout diagnostics if a remote/authenticated session source is consulted; (d) add regression coverage proving both `session help --output-format json` and `session list --output-format json` return machine-readable outcomes within a deterministic budget. **Why this matters:** session inventory/help is a core recovery/control-plane path. If even help/list can hang silently with no bytes, claws cannot distinguish no sessions, missing credentials, remote API stall, corrupted local store, or dispatch deadlock, and resume/cleanup automation blocks before it can choose a safe next action. Source: gaebal-gajae dogfood follow-up for the 00:30 nudge on rebuilt `./rust/target/debug/claw` `8e24f304`.
|
||||
356. **Top-level `status --help --output-format json` exits successfully but emits plain text help instead of JSON** — dogfooded 2026-04-30 for the 01:00 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `74338dc6`. After rebuilding and verifying the binary provenance, repeated bounded runs of `./rust/target/debug/claw status --help --output-format json` exited `0` with `stdout=326` and `stderr=0`, but stdout was plain text (`Status`, `Usage`, `Purpose`, `Output`, `Formats`, `Related`) rather than a JSON object. In the same rebuilt binary, `version --output-format json` returned proper stdout JSON with version/build metadata, proving the JSON output path itself is reachable. This is distinct from #354/#355 memory/session JSON help/list hangs: the status help path returns promptly, but ignores the requested JSON format. **Required fix shape:** (a) make `status --help --output-format json` emit valid stdout JSON with `kind:"help"` or `kind:"status"`, `action:"help"`, usage, options, examples, supported output formats, and related slash/direct commands; (b) preserve text help for default/text mode only; (c) add a `format:"json"` or equivalent field so callers can assert the contract without parsing prose; (d) add regression coverage proving status help with JSON format parses as JSON and does not silently fall back to plain text. **Why this matters:** help is the discovery surface automation uses before invoking status. If `--output-format json` is accepted but help remains plain text, claws must scrape formatting-sensitive prose or special-case help output, defeating the point of machine-readable CLI contracts. Source: gaebal-gajae dogfood follow-up for the 01:00 nudge on rebuilt `./rust/target/debug/claw` `74338dc6`; invalid hang PR #2907 was closed after repeated bounded repros returned promptly.
|
||||
357. **Top-level `doctor --help --output-format json` exits successfully but emits plain text help instead of JSON** — dogfooded 2026-04-30 for the 01:30 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `52a909ce`. After rebuilding and verifying the binary provenance, repeated bounded runs of `./rust/target/debug/claw doctor --help --output-format json` exited `0` with `stdout=343` and `stderr=0`, but stdout was plain text (`Doctor`, `Usage`, `Purpose`, `Output`, `Formats`, `Related`) rather than a JSON object. In the same rebuilt binary, `status --help --output-format json` also returned promptly as plain text (#356), confirming a broader help-format fallback class while keeping this pinpoint on the doctor surface. This is distinct from #354/#355 memory/session JSON help/list hangs: doctor help returns promptly, but ignores the requested JSON format. **Required fix shape:** (a) make `doctor --help --output-format json` emit valid stdout JSON with `kind:"help"` or `kind:"doctor"`, `action:"help"`, usage, checks, options, examples, supported output formats, and related slash/direct commands; (b) preserve text help for default/text mode only; (c) add a `format:"json"` or equivalent field so callers can assert the contract without parsing prose; (d) add regression coverage proving doctor help with JSON format parses as JSON and does not silently fall back to plain text. **Why this matters:** doctor is the diagnostic entrypoint users reach for when things are broken. If JSON help falls back to prose, claws cannot discover diagnostic semantics or present structured recovery instructions without scraping formatting-sensitive text. Source: gaebal-gajae dogfood follow-up for the 01:30 nudge on rebuilt `./rust/target/debug/claw` `52a909ce`; invalid hang PR #2911 was closed after repeated bounded repros returned promptly.
|
||||
358. **Top-level `cost --help --output-format json` hangs with zero stdout/stderr instead of returning bounded command help JSON** — dogfooded 2026-04-30 for the 02:00 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `d95b230c`. After rebuilding and verifying the binary provenance, repeated bounded runs of `timeout 8 ./rust/target/debug/claw cost --help --output-format json` exited `124` with `stdout=0` and `stderr=0`. In the same rebuilt binary, `version --output-format json` returned promptly with version/build metadata, proving the binary itself and the JSON output path are reachable; the hang is specific to the cost help path, though other help surfaces have separate known JSON contract issues (#356/#357). **Required fix shape:** (a) make `cost --help --output-format json` return static/bounded stdout JSON with `kind:"help"` or `kind:"cost"`, `action:"help"`, usage, options, examples, supported output formats, and related slash/direct commands; (b) ensure help rendering does not initialize slow cost/session/accounting providers; (c) if any dynamic provider is accidentally consulted, return a typed JSON timeout/unavailable error instead of hanging; (d) add regression coverage proving cost help in JSON mode returns within a deterministic budget. **Why this matters:** cost/tokens surfaces are commonly consumed by automation for budgeting. If even cost help can hang silently, claws cannot discover cost command semantics or present safe budget diagnostics before running potentially slow accounting paths. Source: gaebal-gajae dogfood follow-up for the 02:00 nudge on rebuilt `./rust/target/debug/claw` `d95b230c`.
|
||||
380. **Top-level `tokens --help --output-format json` hangs with zero stdout/stderr instead of returning bounded command help JSON** — dogfooded 2026-04-30 for the 02:30 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `d95b230c`. After verifying #358 covered `cost --help`, a fresh adjacent probe on the token-budget surface showed the same silent failure class: repeated bounded runs of `timeout 8 ./rust/target/debug/claw tokens --help --output-format json` exited `124` with `stdout=0` and `stderr=0`. In the same rebuilt binary, `version --output-format json` returned promptly with version/build metadata, proving the binary itself and JSON output path are reachable. This is distinct from #358's cost help hang: the affected surface is the sibling `tokens` command help, which agents use before estimating prompt/session token budgets. **Required fix shape:** (a) make `tokens --help --output-format json` return static/bounded stdout JSON with `kind:"help"` or `kind:"tokens"`, `action:"help"`, usage, options, examples, supported output formats, and related slash/direct commands; (b) ensure help rendering does not initialize slow token accounting, session, or provider state; (c) if any dynamic provider is consulted, return a typed JSON timeout/unavailable error instead of hanging; (d) add regression coverage proving tokens help in JSON mode returns within a deterministic budget. **Why this matters:** token budgeting is a preflight clawability surface. If help hangs silently, automation cannot safely discover how to inspect or constrain token usage before running expensive prompts, and budget-aware wrappers stall at the discovery step. Source: gaebal-gajae dogfood follow-up for the 02:30 nudge on rebuilt `./rust/target/debug/claw` `d95b230c`.
|
||||
381. **Top-level `cache --help --output-format json` hangs with zero stdout/stderr instead of returning bounded command help JSON** — dogfooded 2026-04-30 for the 03:00 nudge on current `origin/main` / rebuilt `./rust/target/debug/claw` with embedded `git_sha` `d95b230c`. After #358 and #380 landed for the cost/tokens preflight help hangs, a fresh adjacent probe on the cache-control surface showed the same silent failure class: repeated bounded runs of `timeout --kill-after=1s 8s ./rust/target/debug/claw cache --help --output-format json` exited `124` with `stdout=0` and `stderr=0`. In the same rebuilt binary, `version --output-format json` returned promptly with version/build metadata, proving the binary itself and JSON output path are reachable. This is distinct from the separate `/cache` slash-command envelope mismatch class: the affected surface here is top-level `cache` command help, where agents need bounded local discovery before deciding whether to inspect, clear, or summarize cache state. **Required fix shape:** (a) make `cache --help --output-format json` return static/bounded stdout JSON with `kind:"help"` or `kind:"cache"`, `action:"help"`, usage, options, examples, supported output formats, and related slash/direct commands; (b) ensure help rendering does not initialize slow cache/session/provider state; (c) if any dynamic provider is consulted, return a typed JSON timeout/unavailable error instead of hanging; (d) add regression coverage proving cache help in JSON mode returns within a deterministic budget. **Why this matters:** cache inspection and cleanup are recovery/control-plane operations. If cache help hangs silently, claws cannot safely discover cache semantics before attempting cleanup, and automation stalls before it can choose a non-destructive cache action. Source: gaebal-gajae dogfood follow-up for the 03:00 nudge on rebuilt `./rust/target/debug/claw` `d95b230c`.
|
||||
|
||||
422. **`export --output-format json` and `--resume latest` report the same "no managed sessions" scenario using two different `kind` codes — `no_managed_sessions` vs `session_load_failed` — making "no session found" undetectable by a single kind-code check** — dogfooded 2026-04-30 KST (UTC+9) by Jobdori on `e939777f`. Running `claw export --output-format json` with no session present returns (on stderr, exit 1): `{"error":"no managed sessions found in .claw/sessions/<fingerprint>/","hint":"Start \`claw\` to create a session, then rerun with \`--resume latest\`.\nNote: claw partitions sessions per workspace fingerprint; sessions from other CWDs are invisible.","kind":"no_managed_sessions","type":"error"}`. Running `claw --resume latest /status --output-format json` with no session present returns (on stderr, exit 1): `{"error":"failed to restore session: no managed sessions found in .claw/sessions/<fingerprint>/","hint":"Start \`claw\` to create a session, then rerun with \`--resume latest\`.\nNote: claw partitions sessions per workspace fingerprint; sessions from other CWDs are invisible.","kind":"session_load_failed","type":"error"}`. Both describe the same root condition — there are no sessions to operate on — but they expose it via different `kind` discriminants. Automation that checks `kind == "no_managed_sessions"` to detect a cold workspace will miss the `--resume` path's `session_load_failed`, and vice versa. A wrapper that guards "run with --resume only if a session exists" must special-case both codes. The hint text is identical between them, suggesting the messages are logically equivalent. Additionally neither code matches the proposed canonical names `session_not_found` / `session_load_failed` as stable `ErrorKind` discriminants described in ROADMAP #77's fix shape, which explicitly proposes typed error-kind codes for session lifecycle failures. **Required fix shape:** (a) unify "no sessions found for this workspace fingerprint" under a single canonical `kind` code — either `no_managed_sessions` or `session_not_found` — used consistently by every command path that encounters an empty session registry; (b) if `session_load_failed` is a more general category (covering e.g. corrupt session files, IO errors, schema version mismatches), it should nest a concrete `reason:"no_managed_sessions"` or `reason:"session_not_found"` sub-field so callers can distinguish "empty registry" from "found but unreadable"; (c) align with the canonical error-kind contract proposed in #77; (d) add regression coverage proving `export` and `--resume latest` in an empty workspace both return an error with the same top-level `kind` code. **Why this matters:** session guard-rails in orchestration need a single stable `kind` to detect cold workspaces without enumerating all possible no-session synonyms. Two divergent codes for the same condition make defensive automation brittle and contradict the promise of machine-readable error envelopes. Source: Jobdori live dogfood, `e939777f`, 2026-04-30 KST (UTC+9).
|
||||
|
||||
407. **`config --output-format json` returns `files[].loaded:false` with no `load_error`, `not_found`, or `skip_reason` field — automation cannot distinguish "file does not exist", "file exists but parse failed", and "file exists but was skipped by policy" from the same `loaded:false` value; also `loaded_files` and `merged_keys` are bare integers with no per-file attribution** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `./claw --output-format json config` on a workspace with 5 discovered config files returns `{"kind":"config","cwd":"...","files":[{"loaded":false,"path":"/Users/yeongyu/.claw.json","source":"user"},{"loaded":true,"path":"/Users/yeongyu/.claw/settings.json","source":"user"},{"loaded":true,"path":"/Users/yeongyu/clawd/claw-code/.claw.json","source":"project"},{"loaded":false,"path":"/Users/yeongyu/clawd/claw-code/.claw/settings.json","source":"project"},{"loaded":false,"path":"/Users/yeongyu/clawd/claw-code/.claw/settings.local.json","source":"local"}],"loaded_files":2,"merged_keys":2}`. Three of five files have `loaded:false` with no accompanying `not_found:true`, `parse_error`, `io_error`, or `skip_reason`; automation must stat each path separately to guess why. Also `loaded_files:2` and `merged_keys:2` are bare counts — ambiguous whether `merged_keys:2` means 2 total top-level JSON keys across all files or 2 unique merged settings. **Required fix shape:** (a) add `not_found: bool` and optional `load_error: string` to each `files[]` entry so callers can distinguish missing, parse-broken, and policy-skipped files without filesystem probing; (b) document or rename `merged_keys` as `merged_setting_count` or `total_merged_keys` to remove the int-semantics ambiguity; (c) optionally add `merged_keys_by_file: [{path, keys}]` for attribution; (d) add regression coverage proving `files[]` entries with `loaded:false` carry at minimum `not_found` distinguishing non-existent paths from load failures. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
408. **`status --output-format json` `workspace.changed_files` is ambiguous — on a workspace with 5 untracked files, `changed_files:5`, `staged_files:0`, `unstaged_files:0`, `untracked_files:5`; it is unclear whether `changed_files` is the sum of all four git-status categories or only a subset; automation cannot tell if `changed_files:5` means "5 tracked modified" or "5 total non-clean files including untracked"** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `./claw --output-format json status` returns `{"workspace":{"changed_files":5,"staged_files":0,"unstaged_files":0,"untracked_files":5,...}}` — `changed_files==untracked_files==5` with staged and unstaged both zero. The field name `changed_files` implies "modified tracked files" but the value equals the untracked count, not `staged+unstaged`. Without a comment or documented definition, automation must probe whether `changed_files = staged + unstaged` (excludes untracked) or `changed_files = staged + unstaged + untracked + conflicted` (total dirty). Also `git_state:"dirty · 5 files · 5 untracked"` repeats the same data as a prose string alongside the structured integer fields — redundant human-readable string alongside machine-readable integers. **Required fix shape:** (a) document and stabilize `changed_files` as either `tracked_dirty_count` (staged+unstaged only) or `total_non-clean_count` (staged+unstaged+untracked+conflicted) and rename to remove the ambiguity; (b) ensure a machine consumer can compute `is_clean` as a single boolean field without interpreting `git_state` prose; (c) deprecate or remove `git_state` prose string now that all its constituent counts are available as integers; (d) add regression coverage proving `changed_files` semantics against a workspace with staged, unstaged, untracked, and conflicted files. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
409. **`init --output-format json` emits redundant parallel artifact schemas — `artifacts[].status` and flat `created[]`/`skipped[]`/`updated[]` arrays carry identical state, and `artifacts[].status:"skipped"` omits `skip_reason`** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw init --output-format json` on a fresh directory returns a JSON object with two parallel representations of the same artifact set: (1) `artifacts: [{name, status}]` — a structured per-artifact array; and (2) `created: [...]`, `skipped: [...]`, `updated: [...]` — flat string arrays partitioned by status. Both encode the same four artifact names and their outcomes with no additional information between them. On a subsequent run in an already-initialized directory, every artifact has `status:"skipped"`, but no `reason` field is present on any artifact entry — automation cannot distinguish `"already_exists"` (safe to ignore) from `"permission_denied"`, `"dry_run"`, or `"conflicting_contents"` (each requiring a different response). The `message` field also embeds `"skipped (already exists)"` prose that is absent from the structured payload. **Required fix shape:** (a) pick one canonical artifact representation — either `artifacts[{name, status, reason?, path?}]` or the flat status arrays — and deprecate the other; (b) add a `skip_reason` or `reason` field to `artifacts[]` entries with `status:"skipped"` and `status:"error"`, using an enum such as `already_exists`, `permission_denied`, `dry_run`, `conflict`, `unknown`; (c) add optional `path` (absolute) to each artifact entry so automation can act on the real on-disk location without re-joining with `project_path`; (d) add regression coverage proving `init --output-format json` on an existing directory includes machine-classifiable skip reasons for every skipped artifact and does not rely on the prose `message` field for structured state. **Why this matters:** init is the bootstrapping surface automation uses to ensure a project is claw-ready. If skip classification requires parsing human prose and the structured payload has two redundant formats, claws either over-provision re-inits or cannot distinguish safe skips from blocked writes without brittle message scraping. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
410. **`agents list`, `skills list`, and `mcp list` use three different count-field names and divergent envelope schemas despite being sibling list commands** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running all three list commands with `--output-format json` reveals incompatible envelope shapes: `agents list` emits `count:int` at the top level plus `summary:{active,shadowed,total}` and `working_directory`; `skills list` emits no top-level `count`, only `summary:{active,shadowed,total}`, and omits `working_directory`; `mcp list` uses a different count-field name `configured_servers:int`, has no `count`, no `summary`, and instead adds `status:"ok"` and `config_load_error:null` fields absent from the other two. The three sibling commands cannot be polymorphically consumed with the same count-extraction logic, requiring per-command special-casing at the cardinality check level. **Required fix shape:** (a) define one canonical top-level count field name (`count`, `total`, or `item_count`) and use it across `agents`, `skills`, and `mcp` list envelopes; (b) define one canonical `summary` object shape with at minimum `active`, `total`, and optionally `shadowed` and include it on all three; (c) expose `working_directory` consistently on all list commands or omit it from all; (d) add regression coverage proving the three list envelopes share the same count-field name and summary shape before each release. **Why this matters:** orchestration lanes that inventory agents, skills, and servers before delegation need one count-extraction pattern. Three different field names force per-command special-casing of the most basic cardinality check. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
411. **`plugins enable/disable --output-format json` always emits `reload_runtime:true` regardless of whether state actually changed, and omits `previous_status`, `changed`, `version`, and `source` fields — automation cannot tell if a reload is necessary or if the mutation was a no-op** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw plugins enable example-bundled --output-format json` on an already-enabled plugin returns `{"action":"enable","kind":"plugin","message":"…","reload_runtime":true,"target":"example-bundled"}` — `reload_runtime:true` every time, even on a no-op re-enable. The same applies to idempotent `disable`. Structured fields present: `action`, `kind`, `message`, `reload_runtime`, `target`. Structured fields absent: `previous_status`, `status`, `changed`, `version`, `source`. The actual plugin name, version, and new status are embedded only in the prose `message` field (`"Result enabled example-bundled@bundled\n Name example-bundled\n Version 0.1.0\n Status enabled"`), requiring callers to scrape column-aligned text to extract the post-mutation state. A no-op mutation emitting `reload_runtime:true` forces orchestration to trigger an expensive runtime reload even when no config change occurred. **Required fix shape:** (a) add `changed:bool` so callers can skip runtime reload when `changed:false`; (b) add `previous_status` and `status` fields (enums: `enabled`/`disabled`) so pre/post state is machine-readable without parsing `message`; (c) add `version` and `source` fields at the mutation response level, consistent with `plugins list` entry shape; (d) emit `reload_runtime:false` when `changed:false`; (e) add regression coverage proving idempotent enable/disable sets `changed:false` and `reload_runtime:false`. **Why this matters:** plugin lifecycle is a hot path for automation that conditionally enables plugins before running sessions. If every enable emits `reload_runtime:true` and no `changed` field exists, orchestration must reload unconditionally or maintain external state — both brittle patterns. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
412. **`bootstrap-plan --output-format json` returns `phases: string[]` of raw Rust enum variant names with no description, steps, duration, or dependency metadata — unusable by automation** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw bootstrap-plan --output-format json` returns `{"kind":"bootstrap-plan","phases":["CliEntry","FastPathVersion","StartupProfiler","SystemPromptFastPath","ChromeMcpFastPath","DaemonWorkerFastPath","BridgeFastPath","DaemonFastPath","BackgroundSessionFastPath","TemplateFastPath","EnvironmentRunnerFastPath","MainRuntime"]}`. The envelope has only two keys: `kind` and `phases`. The `phases` array contains 12 raw Rust enum variant name strings — opaque identifiers with no `description`, no `label`, no `steps[]`, no `estimated_ms`, no `dependencies[]`, no `optional:bool`, and no `status` (enabled/disabled/skipped). Automation that calls `bootstrap-plan` to understand startup costs or profile initialization paths receives 12 name strings that reveal nothing about what each phase does, how long it takes, whether it depends on credentials/network/MCP, or which ones can be skipped. **Required fix shape:** (a) replace `phases: string[]` with `phases: [{id, label, description, optional, estimated_ms?, dependencies?, status?}]`; (b) add a top-level `total_phases` count; (c) mark network/credential-dependent phases with a `requires_auth:bool` or `deps:["network","credentials","mcp"]` field so automation can plan for unavailability; (d) add regression coverage proving each phase entry has at least `id`, `label`, and `description` fields and that the count matches the phases array length. **Why this matters:** bootstrap-plan is the startup-cost introspection surface. If its JSON output is 12 opaque variant name strings, automation cannot profile startup, identify slow phases, skip optional phases, or present meaningful startup diagnostics — the entire command serves only as a list of internal identifiers. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
413. **`acp --output-format json` leaks internal ROADMAP tracking numbers and implementation notes as top-level JSON fields — `discoverability_tracking:"ROADMAP #64a"` and `tracking:"ROADMAP #76"` are internal backlog references that should not appear in the public machine-readable contract** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw acp --output-format json` returns a ten-key envelope: `aliases`, `discoverability_tracking`, `kind`, `launch_command`, `message`, `recommended_workflows`, `serve_alias_only`, `status`, `supported`, `tracking`. Two fields are verbatim internal backlog cross-references: `"discoverability_tracking":"ROADMAP #64a"` and `"tracking":"ROADMAP #76"`. These were presumably used during initial scaffolding to track which backlog items the stub relates to, but they are now part of the public JSON contract that automation consumes. The `message` field also contains implementation-note prose (`"ACP/Zed editor integration is not implemented in claw-code yet. \`claw acp serve\`..."`) that describes the build state rather than the command's machine-readable status. **Required fix shape:** (a) remove `discoverability_tracking` and `tracking` from the public JSON envelope or move them to an optional `_debug` or `_meta` sub-object gated on a debug flag; (b) replace `message` prose with a structured `reason` enum (`"not_implemented"`, `"discoverability_only"`, `"serve_only"`) plus optional `detail` string; (c) rename `supported:false` + `status:"discoverability_only"` to a single typed `availability` object with `status`, `reason`, and `target_command` fields; (d) add regression coverage proving the public `acp --output-format json` envelope contains no internal tracking/backlog fields and that `message` is not the sole machine-classifiable signal. **Why this matters:** public JSON APIs should not leak internal ticket references. Automation that snapshots or validates the ACP JSON schema will embed these internal identifiers into external contracts and need to change every time backlog numbering shifts. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
415. **`config <section> --output-format json` returns `merged_keys:int` (a count) with no actual merged key-value pairs — automation cannot read the resolved configuration values from JSON** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw config env --output-format json`, `claw config model --output-format json`, or `claw config hooks --output-format json` all return an identical five-key envelope: `{"cwd":"...","files":[...],"kind":"config","loaded_files":2,"merged_keys":1}`. The `merged_keys` field is an integer count of how many keys were merged across the loaded files, not an object or array of the actual key names and resolved values. The `files` array shows which config files were loaded/missing but contains no per-file key-value content. The merged section content — the actual resolved `env`, `model`, or `hooks` configuration — is entirely absent from the JSON output. It only appears in the prose output as a "Merged section: env / <value>" block. **Required fix shape:** (a) add a `merged` or `resolved` object/array field to the JSON envelope containing the actual key-value pairs that resulted from merging the loaded config files for the requested section; (b) rename `merged_keys` from an integer count to either remove it (derivable from `len(merged)`) or keep it as a companion count field; (c) for each entry in `merged`, include `key`, `value`, and optionally `source_file` so automation can attribute which file contributed the value; (d) add regression coverage proving `config env --output-format json` with a non-empty env section populates `merged` (or equivalent) with the actual resolved key-value pairs. **Why this matters:** the entire purpose of `config env/model/hooks --output-format json` is to allow automation to read the resolved runtime configuration without screen-scraping prose. Returning only a count defeats the purpose and forces callers to either re-parse the prose output or re-read and merge the source config files themselves. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
416. **`plugins list --output-format json` returns the mutation response shape with a prose `message` table instead of a structured `plugins:[]` array — `name`, `version`, `status`, `source` are embedded in `message` prose only** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw plugins list --output-format json` returns `{"action":"list","kind":"plugin","message":"Plugins\n example-bundled v0.1.0 disabled\n sample-hooks v0.1.0 disabled","reload_runtime":false,"target":null}`. This is the same four-key response envelope used by `plugins enable` and `plugins disable` mutation commands, not a list envelope. The `message` field contains the full rendered prose table (plugin name, version, and status as whitespace-aligned columns), but no `plugins` array with structured per-entry objects. `target` is `null` because no specific plugin was targeted. The `reload_runtime:false` field is meaningless for a read-only list operation. **This is distinct from ROADMAP #411** which covers the mutation commands' own missing `changed`/`previous_status`/`version`/`source` fields — #416 targets the list command's structural mismatch: it uses the mutation envelope entirely instead of emitting a dedicated list schema. **Required fix shape:** (a) emit a distinct `{kind:"plugin_list", plugins:[{name, version, status, source, path?, description?}], count}` envelope for the `list` action; (b) omit `action`, `reload_runtime`, and `target` from list responses (mutation-only fields); (c) the `message` field should be absent or optional and must not be the sole machine-readable inventory surface; (d) add regression coverage proving `plugins list --output-format json` populates a `plugins` array with at least `name`, `version`, and `status` fields for each installed plugin. **Why this matters:** automation that calls `plugins list --output-format json` to discover installed plugin inventory receives only a whitespace-aligned prose table in a string field, with `reload_runtime:false` and `target:null` as the only other machine-readable signals — identical noise to what a failed enable command returns. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
418. **`system-prompt --output-format json` exposes `"__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__"` as a literal element in the `sections` array — an internal split delimiter leaked into the public structured output** — dogfooded 2026-04-30 by Jobdori on `e939777f`. Running `claw system-prompt --output-format json` returns `{"kind":"system-prompt","message":"<full prose>","sections":["You are an interactive agent...", "# System\n...", "# Doing tasks\n...", "# Executing actions with care\n...", "__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__", "# Environment context\n...", "# Project context\n...", "# Claude instructions\n...", "# Runtime config\n..."]}`. The `sections` array has 9 elements; element index 4 is the raw string `"__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__"`. This internal sentinel marks the boundary between the static and dynamic sections of the compiled system prompt, used during assembly to split the prompt at injection time. It appears in the public JSON output verbatim as a first-class section, indistinguishable from real sections by type alone. Automation that iterates `sections[]` must special-case this sentinel or it will process an internal implementation string as if it were a real system prompt section. **Required fix shape:** (a) strip `"__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__"` and any similar internal delimiters from the `sections` array before serializing to JSON; (b) if the static/dynamic boundary is semantically meaningful for callers, expose it as a structured metadata field such as `boundary_index:4` or as a `section_type:"static"|"dynamic"` field on each section entry, not as a raw sentinel string in the array; (c) rename the `sections` type from `string[]` to `[{id, type, content}]` to enable this without breaking the boundary signal; (d) add regression coverage proving the `system-prompt --output-format json` output's `sections` array contains no elements whose value equals `"__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__"` or matches `/__[A-Z_]+__/`. **Why this matters:** internal sentinel strings in public JSON are a contract liability — they couple the wire format to internal implementation details. Any refactor that renames or removes the sentinel breaks callers that don't special-case it, and automation that doesn't know to filter it will miscount, misparse, or misrender the system prompt. Source: Jobdori live dogfood, `e939777f`, 2026-04-30.
|
||||
|
||||
|
||||
419. **`mcp <unknown-subcommand> --output-format json` returns `action:"help"` + `unexpected:<arg>` with exit 0 instead of an error envelope — unrecognized MCP subcommands silently succeed** — dogfooded 2026-05-01 by Jobdori on `e939777f`. Running `claw mcp add --output-format json` or `claw mcp remove --output-format json` (subcommands that do not exist) returns exit 0 with stdout JSON `{"action":"help","kind":"mcp","unexpected":"add","usage":{"direct_cli":"claw mcp [list|show <server>|help]","slash_command":"/mcp [list|show <server>|help]","sources":[...]}}`. Exit code is 0. The `action` field is `"help"` — not `"error"` — even though the caller issued a recognized token (`add`/`remove`) that maps to a real but unimplemented feature. The `unexpected` field correctly identifies the unrecognized arg, but automation that checks `exit == 0` or `action != "error"` will treat this as a successful invocation. This is distinct from ROADMAP #108 which covers *unrecognized CLI subcommands* falling through to the LLM prompt path — #419 targets MCP-specific *known-but-unimplemented* subcommands that return `action:"help"` with exit 0 instead of an explicit `action:"error"` envelope. **Required fix shape:** (a) return a non-zero exit code (exit 1 or exit 2) when an unrecognized or unimplemented MCP subcommand is provided; (b) emit `action:"error"` (or `kind:"error"`) with a `code:"unknown_subcommand"` and `unknown:"add"` field instead of `action:"help"`; (c) optionally include the help/usage payload as a sibling field `suggestion:{usage:{...}}` for context; (d) add regression coverage proving `mcp <unknown> --output-format json` returns a non-zero exit code and a non-help action token. **Why this matters:** `add` and `remove` are common MCP lifecycle operations that users will attempt; returning `action:"help"` with exit 0 makes these look like successful no-ops to any automation that doesn't deep-inspect the `unexpected` field. A pipeline that runs `claw mcp add my-server ... && claw mcp show my-server` will silently proceed to the show step even though add silently no-oped. Source: Jobdori live dogfood, `e939777f`, 2026-05-01.
|
||||
|
||||
|
||||
420. **`plugins help --output-format json` returns the mutation response shape (`message`, `reload_runtime`, `target`) instead of the help envelope (`action:"help"`, `kind`, `unexpected`, `usage`) that `mcp help`, `agents help`, and `skills help` all use — schema drift within the same command family** — dogfooded 2026-05-01 by Jobdori on `e939777f`. Running `claw plugins help --output-format json` returns `{"action":"help","kind":"plugin","message":"Unknown /plugins action 'help'. Use list, install, enable, disable, uninstall, or update.","reload_runtime":false,"target":null}`. By contrast, `claw mcp help --output-format json`, `claw agents help --output-format json`, and `claw skills help --output-format json` all return a help envelope: `{"action":"help","kind":"<surface>","unexpected":null,"usage":{"direct_cli":"...","slash_command":"...","sources":[...]}}`. The `plugins` subgroup has not adopted the help envelope schema used by all sibling subgroups. Instead it uses the mutation response shape (`message`, `reload_runtime`, `target`) with an error string in `message` that calls `help` an "unknown action." Automation that checks `usage.direct_cli` to discover plugin commands gets a `TypeError` (key not found) on the plugins help path while succeeding on all sibling subgroups. **Required fix shape:** (a) make `plugins help` return the same help envelope as `mcp help`/`agents help`/`skills help`: `{action:"help", kind:"plugin", unexpected:null, usage:{direct_cli:"claw plugins [list|enable|disable|install|uninstall|update|help]", slash_command:"/plugins [...]", sources:[...]}`; (b) drop `reload_runtime` and `target` from help responses for all plugin subcommands; (c) add regression coverage proving `plugins help --output-format json` contains a `usage.direct_cli` field matching the same envelope shape as `mcp help`/`agents help`/`skills help`; (d) audit all subgroup `help` handlers for the same mutation-envelope contamination. **Why this matters:** help discovery is the bootstrap surface for automation. If `plugins help --output-format json` returns a mutation envelope with an error message instead of a usage envelope, automated schema discovery fails silently for the entire plugins subgroup while working for every other subgroup. Source: Jobdori live dogfood, `e939777f`, 2026-05-01.
|
||||
|
||||
|
||||
421. **`status`, `mcp list`, `doctor` JSON output leak macOS `/private` symlink-canonicalized cwd instead of user-invocation cwd — automation that string-matches on cwd breaks across symlinked filesystems** — dogfooded 2026-05-11 by Jobdori on `b98b9a71` in response to Clawhip pinpoint nudge at `1503207549447573574`. Reproduction on macOS: invoke from `/tmp/claw-dog-cwd` (where `/tmp` symlinks to `/private/tmp`), then `claw status --output-format json` returns `workspace.cwd: "/private/tmp/claw-dog-cwd"`, `claw mcp list --output-format json` returns `working_directory: "/private/tmp/claw-dog-cwd"`. The user's invocation cwd (`$PWD`, `pwd`) is `/tmp/claw-dog-cwd`. Source: `session_control.rs:34` calls `fs::canonicalize(cwd)` for #151 cross-worktree session-bleed prevention, then leaks the canonicalized path through every JSON envelope that reports cwd. **Required fix shape:** (a) keep canonicalized cwd for session keying internally, but report user-input cwd (the value passed by `env::current_dir()` or `--cwd` flag) in JSON output as `cwd`; (b) optionally expose canonical path as a separate field `cwd_canonical` for diagnostic purposes; (c) audit every `--output-format json` surface that emits `cwd` / `working_directory` / `workspace.cwd` for the same leak (status, mcp list, doctor, session list, init, etc.); (d) add regression coverage proving JSON cwd matches `$PWD` on macOS where `/tmp -> /private/tmp` symlink exists. **Why this matters:** automation pipelines that route work to lanes by cwd, or that compare cwd against a registry, break across macOS hosts because the canonicalized form differs from the form the user/orchestrator passed. The leak is silent — no documentation indicates the path will be rewritten. Source: Jobdori live dogfood, `b98b9a71`, 2026-05-11.
|
||||
|
||||
|
||||
422. **Unknown top-level subcommands fall through to chat prompt path instead of returning `unknown_subcommand` error — typos silently send the subcommand string as a chat message to the configured LLM** — dogfooded 2026-05-11 by Jobdori on `b98b9a71` in response to Clawhip pinpoint nudge at `1503215095088676956`. Reproduction: `unset ANTHROPIC_AUTH_TOKEN; export ANTHROPIC_API_KEY=fake-key-for-routing-test; claw completely-bogus-subcommand --output-format json` returns `{"error":"api returned 401 Unauthorized (authentication_error) [trace req_011...]: invalid x-api-key","kind":"api_http_error"}` — proving the unknown token reached the Anthropic API endpoint as a chat prompt. With valid credentials, the bogus subcommand string would be silently consumed as a chat message, billing the user for a typo and producing whatever continuation the LLM generates. **Pre-error path:** `claw <unknown> --output-format json` with no creds returns `kind:"missing_credentials"` (the auth gate fires first), masking the routing bug. Only with creds present does the fallthrough manifest as the actual prompt being sent. **Sibling exit-code bug:** when the chat-path 401 returns, the JSON envelope is `kind:"api_http_error"` but exit code is **0**, while `cli_parse` errors (e.g. `--no-such-flag`) and `missing_credentials` errors correctly exit **1**. Exit-code parity between error envelopes is broken — automation that gates on `$?` will treat the 401-as-chat as success. **Required fix shape:** (a) reserve unknown top-level tokens that match no registered subcommand and emit `kind:"unknown_subcommand"` with `unknown:<token>` field and exit code 1, BEFORE the chat fallback path; (b) when a token is intended as a chat prompt, require an explicit verb (`prompt`, `chat`, `ask`) or `--prompt` flag; (c) ensure exit codes are non-zero for all `kind:*_error` envelopes; (d) regression test: `claw <bogus> --output-format json` with valid auth returns `kind:"unknown_subcommand"` exit 1, never reaches the API. **Why this matters:** automation that calls `claw <subcommand>` with a programmatically constructed verb (typo, version drift, refactored command) silently bills tokens and produces hallucinated output instead of a typed error. Cross-cluster with #108 (CLI fallthrough discovered earlier) — #422 is the post-#108 audit confirming the routing bug still bites with valid credentials. Source: Jobdori live dogfood, `b98b9a71`, 2026-05-11.
|
||||
|
||||
|
||||
423. **`claw prompt` does not read prompt text from stdin when no positional prompt arg is provided — `echo "what is 2+2" | claw prompt --output-format json` returns `kind:"unknown" error:"prompt subcommand requires a prompt string"` instead of consuming stdin** — dogfooded 2026-05-11 by Jobdori on `3c563fa1` in response to Clawhip pinpoint nudge at `1503222644739276951`. Reproduction: `echo "what is 2+2" | claw prompt --output-format json` → `{"error":"prompt subcommand requires a prompt string","hint":null,"kind":"unknown","type":"error"}` exit 1. Same for `claw prompt --output-format json` with stdin redirected from a file. The most common Unix automation pattern (`cmd | claw prompt`) is broken because the prompt subcommand only reads the positional argument, never falls through to stdin. **Sibling envelope-kind bug:** the error `kind` is `"unknown"` instead of a typed `"missing_argument"` or `"validation_error"`. The `unknown` discriminator is the catch-all bucket — automation that switches on `kind` to differentiate input-validation errors from runtime errors gets no signal here. **Required fix shape:** (a) when `prompt` subcommand has no positional prompt arg AND stdin is not a TTY (i.e., piped or redirected), read stdin to EOF and use that as the prompt; (b) emit `kind:"missing_argument"` (not `"unknown"`) when both positional arg and stdin are absent; (c) add `--prompt-stdin` or `--stdin` opt-in flag for explicit control; (d) regression tests: `echo X | claw prompt --output-format json` reaches the runtime with prompt=X, AND `claw prompt < /dev/null` returns `kind:"missing_argument"` exit 1. **Why this matters:** Unix pipelines are the foundation of CLI automation. Every other major CLI (curl, jq, gh, kubectl) accepts stdin as the primary input when no positional arg is given. Breaking this convention forces automation to either inline the prompt as a shell-quoted string (escaping nightmare for multiline/code) or write to a temp file first. The `kind:"unknown"` error category compounds the problem by making the failure indistinguishable from a runtime crash. Source: Jobdori live dogfood, `3c563fa1`, 2026-05-11.
|
||||
|
||||
|
||||
424. **`--model` rejects bare canonical Anthropic model names (`claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`) as `invalid_model_syntax` — only short aliases (`opus`, `sonnet`, `haiku`) and full prefixed form (`anthropic/claude-opus-4-7`) work; sibling: error message stale-suggests `claude-opus-4-6` not `4-7`** — dogfooded 2026-05-11 by Jobdori on `6c0c305a` in response to Clawhip pinpoint nudge at `1503230194889134103`. Reproduction: `claw --model claude-opus-4-7 status --output-format json` → `{"error":"invalid model syntax: 'claude-opus-4-7'. Expected provider/model (e.g., anthropic/claude-opus-4-6) or known alias (opus, sonnet, haiku)","kind":"invalid_model_syntax"}`. Same for `claude-opus-4-6`, `claude-sonnet-4-6`. Forcing `--model anthropic/claude-opus-4-7` works (`model:"anthropic/claude-opus-4-7"`, `model_source:"flag"`). Three problems compounded: (a) Anthropic-canonical model names without provider prefix are rejected even though the `claude-` prefix unambiguously identifies the provider; (b) the error suggests `anthropic/claude-opus-4-6` as the example — `4-7` shipped 2026-04-16 and is the current production Anthropic frontier model, the suggestion is one model behind; (c) the alias list `opus, sonnet, haiku` doesn't disambiguate version (which `opus` does the alias resolve to — `opus-4-6` or `opus-4-7`?). **Required fix shape:** (a) accept bare `claude-*` and `gpt-*` model names as canonical-named-without-prefix and route via name-prefix detection (already implemented for prefix-routed mode); (b) update the example in `invalid_model_syntax` error to current frontier (`anthropic/claude-opus-4-7`); (c) document or expose `opus` → exact-version mapping in the error message and in `claw doctor`/`status` output (`model_alias_resolved_to: "claude-opus-4-7"`); (d) regression test: `claw --model claude-opus-4-7 status --output-format json` returns `model_source:"flag"`, not `kind:"invalid_model_syntax"`. **Sibling bug observed in same probe:** `enabledPlugins` deprecation warning repeats 3 times in stderr for the same `~/.claw/settings.json` load — config file is being loaded/parsed 3 times during a single `status` invocation. **Why this matters:** every Anthropic doc, every CCAPI route, every internal tooling references models by their bare canonical name (`claude-opus-4-7`). Forcing the `anthropic/` prefix breaks copy-paste from Anthropic's own examples and adds a redundant token to every invocation. The stale `4-6` suggestion in the error message actively misdirects users away from the current model. Source: Jobdori live dogfood, `6c0c305a`, 2026-05-11.
|
||||
|
||||
|
||||
425. **Config file precedence (`.claw/settings.json` always wins over `.claw.json`) is undocumented in user-facing surfaces — `config --output-format json` reports both files as `loaded:true` with no `precedence_rank` or `wins_for_keys` attribution; sibling: deprecation warning fires 4× per status invocation (was 3× in #424, regression upward)** — dogfooded 2026-05-11 by Jobdori on `d7dbe951` in response to Clawhip pinpoint nudge at `1503237744451649537`. Reproduction: create `.claw.json` with `{"model":"anthropic/claude-sonnet-4-6"}` and `.claw/settings.json` with `{"model":"anthropic/claude-opus-4-7"}` in the same workspace. `claw status --output-format json` returns `model:"anthropic/claude-opus-4-7", model_source:"config"`. Reverse the files (.claw.json=opus, settings.json=sonnet) → `model:"anthropic/claude-sonnet-4-6"`. Confirmed: `.claw/settings.json` **always** wins over `.claw.json` for conflicting keys, regardless of file mtime or alphabetical order. `claw config --output-format json` reports both as `loaded:true` with no `precedence_rank`, `effective_for_keys`, or `shadowed_keys` attribution. The only signal of precedence is the final merged value in `status` — automation cannot programmatically discover which file contributed which key without re-implementing the merge logic. **Sibling bug (regression from #424):** the `enabledPlugins` deprecation warning now fires **4 times** in stderr per single `status` invocation (was 3× in #424's probe at HEAD `6c0c305a`; current HEAD `d7dbe951` shows 4×). Config load count went up by 1. **Sibling bug observed in config-section probe:** `claw config model --output-format json` with a `.claw.json` that contains a benign unknown key (e.g., `"alpha":"x"`) returns `{"error":"/path/.claw.json: unknown key \"alpha\" (line 1)","kind":"unknown"}` — the entire config command fails with a generic `unknown` kind instead of (a) tolerating unrecognized keys with a warning, or (b) emitting a typed `kind:"unknown_key"` error scoped to the offending file/key. **Required fix shape:** (a) document precedence order in `USAGE.md` (`.claw/settings.local.json > .claw/settings.json > .claw.json` for project scope; `user`/`system` scope at each layer); (b) add `precedence_rank:int` and optional `wins_for_keys:[string]` / `shadowed_keys:[string]` to each entry in `config --output-format json` `files[]`; (c) dedupe the deprecation warning to fire **once per discovered file** instead of N× per load pass; (d) make `config <section> --output-format json` tolerate unknown keys with warnings, OR emit `kind:"unknown_key"` with `path:` and `key:` fields scoped to the offending file. **Why this matters:** users mixing legacy `.claw.json` with new `.claw/settings.json` have no way to verify which file is actually controlling their runtime. The undocumented precedence + missing per-key attribution forces trial-and-error to debug config drift. Cross-references #407 (config files no load_error) and #415 (config section returns merged_keys count not values). Source: Jobdori live dogfood, `d7dbe951`, 2026-05-11.
|
||||
|
||||
|
||||
426. **`ANTHROPIC_MODEL` env var bypasses the `invalid_model_syntax` validator that `--model` enforces — bogus model strings are accepted with `status:"ok"`, deferred-failing only when the first API call is made** — dogfooded 2026-05-11 by Jobdori on `3730b459` in response to Clawhip pinpoint nudge at `1503245298800136296`. Reproduction (asymmetric validation): `claw --model bogus-model-xyz status --output-format json` returns `kind:"invalid_model_syntax"` exit 1; `ANTHROPIC_MODEL=bogus-model-xyz claw status --output-format json` returns `model:"bogus-model-xyz", model_raw:"bogus-model-xyz", model_source:"env", status:"ok"` — the doctor surface lies that the configured model is valid when it is not. The bogus model only manifests as a failure when the first prompt fires and the API rejects it with 404/400. Three sibling discoveries in the same probe: (a) **alias indirection invisible**: `ANTHROPIC_MODEL=opus claw status --output-format json` returns `model:"claude-opus-4-6", model_raw:"opus", model_source:"env"` — the `opus` alias resolves to `claude-opus-4-6` (the *previous* frontier, not the current `claude-opus-4-7` released 2026-04-16). Users typing `opus` get yesterday's model with no warning. (b) **`CLAW_MODEL` env var silently ignored**: `CLAW_MODEL=opus claw status` shows `model:"claude-opus-4-6" model_source:"default"` — the `CLAW_MODEL` env var (the project-namespaced equivalent that users expect) does not exist; only `ANTHROPIC_MODEL` is honored. No warning when a `CLAW_*` env var that looks like it should work is set. (c) **`ANTHROPIC_DEFAULT_MODEL` also silently ignored**: the longer-named env var that some Anthropic SDKs use is not recognized. **Required fix shape:** (a) symmetric validation: `ANTHROPIC_MODEL` env value must pass the same `invalid_model_syntax` check that `--model` does, and `claw status` must return `kind:"invalid_model"` / `status:"warn"` (not `status:"ok"`) when the resolved model is unrecognized; (b) expose alias resolution in `status`: add `model_alias_resolved_to:string|null` field so automation can see `opus → claude-opus-4-6`; (c) bump the `opus` alias to `claude-opus-4-7` (current frontier) or document the alias-to-version mapping policy explicitly; (d) accept `CLAW_MODEL` and `ANTHROPIC_DEFAULT_MODEL` env vars with parity to `ANTHROPIC_MODEL`, OR emit a warning when those env vars are set but unrecognized. **Why this matters:** the most common automation pattern is `export ANTHROPIC_MODEL=...` in a shell rc file. Bogus values pass silently, alias indirection hides the actual model in use, and `CLAW_MODEL` looking like a working name but doing nothing is a footgun. Cross-references #424 (bare canonical names rejected at validator level) — together #424 + #426 make model selection inconsistent across CLI flag, env var, and alias paths. Source: Jobdori live dogfood, `3730b459`, 2026-05-11.
|
||||
|
||||
|
||||
427. **Subcommand `--help` paths (`resume`, `session`, `compact`) hit the auth gate and trigger config validation before returning static help — `claw resume --help` with no credentials returns `missing_credentials` error instead of help text** — dogfooded 2026-05-11 by Jobdori on `1fecdf09` in response to Clawhip pinpoint nudge at `1503252843669491892`. Reproduction (no env vars, isolated `CLAW_CONFIG_HOME`): `claw resume --help` returns `{"error":"missing Anthropic credentials; export ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY..."}` instead of usage text. Same for `claw session --help`, `claw compact --help`. By contrast, `claw prompt --help` and `claw --help` (top-level) return proper usage text without auth. Even worse: with a broken `.claw.json` discovered up the parent directory tree (e.g., `mcpServers.missing-command: missing string field command`), the subcommand `--help` paths fail with `[error-kind: unknown]` from config validation — config load is happening before `--help` is parsed. **Sibling exit-code bug:** `claw resume --help --output-format json` returns `kind:"missing_credentials"` but exits **0** (the exit-code parity bug from #422 reproduces on this path too — only `cli_parse` exits 1 consistently). **Sibling: `claw resume <bogus-id>` should be local-only** but also hits `missing_credentials` — `resume` of a session that doesn't exist on disk should return `kind:"session_not_found"` from a local lookup, not require API credentials. Same class as ROADMAP #357 (session list requires creds) and #369 (session help/fork require credentials) — now confirmed for `resume`. **Required fix shape:** (a) `--help` MUST short-circuit before any auth check, config load, or session resolution — emit static usage text from a compiled-in string table, no I/O; (b) `resume <id>` must check the local session store first; if the id is absent on disk, emit `kind:"session_not_found"` with `sessions_dir` field; only require auth when resuming a known-on-disk session that requires re-establishing API context; (c) ensure exit code 1 for all error envelopes including `missing_credentials` returned from a `--help` path that should never have reached the auth gate; (d) regression test: with empty `CLAW_CONFIG_HOME` and no env vars, every `claw <subcommand> --help` returns usage text on stdout, exit 0, no `kind:*_error` envelope. **Why this matters:** `--help` is the universal CLI discovery primitive. Failing `--help` because of missing API credentials or broken config files makes claw undiscoverable to users debugging an already-broken setup. Cross-references #357 (session list), #369 (session help/fork), #422 (exit code parity), #108 (subcommand fallthrough). Source: Jobdori live dogfood, `1fecdf09`, 2026-05-11.
|
||||
|
||||
|
||||
428. **Default `permission_mode` is `danger-full-access` — claw runs with FULL filesystem + network + tool access out of the box, with no opt-in flag and no warning from `doctor`** — dogfooded 2026-05-11 by Jobdori on `72048449` in response to Clawhip pinpoint nudge at `1503260393622212628`. Reproduction (no env vars, isolated `CLAW_CONFIG_HOME`, no config files, no CLI flags): `claw status --output-format json` returns `permission_mode:"danger-full-access"` as the default. The three supported modes per the validator error message are `read-only`, `workspace-write`, `danger-full-access` — and `danger-full-access` is chosen with zero user opt-in. `claw doctor --output-format json` produces a `sandbox` check with `status:"warn", summary:"sandbox was requested but is not currently active"` (because macOS lacks Linux `unshare`), but **emits no warning, info, or summary about the permission_mode itself being danger-full-access**. There is no `permissions` check in `doctor` output at all. **Required fix shape:** (a) change default `permission_mode` to `workspace-write` (safe-by-default: filesystem write limited to cwd, network limited to LLM endpoints, no arbitrary command exec); (b) require explicit `--permission-mode danger-full-access` or `--dangerously-skip-permissions` to opt into full access; (c) add a `permissions` check to `doctor --output-format json` that emits `status:"warn"` when `permission_mode == "danger-full-access"` without explicit source (flag/env/config), with details like `mode:"danger-full-access", source:"default", message:"running with full access without explicit opt-in"`; (d) document the three modes and the default in USAGE.md with one-paragraph descriptions of what each mode allows. **Sibling typed-error bug:** `claw --permission-mode bogus-mode status --output-format json` returns `kind:"unknown"` instead of `kind:"invalid_permission_mode"` — same catch-all problem as #424, #426. **Sibling flag-name asymmetry:** `--dangerously-skip-permissions` works but `--skip-permissions` (Claude Code's flag) returns `kind:"cli_parse"` `unknown option`. Users migrating from Claude Code lose the short flag name. **Why this matters:** every other security-conscious CLI (Docker, kubectl, terraform) requires explicit opt-in for dangerous modes. Defaulting to `danger-full-access` is a footgun for first-time users who pipe `curl install.sh | sh` and immediately get a tool with full filesystem write and arbitrary command exec. The doctor surface is the only diagnostic users consult before trusting the tool, and it stays silent about the most permissive setting. Cross-references #50, #87, #91, #94, #97, #101, #106, #115, #123 (permission-audit sweep) — those all cover permission *rule* and *list* surfaces; #428 covers the *mode default* itself. Source: Jobdori live dogfood, `72048449`, 2026-05-11.
|
||||
|
||||
|
||||
429. **No global `--cwd`/`-C`/`--directory` flag — `claw` cannot be invoked against an arbitrary working directory without first `cd`-ing into it; `--cwd` only exists as a subcommand option for `system-prompt`, and the `cli_parse` "Did you mean --acp?" suggestion is misleading (the `--acp` flag is unrelated to directory selection)** — dogfooded 2026-05-11 by Jobdori on `ec882f4c` in response to Clawhip pinpoint nudge at `1503267943285264394`. Reproduction: `claw --cwd /tmp/claw-dog-cwd status --output-format json` → `{"error":"unknown option: --cwd","hint":"Did you mean --acp?\nRun `claw --help` for usage.","kind":"cli_parse"}`. Same error for `--cwd <relative>`, `--cwd <nonexistent>`, `--cwd <file-not-dir>`, `--cwd ""`. Inspecting `claw --help`: `--cwd PATH` appears ONLY in the usage line `claw system-prompt [--cwd PATH] [--date YYYY-MM-DD]` — it is not a global flag and is not accepted by `status`, `doctor`, `mcp list`, `init`, or any other subcommand. Users programmatically running claw against multiple workspaces must `cd` into each one before invoking, breaking the `subprocess.run(['claw', 'status', '--cwd', ws], cwd=other_dir)` pattern that every other major CLI (cargo `-C`, git `-C`, npm `--prefix`, gh `--repo` semantically, kubectl `--kubeconfig`+`--context`) supports. **Sibling misleading-suggestion bug:** the `cli_parse` error's `hint` field suggests `Did you mean --acp?` for `--cwd`. `--acp` is the alias for ACP/Zed editor integration (entirely unrelated to working directory). The Levenshtein-distance auto-complete is matching on first-character similarity without considering semantic relatedness. Users following the hint get a totally orthogonal feature. **Required fix shape:** (a) add a global `--cwd PATH` / `-C PATH` flag accepted before any subcommand, parsed in the global flag pre-pass; (b) validate the path exists and is a directory; emit `kind:"invalid_cwd"` with `path:` and `reason:` (`"not_found"`/`"not_a_directory"`/`"empty"`) when validation fails; (c) document the precedence: `--cwd` flag > `$PWD` > `env::current_dir()`; (d) fix the "Did you mean" hint algorithm to filter suggestions by semantic category (don't suggest `--acp` for `--cwd`; suggest `claw system-prompt --cwd PATH` if the user clearly wants `cwd` override but used the wrong scope); (e) regression test: `claw --cwd /tmp status --output-format json` from any `$PWD` returns `workspace.cwd:"/private/tmp"` (or `cwd:"/tmp"` after #421 fix). **Why this matters:** every claw automation orchestrator runs claw against multiple workspaces from a single parent process. Forcing `cd` before each invocation breaks parallelism (can't use shared cwd across concurrent invocations), breaks subprocess wrappers that want to pass cwd explicitly, and breaks `xargs`/`parallel`-style pipelines. Cross-references #421 (cwd canonicalization leak — fix should canonicalize but report user-input via `--cwd`). Source: Jobdori live dogfood, `ec882f4c`, 2026-05-11.
|
||||
|
||||
|
||||
430. **`dump-manifests` is documented as "emit every skill/agent/tool manifest the resolver would load for the current cwd" but actually requires the upstream Claude Code TypeScript source files (`src/commands.ts`, `src/tools.ts`, `src/entrypoints/cli.tsx`) — the command is unusable for any user who installed claw without cloning the original Claude Code repo** — dogfooded 2026-05-11 by Jobdori on `075c2144` in response to Clawhip pinpoint nudge at `1503275502046023690`. Reproduction: `claw dump-manifests --output-format json` returns `{"error":"Manifest source files are missing.","hint":"repo root: /private/tmp/claw-dog-0530\n missing: src/commands.ts, src/tools.ts, src/entrypoints/cli.tsx\n Hint: set CLAUDE_CODE_UPSTREAM=/path/to/upstream or pass \`claw dump-manifests --manifests-dir /path/to/upstream\`.","kind":"missing_manifests"}`. The fresh-main worktree at `/private/tmp/claw-dog-0530` does not contain these TypeScript files because the Rust port doesn't include the upstream TS source. The `--help` text says the command works against "the current cwd" but in practice it requires `CLAUDE_CODE_UPSTREAM=` pointing at an unshipped TS source tree. **Three sibling problems compounded:** (a) **derivative-work disclosure leak**: the error message exposes that `claw-code` is a port of Claude Code (`CLAUDE_CODE_UPSTREAM` env var name) — even if true, surfacing this in a casual diagnostic message couples user-facing behavior to upstream provenance details. (b) **kind drift**: `claw dump-manifests --manifests-dir /tmp/nonexistent --output-format json` returns `kind:"unknown"`, while `claw dump-manifests` (no override) returns `kind:"missing_manifests"`. Same root cause (no usable upstream), two different `kind` discriminators — automation cannot switch on a single error type. (c) **export-positional-arg silently dropped**: probed in the same run — `claw export <bogus-positional>` ignores the path and returns `kind:"no_managed_sessions"` regardless of what positional arg was passed. The `--help` advertises `[PATH]` as the output-file destination but the path is discarded before validation, indistinguishable from invocation with no args. **Required fix shape:** (a) make `dump-manifests` emit the manifests claw-code itself ships with (Rust-resolver-discovered skills/agents/tools), independent of any upstream TS source — that matches the `--help` description; (b) if upstream-comparison is genuinely needed for parity work, move it to a separate command like `parity dump-upstream-manifests` and remove the upstream dependency from `dump-manifests`; (c) standardize on one error `kind` for the manifest-missing failure mode (`missing_manifests` is more descriptive than `unknown`); (d) `claw export <PATH>` must validate the path positional arg before the session-discovery check, so users see `kind:"invalid_output_path"` (or similar) when the path is malformed instead of always seeing `kind:"no_managed_sessions"`. **Why this matters:** `dump-manifests` is the inventory surface a downstream automation lane would call to learn what claw can do in the current workspace. If it's broken without upstream TS source, downstream lanes can't introspect — they have to fall back to `agents list`/`skills list`/`mcp list` separately and re-aggregate. Cross-references #422 (kind:unknown for unknown_subcommand), #423 (kind:unknown for missing_argument), #428 (kind:unknown for invalid_permission_mode) — `kind:"unknown"` keeps appearing as the catch-all for surfaces that should have typed kinds. Source: Jobdori live dogfood, `075c2144`, 2026-05-11.
|
||||
|
||||
|
||||
431. **`skills uninstall <name>` requires Anthropic credentials despite being a local filesystem operation — `claw skills uninstall nonexistent-skill-xyz --output-format json` returns `kind:"missing_credentials"` instead of resolving locally that the skill doesn't exist** — dogfooded 2026-05-11 by Jobdori on `328fd114` in response to Clawhip pinpoint nudge at `1503275502046023690` (sibling probe to #430). Reproduction (no creds, isolated `CLAW_CONFIG_HOME`): `claw skills uninstall nonexistent-skill-xyz --output-format json` returns `{"error":"missing Anthropic credentials; export ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY...","kind":"missing_credentials"}`. Uninstalling a skill is a pure local filesystem operation: read the skills directory, find the named skill, remove its files. There is no semantic reason to require API credentials. Same class of bug as #357 (`session list` requires creds), #369 (`session help/fork` require creds), and #427 (`resume <bogus-id>` requires creds). **Three sibling findings in same probe:** (a) `claw skills install <bogus-name>` returns `{"error":"No such file or directory (os error 2)","kind":"unknown"}` — leaks raw OS error string with no hint about expected install source format (path vs name vs URL?), and the catch-all `kind:"unknown"` again instead of typed `kind:"skill_install_source_not_found"`. (b) `claw skills install` (no args) returns `action:"help"` with `unexpected:"install"` — but `install` IS a documented subcommand. The handler treats it as "unknown action" instead of "missing required argument". Should emit `kind:"missing_argument"` with `argument:"install_source"`. (c) `claw agents create my-agent` returns `action:"help"` with `unexpected:"create my-agent"` — there is no agent-creation surface at all. Users must hand-craft `.claw/agents/<name>.md` files with no scaffolding command, while `claw init` only creates the top-level `.claw/` skeleton. **Required fix shape:** (a) `skills uninstall <name>` must be local-first: enumerate the local skills dir, return `kind:"skill_not_found"` (with `skills_dir:` and `available_names:[]` fields) for missing, or remove the files and return `kind:"skills"` with `action:"uninstall", removed:<name>` for present skills; (b) `skills install <source>` must distinguish source forms (`path:`, `name:`, `url:`) and emit `kind:"invalid_install_source"` with the parsed-and-failed reason; (c) `skills install` (no args) emits `kind:"missing_argument"` with `argument:"install_source"`; (d) add `claw agents create <name>` (or `claw init agent <name>`) that scaffolds `.claw/agents/<name>.md` with a stub frontmatter; or document explicitly that agents are user-authored only. **Why this matters:** lifecycle commands (`uninstall`, `install`, `create`) are the primary surface for managing claw's extension surface area. If `uninstall` requires API creds, an offline user who fat-fingered an install can't undo it. If `install` returns a raw OS error, automation can't programmatically recover. If `agents create` doesn't exist, agent authoring is undocumented file-touching only. Cross-references #357, #369, #427 (auth-gate-on-local-ops cluster), and #422/#423/#428/#430 (`kind:"unknown"` catch-all cluster). Source: Jobdori live dogfood, `328fd114`, 2026-05-11.
|
||||
|
||||
|
||||
432. **`--allowedTools` validator inconsistency: tool name list is half snake_case (`bash`, `read_file`, `write_file`, `edit_file`, `glob_search`, `grep_search`) and half PascalCase (`WebFetch`, `WebSearch`, `TodoWrite`, `Skill`, `Agent`, `Sleep`) with three UPPERCASE entries (`REPL`, `LSP`, `MCP`); accepts undocumented CamelCase aliases (`Read`, `Write`, `Edit`) and silently translates them to snake_case; argument parsing consumes the next positional when value is missing** — dogfooded 2026-05-11 by Jobdori on `fad53e2d` in response to Clawhip pinpoint nudge at `1503283046856655029`. Reproduction: `claw --allowedTools status --output-format json` → `{"error":"unsupported tool in --allowedTools: status (expected one of: bash, read_file, write_file, edit_file, glob_search, grep_search, WebFetch, WebSearch, TodoWrite, Skill, Agent, ToolSearch, NotebookEdit, Sleep, SendUserMessage, Config, EnterPlanMode, ExitPlanMode, StructuredOutput, REPL, PowerShell, AskUserQuestion, TaskCreate, RunTaskPacket, TaskGet, TaskList, TaskStop, TaskUpdate, TaskOutput, WorkerCreate, WorkerGet, WorkerObserve, WorkerResolveTrust, WorkerAwaitReady, WorkerSendPrompt, WorkerRestart, WorkerTerminate, WorkerObserveCompletion, TeamCreate, TeamDelete, CronCreate, CronDelete, CronList, LSP, ListMcpResources, ReadMcpResource, McpAuth, RemoteTrigger, MCP, TestingPermission)","kind":"unknown"}`. The `status` subcommand was consumed as the `--allowedTools` value because the flag parser doesn't distinguish missing-value from end-of-flag-args. The error reveals **the supported tool list mixes naming conventions inconsistently within a single error message**: snake_case (`bash`, `read_file`, `write_file`, `edit_file`, `glob_search`, `grep_search`), PascalCase (`WebFetch`, `WebSearch`, `TodoWrite`, `Skill`, `Agent`, `Sleep`, `Config`, `PowerShell`, `AskUserQuestion`, `TaskCreate`, `WorkerCreate`, `TeamCreate`, `CronCreate`), UPPERCASE (`REPL`, `LSP`, `MCP`), and CamelCase compounds (`McpAuth`, `RemoteTrigger`). **Hidden alias mapping**: `claw --allowedTools Read,Write,Edit status --output-format json` is accepted and returns `allowed_tools.entries:["edit_file","read_file","write_file"]` — proving the validator has an undocumented CamelCase→snake_case alias map (`Read`→`read_file`, `Write`→`write_file`, `Edit`→`edit_file`) that is not surfaced in the error message. Users who copy-paste tool names from Claude Code documentation work, users who copy from the validator error don't. **Sibling missing-value bug:** `claw --allowedTools status` with `status` as a positional subcommand is interpreted as `--allowedTools=status`, swallowing the subcommand. The flag parser must require a value for `--allowedTools` and emit `kind:"missing_argument"` when followed by a recognized subcommand or `--`-prefixed flag instead of silently treating the next arg as a tool name. **Sibling typed-kind bug:** both errors use `kind:"unknown"` instead of typed `kind:"invalid_tool_name"` / `kind:"missing_argument"` — the catch-all keeps appearing (#422/#423/#424/#428/#430/#431/#432). **Required fix shape:** (a) standardize the canonical tool-name registry on one casing convention (snake_case is most CLI-ergonomic) and update both the registry and all CamelCase aliases; (b) document and expose the alias map (`tool_aliases:{Read:"read_file",...}`) in `claw doctor`/`status` and in the validator error; (c) flag parser must require a value for `--allowedTools` and refuse to consume a recognized subcommand or `-`/`--`-prefixed token as the value, emit `kind:"missing_argument"` with `argument:"--allowedTools"`; (d) emit `kind:"invalid_tool_name"` with `tool_name:` and `available:[]` fields instead of `kind:"unknown"`; (e) regression test that `claw --allowedTools <subcommand>` rejects with `missing_argument`, and that the canonical name list in errors uses the same casing as the alias map. **Why this matters:** `--allowedTools` is the primary surface for restricting claw's tool surface area (security-relevant). Inconsistent naming between the validator error and the alias map means users following the error message guidance pick names that work in some places and fail in others. The missing-value bug silently swallows a subcommand, leading to confusing "unsupported tool: status" errors when the user actually wanted to run `claw status`. Cross-references #94/#97/#101/#106/#115/#123 (permission-rule audit), #428 (default permission_mode), #422/#423/#424/#428/#430/#431 (`kind:"unknown"` catch-all). Source: Jobdori live dogfood, `fad53e2d`, 2026-05-11.
|
||||
|
||||
|
||||
433. **Repeated `--output-format` flag silently takes the last value without warning — `claw --output-format json --output-format text status` produces text output, no signal that the prior `json` was overridden; sibling: `--output-format` value is case-sensitive (`JSON` rejected as `kind:"unknown"`); sibling: no `CLAW_OUTPUT_FORMAT` env var for default format override** — dogfooded 2026-05-11 by Jobdori on `ce39d5c5` in response to Clawhip pinpoint nudge at `1503290592556220488`. Reproduction: `claw --output-format json --output-format text status` returns the text-format `Status\n Model claude-opus-4-6...` table — the first `--output-format json` was silently overridden. No warning, no `format_overridden:true` field, no stderr message. Scripts that compose flag arrays from multiple sources (`flags=("${BASE_FLAGS[@]}" --output-format json)` while `BASE_FLAGS` already contains `--output-format text`) silently get the wrong format. **Three sibling findings in same probe:** (a) **case-sensitivity drift**: `claw --output-format JSON status` returns `{"error":"unsupported value for --output-format: JSON (expected text or json)","kind":"unknown"}` — error message tells user to use lowercase `json` but doesn't accept the uppercase form that users often type from muscle memory. Most CLI flag-value validators (cargo, kubectl, gh) are case-insensitive for enum values or accept both forms with normalization. (b) **`kind:"unknown"` for invalid format value**: same catch-all bucket bug as #422/#423/#424/#428/#430/#431/#432 — should be `kind:"invalid_output_format"` with `value:` and `expected:["text","json"]` fields. (c) **no env-var default for output format**: `CLAW_OUTPUT_FORMAT=json claw status` silently ignored — no env override for the global default, forcing scripts to repeat `--output-format json` on every invocation. Other major CLIs honor `KUBECTL_OUTPUT=`, `AWS_DEFAULT_OUTPUT=`, `GH_NO_PROMPT=` etc. (d) **silently-ignored env vars `CLAW_LOG`/`RUST_LOG`**: no env-based log level control surfaced in `claw doctor` — debug logging requires undocumented `RUST_LOG=` (Rust convention) but `claw --help` doesn't mention either. **Required fix shape:** (a) repeated `--output-format` (or any flag that takes a value, not a count flag) emits a warning to stderr (`warning: --output-format specified multiple times; using last value 'text'`) and adds a `format_source:"flag", format_overridden:[]` field to the JSON envelope; (b) accept case-insensitive enum values for `--output-format` (`JSON`, `Json`, `json` all work), document the canonical lowercase form in `--help`; (c) emit `kind:"invalid_output_format"` (not `kind:"unknown"`) when value is invalid; (d) accept `CLAW_OUTPUT_FORMAT` env var as the default for `--output-format`, with flag-overrides-env precedence documented; (e) document `RUST_LOG` / `CLAW_LOG` in `--help` or doctor output as the log-level env vars; (f) regression test: repeated flag emits stderr warning + JSON metadata field; case-insensitive enum accepts all three casings; env-var default is honored when flag is absent. **Why this matters:** scripts that compose flag arrays from multiple sources (CI envs + per-invocation flags) silently get the wrong output format. Case-sensitive enum values trip up users typing from muscle memory. Missing env-var defaults force per-invocation flag repetition. Cross-references #422/#423/#424/#428/#430/#431/#432 (`kind:"unknown"` catch-all cluster). Source: Jobdori live dogfood, `ce39d5c5`, 2026-05-11.
|
||||
|
||||
|
||||
434. **POSIX `--` end-of-flags separator is not recognized — `claw -- "-prompt-with-dash"` returns `{"error":"unknown option: --","hint":"Did you mean -V?","kind":"cli_parse"}` instead of treating subsequent args as positional; shorthand prompt mode cannot accept dash-prefixed prompts at all** — dogfooded 2026-05-11 by Jobdori on `0e5f6958` in response to Clawhip pinpoint nudge at `1503298142286905484`. Reproduction: `claw -- "-prompt-with-dash" --output-format json` returns `{"error":"unknown option: --","hint":"Did you mean -V?\nRun \`claw --help\` for usage.","kind":"cli_parse"}`. The POSIX/GNU CLI convention — universally honored by cargo, git, npm, gh, kubectl, grep, ls, find, etc. — is that `--` terminates flag parsing and treats everything after it as positional arguments. claw rejects `--` itself as an unknown flag. **Sibling misleading-suggestion bug (recurring from #429):** the `cli_parse` hint suggests `Did you mean -V?` for `--`. `-V` is the version flag; `--` is the end-of-flags separator. They have no semantic relationship; the auto-complete is matching on prefix-character similarity only. **Sibling shorthand-prompt limitation:** `claw "-just a prompt" --output-format json` returns `{"error":"unknown option: -just a prompt","kind":"cli_parse"}` and `claw "--bogus-flag-like" --output-format json` returns the same. The shorthand non-interactive prompt mode (documented as `claw [--model MODEL] [--output-format text|json] TEXT`) cannot accept any TEXT that starts with `-` or `--`, even when the entire string is shell-quoted as a single token. Users must use the explicit `prompt` verb (`claw prompt "-prompt-with-dash"` works) to escape this, but the explicit verb is documented as alternative not required. **Required fix shape:** (a) accept POSIX `--` as the end-of-flags marker globally — every arg after `--` is positional; (b) shorthand prompt mode must distinguish "this looks like a flag" from "this is a quoted positional that happens to start with `-`" by looking at whether the token matches any registered flag name (`-h`, `-V`, `--help`, `--version`, etc.) — strings that don't match any flag should be treated as prompt text; (c) fix the "Did you mean" hint algorithm to filter by semantic category (don't suggest `-V` for `--`, suggest "use \`--\` to terminate flag parsing" if the user types just `--`); (d) regression test: `claw -- "-foo"` reaches the runtime with prompt=`-foo`; `claw "-not-a-flag"` is treated as shorthand prompt when no registered flag matches; canonical `--` is recognized. **Why this matters:** POSIX `--` is the universal mechanism for passing arbitrary text (filenames starting with `-`, prompts containing flag-like syntax, log lines, etc.) to a CLI. Failing on `--` makes claw fundamentally unergonomic in shell pipelines (`echo "-q for quiet" | xargs claw` fails). The shorthand-prompt limitation forces users to remember the `prompt` verb specifically when their prompt happens to start with `-`. Cross-references #422 (unknown subcommand fallthrough), #423 (stdin not consumed by prompt), #429 ("Did you mean --acp" misleading suggestion). Source: Jobdori live dogfood, `0e5f6958`, 2026-05-11.
|
||||
|
||||
|
||||
435. **`claw --resume latest` on a fresh workspace exit code is 0 in text mode but 1 in JSON mode (text mode lies about success); sibling: failed `--resume` creates the `.claw/sessions/<fingerprint>/` directory tree as a filesystem side effect of the failure** — dogfooded 2026-05-11 by Jobdori on `e29010ed` in response to Clawhip pinpoint nudge at `1503305692566655096`. Reproduction (fresh empty dir, no `.claw/`, no sessions): `claw --resume latest` (text mode) prints `failed to restore session: no managed sessions found in .claw/sessions/0ead448127a2de44/` and exits **0**. Same invocation with `--output-format json` correctly exits **1** with `kind:"session_load_failed"`. Exit-code parity broken on the same input depending on format flag. **Sibling filesystem-side-effect bug:** after the failed `--resume latest` on a fresh empty workspace, the directory `.claw/sessions/0ead448127a2de44/` (the workspace-fingerprint partition) is created on disk despite the operation failing. The user did not opt into creating workspace metadata — they asked to resume an existing session, the resume failed, and now there's a partition directory hanging around. The fingerprint directory ought to be created lazily on first successful session save, not as a side effect of every resume attempt. **Three sibling findings in the same probe:** (a) **`claw --compact` alone (no other args) drops into the interactive REPL with the ANSI welcome banner** — `--compact` is documented as a modifier that strips tool call details in text mode for piping (`--compact ... useful for piping`), not as a verb that activates the REPL. Running `claw --compact` with no positional should be a no-op or an error explaining the flag needs a subcommand or prompt; entering the REPL is the wrong default. (b) **`claw --compact "hello"` (shorthand prompt) returns `{"error":"unknown subcommand: hello.","hint":"Did you mean help","kind":"unknown"}` — `--compact` disables shorthand prompt mode entirely**, treating the positional as a subcommand instead of as prompt text. Users must use the explicit `prompt` verb (`claw --compact prompt "hello"`) which contradicts the `claw [flags] TEXT` usage line in `--help`. (c) `kind:"unknown"` again for the unknown-subcommand error in --compact path — same catch-all bucket bug appearing for the 11th time across pinpoints. **Required fix shape:** (a) exit code 1 for all `failed_to_restore` / `session_load_failed` text-mode failures; text mode should print to stderr and exit non-zero, not print to stdout and exit 0; (b) defer `.claw/sessions/<fingerprint>/` creation to first successful save; failed `--resume` must not leave filesystem droppings; (c) `claw --compact` alone (no positional, no subcommand, stdin is TTY) should emit `kind:"missing_argument"` with `argument:"prompt or subcommand"` rather than activating the REPL; (d) `--compact` must be transparent to shorthand prompt mode parsing — `claw --compact "hello"` is equivalent to `claw --compact prompt "hello"`, both should reach the prompt path; (e) emit typed `kind:"unknown_subcommand"` not `kind:"unknown"` for fallthrough cases. **Why this matters:** scripts that gate on `$?` after `claw --resume latest` see success on text mode and failure on JSON mode — the same operation, two outcomes. The filesystem side effect pollutes a user's worktree with workspace partitions they didn't ask for, and CI pipelines that snapshot `.claw/` size silently grow on every failed `--resume`. Cross-references #422 (exit-code parity across error envelopes), #423 (`kind:"unknown"` for `missing_argument`), #434 (shorthand prompt limitations). Source: Jobdori live dogfood, `e29010ed`, 2026-05-11.
|
||||
|
||||
|
||||
436. **`claw init` shipped `.claw.json` template explicitly sets `permissions.defaultMode:"dontAsk"` — every user who runs `claw init` gets a config file that disables permission prompts by default; sibling: `init` creates an empty `.claw/` directory with no settings.json template inside, and when `.claw/` already exists it skips the whole artifact (no settings template materialized)** — dogfooded 2026-05-11 by Jobdori on `b8f989b6` in response to Clawhip pinpoint nudge at `1503313241751949335`. Reproduction: `mkdir /tmp/probe && cd /tmp/probe && claw init --output-format json` returns `artifacts:[{name:".claw/",status:"created"},{name:".claw.json",status:"created"},...]`. Inspecting the created `.claw.json`: `{"permissions":{"defaultMode":"dontAsk"}}`. This is the polar opposite of safe-by-default: every user who follows the documented onboarding flow (`claw init` after `curl install.sh`) ships their workspace with permission prompts disabled. Compounds with **#428** (default runtime permission_mode is `danger-full-access`) — between the runtime default and the init template, a fresh claw setup has zero user-facing safety friction. **Sibling: `.claw/` artifact is an empty directory.** After `claw init`, `find .claw -type f` returns nothing. No `settings.json`, no template, no scaffolding — just `mkdir .claw`. The `--help` description implies init produces a usable workspace, but `.claw/settings.json` (the project-scope counterpart of `~/.claw/settings.json`) is never templated. **Sibling: `.claw/` skip-on-exists drops the entire artifact.** If `.claw/` already exists (e.g., from a partial setup, a `--resume` failure side effect per #435, or manual creation), `claw init` returns `.claw/: skipped` and does not materialize any expected sub-content. The other artifacts (`.claw.json`, `.gitignore`, `CLAUDE.md`) are still created, but a future `claw skills install` or `claw plugins enable` may expect `.claw/` to contain template files that are now missing. **Required fix shape:** (a) the shipped `.claw.json` template must default to `permissions.defaultMode:"acceptEdits"` or `"plan"` (safe-by-default modes per #428 spec) — `"dontAsk"` requires explicit opt-in; (b) `claw init` must materialize `.claw/settings.json` with documented schema defaults inside `.claw/` so the directory is useful on its own; (c) when `.claw/` already exists, `init` must report `partial` status (not `skipped`) and still try to create missing sub-files like `.claw/settings.json` without overwriting existing files; (d) emit per-sub-file artifact entries for `.claw/settings.json` and `.claw/sessions/` (skipped status if absent, deferred-to-first-save acceptable) so automation knows what's present; (e) regression test: `claw init` produces a `.claw.json` whose `permissions.defaultMode` is NOT `dontAsk`; `.claw/` contains at least one templated file. **Why this matters:** init is the primary onboarding surface. Every first-time user piping `curl install.sh | sh && claw init` gets a workspace pre-configured to skip permission prompts — and that workspace gets committed to the user's repo via the `init`-added entry. The `.claw/` empty-directory bug means feature discovery (skills, plugins) lacks the scaffolding it implies. Cross-references #428 (runtime default permission_mode), #50/#87/#91/#94/#97/#101/#106/#115/#123 (permission-rule audit), #435 (filesystem side effects on failed resume). Source: Jobdori live dogfood, `b8f989b6`, 2026-05-11.
|
||||
|
||||
|
||||
437. **`version --output-format json` omits build provenance fields — no `is_dirty`, `branch`, `commit_date`, `commit_timestamp`, `rustc_version`; `git_sha` is truncated to 7 chars instead of full 40-char hash; sibling: `executable_path` leaks the build host's path (`/tmp/claw-dog-0530/...`) into runtime output** — dogfooded 2026-05-11 by Jobdori on `8cf628a5` in response to Clawhip pinpoint nudge at `1503320791582900344`. Reproduction: `claw version --output-format json` returns `{"build_date":"2026-05-11","executable_path":"/tmp/claw-dog-0530/rust/target/release/claw","git_sha":"b98b9a7","kind":"version","message":"Claw Code\n Version 0.1.0\n Git SHA b98b9a7\n Target aarch64-apple-darwin\n Build date 2026-05-11","target":"aarch64-apple-darwin","version":"0.1.0"}`. Critical provenance fields missing: (a) **`is_dirty`** — was the working tree clean at build time? Automation that pins on build provenance cannot tell if the binary was built from a clean commit or includes uncommitted changes; (b) **`branch`** — was this built from `main`, `dev/rust`, a release tag, or a feature branch? The `git_sha` alone doesn't reveal the integration point; (c) **`commit_date` / `commit_timestamp`** — only `build_date` (when the binary was compiled) is exposed; the commit itself might be days/weeks older if the build happened later. Reproducibility audits need both; (d) **`rustc_version`** — what Rust compiler version produced this binary? Critical for security advisories (e.g., known regressions in specific rustc versions); (e) **`git_sha` truncated to 7 chars** ("b98b9a7" instead of full "b98b9a71..."): 7-char shas have known collision rates in large repos and prevent unambiguous git rev-parse round-trip. **Sibling: `executable_path` leaks build-host path.** The `executable_path` field returns `/tmp/claw-dog-0530/rust/target/release/claw` — the directory where the binary was compiled, embedded into the binary metadata. For a binary copied/installed/symlinked to a different location, this field still reports the build path, not the actual invocation path. Either the field should reflect the runtime path via `std::env::current_exe()` at runtime (not compile-time), or it should be dropped to avoid leaking compile-host filesystem layout. **Sibling: prose `message` field duplicates structured data.** The `message` field still contains the entire text-mode prose version block (`"Claw Code\n Version 0.1.0\n Git SHA b98b9a7\n..."`) — every field present as structured JSON (`version`, `git_sha`, `target`, `build_date`) is also embedded in the prose. Same issue as #391 (`version json includes prose message field`) which was closed as "fixed" — the prose remains. **Required fix shape:** (a) add `is_dirty:bool`, `branch:string|null`, `commit_date:string` (ISO-8601), `commit_timestamp:int` (Unix epoch), `rustc_version:string` to the JSON envelope; (b) preserve full 40-char `git_sha` and add `git_sha_short:string` as a derived field if 7-char form is needed for UX; (c) `executable_path` should be `std::env::current_exe()` at runtime, not the compile-time path; (d) drop the prose `message` field from JSON or rename it `human_readable:string` and make it explicitly secondary to the structured fields; (e) re-verify #391 closure — the prose `message` is still present, the fix didn't fully land. **Why this matters:** version surface is the canonical provenance probe for security audits, build reproducibility, and bug-report metadata. Missing `is_dirty` means automated triage cannot distinguish "issue against a clean main commit" from "issue against a developer's uncommitted hack". Truncated `git_sha` blocks unambiguous git lookup. Leaked `executable_path` exposes build-host layout. Cross-references #391 (version prose duplication — apparently not fully fixed), #334 (version json omits build_date — fixed, but partial scope), #100 (commit identity audit). Source: Jobdori live dogfood, `8cf628a5`, 2026-05-11.
|
||||
|
||||
|
||||
438. **Memory file discovery only recognizes `CLAUDE.md` — `AGENTS.md` (industry convention used by OpenCode/Codex/Aider/Cursor) and `CLAW.md` (project's own brand name) are silently ignored despite being present in the workspace** — dogfooded 2026-05-11 by Jobdori on `d3a982dd` in response to Clawhip pinpoint nudge at `1503328341422244012`. Reproduction (fresh empty dir, isolated `CLAW_CONFIG_HOME`): create three files in cwd — `CLAUDE.md` (marker `MARKER-FROM-CLAUDE-MD`), `AGENTS.md` (marker `MARKER-FROM-AGENTS-MD`), `CLAW.md` (marker `MARKER-FROM-CLAW-MD`). Run `claw status --output-format json` → `workspace.memory_file_count: 1`. Run `claw system-prompt --output-format json` and search the `message` field for each marker: only `MARKER-FROM-CLAUDE-MD` is found; `MARKER-FROM-AGENTS-MD` and `MARKER-FROM-CLAW-MD` are absent. `claw-code` exclusively recognizes the Claude-branded filename inherited from upstream Claude Code; the project's own `CLAW.md` brand name and the cross-tool industry convention `AGENTS.md` are both silently dropped. **Three sibling implications:** (a) **brand-consistency gap**: a project rebranded from Claude Code to Claw Code that introduces `CLAUDE.md` as its only memory file is internally inconsistent. Users naturally expect `claw <subcommand>` to read `CLAW.md`. (b) **industry-convention gap**: `AGENTS.md` is the convergent convention for OpenCode (oh-my-opencode/sisyphus), OpenAI Codex CLI, Aider, Cursor, Continue.dev, and most ACP harnesses. Users with mixed-tool workflows maintain a shared `AGENTS.md` and expect every AI coding tool to honor it. (c) **silent failure mode**: there is no warning when `AGENTS.md` or `CLAW.md` exist but are not loaded. Users who copy-paste `AGENTS.md` from another tool's docs see `memory_file_count` stay at 0 or 1 and have to guess why their instructions aren't applied. **Required fix shape:** (a) discover and load **`CLAUDE.md`, `CLAW.md`, `AGENTS.md`** in that priority order (existing config-precedence pattern); (b) all three contribute to `memory_file_count` with `memory_files:[{path, source:"claude_md"|"claw_md"|"agents_md", chars}]` array exposed in `status --output-format json`; (c) when multiple files exist, merge or document the precedence: project-specific `CLAUDE.md`/`CLAW.md` overrides industry-shared `AGENTS.md`; (d) `claw doctor --output-format json` adds a `memory` check that warns when `AGENTS.md` exists but is not the loaded variant (alerting users that they may be relying on the wrong file); (e) regression test: workspace with all three files results in `memory_file_count >= 1` and the system prompt contains markers from at least the highest-precedence file. **Why this matters:** `AGENTS.md` is the lingua-franca instruction file for cross-tool AI coding workflows. A team using OpenCode for one project and Claw Code for another keeps their conventions in a shared `AGENTS.md`. Forcing them to also maintain a `CLAUDE.md` for claw-code (with identical content) is friction that breaks the value proposition of a fork. Cross-references #438 itself (the multi-file convention), and AGENTS.md ecosystem references in oh-my-opencode/sisyphus docs. Source: Jobdori live dogfood, `d3a982dd`, 2026-05-11.
|
||||
|
||||
|
||||
439. **Memory file discovery walks ALL ancestor directories up to `$HOME` boundary, silently loading any `CLAUDE.md` it finds — `/tmp/CLAUDE.md` left from a previous test silently bleeds into every project under `/tmp/*/`; no `--no-parent-memory` flag, no `.no-claude-md-boundary` marker file to limit discovery scope** — dogfooded 2026-05-11 by Jobdori on `f4a96740` in response to Clawhip pinpoint nudge at `1503335892461293675`. Reproduction: create three nested `CLAUDE.md` files with unique markers — `/tmp/claw-nested-probe/CLAUDE.md` (`PARENT_CLAUDE`), `subproj/CLAUDE.md` (`CHILD_CLAUDE`), `subproj/deep/CLAUDE.md` (`DEEP_CLAUDE`). Run `claw system-prompt --output-format json` from `subproj/deep/nest/` (note: `nest` has no `CLAUDE.md`). The `message` field contains **all three markers** (PARENT + CHILD + DEEP) and `status --output-format json` reports `memory_file_count: 3`. Boundary tests: (a) `$HOME/CLAUDE.md` is NOT picked up from `/tmp/no-claude-dir` (discovery stops at `$HOME` boundary, good); (b) From `/tmp/deep` (no nested CLAUDE.md), `/tmp/CLAUDE.md` IS picked up (count: 1); (c) git-root is NOT a discovery boundary — running from a git subdir still walks above the git root. **Ambient-context-bleed footgun:** any stale `/tmp/CLAUDE.md` (or `/home/<user>/projects/CLAUDE.md`, or any ancestor-path CLAUDE.md left over from a previous experiment, copy-paste, or AI-generated example) silently bleeds into every workspace nested below it. The user has no signal in `status --output-format json` indicating which ancestor file is contributing — only the aggregate `memory_file_count`. **Three required fixes:** (a) **expose discovery list**: `status --output-format json` and `system-prompt --output-format json` must include `memory_files:[{path, source:"workspace"|"ancestor"|"parent_dir"|"home", chars, contributes:bool}]` so users can see what's leaking in; (b) **add `--no-parent-memory` flag** to limit discovery to cwd only (no ancestor walk), or add a boundary marker (`.claude-no-walk`, `.claw-root`, or honor `.git` as the boundary by default — most users expect repo-root scope); (c) **`doctor` warns** when ancestor `CLAUDE.md` files are loaded from outside the current git repo (suggests they may be unintentional). **Sibling discovery scope question:** discovery walks up to `$HOME` — but for a user with a project at `/Users/foo/work/proj`, that's `/Users/foo/work/CLAUDE.md` + `/Users/foo/CLAUDE.md` (if it exists) both load. The home boundary is exclusive, but the entire `/Users/foo` tree under home is in scope. **Why this matters:** test workspaces, scratch dirs, AI-generated example projects, and shared `/tmp` workdirs are full of stale `CLAUDE.md` files. The current discovery rule means every claw invocation can silently inherit context from arbitrary ancestor paths. Cross-references #438 (memory discovery only finds CLAUDE.md, not AGENTS.md or CLAW.md), #421 (cwd canonicalization leak — the canonicalized form determines which ancestor walk path is used). Source: Jobdori live dogfood, `f4a96740`, 2026-05-11.
|
||||
|
||||
|
||||
440. **One invalid `mcpServers` entry blocks ALL OTHER valid MCP servers from loading — `mcp list --output-format json` returns `configured_servers: 0, servers: []` when even one server has a missing/invalid `command` field, despite other servers in the same config being well-formed; sibling: config parser halts on first invalid entry, never reports the remaining invalid entries** — dogfooded 2026-05-11 by Jobdori on `bd126905` in response to Clawhip pinpoint nudge at `1503343442904879156`. Reproduction: write `.claw.json` containing six `mcpServers` entries — one valid (`valid-server: {command:"/bin/echo", args:["hello"]}`) and five with progressive defects (missing-command, empty-command, null-command, wrong-type-command, extra-unknown-field). Run `claw mcp list --output-format json` → `{"action":"list","config_load_error":"/private/tmp/claw-mcp-probe/.claw.json: mcpServers.missing-command-server: missing string field command","configured_servers":0,"kind":"mcp","servers":[],"status":"degraded"}`. The error mentions only `missing-command-server` (the first invalid entry in JSON-object iteration order); the other four invalid entries are never surfaced. The valid `valid-server` entry is silently dropped because the parser bails on the first error. `status --output-format json` correctly propagates the same `config_load_error` and sets `status:"degraded"`, but no field tells automation which servers are valid vs broken — `servers:[]` is the only signal. **Three problems compounded:** (a) **all-or-nothing loading**: ROADMAP product principle #5 says "partial success is first-class," but mcp config loading is binary. One bad server kills the entire MCP plane; (b) **first-error-only reporting**: a `.claw.json` with five invalid entries surfaces only one error message — the user fixes that one and runs again, gets the next error, and so on. Five iterations needed to discover all errors; (c) **no per-server status**: even with the partial-success fix, the JSON envelope needs `servers:[{name, valid:bool, error?, command?, args?}]` so automation can see which entries are usable. **Required fix shape:** (a) the MCP config parser must collect ALL invalid entries into an `invalid_servers:[{name, error_field, reason}]` array and load all valid ones into `servers:[]`; do not abort on first error; (b) `configured_servers` reflects the count of *valid* loaded servers (not zero) when there are valid entries alongside invalid ones; (c) expose `total_configured:int` (count of entries in source `.claw.json`) AND `valid_count:int` (loaded), AND `invalid_count:int` (rejected) — three distinct counts; (d) `doctor --output-format json` adds an `mcp_validation` check that lists each invalid entry with its error message; (e) regression test: `.claw.json` with one valid + one invalid entry results in `configured_servers: 1, invalid_servers: [{name:"...", reason:"..."}]`. **Why this matters:** users iterate on MCP server lists during onboarding — one typo kills the entire plane, including servers they got working previously. The first-error-only reporting forces N iterations through N invalid entries instead of a single fix-everything-at-once pass. Cross-references #407 (config files no load_error per-file), #415 (config section merged_keys count only), #416 (plugins list prose), #428 (default permission mode), and Product Principle #5. Source: Jobdori live dogfood, `bd126905`, 2026-05-11.
|
||||
|
||||
|
||||
441. **`hooks` config schema diverges from Claude Code documented format — claw-code expects `{"hooks":{"PreToolUse":["command-string"]}}` (array of command strings) while Claude Code documentation specifies `{"hooks":{"PreToolUse":[{"matcher":"Read","hooks":[{"type":"command","command":"..."}]}]}}` (structured matcher objects); users copy-pasting from Claude Code docs see `field "hooks.PreToolUse" must be an array of strings`** — dogfooded 2026-05-11 by Jobdori on `86ff83c2` in response to Clawhip pinpoint nudge at `1503350990680887418`. Reproduction: write `.claw.json` with the Claude-Code-documented hook format `{"hooks":{"PreToolUse":[{"matcher":"Read","hooks":[{"type":"command","command":"/bin/echo pretool"}]}]}}`. Run `claw status --output-format json` → `config_load_error: "/private/tmp/claw-hook-probe/.claw.json: field \"hooks.PreToolUse\" must be an array of strings, got an array (line 3)"`, `status: "degraded"`. The error wording ("must be an array of strings, got an array") is confusingly tautological — the user did provide an array; the parser objects that the array contains objects instead of strings. Replacing with the claw-code-actual format `{"hooks":{"PreToolUse":["/bin/echo pretool"]}}` succeeds: `config_load_error: null, status: "ok"`. The two formats are fundamentally incompatible: claw-code drops the `matcher` field (no tool-specific filtering at the config layer), drops the `type:"command"` discriminator (no future expansion to other hook types), and treats each entry as a bare command string instead of a structured hook spec. **Sibling: PR #3000 (justcode049) was attempting to tolerate object-style hook entries** — that PR's title `fix: tolerate object-style hook entries in config parser` confirms this is a known user complaint, but the PR is still conflicting and unmerged. **Three sibling findings in same probe:** (a) **unknown event names reject entire hooks config**: `.claw.json` with `hooks.InvalidEvent` (not a real event name like `PreToolUse`/`PostToolUse`/`Stop`/`Notification`) triggers `config_load_error: "unknown key \"hooks.InvalidEvent\""` and rejects ALL hooks in the same file, even valid ones — same "one bad apple kills all" pattern as #440 (MCP servers). (b) **`kind:"unknown"` for the validation error** — should be `kind:"invalid_hooks_config"` or `kind:"unknown_hook_event"` (catch-all cluster #422/#423/#424/#428/#430/#431/#432/#433/#435 — 13th occurrence). (c) **first-error-only halting**: a `.claw.json` with `hooks.Stop:"not-an-array"` (type mismatch) AND `hooks.InvalidEvent` (unknown name) AND `hooks.Notification:[{}]` (empty entry) surfaces only the FIRST error in iteration order — user must fix one at a time across 3 iterations. **Required fix shape:** (a) **adopt Claude Code's structured hook format as the canonical**: support `{matcher, hooks:[{type, command}]}` natively, with `matcher` for tool-filtering, `type` for hook-type discriminator (future-proof for `inline`/`webhook`/etc beyond just `command`); (b) **keep backward compat for bare command strings**: legacy `["command-string"]` arrays still load, but emit a deprecation warning suggesting migration to the structured form; (c) **partial-success loading**: invalid hook entries surface in `invalid_hooks:[{event, index, reason}]` while valid ones load — same fix as #440 for MCP; (d) **typed `kind:"invalid_hooks_config"` envelope** instead of `kind:"unknown"`; (e) **rebase and merge PR #3000** which addresses this directly; (f) regression test: Claude-Code-documented hook config loads without error on claw-code. **Why this matters:** users migrating from Claude Code to Claw Code hit this on their first `.claw.json` write. The error message ("array of strings, got an array") is unhelpful; the documentation doesn't surface the schema divergence; and Claude Code's structured format is strictly more expressive (matchers, types) than claw-code's bare-string format. Cross-references #407 (config files no load_error), #410 (list-envelope schema drift), #428 (default permission mode), #440 (one invalid MCP entry blocks all), PR #3000 (justcode049's pending fix). Source: Jobdori live dogfood, `86ff83c2`, 2026-05-11.
|
||||
|
||||
|
||||
442. **`agents` discovery requires TOML format (`.toml` files) while Claude Code documents agents as Markdown with YAML frontmatter (`.md`) — claw-code silently ignores `.md` files in `.claw/agents/` without any warning; the help text lists `.claw/agents, ~/.claw/agents, $CLAW_CONFIG_HOME/agents` as sources but does not mention the `.toml` file format requirement** — dogfooded 2026-05-11 by Jobdori on `8499599b` in response to Clawhip pinpoint nudge at `1503358540230692876`. Reproduction: write `.claw/agents/valid-agent.md` with Claude-Code-format YAML frontmatter `---\nname: valid-agent\ndescription: A simple test agent\ntools: [bash, read_file]\n---\nYou are a helpful agent.` Run `claw agents list --output-format json` → `{"agents":[], "count":0, "summary":{"active":0,"shadowed":0,"total":0}}`. The valid `.md` agent is silently dropped. Replace with `.claw/agents/toml-agent.toml` containing TOML format `name = "toml-agent"\ndescription = "..."` → loads correctly with `count:1`. Source code confirms (`rust/crates/commands/src/lib.rs:3378`): `if entry.path().extension().is_none_or(|ext| ext != "toml") { continue; }` — only `.toml` extension is recognized, all others (including `.md`) skipped without warning. The help text `claw agents --help` documents the source paths but **omits the file-format requirement**. **Five sibling problems compounded:** (a) **schema divergence from Claude Code**: Claude Code's `agents` are documented as `.md` files with YAML frontmatter (matching the `CLAUDE.md`/`.claude/agents/` convention upstream). claw-code chose TOML for no documented reason. Users migrating from Claude Code or copy-pasting community agent definitions hit silent failure. (b) **silent file drop**: invalid agent files (wrong extension, broken frontmatter, missing required fields, file-name vs frontmatter-name mismatch) are all silently ignored with `count:0`. No `invalid_agents:[]` array, no warning, no `kind:"agent_load_failed"` envelope. Same all-or-nothing pattern as #440 (MCP servers) and #441 (hooks). (c) **no documentation of the schema**: `claw agents --help --output-format json` (per #427, this hits the auth gate; without auth it doesn't return the schema either). The required TOML fields (`name`, `description`, `model`, `model_reasoning_effort` per source code) aren't documented in any user-facing surface. (d) **missing `.claude/agents/` discovery**: many existing projects have `.claude/agents/` from Claude Code installs. claw-code only looks at `.claw/agents/` — users have to copy/move their existing agents. (e) **no agent-scaffolding command**: cross-reference #431 — there's no `claw agents create <name>` to generate a valid `.toml` skeleton; users must hand-craft. **Required fix shape:** (a) accept BOTH `.md` (with YAML frontmatter) AND `.toml` formats in `.claw/agents/`; prefer YAML frontmatter for Claude Code parity, keep TOML for back-compat; (b) include `.claude/agents/` in the discovery sources alongside `.claw/agents/` with documented precedence; (c) expose `invalid_agents:[{path, reason}]` array in `agents list --output-format json` so users can see what was skipped and why; (d) document the agent schema (required + optional fields) in `claw agents --help` and in USAGE.md; (e) add `claw agents create <name>` scaffolding command per #431; (f) regression test: `.claw/agents/foo.md` with YAML frontmatter loads correctly. **Why this matters:** agents are the primary extension surface for custom workflows. A silent-drop on the wrong file format breaks the discoverability promise of CLI agents. Claude Code's `.md`-with-YAML convention is the lingua franca across AI coding tools; deviating to TOML breaks copy-paste compatibility. Cross-references #430 (dump-manifests needs upstream), #431 (skills/agents lifecycle), #440 (MCP all-or-nothing), #441 (hooks all-or-nothing), #438 (memory file discovery only CLAUDE.md). Source: Jobdori live dogfood, `8499599b`, 2026-05-11.
|
||||
|
||||
|
||||
443. **`claw acp serve` exits 0 with `status:"discoverability_only", supported:false` instead of failing — automation pipelines see "success" from a command that explicitly says "not implemented"; ROADMAP #413's internal-tracking leak (`discoverability_tracking:"ROADMAP #64a"`, `tracking:"ROADMAP #76"`) still present despite being filed 2026-04-30** — dogfooded 2026-05-11 by Jobdori on `19aaf9d0` in response to Clawhip pinpoint nudge at `1503366101533200435`. Reproduction: `claw acp serve --output-format json` returns exit code **0** with envelope `{aliases:["acp","--acp","-acp"], discoverability_tracking:"ROADMAP #64a", kind:"acp", launch_command:null, message:"ACP/Zed editor integration is not implemented in claw-code yet. \`claw acp serve\` is only a discoverability alias today; it does not launch a daemon or Zed-specific protocol endpoint. Use the normal terminal surfaces for now and track ROADMAP #76 for real ACP support.", recommended_workflows:["claw prompt TEXT","claw","claw doctor"], serve_alias_only:true, status:"discoverability_only", supported:false, tracking:"ROADMAP #76"}`. The exit code is 0 (success) but the command explicitly states it is not implemented. Pipeline like `claw acp serve && zed --connect localhost:12345` will proceed to the zed connect step despite `acp serve` being a no-op. The only signal of no-op is `supported:false` in the JSON body — easy to miss for automation gating on `$?`. **ROADMAP #413 reproduction confirmed unfixed:** #413 (filed 2026-04-30) called out `discoverability_tracking:"ROADMAP #64a"` and `tracking:"ROADMAP #76"` as internal ticket references leaked into public JSON. **11 days later, both fields are still present in the envelope.** The fix was prescribed but never landed. Also `recommended_workflows:["claw prompt TEXT","claw","claw doctor"]` is internal scaffolding (curated suggestion list) exposed as a top-level public field — not normally part of an "ACP status" public contract. **Sibling unknown-subcommand bug:** `claw acp status --output-format json` (a reasonable next-thing-to-try) returns `{"error":"unsupported ACP invocation. Use \`claw acp\`, \`claw acp serve\`, \`claw --acp\`, or \`claw -acp\`.","kind":"unknown"}` exit 0 — the `kind:"unknown"` catch-all yet again (#422/#423/#424/#428/#430/#431/#432/#433/#435/#440/#441/#442 — **14th occurrence**), should be `kind:"unsupported_acp_invocation"`. **Required fix shape:** (a) `claw acp serve` exits **non-zero** (exit code 2 = "not implemented" is conventional) so automation `$?`-gating detects the no-op; (b) deliver #413's fix: remove `discoverability_tracking` and `tracking` top-level fields, OR move them under an optional `_meta` sub-object gated on a debug flag; (c) replace `message` prose with a typed `reason:"not_implemented"` enum + optional `detail` string for downstream pipelines that need a stable signal; (d) drop `recommended_workflows` from the ACP envelope OR move it under `_meta`; (e) the `status:"discoverability_only"` value is non-standard — replace with `status:"not_implemented"` (matching the `supported:false` boolean); (f) typed `kind:"unsupported_acp_invocation"` for the bad-arg path. **Why this matters:** ACP/Zed integration is the integration point for IDE-based AI workflows. A "success" exit code on a "not implemented" stub breaks the contract for any wrapper script that tries to detect ACP availability via `claw acp serve && ...`. The internal-tracking-ID leak (#413) being unfixed for 11 days suggests the JSON envelope audit isn't being executed against the ROADMAP backlog. Cross-references #413 (internal tracking leak — unfixed), #422 (exit-code parity), `kind:"unknown"` catch-all cluster. Source: Jobdori live dogfood, `19aaf9d0`, 2026-05-11.
|
||||
|
||||
|
||||
444. **No broad-cwd safety guard for `--resume` — `claw --resume latest` from `/` attempts to `mkdir /.claw/sessions/<fingerprint>/` and is only stopped by the read-only filesystem at root; from any writable system directory (`/tmp`, `/var/tmp`, `$HOME` itself) it silently creates `.claw/sessions/<fingerprint>/` droppings; exit code is 0 (success) on the read-only filesystem error path** — dogfooded 2026-05-11 by Jobdori on `b2048856` in response to Clawhip pinpoint nudge at `1503373639884607629`. Reproduction: `cd / && claw --resume latest --output-format json` returns `{"error":"failed to restore session: Read-only file system (os error 30)","hint":null,"kind":"session_load_failed","type":"error"}` exit **0**. The OS permission denial is the only thing preventing claw from creating `/.claw/sessions/<fingerprint>/` in the root filesystem. Compare with `cd /tmp && claw --resume latest --output-format json`: silently creates `/tmp/.claw/sessions/<fingerprint>/` partition (confirmed by `ls /tmp/.claw` showing a directory from a prior dogfood session at `13:31` — the May 11 11:00 pinpoint #435 dropping is still there 10+ hours later, despite documented cleanup). Same dogfood session: `cd $HOME && claw --resume latest` would silently create `~/.claw/sessions/<fingerprint>/` (the user's home claw config dir). The shorthand prompt path has a broad-cwd guard (`claw is running from a very broad directory (/). The agent can read and search everything under this path. Use --allow-broad-cwd to proceed anyway`) — but the guard does NOT fire on `--resume`, `--status`, or `claw status` invocations. Inconsistent safety surface: the dangerous path (LLM prompt with full tool access) has a guard, but session-management paths that create filesystem artifacts in broad locations have none. **Three sibling findings in same probe:** (a) **exit-code 0 on filesystem error** (`session_load_failed` envelope returns exit code 0): the read-only-filesystem error from `/.claw` creation path is an unrecoverable failure but the process exits 0 — same exit-parity bug as #422/#435; (b) **stale filesystem droppings**: `/tmp/.claw/` from a 13:31 dogfood session at HEAD `6c0c305a` is still present at 21:30 (10 hours later, 6+ HEADs later). The "deferred cleanup" or "lazy creation" fix prescribed in #435 hasn't landed; (c) **broad-cwd guard misfires on resume**: the existing guard from `run` path (visible in `claw --help` as "Use --allow-broad-cwd to proceed anyway") never fires on `--resume`. Either both paths should guard, or the guard should be promoted to a global pre-check. **Required fix shape:** (a) extend the broad-cwd guard to `--resume`, `claw status`, `claw doctor`, and every command that may create filesystem artifacts; `cd / && claw --resume latest` must fail fast with `kind:"broad_cwd_blocked"` before any filesystem operation; (b) `cd $HOME && claw` should warn that the workspace is your home directory and ask for `--allow-broad-cwd` (the LLM with full filesystem access in `$HOME` is the same blast radius as in `/`); (c) exit code 1 for `session_load_failed` regardless of underlying cause; (d) deliver #435's "defer fingerprint directory creation to first successful save" fix — failed `--resume` must not leave filesystem droppings; (e) cleanup `/tmp/.claw/` style scratch-dir artifacts via a `claw doctor --cleanup` or similar opt-in mechanism; (f) regression test: failed `--resume` does not create any directories under cwd. **Why this matters:** users running claw as part of CI/cron from system directories silently accumulate `.claw/sessions/<fingerprint>/` artifacts in /tmp, /var, /opt, $HOME, etc. Running as root from / would (with a writable root) silently pollute the root filesystem. The broad-cwd guard exists but only covers one entry point. Cross-references #427 (broad-cwd guard fires on resume too — actually it doesn't, that note in #427 was inaccurate), #428 (default permission_mode danger-full-access — compounds with this: full access + no broad-cwd guard = serious blast radius), #435 (filesystem side effects on failed resume), #422 (exit-code parity). Source: Jobdori live dogfood, `b2048856`, 2026-05-11.
|
||||
|
||||
|
||||
445. **Skill name-vs-directory mismatch is silently accepted — `.claw/skills/wrong-name/SKILL.md` with frontmatter `name: actually-different-name` loads as "actually-different-name" without any warning; users who reference the skill by directory name (`claw skills run wrong-name`) get `skill_not_found` while `skills list` shows it under the frontmatter name; sibling: loose `.md` files at the skills-dir root and subdirs without `SKILL.md` are silently dropped** — dogfooded 2026-05-11 by Jobdori on `9e1eafd0` in response to Clawhip pinpoint nudge at `1503381189539528897`. Reproduction: create `.claw/skills/wrong-name/SKILL.md` with frontmatter `---\nname: actually-different-name\ndescription: Skill where dir name and frontmatter name disagree\n---`. Run `claw skills list --output-format json` → the skill is listed with `name: "actually-different-name"` (the frontmatter value), no warning about the dir-vs-name mismatch. Users who type `claw skills run wrong-name` (the dirname they know from `ls`) get a `skill_not_found` error; `claw skills run actually-different-name` works. The two names are decoupled with no surfaced relationship. **Three sibling silent-drop bugs in same probe:** (a) **subdir without SKILL.md silently skipped**: `.claw/skills/no-skill-md/` containing only `README.md` (no `SKILL.md`) is silently skipped from `skills list`. No `invalid_skills:[{path, reason:"missing_SKILL.md"}]` array, no warning, just absent from output. (b) **Loose `.md` at skills dir root silently dropped**: `.claw/skills/loose-skill.md` (not inside a per-skill subdirectory) is silently ignored. Discovery only walks `.claw/skills/*/SKILL.md` — no support for flat `.claw/skills/<name>.md`. (c) **Workspace + user skills merged without per-source filter**: `skills list` returns 74 entries including all `~/.claw/skills/*` user-home skills alongside the project skills. There's no `--scope workspace` flag to limit output to just project-local skills; automation has to filter by `source.id == "project_claw"` post-hoc. **Required fix shape:** (a) when SKILL.md frontmatter `name` differs from the parent directory name, emit a `skills_metadata_drift:[{dir_name, frontmatter_name, path}]` array OR enforce `name = dir_name` as a hard rule; if neither, at minimum a stderr warning on each invocation; (b) skill subdirectories without `SKILL.md` should surface as `invalid_skills:[{path, reason}]` in `skills list --output-format json` (same pattern as #440 MCP servers, #441 hooks, #442 agents); (c) support loose `.md` files at skills-dir root OR document explicitly that only subdirectories with `SKILL.md` are discovered; (d) add `--scope workspace|user|all` flag to `skills list` for filtering; (e) regression test: dir/frontmatter mismatch triggers a deterministic warning or error; subdirs without SKILL.md show in invalid array. **Why this matters:** skill discovery is a security-relevant surface — a user's `claw skills run X` could end up running a different skill than they thought if dir-name and frontmatter-name diverge. The silent drops mean users can't tell why their skill files aren't recognized, leading to "I copied the example and it doesn't work" forum questions. Cross-references #440 (MCP all-or-nothing), #441 (hooks all-or-nothing), #442 (agents need TOML, .md dropped), #431 (skills install raw OS error). Source: Jobdori live dogfood, `9e1eafd0`, 2026-05-11.
|
||||
|
||||
|
||||
446. **Config is loaded 2-3 times per command invocation; each load re-emits identical deprecation warnings without deduplication — `status` triggers 3× `enabledPlugins` warning, `doctor`/`mcp` trigger 2× each, only `version` (config-free) emits 0** — dogfooded 2026-05-11 by Jobdori on `5a4cc506` in response to Clawhip pinpoint nudge at `1503388740595224717`. Reproduction: with a `~/.claw/settings.json` containing the deprecated `enabledPlugins` key, run each command from a fresh empty cwd and count `warning: ... is deprecated` lines on stderr — `claw status 2>&1 >/dev/null | grep -c deprecated` returns **3**, `claw doctor` returns **2**, `claw mcp` returns **2**, `claw version` returns **0**. Each duplicate is byte-identical (same file path, same line number, same field name). The pattern proves the config-load pipeline is invoked 2-3 times within a single command process; warnings are emitted at each load without checking a `warned_files: HashSet<PathBuf>` deduplication set. **Three sibling implications:** (a) **load-count varies by command** — status:3, doctor:2, mcp:2, version:0 — suggesting each command implements its own config-load call rather than going through a shared cached loader; (b) **noise pollution**: users running `claw status` once see the same 64-character warning 3 times in their terminal scrollback, making real warnings (other config errors, real deprecations) lost in the duplicate noise; (c) **performance signal**: 3× config load means 3× JSON parsing of `~/.claw/settings.json`, `~/.claw.json`, `$CLAW_CONFIG_HOME/settings.json`, and the project-local `.claw.json` / `.claw/settings.json` / `.claw/settings.local.json`. For a workspace with 5 config files, that's 15 redundant disk reads per status invocation. Earlier roadmap entries observed 3× (#424) and 4× (#425) warning counts at different HEADs; the count keeps fluctuating, suggesting the underlying issue is config-load fan-out that nobody has refactored. **Required fix shape:** (a) introduce a `ConfigLoader` cache scoped to the command-process lifetime: first load reads files and emits warnings; subsequent calls hit the cache and emit zero warnings; (b) move config validation/warnings to a single canonical entry point (`ConfigLoader::load_with_diagnostics()` returns `(RuntimeConfig, Vec<Warning>)` exactly once); (c) every command that needs config goes through the cached loader instead of re-reading from disk; (d) `doctor --output-format json` exposes `config_load_count:int` field so we can regression-test that loads are deduplicated; (e) regression test: any single command invocation emits each deprecation warning at most once. **Why this matters:** repeated identical warnings train users to ignore stderr noise. Real warnings (a new deprecation, a config error from a different file, an MCP server failure) get drowned out by 3-4 copies of the same notice. The 15-disk-read worst case is wasted I/O that adds startup latency. The fact that count fluctuates between HEADs (3 at `6c0c305a`, 4 at `d7dbe951`, back to 3 at `5a4cc506`) suggests dev velocity is moving config loads around without an architectural fix. Cross-references #424 (deprecation warning 3×), #425 (deprecation warning 4×), #421 (cwd canonicalization — possibly tied to per-load symlink resolution), #428 (default permission_mode loaded from same config files). Source: Jobdori live dogfood, `5a4cc506`, 2026-05-11.
|
||||
|
||||
|
||||
447. **All JSON error envelopes go to STDERR not STDOUT; stdout is empty (0 bytes) on every `--output-format json` failure — breaks the standard automation pattern `output=$(claw cmd --output-format json)` which captures nothing on error and forces ugly `2>&1` redirects to even see the JSON** — dogfooded 2026-05-11 by Jobdori on `5ab969e7` in response to Clawhip pinpoint nudge at `1503396289071808523`. Reproduction (stderr-vs-stdout discipline audit): `claw --no-such-flag --output-format json >stdout.txt 2>stderr.txt` → stdout = **0 bytes**, stderr = 115 bytes containing `{"error":"unknown option: --no-such-flag","hint":"Run \`claw --help\` for usage.","kind":"cli_parse","type":"error"}`. Same pattern across four error envelopes probed: (a) `cli_parse` → stdout 0 / stderr 115; (b) `missing_credentials` → stdout 0 / stderr 853 (includes deprecation warnings ahead of envelope); (c) `session_load_failed` → stdout 0 / stderr 322; (d) `invalid_model_syntax` → stdout 0 / stderr 199. Success paths route correctly: `claw status --output-format json` → stdout 1496 / stderr 0. **The asymmetry is wrong on two axes:** (a) **JSON-format outputs should always go to stdout regardless of success/failure**: every major CLI in this class (kubectl, gh, aws, jq, terraform `-json`, `npm --json`) emits JSON on stdout for both ok and error paths; consumers parse `stdout | jq .kind` and switch on the kind to detect errors. claw's split forces consumers to capture both streams or use `2>&1` which then includes deprecation prose alongside the JSON envelope and breaks parsing. (b) **Deprecation/info warnings leak into the JSON error envelope on stderr**: when stderr is the only path to get the JSON, the deprecation warning prefix (`warning: ... enabledPlugins ... is deprecated`) precedes the JSON, making `tail -1 stderr.txt | jq .` fragile. **Three sibling problems:** (i) **breaks the canonical Bash idiom** `if ! output=$(cmd --output-format json); then echo "$output" | jq .error; fi` — `$output` is empty on error so the `jq` call sees nothing. (ii) **forces N-line stderr parsing**: to get the JSON envelope from stderr, automation must read until EOF, then skip leading `warning:` lines, then parse only the last `{...}` JSON. This is a brittle heuristic that breaks if more warnings are added. (iii) **inconsistent with text mode**: text-mode error output ALSO goes to stderr (e.g., `claw --no-such-flag` → stderr `[error-kind: cli_parse]\nerror: ...`) — that's correct for text mode (stderr is the diagnostic channel). The bug is JSON mode inheriting the same routing. **Required fix shape:** (a) JSON error envelopes go to STDOUT when `--output-format json` is active; (b) keep text-mode error output on stderr (no change for text path); (c) deprecation/info warnings should ALSO go to stderr in JSON mode (they're diagnostic prose, not part of the JSON contract) — separate channels: JSON envelope on stdout, prose warnings on stderr; (d) add `--quiet` / `--no-warn` flag to fully suppress stderr warnings for clean automation; (e) regression test: every `--output-format json` failure path emits the JSON envelope on stdout, exit non-zero, no JSON ever on stderr. **Why this matters:** the entire point of `--output-format json` is enabling automation. Splitting JSON success vs error across stdout vs stderr defeats the purpose — automation must capture both, dedupe sources, and parse mixed streams. Cross-references #422 (exit-code parity across error envelopes), #424 (deprecation warnings noise), #428 (envelope vs prose tension), #446 (multi-load deprecation duplication). Source: Jobdori live dogfood, `5ab969e7`, 2026-05-11.
|
||||
|
||||
|
||||
448. **`sandbox --output-format json` has contradictory state flags — `enabled:true, supported:false, active:false, filesystem_active:true, allowed_mounts:[]`: claim that sandbox is "enabled" while OS doesn't support namespace isolation and `allowed_mounts:[]` is empty contradicts `filesystem_active:true filesystem_mode:"workspace-only"`** — dogfooded 2026-05-11 by Jobdori on `7244a82b` in response to Clawhip pinpoint nudge at `1503403842920779917` (using fresh-current-main runner at `/tmp/claw-dog-1430` per gajae's 14:00 protocol switch). Reproduction: `claw sandbox --output-format json` on macOS (where `unshare` is unavailable) returns `{"active":false,"active_namespace":false,"active_network":false,"allowed_mounts":[],"enabled":true,"fallback_reason":"namespace isolation unavailable (requires Linux with \`unshare\`)","filesystem_active":true,"filesystem_mode":"workspace-only","in_container":false,"kind":"sandbox","markers":[],"requested_namespace":true,"requested_network":false,"supported":false}`. **Three contradictions in the same envelope:** (a) `enabled:true` AND `supported:false`: what does "enabled" mean if the OS doesn't support sandboxing? Read literally, sandbox is *enabled but unsupported* — semantic nonsense. The likely intent is "user requested sandbox in config" but the field name `enabled` says "is ON". A better name would be `requested:true` or `config_intent:true`, with `enabled` reserved for the actually-active state. (b) `filesystem_active:true, filesystem_mode:"workspace-only"` AND `allowed_mounts:[]`: if the filesystem fence is active in workspace-only mode, the workspace directory itself MUST be an allowed mount. An empty `allowed_mounts:[]` array combined with `filesystem_active:true` means either (i) the fence is being misreported (it's not really active), (ii) the workspace is implicit and `allowed_mounts` only lists *additional* mounts, or (iii) the fence has no allowed paths and nothing is readable — all three are inconsistent with the user-facing summary. (c) `active:false` AND `filesystem_active:true`: the top-level `active` field is a single boolean summary, but it disagrees with `filesystem_active:true` (one component is active). Either `active` is "all components active" (then it should be `false` when any component is off) or "any component active" (then it should be `true` when filesystem is). The current value is `false` despite filesystem being active. **Sibling: no `claw sandbox --help`**: `claw sandbox status` and `claw sandbox --help` go to LLM-prompt fallback or hang (gajae confirmed at 13:00 that `sandbox status` returns typed `cli_parse` but `sandbox --help` is bounded — schema is non-uniform across help paths). **Required fix shape:** (a) rename `enabled` to `requested` or `config_intent` to disambiguate from "currently active"; (b) make `allowed_mounts` explicitly include the workspace when filesystem_mode is "workspace-only" (`allowed_mounts:[{path:"<cwd>",writable:true,reason:"workspace_root"}]`); (c) document the `active` aggregate semantics: pick either "all" or "any" composition rule and document the choice; (d) add `active_components:["filesystem"]` array as a richer alternative to the single boolean — surfaces exactly which sandbox subsystems are live; (e) regression test: when `filesystem_mode == "workspace-only"`, `allowed_mounts` MUST contain the cwd and `active` must agree with the documented composition rule. **Why this matters:** sandbox is the trust surface — automation that checks `sandbox.active == true` before running a risky LLM prompt sees `false` (no namespace, no network) and assumes no isolation, but `filesystem_active:true` means there IS partial isolation. The mixed signal forces consumers to OR all `*_active` fields together. Cross-references #428 (default permission_mode=danger-full-access — paired with sandbox-not-active means zero isolation), #444 (no broad-cwd guard — sandbox is the only safety net and its status is unclear). Source: Jobdori live dogfood, `7244a82b`, 2026-05-11.
|
||||
|
||||
|
||||
449. **`claw session list --output-format json` routes through `CliAction::ResumeSession` and hits the auth gate, returning `kind:"missing_credentials"` — but `session list` is a pure local filesystem read that requires no API credentials; by contrast, `claw session` (without `list`) correctly short-circuits with `kind:"unknown"` + "is a slash command" message without touching the auth gate** — dogfooded 2026-05-12 by Jobdori on `8f55870d` in response to Clawhip pinpoint nudge at `1503638404842131456`. Reproduction (no creds, isolated env): `env -i HOME=$HOME PATH=$PATH claw session list --output-format json` → `{"error":"missing Anthropic credentials...","kind":"missing_credentials"}` exit 1. `env -i HOME=$HOME PATH=$PATH claw session --output-format json` → `{"error":"`claw session` is a slash command...","kind":"unknown"}` exit 1 (no auth check). Root cause: the parser routes `session list` via `parse_resume_session_args` treating `list` as a session-path token, producing `CliAction::ResumeSession { session_path: "list", commands: [] }`. `resume_session()` then calls `LiveCli::new()` which instantiates the Anthropic client and fires the credentials guard. The `SlashCommand::Session { action: Some("list") }` special-case path in `run_resume_command()` (line 3654 comment: "`/session list` can be served from the sessions directory without a live session") is only reachable after auth passes — the no-creds guard fires before the slash-command dispatch loop. **Asymmetry:** the internal code already knows `session list` is credential-free (the comment at line 3654 says so), but the CLI entrypoint forces creds before the command ever reaches that branch. **Sibling: `session list` with no sessions returns `kind:"session_load_failed"` (from `--resume latest` fallback) rather than `{"kind":"session_list","sessions":[],"session_details":[]}` — the empty-sessions case is misrouted to the resume-failure path instead of a list-success with zero entries.** **Required fix shape:** (a) add a dedicated `CliAction::SessionList { output_format }` variant dispatched when `claw session list` is parsed — do not route through `ResumeSession`; (b) implement `run_session_list(output_format)` as a credentials-free function that calls `list_managed_sessions()` directly (same logic as the slash-command special-case at line 3659); (c) ensure empty sessions returns `{"kind":"session_list","sessions":[],"session_details":[],"active":null}` with exit 0, not a `session_load_failed` error; (d) add the same fix for sibling local-only commands that currently hit the auth gate: `session delete <id>`, `session export <id>`; (e) regression test: `claw session list --output-format json` with no credentials returns `kind:"session_list"` exit 0. **Why this matters:** session list is the canonical inventory surface for automation pipelines — `claw session list --output-format json | jq '.session_details[] | .id'` is the idiomatic way to enumerate sessions for replay, export, or resume. Requiring API credentials to read a local directory listing breaks offline use, CI environments with no API key configured, and any scripting that runs before credential setup. Cross-references #357 (session list requires creds — this is the same bug surfaced by that entry; #449 provides the root-cause path trace), #369 (session help/fork require creds), #427 (resume --help hits auth gate), #431 (skills uninstall requires creds). Source: Jobdori live dogfood, `8f55870d`, 2026-05-12.
|
||||
|
||||
|
||||
|
||||
450. **`prompt` emits `kind:"missing_credentials"` JSON on STDERR (not stdout), leaving stdout at 0 bytes — automation pattern `output=$(claw prompt hello --output-format json)` captures nothing on auth-absent failure; `doctor` correctly surfaces `auth.status:"warn"` with `api_key_present:false` but exposes no `prompt_ready:false` field that automation can check before invoking `prompt`** — dogfooded 2026-05-16 by Jobdori on `a35ee9a0` in response to Clawhip pinpoint nudge at `1505208225321062521`. Exact reproduction (isolated env, no creds, fresh git repo, HEAD `a35ee9a0`): `timeout 5 env -i HOME=$ISOLATED_HOME PATH=$PATH CLAW_CONFIG_HOME=$PROBE/.claw-cfg claw prompt hello --output-format json > stdout.txt 2> stderr.txt` → stdout = **0 bytes**, stderr = 195 bytes containing `{"error":"missing Anthropic credentials…","exit_code":1,"hint":null,"kind":"missing_credentials","type":"error"}`, exit code 1. Confirms Gaebal's `1505208553793781792` pinpoint that `prompt` timeout + zero bytes was the prior state — HEAD `a35ee9a0` now correctly exits 1 with `kind:"missing_credentials"` **but the envelope is still routed to stderr** (issue #447 class, same class as prior entries #422, #435). **Contrast with `doctor`:** `claw doctor --output-format json 2>/dev/null` succeeds to stdout with `checks[auth].status:"warn"`, `api_key_present:false`, `auth_token_present:false` — but the auth check has no `prompt_ready:false` field. Automation that gates on `doctor` before invoking `prompt` must re-derive readiness from `api_key_present && auth_token_present` — there is no single canonical boolean. **Three compound problems:** (a) **stdout-empty on `--output-format json` failure**: same class as #447; `prompt`'s error envelope goes to stderr, not stdout. The canonical automation idiom `if ! result=$(claw prompt "q" --output-format json); then echo "$result" | jq .kind; fi` sees `$result=""` on failure — the jq call gets nothing. All `--output-format json` error paths must route JSON to stdout per #447 contract; (b) **`doctor` missing `prompt_ready` field**: `doctor --output-format json` already knows auth is absent (`api_key_present:false`) but surfaces no derived `prompt_ready:bool` or `prompt_blocked_reason:string` field. Automation must infer readiness from `api_key_present || auth_token_present || legacy_*_present` — a 5-field OR across legacy fields that is fragile as auth mechanisms evolve. A single `prompt_ready:false` (with `prompt_blocked_reason:"auth_missing"`) inside the `auth` check would give downstream a stable contract; (c) **`claw prompt` with no auth does no preflight and fires straight at the API**: the preflight check that `doctor` runs (auth discovery) is not reused by `prompt` to emit a fast typed error before attempting the network call. Both Gaebal's pinpoint (prompt hanging silently on older HEAD) and the current behavior (prompt hitting auth gate after a brief API attempt) stem from the same root: prompt does not short-circuit at the point where `doctor` already knows auth is absent. If `doctor` can emit `kind:"doctor"` with `auth.status:"warn"` in ~20ms without a network call, `prompt` should emit `kind:"missing_credentials"` in the same window and output it to stdout. **Required fix shape:** (a) `prompt --output-format json` must write the `kind:"missing_credentials"` JSON envelope to **stdout**, not stderr — same fix as #447 for all error envelopes; (b) add `prompt_ready:bool` and `prompt_blocked_reason:string|null` to the `auth` check in `doctor --output-format json`; derive it as `api_key_present || auth_token_present || legacy_saved_oauth_present`; (c) `prompt` must run the credential preflight check (same codepath as doctor's auth check) before attempting any API call and emit `{"kind":"missing_credentials","prompt_blocked_reason":"auth_missing"}` on **stdout** with exit 1 if the check fails; (d) `--output-format json` stdout routing fix must cover: `prompt`, `session list` (cross-ref #449), `skills uninstall` (cross-ref #431), `resume` (cross-ref #435), `acp serve` (cross-ref #443) — the full `kind:"missing_credentials"` class; (e) regression test: `claw prompt hello --output-format json` with no creds writes JSON to stdout (0 bytes stderr), exits 1, `kind:"missing_credentials"`, in under 200ms (no network attempt). **Why this matters:** `prompt` is the primary consumer entry point. Auth-absent failure routing to stderr breaks every automation wrapper that captures `$(claw prompt ... --output-format json)`. The `doctor` preflight metadata gap means auth-readiness checks require parsing 5 legacy fields instead of reading one boolean. Cross-references #447 (all JSON error envelopes on stderr), #449 (session list hits auth gate), #431 (skills uninstall hits auth gate), #357 (auth gate on local ops cluster), #422 (exit-code parity). Source: Jobdori live dogfood, `a35ee9a0`, 2026-05-16.
|
||||
|
||||
451. **Dogfood automation can silently run probes in the wrong repository/worktree when adjacent checkouts share similarly named binaries and stale build artifacts, so reports may mix evidence from Clawdbot/OMX with claw-code** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 12:00/12:30 UTC nudge cycle. While following a tmux-hook JSON lifecycle probe, the shell reported `/home/bellman/clawd` as the top-level worktree and executed `node dist/cli/omx.js ...` from Clawdbot/OMX artifacts instead of a claw-code checkout; a later correction found the actual claw-code repos under `/home/bellman/Workspace/claw-code-*`, including one `main` checkout hundreds of commits behind `origin/main`. The transcript therefore briefly contained plausible-looking CLI evidence from the wrong product tree before git provenance checks caught it. **Required fix shape:** (a) before dogfood probes, emit a mandatory machine-readable provenance preflight with repo root, remote URL, branch, HEAD, upstream HEAD, ahead/behind counts, binary path, and embedded build SHA when available; (b) make report templates include this provenance block before any command evidence; (c) warn or block when the requested product name does not match the remote/package/binary identity, or when the checkout is behind the target upstream by a configured threshold; (d) add regression coverage around multi-worktree/multi-product environments proving dogfood harnesses cannot silently attribute evidence from a neighboring repo or stale artifact. **Why this matters:** stale-branch confusion is not just a git annoyance; it corrupts the evidence chain. Claws can land or report fixes against the wrong codebase if the harness does not prove repo and binary identity before probing. Source: gaebal-gajae dogfood response to Clawhip messages `1506265193863446711` and `1506272743895728249` on 2026-05-19.
|
||||
|
||||
|
||||
452. **Validated claw-code checkouts can have no runnable local debug binary, and the failure is a raw shell `No such file or directory` instead of a typed build/provenance preflight result** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 13:00 UTC nudge after applying the provenance guard from #451. The corrected claw-code checkout was `/home/bellman/Workspace/claw-code-pr2967` with remote `https://github.com/ultraworkers/claw-code.git`, branch `docs/roadmap-workdir-provenance`, HEAD `6183d95`, upstream `origin/main` at `f8e1bb7`, and ahead/behind `1/0`. The first real probe then failed before reaching claw-code logic: `timeout --kill-after=1s 8s ./rust/target/debug/claw plugins list --output-format json` exited `127` with stderr `timeout: failed to run command './rust/target/debug/claw': No such file or directory` and empty stdout. This is distinct from stale-binary mismatch: here the selected checkout is identifiable, but there is no built binary and no canonical instruction/result telling automation whether to build, locate an installed `claw`, or stop. **Required fix shape:** (a) provide a canonical `claw dogfood preflight --output-format json` or equivalent script that checks expected binary paths, installed binary fallback, embedded build SHA, workspace HEAD, and build freshness before any product probe; (b) when the expected local binary is absent, return a typed result such as `kind:"dogfood_preflight"`, `binary_status:"missing"`, `expected_path`, `recommended_build_command`, and `can_use_installed_binary:false|true`; (c) integrate the preflight into dogfood report templates so a missing build artifact is reported as startup friction, not a raw shell 127; (d) add regression/fixture coverage for missing binary, stale binary, matching debug binary, and installed-binary fallback cases. **Why this matters:** after #451 proves the repo is right, claws still need to prove the executable exists and corresponds to that repo. A raw shell missing-file error wastes a nudge cycle and tempts operators to run whatever stale binary happens to be nearby. Source: gaebal-gajae dogfood response to Clawhip message `1506280293831544997` on 2026-05-19.
|
||||
|
||||
|
||||
453. **Plugin list JSON can report bundled plugin `source` paths from a stale user registry in a different checkout, with no stale-source warning or current-bundled-root distinction** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 13:30 UTC nudge after a successful local build in `/home/bellman/Workspace/claw-code-pr2967` (branch `docs/roadmap-workdir-provenance`, HEAD `25d663d`, binary `./rust/target/debug/claw` reporting `git_sha:"25d663d"`). Running `./rust/target/debug/claw plugins list --output-format json` returned structured `plugins[]`, but both bundled plugin entries reported `source` under `/home/bellman/Workspace/claw-code-parity-worktrees/clawcode-ux-enhance/...` instead of the current checkout. Cleaning and rebuilding the `plugins` crate did not change the output; the stale paths came from `~/.claw/plugins/installed.json`, where bundled plugin records persisted old `source.path` values. The JSON payload gave no `source_stale`, `source_exists`, `current_bundled_root`, `registry_path`, or `source_origin:"registry"` cue, so automation would treat another worktree's bundled plugin path as current truth. **Required fix shape:** (a) for bundled plugins, derive/display source from the current binary/workspace bundled root rather than a persistent user registry path when possible; (b) if registry source is retained, expose `registry_path`, `source_origin`, `source_exists`, `source_matches_current_bundle_root`, and `current_bundled_root` fields; (c) warn in text mode and JSON diagnostics when bundled plugin registry records point outside the current binary/workspace provenance; (d) add regression coverage where `installed.json` contains stale bundled paths from another checkout and `plugins list --output-format json` either self-heals or marks the source stale. **Why this matters:** plugin lifecycle actions rely on source provenance. If a fresh build from checkout A reports bundled plugin sources from checkout B, claws can inspect, enable, update, or debug the wrong plugin tree and misattribute lifecycle failures to current code. Source: gaebal-gajae dogfood response to Clawhip message `1506287843021160500` on 2026-05-19.
|
||||
|
||||
|
||||
454. **`plugins help --output-format json` returns a success-shaped plugin inventory plus `Unknown /plugins action 'help'` prose instead of structured plugin command help or supported lifecycle actions** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 14:00 UTC nudge on a freshly built binary from `/home/bellman/Workspace/claw-code-pr2967` (`./rust/target/debug/claw version --output-format json` reported `git_sha:"25d663d"`; the worktree had roadmap-only commits ahead of that source build). Running `./rust/target/debug/claw plugins help --output-format json` exited `0` and returned JSON with `kind:"plugin"`, `action:"help"`, `status:"ok"`, a full `plugins[]` inventory, and message `Unknown /plugins action 'help'. Use list, install, enable, disable, uninstall, or update.` It did not return `supported_actions[]`, usage, action metadata, destructive-action markers, target requirements, or a typed `unsupported_action` / `help_unavailable` status. The same probe also confirmed `plugins show example-bundled --output-format json` still returns `status:"ok"` plus an unknown-action message and no selected `plugin` object, matching the existing unsupported-show class, but the new pinpoint is the absence of a plugin lifecycle discovery/help contract. **Required fix shape:** (a) implement `plugins help --output-format json` as a real help/discovery payload with `supported_actions[]`, per-action `requires_target`, `destructive`, `resume_safe`/automation notes, usage, and examples; (b) if `help` is intentionally unsupported, return a non-ok typed JSON envelope with `code:"unsupported_plugin_action"` and structured `supported_actions[]`, not `status:"ok"`; (c) avoid attaching full plugin inventory to unsupported/help responses unless requested, or mark it as incidental; (d) add regression coverage proving plugin lifecycle help is machine-readable and does not require scraping `message` for available actions. **Why this matters:** plugin lifecycle commands include install/enable/disable/update/uninstall; claws need a safe discovery surface before attempting mutations. A success-shaped unknown-help response with only prose action names keeps lifecycle automation brittle and encourages trial-and-error against mutating commands. Source: gaebal-gajae dogfood response to Clawhip message `1506295397285494905` on 2026-05-19.
|
||||
|
||||
|
||||
455. **Plugin help entrypoints hang before producing any help bytes, and with normal user config they emit only a deprecation warning before timeout** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 14:30 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. Bounded probes of `plugins --help --output-format json`, `plugins help --output-format json`, and `plugins list --help --output-format json` each timed out after 8s with `stdout=0`; under the normal user config each had `stderr=121` containing only the repeated config deprecation warning, and under an isolated clean `HOME`/`CLAW_CONFIG_HOME` even `plugins --help` and JSON help forms timed out with both stdout and stderr empty. This is distinct from #454's success-shaped unknown-help JSON observed on the action-dispatch path: the flag-style/local help path can hang before returning any help or typed JSON at all. **Required fix shape:** (a) route `plugins --help`, `plugins help`, and subcommand help forms through static help rendering before config/plugin registry loading; (b) make JSON help return bounded stdout JSON with `kind:"plugin"`, `action:"help"`, `supported_actions[]`, and usage metadata; (c) if dynamic plugin state is intentionally consulted, enforce a short internal timeout and return a typed `plugin_help_unavailable` JSON error instead of zero-byte hangs; (d) add regression coverage with clean home and deprecated-config home proving plugin help emits bytes promptly and does not initialize slow lifecycle/registry paths. **Why this matters:** help must be the safe escape hatch when plugin lifecycle is broken. If every plugin help spelling can hang before bytes, claws cannot discover valid plugin actions, recover from registry issues, or explain how to fix lifecycle state without external docs. Source: gaebal-gajae dogfood response to Clawhip message `1506302942754639892` on 2026-05-19.
|
||||
|
||||
|
||||
456. **Static `--help --output-format json` hangs across multiple local lifecycle namespaces under a clean home, so help discovery is not a reliable no-side-effect escape hatch** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 15:00 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. After pushing the accumulated roadmap branch, clean-environment probes using isolated `HOME` and `CLAW_CONFIG_HOME` showed that `mcp --help --output-format json`, `agents --help --output-format json`, `skills --help --output-format json`, `memory --help --output-format json`, and `session --help --output-format json` each timed out after 8s with `stdout=0` and `stderr=0`. This extends the plugin-specific #455 into a shared parser/help-layer gap: even command namespaces whose help should be static and local can enter a zero-byte hang before any JSON or text help is emitted. **Required fix shape:** (a) centralize `--help`/`help` handling before config, auth, registry, session, MCP, memory, or plugin initialization for all local lifecycle namespaces; (b) make `--output-format json` help return a bounded stdout payload with `kind:"help"`, namespace, usage, supported actions/sections, output formats, and side-effect/auth requirements; (c) add a global deterministic help timeout guard that returns typed JSON such as `kind:"help_unavailable"` instead of allowing zero-byte hangs; (d) add clean-home regression coverage for `mcp`, `agents`, `skills`, `memory`, `session`, and `plugins` help forms proving they emit bytes promptly and do not touch slow lifecycle providers. **Why this matters:** help is the only safe discovery path when lifecycle state is broken. If help itself can hang with no bytes, claws cannot learn how to inspect or recover MCP, agents, skills, memory, sessions, or plugins without external docs or guesswork. Source: gaebal-gajae dogfood response to Clawhip message `1506310493080518767` on 2026-05-19.
|
||||
|
||||
|
||||
457. **Root help is bounded in text mode, but `help --output-format json` and command `--help --output-format json` convert help into a zero-byte hang** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 15:30 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. In an isolated clean `HOME`/`CLAW_CONFIG_HOME`, `./rust/target/debug/claw --help` and `./rust/target/debug/claw help` both exited 0 and printed 7403 bytes of root text help. But `help --output-format json`, `version --help --output-format json`, `doctor --help --output-format json`, and `status --help --output-format json` each timed out after 8s with `stdout=0` and `stderr=0`. This narrows #456: the help renderer itself is capable of returning promptly, but the parser/order path that combines help with JSON output appears to route into a different slow/non-returning path. **Required fix shape:** (a) parse `--help`/`help` before selecting command execution paths, auth, provider, session, or lifecycle initialization, while still preserving requested output format; (b) make root and command help JSON static/bounded with `kind:"help"`, `scope`, `command`, `usage`, `options`, `examples`, and `supported_output_formats`; (c) add regression coverage proving `claw --help`, `claw help`, `claw help --output-format json`, and representative command help forms all return within a small deterministic budget under clean home; (d) ensure JSON help does not silently fall back to text or zero-byte timeout. **Why this matters:** this is a parser-order failure in the safest command surface. Operators can get text help, but the moment automation asks for JSON discovery, help becomes a hang, forcing claws back to prose scraping or external docs. Source: gaebal-gajae dogfood response to Clawhip message `1506318038167847045` on 2026-05-19.
|
||||
|
||||
|
||||
458. **Global output-format flag ordering is parser-hostile, and even `version --output-format json` can hang under clean env despite normal-env success** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 16:00 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with normal-env `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. Clean-environment flag-order probes showed `--output-format json --help`, `--output-format json help`, `--output-format json version`, `--version --output-format json`, and `--output-format json --version` each failed immediately with text-mode stderr only, e.g. `[error-kind: cli_parse] error: unknown option: --output-format json --help`, and `stdout=0`. The parser appears to treat the whole trailing string as one unknown option rather than recognizing a global output-format flag before the command. Separately, in the same clean `HOME`/`CLAW_CONFIG_HOME`, canonical `version --output-format json` timed out after 8s with `stdout=0`/`stderr=0`, even though the same command succeeds in the normal environment. **Required fix shape:** (a) make global flags such as `--output-format json` accepted before or after the subcommand, or return structured JSON `cli_parse` errors on stdout when JSON format is requested anywhere in argv; (b) parse flag values as separate tokens in error reporting instead of echoing combined strings like `--output-format json --help` as one option; (c) ensure `version` is fully local/static and cannot hang under clean env or missing config/auth; (d) add clean-env regression coverage for `version --output-format json`, `--output-format json version`, `--version --output-format json`, and JSON parse errors with stdout envelopes. **Why this matters:** claws often put global flags first for CLI uniformity and run in sanitized envs. If global JSON selection is order-sensitive and local version can hang only under clean env, startup probes become unreliable exactly in CI/sandbox contexts. Source: gaebal-gajae dogfood response to Clawhip message `1506325598245748828` on 2026-05-19.
|
||||
|
||||
|
||||
459. **Dogfood timeout claims lack a required retry/evidence contract, so transient hangs can be recorded as durable product gaps without immediate reproducibility metadata** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 16:30 UTC nudge while narrowing #458 on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. The previous 16:00 pass saw clean-env `version --output-format json` time out with zero bytes, but a focused 16:30 retry matrix using isolated `HOME`/`CLAW_CONFIG_HOME` plus minimal env variants (`TERM`, `USER`/`LOGNAME`, `SHELL`, `LANG`/`LC_ALL`, and all combined) returned valid version JSON every time. That means the earlier timeout may have been transient harness load, process scheduling, or invocation interference, while the report format had no mandatory retry count, timing, command log artifact, or “reproduced N/M” field to distinguish flaky evidence from stable behavior. **Required fix shape:** (a) dogfood timeout reports must include retry count, per-attempt exit code/stdout/stderr byte counts, elapsed duration, env summary, binary provenance, and whether the failure reproduced after process isolation; (b) add a standard `timeout_evidence` block to report templates and ROADMAP entries before filing zero-byte hang claims; (c) classify un-reproduced hangs as `flaky_unconfirmed` with follow-up probes instead of stable product bugs; (d) provide a small harness command that runs bounded retries and emits machine-readable evidence JSON. **Why this matters:** zero-byte timeouts are high-severity but easy to misattribute. Without a retry/evidence contract, claws can pollute the backlog with transient scheduler artifacts or miss real nondeterministic hangs because the evidence shape is too thin. Source: gaebal-gajae dogfood response to Clawhip message `1506333141571211314` on 2026-05-19.
|
||||
|
||||
|
||||
460. **Root `help --output-format json` is reproducibly bounded but only wraps 7.4KB of prose in `{kind,message}`, with no structured command or slash-command schema** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 17:00 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. Applying the retry/evidence discipline from #459, three clean-home attempts of `./rust/target/debug/claw help --output-format json` all exited 0 with `stdout=7563`, `stderr=0`, valid JSON keys exactly `kind,message`, `kind:"help"`, and a `message` string of length 7401. There were no `commands[]`, `options[]`, `slash_commands[]`, resume-safety flags, output-format support metadata, side-effect/auth requirements, or examples as structured fields. This supersedes the flaky zero-byte hang framing from #457 for root help: the stable reproducible gap is schema opacity, not timeout. **Required fix shape:** (a) keep `message` for human rendering but add a versioned structured help schema with `schema_version`, `commands[]`, `global_options[]`, `slash_commands[]`, `examples[]`, and `related_docs[]`; (b) include per-command fields such as `name`, `aliases`, `usage`, `description`, `supports_json`, `requires_auth`, `side_effects`, and `resume_safe` where applicable; (c) expose slash-command metadata without requiring prose scraping; (d) add regression coverage proving root help JSON has stable structured fields and that old `message` remains optional/backward-compatible. **Why this matters:** help JSON is the bootstrap discovery surface for claws. Valid JSON that contains only prose still forces automation to scrape text before choosing safe commands or resume paths. Source: gaebal-gajae dogfood response to Clawhip message `1506340691431657472` on 2026-05-19.
|
||||
|
||||
|
||||
461. **Command-specific `--help --output-format json` reproducibly zero-byte hangs even though root JSON help returns bounded prose JSON** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 17:30 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. After #460 confirmed root `help --output-format json` is bounded but schema-opaque, two clean-home attempts each for `status --help --output-format json`, `doctor --help --output-format json`, `version --help --output-format json`, and `sandbox --help --output-format json` all timed out after 8s with `stdout=0` and `stderr=0`. This establishes a split contract: root JSON help reaches the help serializer, while command-specific JSON help falls into a non-returning command execution/parser path. **Required fix shape:** (a) add a command-help dispatch layer that catches `<command> --help` before entering the command's runtime handler; (b) share the same bounded JSON help schema from #460 for command-specific help, with fields `kind:"help"`, `command`, `usage`, `options`, `examples`, `supports_json`, `requires_auth`, and `side_effects`; (c) ensure local/static commands like `version`, `status`, `doctor`, and `sandbox` never initialize slow providers just to render help; (d) add clean-home regression coverage proving command-specific JSON help emits bytes promptly for representative static and lifecycle commands. **Why this matters:** claws often discover command contracts one command at a time. If root help is available but every command's JSON help hangs, automation still cannot inspect option-level semantics safely and must scrape root prose or guess. Source: gaebal-gajae dogfood response to Clawhip message `1506348241128788111` on 2026-05-19.
|
||||
|
||||
|
||||
462. **Text-mode `<command> --help` also zero-byte hangs, proving the bug is command-help dispatch rather than JSON serialization** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 18:00 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. After #461 showed command-specific JSON help hangs, a text-mode retry matrix in isolated clean `HOME`/`CLAW_CONFIG_HOME` showed two attempts each for `status --help`, `doctor --help`, `version --help`, `sandbox --help`, `mcp --help`, `agents --help`, and `skills --help` all timed out after 6s with `stdout=0` and `stderr=0`. Root `--help` and `help` remain bounded, so the split is not help text rendering generally and not JSON formatting specifically; it is the command-specific help dispatch path failing to intercept `<command> --help` before some non-returning runtime path. **Required fix shape:** (a) implement a first-stage argv parser that recognizes `<command> --help` and `<command> help` for every registered command before command runtime initialization; (b) render static text help in text mode and structured JSON help in JSON mode from the same command metadata registry; (c) add regression coverage for both text and JSON help for representative static commands (`version`, `status`, `doctor`, `sandbox`) and lifecycle commands (`mcp`, `agents`, `skills`, `plugins`); (d) ensure every command-help path has a bounded no-provider/no-auth/no-config execution budget. **Why this matters:** users do not only run root `--help`; they naturally ask `claw status --help` or `claw mcp --help`. If that path hangs silently, the product loses the most basic local recovery surface before any real action starts. Source: gaebal-gajae dogfood response to Clawhip message `1506355792582938665` on 2026-05-19.
|
||||
|
||||
|
||||
463. **Root help advertises direct slash examples like `claw /skills`, but direct slash behavior is inconsistent: `/skills` runs a huge local report, `/help` aliases root help, while `/status` is rejected as interactive-only despite being marked resume-safe** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 18:30 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. Root help examples include `claw /skills`, and clean-home probes showed direct `claw /skills` exits 0 and prints a 27KB skill inventory, while direct `claw /help` exits 0 and prints root help. But direct `claw /status` exits 1 with `slash command /status is interactive-only... use claw --resume SESSION.jsonl /status ... when the command is marked [resume] in /help`, even though `/status` is explicitly marked `[resume]` in root help and has a top-level sibling `claw status`. The same surface therefore mixes three semantics for direct slash invocation: accepted alias, accepted local slash report, and rejected interactive-only/resume-only command. **Required fix shape:** (a) define a single direct-slash CLI contract: either reject all slash commands outside REPL/resume with structured guidance, or allow resume-safe/local slash commands consistently; (b) if allowing direct slash commands, route `/status` to the same local/resume-safe status serializer as `claw status` or require `--resume` with a typed `resume_required` code; (c) make help examples distinguish top-level commands from slash commands and avoid advertising `claw /skills` unless direct slash invocation is intentional for the whole supported set; (d) add regression coverage for `/help`, `/skills`, `/status`, `/mcp`, `/agents`, and their top-level equivalents proving consistent direct/resume/text/json behavior. **Why this matters:** claws copy help examples literally. If `claw /skills` works but `claw /status` says interactive-only despite `[resume]`, automation cannot infer which slash commands are safe outside the REPL and will oscillate between direct, top-level, and resume forms. Source: gaebal-gajae dogfood response to Clawhip message `1506363340048564425` on 2026-05-19.
|
||||
|
||||
|
||||
464. **Global `--output-format json` placement is broken for top-level subcommands: post-subcommand placement silently hangs, while pre-subcommand placement is rejected as `cli_parse` despite help documenting `--output-format` as a global flag** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 19:00/19:30 UTC nudges on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. Root help lists `--output-format FORMAT` in the global Flags section and examples like `claw [--model MODEL] [--output-format text|json] prompt TEXT`, while top-level commands (`claw status`, `claw doctor`, `claw mcp`, `claw skills`, `claw version`) advertise local diagnostic/report surfaces. Clean-home probes showed two distinct bad paths: `claw version --output-format json`, `claw status --output-format json`, and `claw skills --output-format json` timed out after 5s with `stdout=0`/`stderr=0`; the more canonical GNU-style global placement `claw --output-format json version`, `claw --output-format json status`, and `claw --output-format json skills` exited 1 with `[error-kind: cli_parse] error: unknown option: --output-format json <command>`. The only working form observed for `version` is the special local parser path used by `claw version --output-format json` in a non-clean environment/provenance preflight, which contradicts the clean-home bounded test and suggests the parser/runtime path is environment-sensitive as well as placement-sensitive. **Required fix shape:** (a) make `--output-format` a true global flag accepted before any subcommand and before slash commands; (b) make top-level local commands also accept trailing `--output-format` if the project wants common CLI ergonomics; (c) normalize both placements into one parsed `OutputFormat` before command dispatch; (d) ensure parse errors in JSON-requested mode emit structured JSON rather than prose-on-stderr; (e) add clean-home regression coverage for `claw --output-format json version|status|doctor|mcp|skills` and `claw version|status|doctor|mcp|skills --output-format json`, with bounded no-provider execution. **Why this matters:** claws and shell users routinely place global flags either before or after subcommands. A machine-readable output flag that sometimes hangs and sometimes parse-errors means automation cannot reliably request JSON for the exact local diagnostics it needs during recovery. Source: gaebal-gajae dogfood response to Clawhip messages `1506370889653027012` and `1506378443812765756` on 2026-05-19.
|
||||
|
||||
|
||||
465. **`claw skills` is technically bounded but operationally noisy: the default text output dumps full descriptions for every discovered skill (~27KB / 65 skills in clean-home dogfood), unlike `status`, `doctor`, `mcp`, `sandbox`, and `agents` which stay compact enough for recovery logs** — dogfooded 2026-05-19 from the `#clawcode-building-in-public` 20:00 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with `./rust/target/debug/claw version --output-format json` reporting `git_sha:"25d663d"`. Clean-home text probes showed `version`, `status`, `doctor`, `mcp`, `sandbox`, and `agents` all exit promptly with compact diagnostic reports (`version` 136 bytes, `status` 1274 bytes, `doctor` 2519 bytes, `mcp` 117 bytes, `sandbox` 352 bytes, `agents` 17 bytes). `skills` also exits 0, but emits 27,573 bytes by default because it prints every skill name plus full description. This makes `claw skills` a poor first-response diagnostic in clawhip logs, tmux tails, CI failure artifacts, and copied support snippets: the useful inventory count and roots are buried under pages of prose. This is distinct from discovery-scope/security issues (#85/#95): even when the skill set is legitimate, the default report shape is too verbose for a recovery command. **Required fix shape:** (a) make default text `claw skills` compact: counts by source/root plus names only, truncated with an explicit `--verbose` hint; (b) add `claw skills --verbose` or `claw skills list --verbose` for full descriptions; (c) add `--format compact|verbose` or reuse `--compact` if the CLI standardizes it; (d) keep JSON mode complete but add `summary` and `entries[].description` fields so consumers can choose; (e) add regression coverage enforcing that default text output for 50+ skills stays under a small byte/line budget while verbose preserves current detail. **Why this matters:** local diagnostics should be safe to paste and scan during failures. A 27KB default skill dump hides the actual signal and makes every dogfood/support loop noisier than necessary. Source: gaebal-gajae dogfood response to Clawhip message `1506385992368652489` on 2026-05-19.
|
||||
|
||||
708
SCHEMAS.md
708
SCHEMAS.md
@@ -1,708 +0,0 @@
|
||||
# JSON Envelope Schemas — Clawable CLI Contract
|
||||
|
||||
> **⚠️ CRITICAL: This document describes the TARGET v2.0 envelope schema, not the current v1.0 binary behavior.** The Rust binary currently emits a **flat v1.0 envelope** that does NOT include `timestamp`, `command`, `exit_code`, `output_format`, or `schema_version` fields. See [`FIX_LOCUS_164.md`](./FIX_LOCUS_164.md) for the full migration plan and timeline. **Do not build automation against the field shapes below without first testing against the actual binary output.** Use `claw <command> --output-format json` to inspect what your binary version actually emits.
|
||||
|
||||
This document locks the **target** field-level contract for all clawable-surface commands. After the v1.0→v2.0 migration (FIX_LOCUS_164 Phase 2), every command accepting `--output-format json` will conform to the envelope shapes documented here.
|
||||
|
||||
**Target audience:** Claws planning v2.0 migration, reference implementers, contract validators.
|
||||
|
||||
**Current v1.0 reality:** See [`ERROR_HANDLING.md`](./ERROR_HANDLING.md) Appendix A for the flat envelope shape the binary actually emits today.
|
||||
|
||||
---
|
||||
|
||||
## Common Fields (All Envelopes) — TARGET v2.0 SCHEMA
|
||||
|
||||
**This section describes the v2.0 target schema. The current v1.0 binary does NOT emit these fields.** See FIX_LOCUS_164.md for the migration timeline.
|
||||
|
||||
After v2.0 migration, every command response, success or error, will carry:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "list-sessions",
|
||||
"exit_code": 0,
|
||||
"output_format": "json",
|
||||
"schema_version": "2.0"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| `timestamp` | ISO 8601 UTC | Yes | Time command completed |
|
||||
| `command` | string | Yes | argv[1] (e.g. "list-sessions") |
|
||||
| `exit_code` | int (0/1/2) | Yes | 0=success, 1=error/not-found, 2=timeout |
|
||||
| `output_format` | string | Yes | Always "json" (for symmetry with text mode) |
|
||||
| `schema_version` | string | Yes | "1.0" (bump for breaking changes) |
|
||||
|
||||
---
|
||||
|
||||
## Turn Result Fields (Multi-Turn Sessions)
|
||||
|
||||
When a command's response includes a `turn` object (e.g., in `bootstrap` or `turn-loop`), it carries:
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| `prompt` | string | Yes | User input for this turn |
|
||||
| `output` | string | Yes | Assistant response |
|
||||
| `stop_reason` | enum | Yes | One of: `completed`, `timeout`, `cancelled`, `max_budget_reached`, `max_turns_reached` |
|
||||
| `cancel_observed` | bool | Yes | #164 Stage B: cancellation was signaled and observed (#161/#164) |
|
||||
|
||||
---
|
||||
|
||||
## Error Envelope
|
||||
|
||||
When a command fails (exit code 1), responses carry:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "exec-command",
|
||||
"exit_code": 1,
|
||||
"error": {
|
||||
"kind": "filesystem",
|
||||
"operation": "write",
|
||||
"target": "/tmp/nonexistent/out.md",
|
||||
"retryable": true,
|
||||
"message": "No such file or directory",
|
||||
"hint": "intermediate directory does not exist; try mkdir -p /tmp/nonexistent"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| `error.kind` | enum | Yes | One of: `filesystem`, `auth`, `session`, `parse`, `runtime`, `mcp`, `delivery`, `usage`, `policy`, `unknown` |
|
||||
| `error.operation` | string | Yes | Syscall/method that failed (e.g. "write", "open", "resolve_session") |
|
||||
| `error.target` | string | Yes | Resource that failed (path, session-id, server-name, etc.) |
|
||||
| `error.retryable` | bool | Yes | Whether caller can safely retry without intervention |
|
||||
| `error.message` | string | Yes | Platform error message (e.g. errno text) |
|
||||
| `error.hint` | string | No | Optional actionable next step |
|
||||
|
||||
---
|
||||
|
||||
## Not-Found Envelope
|
||||
|
||||
When an entity does not exist (exit code 1, but not a failure):
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "load-session",
|
||||
"exit_code": 1,
|
||||
"name": "does-not-exist",
|
||||
"found": false,
|
||||
"error": {
|
||||
"kind": "session_not_found",
|
||||
"message": "session 'does-not-exist' not found in .claw/sessions/",
|
||||
"retryable": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| `name` | string | Yes | Entity name/id that was looked up |
|
||||
| `found` | bool | Yes | Always `false` for not-found |
|
||||
| `error.kind` | enum | Yes | One of: `command_not_found`, `tool_not_found`, `session_not_found` |
|
||||
| `error.message` | string | Yes | User-visible explanation |
|
||||
| `error.retryable` | bool | Yes | Usually `false` (entity will not magically appear) |
|
||||
|
||||
---
|
||||
|
||||
## Per-Command Success Schemas
|
||||
|
||||
### `list-sessions`
|
||||
|
||||
**Status**: ✅ Implemented (closed #251 cycle #45, 2026-04-23).
|
||||
|
||||
**Actual binary envelope** (as of #251 fix):
|
||||
```json
|
||||
{
|
||||
"command": "list-sessions",
|
||||
"sessions": [
|
||||
{
|
||||
"id": "session-1775777421902-1",
|
||||
"path": "/path/to/.claw/sessions/session-1775777421902-1.jsonl",
|
||||
"updated_at_ms": 1775777421902,
|
||||
"message_count": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Aspirational (future) shape**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "list-sessions",
|
||||
"exit_code": 0,
|
||||
"output_format": "json",
|
||||
"schema_version": "1.0",
|
||||
"directory": ".claw/sessions",
|
||||
"sessions_count": 2,
|
||||
"sessions": [
|
||||
{
|
||||
"session_id": "sess_abc123",
|
||||
"created_at": "2026-04-21T15:30:00Z",
|
||||
"last_modified": "2026-04-22T09:45:00Z",
|
||||
"prompt_count": 5,
|
||||
"stopped": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Gap**: Current impl lacks `timestamp`, `exit_code`, `output_format`, `schema_version`, `directory`, `sessions_count` (derivable), and the session object uses `id`/`updated_at_ms`/`message_count` instead of `session_id`/`last_modified`/`prompt_count`. Follow-up #250 Option B to align field names and add common-envelope fields.
|
||||
|
||||
### `delete-session`
|
||||
|
||||
**Status**: ⚠️ Stub only (closed #251 dispatch-order fix; full impl deferred).
|
||||
|
||||
**Actual binary envelope** (as of #251 fix):
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"command": "delete-session",
|
||||
"error": "not_yet_implemented",
|
||||
"kind": "not_yet_implemented"
|
||||
}
|
||||
```
|
||||
|
||||
Exit code: 1. No credentials required. The stub ensures the verb does NOT fall through to Prompt/auth (the #251 fix), but the actual delete operation is not yet wired.
|
||||
|
||||
**Aspirational (future) shape**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "delete-session",
|
||||
"exit_code": 0,
|
||||
"session_id": "sess_abc123",
|
||||
"deleted": true,
|
||||
"directory": ".claw/sessions"
|
||||
}
|
||||
```
|
||||
|
||||
### `load-session`
|
||||
|
||||
**Status**: ✅ Implemented (closed #251 cycle #45, 2026-04-23).
|
||||
|
||||
**Actual binary envelope** (as of #251 fix):
|
||||
```json
|
||||
{
|
||||
"command": "load-session",
|
||||
"session": {
|
||||
"id": "session-abc123",
|
||||
"path": "/path/to/.claw/sessions/session-abc123.jsonl",
|
||||
"messages": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For nonexistent sessions, emits a local `session_not_found` error (NOT `missing_credentials`):
|
||||
```json
|
||||
{
|
||||
"error": "session not found: nonexistent",
|
||||
"kind": "session_not_found",
|
||||
"type": "error",
|
||||
"hint": "Hint: managed sessions live in .claw/sessions/<hash>/ ..."
|
||||
}
|
||||
```
|
||||
|
||||
**Aspirational (future) shape**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "load-session",
|
||||
"exit_code": 0,
|
||||
"session_id": "sess_abc123",
|
||||
"loaded": true,
|
||||
"directory": ".claw/sessions",
|
||||
"path": ".claw/sessions/sess_abc123.jsonl"
|
||||
}
|
||||
```
|
||||
|
||||
**Gap**: Current impl uses nested `session: {...}` instead of flat fields, and omits common-envelope fields. Follow-up #250 Option B to align.
|
||||
|
||||
### `flush-transcript`
|
||||
|
||||
**Status**: ⚠️ Stub only (closed #251 dispatch-order fix; full impl deferred).
|
||||
|
||||
**Actual binary envelope** (as of #251 fix):
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"command": "flush-transcript",
|
||||
"error": "not_yet_implemented",
|
||||
"kind": "not_yet_implemented"
|
||||
}
|
||||
```
|
||||
|
||||
Exit code: 1. No credentials required. Like `delete-session`, this stub resolves the #251 dispatch-order bug but the actual flush operation is not yet wired.
|
||||
|
||||
**Aspirational (future) shape**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "flush-transcript",
|
||||
"exit_code": 0,
|
||||
"session_id": "sess_abc123",
|
||||
"path": ".claw/sessions/sess_abc123.jsonl",
|
||||
"flushed": true,
|
||||
"messages_count": 12,
|
||||
"input_tokens": 4500,
|
||||
"output_tokens": 1200
|
||||
}
|
||||
```
|
||||
|
||||
### `show-command`
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "show-command",
|
||||
"exit_code": 0,
|
||||
"name": "add-dir",
|
||||
"found": true,
|
||||
"source_hint": "commands/add-dir/add-dir.tsx",
|
||||
"responsibility": "creates a new directory in the worktree"
|
||||
}
|
||||
```
|
||||
|
||||
### `show-tool`
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "show-tool",
|
||||
"exit_code": 0,
|
||||
"name": "BashTool",
|
||||
"found": true,
|
||||
"source_hint": "tools/BashTool/BashTool.tsx"
|
||||
}
|
||||
```
|
||||
|
||||
### `exec-command`
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "exec-command",
|
||||
"exit_code": 0,
|
||||
"name": "add-dir",
|
||||
"prompt": "create src/util/",
|
||||
"handled": true,
|
||||
"message": "created directory",
|
||||
"source_hint": "commands/add-dir/add-dir.tsx"
|
||||
}
|
||||
```
|
||||
|
||||
### `exec-tool`
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "exec-tool",
|
||||
"exit_code": 0,
|
||||
"name": "BashTool",
|
||||
"payload": "cargo build",
|
||||
"handled": true,
|
||||
"message": "exit code 0",
|
||||
"source_hint": "tools/BashTool/BashTool.tsx"
|
||||
}
|
||||
```
|
||||
|
||||
### `route`
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "route",
|
||||
"exit_code": 0,
|
||||
"prompt": "add a test",
|
||||
"limit": 10,
|
||||
"match_count": 3,
|
||||
"matches": [
|
||||
{
|
||||
"kind": "command",
|
||||
"name": "add-file",
|
||||
"score": 0.92,
|
||||
"source_hint": "commands/add-file/add-file.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `bootstrap`
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "bootstrap",
|
||||
"exit_code": 0,
|
||||
"prompt": "hello",
|
||||
"setup": {
|
||||
"python_version": "3.13.12",
|
||||
"implementation": "CPython",
|
||||
"platform_name": "darwin",
|
||||
"test_command": "pytest"
|
||||
},
|
||||
"routed_matches": [
|
||||
{"kind": "command", "name": "init", "score": 0.85, "source_hint": "..."}
|
||||
],
|
||||
"turn": {
|
||||
"prompt": "hello",
|
||||
"output": "...",
|
||||
"stop_reason": "completed"
|
||||
},
|
||||
"persisted_session_path": ".claw/sessions/sess_abc.jsonl"
|
||||
}
|
||||
```
|
||||
|
||||
### `command-graph`
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "command-graph",
|
||||
"exit_code": 0,
|
||||
"builtins_count": 185,
|
||||
"plugin_like_count": 20,
|
||||
"skill_like_count": 2,
|
||||
"total_count": 207,
|
||||
"builtins": [
|
||||
{"name": "add-dir", "source_hint": "commands/add-dir/add-dir.tsx"}
|
||||
],
|
||||
"plugin_like": [],
|
||||
"skill_like": []
|
||||
}
|
||||
```
|
||||
|
||||
### `tool-pool`
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "tool-pool",
|
||||
"exit_code": 0,
|
||||
"simple_mode": false,
|
||||
"include_mcp": true,
|
||||
"tool_count": 184,
|
||||
"tools": [
|
||||
{"name": "BashTool", "source_hint": "tools/BashTool/BashTool.tsx"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `bootstrap-graph`
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-22T10:10:00Z",
|
||||
"command": "bootstrap-graph",
|
||||
"exit_code": 0,
|
||||
"stages": ["stage 1", "stage 2", "..."],
|
||||
"note": "bootstrap-graph is markdown-only in this version"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Versioning & Compatibility
|
||||
|
||||
- **schema_version = "1.0":** Current as of 2026-04-22. Covers all 13 clawable commands.
|
||||
- **Breaking changes** (e.g. renaming a field) bump schema_version to "2.0".
|
||||
- **Additive changes** (e.g. new optional field) stay at "1.0" and are backward compatible.
|
||||
- Downstream claws **must** check `schema_version` before relying on field presence.
|
||||
|
||||
---
|
||||
|
||||
## Regression Testing
|
||||
|
||||
Each command is covered by:
|
||||
1. **Fixture file** (golden JSON snapshot under `tests/fixtures/json/<command>.json`)
|
||||
2. **Parametrised test** in `test_cli_parity_audit.py::TestJsonOutputContractEndToEnd`
|
||||
3. **Field consistency test** (new, tracked as ROADMAP #172)
|
||||
|
||||
To update a fixture after a intentional schema change:
|
||||
```bash
|
||||
claw <command> --output-format json <args> > tests/fixtures/json/<command>.json
|
||||
# Review the diff, commit
|
||||
git add tests/fixtures/json/<command>.json
|
||||
```
|
||||
|
||||
To verify no regressions:
|
||||
```bash
|
||||
cargo test --release test_json_envelope_field_consistency
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Notes
|
||||
|
||||
**Why common fields on every response?**
|
||||
- Downstream claws can build one error handler that works for all commands
|
||||
- Timestamp + command + exit_code give context without scraping argv or timestamps from command output
|
||||
- `schema_version` signals compatibility for future upgrades
|
||||
|
||||
**Why both "found" and "error" on not-found?**
|
||||
- Exit code 1 covers both "entity missing" and "operation failed"
|
||||
- `found=false` distinguishes not-found from error without string matching
|
||||
- `error.kind` and `error.retryable` let automation decide: retry a temporary miss vs escalate a permanent refusal
|
||||
|
||||
**Why "operation" and "target" in error?**
|
||||
- Claws can aggregate failures by operation type (e.g. "how many `write` ops failed?")
|
||||
- Claws can implement per-target retry policy (e.g. "skip missing files, retry networking")
|
||||
- Pure text errors ("No such file") do not provide enough structure for pattern matching
|
||||
|
||||
**Why "handled" vs "found"?**
|
||||
- `show-command` reports `found: bool` (inventory signal: "does this exist?")
|
||||
- `exec-command` reports `handled: bool` (operational signal: "was this work performed?")
|
||||
- The names matter: a command can be found but not handled (e.g. too large for context window), or handled silently (no output message)
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Current v1.0 vs. Target v2.0 Envelope Shapes
|
||||
|
||||
### ⚠️ IMPORTANT: Binary Reality vs. This Document
|
||||
|
||||
**This entire SCHEMAS.md document describes the TARGET v2.0 schema.** The actual Rust binary currently emits v1.0 (flat) envelopes.
|
||||
|
||||
**Do not assume the fields documented above are in the binary right now.** They are not.
|
||||
|
||||
### Current v1.0 Envelope (What the Rust Binary Actually Emits)
|
||||
|
||||
The Rust binary in `rust/` currently emits a **flat v1.0 envelope** without common metadata wrapper:
|
||||
|
||||
#### v1.0 Success Envelope Example
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "list-sessions",
|
||||
"sessions": [
|
||||
{"id": "abc123", "created": "2026-04-22T10:00:00Z", "turns": 5}
|
||||
],
|
||||
"type": "success"
|
||||
}
|
||||
```
|
||||
|
||||
**Key differences from v2.0 above:**
|
||||
- NO `timestamp`, `command`, `exit_code`, `output_format`, `schema_version` fields
|
||||
- `kind` field contains the verb name (or is entirely absent for success)
|
||||
- `type: "success"` flag at top level
|
||||
- Verb-specific fields (`sessions`, `turn`, etc.) at top level
|
||||
|
||||
#### v1.0 Error Envelope Example
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "session 'xyz789' not found in .claw/sessions",
|
||||
"hint": "use 'list-sessions' to see available sessions",
|
||||
"kind": "session_not_found",
|
||||
"type": "error"
|
||||
}
|
||||
```
|
||||
|
||||
**Key differences from v2.0 error above:**
|
||||
- `error` field is a **STRING**, not a nested object
|
||||
- NO `error.operation`, `error.target`, `error.retryable` structured fields
|
||||
- `kind` is at top-level, not nested
|
||||
- NO `timestamp`, `command`, `exit_code`, `output_format`, `schema_version`
|
||||
- Extra `type: "error"` flag
|
||||
|
||||
### Migration Timeline (FIX_LOCUS_164)
|
||||
|
||||
See [`FIX_LOCUS_164.md`](./FIX_LOCUS_164.md) for the full phased migration:
|
||||
|
||||
- **Phase 1 (Opt-in):** `claw <cmd> --output-format json --envelope-version=2.0` emits v2.0 shape
|
||||
- **Phase 2 (Default):** v2.0 becomes default; `--legacy-envelope` flag opts into v1.0
|
||||
- **Phase 3 (Deprecation):** v1.0 warnings, then removal
|
||||
|
||||
### Building Automation Against v1.0 (Current)
|
||||
|
||||
**For claws building automation today** (against the real binary, not this schema):
|
||||
|
||||
1. **Check `type` field first** (string: "success" or "error")
|
||||
2. **For success:** verb-specific fields are at top level. Use `jq .kind` for verb ID (if present)
|
||||
3. **For error:** access `error` (string), `hint` (string), `kind` (string) all at top level
|
||||
4. **Do not expect:** `timestamp`, `command`, `exit_code`, `output_format`, `schema_version` — they don't exist yet
|
||||
5. **Test your code** against `claw <cmd> --output-format json` output to verify assumptions before deploying
|
||||
|
||||
### Example: Python Consumer Code (v1.0)
|
||||
|
||||
**Correct pattern for v1.0 (current binary):**
|
||||
|
||||
```python
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
["claw", "list-sessions", "--output-format", "json"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
envelope = json.loads(result.stdout)
|
||||
|
||||
# v1.0: type is at top level
|
||||
if envelope.get("type") == "error":
|
||||
error_msg = envelope.get("error", "unknown error") # error is a STRING
|
||||
error_kind = envelope.get("kind") # kind is at TOP LEVEL
|
||||
print(f"Error: {error_kind} — {error_msg}")
|
||||
else:
|
||||
# Success path: verb-specific fields at top level
|
||||
sessions = envelope.get("sessions", [])
|
||||
for session in sessions:
|
||||
print(f"Session: {session['id']}")
|
||||
```
|
||||
|
||||
**After v2.0 migration, this code will break.** Claws building for v2.0 compatibility should:
|
||||
|
||||
1. Check `schema_version` field
|
||||
2. Parse differently based on version
|
||||
3. Or wait until Phase 2 default bump is announced, then migrate
|
||||
|
||||
### Why This Mismatch Exists
|
||||
|
||||
SCHEMAS.md was written as the **target design** for v2.0. The Rust binary is still on v1.0. The migration (FIX_LOCUS_164) will bring the binary in line with this schema, but it hasn't happened yet.
|
||||
|
||||
**This mismatch is the root cause of doc-truthfulness issues #78, #79, #165.** All three docs were documenting the v2.0 target as if it were current reality.
|
||||
|
||||
### Questions?
|
||||
|
||||
- **"Is v2.0 implemented?"** No. The binary is v1.0. See FIX_LOCUS_164.md for the implementation roadmap.
|
||||
- **"Should I build against v2.0 schema?"** No. Build against v1.0 (current). Test your code with `claw` to verify.
|
||||
- **"When does v2.0 ship?"** See FIX_LOCUS_164.md Phase 1 estimate: ~6 dev-days. Not scheduled yet.
|
||||
- **"Can I use v2.0 now?"** Only if you explicitly pass `--envelope-version=2.0` (which doesn't exist yet in v1.0 binary).
|
||||
|
||||
---
|
||||
|
||||
## v1.5 Emission Baseline — Per-Verb Shape Catalog (Cycle #91, Phase 0 Task 3)
|
||||
|
||||
**Status:** 📸 Snapshot of actual binary behavior as of cycle #91 (2026-04-23). Anchored by controlled matrix `/tmp/cycle87-audit/matrix.json` + Phase 0 tests in `output_format_contract.rs`.
|
||||
|
||||
### Purpose
|
||||
|
||||
This section documents **what each verb actually emits under `--output-format json`** as of the v1.5 emission baseline (post-cycle #89 emission routing fix, pre-Phase 1 shape normalization).
|
||||
|
||||
This is a **reference artifact**, not a target schema. It describes the reality that:
|
||||
|
||||
1. `--output-format json` exists and emits JSON (enforced by Phase 0 Task 2)
|
||||
2. All output goes to stdout (enforced by #168c fix, cycle #89)
|
||||
3. Each verb has a bespoke top-level shape (documented below; to be normalized in Phase 1)
|
||||
|
||||
### Emission Contract (v1.5 Baseline)
|
||||
|
||||
| Property | Rule | Enforced By |
|
||||
|---|---|---|
|
||||
| Exit 0 + stdout empty (silent success) | **Forbidden** | Test: `emission_contract_no_silent_success_under_output_format_json_168c_task2` |
|
||||
| Exit 0 + stdout contains valid JSON | Required | Test: same (parses each safe-success verb) |
|
||||
| Exit != 0 + JSON envelope on stdout | Required | Test: same + `error_envelope_emitted_to_stdout_under_output_format_json_168c` |
|
||||
| Error envelope on stderr under `--output-format json` | **Forbidden** | Test: #168c regression test |
|
||||
| Text mode routes errors to stderr | Preserved | Backward compat; not changed by cycle #89 |
|
||||
|
||||
### Per-Verb Shape Catalog
|
||||
|
||||
Captured from controlled matrix (cycle #87) and verified against post-#168c binary (cycle #91).
|
||||
|
||||
#### Verbs with `kind` top-level field (12/13)
|
||||
|
||||
| Verb | Top-level keys | Notes |
|
||||
|---|---|---|
|
||||
| `help` | `kind, message` | Minimal shape |
|
||||
| `version` | `git_sha, kind, message, target, version` | Build metadata |
|
||||
| `doctor` | `checks, has_failures, kind, message, report, summary` | Diagnostic results |
|
||||
| `mcp` | `action, config_load_error, configured_servers, kind, servers, status, working_directory` | MCP state |
|
||||
| `skills` | `action, kind, skills, summary` | Skills inventory |
|
||||
| `agents` | `action, agents, count, kind, summary, working_directory` | Agent inventory |
|
||||
| `sandbox` | `active, active_namespace, active_network, allowed_mounts, enabled, fallback_reason, filesystem_active, filesystem_mode, in_container, kind, markers, requested_namespace, requested_network, supported` | Sandbox state (14 keys) |
|
||||
| `status` | `config_load_error, kind, model, model_raw, model_source, permission_mode, sandbox, status, usage, workspace` | Runtime status |
|
||||
| `system-prompt` | `kind, message, sections` | Prompt sections |
|
||||
| `bootstrap-plan` | `kind, phases` | Bootstrap phases |
|
||||
| `export` | `file, kind, message, messages, session_id` | Export metadata |
|
||||
| `acp` | `aliases, discoverability_tracking, kind, launch_command, message, recommended_workflows, serve_alias_only, status, supported, tracking` | ACP discoverability |
|
||||
|
||||
#### Verb with `command` top-level field (1/13) — Phase 1 normalization target
|
||||
|
||||
| Verb | Top-level keys | Notes |
|
||||
|---|---|---|
|
||||
| `list-sessions` | `command, sessions` | **Deviation:** uses `command` instead of `kind`. Target Phase 1 fix. |
|
||||
|
||||
#### Verbs with error-only emission in test env (exit != 0)
|
||||
|
||||
These verbs require external state (credentials, session fixtures, manifests) and return error envelopes in clean test environments:
|
||||
|
||||
| Verb | Error envelope keys | Notes |
|
||||
|---|---|---|
|
||||
| `bootstrap` | `error, hint, kind, type` | Requires `ANTHROPIC_AUTH_TOKEN` for success path |
|
||||
| `dump-manifests` | `error, hint, kind, type` | Requires upstream manifest source |
|
||||
| `state` | `error, hint, kind, type` | Requires worker state file |
|
||||
|
||||
**Common error envelope shape (all verbs):** `{error, hint, kind, type}` — this is the one consistently-shaped part of v1.5.
|
||||
|
||||
### Standard Error Envelope (v1.5)
|
||||
|
||||
Error envelopes are the **only** part of v1.5 with a guaranteed consistent shape across all verbs:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"error": "short human-readable reason",
|
||||
"kind": "snake_case_machine_readable_classification",
|
||||
"hint": "optional remediation hint (may be null)"
|
||||
}
|
||||
```
|
||||
|
||||
**Classification kinds** (from `classify_error_kind` in `main.rs`):
|
||||
- `cli_parse` — argument parsing error
|
||||
- `missing_credentials` — auth token/key missing
|
||||
- `session_not_found` — load-session target missing
|
||||
- `session_load_failed` — persisted session unreadable
|
||||
- `no_managed_sessions` — no sessions exist to list
|
||||
- `missing_manifests` — upstream manifest sources absent
|
||||
- `filesystem_io_error` — file operation failure
|
||||
- `api_http_error` — upstream API returned non-2xx
|
||||
- `unknown` — classifier fallthrough
|
||||
|
||||
### How This Differs from v2.0 Target
|
||||
|
||||
| Aspect | v1.5 (this doc) | v2.0 Target (SCHEMAS.md top) |
|
||||
|---|---|---|
|
||||
| Top-level verb ID | 12 use `kind`, 1 uses `command` | Common `command` field |
|
||||
| Common metadata | None (no `timestamp`, `exit_code`, etc.) | `timestamp`, `command`, `exit_code`, `output_format`, `schema_version` |
|
||||
| Error envelope | `{error, hint, kind, type}` flat | `{error: {message, kind, operation, target, retryable}, ...}` nested |
|
||||
| Success shape | Verb-specific (13 bespoke) | Common wrapper with `data` field |
|
||||
|
||||
### Consumer Guidance (Against v1.5 Baseline)
|
||||
|
||||
**For claws consuming v1.5 today:**
|
||||
|
||||
1. **Always use `--output-format json`** — text format has no stability contract (#167)
|
||||
2. **Check `type` field first** — "error" or absent/other (treat as success)
|
||||
3. **For errors:** access `error` (string), `kind` (string), `hint` (nullable string)
|
||||
4. **For success:** use verb-specific keys per catalog above
|
||||
5. **Do NOT assume** `kind` field exists on success path — `list-sessions` uses `command` instead
|
||||
6. **Do NOT assume** metadata fields (`timestamp`, `exit_code`, etc.) — they are v2.0 target only
|
||||
7. **Check exit code** for pass/fail; don't infer from payload alone
|
||||
|
||||
### Phase 1 Normalization Targets (After This Baseline Locks)
|
||||
|
||||
Phase 1 (shape stabilization) will normalize these divergences:
|
||||
|
||||
- `list-sessions`: `command` → `kind` (align with 12/13 convention)
|
||||
- Potentially: unify where `message` field appears (9/13 have it, inconsistently populated)
|
||||
- Potentially: unify where `action` field appears (only in 3 inventory verbs: `mcp`, `skills`, `agents`)
|
||||
|
||||
Phase 1 does **not** add common metadata (`timestamp`, `exit_code`) — that's Phase 2 (v2.0 wrapper).
|
||||
|
||||
### Regenerating This Catalog
|
||||
|
||||
The catalog is derived from running the controlled matrix. Phase 0 Task 4 will add a deterministic script; for now, reproduce with:
|
||||
|
||||
```
|
||||
for verb in help version list-sessions doctor mcp skills agents sandbox status system-prompt bootstrap-plan export acp; do
|
||||
echo "=== $verb ==="
|
||||
claw $verb --output-format json | jq 'keys'
|
||||
done
|
||||
```
|
||||
|
||||
This matches what the Phase 0 Task 2 test enforces programmatically.
|
||||
|
||||
70
SECURITY.md
70
SECURITY.md
@@ -1,49 +1,49 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
## Supported versions
|
||||
|
||||
This project is pre-1.0 / active development. Only the `main` branch (and the current active feature branch) receives security attention. No LTS commitment exists yet.
|
||||
Security fixes target the current `main` branch and the latest published
|
||||
release artifacts when available. Older experimental branches are not supported
|
||||
unless a maintainer explicitly marks them as supported.
|
||||
|
||||
| Branch | Supported |
|
||||
|--------|-----------|
|
||||
| `main` | ✅ |
|
||||
| older forks/branches | ❌ |
|
||||
## Reporting a vulnerability
|
||||
|
||||
## Reporting a Vulnerability
|
||||
Please do **not** open a public issue for a suspected vulnerability. Use GitHub
|
||||
private vulnerability reporting for `ultraworkers/claw-code` when available, or
|
||||
contact a maintainer through the repository's published support channel with a
|
||||
minimal, non-destructive reproduction.
|
||||
|
||||
**Do not file a public GitHub issue for security vulnerabilities.**
|
||||
Include:
|
||||
|
||||
Please use [GitHub Security Advisories](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability) to report privately:
|
||||
- affected command, crate, or workflow;
|
||||
- operating system and shell, especially for Windows/PowerShell path issues;
|
||||
- whether live credentials, MCP servers, plugins, or workspace filesystem
|
||||
access are involved;
|
||||
- expected impact and any safe proof-of-concept steps.
|
||||
|
||||
1. Go to the **Security** tab of this repository
|
||||
2. Click **"Report a vulnerability"**
|
||||
3. Describe the issue with reproduction steps and impact
|
||||
|
||||
We aim to acknowledge within **72 hours** and work toward coordinated disclosure.
|
||||
|
||||
## Disclosure Process
|
||||
|
||||
1. Report received → acknowledgement within 72h
|
||||
2. We assess severity and reproduce the issue
|
||||
3. Fix developed and reviewed privately
|
||||
4. Fix shipped; advisory published after patch is live
|
||||
5. Credit given to reporter (unless they prefer anonymity)
|
||||
Do not include real API keys, private prompts, session transcripts with secrets,
|
||||
or exploit payloads that modify third-party systems.
|
||||
|
||||
## Scope
|
||||
|
||||
**In scope:**
|
||||
- Remote code execution (RCE)
|
||||
- Authentication or authorization bypass
|
||||
- Secrets / credentials exfiltration
|
||||
- Sandbox escape (agent isolation boundary violations)
|
||||
- Privilege escalation
|
||||
In scope:
|
||||
|
||||
**Out of scope:**
|
||||
- Denial of service (DoS/resource exhaustion)
|
||||
- Social engineering attacks
|
||||
- Vulnerabilities in third-party dependencies — report those upstream
|
||||
- Behavior that is working as intended (check ROADMAP.md pinpoints first)
|
||||
- workspace path traversal or symlink escapes;
|
||||
- permission bypasses, sandbox misreporting, or unsafe tool execution;
|
||||
- credential disclosure in logs, JSON output, telemetry, docs, or examples;
|
||||
- plugin, hook, MCP, provider, or config behavior that can unexpectedly execute
|
||||
code or leak secrets.
|
||||
|
||||
## License
|
||||
Out of scope:
|
||||
|
||||
This project is [MIT-licensed](./LICENSE) — provided as-is, without warranty of any kind.
|
||||
- social engineering;
|
||||
- denial-of-service without a practical security impact;
|
||||
- issues that require already-compromised local developer credentials;
|
||||
- reports against third-party providers or upstream tools without a Claw Code
|
||||
integration issue.
|
||||
|
||||
## Handling expectations
|
||||
|
||||
Maintainers will acknowledge valid private reports as soon as practical, keep
|
||||
discussion private until a fix or mitigation is available, and credit reporters
|
||||
when requested and appropriate.
|
||||
|
||||
24
SUPPORT.md
Normal file
24
SUPPORT.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Support
|
||||
|
||||
Use the lightest support path that fits the request:
|
||||
|
||||
- **Usage questions:** start with [USAGE.md](./USAGE.md) and
|
||||
[rust/README.md](./rust/README.md).
|
||||
- **Bugs or regressions:** open a GitHub issue with the command, OS/shell,
|
||||
expected behavior, actual behavior, and relevant non-secret output.
|
||||
- **Security issues:** follow [SECURITY.md](./SECURITY.md) instead of opening a
|
||||
public issue.
|
||||
- **Community discussion:** use the UltraWorkers Discord linked from
|
||||
[README.md](./README.md).
|
||||
|
||||
When asking for help, include:
|
||||
|
||||
```text
|
||||
claw --version
|
||||
claw doctor
|
||||
operating system and shell
|
||||
command you ran
|
||||
```
|
||||
|
||||
Redact API keys, bearer tokens, private prompts, session transcripts, and local
|
||||
paths that reveal sensitive information before sharing output.
|
||||
@@ -1,98 +0,0 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Upstream stream-init failures (`500 empty_stream`)
|
||||
|
||||
**Symptom:** claw-code exits with `500 empty_stream: upstream stream closed before first payload` or similar upstream stream-init error.
|
||||
|
||||
**Root cause:** Upstream provider (Anthropic, OpenAI, other) closed the HTTP connection before sending the first response payload. Common causes:
|
||||
- Transient network issue between claw-code and provider
|
||||
- Provider overload / temporary service degradation
|
||||
- Authentication token expired or invalid
|
||||
- Rate limit exceeded (even if not visible in response headers)
|
||||
|
||||
**Mitigation:**
|
||||
1. **Check credentials:** Verify `claw whoami` shows the expected provider and account. Re-authenticate if expired.
|
||||
2. **Wait and retry:** Provider transient issues usually resolve within 30-60 seconds. Wait a minute, then retry the same command.
|
||||
3. **Check provider status:** Visit the provider's status page (e.g., status.anthropic.com, status.openai.com).
|
||||
4. **Reduce request size:** If the prompt is large, try a smaller request first to isolate stream-init from context-window failures.
|
||||
5. **Check network:** Ensure your network connection is stable. If behind a proxy, verify proxy allows streaming responses.
|
||||
|
||||
**When to escalate:**
|
||||
- If stream-init failures persist >10 minutes across multiple requests
|
||||
- If `claw whoami` fails to authenticate
|
||||
- If no provider status page shows degradation
|
||||
|
||||
**Related pinpoint:** #290 (typed stream-init failure envelope — future improvement for better diagnostics)
|
||||
|
||||
---
|
||||
|
||||
## Context-window-blocked errors
|
||||
|
||||
**Symptom:** claw-code exits with `context_window_blocked` or similar provider error when resuming a long session, or when sending a request with a very large prompt + accumulated history.
|
||||
|
||||
**Root cause:** Session size exceeded provider context window before claw-code's auto-compaction could reduce it. Auto-compaction is currently REACTIVE-AFTER-SUCCESS — it only fires after a successful provider response. If the request itself is oversized, compaction never runs.
|
||||
|
||||
**Mitigation:**
|
||||
1. **Resume with manual compact:** `claw resume <session> --compact-before` (if available); else manually compact via `/compact` slash command before retrying
|
||||
2. **Start a fresh session:** Sometimes the cleanest path; existing session-state preserved in `~/.claw/sessions/<id>/`
|
||||
3. **Reduce prompt size:** If interactive, send shorter prompts; truncate file contents before pasting
|
||||
4. **Adjust threshold:** Lower `CLAW_AUTO_COMPACT_INPUT_TOKENS_THRESHOLD` env var (default varies by provider)
|
||||
|
||||
**Related pinpoints:** #287 (auto-compaction reactive-not-preflight, CRITICAL), #283 (threshold env-only no settings.json key), #288 (failure envelope omits diagnostics)
|
||||
|
||||
---
|
||||
|
||||
## Manual `/compact` reports "session below compaction threshold"
|
||||
|
||||
**Symptom:** You run `/compact` to manually compact a session, but it reports `session below compaction threshold` even though the session feels large.
|
||||
|
||||
**Root cause:** The "below threshold" message is currently a catch-all for multiple skip reasons:
|
||||
- Too few compactable messages
|
||||
- Already compacted (only summary remains)
|
||||
- Compactable tokens below threshold
|
||||
- Tool-use/tool-result boundary preserved
|
||||
- Live vs resume threshold divergence
|
||||
|
||||
**Mitigation:**
|
||||
1. **Check session state:** `claw session info <id>` to inspect message count, total tokens
|
||||
2. **Force compaction:** Currently no `--force` flag exists; track #289 for typed skip-reason discriminants
|
||||
3. **Workaround:** Continue session and let auto-compact fire after next provider response (when reactive-after-success path is available)
|
||||
|
||||
**Related pinpoint:** #289 (manual `/compact` skip-reason flattened, lacks typed discriminants)
|
||||
|
||||
---
|
||||
|
||||
## Parallel agent stuck in "running" state
|
||||
|
||||
**Symptom:** A parallel agent lane shows `status: running` indefinitely, never transitioning to `completed` or `error`. Downstream coordination treats it as still-working.
|
||||
|
||||
**Root cause:** `Agent::execute_agent` writes a `running` manifest BEFORE spawning a detached `std::thread::spawn`. The `JoinHandle` is dropped. If the process crashes during agent execution, the manifest stays as `running` forever (zombie state). No heartbeat or stale-reaper exists.
|
||||
|
||||
**Mitigation:**
|
||||
1. **Manual cleanup:** Inspect `~/.claw/agents/<lane>/` and remove stale `manifest.json` files where last-modified > N minutes ago
|
||||
2. **Restart agent lane:** `claw agent restart <lane>`
|
||||
3. **Kill orphaned processes:** `pgrep claw` to find lingering processes
|
||||
|
||||
**Related pinpoint:** #286 (Parallel `Agent` detached-thread no-heartbeat no-reaper)
|
||||
|
||||
---
|
||||
|
||||
## Sustained upstream provider failures (`500 empty_stream` repeating)
|
||||
|
||||
**Symptom:** Same upstream provider error (e.g., `500 empty_stream: upstream stream closed before first payload`) repeats 5+ times in <60 minutes. Retries hit the same dead upstream blindly.
|
||||
|
||||
**Root cause:** claw-code does NOT detect repeat-failure patterns. No circuit-breaker. No automatic provider-fallback when configured. Each retry attempts the same provider+endpoint regardless of recent failure history.
|
||||
|
||||
**Mitigation:**
|
||||
1. **Manual circuit-breaker:** Wait 5-10 minutes after repeated failures before retrying
|
||||
2. **Switch provider:** If you have multiple providers configured (`ANTHROPIC_API_KEY` + `OPENAI_API_KEY`), restart with different model prefix (e.g., `gpt-4` instead of `claude-`)
|
||||
3. **Check provider status pages:** status.anthropic.com, status.openai.com
|
||||
4. **Verify upstream endpoint:** If using a proxy (CCAPI, custom OpenAI-compatible endpoint), check proxy logs
|
||||
|
||||
**Related pinpoints:** #291 (no repeat-failure detection / circuit-breaker), #285 (declarative providers config for fallback), #290 (stream-init failure envelope)
|
||||
|
||||
---
|
||||
|
||||
## Other common failures
|
||||
|
||||
*[placeholder for future sections: tool-use failures, session corruption]*
|
||||
311
USAGE.md
311
USAGE.md
@@ -2,9 +2,6 @@
|
||||
|
||||
This guide covers the current Rust workspace under `rust/` and the `claw` CLI binary. If you are brand new, make the doctor health check your first run: start `claw`, then run `/doctor`.
|
||||
|
||||
> [!TIP]
|
||||
> **Building orchestration code that calls `claw` as a subprocess?** See [`ERROR_HANDLING.md`](./ERROR_HANDLING.md) for the unified error-handling pattern (one handler for all 14 clawable commands, exit codes, JSON envelope contract, and recovery strategies).
|
||||
|
||||
## Quick-start health check
|
||||
|
||||
Run this before prompts, sessions, or automation:
|
||||
@@ -34,61 +31,7 @@ cd rust
|
||||
cargo build --workspace
|
||||
```
|
||||
|
||||
The CLI binary is available at `rust/target/debug/claw` after a debug build. Make the doctor check above your first post-build step.
|
||||
|
||||
### Add binary to PATH
|
||||
|
||||
To run `claw` from anywhere without typing the full path:
|
||||
|
||||
**Option 1: Symlink to a directory already in your PATH**
|
||||
|
||||
```bash
|
||||
# Find a PATH directory (usually ~/.local/bin or /usr/local/bin)
|
||||
echo $PATH
|
||||
|
||||
# Create symlink (adjust path and PATH-dir as needed)
|
||||
ln -s /Users/yeongyu/clawd/claw-code/rust/target/debug/claw ~/.local/bin/claw
|
||||
|
||||
# Verify it's in PATH
|
||||
which claw
|
||||
```
|
||||
|
||||
**Option 2: Add the binary directory to PATH directly**
|
||||
|
||||
Add this to your shell rc file (`~/.bashrc`, `~/.zshrc`, etc.):
|
||||
|
||||
```bash
|
||||
export PATH="$PATH:/Users/yeongyu/clawd/claw-code/rust/target/debug"
|
||||
```
|
||||
|
||||
Then reload:
|
||||
|
||||
```bash
|
||||
source ~/.zshrc # or ~/.bashrc
|
||||
```
|
||||
|
||||
### Verify install
|
||||
|
||||
After adding to PATH, verify the binary works:
|
||||
|
||||
```bash
|
||||
# Should print version and exit successfully
|
||||
claw version
|
||||
|
||||
# Should run health check (shows which components are initialized)
|
||||
claw doctor
|
||||
|
||||
# Should show available commands
|
||||
claw --help
|
||||
```
|
||||
|
||||
If `claw: command not found`, the PATH addition didn't take. Re-check:
|
||||
|
||||
```bash
|
||||
echo $PATH # verify your PATH directory is listed
|
||||
which claw # should show full path to binary
|
||||
ls -la ~/.local/bin/claw # if using symlink, verify it exists and points to target/debug/claw
|
||||
```
|
||||
The CLI binary is available at `rust/target/debug/claw` after a debug build (`rust\target\debug\claw.exe` on Windows). Make the doctor check above your first post-build step. For PowerShell-first install, release ZIP, PATH, provider-switching, and Windows/WSL notification examples, see [`docs/windows-install-release.md`](./docs/windows-install-release.md).
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -152,69 +95,11 @@ cd rust
|
||||
|
||||
### JSON output for scripting
|
||||
|
||||
All clawable commands support `--output-format json` for machine-readable output.
|
||||
|
||||
**IMPORTANT SCHEMA VERSION NOTICE:**
|
||||
|
||||
The JSON envelope is currently in **v1.0 (flat shape)** and is scheduled to migrate to **v2.0 (nested schema)** in a future release. See [`FIX_LOCUS_164.md`](./FIX_LOCUS_164.md) for the full migration plan.
|
||||
|
||||
#### Current (v1.0) envelope shape
|
||||
|
||||
**Success envelope** — verb-specific fields + `kind: "<verb-name>"`:
|
||||
```json
|
||||
{
|
||||
"kind": "doctor",
|
||||
"checks": [...],
|
||||
"summary": {...},
|
||||
"has_failures": false,
|
||||
"report": "...",
|
||||
"message": "..."
|
||||
}
|
||||
```
|
||||
|
||||
**Error envelope** — flat error fields at top level:
|
||||
```json
|
||||
{
|
||||
"error": "unrecognized argument `foo`",
|
||||
"hint": "Run `claw --help` for usage.",
|
||||
"kind": "cli_parse",
|
||||
"type": "error"
|
||||
}
|
||||
```
|
||||
|
||||
**Known issues with v1.0:**
|
||||
- Missing `exit_code`, `command`, `timestamp`, `output_format`, `schema_version` fields
|
||||
- `error` is a string, not a structured object with operation/target/retryable/message/hint
|
||||
- `kind` field is semantically overloaded (verb identity in success, error classification in error)
|
||||
- See [`SCHEMAS.md`](./SCHEMAS.md) for documented (v2.0 target) schema and [`FIX_LOCUS_164.md`](./FIX_LOCUS_164.md) for migration details
|
||||
|
||||
#### Using v1.0 envelopes in your code
|
||||
|
||||
**Success path:** Check for absence of `type: "error"`, then access verb-specific fields:
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw doctor --output-format json | jq '.kind, .has_failures'
|
||||
```
|
||||
|
||||
**Error path:** Check for `type == "error"`, then access `error` (string) and `kind` (error classification):
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw doctor invalid-arg --output-format json | jq '.error, .kind'
|
||||
```
|
||||
|
||||
**Do NOT rely on `kind` alone for dispatching** — it has different meanings in success vs. error. Always check `type == "error"` first.
|
||||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw --output-format json prompt "status"
|
||||
./target/debug/claw --output-format json load-session my-session-id
|
||||
./target/debug/claw --output-format json turn-loop "analyze logs" --max-turns 1
|
||||
```
|
||||
|
||||
**Building a dispatcher or orchestration script?** See [`ERROR_HANDLING.md`](./ERROR_HANDLING.md) for the unified error-handling pattern. One code example works for all 14 clawable commands: parse the exit code, classify by `error.kind`, apply recovery strategies (retry, timeout recovery, validation, logging). Use that pattern instead of reimplementing error handling per command.
|
||||
|
||||
**Migrating to v2.0?** Check back after [`FIX_LOCUS_164`](./FIX_LOCUS_164.md) is implemented. Phase 1 will add a `--envelope-version=2.0` flag for opt-in access to the structured envelope schema. Phase 2 will make v2.0 the default. Phase 3 will deprecate v1.0.
|
||||
|
||||
### Inspect worker state
|
||||
|
||||
The `claw state` command reads `.claw/worker-state.json`, which is written by the interactive REPL or a one-shot prompt when a worker executes a task. This file contains the worker ID, session reference, model, and permission mode.
|
||||
@@ -345,9 +230,37 @@ export ANTHROPIC_AUTH_TOKEN="anthropic-oauth-or-proxy-bearer-token"
|
||||
|
||||
**If you meant a different provider:** if `claw` reports missing Anthropic credentials but you already have `OPENAI_API_KEY`, `XAI_API_KEY`, or `DASHSCOPE_API_KEY` exported, you most likely forgot to prefix the model name with the provider's routing prefix. Use `--model openai/gpt-4.1-mini` (OpenAI-compat / OpenRouter / Ollama), `--model grok` (xAI), or `--model qwen-plus` (DashScope) and the prefix router will select the right backend regardless of the ambient credentials. The error message now includes a hint that names the detected env var.
|
||||
|
||||
|
||||
### Windows PowerShell provider switching
|
||||
|
||||
The same provider rules work in PowerShell. Use placeholder values in docs and tests; put real keys only in your private environment. Remove unrelated provider env vars when validating a switch so failures are easy to diagnose.
|
||||
|
||||
`CLAUDE_CODE_PROVIDER` is not required for normal Claw routing; prefer explicit model prefixes such as `openai/` and provider-specific env vars so PowerShell examples stay portable.
|
||||
|
||||
```powershell
|
||||
# Anthropic direct
|
||||
$env:ANTHROPIC_API_KEY = "sk-ant-REPLACE_ME"
|
||||
Remove-Item Env:\OPENAI_BASE_URL -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
.\target\debug\claw.exe --model "sonnet" prompt "reply with ready"
|
||||
|
||||
# OpenAI-compatible gateway / OpenRouter
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
||||
$env:OPENAI_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
$env:OPENAI_API_KEY = "sk-or-v1-REPLACE_ME"
|
||||
.\target\debug\claw.exe --model "openai/gpt-4.1-mini" prompt "reply with ready"
|
||||
|
||||
# Local OpenAI-compatible server
|
||||
$env:OPENAI_BASE_URL = "http://127.0.0.1:11434/v1"
|
||||
Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
.\target\debug\claw.exe --model "llama3.2" prompt "reply with ready"
|
||||
```
|
||||
|
||||
See the full [Windows install and release quickstart](./docs/windows-install-release.md) for release artifact setup, persistent `setx` usage, and WSL notes.
|
||||
|
||||
## Local Models
|
||||
|
||||
`claw` can talk to local servers and provider gateways through either Anthropic-compatible or OpenAI-compatible endpoints. Use `ANTHROPIC_BASE_URL` with `ANTHROPIC_AUTH_TOKEN` for Anthropic-compatible services, or `OPENAI_BASE_URL` with `OPENAI_API_KEY` for OpenAI-compatible services.
|
||||
`claw` can talk to local servers and provider gateways through either Anthropic-compatible or OpenAI-compatible endpoints. Use `ANTHROPIC_BASE_URL` with `ANTHROPIC_AUTH_TOKEN` for Anthropic-compatible services, or `OPENAI_BASE_URL` with `OPENAI_API_KEY` for OpenAI-compatible services. For copyable Ollama, llama.cpp, vLLM, raw `/v1/chat/completions`, and local skills install examples, see [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md).
|
||||
|
||||
### Anthropic-compatible endpoint
|
||||
|
||||
@@ -421,7 +334,7 @@ Reasoning variants (`qwen-qwq-*`, `qwq-*`, `*-thinking`) automatically strip `te
|
||||
|
||||
The OpenAI-compatible backend also serves as the gateway for **OpenRouter**, **Ollama**, and any other service that speaks the OpenAI `/v1/chat/completions` wire format — just point `OPENAI_BASE_URL` at the service.
|
||||
|
||||
**Model-name prefix routing:** If a model name starts with `openai/`, `gpt-`, `qwen/`, or `qwen-`, the provider is selected by the prefix regardless of which env vars are set. This prevents accidental misrouting to Anthropic when multiple credentials exist in the environment.
|
||||
**Model-name prefix routing:** If a model name starts with `openai/`, `gpt-`, `qwen/`, `qwen-`, `kimi/`, or `kimi-`, the provider is selected by the prefix regardless of which env vars are set. This prevents accidental misrouting to Anthropic when multiple credentials exist in the environment. For the default OpenAI API, `openai/` is a routing prefix and is stripped before the request hits the wire. For a custom `OPENAI_BASE_URL`, slash-containing OpenAI-compatible slugs (for example OpenRouter-style `openai/gpt-4.1-mini`) are preserved so the gateway receives the model ID it expects.
|
||||
|
||||
### Tested models and aliases
|
||||
|
||||
@@ -435,8 +348,11 @@ These are the models registered in the built-in alias table with known token lim
|
||||
| `grok` / `grok-3` | `grok-3` | xAI | 64 000 | 131 072 |
|
||||
| `grok-mini` / `grok-3-mini` | `grok-3-mini` | xAI | 64 000 | 131 072 |
|
||||
| `grok-2` | `grok-2` | xAI | — | — |
|
||||
| `kimi` | `kimi-k2.5` | DashScope | 16 384 | 256 000 |
|
||||
| `gpt-4.1` / `gpt-4.1-mini` / `gpt-4.1-nano` | same | OpenAI-compatible | 32 768 | 1 047 576 |
|
||||
| `gpt-5.4` / `gpt-5.4-mini` / `gpt-5.4-nano` | same | OpenAI-compatible | 128 000 | 1 000 000 / 400 000 |
|
||||
|
||||
Any model name that does not match an alias is passed through verbatim. This is how you use OpenRouter model slugs (`openai/gpt-4.1-mini`), Ollama tags (`llama3.2`), or full Anthropic model IDs (`claude-sonnet-4-20250514`).
|
||||
Any model name that does not match an alias is passed through verbatim after provider routing is resolved. This is how you use OpenRouter model slugs (`openai/gpt-4.1-mini` with a custom `OPENAI_BASE_URL`), Ollama tags (`llama3.2`), or full Anthropic model IDs (`claude-sonnet-4-20250514`).
|
||||
|
||||
### User-defined aliases
|
||||
|
||||
@@ -458,11 +374,29 @@ Local project settings override user-level settings. Aliases resolve through the
|
||||
|
||||
1. If the resolved model name starts with `claude` → Anthropic.
|
||||
2. If it starts with `grok` → xAI.
|
||||
3. Otherwise, `claw` checks which credential is set: `ANTHROPIC_API_KEY`/`ANTHROPIC_AUTH_TOKEN` first, then `OPENAI_API_KEY`, then `XAI_API_KEY`.
|
||||
4. If nothing matches, it defaults to Anthropic.
|
||||
3. If it starts with `openai/` or `gpt-` → OpenAI-compatible.
|
||||
4. If it starts with `qwen/`, `qwen-`, `kimi/`, or `kimi-` → DashScope-compatible OpenAI wire format.
|
||||
5. If `OPENAI_BASE_URL` and `OPENAI_API_KEY` are set, unknown model names route to the OpenAI-compatible client for local/gateway servers.
|
||||
6. Otherwise, `claw` checks which credential is set: Anthropic first, then OpenAI, then xAI. If only `OPENAI_BASE_URL` is set, it still routes to OpenAI-compatible for authless local servers.
|
||||
7. If nothing matches, it defaults to Anthropic.
|
||||
|
||||
|
||||
### Provider diagnostics and custom OpenAI-compatible parameters
|
||||
|
||||
The API layer exposes a provider diagnostics snapshot via `api::provider_diagnostics_for_model(model)`. It reports the resolved provider, auth/base-url environment variables, default base URL, whether the provider uses the OpenAI-compatible wire format, whether reasoning tuning parameters are stripped, whether DeepSeek V4 reasoning history is preserved, proxy support, extra-body support, and whether slash-containing model IDs are preserved for custom OpenAI-compatible gateways.
|
||||
|
||||
For gateway features that are not first-class request fields yet, `MessageRequest::extra_body` passes through provider-specific JSON parameters such as `web_search_options` or `parallel_tool_calls`. Core protocol fields (`model`, `messages`, `stream`, `tools`, `tool_choice`, `max_tokens`, and `max_completion_tokens`) are protected and cannot be overridden through `extra_body`.
|
||||
|
||||
## File context and navigation
|
||||
|
||||
Use `@path/to/file` in prompts to submit repository files as context, for example `Read @src/app.ts and explain the bug`, `Compare @old.md and @new.md`, or `Use @logs/error.txt as context and suggest a fix`. Prompt history, `Ctrl-r`, and long-output scrolling come from your shell, terminal, or tmux rather than from Claw itself. See [`docs/navigation-file-context.md`](./docs/navigation-file-context.md) for scrollback, attachment, and secret-redaction guidance.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Is Claw Code Claude-only?
|
||||
|
||||
No. Claw Code is a Claude-Code-shaped workflow/runtime, not a Claude-only product. It can target Anthropic and OpenAI-compatible/provider-routed/local models depending on config. Non-Claude providers may require stricter response-shape and tool-call compatibility, so some workflows can be rougher than first-party Anthropic/OpenAI paths; provider-specific identity leaks are bugs, not product intent. See [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md) for local provider examples.
|
||||
|
||||
### What about Codex?
|
||||
|
||||
The name "codex" appears in the Claw Code ecosystem but it does **not** refer to OpenAI Codex (the code-generation model). Here is what it means in this project:
|
||||
@@ -516,6 +450,18 @@ let client = build_http_client_with(&config).expect("proxy client");
|
||||
- Empty values are treated as unset, so leaving `HTTPS_PROXY=""` in your shell will not enable a proxy.
|
||||
- If a proxy URL cannot be parsed, `claw` falls back to a direct (no-proxy) client so existing workflows keep working; double-check the URL if you expected the request to be tunnelled.
|
||||
|
||||
## Skills
|
||||
|
||||
Use `/skills list` in the interactive REPL or `claw skills --output-format json` from the direct CLI to inspect installed skills. For offline/local installs, install the directory that contains `SKILL.md`, then verify the discovered name before invoking it:
|
||||
|
||||
```text
|
||||
/skills install /absolute/path/to/my-skill
|
||||
/skills list
|
||||
/skills my-skill
|
||||
```
|
||||
|
||||
If install succeeds but invocation fails with a provider HTTP error, treat provider setup separately: run `claw doctor` and a one-shot prompt smoke test before reinstalling the skill. See [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md#local-skills-install-from-disk) for the full checklist.
|
||||
|
||||
## Common operational commands
|
||||
|
||||
```bash
|
||||
@@ -528,93 +474,6 @@ cd rust
|
||||
./target/debug/claw system-prompt --cwd .. --date 2026-04-04
|
||||
```
|
||||
|
||||
### `dump-manifests` — Export upstream plugin/MCP manifests
|
||||
|
||||
**Purpose:** Dump built-in tool and plugin manifests to stdout as JSON, for parity comparison against the upstream Claude Code TypeScript implementation.
|
||||
|
||||
**Prerequisite:** This command requires access to upstream source files (`src/commands.ts`, `src/tools.ts`, `src/entrypoints/cli.tsx`). Set `CLAUDE_CODE_UPSTREAM` env var or pass `--manifests-dir`.
|
||||
|
||||
```bash
|
||||
# Via env var
|
||||
CLAUDE_CODE_UPSTREAM=/path/to/upstream claw dump-manifests
|
||||
|
||||
# Via flag
|
||||
claw dump-manifests --manifests-dir /path/to/upstream
|
||||
```
|
||||
|
||||
**When to use:** Parity work (comparing the Rust port's tool/plugin surface against the canonical TypeScript implementation). Not needed for normal operation.
|
||||
|
||||
**Error mode:** If upstream sources are missing, exits with `error-kind: missing_manifests` and a hint about how to provide them.
|
||||
|
||||
### `bootstrap-plan` — Show startup component graph
|
||||
|
||||
**Purpose:** Print the ordered list of startup components that are initialized when `claw` begins a session. Useful for debugging startup issues or verifying that fast-path optimizations are in place.
|
||||
|
||||
```bash
|
||||
claw bootstrap-plan
|
||||
```
|
||||
|
||||
**Sample output:**
|
||||
```
|
||||
- CliEntry
|
||||
- FastPathVersion
|
||||
- StartupProfiler
|
||||
- SystemPromptFastPath
|
||||
- ChromeMcpFastPath
|
||||
```
|
||||
|
||||
**When to use:**
|
||||
- Debugging why startup is slow (compare your plan to the expected one)
|
||||
- Verifying that fast-path components are registered
|
||||
- Understanding the load order before customizing hooks or plugins
|
||||
|
||||
**Related:** See `claw doctor` for health checks against these startup components.
|
||||
|
||||
### `acp` — Agent Context Protocol / Zed editor integration status
|
||||
|
||||
**Purpose:** Report the current state of the ACP (Agent Context Protocol) / Zed editor integration. Currently **discoverability only** — no editor daemon is available yet.
|
||||
|
||||
```bash
|
||||
claw acp
|
||||
claw acp serve # same output; `serve` is accepted but not yet launchable
|
||||
claw --acp # alias
|
||||
claw -acp # alias
|
||||
```
|
||||
|
||||
**Sample output:**
|
||||
```
|
||||
ACP / Zed
|
||||
Status discoverability only
|
||||
Launch `claw acp serve` / `claw --acp` / `claw -acp` report status only; no editor daemon is available yet
|
||||
Today use `claw prompt`, the REPL, or `claw doctor` for local verification
|
||||
Tracking ROADMAP #76
|
||||
```
|
||||
|
||||
**When to use:** Check whether ACP/Zed integration is ready in your current build. Plan around its availability (track ROADMAP #76 for status).
|
||||
|
||||
**Today's alternatives:** Use `claw prompt` for one-shot runs, the interactive REPL for iterative work, or `claw doctor` for local verification.
|
||||
|
||||
### `export` — Export session transcript
|
||||
|
||||
**Purpose:** Export a managed session's transcript to a file or stdout. Operates on the currently-resumed session (requires `--resume`).
|
||||
|
||||
```bash
|
||||
# Export latest session
|
||||
claw --resume latest export
|
||||
|
||||
# Export specific session
|
||||
claw --resume <session-id> export
|
||||
```
|
||||
|
||||
**Prerequisite:** A managed session must exist under `.claw/sessions/<workspace-fingerprint>/`. If no sessions exist, the command exits with `error-kind: no_managed_sessions` and a hint to start a session first.
|
||||
|
||||
**When to use:**
|
||||
- Archive session transcripts for review
|
||||
- Share session context with teammates
|
||||
- Feed session history into downstream tooling
|
||||
|
||||
**Related:** Inside the REPL, `/export` is also available as a slash command for the active session.
|
||||
|
||||
## Session management
|
||||
|
||||
REPL turns are persisted under `.claw/sessions/` in the current workspace.
|
||||
@@ -625,27 +484,7 @@ cd rust
|
||||
./target/debug/claw --resume latest /status /diff
|
||||
```
|
||||
|
||||
### Interactive slash commands (inside the REPL)
|
||||
|
||||
Useful interactive commands include:
|
||||
|
||||
- `/help` — Show help for all available commands
|
||||
- `/status` — Display current session and workspace status
|
||||
- `/cost` — Show token usage and cost estimates for the session
|
||||
- `/config` — Display current configuration and environment state
|
||||
- `/session` — Show session ID, creation time, and persisted metadata
|
||||
- `/model` — Display or switch the active model
|
||||
- `/permissions` — Check sandbox permissions and capability grants
|
||||
- `/export [file]` — Export the current conversation to a file (or resume from backup)
|
||||
- `/ultraplan [task]` — Run a deep planning prompt with multi-step reasoning (good for complex refactoring tasks)
|
||||
- `/teleport <symbol-or-path>` — Jump to a file or symbol by searching the workspace (IDE-like navigation)
|
||||
- `/bughunter [scope]` — Inspect the codebase for likely bugs in an optional scope (e.g., `src/runtime`)
|
||||
- `/commit` — Generate a commit message and create a git commit from the conversation
|
||||
- `/pr [context]` — Draft or create a pull request from the conversation
|
||||
- `/issue [context]` — Draft or create a GitHub issue from the conversation
|
||||
- `/diff` — Show unified diff of changes made in the current session
|
||||
- `/plugin [list|install|enable|disable|uninstall|update]` — Manage Claw Code plugins
|
||||
- `/agents [list|help]` — List configured agents or get help on agent commands
|
||||
Useful interactive commands include `/help`, `/status`, `/cost`, `/config`, `/session`, `/model`, `/permissions`, and `/export`.
|
||||
|
||||
## Config file resolution order
|
||||
|
||||
@@ -693,17 +532,3 @@ Current Rust crates:
|
||||
- `rusty-claude-cli`
|
||||
- `telemetry`
|
||||
- `tools`
|
||||
|
||||
## Documentation
|
||||
|
||||
- [ARCHITECTURE.md](docs/ARCHITECTURE.md) — System overview, crate layout, request flow
|
||||
- [CONFIGURATION.md](docs/CONFIGURATION.md) — Env vars, settings.json, provider config
|
||||
- [SUPPORTED_PROVIDERS.md](docs/SUPPORTED_PROVIDERS.md) — Provider/model matrix
|
||||
- [API_REFERENCE.md](docs/API_REFERENCE.md) — JSON output envelope, error format
|
||||
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) — Common failure modes and mitigation
|
||||
- [ROADMAP.md](ROADMAP.md) — Pinpoint-driven development roadmap
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md) — How to contribute, pinpoint format
|
||||
- [PINPOINT_FILING_GUIDE.md](docs/PINPOINT_FILING_GUIDE.md) — Step-by-step pinpoint workflow
|
||||
- [CHANGELOG.md](CHANGELOG.md) — Recent changes
|
||||
- [SECURITY.md](SECURITY.md) — Responsible disclosure
|
||||
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) — Community standards
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
# API Reference — JSON Output Envelope Contract
|
||||
|
||||
This document describes the machine-readable JSON output emitted by `claw` when
|
||||
`--output-format json` is passed. All JSON envelopes are written to **stdout**.
|
||||
Stderr is reserved for non-contractual diagnostics only (see pinpoint #168c).
|
||||
|
||||
---
|
||||
|
||||
## Output Format Flag
|
||||
|
||||
```
|
||||
claw [command] --output-format json
|
||||
claw [command] --output-format text # default
|
||||
```
|
||||
|
||||
When `json` is active, **all** output (success and error) is emitted as a single
|
||||
JSON object on stdout. Consumers must not parse stderr for errors.
|
||||
|
||||
---
|
||||
|
||||
## Success Envelope — `claw -p <prompt>`
|
||||
|
||||
Full non-compact run (default):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "<final assistant text>",
|
||||
"model": "claude-opus-4-5",
|
||||
"iterations": 3,
|
||||
"auto_compaction": null,
|
||||
"tool_uses": [...],
|
||||
"tool_results": [...],
|
||||
"prompt_cache_events": [...],
|
||||
"usage": {
|
||||
"input_tokens": 1234,
|
||||
"output_tokens": 567,
|
||||
"cache_creation_input_tokens": 0,
|
||||
"cache_read_input_tokens": 0
|
||||
},
|
||||
"estimated_cost": "$0.0123"
|
||||
}
|
||||
```
|
||||
|
||||
Compact run (`--compact`):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "<final assistant text>",
|
||||
"compact": true,
|
||||
"model": "claude-opus-4-5",
|
||||
"usage": {
|
||||
"input_tokens": 1234,
|
||||
"output_tokens": 567,
|
||||
"cache_creation_input_tokens": 0,
|
||||
"cache_read_input_tokens": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Reference
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `message` | string | Final assistant reply text |
|
||||
| `model` | string | Model identifier used for the turn |
|
||||
| `iterations` | integer | Number of tool-use / re-prompt iterations |
|
||||
| `compact` | boolean | Present and `true` when `--compact` mode was active |
|
||||
| `auto_compaction` | object\|null | Non-null when auto-compaction fired (see below) |
|
||||
| `tool_uses` | array | Tool calls made during the turn (TODO: verify schema) |
|
||||
| `tool_results` | array | Results returned to the model (TODO: verify schema) |
|
||||
| `prompt_cache_events` | array | Cache-hit/miss events (TODO: verify schema) |
|
||||
| `usage.input_tokens` | integer | Input tokens billed |
|
||||
| `usage.output_tokens` | integer | Output tokens billed |
|
||||
| `usage.cache_creation_input_tokens` | integer | Tokens written to prompt cache |
|
||||
| `usage.cache_read_input_tokens` | integer | Tokens served from prompt cache |
|
||||
| `estimated_cost` | string | Human-readable USD cost estimate (e.g. `"$0.0123"`) |
|
||||
|
||||
#### `auto_compaction` sub-object
|
||||
|
||||
```json
|
||||
{
|
||||
"removed_messages": 12,
|
||||
"notice": "Auto-compacted: removed 12 messages to free context."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Envelope
|
||||
|
||||
When a command fails under `--output-format json`, an error envelope is written
|
||||
to **stdout** (pinpoint #168c / #288):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"error": "<short human-readable reason>",
|
||||
"kind": "<snake_case error kind token>",
|
||||
"hint": "<optional actionable hint>"
|
||||
}
|
||||
```
|
||||
|
||||
### Error Envelope Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `type` | string | Always `"error"` |
|
||||
| `error` | string | Short prose description of the failure |
|
||||
| `kind` | string | Machine-readable snake_case token (see §Error Kinds) |
|
||||
| `hint` | string\|null | Optional remediation hint |
|
||||
|
||||
### Error Kinds (selected)
|
||||
|
||||
`kind` values are classified by `classify_error_kind()`. Common tokens include:
|
||||
|
||||
- `not_yet_implemented` — command stub not yet shipped
|
||||
- `config_error` — configuration file parse / validation failure
|
||||
- `auth_error` — API key or credential problem
|
||||
- `permission_denied` — tool-use permission denied
|
||||
- `model_error` — upstream model API error
|
||||
|
||||
See pinpoint #266 (typed-error-kind) for the full taxonomy.
|
||||
|
||||
---
|
||||
|
||||
## Streaming Behavior
|
||||
|
||||
`claw` always uses streaming internally (HTTP chunked transfer to the Anthropic
|
||||
API) but the **JSON output envelope is emitted once**, after the turn completes.
|
||||
There is no per-token or per-chunk JSON stream exposed to the caller.
|
||||
|
||||
In REPL / interactive mode (`claw` with no `-p`) the JSON format applies only to
|
||||
structured sub-commands, not to the interactive session itself.
|
||||
|
||||
---
|
||||
|
||||
## Status Snapshot (`claw status`)
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "status",
|
||||
"status": "ok",
|
||||
"config_load_error": null,
|
||||
"model": "claude-opus-4-5",
|
||||
"model_source": "config",
|
||||
"model_raw": null,
|
||||
"permission_mode": "default",
|
||||
"usage": {
|
||||
"messages": 42,
|
||||
"turns": 10,
|
||||
"latest_total": 5678,
|
||||
"cumulative_input": 12345,
|
||||
"cumulative_output": 4567,
|
||||
"cumulative_total": 16912,
|
||||
"estimated_tokens": 16912
|
||||
},
|
||||
"workspace": {
|
||||
"cwd": "/Users/you/project",
|
||||
"project_root": "/Users/you/project",
|
||||
"git_branch": "main",
|
||||
"git_state": "clean",
|
||||
"changed_files": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Pinpoints
|
||||
|
||||
- **#288** — error-envelope stdout emission contract
|
||||
- **#266** — typed-error-kind taxonomy
|
||||
- **#168c** — `--output-format json` routes error envelopes to stdout
|
||||
- **#247** — JSON envelope field preservation (hint / help text)
|
||||
@@ -1,110 +0,0 @@
|
||||
# claw-code Architecture
|
||||
|
||||
A high-level overview of how claw-code is structured. For implementation details, see source code in `rust/crates/`. For provider details, see [SUPPORTED_PROVIDERS.md](./SUPPORTED_PROVIDERS.md). For pinpoint navigation, see [ROADMAP.md](../ROADMAP.md#pinpoint-cluster-index).
|
||||
|
||||
## Overview
|
||||
|
||||
claw-code is a Rust-based CLI for interacting with LLM providers (Anthropic, OpenAI-compatible, xAI, DashScope, etc.). It provides:
|
||||
|
||||
- Streaming conversation with auto-compaction
|
||||
- Tool execution (file read/write, bash, MCP)
|
||||
- Multi-provider routing
|
||||
- Session persistence
|
||||
- Parallel agent execution
|
||||
|
||||
## Workspace Layout
|
||||
|
||||
The Rust workspace is organized in `rust/crates/`:
|
||||
|
||||
### Core crates
|
||||
|
||||
- **`rusty-claude-cli`** — CLI entry point. Parses args, routes commands, manages TUI/headless modes.
|
||||
- **`runtime`** — Conversation engine. Manages session state, message history, auto-compaction, tool dispatch, hooks, MCP, and branch/lane events.
|
||||
- **`api`** — Provider abstraction. Hosts `MODEL_REGISTRY` (provider/model routing), SSE streaming, request/response handling. Providers: `anthropic`, `openai_compat`.
|
||||
- **`tools`** — Tool definitions. File I/O, bash execution, MCP integration, PDF extraction.
|
||||
|
||||
### Support crates
|
||||
|
||||
- **`commands`** — Parsed command dispatch layer between CLI and runtime.
|
||||
- **`plugins`** — Plugin/hook lifecycle (`hooks.rs`).
|
||||
- **`telemetry`** — Metrics and tracing instrumentation.
|
||||
- **`compat-harness`** — Parity test harness for Rust-port validation.
|
||||
- **`mock-anthropic-service`** — Local mock server for offline/test use.
|
||||
|
||||
## Request Flow
|
||||
|
||||
1. **CLI parse** (`rusty-claude-cli/src/main.rs`) — interprets args, env vars, settings.json
|
||||
2. **Provider selection** (`api/src/providers/mod.rs`) — routes to provider via `MODEL_REGISTRY` based on model prefix
|
||||
3. **Conversation execution** (`runtime/src/conversation.rs`) — sends to provider via SSE, receives streamed response
|
||||
4. **Tool dispatch** (`tools/src/lib.rs`) — if response includes `tool_use`, execute and feed back `tool_result`
|
||||
5. **Auto-compaction check** (`runtime/src/compact.rs`) — REACTIVE-AFTER-SUCCESS only (see #287 for preflight gap)
|
||||
6. **Output** — JSON envelope (`--output-format json`) or text (default)
|
||||
|
||||
## Key Subsystems
|
||||
|
||||
### Auto-compaction
|
||||
|
||||
Triggered post-turn when `usage.input_tokens > threshold`. See:
|
||||
- Threshold via env-only (#283)
|
||||
- Reactive-not-preflight (#287, CRITICAL)
|
||||
- Manual `/compact` skip-reasons (#289)
|
||||
- Failure envelope coverage (#288)
|
||||
|
||||
### Provider routing
|
||||
|
||||
Hard-coded `MODEL_REGISTRY` + env-var-based auth + model-prefix heuristics. See:
|
||||
- [SUPPORTED_PROVIDERS.md](./SUPPORTED_PROVIDERS.md) for current providers
|
||||
- #285 for declarative providers/models/websearch source-of-truth
|
||||
- #245, #246 for declarative config & backend swap
|
||||
- #290, #291, #292 for transport resilience (stream-init, circuit-breaker, escalation)
|
||||
|
||||
### Parallel agents
|
||||
|
||||
Lane-based execution via `runtime/src/lane_events.rs`. Manifest-driven lifecycle. See:
|
||||
- #286 for detached-thread + no-heartbeat issue (CRITICAL)
|
||||
|
||||
### Tool lifecycle / hooks
|
||||
|
||||
Tools defined in `tools/src/`. Hook events emitted via `runtime/src/hooks.rs` and `plugins/src/hooks.rs`. See:
|
||||
- #254 (MCP refresh)
|
||||
- #268 (tool-rendering parity)
|
||||
- #274 (hook-execution-event envelope)
|
||||
- #280 (hook event tap)
|
||||
|
||||
### Session persistence
|
||||
|
||||
Sessions managed in `runtime/src/session.rs`. See:
|
||||
- #278 (version-comparison)
|
||||
- #279 (unknown-field policy)
|
||||
|
||||
### CLI dispatch
|
||||
|
||||
CLI parsing in `rusty-claude-cli/src/main.rs`. Issues:
|
||||
- #262 `--max-turns` spec
|
||||
- #267 `--cwd` runtime fix
|
||||
- #272 position-independent parsing
|
||||
- #282 env-vs-config consolidation
|
||||
|
||||
## Build & Test
|
||||
|
||||
See [CONTRIBUTING.md](../CONTRIBUTING.md) for build commands. Quick reference:
|
||||
|
||||
```
|
||||
cd rust && cargo build # Build all crates
|
||||
cd rust && cargo test # Run all Rust tests
|
||||
```
|
||||
|
||||
## Tracing & Debugging
|
||||
|
||||
- **Session state:** `runtime/src/session.rs` + `~/.claw/sessions/<id>/`
|
||||
- **Provider responses:** Set `RUST_LOG=trace` for verbose SSE logs
|
||||
- **Parity checks:** Use `compat-harness` crate for Rust-port validation
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [ROADMAP.md](../ROADMAP.md) — Pinpoints by cluster
|
||||
- [TROUBLESHOOTING.md](../TROUBLESHOOTING.md) — User-facing failure mitigation
|
||||
- [SUPPORTED_PROVIDERS.md](./SUPPORTED_PROVIDERS.md) — Provider/model details
|
||||
- [CONTRIBUTING.md](../CONTRIBUTING.md) — Pinpoint filing format
|
||||
- [PINPOINT_FILING_GUIDE.md](./PINPOINT_FILING_GUIDE.md) — Filing workflow
|
||||
- [CHANGELOG.md](../CHANGELOG.md) — Recent changes
|
||||
@@ -1,96 +0,0 @@
|
||||
# Configuration
|
||||
|
||||
claw-code configuration reference. For provider details, see [SUPPORTED_PROVIDERS.md](./SUPPORTED_PROVIDERS.md). For architecture, see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
||||
|
||||
## Configuration Sources
|
||||
|
||||
claw-code reads configuration from multiple sources (in priority order):
|
||||
|
||||
1. **CLI flags** — highest priority (e.g., `--model`, `--max-turns`, `--cwd`)
|
||||
2. **Environment variables** — `ANTHROPIC_*`, `OPENAI_*`, `XAI_*`, `DASHSCOPE_*`, `CLAW_*`, etc.
|
||||
3. **settings.json** — `.claw/settings.json` in the project directory, or `~/.claw/settings.json` as a user-level default
|
||||
4. **Hardcoded defaults** — lowest priority
|
||||
|
||||
> **Known issue (#283):** Auto-compaction threshold (`CLAUDE_CODE_AUTO_COMPACT_INPUT_TOKENS`) is env-var-only; no `settings.json` key exists yet.
|
||||
> **Known issue (#282):** env-vs-config consolidation is incomplete; some settings only work in one source.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Provider Authentication
|
||||
|
||||
| Variable | Provider | Notes |
|
||||
|----------|----------|-------|
|
||||
| `ANTHROPIC_API_KEY` | Anthropic (Claude models) | Primary credential for Claude |
|
||||
| `ANTHROPIC_AUTH_TOKEN` | Anthropic | Alternative to `ANTHROPIC_API_KEY` |
|
||||
| `ANTHROPIC_BASE_URL` | Anthropic | Custom endpoint (e.g., proxy) |
|
||||
| `OPENAI_API_KEY` | OpenAI-compatible | Required for `gpt-*` / `openai/` models |
|
||||
| `OPENAI_BASE_URL` | OpenAI-compatible | Custom endpoint (OpenRouter, Ollama, etc.) |
|
||||
| `XAI_API_KEY` | xAI (Grok models) | Required for `grok-*` models |
|
||||
| `XAI_BASE_URL` | xAI | Custom endpoint |
|
||||
| `DASHSCOPE_API_KEY` | DashScope (Qwen/Kimi models) | Required for `qwen-*` / `kimi-*` models |
|
||||
| `DASHSCOPE_BASE_URL` | DashScope | Custom endpoint |
|
||||
|
||||
### Model Selection
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `ANTHROPIC_MODEL` | `claude-sonnet-4-6` | Default model when `--model` flag is not passed |
|
||||
|
||||
### Runtime Configuration
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `CLAUDE_CODE_AUTO_COMPACT_INPUT_TOKENS` | provider-specific | Auto-compaction trigger threshold (see #283) |
|
||||
| `CLAW_CONFIG_HOME` | `~/.claw` | Override config directory location |
|
||||
| `CLAWD_WEB_SEARCH_BASE_URL` | (built-in) | Custom base URL for web search tool |
|
||||
| `CLAWD_TODO_STORE` | `~/.claw/todos` | Override todo storage path |
|
||||
| `CLAWD_AGENT_STORE` | `~/.claw/agents` | Override agent store path |
|
||||
| `RUST_LOG` | `info` | Log verbosity (`trace`/`debug`/`info`/`warn`/`error`) |
|
||||
|
||||
**Related paths also respected:** `CODEX_HOME`, `CLAUDE_CONFIG_DIR` (legacy compatibility).
|
||||
|
||||
## settings.json
|
||||
|
||||
Located at `.claw/settings.json` (project-local) or `~/.claw/settings.json` (user-level). Project-local takes precedence over user-level.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "claude-sonnet-4-6"
|
||||
}
|
||||
```
|
||||
|
||||
`claw /config` shows the merged, resolved configuration from all sources.
|
||||
|
||||
> **Known gap (#285):** No declarative `providers` or `models` block in `settings.json`. Provider selection is currently model-prefix-based via a hardcoded `MODEL_REGISTRY`. See [SUPPORTED_PROVIDERS.md](./SUPPORTED_PROVIDERS.md) for the full provider/model matrix.
|
||||
|
||||
## Provider Selection
|
||||
|
||||
Provider is auto-selected from model name prefix or the `openai/` namespace prefix:
|
||||
|
||||
| Model pattern | Provider | Auth env |
|
||||
|--------------|----------|----------|
|
||||
| `claude-*` | Anthropic | `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` |
|
||||
| `gpt-*`, `openai/*` | OpenAI-compatible | `OPENAI_API_KEY` |
|
||||
| `grok-*` | xAI | `XAI_API_KEY` |
|
||||
| `qwen-*`, `kimi-*` | DashScope | `DASHSCOPE_API_KEY` |
|
||||
|
||||
When `OPENAI_BASE_URL` is set, the OpenAI-compatible provider is preferred for unrecognised model names — useful for Ollama or OpenRouter.
|
||||
|
||||
## Session Storage
|
||||
|
||||
Sessions are stored in `~/.claw/sessions/<session-id>/` (or under `CLAW_CONFIG_HOME`). Each session contains:
|
||||
|
||||
- Conversation history (messages)
|
||||
- Session metadata (model, created_at, etc.)
|
||||
- Tool execution state
|
||||
|
||||
See pinpoints #278 (version-comparison) and #279 (unknown-field policy) for known session persistence caveats.
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [SUPPORTED_PROVIDERS.md](./SUPPORTED_PROVIDERS.md) — Provider/model matrix and auth details
|
||||
- [ARCHITECTURE.md](./ARCHITECTURE.md) — Crate layout and request flow
|
||||
- [TROUBLESHOOTING.md](../TROUBLESHOOTING.md) — Failure mitigation
|
||||
- [ROADMAP.md](../ROADMAP.md) — Pinpoints by cluster
|
||||
@@ -9,7 +9,8 @@ This document describes model-specific handling in the OpenAI-compatible provide
|
||||
- [Kimi Models (is_error Exclusion)](#kimi-models-is_error-exclusion)
|
||||
- [Reasoning Models (Tuning Parameter Stripping)](#reasoning-models-tuning-parameter-stripping)
|
||||
- [GPT-5 (max_completion_tokens)](#gpt-5-max_completion_tokens)
|
||||
- [Qwen Models (DashScope Routing)](#qwen-models-dashscope-routing)
|
||||
- [Qwen and Kimi Models (DashScope Routing)](#qwen-and-kimi-models-dashscope-routing)
|
||||
- [Custom Gateway Slugs and Extra Body Parameters](#custom-gateway-slugs-and-extra-body-parameters)
|
||||
- [Implementation Details](#implementation-details)
|
||||
- [Adding New Models](#adding-new-models)
|
||||
- [Testing](#testing)
|
||||
@@ -22,6 +23,8 @@ The `openai_compat.rs` provider translates Claude Code's internal message format
|
||||
- Sampling parameters (temperature, top_p, etc.)
|
||||
- Token limit fields (`max_tokens` vs `max_completion_tokens`)
|
||||
- Base URL routing
|
||||
- Provider-specific extra body parameters (`web_search_options`, `parallel_tool_calls`, local-server switches, etc.)
|
||||
- Provider diagnostics for status/doctor-style surfaces
|
||||
|
||||
## Model-Specific Handling
|
||||
|
||||
@@ -46,7 +49,7 @@ The `openai_compat.rs` provider translates Claude Code's internal message format
|
||||
fn model_rejects_is_error_field(model: &str) -> bool {
|
||||
let lowered = model.to_ascii_lowercase();
|
||||
let canonical = lowered.rsplit('/').next().unwrap_or(lowered.as_str());
|
||||
canonical.starts_with("kimi-")
|
||||
canonical.starts_with("kimi")
|
||||
}
|
||||
```
|
||||
|
||||
@@ -120,13 +123,13 @@ let max_tokens_key = if wire_model.starts_with("gpt-5") {
|
||||
|
||||
---
|
||||
|
||||
### Qwen Models (DashScope Routing)
|
||||
### Qwen and Kimi Models (DashScope Routing)
|
||||
|
||||
**Affected models:** All models with `qwen` prefix
|
||||
**Affected models:** All models with `qwen` or `kimi` prefixes, including `qwen/`, `qwen-`, `kimi/`, and `kimi-` forms.
|
||||
|
||||
**Behavior:** Routed to DashScope (`https://dashscope.aliyuncs.com/compatible-mode/v1`) rather than default providers.
|
||||
**Behavior:** Routed to DashScope (`https://dashscope.aliyuncs.com/compatible-mode/v1`) rather than ambient-credential fallback providers. Known routing prefixes are stripped before sending the wire model.
|
||||
|
||||
**Rationale:** Qwen models are hosted by Alibaba Cloud's DashScope service, not OpenAI or Anthropic.
|
||||
**Rationale:** Qwen and Kimi compatible-mode models are hosted through Alibaba Cloud's DashScope service, not OpenAI or Anthropic.
|
||||
|
||||
**Configuration:**
|
||||
```rust
|
||||
@@ -137,6 +140,21 @@ pub const DEFAULT_DASHSCOPE_BASE_URL: &str = "https://dashscope.aliyuncs.com/com
|
||||
|
||||
**Note:** Some Qwen models are also reasoning models (see [Reasoning Models](#reasoning-models-tuning-parameter-stripping) above) and receive both treatments.
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Custom Gateway Slugs and Extra Body Parameters
|
||||
|
||||
**Affected models:** Slash-containing model IDs routed through the OpenAI-compatible provider, especially custom gateways configured with `OPENAI_BASE_URL` such as OpenRouter, local routers, or other `/v1/chat/completions` services.
|
||||
|
||||
**Behavior:**
|
||||
- The default OpenAI API treats `openai/` as a routing prefix and sends the bare model name on the wire.
|
||||
- Custom OpenAI-compatible base URLs preserve slash-containing slugs such as `openai/gpt-4.1-mini` so the gateway receives the exact model ID it expects.
|
||||
- `MessageRequest::extra_body` passes through custom request JSON after core fields are populated. This supports provider-specific options such as `web_search_options` and `parallel_tool_calls`.
|
||||
- Protected core fields (`model`, `messages`, `stream`, `tools`, `tool_choice`, `max_tokens`, `max_completion_tokens`) cannot be overridden through `extra_body`.
|
||||
|
||||
**Testing:** See `custom_openai_gateway_preserves_slash_model_ids_and_extra_body_params` in `openai_compat_integration.rs` and `extra_body_params_are_passed_through_without_overriding_core_fields` in `openai_compat.rs`.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### File Location
|
||||
@@ -152,7 +170,8 @@ rust/crates/api/src/providers/openai_compat.rs
|
||||
| `model_rejects_is_error_field()` | Detects models that don't support `is_error` in tool results |
|
||||
| `is_reasoning_model()` | Detects reasoning models that need tuning param stripping |
|
||||
| `translate_message()` | Converts internal messages to OpenAI format (applies `is_error` logic) |
|
||||
| `build_chat_completion_request()` | Constructs full request payload (applies all model-specific logic) |
|
||||
| `build_chat_completion_request()` | Constructs full request payload (applies all model-specific logic and safe `extra_body` passthrough) |
|
||||
| `provider_diagnostics_for_model()` | Produces provider/status diagnostics including auth/base-url vars, reasoning behavior, proxy support, extra-body support, and slash-model preservation |
|
||||
|
||||
### Provider Prefix Handling
|
||||
|
||||
@@ -165,7 +184,7 @@ let canonical = model.to_ascii_lowercase()
|
||||
.unwrap_or(model);
|
||||
```
|
||||
|
||||
This ensures consistent detection regardless of whether models are referenced with or without provider prefixes.
|
||||
This ensures consistent detection regardless of whether models are referenced with or without provider prefixes. Wire-model handling is more specific: known routing prefixes are stripped for provider-native defaults, while custom OpenAI-compatible base URLs preserve slash-containing gateway slugs.
|
||||
|
||||
## Adding New Models
|
||||
|
||||
@@ -183,11 +202,15 @@ When adding support for new models:
|
||||
- Does it require `max_completion_tokens` instead of `max_tokens`?
|
||||
- Update the `max_tokens_key` logic
|
||||
|
||||
4. **Add tests**
|
||||
4. **Check custom gateway behavior**
|
||||
- Should slash-containing IDs be preserved for custom `OPENAI_BASE_URL` gateways?
|
||||
- Does the feature belong in a typed request field or `extra_body` passthrough?
|
||||
|
||||
5. **Add tests**
|
||||
- Unit test for detection function
|
||||
- Integration test in `build_chat_completion_request`
|
||||
|
||||
5. **Update this documentation**
|
||||
6. **Update this documentation**
|
||||
- Add the model to the affected lists
|
||||
- Document any special behavior
|
||||
|
||||
@@ -204,6 +227,8 @@ cargo test --package api model_rejects_is_error_field
|
||||
cargo test --package api reasoning_model
|
||||
cargo test --package api gpt5
|
||||
cargo test --package api qwen
|
||||
cargo test --package api custom_openai_gateway_preserves_slash_model_ids_and_extra_body_params
|
||||
cargo test --package api provider_diagnostics_explain_openai_compatible_capabilities
|
||||
```
|
||||
|
||||
### Test Files
|
||||
@@ -231,6 +256,6 @@ fn my_new_model_is_detected() {
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-04-16*
|
||||
*Last updated: 2026-05-15*
|
||||
|
||||
For questions or updates, see the implementation in `rust/crates/api/src/providers/openai_compat.rs`.
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# Pinpoint Filing Guide
|
||||
|
||||
This guide walks through the workflow for filing a new claw-code pinpoint, from initial friction to merged ROADMAP entry. For format details, see [CONTRIBUTING.md](../CONTRIBUTING.md). For issue template, see [.github/ISSUE_TEMPLATE/pinpoint.md](../.github/ISSUE_TEMPLATE/pinpoint.md).
|
||||
|
||||
## What is a Pinpoint?
|
||||
|
||||
A pinpoint is a precise, distinct claw-code clawability gap captured in ROADMAP.md format. Pinpoints differ from generic issues by:
|
||||
- **Specificity:** Exact file paths, function names, line numbers when available
|
||||
- **Distinctness:** Verified not already covered by existing pinpoints
|
||||
- **Live evidence:** Real friction event, not hypothetical
|
||||
- **Fix shape:** Concrete delta proposal, not vague "should improve X"
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify friction
|
||||
|
||||
Use claw-code in real work. When you hit friction (slow startup, broken behavior, opaque error, missing feature, test brittleness, etc.), STOP and capture:
|
||||
- What you were trying to do
|
||||
- What you expected to happen
|
||||
- What actually happened
|
||||
- Exact error message / log output (verbatim)
|
||||
|
||||
### Step 2: Identify distinct axis
|
||||
|
||||
Open ROADMAP.md and search for related existing pinpoints (use the [Cluster Index](../ROADMAP.md#pinpoint-cluster-index)).
|
||||
|
||||
For each candidate match:
|
||||
- Does the existing pinpoint cover this exact symptom?
|
||||
- Does it cover this exact axis (e.g., timing vs envelope vs config)?
|
||||
- Is your case a SUBSET, a SUPERSET, or an ORTHOGONAL axis?
|
||||
|
||||
If your case is orthogonal, file new. If subset, add live-evidence as additional context to existing pinpoint. If superset, file new + cross-reference existing.
|
||||
|
||||
### Step 3: Verify with code
|
||||
|
||||
Before filing, look at the relevant source code:
|
||||
- `rust/crates/api/src/sse.rs` — provider routing
|
||||
- `rust/crates/runtime/src/conversation.rs` — auto-compaction logic
|
||||
- `rust/crates/rusty-claude-cli/src/main.rs` — CLI entry
|
||||
- Search with grep / ripgrep to find the relevant module
|
||||
|
||||
If the code clearly does NOT have the feature you expected, file a pinpoint. If the code DOES have the feature but it's broken, file a bug.
|
||||
|
||||
### Step 4: Write the entry
|
||||
|
||||
Follow the canonical 5-section format (see [CONTRIBUTING.md](../CONTRIBUTING.md)):
|
||||
1. **Exact pinpoint** — One precise sentence
|
||||
2. **Live evidence** — Real friction event with timestamps
|
||||
3. **Why distinct** — Explicit comparison to nearest existing pinpoints
|
||||
4. **Concrete delta** — What you're filing (e.g., "ROADMAP.md appended")
|
||||
5. **Fix shape recorded** — Bullet list of suggested implementation steps
|
||||
|
||||
### Step 5: Submit
|
||||
|
||||
Append to ROADMAP.md and commit:
|
||||
|
||||
```
|
||||
git add ROADMAP.md
|
||||
git commit -m "roadmap: #<NNN> filed (<short title>)"
|
||||
git push origin <branch>
|
||||
git push fork <branch>
|
||||
```
|
||||
|
||||
Verify three-way parity (local == origin == fork) before posting any update.
|
||||
|
||||
## Worked Example: #290 (stream-init failure envelope)
|
||||
|
||||
This shows how #290 was filed in real-time on 2026-04-26.
|
||||
|
||||
### Step 1: Friction identified
|
||||
|
||||
gaebal-gajae's session hit `500 empty_stream: upstream stream closed before first payload` repeatedly (4x in 30 min). Bare-string error surfaced; no diagnostics, no retry guidance.
|
||||
|
||||
### Step 2: Distinct axis identified
|
||||
|
||||
- #266 (typed-error-kind taxonomy) covers single-failure categorization, NOT stream-init specifically
|
||||
- #287 (auto-compaction reactive) covers session-size failures, NOT transport
|
||||
- #288 (JSON envelope failure) covers context-window envelope, NOT stream-init
|
||||
|
||||
→ Orthogonal: filed new #290 covering typed-stream-init-failure-envelope
|
||||
|
||||
### Step 3: Code verified
|
||||
|
||||
Inspected `rust/crates/api/src/sse.rs` — confirmed no `failure_class=upstream_stream_init` discriminant, no retry recommendation in JSON envelope.
|
||||
|
||||
### Step 4: Entry written
|
||||
|
||||
Used canonical 5-section format. Listed 4 live evidence timestamps. Cross-referenced #266, #287, #288 in "Why distinct."
|
||||
|
||||
### Step 5: Submitted
|
||||
|
||||
Commit `0f38975`, pushed to both origin and fork, parity verified, Discord post under 1500 chars.
|
||||
|
||||
**Total time: ~2 minutes from friction identification to merged ROADMAP entry.**
|
||||
|
||||
## Tips
|
||||
|
||||
- **File while it's fresh.** Wait too long and you'll forget exact symptoms.
|
||||
- **Check Cluster Index FIRST** — saves time vs scanning full ROADMAP.
|
||||
- **Write Fix Shape even if you don't implement.** Helps future contributors.
|
||||
- **Live evidence with timestamps > theoretical examples.** Real-world friction always wins.
|
||||
@@ -1,81 +0,0 @@
|
||||
# Supported Providers
|
||||
|
||||
claw-code currently supports the following LLM providers. This is a snapshot of the current code state and may change. The canonical source of truth is `MODEL_REGISTRY` and provider routing logic in `rust/crates/api/src/providers/mod.rs`.
|
||||
|
||||
> **Note:** A declarative `providers` / `models` / `websearch` config in `settings.json` is tracked as pinpoint #285 and is not yet implemented. Until then, provider/model selection is determined by:
|
||||
> 1. The model name prefix (e.g., `claude-`, `grok-`, `openai/`, `qwen/`, `kimi-`)
|
||||
> 2. Environment variables (e.g., `ANTHROPIC_API_KEY`, `XAI_API_KEY`, `DASHSCOPE_API_KEY`, `OPENAI_API_KEY`)
|
||||
> 3. Hard-coded heuristics in `MODEL_REGISTRY` and `detect_provider_kind()`
|
||||
|
||||
## Anthropic
|
||||
|
||||
- **Status:** Primary supported provider
|
||||
- **Models:**
|
||||
- `claude-opus-4-6` (alias: `opus`) — 200K context, 32K max output
|
||||
- `claude-sonnet-4-6` (alias: `sonnet`) — 200K context, 64K max output
|
||||
- `claude-haiku-4-5-20251213` (alias: `haiku`) — 200K context, 64K max output
|
||||
- **Auth:** `ANTHROPIC_API_KEY` env var, or OAuth bearer via `claw login` (`ANTHROPIC_AUTH_TOKEN`)
|
||||
- **Base URL:** `https://api.anthropic.com` (override: `ANTHROPIC_BASE_URL`)
|
||||
- **Known issues:** Subject to upstream stream-init failures (see #290, #291)
|
||||
|
||||
## xAI (Grok)
|
||||
|
||||
- **Status:** Supported via OpenAI-compatible client
|
||||
- **Models:**
|
||||
- `grok-3` (aliases: `grok`, `grok-3`) — 131K context, 64K max output
|
||||
- `grok-3-mini` (aliases: `grok-mini`, `grok-3-mini`) — 131K context, 64K max output
|
||||
- `grok-2` — context/output limits not yet registered in token metadata
|
||||
- **Auth:** `XAI_API_KEY`
|
||||
- **Base URL:** `https://api.x.ai/v1` (override: `XAI_BASE_URL`)
|
||||
- **Known issues:** None currently tracked
|
||||
|
||||
## Alibaba DashScope (Qwen / Kimi)
|
||||
|
||||
- **Status:** Supported via OpenAI-compatible client pointed at DashScope compatible-mode endpoint
|
||||
- **Models:**
|
||||
- `qwen/*` and `qwen-*` prefix — routes to DashScope (e.g., `qwen-plus`, `qwen-max`, `qwen-turbo`, `qwen/qwen3-coder`)
|
||||
- `kimi-k2.5` (alias: `kimi`) — 256K context, 16K max output
|
||||
- `kimi-k1.5` — 256K context, 16K max output
|
||||
- `kimi/*` and `kimi-*` prefix — routes to DashScope
|
||||
- **Auth:** `DASHSCOPE_API_KEY`
|
||||
- **Base URL:** `https://dashscope.aliyuncs.com/compatible-mode/v1` (override: `DASHSCOPE_BASE_URL`)
|
||||
- **Known issues:** None currently tracked
|
||||
|
||||
## OpenAI / OpenAI-Compatible Endpoints
|
||||
|
||||
- **Status:** Supported via OpenAI-compatible client; also covers local providers (Ollama, LM Studio, vLLM, OpenRouter)
|
||||
- **Models:** `openai/` prefix (e.g., `openai/gpt-4.1-mini`) or bare `gpt-*` prefix
|
||||
- **Auth:** `OPENAI_API_KEY`
|
||||
- **Base URL:** `https://api.openai.com/v1` (override: `OPENAI_BASE_URL` — also used for local providers)
|
||||
- **Local provider routing:** When `OPENAI_BASE_URL` is set and `OPENAI_API_KEY` is present, unknown model names (e.g., `qwen2.5-coder:7b`) also route here
|
||||
- **Known issues:** Declarative per-model config tracked in #285
|
||||
|
||||
## Web Search
|
||||
|
||||
- **Status:** Hard-coded heuristics; declarative `websearch` config tracked in #285
|
||||
|
||||
## Provider Selection Order
|
||||
|
||||
When the model name has no recognized prefix, `detect_provider_kind()` falls through in this order:
|
||||
|
||||
1. Model prefix match (`claude-` → Anthropic, `grok-` → xAI, `openai/` or `gpt-` → OpenAI, `qwen/` or `qwen-` → DashScope, `kimi/` or `kimi-` → DashScope)
|
||||
2. `OPENAI_BASE_URL` + `OPENAI_API_KEY` set → OpenAI-compat
|
||||
3. Anthropic credentials found → Anthropic
|
||||
4. `OPENAI_API_KEY` found → OpenAI
|
||||
5. `XAI_API_KEY` found → xAI
|
||||
6. `OPENAI_BASE_URL` set (no key) → OpenAI-compat (for keyless local providers)
|
||||
7. Default fallback → Anthropic
|
||||
|
||||
## Reporting Provider Issues
|
||||
|
||||
For provider-specific bugs (e.g., `500 empty_stream` from upstream), see [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for mitigation steps.
|
||||
|
||||
For pinpointing a missing provider feature, file via [ISSUE_TEMPLATE/pinpoint.md](../.github/ISSUE_TEMPLATE/pinpoint.md).
|
||||
|
||||
## Related Pinpoints
|
||||
|
||||
- #245 — Provider declarative config
|
||||
- #246 — Backend swap
|
||||
- #285 — Provider/model/websearch source of truth
|
||||
- #290 — Stream-init failure envelope
|
||||
- #291 — Repeat-failure circuit-breaker
|
||||
44
docs/anti-slop-triage.md
Normal file
44
docs/anti-slop-triage.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Anti-slop issue and PR triage
|
||||
|
||||
Use this checklist before spending engineering time on low-signal issues, generated PRs, duplicate fixes, or broad unsolicited changes. The goal is not to reject community work by default; it is to make each merge, defer, or close recommendation evidence-backed and safe.
|
||||
|
||||
## Classifications
|
||||
|
||||
| Classification | Use when | Required evidence | Safe action |
|
||||
| --- | --- | --- | --- |
|
||||
| `actionable-bug` | The report has a reproducible product failure. | Repro steps, failing test, logs with secrets removed, or matching roadmap item. | Fix, assign, or link to an existing fix. |
|
||||
| `actionable-docs` | The report identifies missing, stale, or confusing documentation. | Current doc path plus desired corrected source of truth. | Patch docs or link to the owning docs lane. |
|
||||
| `actionable-feature` | The request matches Claw Code direction and has a concrete acceptance shape. | Issue/PR link plus roadmap or maintainer rationale. | Defer to planning or implement if already scoped. |
|
||||
| `duplicate` | Another issue/PR already covers the same user-visible outcome. | Link the canonical issue/PR and note any extra evidence worth preserving. | Cross-link; close only with maintainer/owner policy. |
|
||||
| `spam-or-promotion` | The content is promotional, irrelevant, or abusive. | URL/title/body excerpt summary, not a full repost. | Label/close per repository policy. |
|
||||
| `generated-slop-or-hallucinated` | The change is broad, mechanically generated, unreviewable, or names APIs/files that do not exist. | Diff/path examples, missing symbols, or unverifiable claims. | Request a narrow repro or reject/defer with rationale. |
|
||||
| `unsafe-or-security-sensitive` | The report includes secrets, exploit detail, or risky operational instructions. | Redacted summary and security policy link. | Move to the private/security path; do not expand public details. |
|
||||
| `not-reproducible-yet` | The claim might be valid but lacks enough evidence to act. | Missing command, environment, expected/actual behavior, or version. | Ask for repro details; do not implement speculative fixes. |
|
||||
| `externally-blocked` | Progress depends on upstream services, credentials, policy, or unavailable owner approval. | Blocking dependency and owner/gate. | Defer with a concrete unblock condition. |
|
||||
|
||||
## PR review gate
|
||||
|
||||
Every PR triage note should answer:
|
||||
|
||||
1. Is the PR a merge candidate, a request-changes candidate, a duplicate, unsafe, out-of-scope, or generated slop?
|
||||
2. What exact evidence supports that classification?
|
||||
3. Which tests/docs checks were run or intentionally skipped?
|
||||
4. Which issue, roadmap row, or user problem does it resolve?
|
||||
5. If it should not merge now, what is the minimal non-destructive next action?
|
||||
|
||||
Automation lanes must not merge or close remote PRs/issues. They may produce a ledger row, add local documentation/templates, and report recommended actions for a maintainer-owned final gate.
|
||||
|
||||
## Issue intake gate
|
||||
|
||||
Every issue triage note should answer:
|
||||
|
||||
1. Is the issue correct, duplicate, spam, invalid, externally blocked, or not reproducible yet?
|
||||
2. If correct and resolvable, what fix path or already-merged commit resolves it?
|
||||
3. If not currently resolvable, what evidence would change the classification?
|
||||
4. Are secrets, private data, or security details present that require a private path?
|
||||
|
||||
## Template locations
|
||||
|
||||
- Issue intake form: `.github/ISSUE_TEMPLATE/anti_slop_triage.yml`
|
||||
- PR review checklist: `.github/PULL_REQUEST_TEMPLATE.md`
|
||||
- Final aggregate gate: `docs/pr-issue-resolution-gate.md`
|
||||
185
docs/g002-security-verification-map.md
Normal file
185
docs/g002-security-verification-map.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# G002 alpha security map and verification plan
|
||||
|
||||
Generated by `worker-4` for OMX team task 5 on 2026-05-14.
|
||||
|
||||
## Scope and coordination
|
||||
|
||||
- Active goal context: `G002-alpha-security` / Stream 6 day-one security and permissions gate.
|
||||
- Worker ownership: `worker-1` owns minimal implementation changes for workspace/path enforcement. `worker-4` owns this repository map, integration verification plan, changed-file/commit report, and exact verification evidence.
|
||||
- Boundary: this report does not mutate `.omx/ultragoal` and does not edit shared security/path tests.
|
||||
- Parallel probe status: three native subagents were spawned for repository map, test probe, and change-slice probe, but all failed before returning findings with `429 Too Many Requests`; local mapping below is based on direct repository inspection.
|
||||
|
||||
## Current permission and path enforcement map
|
||||
|
||||
### Runtime permission policy and enforcer
|
||||
|
||||
- `rust/crates/runtime/src/permissions.rs`
|
||||
- Owns the `PermissionMode` ordering and `PermissionPolicy` authorization contract.
|
||||
- Existing tests cover read-only denial, workspace-write escalation, prompt approvals/denials, danger-full-access allowance, override recording, and required-mode reporting.
|
||||
- Integration risk: any new dynamic file/path rule must preserve the existing `PermissionPolicy::authorize` semantics so prompt/override audit events remain stable.
|
||||
|
||||
- `rust/crates/runtime/src/permission_enforcer.rs`
|
||||
- `PermissionEnforcer::check`, `check_with_required_mode`, `check_file_write`, and `check_bash` convert policy outcomes into structured `EnforcementResult` payloads.
|
||||
- `check_file_write` currently has the direct write gate for workspace-write mode.
|
||||
- `is_within_workspace` is a string-prefix boundary check after simple relative-path joining; it does not canonicalize symlinks, `..`, Windows drive prefixes, or case variants.
|
||||
- Existing tests cover read-only denial, workspace-write inside/outside paths, trailing slashes, root equality, bash read-only heuristics, prompt-mode denial payloads, and structured denied fields.
|
||||
|
||||
### File tool path handling
|
||||
|
||||
- `rust/crates/runtime/src/file_ops.rs`
|
||||
- `read_file`, `write_file`, and `edit_file` normalize paths before filesystem operations but do not themselves require a workspace root.
|
||||
- `read_file_in_workspace`, `write_file_in_workspace`, and `edit_file_in_workspace` exist as boundary-enforced wrappers.
|
||||
- `validate_workspace_boundary` canonicalizes through the caller-provided resolved path and checks `starts_with(workspace_root)`.
|
||||
- `is_symlink_escape` detects direct symlink escapes by comparing canonical target to canonical workspace root.
|
||||
- Search tools (`glob_search`, `grep_search`) derive walk roots and prune heavy directories, but they are separate from the write enforcement path.
|
||||
- Existing tests cover oversized/binary reads, workspace-boundary read rejection, symlink escape detection, glob brace expansion, ignored directories, and grep/glob behavior.
|
||||
|
||||
### Bash command validation
|
||||
|
||||
- `rust/crates/runtime/src/bash_validation.rs`
|
||||
- `validate_command` runs mode validation, sed validation, destructive warning checks, then path validation.
|
||||
- `validate_read_only` blocks write-like commands, state-modifying commands, write redirects, and mutating git subcommands in read-only mode.
|
||||
- `validate_mode` warns when workspace-write commands appear to target hard-coded system paths.
|
||||
- `validate_paths` warns for `../`, `~/`, and `$HOME` references; it is intentionally heuristic and does not resolve shell expansion or canonical targets.
|
||||
- Existing tests cover read-only blockers, destructive warnings, sed in-place blocking, path traversal/home warnings, command classification, and full pipeline allow/block/warn outcomes.
|
||||
|
||||
### Sandbox and diagnostics surfaces
|
||||
|
||||
- `rust/crates/runtime/src/sandbox.rs`
|
||||
- Owns container/sandbox status detection and workspace-only sandbox command construction.
|
||||
- Relevant for day-one security because sandbox status must not overstate filesystem isolation.
|
||||
|
||||
- `rust/crates/rusty-claude-cli/src/main.rs`
|
||||
- Owns CLI permission-mode parsing, direct JSON/text diagnostic output, `/permissions`, `/status`, `/doctor`, and command dispatch paths.
|
||||
- Existing CLI integration tests under `rust/crates/rusty-claude-cli/tests/` cover permission prompt scenarios and output-format contracts.
|
||||
|
||||
- `rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs`
|
||||
- End-to-end harness includes `bash_permission_prompt_approved`, `bash_permission_prompt_denied`, read/write file allow/deny, and plugin workspace-write scenarios.
|
||||
|
||||
## Existing G002-adjacent coverage
|
||||
|
||||
- Unit-level permission coverage:
|
||||
- `cargo test -p runtime permissions::tests`
|
||||
- `cargo test -p runtime permission_enforcer::tests`
|
||||
- `cargo test -p runtime bash_validation::tests`
|
||||
- `cargo test -p runtime file_ops::tests`
|
||||
|
||||
- CLI and integration coverage:
|
||||
- `cargo test -p rusty-claude-cli --test mock_parity_harness`
|
||||
- `cargo test -p rusty-claude-cli --test output_format_contract`
|
||||
- `cargo test -p rusty-claude-cli --test cli_flags_and_config_defaults`
|
||||
|
||||
- Board/report validation coverage:
|
||||
- `python3 scripts/validate_cc2_board.py --board .omx/cc2/board.json`
|
||||
- `python3 .omx/cc2/validate_issue_parity_intake.py .omx/cc2/issue-parity-intake.json`
|
||||
|
||||
## Recommended safe work slices
|
||||
|
||||
### Implementation lane (owned by worker-1 unless re-scoped)
|
||||
|
||||
1. Replace string-prefix workspace boundary checks with canonical path comparison in the runtime enforcement path.
|
||||
- Primary files: `rust/crates/runtime/src/permission_enforcer.rs`, possibly shared helper extraction from `rust/crates/runtime/src/file_ops.rs`.
|
||||
- Regression cases: `../` traversal, symlink escape, root prefix collision (`/workspace` vs `/workspacex`), relative paths, trailing slash root equality.
|
||||
|
||||
2. Ensure direct file tools call workspace-aware wrappers when active permission mode is `workspace-write`.
|
||||
- Primary files: likely `rust/crates/runtime/src/mcp_tool_bridge.rs` and/or the runtime tool execution bridge that calls `file_ops`.
|
||||
- Regression cases: direct read/write paths, missing parent creation, symlink parent escape, and error payload stability.
|
||||
|
||||
3. Keep bash validation as a warning/classification layer unless a real shell-expansion resolver is introduced.
|
||||
- Primary files: `rust/crates/runtime/src/bash_validation.rs`, `rust/crates/runtime/src/bash.rs`.
|
||||
- Risk: heuristic parsing cannot faithfully resolve shell expansion, globs, aliases, or platform-specific path rules; avoid claiming hard enforcement unless execution sandbox or command resolver proves it.
|
||||
|
||||
### Test lane (coordinate with worker-3/worker-1 before editing)
|
||||
|
||||
1. Add unit regressions close to each enforcement function before changing behavior.
|
||||
- `permission_enforcer.rs`: canonical path boundary and Windows-shaped path cases.
|
||||
- `file_ops.rs`: write/edit workspace wrappers with symlink parent escapes and missing file parent canonicalization.
|
||||
- `bash_validation.rs`: shell expansion/glob/path warnings remain warnings unless a resolver is introduced.
|
||||
|
||||
2. Add at least one integration test proving the runtime bridge actually routes file tools through workspace enforcement, not only helper functions.
|
||||
- Candidate: `rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs` for direct write denial and no file created outside workspace.
|
||||
|
||||
3. Preserve existing prompt/event visibility tests.
|
||||
- Candidate surfaces: permission prompt scenarios in `mock_parity_harness.rs`, status/doctor JSON in `output_format_contract.rs`.
|
||||
|
||||
### Docs/reporting lane (owned by worker-4)
|
||||
|
||||
1. Keep this file as the integration handoff artifact for G002 mapping and verification.
|
||||
2. Report changed files and commits relative to `origin/main` so the leader can integrate worker branches deterministically.
|
||||
3. Include exact command evidence in the task lifecycle result.
|
||||
|
||||
## Changed files relative to `origin/main` at map time
|
||||
|
||||
The worktree currently contains these files added relative to `origin/main` before this task report:
|
||||
|
||||
- `.omx/cc2/board.json`
|
||||
- `.omx/cc2/board.md`
|
||||
- `.omx/cc2/issue-parity-intake.json`
|
||||
- `.omx/cc2/issue-parity-intake.md`
|
||||
- `.omx/cc2/render_board_md.py`
|
||||
- `.omx/cc2/validate_issue_parity_intake.py`
|
||||
- `scripts/cc2_board.py`
|
||||
- `scripts/generate_cc2_board.py`
|
||||
- `scripts/validate_cc2_board.py`
|
||||
|
||||
This task adds:
|
||||
|
||||
- `docs/g002-security-verification-map.md`
|
||||
|
||||
## Commits relative to `origin/main` at map time
|
||||
|
||||
- `8311655` — `omx(team): auto-checkpoint worker-1 [1]`
|
||||
- `c6e2a7d` — `omx(team): merge worker-1`
|
||||
- `481585f` — `omx(team): auto-checkpoint worker-1 [1]`
|
||||
- `74bbf4b` — `omx(team): auto-checkpoint worker-4 [unknown]`
|
||||
- `5c77896` — `omx(team): auto-checkpoint worker-1 [1]`
|
||||
- `07dad88` — `Classify issue and parity intake for CC2 board integration`
|
||||
- `424825f` — `task: G001 human board and docs rendering`
|
||||
- `d15268e` — `Create a canonical CC2 board so every frozen ROADMAP heading is verifiably mapped`
|
||||
- `45b43b5` — `Make the CC2 board schema executable for G001`
|
||||
|
||||
## Verification checklist for leader integration
|
||||
|
||||
Run these from the repository root unless noted:
|
||||
|
||||
1. Python board/schema validation:
|
||||
- `python3 scripts/validate_cc2_board.py --board .omx/cc2/board.json`
|
||||
- `python3 .omx/cc2/validate_issue_parity_intake.py .omx/cc2/issue-parity-intake.json`
|
||||
|
||||
2. Rust formatting and lint/type checks:
|
||||
- `scripts/fmt.sh --check`
|
||||
- `(cd rust && cargo check --workspace)`
|
||||
- `(cd rust && cargo clippy --workspace --all-targets -- -D warnings)`
|
||||
|
||||
3. Targeted G002 security tests:
|
||||
- `(cd rust && cargo test -p runtime permissions::tests permission_enforcer::tests bash_validation::tests file_ops::tests)`
|
||||
- `(cd rust && cargo test -p rusty-claude-cli --test mock_parity_harness)`
|
||||
|
||||
4. Full regression:
|
||||
- `(cd rust && cargo test --workspace)`
|
||||
|
||||
|
||||
## Worker-4 verification evidence (2026-05-14)
|
||||
|
||||
PASS:
|
||||
|
||||
- `python3 scripts/validate_cc2_board.py --board .omx/cc2/board.json` → `PASS cc2 board validation`; 729 items; ROADMAP headings `124/124`; ROADMAP actions `542/542`.
|
||||
- `python3 .omx/cc2/validate_issue_parity_intake.py .omx/cc2/issue-parity-intake.json` → `PASS issue/parity intake: 19 issue rows, 9 parity rows`.
|
||||
- `scripts/fmt.sh --check` → no output and zero exit before Rust checks continued.
|
||||
- `(cd rust && cargo check --workspace)` → `Finished dev profile` successfully.
|
||||
- `(cd rust && cargo test -p runtime permissions::tests)` → 9 passed.
|
||||
- `(cd rust && cargo test -p runtime permission_enforcer::tests)` → 21 passed.
|
||||
- `(cd rust && cargo test -p runtime bash_validation::tests)` → 32 passed.
|
||||
- `(cd rust && cargo test -p runtime file_ops::tests)` → 14 passed.
|
||||
- `(cd rust && cargo test -p rusty-claude-cli --test mock_parity_harness)` → 1 passed.
|
||||
|
||||
FAIL / integration blockers observed on this worktree:
|
||||
|
||||
- `(cd rust && cargo clippy --workspace --all-targets -- -D warnings)` failed in existing runtime code, not this docs-only task:
|
||||
- `rust/crates/runtime/src/compact.rs:215` / `:216`: `clippy::match_same_arms`.
|
||||
- `rust/crates/runtime/src/policy_engine.rs:5`: `clippy::duration-suboptimal-units`.
|
||||
- `rust/crates/runtime/src/sandbox.rs:295-302`: `clippy::map_unwrap_or`.
|
||||
- `(cd rust && cargo test --workspace)` failed after broad success in API/commands/plugins/runtime tests because `rusty-claude-cli` unit test `tests::session_lifecycle_prefers_running_process_over_idle_shell` asserted `RunningProcess` but observed `IdleShell`.
|
||||
- Rerun of the specific failing test confirmed deterministic failure: `(cd rust && cargo test -p rusty-claude-cli --bin claw tests::session_lifecycle_prefers_running_process_over_idle_shell -- --exact --nocapture)` → 0 passed, 1 failed with the same `IdleShell` vs `RunningProcess` assertion.
|
||||
|
||||
Recommended owner for failures: not `worker-4` unless re-scoped. These failures are outside the docs/report artifact and touch shared runtime/CLI implementation files.
|
||||
96
docs/g003-boot-session-verification-map.md
Normal file
96
docs/g003-boot-session-verification-map.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# G003 boot/session/preflight verification map
|
||||
|
||||
Generated by `worker-1` for OMX team task 2 on 2026-05-14.
|
||||
|
||||
## Scope and coordination
|
||||
|
||||
- Active goal context: `G003-boot-session` / Stream 1 reliable worker boot and session control.
|
||||
- Boundary: this artifact is an audit/integration map only. It does not mutate `.omx/ultragoal` and it does not change shared implementation or tests.
|
||||
- Current worker split from leader mailbox:
|
||||
- `worker-1`: task 1 worker boot / prompt SLA plus this task 2 audit map.
|
||||
- `worker-2`: default trusted roots / trust resolver.
|
||||
- `worker-3`: startup-no-evidence classifier.
|
||||
- `worker-4`: session control plus preflight/doctor JSON surfaces.
|
||||
- Native subagent probes were attempted for Task 2 (`test probe` and `debug/root-cause probe`) but both failed before returning findings with `429 Too Many Requests`; the map below is based on direct repository inspection.
|
||||
|
||||
## Implementation surface map
|
||||
|
||||
### Worker boot lifecycle and prompt SLA
|
||||
|
||||
- `rust/crates/runtime/src/worker_boot.rs`
|
||||
- Core state types: `WorkerStatus`, `WorkerFailureKind`, `WorkerEventKind`, `WorkerEventPayload`, `StartupFailureClassification`, `StartupEvidenceBundle`, `WorkerTaskReceipt`, and `WorkerReadySnapshot`.
|
||||
- Control plane: `WorkerRegistry::{create,get,observe,resolve_trust,send_prompt,await_ready,restart,terminate,observe_completion,observe_startup_timeout}`.
|
||||
- Lifecycle states currently covered in code: `spawning`, `trust_required`, `tool_permission_required`, `ready_for_prompt`, `running`, `finished`, and `failed`.
|
||||
- Prompt delivery semantics currently use `Running` events and fields `prompt_in_flight`, `last_prompt`, `expected_receipt`, `replay_prompt`, and `prompt_delivery_attempts`.
|
||||
- Startup-no-evidence surface: `observe_startup_timeout` builds `StartupEvidenceBundle` and classifies trust, tool permission, prompt acceptance timeout, prompt misdelivery, transport death, worker crash, or unknown.
|
||||
- File observability surface: `emit_state_file` writes `.claw/worker-state.json` with status, readiness, trust state, prompt-in-flight flag, last event, and update age.
|
||||
|
||||
- `rust/crates/tools/src/lib.rs`
|
||||
- Tool APIs expose the worker control plane through `WorkerCreate`, `WorkerGet`, `WorkerObserve`, `WorkerResolveTrust`, `WorkerAwaitReady`, `WorkerSendPrompt`, `WorkerRestart`, `WorkerTerminate`, and `WorkerObserveCompletion`.
|
||||
- `WorkerCreate` merges `ConfigLoader::trusted_roots()` with per-call `trusted_roots` before calling `WorkerRegistry::create`.
|
||||
- Tool-level tests exercise worker create/observe/send/restart/terminate/completion and state-file transitions.
|
||||
|
||||
### Trust resolver and default trusted roots
|
||||
|
||||
- `rust/crates/runtime/src/trust_resolver.rs`
|
||||
- `TrustConfig`, `TrustAllowlistEntry`, and `TrustResolver` model trust prompts, allowlist/denylist policy, auto-trust, manual approval, and emitted trust events.
|
||||
- `path_matches_trusted_root` and internal `path_matches` canonicalize paths when possible.
|
||||
- Hazard: prefix matching must avoid accidental sibling matches such as `/tmp/work` matching `/tmp/work-evil`; worker-2 owns any changes here.
|
||||
|
||||
- `rust/crates/runtime/src/config.rs`
|
||||
- `trustedRoots` is parsed by `parse_optional_trusted_roots` and exposed through `RuntimeConfig::trusted_roots()` / feature config accessors.
|
||||
- Current default is empty when unset; any project default roots work belongs to worker-2.
|
||||
|
||||
### Session control
|
||||
|
||||
- `rust/crates/runtime/src/session_control.rs`
|
||||
- `SessionStore` namespaces sessions by canonical workspace fingerprint.
|
||||
- Key API: `from_cwd`, `from_data_dir`, `create_handle`, `resolve_reference`, `resolve_managed_path`, `list_sessions`, `latest_session`, `load_session`, and `fork_session`.
|
||||
- Guardrail: `validate_loaded_session` rejects cross-workspace sessions and allows legacy sessions only when their path remains inside the current workspace.
|
||||
- Worker-4 owns changes to this lane.
|
||||
|
||||
### CLI doctor/status/preflight and bootstrap-adjacent surfaces
|
||||
|
||||
- `rust/crates/commands/src/lib.rs`
|
||||
- Slash command definitions include `/status`, `/sandbox`, and `/doctor`.
|
||||
- JSON rendering for command surfaces exists through handler functions and tests in the same module.
|
||||
|
||||
- `rust/crates/tools/src/lib.rs`
|
||||
- Bash and PowerShell tool runners include `workspace_test_branch_preflight`, which returns structured output with `return_code_interpretation: preflight_blocked:branch_divergence` for broad workspace tests on stale branches.
|
||||
- Tests around `bash_workspace_tests_are_blocked_when_branch_is_behind_main` and targeted-test skipping protect this preflight behavior.
|
||||
|
||||
## Existing focused verification commands
|
||||
|
||||
Run from `rust/` unless noted.
|
||||
|
||||
- Worker boot runtime contract:
|
||||
- `cargo test -p runtime worker_boot -- --nocapture`
|
||||
- Worker tool API contract:
|
||||
- `cargo test -p tools worker_ -- --nocapture`
|
||||
- Session control contract:
|
||||
- `cargo test -p runtime session_control -- --nocapture`
|
||||
- Trust resolver/config trusted roots:
|
||||
- `cargo test -p runtime trust_resolver -- --nocapture`
|
||||
- `cargo test -p runtime config::tests::parses_trusted_roots_from_settings config::tests::trusted_roots_default_is_empty_when_unset -- --nocapture`
|
||||
- Preflight/tool branch guardrails:
|
||||
- `cargo test -p tools bash_workspace_tests_are_blocked_when_branch_is_behind_main bash_targeted_tests_skip_branch_preflight -- --nocapture`
|
||||
- Formatting/type/lint baseline:
|
||||
- `../scripts/fmt.sh --check`
|
||||
- `cargo check -p runtime -p tools -p commands`
|
||||
- `cargo clippy -p runtime -p tools -p commands --all-targets --no-deps -- -D warnings`
|
||||
|
||||
## Gaps and hazards for leader integration
|
||||
|
||||
- Prompt SLA event naming is partially implicit: `send_prompt` emits `WorkerEventKind::Running`; it does not expose separate `prompt.sent`, `prompt.accepted`, `prompt.acceptance_delayed`, or `prompt.acceptance_timeout` event names. The current equivalent evidence is `prompt_in_flight`, `Running`, `observe_completion`, and startup-timeout classification.
|
||||
- `StartupFailureClassification::PromptAcceptanceTimeout` is covered in `worker_boot` tests; full terminal/transport integration should still be verified by the leader or worker-3 if a real pane watcher exists outside the in-memory registry.
|
||||
- Default trusted roots are parsed and merged into `WorkerCreate`, but unset config currently means no default roots. Worker-2 owns any change to default root selection.
|
||||
- Session control protects workspace fingerprints at load/fork time; worker-4 owns CLI/doctor/preflight JSON contract changes.
|
||||
- Full-workspace clippy currently has known unrelated runtime findings observed during task 1 verification; do not block this docs-only map on those unless leader re-scopes cleanup.
|
||||
|
||||
## Recommended safe integration order
|
||||
|
||||
1. Integrate worker boot / prompt SLA changes first and run `cargo test -p runtime worker_boot -- --nocapture` plus `cargo test -p tools worker_ -- --nocapture`.
|
||||
2. Integrate trust-root changes and rerun trust/config tests plus the worker create config merge test.
|
||||
3. Integrate startup-no-evidence classifier changes and rerun `cargo test -p runtime worker_boot -- --nocapture`.
|
||||
4. Integrate session control / preflight / doctor JSON changes and rerun session-control, commands JSON, and preflight tests.
|
||||
5. Run final formatting, targeted cargo check/clippy, then broader workspace tests with known full-workspace failures documented separately.
|
||||
67
docs/g004-events-reports-contract.md
Normal file
67
docs/g004-events-reports-contract.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# G004 event and report contract guidance
|
||||
|
||||
Captured: 2026-05-14 during the Stream 2 `G004-events-reports` team run.
|
||||
|
||||
Purpose: keep the user/developer-facing contract guidance for ROADMAP Phase 2 in one tracked source that points back to the code and roadmap anchors. This document is intentionally not the implementation map for task 5; it describes the interoperability contract consumers should rely on as the lane-event, report-schema, approval-token, and capability-negotiation lanes land.
|
||||
|
||||
## Source-of-truth anchors
|
||||
|
||||
| Contract family | Roadmap anchor | Current implementation / owner-facing anchor | Consumer guidance |
|
||||
| --- | --- | --- | --- |
|
||||
| Canonical lane events | `ROADMAP.md` Phase 2 §4, §4.5, §4.6, §4.7 | `rust/crates/runtime/src/lane_events.rs` (`LaneEventName`, `LaneEventStatus`, `LaneEventMetadata`, terminal reconciliation helpers) | Consume `event`, `status`, `emittedAt`, and `metadata` fields as the canonical state stream; do not infer lane state from terminal text when a structured event is present. |
|
||||
| Report schema v1 and projections | `ROADMAP.md` §4.25-§4.34 | Stream 2 report-schema lane / fixtures as they land | Treat a report as a versioned canonical payload plus derived projections. A projection may omit or transform fields only with explicit provenance: compatibility downgrade, redaction policy, truncation, or source absence. |
|
||||
| Policy-blocked handoff and approval-token chain | `ROADMAP.md` §4.37-§4.39 | Stream 2 approval-token lane as it lands | Treat policy blocks and owner approvals as typed artifacts, not prose. Execute an exception only when the approval token matches actor, policy, action, repo/branch/commit scope, expiry, and one-time-use state. |
|
||||
| Capability negotiation | `ROADMAP.md` §4.25, §4.26, §4.32, §4.34 | Report-schema/projection fixtures and consumer conformance cases as they land | Consumers must advertise supported schema versions, optional field families, projection views, redaction semantics, and downgrade handling before relying on reduced payloads. |
|
||||
|
||||
## Lane event contract
|
||||
|
||||
The lane-event stream is the first machine-trustworthy surface for Stream 2. Consumers should expect these invariants when reading `LaneEvent` payloads:
|
||||
|
||||
- `event` is a typed event name, currently including the core lane lifecycle (`lane.started`, `lane.ready`, `lane.blocked`, `lane.red`, `lane.green`, `lane.finished`, `lane.failed`), branch health (`branch.stale_against_main`, `branch.workspace_mismatch`), reconciliation (`lane.reconciled`, `lane.superseded`, `lane.closed`), and ship provenance (`ship.prepared`, `ship.commits_selected`, `ship.merged`, `ship.pushed_main`).
|
||||
- `status` is the normalized state for the event; consumers should prefer it over freeform `detail` text for automation.
|
||||
- `metadata.seq`, `metadata.timestamp_ms`, and terminal fingerprints are the ordering/deduplication hooks. Consumers should use terminal reconciliation output rather than double-reporting contradictory terminal bursts.
|
||||
- `metadata.provenance`, `metadata.environment_label`, `metadata.emitter_identity`, and `metadata.confidence_level` tell consumers whether an event is live lane truth, test traffic, healthcheck/replay output, or transport-layer evidence.
|
||||
- `metadata.session_identity` and `metadata.ownership` bind a lane event to the session, workspace, workflow scope, owner, and watcher action. A watcher should not act on events whose ownership says `observe` or `ignore`.
|
||||
|
||||
Minimal consumer rule: if a structured event exists, pane text is supporting evidence only. Pane scraping must not override a higher-confidence typed event with matching session/workflow ownership.
|
||||
|
||||
## Report schema v1 contract
|
||||
|
||||
A Stream 2 report should be treated as a canonical fact record with optional projections. Consumers should preserve these semantics even when they receive only a downgraded view:
|
||||
|
||||
- Every report payload declares a schema version and a stable report identity/content hash for the full-fidelity canonical payload.
|
||||
- Assertions are labeled as `fact`, `hypothesis`, or another declared evidence class, with confidence and source references. Negative evidence is first-class: `not observed`, `checked and absent`, and `redacted` are distinct states.
|
||||
- Field deltas name the field, previous value/state, new value/state, attribution, and whether the delta came from source content, projection, downgrade, or redaction policy.
|
||||
- Projections carry lineage back to the canonical report id/content hash and name the projection view, capability set, schema version, redaction policy, and deterministic rendering inputs.
|
||||
- Redaction provenance is explicit. A missing field without a redaction/downgrade/source-absence reason is not enough evidence for an automated consumer to conclude the underlying fact is absent.
|
||||
|
||||
Minimal consumer rule: store the canonical identity and projection metadata together. Do not compare two projections as state changes unless their canonical content hash or declared projection inputs differ.
|
||||
|
||||
## Approval-token and policy-blocked contract
|
||||
|
||||
Policy-blocked actions and owner-approved exceptions belong in the same structured event/report family:
|
||||
|
||||
- A policy block names the typed reason, policy source, actor scope, blocked action, and safe fallback path.
|
||||
- An approval token names the approving actor, policy exception, action, repository/worktree/branch/commit scope, expiry, and allowed use count.
|
||||
- Token consumption records the exact action and scope that spent the token. Replays, scope expansion, expired tokens, and revoked tokens should surface typed policy errors.
|
||||
- Delegation traceability stays attached when another worker/lane executes the approved action; the executor must be able to prove which approval artifact authorized the exception.
|
||||
|
||||
Minimal consumer rule: prose such as "approved" is not an executable approval. Require the structured token and verify that it is unconsumed and scoped to the exact action before proceeding.
|
||||
|
||||
## Capability negotiation and conformance
|
||||
|
||||
Mixed-version consumers are expected during Stream 2 rollout. Producers and consumers should negotiate instead of silently dropping fields:
|
||||
|
||||
- Consumers advertise supported report schema versions, field families, projection views, redaction states, downgrade semantics, and fixture/conformance suite version.
|
||||
- Producers preserve one canonical full-fidelity report and emit downgraded projections only with `downgraded_for_compatibility` metadata.
|
||||
- Deterministic projection inputs include schema version, consumer capability set, projection policy version, redaction policy version, and canonical content hash.
|
||||
- Consumer conformance should distinguish syntax acceptance from semantic correctness, especially for `redacted` vs `missing`, stale vs current projections, negative evidence, and approval-token replay states.
|
||||
|
||||
Minimal consumer rule: an older consumer may accept a downgraded projection, but it must surface the downgrade as a capability limitation rather than treating omitted fields as canonical absence.
|
||||
|
||||
## Documentation maintenance rules
|
||||
|
||||
- Keep ROADMAP Phase 2 as the product requirement source and this file as the contract-reading guide.
|
||||
- Keep Rust type names and event names aligned with `rust/crates/runtime/src/lane_events.rs`; update this document in the same change when public event names or metadata semantics change.
|
||||
- Keep report-schema examples/fixtures aligned with this guide once the schema lane lands; fixture updates should explain intentional schema or projection changes.
|
||||
- Do not mutate `.omx/ultragoal` from worker lanes. Leader-owned Ultragoal checkpointing consumes commits and verification evidence from task results.
|
||||
57
docs/g004-events-reports-verification-map.md
Normal file
57
docs/g004-events-reports-verification-map.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# G004 events/reports verification map
|
||||
|
||||
Scope source: OMX team `g004-events-reports-u-e61d2271`, worker-1 tasks 1, 2, 4, 5. Workers must not mutate `.omx/ultragoal`; leader owns aggregate checkpoints.
|
||||
|
||||
## Ownership boundaries
|
||||
|
||||
- **Lane events / event identity / terminal reconciliation** — `rust/crates/runtime/src/lane_events.rs`, exported through `rust/crates/runtime/src/lib.rs`; tool-manifest consumers in `rust/crates/tools/src/lib.rs` write `LaneEvent` vectors.
|
||||
- **Report schema v1 / projection / redaction / capability negotiation** — `rust/crates/runtime/src/report_schema.rs`, exported through `rust/crates/runtime/src/lib.rs`; fixture note at `rust/crates/runtime/tests/fixtures/report_schema_v1/README.md`.
|
||||
- **Approval-token chain** — ROADMAP §§4.38-4.40; owned by worker-2 for this team split. Worker-1 did not edit it.
|
||||
- **Pinpoint closure batch** — runtime hygiene across compact/search-parser/policy/sandbox/integration-test surfaces: `rust/crates/runtime/src/compact.rs`, `rust/crates/runtime/src/file_ops.rs`, `rust/crates/runtime/src/policy_engine.rs`, `rust/crates/runtime/src/sandbox.rs`, `rust/crates/runtime/tests/integration_tests.rs`.
|
||||
- **Regression harness / docs alignment** — worker-3/worker-4 lanes per leader split. Coordinate before editing shared docs/tests.
|
||||
|
||||
## Relevant symbols and files
|
||||
|
||||
- `LaneEventName`, `LaneEventStatus`, `LaneEventMetadata`, `LaneEventBuilder`, `compute_event_fingerprint`, `dedupe_terminal_events`, `reconcile_terminal_events` in `runtime/src/lane_events.rs`.
|
||||
- `CanonicalReportV1`, `ReportClaim`, `NegativeEvidence`, `FieldDelta`, `ConsumerCapabilities`, `ReportProjectionV1`, `canonicalize_report`, `project_report`, `report_schema_v1_registry` in `runtime/src/report_schema.rs`.
|
||||
- `AgentOutput.lane_events`, `persist_agent_terminal_state`, `write_agent_manifest`, `maybe_commit_provenance` in `tools/src/lib.rs`.
|
||||
- Search/parser closure helpers: `summarize_messages` in `compact.rs`, `grep_search_impl` / `build_grep_content_output` in `file_ops.rs`.
|
||||
|
||||
## Completed worker-1 commits
|
||||
|
||||
- `f45f05e` / task 1 auto-checkpoint — terminal event fingerprints use stable SHA-256-derived canonical JSON, and production convenience terminal events attach/refresh fingerprints after payload changes.
|
||||
- `3989fc0` — report schema v1 contract, deterministic projection/redaction provenance, capability negotiation, and fixture note.
|
||||
- `7fff4c4` / task 4 auto-checkpoint — strict runtime clippy closure batch across compact/file_ops/policy/sandbox/integration tests.
|
||||
|
||||
## Current verification evidence
|
||||
|
||||
Run from `rust/` unless noted:
|
||||
|
||||
- `cargo test -p runtime lane_events -- --nocapture` — PASS, 46 lane-event tests.
|
||||
- `cargo test -p runtime report_schema -- --nocapture` — PASS, 4 report-schema tests.
|
||||
- `cargo check -p runtime` — PASS.
|
||||
- `cargo clippy -p runtime --all-targets -- -D warnings` — PASS after task 4 closure batch.
|
||||
- `cargo test -p runtime -- --nocapture` — PASS, 531 unit tests, 12 integration tests, doc-tests pass.
|
||||
- `cargo test -p tools lane_event_schema_serializes_to_canonical_names -- --nocapture` — PASS, 1 targeted tools contract test.
|
||||
|
||||
## Leader integration verification plan
|
||||
|
||||
1. Inspect worker commits: `git log --oneline --decorate --max-count=8`.
|
||||
2. Re-run focused contracts:
|
||||
- `cd rust && cargo test -p runtime lane_events -- --nocapture`
|
||||
- `cd rust && cargo test -p runtime report_schema -- --nocapture`
|
||||
- `cd rust && cargo test -p tools lane_event_schema_serializes_to_canonical_names -- --nocapture`
|
||||
3. Re-run runtime quality gate:
|
||||
- `cd rust && cargo check -p runtime`
|
||||
- `cd rust && cargo clippy -p runtime --all-targets -- -D warnings`
|
||||
- `cd rust && cargo test -p runtime -- --nocapture`
|
||||
4. If merging with worker-2 approval-token work, additionally run the worker-2 focused approval-token tests and check for export conflicts in `runtime/src/lib.rs`.
|
||||
5. If merging with worker-3/4 docs or harness work, re-run their named regression harnesses plus `git diff --check`.
|
||||
|
||||
## Integration hazards
|
||||
|
||||
- `runtime/src/lib.rs` export blocks are shared; resolve conflicts by keeping both lane-event and report-schema exports sorted enough to remain readable.
|
||||
- `tools/src/lib.rs` serializes lane events into agent manifests; terminal fingerprint changes intentionally affect `metadata.event_fingerprint` for finished/failed/superseded/merged/closed events with payloads.
|
||||
- `report_schema.rs` currently defines the reusable contract and in-code deterministic fixtures; it does not yet wire report emission into CLI/status surfaces.
|
||||
- ROADMAP approval-token §§4.38-4.40 remain a separate lane; do not treat worker-1 report schema as an approval artifact.
|
||||
- Full workspace checks may include unrelated slow/provider-dependent tests; the verified local gate for this stream is runtime + targeted tools tests above.
|
||||
40
docs/g005-branch-recovery-verification-map.md
Normal file
40
docs/g005-branch-recovery-verification-map.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# G005 Branch Recovery Verification Map
|
||||
|
||||
Scope: worker-1 follow-up map for G005 branch/test awareness and recovery. This file intentionally does not mutate leader-owned `.omx/ultragoal` state.
|
||||
|
||||
## Covered ROADMAP / PRD pinpoints
|
||||
|
||||
- `ROADMAP.md:912-921` — Phase 3 §7 stale-branch detection before broad verification: broad workspace test commands are preflighted before execution, stale/diverged branches emit `branch.stale_against_main`, and targeted tests bypass the broad-test gate.
|
||||
- `ROADMAP.md:922-933` — Phase 3 §8 recovery recipes: stale-branch recovery remains represented by the `stale_branch` recipe, with one automatic attempt before escalation.
|
||||
- `ROADMAP.md:935-949` — Phase 3 §8.5 recovery attempt ledger: `RecoveryContext` now exposes ledger entries with recipe id, attempt count, state, started/finished markers, last failure summary, and escalation reason.
|
||||
- `ROADMAP.md:951-970` — Phase 3 §9 green-ness / hung-test reporting: timed-out test commands now classify as `test.hung` with structured provenance instead of generic timeout.
|
||||
- `prd.json:37-44` — US-003 stale-branch detection before broad verification: verified through the `workspace_test_branch_preflight` broad-test block and targeted-test bypass tests.
|
||||
- `prd.json:50-57` — US-004 recovery recipes with ledger: verified through recovery ledger unit coverage and serialization-compatible recovery structs.
|
||||
|
||||
## Implementation anchors
|
||||
|
||||
- `rust/crates/runtime/src/stale_branch.rs` — existing branch freshness model and policy actions for fresh, stale, and diverged branches.
|
||||
- `rust/crates/tools/src/lib.rs` — `workspace_test_branch_preflight`, `branch_divergence_output`, Bash/PowerShell broad-test gating, and `test.hung` structured timeout provenance on tool-shell timeouts.
|
||||
- `rust/crates/runtime/src/recovery_recipes.rs` — recovery recipes plus `RecoveryLedgerEntry` / `RecoveryAttemptState` ledger surface.
|
||||
- `rust/crates/runtime/src/bash.rs` — runtime Bash timeout classification and structured provenance for hung test commands.
|
||||
- `rust/crates/runtime/src/lib.rs` — public exports for the recovery ledger types.
|
||||
|
||||
## Verification evidence
|
||||
|
||||
- `cargo test -p runtime` → PASS: 538 unit tests, 2 G004 conformance tests, 12 integration tests, and doctests passed.
|
||||
- `cargo test -p tools bash_tool_classifies_test_timeout_as_hung_with_provenance -- --nocapture` → PASS.
|
||||
- `cargo test -p tools bash_workspace_tests_are_blocked_when_branch_is_behind_main -- --nocapture` → PASS.
|
||||
- `cargo test -p tools bash_targeted_tests_skip_branch_preflight -- --nocapture` → PASS.
|
||||
- `cargo check -p runtime -p tools` → PASS.
|
||||
- `cargo clippy -p runtime --all-targets -- -D warnings` → PASS.
|
||||
- `cargo clippy -p tools --lib --no-deps -- -D warnings` → PASS.
|
||||
|
||||
## Known unresolved / out-of-scope items
|
||||
|
||||
- Full `cargo test -p tools` is still red on six permission-enforcer expectation tests unrelated to G005 branch freshness, recovery ledger, or hung-test classification. The failing tests assert old permission wording/read-only behavior and pre-existed this follow-up scope.
|
||||
- ROADMAP stale-base JSON/doctor/status pinpoints remain broader CLI diagnostic-surface work, especially `ROADMAP.md:2425-2489`, `ROADMAP.md:4346-4431`, and `ROADMAP.md:5061-5086`. They are related to branch freshness, but task 1 only required the broad-test freshness gate and narrow reporting surfaces.
|
||||
- No `.omx/ultragoal` files were changed; leader-owned Ultragoal checkpointing remains outside worker scope.
|
||||
|
||||
## Delegation evidence
|
||||
|
||||
Subagent spawn evidence: 1, Repository map probe `019e25d5-9be9-7193-8a33-f21450beb62c`; spawned before further serial task-2 mapping per contract, but errored with 429 Too Many Requests, so direct repo evidence was integrated instead.
|
||||
34
docs/g006-task-policy-board-verification-map.md
Normal file
34
docs/g006-task-policy-board-verification-map.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# G006 Task Policy Board Verification Map
|
||||
|
||||
Goal: `G006-task-policy-board` — Stream 4 task packets, executable policy engine, lane board/status JSON, and running-state liveness heartbeat.
|
||||
|
||||
## Prompt-to-artifact checklist
|
||||
|
||||
| Requirement | Artifact/evidence |
|
||||
| --- | --- |
|
||||
| Typed task packet schema with objective, scope, files/resources, acceptance criteria, model/provider, permission profile, recovery policy, verification plan, reporting targets | `rust/crates/runtime/src/task_packet.rs` extends `TaskPacket` with `acceptance_criteria`, `resources`, `model`, `provider`, `permission_profile`, `recovery_policy`, `verification_plan`, and `reporting_targets`; tests cover legacy defaulted JSON and rich CC2 roundtrip. |
|
||||
| Backwards compatibility for existing task packets and tool callers | `serde(default)`/optional fields in `task_packet.rs`; `rust/crates/tools/src/lib.rs` `run_task_packet_creates_packet_backed_task` updated for rich schema; legacy packet test keeps old JSON accepted. |
|
||||
| Executable policy decisions for retry/rebase/merge/escalate/stale cleanup/approval token | `rust/crates/runtime/src/policy_engine.rs` adds `RetryAvailable`, `RebaseRequired`, `StaleCleanupRequired`, approval-token conditions/actions, `PolicyEvaluation`, `PolicyDecisionEvent`, and decision-table tests. |
|
||||
| Policy decisions explainable and typed-event logged/emittable | `PolicyDecisionEvent` serializable typed event with `rule_name`, `priority`, `kind`, `explanation`, `approval_token_id`; `evaluate_with_events` emits event per flattened action. |
|
||||
| Active lane board/dashboard/status JSON over canonical state | `rust/crates/runtime/src/task_registry.rs` adds `LaneBoard`, `LaneBoardEntry`, `LaneFreshness`, `lane_board_at`, and `lane_status_json_at`; CLI status JSON advertises lane board contract in `rust/crates/rusty-claude-cli/src/main.rs`. |
|
||||
| Heartbeats independent of terminal rendering with healthy/stalled/transport-dead cases | `rust/crates/runtime/src/session.rs` adds `SessionHeartbeat`/`SessionLiveness` from persisted session health state; `task_registry.rs` heartbeat freshness is computed from canonical heartbeat timestamps and transport state. |
|
||||
| Task/lane status JSON shows active/blocked/finished lanes with heartbeat freshness | `task_registry::tests::lane_board_groups_active_blocked_finished_and_reports_freshness`; `status_json_surfaces_session_lifecycle_for_clawhip`/status JSON surfaces lane board metadata. |
|
||||
| Leader-owned ultragoal audit remains separate from workers | No worker changed `.omx/ultragoal`; leader will checkpoint with fresh `get_goal` only after terminal verification. |
|
||||
|
||||
## Verification run
|
||||
|
||||
- `git diff --check` — PASS
|
||||
- `cargo fmt --manifest-path rust/Cargo.toml --all -- --check` — PASS
|
||||
- `cargo check --manifest-path rust/Cargo.toml -p runtime -p tools -p rusty-claude-cli` — PASS
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p runtime task_packet -- --nocapture` — PASS (5 task packet tests)
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p runtime policy_engine -- --nocapture` — PASS (12 unit + 1 integration match)
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p runtime task_registry -- --nocapture` — PASS (17 task registry tests)
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p runtime session_heartbeat -- --nocapture` — PASS (1 heartbeat test)
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p tools run_task_packet_creates_packet_backed_task -- --nocapture` — PASS
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p tools lane_completion -- --nocapture` — PASS (6 tests)
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli status_json_surfaces -- --nocapture` — PASS
|
||||
|
||||
## Remaining gates
|
||||
|
||||
- G006 can be checkpointed after team lifecycle is reconciled terminal and this commit is pushed.
|
||||
- Open PR/issue reconciliation remains explicitly deferred to G011/G012 via `docs/pr-issue-resolution-gate.md`.
|
||||
55
docs/g007-mcp-lifecycle-mapping.md
Normal file
55
docs/g007-mcp-lifecycle-mapping.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# G007 MCP Lifecycle Mapping
|
||||
|
||||
This map captures the current MCP/plugin lifecycle implementation surfaces for the
|
||||
G007 plugin/MCP maturity lane. It is intentionally evidence-oriented: each row
|
||||
names the runtime surface, the code owner boundary, and the current gap when the
|
||||
surface is metadata-only.
|
||||
|
||||
## Degraded MCP startup
|
||||
|
||||
| Concern | Current surface | Notes |
|
||||
| --- | --- | --- |
|
||||
| Best-effort discovery | `rust/crates/runtime/src/mcp_stdio.rs` (`McpServerManager::discover_tools_best_effort`) | Discovers every configured stdio server, keeps tools from working servers, and records per-server failures without aborting the whole startup. |
|
||||
| Failure payload | `rust/crates/runtime/src/mcp_stdio.rs` (`McpDiscoveryFailure`, `UnsupportedMcpServer`) | Failure records include `server_name`, lifecycle `phase`, `required`, `error`, `recoverable`, and structured `context`. Unsupported non-stdio servers keep `transport`, `required`, and `reason`. |
|
||||
| Degraded report model | `rust/crates/runtime/src/mcp_lifecycle_hardened.rs` (`McpDegradedReport`, `McpFailedServer`, `McpErrorSurface`) | Normalizes degraded startup into working servers, failed servers, available tools, and missing tools. `McpErrorSurface` carries phase, server, message, context, and recoverability. |
|
||||
| CLI runtime handoff | `rust/crates/rusty-claude-cli/src/main.rs` (`RuntimeMcpState::new`) | Converts discovery failures and unsupported servers into a runtime degraded report, including `required` in the error context. |
|
||||
|
||||
## Required vs. optional MCP servers
|
||||
|
||||
| Concern | Current surface | Notes |
|
||||
| --- | --- | --- |
|
||||
| Config contract | `rust/crates/runtime/src/config.rs` (`ScopedMcpServerConfig.required`) | `mcpServers.<name>.required` parses as a boolean and defaults to `false`; invalid non-boolean values are rejected by the shared optional-bool parser. |
|
||||
| Scope merge | `rust/crates/runtime/src/config.rs` (`merge_mcp_servers`) | Requiredness is stored beside the scope and transport-specific config after normal user/project/local merging. |
|
||||
| Inventory/reporting | `rust/crates/commands/src/lib.rs` (`mcp_server_json`, `render_mcp_server_report`) | JSON reports expose `server.required`; text `show` reports include `Required`. |
|
||||
| Discovery propagation | `rust/crates/runtime/src/mcp_stdio.rs` | Requiredness is copied into managed stdio servers, unsupported server records, discovery failures, and degraded startup context. |
|
||||
| Cache/signature identity | `rust/crates/runtime/src/mcp.rs` (`scoped_mcp_config_hash`) | The hash includes `required:<bool>` so required/optional changes affect MCP config identity. |
|
||||
| Remaining policy gap | runtime behavior | The flag is currently surfaced and propagated as lifecycle metadata. It does not yet fail the whole runtime/session solely because a required server failed; consumers must inspect the degraded report context today. |
|
||||
|
||||
## Config interpolation and redaction surfaces
|
||||
|
||||
| Concern | Current surface | Notes |
|
||||
| --- | --- | --- |
|
||||
| Raw config parsing | `rust/crates/runtime/src/config.rs` (`parse_mcp_server_config`, `parse_mcp_remote_server_config`) | `command`, `args`, `url`, `headers`, and `headersHelper` are loaded as literal strings. No dedicated environment, tilde, or workspace-root interpolation pass is present in this parser. |
|
||||
| Redacted key reporting | `rust/crates/commands/src/lib.rs` (`mcp_server_details_json`, `render_mcp_server_report`) | Stdio env and remote/websocket header values are not printed; only `env_keys` / `Header keys` are surfaced. |
|
||||
| Unredacted reporting risk | `rust/crates/commands/src/lib.rs` (`mcp_server_summary`, `mcp_server_details_json`, text `show`) | Command, args, URL, `headers_helper`, OAuth metadata URL/client id, and managed proxy URL/id are currently emitted verbatim. Treat these fields as not-redacted unless a future policy layer classifies them safe. |
|
||||
| OAuth exposure | `rust/crates/commands/src/lib.rs` (`mcp_oauth_json`, `format_mcp_oauth`) | OAuth secret-like values are mostly absent from the current config model, but client id and metadata URL are still reported directly. |
|
||||
|
||||
## Plugin lifecycle contract adjacency
|
||||
|
||||
| Concern | Current surface | Notes |
|
||||
| --- | --- | --- |
|
||||
| Manifest lifecycle | `rust/crates/plugins/src/lib.rs` (`PluginLifecycle`) | Plugin manifests support `lifecycle.Init` and `lifecycle.Shutdown` command arrays. |
|
||||
| Registry summary | `rust/crates/plugins/src/lib.rs` (`PluginSummary::lifecycle_state`) | Installed summaries include enabled state, lifecycle commands, and derived lifecycle state (`ready` or `disabled`). Load failures remain first-class in registry reports. |
|
||||
| CLI JSON output | `rust/crates/rusty-claude-cli/src/main.rs` (`plugin_command_json`) | Plugin command JSON emits top-level `status`, per-plugin `lifecycle_state` and lifecycle command counts, plus `load_failures` with `lifecycle_state: load_failed`. |
|
||||
|
||||
## Verification anchors
|
||||
|
||||
The current regression anchors for this map are:
|
||||
|
||||
- `cargo test -p runtime parses_typed_mcp_and_oauth_config -- --nocapture`
|
||||
- `cargo test -p runtime manager_discovery_report_keeps_healthy_servers_when_one_server_fails -- --nocapture`
|
||||
- `cargo test -p runtime manager_records_unsupported_non_stdio_servers_without_panicking -- --nocapture`
|
||||
- `cargo test -p commands renders_mcp_reports -- --nocapture`
|
||||
- `cargo test -p plugins installed_plugin_registry_report_collects_load_failures_from_install_root -- --nocapture`
|
||||
- `cargo test -p rusty-claude-cli --test output_format_contract plugins_json_surfaces_lifecycle_contract_when_plugin_is_installed -- --nocapture`
|
||||
|
||||
54
docs/g007-plugin-mcp-verification-map.md
Normal file
54
docs/g007-plugin-mcp-verification-map.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# G007 Plugin/MCP Lifecycle Verification Map
|
||||
|
||||
Goal: `G007-plugin-mcp` — Stream 5 plugin/MCP lifecycle maturity from ROADMAP Phase 5.
|
||||
|
||||
Scope: worker-2 follow-up map for W4 mock integration and regression verification. This file intentionally does not mutate leader-owned `.omx/ultragoal` state.
|
||||
|
||||
## Covered ROADMAP / CC2 anchors
|
||||
|
||||
- `ROADMAP.md:55-57` — Current pain point §6: plugin/MCP startup failures, handshake failures, config errors, partial startup, and degraded mode need clean classification.
|
||||
- `ROADMAP.md:67` — Product principle §5: MCP partial success must be first-class and structurally report successful and failed servers.
|
||||
- `ROADMAP.md:1033-1059` — Phase 5: first-class plugin/MCP lifecycle contract and MCP end-to-end lifecycle parity.
|
||||
- `.omx/cc2/board.md` Stream 5 active headings: `CC2-RM-H0010`, `CC2-RM-H0080`, `CC2-RM-H0081`, and `CC2-RM-H0082` remain the goal-level source-of-truth anchors for plugin/MCP lifecycle maturity.
|
||||
- `PARITY.md` harness checklist: mock parity scenarios are the executable regression surface for streamed model turns, plugin tool roundtrips, permissions, compaction metadata, and token/cost output.
|
||||
|
||||
## Mock integration anchors
|
||||
|
||||
| Area | Artifact/evidence |
|
||||
| --- | --- |
|
||||
| Deterministic model server | `rust/crates/mock-anthropic-service/src/lib.rs` implements the Anthropic-compatible mock server and scenario router used by CLI parity tests. |
|
||||
| End-to-end CLI mock harness | `rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs` starts the mock server, runs clean-environment `claw` commands, asserts JSON output, and optionally writes a machine-readable report via `MOCK_PARITY_REPORT_PATH`. |
|
||||
| Scenario manifest / docs parity guard | `rust/mock_parity_scenarios.json` is required to stay ordered with harness cases; `rust/scripts/run_mock_parity_diff.py --no-run` verifies every manifest `parity_refs[]` string exists in `PARITY.md`. |
|
||||
| Convenience runner | `rust/scripts/run_mock_parity_harness.sh` runs `cargo test -p rusty-claude-cli --test mock_parity_harness -- --nocapture`. |
|
||||
| Plugin-path regression | `plugin_tool_roundtrip` loads an external plugin fixture from isolated settings and executes `plugin_echo` through the runtime tool registry. |
|
||||
| Lifecycle-adjacent regression | `auto_compact_triggered` and `token_cost_reporting` prove runtime JSON keeps compaction and usage/cost fields parseable under mock responses, preventing parity drift in machine-readable output. |
|
||||
| MCP degraded-startup regression | `rust/crates/runtime/src/mcp_stdio.rs::manager_discovery_report_keeps_healthy_servers_when_one_server_fails` proves a healthy MCP server remains callable while a broken peer is surfaced in a structured degraded report. |
|
||||
| Plugin lifecycle state regression | `rust/crates/runtime/src/plugin_lifecycle.rs` unit tests cover healthy, degraded, failed, and shutdown states plus startup-event mapping. |
|
||||
|
||||
## Regression verification commands
|
||||
|
||||
Use the smallest command that proves the changed or audited surface, then broaden only when integration risk requires it.
|
||||
|
||||
- Mock scenario/docs map only:
|
||||
- `cd rust && python3 scripts/run_mock_parity_diff.py --no-run`
|
||||
- Full mock integration:
|
||||
- `cd rust && cargo test -p rusty-claude-cli --test mock_parity_harness -- --nocapture`
|
||||
- `cd rust && python3 scripts/run_mock_parity_diff.py`
|
||||
- Plugin/MCP lifecycle contract:
|
||||
- `cd rust && cargo test -p runtime plugin_lifecycle -- --nocapture`
|
||||
- `cd rust && cargo test -p runtime mcp_stdio::tests::manager_discovery_report_keeps_healthy_servers_when_one_server_fails -- --exact --nocapture`
|
||||
- Standard Rust gates for implementation changes touching these surfaces:
|
||||
- `cd rust && cargo fmt --all -- --check`
|
||||
- `cd rust && cargo check -p runtime -p rusty-claude-cli -p mock-anthropic-service`
|
||||
- `cd rust && cargo clippy -p runtime --all-targets -- -D warnings`
|
||||
|
||||
## Known gaps / follow-ups
|
||||
|
||||
- The mock parity harness validates plugin tool execution but does not yet spin up a real MCP stdio server through the CLI prompt path; MCP degraded-startup remains covered by runtime manager tests.
|
||||
- Worker-4 owns the plugin command fallthrough regression implementation lane (`task-10`); this map records the verification/docs boundary and should not duplicate that parser work.
|
||||
- Full `cargo clippy -p runtime --all-targets -- -D warnings` can be blocked by unrelated `policy_engine.rs` clippy violations in this worktree; when that happens, report the exact pre-existing diagnostics and keep focused lifecycle tests green.
|
||||
- No `.omx/ultragoal` files were changed; leader-owned Ultragoal checkpointing remains outside worker scope.
|
||||
|
||||
## Delegation evidence
|
||||
|
||||
Subagent spawn evidence: Task 9 spawned repository map probe `019e291d-e700-7171-b7bc-27ec0f6c850f`, debug/root-cause probe `019e291d-e86f-78d0-a137-214ede03285c`, and test/docs probe `019e291e-135c-79e1-80d0-9fd82866bd6e` before deeper local inspection. The repository-map probe errored with 429; the remaining probes did not return before the local verification map was grounded from repo evidence, so direct findings above were integrated.
|
||||
89
docs/g009-windows-docs-release-verification-map.md
Normal file
89
docs/g009-windows-docs-release-verification-map.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# G009 Windows docs/release readiness verification map
|
||||
|
||||
## Scope and source
|
||||
|
||||
This map ties the Stream 8 acceptance target from `.omx/plans/claw-code-2-0-adaptive-plan.md` to repository artifacts and local verification. It is the worker-1 integration lane artifact; it does not mutate `.omx/ultragoal` and avoids duplicating peer implementation lanes for Windows CI, install/provider docs, and policy/link work.
|
||||
|
||||
Stream 8 source requirement summary:
|
||||
|
||||
- PowerShell-first docs and CLI examples.
|
||||
- Safe provider switching examples.
|
||||
- Staged packaging path: source-only alpha first, binary release matrix next, package managers later.
|
||||
- Windows smoke CI for help/doctor/config/status without live credentials.
|
||||
- License, contribution, security, and support policies.
|
||||
- Command/link validation for adoption docs.
|
||||
|
||||
## Acceptance-to-evidence matrix
|
||||
|
||||
| Acceptance area | Repository artifact(s) | Verification command(s) | Notes |
|
||||
|---|---|---|---|
|
||||
| PowerShell-first Windows install/run path | `README.md` (`Windows setup`, post-build binary location, PowerShell `.exe` examples); `install.sh` (Unix/WSL installer guard) | `python3 .github/scripts/check_doc_source_of_truth.py`; `cargo run -p rusty-claude-cli -- --help` | Current docs explicitly present Windows as a supported PowerShell path for source builds and `claw.exe`; `install.sh` is Linux/macOS/WSL-oriented, so native PowerShell binary usage and WSL installer usage must stay clearly separated. |
|
||||
| Safe provider switching examples | `USAGE.md` (`Auth`, `Local Models`, `Supported Providers & Models`); `docs/MODEL_COMPATIBILITY.md` | `cargo test -p api providers::`; `cargo test -p rusty-claude-cli --test output_format_contract provider_diagnostics_explain_openai_compatible_capabilities -- --nocapture` | Provider docs cover Anthropic API-key vs bearer-token shape, OpenAI-compatible routing, Ollama/OpenRouter/DashScope examples, and prefix routing to avoid ambient credential misrouting. |
|
||||
| Release artifact quickstart and staged packaging path | `README.md` (`Quick start`, `Post-build: locate the binary and verify`); `.github/workflows/release.yml`; `docs/windows-install-release.md` | `cargo build --release -p rusty-claude-cli`; `cargo run -p rusty-claude-cli -- version --output-format json`; `python3 .github/scripts/check_release_readiness.py (release-readiness gate)` | Release workflow packages Linux, macOS, and `claw-windows-x64.exe` assets with `.sha256` checksum files. README remains source-build-first, and the Windows quickstart names the checksum verification path. |
|
||||
| Windows smoke CI without live credentials | `.github/workflows/rust-ci.yml`; CLI local-only surfaces in `rust/crates/rusty-claude-cli/src/main.rs` (`help`, `doctor`, resumed `/config`, `status`) | `cargo run -p rusty-claude-cli -- --help`; `cargo run -p rusty-claude-cli -- doctor --output-format json`; `cargo run -p rusty-claude-cli -- status --output-format json`; `cargo run -p rusty-claude-cli -- config --output-format json` | The smoke target is local-only command execution with isolated config and no real provider credentials. If the Windows CI lane is not present in a branch, this map is the integration checklist for that lane. |
|
||||
| License metadata | `rust/Cargo.toml` (`workspace.package.license = "MIT"`) | `grep -n '^license = "MIT"' rust/Cargo.toml` | Cargo metadata declares MIT. A root `LICENSE` file remains the user-facing policy artifact to add if not already present in the policy lane. |
|
||||
| Contribution/security/support policies | Expected root policy docs: `CONTRIBUTING.md`, `SECURITY.md`, `SUPPORT.md`; existing support links in `README.md` | `test -f CONTRIBUTING.md`; `test -f SECURITY.md`; `test -f SUPPORT.md`; `python3 .github/scripts/check_doc_source_of_truth.py` | These files are policy-lane outputs. This map records the exact release gate so missing files fail visibly instead of being inferred from README links. |
|
||||
| Command/link validation | `.github/scripts/check_doc_source_of_truth.py`; `README.md`; `USAGE.md`; `docs/**` | `python3 .github/scripts/check_doc_source_of_truth.py`; `python3 - <<'PY' ...` link/reference check listed below | Existing validation catches stale branding/assets/invites across adoption docs. The lightweight reference check below catches broken relative Markdown links without network access. |
|
||||
|
||||
## Windows/local smoke command contract
|
||||
|
||||
Use isolated config and no live credentials. These commands must not require `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `XAI_API_KEY`, or `DASHSCOPE_API_KEY`:
|
||||
|
||||
```powershell
|
||||
# From repository root on Windows PowerShell
|
||||
$env:CLAW_CONFIG_HOME = Join-Path $env:TEMP "claw-smoke-config"
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\ANTHROPIC_AUTH_TOKEN -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\XAI_API_KEY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\DASHSCOPE_API_KEY -ErrorAction SilentlyContinue
|
||||
cd rust
|
||||
cargo run -p rusty-claude-cli -- --help
|
||||
cargo run -p rusty-claude-cli -- doctor --output-format json
|
||||
cargo run -p rusty-claude-cli -- status --output-format json
|
||||
cargo run -p rusty-claude-cli -- config --output-format json
|
||||
```
|
||||
|
||||
Equivalent Unix smoke used by this worker:
|
||||
|
||||
```bash
|
||||
env -u ANTHROPIC_API_KEY -u ANTHROPIC_AUTH_TOKEN -u OPENAI_API_KEY -u XAI_API_KEY -u DASHSCOPE_API_KEY \
|
||||
CLAW_CONFIG_HOME="$(mktemp -d)" cargo run -p rusty-claude-cli -- --help
|
||||
```
|
||||
|
||||
## Offline Markdown reference check
|
||||
|
||||
```bash
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import re, sys
|
||||
root = Path.cwd()
|
||||
errors = []
|
||||
for path in [Path('README.md'), Path('USAGE.md'), Path('PARITY.md'), Path('PHILOSOPHY.md'), *Path('docs').glob('*.md')]:
|
||||
if not path.exists():
|
||||
continue
|
||||
text = path.read_text(encoding='utf-8')
|
||||
for match in re.finditer(r'\[[^\]]+\]\(([^)]+)\)', text):
|
||||
target = match.group(1).split('#', 1)[0]
|
||||
if not target or '://' in target or target.startswith('mailto:'):
|
||||
continue
|
||||
if not (root / path.parent / target).resolve().exists():
|
||||
line = text.count('\n', 0, match.start()) + 1
|
||||
errors.append(f'{path}:{line}: missing relative link target {match.group(1)}')
|
||||
if errors:
|
||||
print('\n'.join(errors))
|
||||
sys.exit(1)
|
||||
print('offline markdown reference check passed')
|
||||
PY
|
||||
```
|
||||
|
||||
## Release gate
|
||||
|
||||
A Stream 8 release candidate is ready when all of the following are true:
|
||||
|
||||
1. PowerShell examples in `README.md` build and run `claw.exe` from a clean Windows checkout.
|
||||
2. Provider examples in `USAGE.md` show session-local/shell-local switching, include cleanup for conflicting ambient credentials (`unset` / `Remove-Item Env:`), and never instruct users to paste secrets into persistent config by default.
|
||||
3. Windows smoke CI runs help/doctor/config/status without live credentials, separates native PowerShell `claw.exe` smoke from WSL `install.sh` smoke, and archives JSON output on failure.
|
||||
4. Release artifacts include the documented platform matrix or the docs clearly state source-only alpha status.
|
||||
5. `LICENSE`, `CONTRIBUTING.md`, `SECURITY.md`, and `SUPPORT.md` exist or the policy lane records an explicit release-blocking exception.
|
||||
6. Doc source-of-truth and offline relative-link validation pass.
|
||||
62
docs/g010-clone-disambiguation-metadata.md
Normal file
62
docs/g010-clone-disambiguation-metadata.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# G010 clone disambiguation metadata and verification map
|
||||
|
||||
Scope: worker-2 task 5 for `G010-session-hygiene` / Stream 9 session hygiene, local state, and recovery UX. This artifact maps the clone/worktree disambiguation contract and the focused verification surface without mutating leader-owned `.omx/ultragoal` state.
|
||||
|
||||
## Contract summary
|
||||
|
||||
Claw session state is intentionally scoped to the current workspace clone/worktree. Operators and automation should treat the **session partition**, not a bare session id or the flat `.claw/sessions/` directory, as the identity boundary.
|
||||
|
||||
Required metadata and behaviors:
|
||||
|
||||
- **Workspace-bound partition**: managed sessions live under `.claw/sessions/<workspace_fingerprint>/`, where the fingerprint is a stable 16-character FNV-1a digest of the canonical workspace path.
|
||||
- **Canonical path input**: `SessionStore::from_cwd` and `SessionStore::from_data_dir` canonicalize their workspace path before computing the partition, preventing `/tmp/foo` vs `/private/tmp/foo` and relative-vs-absolute spelling from creating two stores for the same clone.
|
||||
- **Clone/worktree isolation**: two distinct clones or worktrees must get different session partitions, even if session ids collide.
|
||||
- **Legacy safety**: flat legacy sessions under `.claw/sessions/` remain readable only when they are bound to the same workspace or are unbound but physically inside the current workspace; sessions whose persisted `workspace_root` points at another clone are rejected as `WorkspaceMismatch`.
|
||||
- **Fork lineage stays local**: `/session fork` / managed session forking keeps the forked session in the same workspace partition and records parent id plus optional branch name.
|
||||
- **User-facing disambiguation**: empty-session copy names the actual fingerprint directory and explains that sessions from other CWDs are intentionally invisible.
|
||||
|
||||
## Implementation anchors
|
||||
|
||||
| Contract area | Repo anchor | Evidence role |
|
||||
| --- | --- | --- |
|
||||
| Partition layout and canonical workspace root | `rust/crates/runtime/src/session_control.rs:10-18`, `:32-47`, `:54-71` | Documents and implements `.claw/sessions/<workspace_hash>/` for `from_cwd` and explicit data-dir stores. |
|
||||
| Fingerprint algorithm | `rust/crates/runtime/src/session_control.rs:300-312` | Defines the 16-character FNV-1a workspace fingerprint used as the clone disambiguator. |
|
||||
| Managed create/resolve/list/load/fork APIs | `rust/crates/runtime/src/session_control.rs:86-204` | Ensures handles, `latest`, load, and fork resolve inside the active partition. |
|
||||
| Legacy/cross-workspace guard | `rust/crates/runtime/src/session_control.rs:213-233`, `:557-567` | Rejects mismatched persisted `workspace_root` and allows only same-workspace legacy files. |
|
||||
| Empty partition copy | `rust/crates/runtime/src/session_control.rs:535-543` | Reports `.claw/sessions/<fingerprint>/` plus the workspace-partition note. |
|
||||
| CLI wrapper | `rust/crates/rusty-claude-cli/src/main.rs:5952-6040` | Routes session CLI helpers through `current_session_store()`, so CLI list/latest/load uses the same partition. |
|
||||
| CLI session-list lifecycle context | `rust/crates/rusty-claude-cli/src/main.rs:5991-6027`, `:12960-12990` | Renders saved-only/dirty/abandoned lifecycle context for the current partition. |
|
||||
| CLI session resolution regression | `rust/crates/rusty-claude-cli/src/main.rs:13470-13579` | Covers JSONL default, legacy flat resolution, latest selection, and workspace mismatch rejection from CLI wrappers. |
|
||||
|
||||
## Covered roadmap and dogfood anchors
|
||||
|
||||
- `ROADMAP.md:1125-1129` — session files are namespaced by workspace fingerprint, and wrong-workspace session access is rejected.
|
||||
- `ROADMAP.md:1419-1441` — empty/missing session messages must expose the fingerprint directory instead of implying a flat `.claw/sessions/` search.
|
||||
- `ROADMAP.md:1453-1476` — the session partition boundary must be visible or shared deliberately; current contract is visible CWD/workspace partitioning.
|
||||
- `ROADMAP.md:5797-5902` — canonicalization closes the symlink/path-equivalence split in workspace fingerprints.
|
||||
- `ROADMAP.md:6342-6366` and `ROADMAP.md:6384-6411` — remaining Stream 9 risks around reported CWD form, failed-resume filesystem side effects, and broad-CWD resume guards are related UX/recovery lanes, not clone identity itself.
|
||||
|
||||
## Focused verification map
|
||||
|
||||
| Claim | Focused check |
|
||||
| --- | --- |
|
||||
| Same canonical workspace spellings share one partition | `cargo test --manifest-path rust/Cargo.toml -p runtime session_store_from_cwd_canonicalizes_equivalent_paths -- --nocapture` |
|
||||
| Distinct clones/worktrees do not see each other's sessions | `cargo test --manifest-path rust/Cargo.toml -p runtime session_store_from_cwd_isolates_sessions_by_workspace -- --nocapture` |
|
||||
| Explicit data-dir stores still namespace by workspace | `cargo test --manifest-path rust/Cargo.toml -p runtime session_store_from_data_dir_namespaces_by_workspace -- --nocapture` |
|
||||
| Same-workspace legacy sessions are readable; cross-workspace ones are rejected | `cargo test --manifest-path rust/Cargo.toml -p runtime session_store_rejects_legacy_session_from_other_workspace session_store_loads_safe_legacy_session_from_same_workspace session_store_loads_unbound_legacy_session_from_same_workspace -- --nocapture` |
|
||||
| `latest` and managed reference resolution stay inside the active partition | `cargo test --manifest-path rust/Cargo.toml -p runtime session_store_latest_and_resolve_reference -- --nocapture` and `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli latest_session_alias_resolves_most_recent_managed_session -- --nocapture` |
|
||||
| Forks retain partition and lineage metadata | `cargo test --manifest-path rust/Cargo.toml -p runtime session_store_fork_stays_in_same_namespace -- --nocapture` |
|
||||
| CLI wrapper rejects wrong-workspace files | `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli load_session_reference_rejects_workspace_mismatch -- --nocapture` |
|
||||
| Docs-only map is syntactically clean | `git diff --check` |
|
||||
| Broader type/test gate for the touched domain | `cargo check --manifest-path rust/Cargo.toml -p runtime -p rusty-claude-cli` plus `cargo test --manifest-path rust/Cargo.toml -p runtime session_control -- --nocapture` |
|
||||
|
||||
## Known boundaries and integration notes
|
||||
|
||||
- This worker intentionally did **not** edit `docs/g010-session-hygiene-verification-map.md` because worker-4 task 7 also names that final aggregate map. This file is the worker-2 clone-disambiguation map that worker-4/leader can link or merge into the aggregate map.
|
||||
- The current `SessionStore::from_cwd` contract keys on the canonical current directory, not necessarily the git top-level. That is acceptable only if status/help surfaces keep the partition boundary visible; `ROADMAP.md:1453-1476` remains the product tradeoff record.
|
||||
- Failed-resume directory creation and broad-CWD guards are related session hygiene hazards but are owned by the Stream 9 CLI/recovery lanes, not this docs-only clone-disambiguation task.
|
||||
- No `.omx/ultragoal` files were changed; leader-owned aggregate checkpointing consumes this commit and task lifecycle evidence.
|
||||
|
||||
## Delegation evidence
|
||||
|
||||
Subagent spawn evidence: 1, repository map probe `019e295d-a3dc-7041-bc96-30ee52b95698`; spawned before deeper serial mapping per task contract, but it errored with `429 Too Many Requests`, so direct repo evidence above was integrated instead.
|
||||
21
docs/g010-session-hygiene-verification-map.md
Normal file
21
docs/g010-session-hygiene-verification-map.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# G010 Session Hygiene Verification Map
|
||||
|
||||
Stream 9 session hygiene is implemented in the Rust runtime/CLI as workspace-scoped session storage plus resume-safe recovery commands.
|
||||
|
||||
## Acceptance mapping
|
||||
|
||||
| Acceptance area | Code surface | Evidence |
|
||||
| --- | --- | --- |
|
||||
| Generated session files are not accidentally committed | `.gitignore`, `rust/.gitignore` ignore `.claw/sessions/` and `.claude/sessions/` | `git check-ignore .claw/sessions/example.jsonl rust/.claw/sessions/example.jsonl .claude/sessions/example.json` |
|
||||
| Per-worktree session isolation | `rust/crates/runtime/src/session_control.rs` (`SessionStore`, `workspace_fingerprint`, workspace validation) | `cargo test -p runtime session_store_from_cwd_isolates_sessions_by_workspace` |
|
||||
| List/resume/delete/exists contracts | `rust/crates/commands/src/lib.rs` parses `/session list`, `/session exists`, `/session delete`, `/resume`; `rust/crates/rusty-claude-cli/src/main.rs` renders text/JSON resume-safe session commands | `cargo test -p rusty-claude-cli session_exists_resume_command_reports_json_contract`; `cargo test -p rusty-claude-cli resume_report_uses_sectioned_layout` |
|
||||
| Compact and provider context-window recovery | `rust/crates/runtime/src/compact.rs`; `rust/crates/rusty-claude-cli/src/main.rs` context-window error recovery guidance and resumed `/compact` | `cargo test -p rusty-claude-cli provider_context_window_errors_are_reframed_with_same_guidance`; `cargo test -p commands compacts_sessions_via_slash_command` |
|
||||
| JSONL bloat safeguards | `rust/crates/runtime/src/session.rs` rotates oversized JSONL session files and keeps bounded rotated logs | `cargo test -p runtime rotates_and_cleans_up_large_session_logs` |
|
||||
| Interrupt/recovery path | `rust/crates/rusty-claude-cli/src/main.rs` keeps `/clear --confirm`, `/compact`, `/status`, and `/resume latest` resume-safe for unusable threads | `cargo test -p rusty-claude-cli context_window_preflight_errors_render_recovery_steps`; `cargo test -p rusty-claude-cli parses_resume_flag_with_multiple_slash_commands` |
|
||||
| Clone/session disambiguation | `Session` persists `workspace_root`; forks persist parent/branch metadata; session list shows lineage and lifecycle | `cargo test -p runtime persists_workspace_root_round_trip_and_forks_inherit_it`; `cargo test -p runtime forks_sessions_with_branch_metadata_and_persists_it` |
|
||||
|
||||
## Notes for leader audit
|
||||
|
||||
- Workers did not mutate `.omx/ultragoal`; this file is a repo-local verification map for team evidence only.
|
||||
- Runtime-owned session state remains under ignored `.claw/sessions/<workspace-fingerprint>/` paths.
|
||||
- Resume-safe JSON output uses stable `kind` fields (`restored`, `compact`, `session_list`, `session_exists`, etc.) so claws can route without scraping text.
|
||||
68
docs/g011-acp-json-rpc-status-contract.md
Normal file
68
docs/g011-acp-json-rpc-status-contract.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# G011 ACP/Zed and JSON-RPC status contract
|
||||
|
||||
Claw Code 2.0 keeps ACP/Zed and JSON-RPC serving behind the stable task,
|
||||
session-control, and event/report contracts from the roadmap. The current public
|
||||
surface is therefore a **truthful unsupported status**, not a hidden daemon.
|
||||
|
||||
## Supported status queries
|
||||
|
||||
The following commands are status queries and exit with code `0`:
|
||||
|
||||
```bash
|
||||
claw acp
|
||||
claw acp serve
|
||||
claw --acp
|
||||
claw -acp
|
||||
claw acp --output-format json
|
||||
claw acp serve --output-format json
|
||||
```
|
||||
|
||||
`serve` is deliberately an alias for status today. It does not bind a socket,
|
||||
start a daemon, or expose a JSON-RPC endpoint.
|
||||
|
||||
## JSON envelope
|
||||
|
||||
`claw acp --output-format json` returns a stable envelope for editor probes and
|
||||
CI checks:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"kind": "acp",
|
||||
"status": "unsupported",
|
||||
"phase": "discoverability_only",
|
||||
"supported": false,
|
||||
"exit_code": 0,
|
||||
"serve_alias_only": true,
|
||||
"protocol": {
|
||||
"name": "ACP/Zed",
|
||||
"json_rpc": false,
|
||||
"daemon": false,
|
||||
"endpoint": null,
|
||||
"serve_starts_daemon": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Consumers should check `kind == "acp"`, `supported == false`, and
|
||||
`protocol.json_rpc == false` instead of inferring support from command presence.
|
||||
|
||||
## Unsupported invocations
|
||||
|
||||
Malformed ACP invocations, such as `claw acp start`, exit with code `1`. With
|
||||
`--output-format json`, stderr uses the normal CLI error envelope and sets:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"kind": "unsupported_acp_invocation",
|
||||
"exit_code": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Deferral gate
|
||||
|
||||
Real ACP/Zed or JSON-RPC serve work remains deferred until the roadmap contracts
|
||||
for task packets, session control, and event/report schemas are stable. This
|
||||
keeps desktop, marketplace, and editor integrations from becoming alternate
|
||||
sources of truth before the CLI/file/API contracts are ready.
|
||||
62
docs/g011-ecosystem-ops-ux-verification-map.md
Normal file
62
docs/g011-ecosystem-ops-ux-verification-map.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# G011 Ecosystem/Ops/UX Verification Map
|
||||
|
||||
G011 closes the laterals that were intentionally deferred from the earlier safety,
|
||||
session, MCP, Windows, and docs streams. This map is the cross-lane gate for the
|
||||
team run: it names the surfaces that can be verified locally, the exact checks to
|
||||
rerun after worker integrations, and the UX deferrals that must remain explicit
|
||||
until their product contracts are stable.
|
||||
|
||||
## Cross-lane acceptance matrix
|
||||
|
||||
| Lane | Owned surface | Regression evidence | Gate / gap |
|
||||
| --- | --- | --- | --- |
|
||||
| ACP/Zed status and JSON contracts | `rust/crates/rusty-claude-cli/src/main.rs` parses `claw acp`, `claw acp serve`, `--acp`, and `-acp`; `README.md` and `rust/README.md` document discoverability-only status | `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test output_format_contract acp_guidance_emits_json_when_requested -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli local_command_help_flags_stay_on_the_local_parser_path -- --nocapture` | Real ACP/Zed daemon support remains deferred; status output must not imply a running protocol endpoint. |
|
||||
| Plugin/marketplace local routing | `rust/crates/rusty-claude-cli/src/main.rs` routes `claw plugins`, `claw plugin`, and `claw marketplace` to local plugin handling; `rust/crates/commands/src/lib.rs` keeps `/plugin` aliases in shared slash-command help | `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli removed_login_and_logout_subcommands_error_helpfully -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli direct_slash_commands_surface_shared_validation_errors -- --nocapture`; `python3 -m unittest tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_filter_excludes_plugin_sources tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_aliases_execute_as_local_commands tests.test_porting_workspace.PortingWorkspaceTests.test_route_plugin_slash_commands_match_commands tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_stream_emits_command_match tests.test_porting_workspace.PortingWorkspaceTests.test_turn_loop_plugin_commands_are_not_prompt_only` | Marketplace is an alias to local plugin management only; no remote marketplace browsing/install contract is claimed. |
|
||||
| TUI/copy/paste/clickable path UX | `rust/crates/commands/src/lib.rs` advertises `/copy`, `/paste`, `/desktop`, and path-oriented commands; `rust/crates/rusty-claude-cli/src/main.rs` renders compact file/tool paths for terminal readability | `cargo test --manifest-path rust/Cargo.toml -p commands renders_help_with_grouped_categories_and_keyboard_shortcuts -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli tool_rendering_helpers_compact_output -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli tool_rendering_truncates_large_read_output_for_display_only -- --nocapture` | Clipboard integration, full-screen TUI mode, and clickable terminal hyperlinks are not stable product contracts yet; keep them as roadmap/UX follow-ups unless a targeted implementation lands. |
|
||||
| Desktop integration deferral | `rust/crates/commands/src/lib.rs` includes `/desktop`; `rust/crates/rusty-claude-cli/src/main.rs` treats it as not implemented in the current build | `cargo test --manifest-path rust/Cargo.toml -p commands renders_help_from_shared_specs -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p commands renders_per_command_help_detail -- --nocapture` | `/desktop` must stay discoverable but non-committal until a desktop launch/API contract exists. |
|
||||
| Navigation/file-context/local-provider docs | `README.md`, `USAGE.md`, `rust/README.md`, `docs/MODEL_COMPATIBILITY.md`, and worker-2 docs updates | `python3 .github/scripts/check_doc_source_of_truth.py`; `python3 .github/scripts/check_release_readiness.py`; `git diff --check` | Re-run after docs integrations; this lane should not alter Rust behavior unless docs expose a code contract gap. |
|
||||
| Issue/PR ops gate | `docs/pr-issue-resolution-gate.md`, `docs/roadmap-pr-goals.md`, and issue/PR triage templates if present | `python3 .github/scripts/check_release_readiness.py`; `git diff --check`; optional `python3 scripts/validate_cc2_board.py` only when `.omx/cc2/board.md` changes | Worker lanes must not merge/close remote PRs or issues; final reconciliation remains leader-owned. |
|
||||
|
||||
## Task 5 UX/deferral support notes
|
||||
|
||||
- `/copy`, `/paste`, and `/desktop` are parsed slash-command names, but current
|
||||
runtime handling still reports unimplemented commands rather than performing
|
||||
clipboard or desktop side effects. That is safer than pretending support exists.
|
||||
- `/marketplace` is intentionally a plugin alias; it should not be described as
|
||||
a remote marketplace until install/search/update semantics and trust policy are
|
||||
specified.
|
||||
- Path readability is covered by terminal rendering helpers that compact long
|
||||
tool outputs and preserve paths in read/write/edit summaries. Clickable OSC-8
|
||||
links, if added later, need separate tests because terminal support varies.
|
||||
- Full-screen TUI mode remains aspirational (`rust/TUI-ENHANCEMENT-PLAN.md`);
|
||||
current verification should focus on the inline REPL/help/status surfaces.
|
||||
|
||||
## Final verification sequence
|
||||
|
||||
Run these after all G011 worker commits are integrated into the leader branch:
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
python3 .github/scripts/check_doc_source_of_truth.py
|
||||
python3 .github/scripts/check_release_readiness.py
|
||||
cargo check --manifest-path rust/Cargo.toml -p commands -p rusty-claude-cli
|
||||
cargo test --manifest-path rust/Cargo.toml -p commands renders_help_from_shared_specs -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p commands renders_help_with_grouped_categories_and_keyboard_shortcuts -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p commands renders_per_command_help_detail -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli removed_login_and_logout_subcommands_error_helpfully -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli direct_slash_commands_surface_shared_validation_errors -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli local_command_help_flags_stay_on_the_local_parser_path -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli tool_rendering_helpers_compact_output -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli tool_rendering_truncates_large_read_output_for_display_only -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test output_format_contract acp_guidance_emits_json_when_requested -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test output_format_contract plugins_json_surfaces_lifecycle_contract_when_plugin_is_installed -- --nocapture
|
||||
python3 -m unittest tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_filter_excludes_plugin_sources tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_aliases_execute_as_local_commands tests.test_porting_workspace.PortingWorkspaceTests.test_route_plugin_slash_commands_match_commands tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_stream_emits_command_match tests.test_porting_workspace.PortingWorkspaceTests.test_turn_loop_plugin_commands_are_not_prompt_only
|
||||
```
|
||||
|
||||
## Leader audit notes
|
||||
|
||||
- This map is repo-local evidence only; workers must not mutate `.omx/ultragoal`.
|
||||
- If a check fails because another lane is still in progress, record the failing
|
||||
command and rerun after that lane is integrated instead of weakening the gate.
|
||||
- The minimum terminal condition is: docs checks pass, Rust targeted tests pass,
|
||||
and any still-deferred UX surface is explicitly named above.
|
||||
73
docs/g012-final-release-readiness-report.md
Normal file
73
docs/g012-final-release-readiness-report.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# G012 Final Release Readiness Report
|
||||
|
||||
Snapshot: 2026-05-15T02:59:29Z on `origin/main` / `HEAD` `2e93264919f38835410668ff6ca588606bc629f0`.
|
||||
|
||||
This is the worker-1 roadmap/board audit and release-readiness evidence map for the
|
||||
Claw Code 2.0 final gate. It is intentionally repo-local and non-destructive: it
|
||||
references `.omx/ultragoal` evidence without modifying leader-owned ultragoal
|
||||
state, and it does not merge PRs or close issues owned by the W3/W4 lanes.
|
||||
|
||||
## Release readiness summary
|
||||
|
||||
| Gate | Evidence | Result |
|
||||
| --- | --- | --- |
|
||||
| Ultragoal stream completion | `.omx/ultragoal/goals.json` shows G001-G011 complete and G012 pending at this snapshot. | PASS for pre-final stream completion; G012 remains the active final gate. |
|
||||
| Roadmap board coverage | `python3 scripts/validate_cc2_board.py` -> `PASS cc2 board validation`; 729 board items; 124/124 ROADMAP headings mapped; 542/542 ROADMAP actions mapped. | PASS |
|
||||
| Issue/parity intake coverage | `python3 .omx/cc2/validate_issue_parity_intake.py` -> `PASS issue/parity intake: 19 issue rows, 9 parity rows`. | PASS |
|
||||
| Release docs/readiness script | `python3 .github/scripts/check_release_readiness.py` -> `release-readiness check passed`. | PASS |
|
||||
| Documentation source-of-truth | `python3 .github/scripts/check_doc_source_of_truth.py` -> `doc source-of-truth check passed`. | PASS |
|
||||
| Fresh open PR snapshot | `gh pr list --state open --limit 1000 --json number,title,state,updatedAt,url,isDraft,mergeable` -> 51 open PR records; newest #3040. | PASS for snapshot capture; W3 owns reconciliation/action. |
|
||||
| Fresh open issue snapshot | `gh issue list --state open --limit 1000 --json number,title,state,updatedAt,url,labels` -> 1000 open issue records; newest returned #3036. | PASS for snapshot capture with limit caveat; W4 owns reconciliation/action. |
|
||||
|
||||
## Stream evidence index
|
||||
|
||||
| Goal | Status in local ultragoal state | Primary tracked evidence |
|
||||
| --- | --- | --- |
|
||||
| G001 Stream 0 board | complete | `.omx/cc2/board.json`, `.omx/cc2/board.md`, `scripts/validate_cc2_board.py` |
|
||||
| G002 security | complete | `docs/g002-security-verification-map.md` |
|
||||
| G003 boot/session | complete | `docs/g003-boot-session-verification-map.md` |
|
||||
| G004 events/reports | complete | `docs/g004-events-reports-verification-map.md`, `docs/g004-events-reports-contract.md` |
|
||||
| G005 branch/recovery | complete | `docs/g005-branch-recovery-verification-map.md` |
|
||||
| G006 task/policy/board | complete | `docs/g006-task-policy-board-verification-map.md` |
|
||||
| G007 plugin/MCP | complete | `docs/g007-plugin-mcp-verification-map.md`, `docs/g007-mcp-lifecycle-mapping.md` |
|
||||
| G008 provider compatibility | complete | `docs/local-openai-compatible-providers.md` plus ultragoal quality-gate artifact |
|
||||
| G009 Windows/docs/release | complete | `docs/g009-windows-docs-release-verification-map.md`, `docs/windows-install-release.md` |
|
||||
| G010 session hygiene | complete | `docs/g010-session-hygiene-verification-map.md`, `docs/g010-clone-disambiguation-metadata.md` |
|
||||
| G011 ecosystem/ops/UX | complete | `docs/g011-ecosystem-ops-ux-verification-map.md`, `docs/g011-acp-json-rpc-status-contract.md`, `docs/pr-issue-resolution-gate.md` |
|
||||
| G012 final gate | pending | This report plus W2/W3/W4 final gate reports. |
|
||||
|
||||
## Roadmap PR audit snapshot
|
||||
|
||||
`docs/roadmap-pr-goals.md` lists 17 roadmap/product-fit PRs that must be merged
|
||||
only when correct, resolvable, and safe. The fresh GitHub snapshot shows all 17
|
||||
remain open. Sixteen roadmap-doc PRs are currently `CONFLICTING`, so they are not
|
||||
safe direct-merge candidates from this worker lane. PR #2824 is `MERGEABLE`, but
|
||||
it is explicitly product-fit review rather than a direct roadmap merge candidate.
|
||||
|
||||
| PR | Title | Mergeable | Draft | Updated | Worker-1 final-gate disposition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| #2824 | docs: personal assistant roadmap | MERGEABLE | false | 2026-04-28T13:05:03Z | Defer to product-fit/leader decision; do not auto-merge as CC2 release gate evidence. |
|
||||
| #2839 | docs(roadmap): add #330 — resume mode stats/cost always zero | CONFLICTING | false | 2026-04-29T12:36:19Z | Not mergeable without conflict resolution; mapped into completed session/status streams. |
|
||||
| #2841 | docs(roadmap): add #332 — doctor json missing top-level status field | CONFLICTING | false | 2026-04-29T13:04:12Z | Not mergeable without conflict resolution; mapped into completed boot/doctor streams. |
|
||||
| #2842 | docs(roadmap): add #334 — version json omits build_date and uses short sha only | CONFLICTING | false | 2026-04-29T13:35:01Z | Not mergeable without conflict resolution; release-readiness docs/scripts pass at HEAD. |
|
||||
| #2844 | docs(roadmap): add #336 — session subcommand resume inconsistency and type/kind error mismatch | CONFLICTING | false | 2026-04-29T14:03:19Z | Not mergeable without conflict resolution; mapped into completed session hygiene streams. |
|
||||
| #2846 | docs(roadmap): add #331 — export silently overwrites on repeated invocations | CONFLICTING | false | 2026-04-29T13:02:02Z | Not mergeable without conflict resolution; action remains W3/leader triage if still desired. |
|
||||
| #2848 | docs(roadmap): add #333 — no in-session settings inspect command | CONFLICTING | false | 2026-04-29T13:32:01Z | Not mergeable without conflict resolution; action remains W3/leader triage if still desired. |
|
||||
| #2850 | docs(roadmap): add #335 — session list omits created_at_ms field | CONFLICTING | false | 2026-04-29T14:01:29Z | Not mergeable without conflict resolution; mapped into completed session metadata streams. |
|
||||
| #2858 | docs(roadmap): add #343 — session subcommand resume-safety inconsistently enforced | CONFLICTING | false | 2026-04-29T16:02:45Z | Not mergeable without conflict resolution; mapped into completed session/recovery streams. |
|
||||
| #2862 | docs(roadmap): add #342 — status json omits active session ID, workspace counters ambiguous | CONFLICTING | false | 2026-04-29T19:04:31Z | Not mergeable without conflict resolution; mapped into completed status/session streams. |
|
||||
| #2864 | docs(roadmap): add #364 — /cost returns no cost_usd; identical to /stats | CONFLICTING | false | 2026-04-29T22:32:52Z | Not mergeable without conflict resolution; mapped into completed UX/status contract review. |
|
||||
| #2865 | docs(roadmap): add #362 — doctor auth false-positive: misses CLI session tokens | CONFLICTING | false | 2026-04-29T22:06:28Z | Not mergeable without conflict resolution; mapped into completed doctor/auth stream work. |
|
||||
| #2867 | docs(roadmap): add #368 — export always appends .txt; response.file reflects mangled path | CONFLICTING | false | 2026-04-29T23:35:35Z | Not mergeable without conflict resolution; action remains W3/leader triage if still desired. |
|
||||
| #2868 | docs(roadmap): add #356 — session list title always null; no rename command | CONFLICTING | false | 2026-04-29T20:36:43Z | Not mergeable without conflict resolution; mapped into completed session identity streams. |
|
||||
| #2869 | docs(roadmap): add #358 — history entries missing role field, no pagination | CONFLICTING | false | 2026-04-29T21:02:55Z | Not mergeable without conflict resolution; mapped into completed session/history review. |
|
||||
| #2872 | docs(roadmap): add #360 — /tokens, /stats, /cost identical output; no context-window or cost_usd | CONFLICTING | false | 2026-04-29T21:32:57Z | Not mergeable without conflict resolution; mapped into completed UX/status contract review. |
|
||||
| #2876 | docs(roadmap): add #354 — /cwd suggests itself in did-you-mean; self-referential loop | CONFLICTING | false | 2026-04-29T20:01:22Z | Not mergeable without conflict resolution; mapped into completed command UX review. |
|
||||
|
||||
## Final-gate stop condition for worker-1
|
||||
|
||||
Worker-1's release-readiness lane is complete when this report is committed and
|
||||
its checks pass. Overall G012 completion still requires the leader to integrate
|
||||
W2 quality-gate classification and W3/W4 PR/issue reconciliation evidence. This
|
||||
report does not claim the remote PR/issue backlog is resolved; it provides the
|
||||
fresh roadmap/board/readiness audit that those lanes can reference.
|
||||
150
docs/local-openai-compatible-providers.md
Normal file
150
docs/local-openai-compatible-providers.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Local OpenAI-compatible providers and skills setup
|
||||
|
||||
This guide covers two common offline/local workflows:
|
||||
|
||||
1. running Claw against an OpenAI-compatible local model server such as Ollama, llama.cpp, or vLLM; and
|
||||
2. installing local skills from disk so Claw can discover them without network access.
|
||||
|
||||
## Claw is not Claude-only
|
||||
|
||||
Claw Code is a Claude-Code-shaped workflow/runtime, not a Claude-only product. It supports Anthropic directly and can target OpenAI-compatible, provider-routed, and local models depending on configuration. Non-Claude providers are supported honestly: they may require stricter tool-call and response-shape compatibility, and some slash/tool workflows can be rougher than first-party Anthropic/OpenAI paths. Provider-specific identity leaks are bugs, not intended product positioning.
|
||||
|
||||
If you need the most polished daily-driver experience for a specific non-Claude model today, compare that provider’s native tools. If you need runtime/provider hackability, Claw’s OpenAI-compatible route is the intended extension path.
|
||||
|
||||
## OpenAI-compatible routing basics
|
||||
|
||||
Set `OPENAI_BASE_URL` to the server’s `/v1` endpoint and set `OPENAI_API_KEY` to either the required token or a harmless placeholder for local servers that expect an Authorization header. The model name must match what the server exposes.
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="http://127.0.0.1:11434/v1"
|
||||
export OPENAI_API_KEY="local-dev-token"
|
||||
claw --model "qwen3:latest" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
Routing notes:
|
||||
|
||||
- Use the `openai/` prefix for OpenAI-compatible gateways when you need prefix routing to win over ambient Anthropic credentials, for example `--model "openai/gpt-4.1-mini"` with OpenRouter.
|
||||
- For local servers, prefer the exact model ID reported by the server (`qwen3:latest`, `llama3.2`, `Qwen/Qwen2.5-Coder-7B-Instruct`, etc.). If your local gateway exposes slash-containing IDs, use that exact slug.
|
||||
- If you have multiple provider keys in your environment, remove unrelated keys while smoke-testing a local route or choose a model prefix that unambiguously selects the intended provider.
|
||||
- Tool workflows need model/server support for OpenAI-compatible tool calls. Plain prompt smoke tests can pass even when slash/tool workflows still fail because the server returns an incompatible tool-call shape.
|
||||
|
||||
## Raw `/v1/chat/completions` smoke test
|
||||
|
||||
Before debugging Claw, verify the local server speaks the expected wire format:
|
||||
|
||||
```bash
|
||||
curl -sS "$OPENAI_BASE_URL/chat/completions" \
|
||||
-H "Authorization: Bearer ${OPENAI_API_KEY:-local-dev-token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "qwen3:latest",
|
||||
"messages": [{"role": "user", "content": "Reply exactly HELLO_WORLD_123"}],
|
||||
"stream": false
|
||||
}'
|
||||
```
|
||||
|
||||
Expected result: a JSON response with one assistant message containing `HELLO_WORLD_123`. If this fails, fix the local server, model name, or auth token before changing Claw settings.
|
||||
|
||||
## Ollama
|
||||
|
||||
Start Ollama and pull a model:
|
||||
|
||||
```bash
|
||||
ollama pull qwen3:latest
|
||||
ollama serve
|
||||
```
|
||||
|
||||
In another shell:
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="http://127.0.0.1:11434/v1"
|
||||
export OPENAI_API_KEY="local-dev-token"
|
||||
claw --model "qwen3:latest" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
If Ollama is running without auth and your build accepts authless local OpenAI-compatible servers, `unset OPENAI_API_KEY` is also acceptable. Use a placeholder token rather than a real cloud API key for local testing.
|
||||
|
||||
## llama.cpp server
|
||||
|
||||
Start a llama.cpp OpenAI-compatible server with the model name you want Claw to send:
|
||||
|
||||
```bash
|
||||
llama-server -m ./models/qwen2.5-coder.gguf --host 127.0.0.1 --port 8080 --alias qwen2.5-coder
|
||||
```
|
||||
|
||||
Then smoke-test through Claw:
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="http://127.0.0.1:8080/v1"
|
||||
export OPENAI_API_KEY="local-dev-token"
|
||||
claw --model "qwen2.5-coder" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
## vLLM or another OpenAI-compatible server
|
||||
|
||||
Start vLLM with an OpenAI-compatible API server:
|
||||
|
||||
```bash
|
||||
vllm serve Qwen/Qwen2.5-Coder-7B-Instruct --host 127.0.0.1 --port 8000
|
||||
```
|
||||
|
||||
Then route Claw to it:
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="http://127.0.0.1:8000/v1"
|
||||
export OPENAI_API_KEY="local-dev-token"
|
||||
claw --model "Qwen/Qwen2.5-Coder-7B-Instruct" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
## Local skills install from disk
|
||||
|
||||
Skills are discovered from Claw skill roots such as `.claw/skills/` in a workspace and `~/.claw/skills/` for user-level installs. Legacy `.codex/skills/` roots may also be scanned for compatibility, but new local Claw projects should prefer `.claw/skills/`.
|
||||
|
||||
A skill directory should contain a `SKILL.md` file with frontmatter:
|
||||
|
||||
```text
|
||||
my-skill/
|
||||
└── SKILL.md
|
||||
```
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: Explain when this skill should be used.
|
||||
---
|
||||
|
||||
# My Skill
|
||||
|
||||
Instructions for the agent go here.
|
||||
```
|
||||
|
||||
Install a skill from a local path in the interactive REPL:
|
||||
|
||||
```text
|
||||
/skills install /absolute/path/to/my-skill
|
||||
/skills list
|
||||
/skills my-skill
|
||||
```
|
||||
|
||||
Or inspect skills from the direct CLI surface:
|
||||
|
||||
```bash
|
||||
claw skills --output-format json
|
||||
```
|
||||
|
||||
Offline install checklist:
|
||||
|
||||
- Install the specific skill directory, not only the repository root, unless that repository root itself contains `SKILL.md`.
|
||||
- Keep the frontmatter `name` aligned with the directory name users will type.
|
||||
- After installing, run `/skills list` or `claw skills --output-format json` to confirm the discovered name and source path.
|
||||
- If a skill invocation fails with an HTTP/provider error, the skill may have installed correctly but the current model/provider call failed. Run `claw doctor`, verify provider credentials, and try a simple prompt smoke test before reinstalling the skill.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Check |
|
||||
|---|---|
|
||||
| Claw still asks for Anthropic credentials | Use an explicit OpenAI-compatible model route or remove unrelated Anthropic env vars during local smoke tests. |
|
||||
| `model not found` from local server | Use the exact model ID exposed by Ollama/llama.cpp/vLLM. |
|
||||
| Plain prompt works but tools fail | Confirm the model/server supports OpenAI-compatible tool calls and response shapes. |
|
||||
| Skill says installed but `/skills <name>` fails | Check `/skills list` for the discovered name and source; verify provider credentials separately with `claw doctor`. |
|
||||
| A local docs/log file contains secrets | Redact it before using `@path` file context or attaching it to an issue. |
|
||||
69
docs/navigation-file-context.md
Normal file
69
docs/navigation-file-context.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Navigation and file context guide
|
||||
|
||||
This guide answers the common “how do I browse output?” and “how do I submit a file?” questions for Claw Code. Claw is an agent CLI, not a full file manager: terminal navigation comes from your shell or terminal, while file context is passed explicitly in prompts.
|
||||
|
||||
## Prompt and terminal navigation
|
||||
|
||||
Use your terminal’s normal controls for command history and long output:
|
||||
|
||||
- `Up` / `Down` usually move through shell or REPL prompt history.
|
||||
- `Ctrl-r` searches shell history in most shells.
|
||||
- Long command output is viewed with your terminal scrollback. In tmux, enter copy mode with `Ctrl-b [` then use arrows, PageUp/PageDown, search, or your mouse depending on tmux config.
|
||||
- If output is too large to scroll comfortably, redirect it to a file and give that file to Claw as context:
|
||||
```bash
|
||||
cargo test --workspace 2>&1 | tee logs/test-output.txt
|
||||
claw prompt "Use @logs/test-output.txt as context and summarize the failing tests."
|
||||
```
|
||||
|
||||
Claw may provide slash commands that inspect workspace state, but those commands do not replace your terminal’s scrollback or shell history.
|
||||
|
||||
## Submit repository files with `@path`
|
||||
|
||||
Mention files from the current workspace with `@` paths. Use relative paths from the repository or current working directory:
|
||||
|
||||
```text
|
||||
Read @src/app.ts and explain the bug.
|
||||
Compare @old.md and @new.md.
|
||||
Use @logs/error.txt as context and suggest a fix.
|
||||
Review @README.md and @docs/navigation-file-context.md for consistency.
|
||||
```
|
||||
|
||||
Tips:
|
||||
|
||||
- Prefer the smallest useful file set. Large directories or logs can consume context quickly.
|
||||
- Use exact paths when possible (`@rust/crates/runtime/src/lib.rs`) instead of vague descriptions.
|
||||
- For generated logs, save them under a temporary or ignored directory such as `logs/` and reference the file.
|
||||
- If the file is outside the repository, copy it into a safe workspace location first or use an app/UI attachment feature if your Claw surface supports attachments.
|
||||
|
||||
## Browse or inspect files
|
||||
|
||||
Claw can answer questions about files you reference, and you can ask it to inspect likely locations:
|
||||
|
||||
```text
|
||||
Find where provider routing is implemented and summarize the relevant files.
|
||||
Read @USAGE.md and tell me where local model setup is documented.
|
||||
Search for the command that handles skills install, then explain the control flow.
|
||||
```
|
||||
|
||||
For deterministic shell-side browsing, ordinary commands still work:
|
||||
|
||||
```bash
|
||||
find docs -maxdepth 2 -type f | sort
|
||||
rg -n "OPENAI_BASE_URL|skills install" USAGE.md docs rust
|
||||
sed -n '250,340p' USAGE.md
|
||||
```
|
||||
|
||||
## Attach external files where supported
|
||||
|
||||
Some UI surfaces let you drag and drop or attach files directly. When that is available, use attachments for files that should not be committed to the repo. In terminal-only usage, copy the file into the workspace, reference it with `@path`, then remove it when finished if it was temporary.
|
||||
|
||||
## Secret and credential safety
|
||||
|
||||
Do not paste real API keys, OAuth tokens, private logs, or customer data into prompts, issue comments, screenshots, or committed docs. Before submitting a file:
|
||||
|
||||
- Replace live keys with placeholders such as `sk-ant-REPLACE_ME`, `sk-or-v1-REPLACE_ME`, or `local-dev-token`.
|
||||
- Redact bearer tokens, cookies, session IDs, and private base URLs.
|
||||
- Prefer minimal reproductions over full production logs.
|
||||
- Keep `.env`, key files, and private logs out of git.
|
||||
|
||||
If a task requires credentials, describe the variable names and expected shapes instead of sharing the values.
|
||||
67
docs/pr-issue-resolution-gate.md
Normal file
67
docs/pr-issue-resolution-gate.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Claw Code 2.0 PR and Issue Resolution Gate
|
||||
|
||||
This gate was added to the Claw Code 2.0 Ultragoal after the explicit requirement:
|
||||
|
||||
> all PRs should be merged and all issues should be resolved if resolvable and correct.
|
||||
|
||||
## Scope
|
||||
|
||||
Before the Claw Code 2.0 Ultragoal can be marked complete:
|
||||
|
||||
1. Every open GitHub PR at the current final-gate snapshot must be triaged.
|
||||
2. PRs that are correct, compatible with Claw Code 2.0 direction, and pass required verification must be merged.
|
||||
3. PRs that are stale, incorrect, duplicative, unsafe, spam, or outside Claw Code scope must not be merged; each needs a recorded rationale.
|
||||
4. Every open GitHub issue at the current final-gate snapshot must be triaged.
|
||||
5. Issues that are resolvable and correct must be fixed or explicitly linked to a merged fix.
|
||||
6. Issues that are spam, duplicates, incorrect, unactionable, externally blocked, or not Claw Code work must be closed or labeled/commented with rationale when repository policy allows.
|
||||
7. The final completion audit must use a fresh GitHub snapshot, not only the planning snapshot.
|
||||
|
||||
## Current live snapshot
|
||||
|
||||
A fresh non-destructive snapshot was captured locally during G011 W3 execution:
|
||||
|
||||
- Command: `gh pr list --state open --limit 1000 --json number,title,state,updatedAt,url`
|
||||
- Command: `gh issue list --state open --limit 1000 --json number,title,state,updatedAt,url,labels`
|
||||
- Captured on: 2026-05-15T02:39:41Z during the active Ultragoal run.
|
||||
- Observed counts: 51 open PR records and 1000 open issue records from GitHub CLI list calls.
|
||||
- Most recent open PR in the snapshot: #3040, `fix: recognize OPENAI_API_KEY as valid auth for OpenAI-compatible endpoints`, updated 2026-05-14T11:35:23Z.
|
||||
- Most recent open issue in the snapshot: #3039, `How to install skills?`, updated 2026-05-14T08:14:36Z.
|
||||
- The issue snapshot hit the configured `--limit 1000`, so the final gate must treat the issue count as at least 1000 unless a higher-limit export or paginated ledger is captured.
|
||||
|
||||
These command outputs are evidence inputs, not final proof. The final gate must refresh them and compare deltas before any completion claim.
|
||||
|
||||
## Anti-slop triage templates
|
||||
|
||||
Use `docs/anti-slop-triage.md` plus the repository templates before acting on the live snapshot:
|
||||
|
||||
- `.github/ISSUE_TEMPLATE/anti_slop_triage.yml` records the initial issue classification, evidence, and non-destructive next action.
|
||||
- `.github/PULL_REQUEST_TEMPLATE.md` adds PR classification, verification, and resolution-gate checklist items.
|
||||
|
||||
The anti-slop classifications are: `actionable-bug`, `actionable-docs`, `actionable-feature`, `duplicate`, `spam-or-promotion`, `generated-slop-or-hallucinated`, `unsafe-or-security-sensitive`, `not-reproducible-yet`, and `externally-blocked`.
|
||||
|
||||
Automation lanes may recommend labels, comments, defer/close rationales, or merge candidates, but must not merge or close remote PRs/issues without maintainer-owned approval.
|
||||
|
||||
|
||||
## G012 final PR reconciliation snapshot
|
||||
|
||||
Worker-3 captured a fresh PR ledger for the final Claw Code 2.0 gate in `docs/pr-triage-g012-final-gate.json`.
|
||||
|
||||
- Captured on: 2026-05-15T02:58:00Z during G012 final-gate execution.
|
||||
- Commands: `gh pr list --state open --limit 100 ...` plus `gh pr view <number> ...` for per-PR file and merge-state evidence.
|
||||
- Observed count: 51 open PR records.
|
||||
- Merge action taken by worker-3: none. The safety policy requires correct, safe, non-conflicting, resolvable PRs with evidence; this snapshot found 32 PRs in `CONFLICTING`/`DIRTY` state and 19 `MERGEABLE` PRs that GitHub reported as `UNSTABLE` with no fresh check-rollup evidence in the live snapshot.
|
||||
- Docs-only candidate-review PRs: #3021 and #2824 remain deferred until content/source-of-truth review and fresh verification are available.
|
||||
|
||||
## Required final evidence
|
||||
|
||||
The final report must include:
|
||||
|
||||
- Fresh `gh pr list --state open` and `gh issue list --state open` snapshots.
|
||||
- A PR ledger with one row per PR: merge / reject / defer, reason, verification, commit/merge reference.
|
||||
- An issue ledger with one row per issue: fixed / duplicate / spam / invalid / deferred-with-rationale / externally-blocked, reason, and linked evidence.
|
||||
- Verification that no correct, mergeable PR remains unmerged without rationale.
|
||||
- Verification that no resolvable, correct issue remains open without a fix or rationale.
|
||||
|
||||
## Non-goals
|
||||
|
||||
This gate does not require merging unsafe, unverified, incompatible, spam, or incorrect contributions. It requires explicit evidence-backed triage and action for everything that is correct and resolvable.
|
||||
1461
docs/pr-triage-g012-final-gate.json
Normal file
1461
docs/pr-triage-g012-final-gate.json
Normal file
File diff suppressed because it is too large
Load Diff
58
docs/roadmap-pr-goals.md
Normal file
58
docs/roadmap-pr-goals.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Roadmap PR goal intake
|
||||
|
||||
Captured: 2026-05-14 (Asia/Seoul) during the Claw Code 2.0 Ultragoal run.
|
||||
|
||||
Purpose: make the user's follow-up requirement durable: all roadmap PRs should be merged when correct/resolvable, and unresolved roadmap deltas should become Ultragoal work rather than being lost. This file is a tracked companion to the leader-owned `.omx/ultragoal/goals.json` and `.omx/ultragoal/ledger.jsonl` artifacts.
|
||||
|
||||
## Merge policy
|
||||
|
||||
- Merge only PRs that are still relevant to Claw Code 2.0, are non-draft, target `main`, and are conflict-free after a fresh mergeability refresh.
|
||||
- Prefer squash merges with a Lore-style body when GitHub allows a direct PR merge.
|
||||
- If a PR is documentation-only but adds a real roadmap gap, merging it is acceptable once checks/conflicts are clean.
|
||||
- If a PR is stale, duplicated by already-landed work, or not product-aligned, do not force-merge; record the rationale and map any still-correct requirement into G011/G012.
|
||||
- After merging roadmap PRs, refresh generated board artifacts (`.omx/cc2/board.json`, `.omx/cc2/board.md`) so Stream 0 coverage stays current.
|
||||
|
||||
## Open roadmap PRs with green historical checks
|
||||
|
||||
These are first-pass merge candidates, pending fresh mergeability and conflict checks against current `main`.
|
||||
|
||||
| PR | Title | Branch | Checks | Mergeable | URL |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| #2848 | docs(roadmap): add #333 — no in-session settings inspect command | `docs/roadmap-333-no-settings-inspect-command` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2848 |
|
||||
| #2846 | docs(roadmap): add #331 — export silently overwrites on repeated invocations | `docs/roadmap-331-export-filename-collision` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2846 |
|
||||
| #2869 | docs(roadmap): add #358 — history entries missing role field, no pagination | `docs/roadmap-348-history-entries-missing-role` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2869 |
|
||||
| #2850 | docs(roadmap): add #335 — session list omits created_at_ms field | `docs/roadmap-335-session-list-no-created-at` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2850 |
|
||||
| #2868 | docs(roadmap): add #356 — session list title always null; no rename command | `docs/roadmap-347-session-list-title-always-null` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2868 |
|
||||
| #2865 | docs(roadmap): add #362 — doctor auth false-positive: misses CLI session tokens | `docs/roadmap-345-doctor-auth-check-incomplete` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2865 |
|
||||
| #2864 | docs(roadmap): add #364 — /cost returns no cost_usd; identical to /stats | `docs/roadmap-344-cost-command-no-dollar-amount` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2864 |
|
||||
| #2867 | docs(roadmap): add #368 — export always appends .txt; response.file reflects mangled path | `docs/roadmap-346-export-forces-txt-extension` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2867 |
|
||||
| #2862 | docs(roadmap): add #342 — status json omits active session ID, workspace counters ambiguous | `docs/roadmap-342-v2` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2862 |
|
||||
| #2876 | docs(roadmap): add #354 — /cwd suggests itself in did-you-mean; self-referential loop | `docs/roadmap-354-cwd-self-referential-suggestion` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2876 |
|
||||
| #2872 | docs(roadmap): add #360 — /tokens, /stats, /cost identical output; no context-window or cost_usd | `docs/roadmap-349-tokens-stats-cost-identical` -> `main` | 4/4 checks successful | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2872 |
|
||||
|
||||
## Open roadmap PRs needing local validation or CI refresh
|
||||
|
||||
These have no check rollup in the live snapshot; validate locally or refresh CI before merging.
|
||||
|
||||
| PR | Title | Branch | Checks | Mergeable | URL |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| #2858 | docs(roadmap): add #343 — session subcommand resume-safety inconsistently enforced | `docs/roadmap-340-session-resume-safe-inconsistent` -> `main` | no checks reported | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2858 |
|
||||
| #2839 | docs(roadmap): add #330 — resume mode stats/cost always zero | `docs/roadmap-324-resume-stats-zero` -> `main` | no checks reported | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2839 |
|
||||
| #2841 | docs(roadmap): add #332 — doctor json missing top-level status field | `docs/roadmap-325-doctor-no-status-field` -> `main` | no checks reported | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2841 |
|
||||
| #2844 | docs(roadmap): add #336 — session subcommand resume inconsistency and type/kind error mismatch | `docs/roadmap-329-session-subcommand-resume-inconsistency` -> `main` | no checks reported | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2844 |
|
||||
| #2842 | docs(roadmap): add #334 — version json omits build_date and uses short sha only | `docs/roadmap-328-version-json-incomplete` -> `main` | no checks reported | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2842 |
|
||||
|
||||
## Product-fit review before merge
|
||||
|
||||
These may be broader than the Claw Code 2.0 roadmap scope and need a product-fit decision before merge.
|
||||
|
||||
| PR | Title | Branch | Checks | Mergeable | URL |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| #2824 | docs: personal assistant roadmap | `pr/docs-personal-assistant-roadmap` -> `main` | no checks reported | UNKNOWN | https://github.com/ultraworkers/claw-code/pull/2824 |
|
||||
|
||||
## Ultragoal mapping
|
||||
|
||||
- G003-G010: close implementation gaps that overlap a roadmap PR title if the requirement belongs to the active stream.
|
||||
- G011: reconcile ecosystem/ops/UX roadmap PRs and unresolved correct issues that do not fit earlier streams.
|
||||
- G012: final release gate must prove that every open roadmap PR was merged, closed as duplicate/obsolete, or converted into an explicit remaining goal with evidence.
|
||||
|
||||
195
docs/windows-install-release.md
Normal file
195
docs/windows-install-release.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Windows install and release quickstart
|
||||
|
||||
This page is the PowerShell-first path for installing, verifying, and safely switching providers on Windows. It is intentionally copyable without embedding live secrets.
|
||||
|
||||
## Choose an install path
|
||||
|
||||
### Option A: build from source in PowerShell
|
||||
|
||||
Use this when you are developing Claw Code or testing a local checkout.
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/ultraworkers/claw-code
|
||||
Set-Location .\claw-code\rust
|
||||
cargo build --workspace
|
||||
.\target\debug\claw.exe --help
|
||||
.\target\debug\claw.exe doctor
|
||||
```
|
||||
|
||||
For an optimized local binary:
|
||||
|
||||
```powershell
|
||||
Set-Location .\claw-code\rust
|
||||
cargo build --workspace --release
|
||||
.\target\release\claw.exe --help
|
||||
```
|
||||
|
||||
### Option B: use a release artifact
|
||||
|
||||
Use this when a GitHub release publishes a Windows artifact. The release workflow publishes `claw-windows-x64.exe` plus `claw-windows-x64.exe.sha256`; if a future release wraps the binary in a ZIP, prefer the `windows-x86_64` / `pc-windows-msvc` asset and its matching checksum file.
|
||||
|
||||
```powershell
|
||||
$Asset = "claw-windows-x64.exe"
|
||||
$InstallRoot = "$env:LOCALAPPDATA\Programs\claw"
|
||||
New-Item -ItemType Directory -Force $InstallRoot | Out-Null
|
||||
|
||||
# Download $Asset and $Asset.sha256 from the release page, then verify them:
|
||||
$Actual = (Get-FileHash ".\$Asset" -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$Expected = (Get-Content ".\$Asset.sha256" | Select-Object -First 1).Split()[0].ToLowerInvariant()
|
||||
if ($Actual -ne $Expected) { throw "checksum mismatch for $Asset" }
|
||||
|
||||
Copy-Item ".\$Asset" "$InstallRoot\claw.exe" -Force
|
||||
& "$InstallRoot\claw.exe" --help
|
||||
& "$InstallRoot\claw.exe" doctor
|
||||
```
|
||||
|
||||
To make that binary available in new PowerShell windows:
|
||||
|
||||
```powershell
|
||||
$InstallRoot = "$env:LOCALAPPDATA\Programs\claw"
|
||||
[Environment]::SetEnvironmentVariable(
|
||||
"Path",
|
||||
[Environment]::GetEnvironmentVariable("Path", "User") + ";$InstallRoot",
|
||||
"User"
|
||||
)
|
||||
```
|
||||
|
||||
Open a new terminal before running `claw --help` from another directory.
|
||||
|
||||
### Option C: WSL
|
||||
|
||||
The repository `install.sh` path is for Linux, macOS, and Windows via WSL. Run it from inside your WSL distribution, not from native PowerShell:
|
||||
|
||||
```powershell
|
||||
wsl --install
|
||||
wsl
|
||||
```
|
||||
|
||||
Then inside WSL:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ultraworkers/claw-code
|
||||
cd claw-code
|
||||
./install.sh
|
||||
```
|
||||
|
||||
## First-run health checks
|
||||
|
||||
Run these before using live prompts:
|
||||
|
||||
```powershell
|
||||
Set-Location .\claw-code\rust
|
||||
.\target\debug\claw.exe --help
|
||||
.\target\debug\claw.exe doctor
|
||||
.\target\debug\claw.exe status --output-format json
|
||||
.\target\debug\claw.exe config --output-format json
|
||||
```
|
||||
|
||||
`doctor`, `status`, `config`, and `version` support `--output-format json`; do not use a separate `--json` suffix.
|
||||
|
||||
## Safe credential setup
|
||||
|
||||
Set keys only in your local environment or a private `.env` file. Do not paste real keys into shell history shared with others, issue trackers, or documentation.
|
||||
|
||||
Current PowerShell session only:
|
||||
|
||||
```powershell
|
||||
$env:ANTHROPIC_API_KEY = "sk-ant-REPLACE_ME"
|
||||
```
|
||||
|
||||
Persist for future PowerShell windows:
|
||||
|
||||
```powershell
|
||||
setx ANTHROPIC_API_KEY "sk-ant-REPLACE_ME"
|
||||
```
|
||||
|
||||
Open a new terminal after `setx`. To remove a session-local key while testing provider switching:
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
||||
```
|
||||
|
||||
## Safe provider switching examples
|
||||
|
||||
Provider routing is model-prefix first. When multiple credentials exist, choose an explicit model prefix so `claw` does not infer the wrong backend.
|
||||
|
||||
### Anthropic direct
|
||||
|
||||
```powershell
|
||||
$env:ANTHROPIC_API_KEY = "sk-ant-REPLACE_ME"
|
||||
Remove-Item Env:\OPENAI_BASE_URL -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
|
||||
.\target\debug\claw.exe --model "sonnet" prompt "reply with ready"
|
||||
```
|
||||
|
||||
### OpenAI-compatible gateway or OpenRouter
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
||||
$env:OPENAI_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
$env:OPENAI_API_KEY = "sk-or-v1-REPLACE_ME"
|
||||
|
||||
.\target\debug\claw.exe --model "openai/gpt-4.1-mini" prompt "reply with ready"
|
||||
```
|
||||
|
||||
For the default OpenAI-compatible API, omit `OPENAI_BASE_URL` or set it to `https://api.openai.com/v1`, and keep the `openai/` or `gpt-` model prefix explicit.
|
||||
|
||||
### Local OpenAI-compatible server
|
||||
|
||||
Use a loopback URL and a placeholder token unless your local server requires a real one:
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
||||
$env:OPENAI_BASE_URL = "http://127.0.0.1:11434/v1"
|
||||
$env:OPENAI_API_KEY = "local-dev-token"
|
||||
|
||||
.\target\debug\claw.exe --model "llama3.2" prompt "reply with ready"
|
||||
```
|
||||
|
||||
If the local server is authless, remove `OPENAI_API_KEY` instead of putting a real cloud key into local testing:
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
```
|
||||
|
||||
### DashScope / Qwen
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
||||
$env:DASHSCOPE_API_KEY = "sk-REPLACE_ME"
|
||||
|
||||
.\target\debug\claw.exe --model "qwen-plus" prompt "reply with ready"
|
||||
```
|
||||
|
||||
## Windows and WSL notifications
|
||||
|
||||
Notification support is exposed through the `notifications` slash command in the interactive REPL. Use JSON/status commands first to confirm the CLI runs, then configure notifications from the REPL if your workflow needs them.
|
||||
|
||||
Native PowerShell smoke path:
|
||||
|
||||
```powershell
|
||||
Set-Location .\claw-code\rust
|
||||
.\target\debug\claw.exe
|
||||
# inside the REPL:
|
||||
/notifications
|
||||
```
|
||||
|
||||
WSL smoke path:
|
||||
|
||||
```bash
|
||||
cd claw-code/rust
|
||||
./target/debug/claw
|
||||
# inside the REPL:
|
||||
/notifications
|
||||
```
|
||||
|
||||
When moving between PowerShell and WSL, keep provider keys in the environment where `claw` is actually running; Windows user env vars set with `setx` are not automatically the same as WSL shell exports.
|
||||
|
||||
## Troubleshooting checklist
|
||||
|
||||
- `claw` not found: use `claw.exe` on Windows or run the binary by full path (`.\target\debug\claw.exe`).
|
||||
- `cargo` not found: reopen PowerShell after installing Rust from <https://rustup.rs/>.
|
||||
- `401 Invalid bearer token`: put `sk-ant-*` values in `ANTHROPIC_API_KEY`, not `ANTHROPIC_AUTH_TOKEN`.
|
||||
- Wrong provider selected: add an explicit model prefix such as `openai/gpt-4.1-mini`, `qwen-plus`, or `grok`.
|
||||
- Release ZIP extracted but command still fails: open a new terminal after updating the user `Path`, or call `& "$env:LOCALAPPDATA\Programs\claw\claw.exe"` directly.
|
||||
@@ -16,7 +16,7 @@ unsafe_code = "forbid"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "allow", priority = -1 }
|
||||
module_name_repetitions = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
|
||||
@@ -22,6 +22,8 @@ The harness runs these scripted scenarios against a fresh workspace and isolated
|
||||
8. `bash_permission_prompt_approved`
|
||||
9. `bash_permission_prompt_denied`
|
||||
10. `plugin_tool_roundtrip`
|
||||
11. `auto_compact_triggered`
|
||||
12. `token_cost_reporting`
|
||||
|
||||
## Run
|
||||
|
||||
@@ -37,7 +39,7 @@ cd rust/
|
||||
python3 scripts/run_mock_parity_diff.py
|
||||
```
|
||||
|
||||
Scenario-to-PARITY mappings live in `mock_parity_scenarios.json`.
|
||||
Scenario-to-PARITY mappings live in `mock_parity_scenarios.json`; keep this manifest aligned with `rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs` and `PARITY.md` via `python3 scripts/run_mock_parity_diff.py --no-run`.
|
||||
|
||||
## Manual mock server
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ Top-level commands:
|
||||
init
|
||||
```
|
||||
|
||||
`claw acp` is a local discoverability surface for editor-first users: it reports the current ACP/Zed status without starting the runtime. As of April 16, 2026, claw-code does **not** ship an ACP/Zed daemon entrypoint yet, and `claw acp serve` is only a status alias until the real protocol surface lands.
|
||||
`claw acp` is a local discoverability surface for editor-first users: it reports the current ACP/Zed status without starting the runtime. As of April 16, 2026, claw-code does **not** ship an ACP/Zed daemon or JSON-RPC entrypoint yet, and `claw acp serve` is only a status alias until the real protocol surface lands. Status queries exit 0 and expose the same machine-readable contract via `--output-format json`; malformed ACP invocations exit 1 with `kind: unsupported_acp_invocation`.
|
||||
|
||||
The command surface is moving quickly. For the canonical live help text, run:
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ fn create_sample_request(message_count: usize) -> MessageRequest {
|
||||
presence_penalty: None,
|
||||
stop: None,
|
||||
reasoning_effort: None,
|
||||
extra_body: std::collections::BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@ const CONTEXT_WINDOW_ERROR_MARKERS: &[&str] = &[
|
||||
"too many tokens",
|
||||
"prompt is too long",
|
||||
"input is too long",
|
||||
"input tokens exceed",
|
||||
"configured limit",
|
||||
"messages resulted in",
|
||||
"completion tokens",
|
||||
"prompt tokens",
|
||||
"request is too large",
|
||||
];
|
||||
|
||||
@@ -542,6 +547,26 @@ mod tests {
|
||||
assert_eq!(error.request_id(), Some("req_ctx_123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_openai_configured_limit_errors_as_context_window_failures() {
|
||||
let error = ApiError::Api {
|
||||
status: reqwest::StatusCode::BAD_REQUEST,
|
||||
error_type: Some("invalid_request_error".to_string()),
|
||||
message: Some(
|
||||
"Input tokens exceed the configured limit of 922000 tokens. Your messages resulted in 1860900 tokens. Please reduce the length of the messages."
|
||||
.to_string(),
|
||||
),
|
||||
request_id: Some("req_ctx_openai_123".to_string()),
|
||||
body: String::new(),
|
||||
retryable: false,
|
||||
suggested_action: None,
|
||||
};
|
||||
|
||||
assert!(error.is_context_window_failure());
|
||||
assert_eq!(error.safe_failure_class(), "context_window");
|
||||
assert_eq!(error.request_id(), Some("req_ctx_openai_123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_credentials_without_hint_renders_the_canonical_message() {
|
||||
// given
|
||||
|
||||
@@ -20,12 +20,15 @@ pub use prompt_cache::{
|
||||
};
|
||||
pub use providers::anthropic::{AnthropicClient, AnthropicClient as ApiClient, AuthSource};
|
||||
pub use providers::openai_compat::{
|
||||
build_chat_completion_request, flatten_tool_result_content, is_reasoning_model,
|
||||
model_rejects_is_error_field, translate_message, OpenAiCompatClient, OpenAiCompatConfig,
|
||||
build_chat_completion_request, check_request_body_size, estimate_request_body_size,
|
||||
flatten_tool_result_content, is_reasoning_model, model_rejects_is_error_field,
|
||||
model_requires_reasoning_content_in_history, translate_message, OpenAiCompatClient,
|
||||
OpenAiCompatConfig,
|
||||
};
|
||||
pub use providers::{
|
||||
detect_provider_kind, max_tokens_for_model, max_tokens_for_model_with_override,
|
||||
resolve_model_alias, ProviderKind,
|
||||
model_family_identity_for, model_family_identity_for_kind, provider_diagnostics_for_model,
|
||||
resolve_model_alias, ProviderDiagnostics, ProviderKind,
|
||||
};
|
||||
pub use sse::{parse_frame, SseParser};
|
||||
pub use types::{
|
||||
|
||||
@@ -600,8 +600,9 @@ fn jitter_for_base(base: Duration) -> Duration {
|
||||
}
|
||||
let raw_nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|elapsed| u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX))
|
||||
.unwrap_or(0);
|
||||
.map_or(0, |elapsed| {
|
||||
u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX)
|
||||
});
|
||||
let tick = JITTER_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
// splitmix64 finalizer — mixes the low bits so large bases still see
|
||||
// jitter across their full range instead of being clamped to subsec nanos.
|
||||
@@ -844,19 +845,17 @@ impl MessageStream {
|
||||
StreamEvent::MessageDelta(MessageDeltaEvent { usage, .. }) => {
|
||||
self.latest_usage = Some(usage.clone());
|
||||
}
|
||||
StreamEvent::MessageStop(_) => {
|
||||
if !self.usage_recorded {
|
||||
if let (Some(prompt_cache), Some(usage)) =
|
||||
(&self.prompt_cache, self.latest_usage.as_ref())
|
||||
{
|
||||
let record = prompt_cache.record_usage(&self.request, usage);
|
||||
*self
|
||||
.last_prompt_cache_record
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(record);
|
||||
}
|
||||
self.usage_recorded = true;
|
||||
StreamEvent::MessageStop(_) if !self.usage_recorded => {
|
||||
if let (Some(prompt_cache), Some(usage)) =
|
||||
(&self.prompt_cache, self.latest_usage.as_ref())
|
||||
{
|
||||
let record = prompt_cache.record_usage(&self.request, usage);
|
||||
*self
|
||||
.last_prompt_cache_record
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(record);
|
||||
}
|
||||
self.usage_recorded = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![allow(clippy::cast_possible_truncation)]
|
||||
#![allow(dead_code)]
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
@@ -28,7 +29,7 @@ pub trait Provider {
|
||||
) -> ProviderFuture<'a, Self::Stream>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
pub enum ProviderKind {
|
||||
Anthropic,
|
||||
Xai,
|
||||
@@ -49,6 +50,74 @@ pub struct ModelTokenLimit {
|
||||
pub context_window_tokens: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProviderWireProtocol {
|
||||
AnthropicMessages,
|
||||
OpenAiChatCompletions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProviderFeatureSupport {
|
||||
Supported,
|
||||
Unsupported,
|
||||
PassthroughAsTool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct ProviderCapabilityReport {
|
||||
pub provider: ProviderKind,
|
||||
pub wire_protocol: ProviderWireProtocol,
|
||||
pub auth_env: &'static str,
|
||||
pub base_url_env: &'static str,
|
||||
pub default_base_url: &'static str,
|
||||
pub tool_calls: ProviderFeatureSupport,
|
||||
pub streaming: ProviderFeatureSupport,
|
||||
pub streaming_usage: ProviderFeatureSupport,
|
||||
pub prompt_cache: ProviderFeatureSupport,
|
||||
pub custom_parameters: ProviderFeatureSupport,
|
||||
pub reasoning_effort: ProviderFeatureSupport,
|
||||
pub reasoning_content_history: ProviderFeatureSupport,
|
||||
pub fixed_sampling_reasoning_models: ProviderFeatureSupport,
|
||||
pub web_search: ProviderFeatureSupport,
|
||||
pub web_fetch: ProviderFeatureSupport,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProviderDiagnosticSeverity {
|
||||
Info,
|
||||
Warning,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct ProviderDiagnostic {
|
||||
pub code: &'static str,
|
||||
pub severity: ProviderDiagnosticSeverity,
|
||||
pub message: String,
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct ProviderDiagnostics {
|
||||
pub requested_model: String,
|
||||
pub resolved_model: String,
|
||||
pub provider: ProviderKind,
|
||||
pub auth_env: &'static str,
|
||||
pub base_url_env: &'static str,
|
||||
pub default_base_url: &'static str,
|
||||
pub openai_compatible: bool,
|
||||
pub reasoning_model: bool,
|
||||
pub preserves_reasoning_content_in_history: bool,
|
||||
pub strips_tuning_params: bool,
|
||||
pub supports_stream_usage: bool,
|
||||
pub honors_proxy_env: bool,
|
||||
pub supports_extra_body_params: bool,
|
||||
pub preserves_slash_model_ids_on_custom_base_url: bool,
|
||||
}
|
||||
|
||||
const MODEL_REGISTRY: &[(&str, ProviderMetadata)] = &[
|
||||
(
|
||||
"opus",
|
||||
@@ -219,6 +288,55 @@ pub fn metadata_for_model(model: &str) -> Option<ProviderMetadata> {
|
||||
None
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn provider_diagnostics_for_model(model: &str) -> ProviderDiagnostics {
|
||||
let resolved_model = resolve_model_alias(model);
|
||||
let metadata =
|
||||
metadata_for_model(&resolved_model).unwrap_or_else(|| {
|
||||
match detect_provider_kind(&resolved_model) {
|
||||
ProviderKind::Anthropic => ProviderMetadata {
|
||||
provider: ProviderKind::Anthropic,
|
||||
auth_env: "ANTHROPIC_API_KEY",
|
||||
base_url_env: "ANTHROPIC_BASE_URL",
|
||||
default_base_url: anthropic::DEFAULT_BASE_URL,
|
||||
},
|
||||
ProviderKind::Xai => ProviderMetadata {
|
||||
provider: ProviderKind::Xai,
|
||||
auth_env: "XAI_API_KEY",
|
||||
base_url_env: "XAI_BASE_URL",
|
||||
default_base_url: openai_compat::DEFAULT_XAI_BASE_URL,
|
||||
},
|
||||
ProviderKind::OpenAi => ProviderMetadata {
|
||||
provider: ProviderKind::OpenAi,
|
||||
auth_env: "OPENAI_API_KEY",
|
||||
base_url_env: "OPENAI_BASE_URL",
|
||||
default_base_url: openai_compat::DEFAULT_OPENAI_BASE_URL,
|
||||
},
|
||||
}
|
||||
});
|
||||
let openai_compatible = matches!(metadata.provider, ProviderKind::OpenAi | ProviderKind::Xai);
|
||||
let reasoning_model = openai_compatible && openai_compat::is_reasoning_model(&resolved_model);
|
||||
|
||||
ProviderDiagnostics {
|
||||
requested_model: model.to_string(),
|
||||
resolved_model: resolved_model.clone(),
|
||||
provider: metadata.provider,
|
||||
auth_env: metadata.auth_env,
|
||||
base_url_env: metadata.base_url_env,
|
||||
default_base_url: metadata.default_base_url,
|
||||
openai_compatible,
|
||||
reasoning_model,
|
||||
preserves_reasoning_content_in_history: openai_compatible
|
||||
&& openai_compat::model_requires_reasoning_content_in_history(&resolved_model),
|
||||
strips_tuning_params: reasoning_model,
|
||||
supports_stream_usage: metadata.provider == ProviderKind::OpenAi
|
||||
&& metadata.default_base_url == openai_compat::DEFAULT_OPENAI_BASE_URL,
|
||||
honors_proxy_env: true,
|
||||
supports_extra_body_params: openai_compatible,
|
||||
preserves_slash_model_ids_on_custom_base_url: metadata.provider == ProviderKind::OpenAi,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn detect_provider_kind(model: &str) -> ProviderKind {
|
||||
if let Some(metadata) = metadata_for_model(model) {
|
||||
@@ -251,18 +369,230 @@ pub fn detect_provider_kind(model: &str) -> ProviderKind {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn max_tokens_for_model(model: &str) -> u32 {
|
||||
model_token_limit(model).map_or_else(
|
||||
|| {
|
||||
let canonical = resolve_model_alias(model);
|
||||
if canonical.contains("opus") {
|
||||
32_000
|
||||
pub const fn model_family_identity_for_kind(kind: ProviderKind) -> runtime::ModelFamilyIdentity {
|
||||
match kind {
|
||||
ProviderKind::Anthropic => runtime::ModelFamilyIdentity::Claude,
|
||||
ProviderKind::Xai | ProviderKind::OpenAi => runtime::ModelFamilyIdentity::Generic,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn model_family_identity_for(model: &str) -> runtime::ModelFamilyIdentity {
|
||||
model_family_identity_for_kind(detect_provider_kind(model))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn provider_capabilities_for_model(model: &str) -> ProviderCapabilityReport {
|
||||
let metadata = metadata_for_model(model).unwrap_or_else(|| {
|
||||
let provider = detect_provider_kind(model);
|
||||
metadata_for_provider_kind(provider)
|
||||
});
|
||||
|
||||
let (
|
||||
wire_protocol,
|
||||
streaming_usage,
|
||||
prompt_cache,
|
||||
custom_parameters,
|
||||
reasoning_effort,
|
||||
reasoning_content_history,
|
||||
fixed_sampling_reasoning_models,
|
||||
) = match metadata.provider {
|
||||
ProviderKind::Anthropic => (
|
||||
ProviderWireProtocol::AnthropicMessages,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
ProviderFeatureSupport::Supported,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
),
|
||||
ProviderKind::Xai => (
|
||||
ProviderWireProtocol::OpenAiChatCompletions,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
ProviderFeatureSupport::Supported,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
ProviderFeatureSupport::Supported,
|
||||
),
|
||||
ProviderKind::OpenAi => (
|
||||
ProviderWireProtocol::OpenAiChatCompletions,
|
||||
ProviderFeatureSupport::Supported,
|
||||
ProviderFeatureSupport::Unsupported,
|
||||
ProviderFeatureSupport::Supported,
|
||||
ProviderFeatureSupport::Supported,
|
||||
if openai_compat::model_requires_reasoning_content_in_history(model) {
|
||||
ProviderFeatureSupport::Supported
|
||||
} else {
|
||||
64_000
|
||||
}
|
||||
ProviderFeatureSupport::Unsupported
|
||||
},
|
||||
ProviderFeatureSupport::Supported,
|
||||
),
|
||||
};
|
||||
|
||||
ProviderCapabilityReport {
|
||||
provider: metadata.provider,
|
||||
wire_protocol,
|
||||
auth_env: metadata.auth_env,
|
||||
base_url_env: metadata.base_url_env,
|
||||
default_base_url: metadata.default_base_url,
|
||||
tool_calls: ProviderFeatureSupport::Supported,
|
||||
streaming: ProviderFeatureSupport::Supported,
|
||||
streaming_usage,
|
||||
prompt_cache,
|
||||
custom_parameters,
|
||||
reasoning_effort,
|
||||
reasoning_content_history,
|
||||
fixed_sampling_reasoning_models,
|
||||
web_search: ProviderFeatureSupport::PassthroughAsTool,
|
||||
web_fetch: ProviderFeatureSupport::PassthroughAsTool,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn provider_diagnostics_for_request(request: &MessageRequest) -> Vec<ProviderDiagnostic> {
|
||||
let capabilities = provider_capabilities_for_model(&request.model);
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
if request.reasoning_effort.is_some()
|
||||
&& capabilities.reasoning_effort == ProviderFeatureSupport::Unsupported
|
||||
{
|
||||
diagnostics.push(ProviderDiagnostic {
|
||||
code: "reasoning_effort_unsupported",
|
||||
severity: ProviderDiagnosticSeverity::Warning,
|
||||
message: format!(
|
||||
"{} does not map `reasoning_effort` for model `{}`.",
|
||||
provider_label(capabilities.provider),
|
||||
request.model
|
||||
),
|
||||
action: "Remove `reasoning_effort` or route to an OpenAI-compatible reasoning model such as `openai/o4-mini`.".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if openai_compat::is_reasoning_model(&request.model)
|
||||
&& has_openai_tuning_parameters(request)
|
||||
&& capabilities.fixed_sampling_reasoning_models == ProviderFeatureSupport::Supported
|
||||
{
|
||||
diagnostics.push(ProviderDiagnostic {
|
||||
code: "reasoning_model_fixed_sampling",
|
||||
severity: ProviderDiagnosticSeverity::Info,
|
||||
message: format!(
|
||||
"Model `{}` is treated as a fixed-sampling reasoning model; tuning parameters are omitted before the provider call.",
|
||||
request.model
|
||||
),
|
||||
action: "Leave temperature/top_p/frequency_penalty/presence_penalty unset for reasoning models to match provider validation rules.".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if openai_compat::model_requires_reasoning_content_in_history(&request.model) {
|
||||
diagnostics.push(ProviderDiagnostic {
|
||||
code: "deepseek_v4_reasoning_history",
|
||||
severity: ProviderDiagnosticSeverity::Info,
|
||||
message: format!(
|
||||
"Model `{}` requires assistant thinking history to be echoed as `reasoning_content`.",
|
||||
request.model
|
||||
),
|
||||
action: "Keep prior assistant Thinking blocks in history; the OpenAI-compatible serializer will emit `reasoning_content` for DeepSeek V4 models.".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if declares_tool(request, "web_search") {
|
||||
diagnostics.push(web_passthrough_diagnostic(
|
||||
"web_search_passthrough_tool",
|
||||
"web_search",
|
||||
capabilities.provider,
|
||||
));
|
||||
}
|
||||
if declares_tool(request, "web_fetch") {
|
||||
diagnostics.push(web_passthrough_diagnostic(
|
||||
"web_fetch_passthrough_tool",
|
||||
"web_fetch",
|
||||
capabilities.provider,
|
||||
));
|
||||
}
|
||||
|
||||
diagnostics
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn metadata_for_provider_kind(provider: ProviderKind) -> ProviderMetadata {
|
||||
match provider {
|
||||
ProviderKind::Anthropic => ProviderMetadata {
|
||||
provider,
|
||||
auth_env: "ANTHROPIC_API_KEY",
|
||||
base_url_env: "ANTHROPIC_BASE_URL",
|
||||
default_base_url: anthropic::DEFAULT_BASE_URL,
|
||||
},
|
||||
|limit| limit.max_output_tokens,
|
||||
)
|
||||
ProviderKind::Xai => ProviderMetadata {
|
||||
provider,
|
||||
auth_env: "XAI_API_KEY",
|
||||
base_url_env: "XAI_BASE_URL",
|
||||
default_base_url: openai_compat::DEFAULT_XAI_BASE_URL,
|
||||
},
|
||||
ProviderKind::OpenAi => ProviderMetadata {
|
||||
provider,
|
||||
auth_env: "OPENAI_API_KEY",
|
||||
base_url_env: "OPENAI_BASE_URL",
|
||||
default_base_url: openai_compat::DEFAULT_OPENAI_BASE_URL,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
const fn provider_label(provider: ProviderKind) -> &'static str {
|
||||
match provider {
|
||||
ProviderKind::Anthropic => "Anthropic",
|
||||
ProviderKind::Xai => "xAI",
|
||||
ProviderKind::OpenAi => "OpenAI-compatible",
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn has_openai_tuning_parameters(request: &MessageRequest) -> bool {
|
||||
request.temperature.is_some()
|
||||
|| request.top_p.is_some()
|
||||
|| request.frequency_penalty.is_some()
|
||||
|| request.presence_penalty.is_some()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn declares_tool(request: &MessageRequest, tool_name: &str) -> bool {
|
||||
request.tools.as_ref().is_some_and(|tools| {
|
||||
tools
|
||||
.iter()
|
||||
.any(|tool| tool.name.eq_ignore_ascii_case(tool_name))
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn web_passthrough_diagnostic(
|
||||
code: &'static str,
|
||||
tool_name: &'static str,
|
||||
provider: ProviderKind,
|
||||
) -> ProviderDiagnostic {
|
||||
ProviderDiagnostic {
|
||||
code,
|
||||
severity: ProviderDiagnosticSeverity::Info,
|
||||
message: format!(
|
||||
"`{tool_name}` is exposed to {} as a normal function tool, not as a provider-native web capability.",
|
||||
provider_label(provider)
|
||||
),
|
||||
action: format!(
|
||||
"Provide a local `{tool_name}` tool implementation or route through a provider adapter that explicitly supports native web tools."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn max_tokens_for_model(model: &str) -> u32 {
|
||||
let canonical = resolve_model_alias(model);
|
||||
let heuristic = if canonical.contains("opus") {
|
||||
32_000
|
||||
} else {
|
||||
64_000
|
||||
};
|
||||
|
||||
model_token_limit(model).map_or(heuristic, |limit| heuristic.min(limit.max_output_tokens))
|
||||
}
|
||||
|
||||
/// Returns the effective max output tokens for a model, preferring a plugin
|
||||
@@ -276,7 +606,8 @@ pub fn max_tokens_for_model_with_override(model: &str, plugin_override: Option<u
|
||||
#[must_use]
|
||||
pub fn model_token_limit(model: &str) -> Option<ModelTokenLimit> {
|
||||
let canonical = resolve_model_alias(model);
|
||||
match canonical.as_str() {
|
||||
let base_model = canonical.rsplit('/').next().unwrap_or(canonical.as_str());
|
||||
match base_model {
|
||||
"claude-opus-4-6" => Some(ModelTokenLimit {
|
||||
max_output_tokens: 32_000,
|
||||
context_window_tokens: 200_000,
|
||||
@@ -289,6 +620,20 @@ pub fn model_token_limit(model: &str) -> Option<ModelTokenLimit> {
|
||||
max_output_tokens: 64_000,
|
||||
context_window_tokens: 131_072,
|
||||
}),
|
||||
// GPT-4.1 family via the OpenAI API.
|
||||
"gpt-4.1" | "gpt-4.1-mini" | "gpt-4.1-nano" => Some(ModelTokenLimit {
|
||||
max_output_tokens: 32_768,
|
||||
context_window_tokens: 1_047_576,
|
||||
}),
|
||||
// GPT-5.4 family via the OpenAI API.
|
||||
"gpt-5.4" => Some(ModelTokenLimit {
|
||||
max_output_tokens: 128_000,
|
||||
context_window_tokens: 1_000_000,
|
||||
}),
|
||||
"gpt-5.4-mini" | "gpt-5.4-nano" => Some(ModelTokenLimit {
|
||||
max_output_tokens: 128_000,
|
||||
context_window_tokens: 400_000,
|
||||
}),
|
||||
// Kimi models via DashScope (Moonshot AI)
|
||||
// Source: https://platform.moonshot.cn/docs/intro
|
||||
"kimi-k2.5" | "kimi-k1.5" => Some(ModelTokenLimit {
|
||||
@@ -470,8 +815,10 @@ mod tests {
|
||||
use super::{
|
||||
anthropic_missing_credentials, anthropic_missing_credentials_hint, detect_provider_kind,
|
||||
load_dotenv_file, max_tokens_for_model, max_tokens_for_model_with_override,
|
||||
model_token_limit, parse_dotenv, preflight_message_request, resolve_model_alias,
|
||||
ProviderKind,
|
||||
model_family_identity_for, model_family_identity_for_kind, model_token_limit, parse_dotenv,
|
||||
preflight_message_request, provider_capabilities_for_model,
|
||||
provider_diagnostics_for_request, resolve_model_alias, ProviderFeatureSupport,
|
||||
ProviderKind, ProviderWireProtocol,
|
||||
};
|
||||
|
||||
/// Serializes every test in this module that mutates process-wide
|
||||
@@ -530,6 +877,141 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_provider_kind_to_model_family_identity() {
|
||||
// given: each supported provider kind
|
||||
let anthropic = ProviderKind::Anthropic;
|
||||
let openai = ProviderKind::OpenAi;
|
||||
let xai = ProviderKind::Xai;
|
||||
|
||||
// when: converting provider kinds to prompt model family identities
|
||||
let anthropic_identity = model_family_identity_for_kind(anthropic);
|
||||
let openai_identity = model_family_identity_for_kind(openai);
|
||||
let xai_identity = model_family_identity_for_kind(xai);
|
||||
|
||||
// then: Anthropic stays Claude and OpenAI-compatible providers are generic
|
||||
assert_eq!(anthropic_identity, runtime::ModelFamilyIdentity::Claude);
|
||||
assert_eq!(openai_identity, runtime::ModelFamilyIdentity::Generic);
|
||||
assert_eq!(xai_identity, runtime::ModelFamilyIdentity::Generic);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_model_name_to_model_family_identity() {
|
||||
// given: Anthropic, OpenAI-compatible, and xAI model names
|
||||
let claude_model = "claude-opus-4-6";
|
||||
let openai_model = "openai/gpt-4.1-mini";
|
||||
let xai_model = "grok-3";
|
||||
|
||||
// when: detecting prompt model family identities from model names
|
||||
let claude_identity = model_family_identity_for(claude_model);
|
||||
let openai_identity = model_family_identity_for(openai_model);
|
||||
let xai_identity = model_family_identity_for(xai_model);
|
||||
|
||||
// then: Anthropic stays Claude and OpenAI-compatible providers are generic
|
||||
assert_eq!(claude_identity, runtime::ModelFamilyIdentity::Claude);
|
||||
assert_eq!(openai_identity, runtime::ModelFamilyIdentity::Generic);
|
||||
assert_eq!(xai_identity, runtime::ModelFamilyIdentity::Generic);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_capability_matrix_snapshots_openai_compat_differences() {
|
||||
let openai = provider_capabilities_for_model("openai/gpt-4.1-mini");
|
||||
assert_eq!(openai.provider, ProviderKind::OpenAi);
|
||||
assert_eq!(
|
||||
openai.wire_protocol,
|
||||
ProviderWireProtocol::OpenAiChatCompletions
|
||||
);
|
||||
assert_eq!(openai.auth_env, "OPENAI_API_KEY");
|
||||
assert_eq!(openai.streaming_usage, ProviderFeatureSupport::Supported);
|
||||
assert_eq!(openai.reasoning_effort, ProviderFeatureSupport::Supported);
|
||||
assert_eq!(openai.web_search, ProviderFeatureSupport::PassthroughAsTool);
|
||||
assert_eq!(openai.web_fetch, ProviderFeatureSupport::PassthroughAsTool);
|
||||
|
||||
let deepseek = provider_capabilities_for_model("openai/deepseek-v4-pro");
|
||||
assert_eq!(
|
||||
deepseek.reasoning_content_history,
|
||||
ProviderFeatureSupport::Supported
|
||||
);
|
||||
|
||||
let xai = provider_capabilities_for_model("grok-3");
|
||||
assert_eq!(xai.provider, ProviderKind::Xai);
|
||||
assert_eq!(xai.auth_env, "XAI_API_KEY");
|
||||
assert_eq!(xai.reasoning_effort, ProviderFeatureSupport::Unsupported);
|
||||
assert_eq!(xai.streaming_usage, ProviderFeatureSupport::Unsupported);
|
||||
|
||||
let anthropic = provider_capabilities_for_model("claude-sonnet-4-6");
|
||||
assert_eq!(anthropic.provider, ProviderKind::Anthropic);
|
||||
assert_eq!(
|
||||
anthropic.wire_protocol,
|
||||
ProviderWireProtocol::AnthropicMessages
|
||||
);
|
||||
assert_eq!(anthropic.prompt_cache, ProviderFeatureSupport::Supported);
|
||||
assert_eq!(
|
||||
anthropic.custom_parameters,
|
||||
ProviderFeatureSupport::Unsupported
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_diagnostics_explain_deepseek_reasoning_and_web_tool_passthrough() {
|
||||
let request = MessageRequest {
|
||||
model: "openai/deepseek-v4-pro".to_string(),
|
||||
max_tokens: 1024,
|
||||
messages: vec![InputMessage::user_text("research this")],
|
||||
tools: Some(vec![
|
||||
ToolDefinition {
|
||||
name: "web_search".to_string(),
|
||||
description: Some("Search the web".to_string()),
|
||||
input_schema: json!({"type": "object"}),
|
||||
},
|
||||
ToolDefinition {
|
||||
name: "web_fetch".to_string(),
|
||||
description: Some("Fetch a URL".to_string()),
|
||||
input_schema: json!({"type": "object"}),
|
||||
},
|
||||
]),
|
||||
stream: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let diagnostics = provider_diagnostics_for_request(&request);
|
||||
let codes = diagnostics
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.code)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(codes.contains(&"deepseek_v4_reasoning_history"));
|
||||
assert!(codes.contains(&"web_search_passthrough_tool"));
|
||||
assert!(codes.contains(&"web_fetch_passthrough_tool"));
|
||||
assert!(diagnostics
|
||||
.iter()
|
||||
.any(|diagnostic| diagnostic.action.contains("provider adapter")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_diagnostics_warn_for_unsupported_reasoning_effort() {
|
||||
let request = MessageRequest {
|
||||
model: "grok-3-mini".to_string(),
|
||||
max_tokens: 1024,
|
||||
messages: vec![InputMessage::user_text("think")],
|
||||
reasoning_effort: Some("high".to_string()),
|
||||
temperature: Some(0.7),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let diagnostics = provider_diagnostics_for_request(&request);
|
||||
let codes = diagnostics
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.code)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(codes.contains(&"reasoning_effort_unsupported"));
|
||||
assert!(codes.contains(&"reasoning_model_fixed_sampling"));
|
||||
assert!(diagnostics.iter().any(|diagnostic| diagnostic
|
||||
.message
|
||||
.contains("does not map `reasoning_effort`")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openai_namespaced_model_routes_to_openai_not_anthropic() {
|
||||
// Regression: "openai/gpt-4.1-mini" was misrouted to Anthropic when
|
||||
@@ -610,10 +1092,32 @@ mod tests {
|
||||
assert_eq!(super::resolve_model_alias("KIMI"), "kimi-k2.5"); // case insensitive
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_diagnostics_explain_openai_compatible_capabilities() {
|
||||
let diagnostics = super::provider_diagnostics_for_model("openai/deepseek-v4-pro");
|
||||
|
||||
assert_eq!(diagnostics.provider, ProviderKind::OpenAi);
|
||||
assert_eq!(diagnostics.auth_env, "OPENAI_API_KEY");
|
||||
assert!(diagnostics.openai_compatible);
|
||||
assert!(diagnostics.preserves_reasoning_content_in_history);
|
||||
assert!(diagnostics.supports_extra_body_params);
|
||||
assert!(diagnostics.honors_proxy_env);
|
||||
assert!(diagnostics.preserves_slash_model_ids_on_custom_base_url);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_existing_max_token_heuristic() {
|
||||
assert_eq!(max_tokens_for_model("opus"), 32_000);
|
||||
assert_eq!(max_tokens_for_model("grok-3"), 64_000);
|
||||
assert_eq!(max_tokens_for_model("gpt-5.4"), 64_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caps_default_max_tokens_to_openai_model_limits() {
|
||||
assert_eq!(max_tokens_for_model("gpt-4.1-mini"), 32_768);
|
||||
assert_eq!(max_tokens_for_model("openai/gpt-4.1-mini"), 32_768);
|
||||
assert_eq!(max_tokens_for_model("gpt-5.4"), 64_000);
|
||||
assert_eq!(max_tokens_for_model("openai/gpt-5.4"), 64_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -680,6 +1184,18 @@ mod tests {
|
||||
.context_window_tokens,
|
||||
131_072
|
||||
);
|
||||
assert_eq!(
|
||||
model_token_limit("openai/gpt-4.1-mini")
|
||||
.expect("openai/gpt-4.1-mini should be registered")
|
||||
.context_window_tokens,
|
||||
1_047_576
|
||||
);
|
||||
assert_eq!(
|
||||
model_token_limit("gpt-5.4")
|
||||
.expect("gpt-5.4 should be registered")
|
||||
.context_window_tokens,
|
||||
1_000_000
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -728,6 +1244,42 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preflight_blocks_oversized_requests_for_gpt_5_4() {
|
||||
let request = MessageRequest {
|
||||
model: "gpt-5.4".to_string(),
|
||||
max_tokens: 64_000,
|
||||
messages: vec![InputMessage {
|
||||
role: "user".to_string(),
|
||||
content: vec![InputContentBlock::Text {
|
||||
text: "x".repeat(3_900_000),
|
||||
}],
|
||||
}],
|
||||
system: Some("Keep the answer short.".to_string()),
|
||||
tools: None,
|
||||
tool_choice: None,
|
||||
stream: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let error = preflight_message_request(&request)
|
||||
.expect_err("oversized gpt-5.4 request should be rejected before the provider call");
|
||||
|
||||
match error {
|
||||
ApiError::ContextWindowExceeded {
|
||||
model,
|
||||
requested_output_tokens,
|
||||
context_window_tokens,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(model, "gpt-5.4");
|
||||
assert_eq!(requested_output_tokens, 64_000);
|
||||
assert_eq!(context_window_tokens, 1_000_000);
|
||||
}
|
||||
other => panic!("expected context-window preflight failure, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preflight_skips_unknown_models() {
|
||||
let request = MessageRequest {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
@@ -145,6 +146,12 @@ impl OpenAiCompatClient {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_http_client(mut self, http: reqwest::Client) -> Self {
|
||||
self.http = http;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_retry_policy(
|
||||
mut self,
|
||||
@@ -267,14 +274,18 @@ impl OpenAiCompatClient {
|
||||
request: &MessageRequest,
|
||||
) -> Result<reqwest::Response, ApiError> {
|
||||
// Pre-flight check: verify request body size against provider limits
|
||||
check_request_body_size(request, self.config())?;
|
||||
check_request_body_size_for_base_url(request, self.config(), &self.base_url)?;
|
||||
|
||||
let request_url = chat_completions_endpoint(&self.base_url);
|
||||
self.http
|
||||
.post(&request_url)
|
||||
.header("content-type", "application/json")
|
||||
.bearer_auth(&self.api_key)
|
||||
.json(&build_chat_completion_request(request, self.config()))
|
||||
.json(&build_chat_completion_request_for_base_url(
|
||||
request,
|
||||
self.config(),
|
||||
&self.base_url,
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.map_err(ApiError::from)
|
||||
@@ -327,8 +338,9 @@ fn jitter_for_base(base: Duration) -> Duration {
|
||||
}
|
||||
let raw_nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|elapsed| u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX))
|
||||
.unwrap_or(0);
|
||||
.map_or(0, |elapsed| {
|
||||
u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX)
|
||||
});
|
||||
let tick = JITTER_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
let mut mixed = raw_nanos
|
||||
.wrapping_add(tick)
|
||||
@@ -443,6 +455,8 @@ struct StreamState {
|
||||
stop_reason: Option<String>,
|
||||
usage: Option<Usage>,
|
||||
tool_calls: BTreeMap<u32, ToolCallState>,
|
||||
thinking_started: bool,
|
||||
thinking_finished: bool,
|
||||
}
|
||||
|
||||
impl StreamState {
|
||||
@@ -456,9 +470,12 @@ impl StreamState {
|
||||
stop_reason: None,
|
||||
usage: None,
|
||||
tool_calls: BTreeMap::new(),
|
||||
thinking_started: false,
|
||||
thinking_finished: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn ingest_chunk(&mut self, chunk: ChatCompletionChunk) -> Result<Vec<StreamEvent>, ApiError> {
|
||||
let mut events = Vec::new();
|
||||
if !self.message_started {
|
||||
@@ -484,44 +501,65 @@ impl StreamState {
|
||||
}
|
||||
|
||||
if let Some(usage) = chunk.usage {
|
||||
self.usage = Some(Usage {
|
||||
input_tokens: usage.prompt_tokens,
|
||||
cache_creation_input_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
output_tokens: usage.completion_tokens,
|
||||
});
|
||||
self.usage = Some(usage.normalized());
|
||||
}
|
||||
|
||||
for choice in chunk.choices {
|
||||
if let Some(reasoning) = choice
|
||||
.delta
|
||||
.reasoning_content
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
if !self.thinking_started {
|
||||
self.thinking_started = true;
|
||||
events.push(StreamEvent::ContentBlockStart(ContentBlockStartEvent {
|
||||
index: 0,
|
||||
content_block: OutputContentBlock::Thinking {
|
||||
thinking: String::new(),
|
||||
signature: None,
|
||||
},
|
||||
}));
|
||||
}
|
||||
events.push(StreamEvent::ContentBlockDelta(ContentBlockDeltaEvent {
|
||||
index: 0,
|
||||
delta: ContentBlockDelta::ThinkingDelta {
|
||||
thinking: reasoning,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
if let Some(content) = choice.delta.content.filter(|value| !value.is_empty()) {
|
||||
self.close_thinking(&mut events);
|
||||
if !self.text_started {
|
||||
self.text_started = true;
|
||||
events.push(StreamEvent::ContentBlockStart(ContentBlockStartEvent {
|
||||
index: 0,
|
||||
index: self.text_block_index(),
|
||||
content_block: OutputContentBlock::Text {
|
||||
text: String::new(),
|
||||
},
|
||||
}));
|
||||
}
|
||||
events.push(StreamEvent::ContentBlockDelta(ContentBlockDeltaEvent {
|
||||
index: 0,
|
||||
index: self.text_block_index(),
|
||||
delta: ContentBlockDelta::TextDelta { text: content },
|
||||
}));
|
||||
}
|
||||
|
||||
for tool_call in choice.delta.tool_calls {
|
||||
self.close_thinking(&mut events);
|
||||
let tool_index_offset = self.tool_index_offset();
|
||||
let state = self.tool_calls.entry(tool_call.index).or_default();
|
||||
state.apply(tool_call);
|
||||
let block_index = state.block_index();
|
||||
let block_index = state.block_index(tool_index_offset);
|
||||
if !state.started {
|
||||
if let Some(start_event) = state.start_event()? {
|
||||
if let Some(start_event) = state.start_event(tool_index_offset)? {
|
||||
state.started = true;
|
||||
events.push(StreamEvent::ContentBlockStart(start_event));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(delta_event) = state.delta_event() {
|
||||
if let Some(delta_event) = state.delta_event(tool_index_offset) {
|
||||
events.push(StreamEvent::ContentBlockDelta(delta_event));
|
||||
}
|
||||
if choice.finish_reason.as_deref() == Some("tool_calls") && !state.stopped {
|
||||
@@ -535,11 +573,12 @@ impl StreamState {
|
||||
if let Some(finish_reason) = choice.finish_reason {
|
||||
self.stop_reason = Some(normalize_finish_reason(&finish_reason));
|
||||
if finish_reason == "tool_calls" {
|
||||
let tool_index_offset = self.tool_index_offset();
|
||||
for state in self.tool_calls.values_mut() {
|
||||
if state.started && !state.stopped {
|
||||
state.stopped = true;
|
||||
events.push(StreamEvent::ContentBlockStop(ContentBlockStopEvent {
|
||||
index: state.block_index(),
|
||||
index: state.block_index(tool_index_offset),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -557,19 +596,21 @@ impl StreamState {
|
||||
self.finished = true;
|
||||
|
||||
let mut events = Vec::new();
|
||||
self.close_thinking(&mut events);
|
||||
if self.text_started && !self.text_finished {
|
||||
self.text_finished = true;
|
||||
events.push(StreamEvent::ContentBlockStop(ContentBlockStopEvent {
|
||||
index: 0,
|
||||
index: self.text_block_index(),
|
||||
}));
|
||||
}
|
||||
|
||||
let tool_index_offset = self.tool_index_offset();
|
||||
for state in self.tool_calls.values_mut() {
|
||||
if !state.started {
|
||||
if let Some(start_event) = state.start_event()? {
|
||||
if let Some(start_event) = state.start_event(tool_index_offset)? {
|
||||
state.started = true;
|
||||
events.push(StreamEvent::ContentBlockStart(start_event));
|
||||
if let Some(delta_event) = state.delta_event() {
|
||||
if let Some(delta_event) = state.delta_event(tool_index_offset) {
|
||||
events.push(StreamEvent::ContentBlockDelta(delta_event));
|
||||
}
|
||||
}
|
||||
@@ -577,7 +618,7 @@ impl StreamState {
|
||||
if state.started && !state.stopped {
|
||||
state.stopped = true;
|
||||
events.push(StreamEvent::ContentBlockStop(ContentBlockStopEvent {
|
||||
index: state.block_index(),
|
||||
index: state.block_index(tool_index_offset),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -603,6 +644,31 @@ impl StreamState {
|
||||
}
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
fn close_thinking(&mut self, events: &mut Vec<StreamEvent>) {
|
||||
if self.thinking_started && !self.thinking_finished {
|
||||
self.thinking_finished = true;
|
||||
events.push(StreamEvent::ContentBlockStop(ContentBlockStopEvent {
|
||||
index: 0,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const fn text_block_index(&self) -> u32 {
|
||||
if self.thinking_started {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
const fn tool_index_offset(&self) -> u32 {
|
||||
if self.thinking_started {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -630,12 +696,12 @@ impl ToolCallState {
|
||||
}
|
||||
}
|
||||
|
||||
const fn block_index(&self) -> u32 {
|
||||
self.openai_index + 1
|
||||
const fn block_index(&self, offset: u32) -> u32 {
|
||||
self.openai_index + offset
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn start_event(&self) -> Result<Option<ContentBlockStartEvent>, ApiError> {
|
||||
fn start_event(&self, offset: u32) -> Result<Option<ContentBlockStartEvent>, ApiError> {
|
||||
let Some(name) = self.name.clone() else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -644,7 +710,7 @@ impl ToolCallState {
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("tool_call_{}", self.openai_index));
|
||||
Ok(Some(ContentBlockStartEvent {
|
||||
index: self.block_index(),
|
||||
index: self.block_index(offset),
|
||||
content_block: OutputContentBlock::ToolUse {
|
||||
id,
|
||||
name,
|
||||
@@ -653,14 +719,14 @@ impl ToolCallState {
|
||||
}))
|
||||
}
|
||||
|
||||
fn delta_event(&mut self) -> Option<ContentBlockDeltaEvent> {
|
||||
fn delta_event(&mut self, offset: u32) -> Option<ContentBlockDeltaEvent> {
|
||||
if self.emitted_len >= self.arguments.len() {
|
||||
return None;
|
||||
}
|
||||
let delta = self.arguments[self.emitted_len..].to_string();
|
||||
self.emitted_len = self.arguments.len();
|
||||
Some(ContentBlockDeltaEvent {
|
||||
index: self.block_index(),
|
||||
index: self.block_index(offset),
|
||||
delta: ContentBlockDelta::InputJsonDelta {
|
||||
partial_json: delta,
|
||||
},
|
||||
@@ -690,6 +756,8 @@ struct ChatMessage {
|
||||
#[serde(default)]
|
||||
content: Option<String>,
|
||||
#[serde(default)]
|
||||
reasoning_content: Option<String>,
|
||||
#[serde(default)]
|
||||
tool_calls: Vec<ResponseToolCall>,
|
||||
}
|
||||
|
||||
@@ -711,6 +779,29 @@ struct OpenAiUsage {
|
||||
prompt_tokens: u32,
|
||||
#[serde(default)]
|
||||
completion_tokens: u32,
|
||||
#[serde(default)]
|
||||
prompt_tokens_details: Option<OpenAiPromptTokensDetails>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct OpenAiPromptTokensDetails {
|
||||
#[serde(default)]
|
||||
cached_tokens: u32,
|
||||
}
|
||||
|
||||
impl OpenAiUsage {
|
||||
fn normalized(&self) -> Usage {
|
||||
let cached_tokens = self
|
||||
.prompt_tokens_details
|
||||
.as_ref()
|
||||
.map_or(0, |details| details.cached_tokens);
|
||||
Usage {
|
||||
input_tokens: self.prompt_tokens.saturating_sub(cached_tokens),
|
||||
cache_creation_input_tokens: 0,
|
||||
cache_read_input_tokens: cached_tokens,
|
||||
output_tokens: self.completion_tokens,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -735,6 +826,8 @@ struct ChunkChoice {
|
||||
struct ChunkDelta {
|
||||
#[serde(default)]
|
||||
content: Option<String>,
|
||||
#[serde(default)]
|
||||
reasoning_content: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
|
||||
tool_calls: Vec<DeltaToolCall>,
|
||||
}
|
||||
@@ -793,9 +886,19 @@ pub fn is_reasoning_model(model: &str) -> bool {
|
||||
|| canonical.contains("thinking")
|
||||
}
|
||||
|
||||
/// Returns true for OpenAI-compatible `DeepSeek` V4 models that require prior
|
||||
/// assistant reasoning to be echoed back as `reasoning_content` in history.
|
||||
#[must_use]
|
||||
pub fn model_requires_reasoning_content_in_history(model: &str) -> bool {
|
||||
let lowered = model.to_ascii_lowercase();
|
||||
let canonical = lowered.rsplit('/').next().unwrap_or(lowered.as_str());
|
||||
canonical.starts_with("deepseek-v4")
|
||||
}
|
||||
|
||||
/// Strip routing prefix (e.g., "openai/gpt-4" → "gpt-4") for the wire.
|
||||
/// The prefix is used only to select transport; the backend expects the
|
||||
/// bare model id.
|
||||
#[allow(dead_code)]
|
||||
fn strip_routing_prefix(model: &str) -> &str {
|
||||
if let Some(pos) = model.find('/') {
|
||||
let prefix = &model[..pos];
|
||||
@@ -811,10 +914,51 @@ fn strip_routing_prefix(model: &str) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
fn wire_model_for_base_url<'a>(
|
||||
model: &'a str,
|
||||
config: OpenAiCompatConfig,
|
||||
base_url: &str,
|
||||
) -> Cow<'a, str> {
|
||||
let Some(pos) = model.find('/') else {
|
||||
return Cow::Borrowed(model);
|
||||
};
|
||||
let prefix = &model[..pos];
|
||||
let lowered_prefix = prefix.to_ascii_lowercase();
|
||||
|
||||
if lowered_prefix == "openai" {
|
||||
let trimmed_base_url = base_url.trim_end_matches('/');
|
||||
let default_openai = DEFAULT_OPENAI_BASE_URL.trim_end_matches('/');
|
||||
if config.provider_name == "OpenAI" && trimmed_base_url != default_openai {
|
||||
// OpenAI-compatible gateways such as OpenRouter commonly use
|
||||
// slash-containing model slugs (for example `openai/gpt-4.1-mini`).
|
||||
// Preserve the slug when the user configured a non-default OpenAI
|
||||
// base URL; the prefix still routed to the OpenAI-compatible client,
|
||||
// but the gateway owns the final model namespace.
|
||||
return Cow::Borrowed(model);
|
||||
}
|
||||
return Cow::Borrowed(&model[pos + 1..]);
|
||||
}
|
||||
|
||||
if matches!(lowered_prefix.as_str(), "xai" | "grok" | "qwen" | "kimi") {
|
||||
return Cow::Borrowed(&model[pos + 1..]);
|
||||
}
|
||||
|
||||
Cow::Borrowed(model)
|
||||
}
|
||||
|
||||
/// Estimate the serialized JSON size of a request payload in bytes.
|
||||
/// This is a pre-flight check to avoid hitting provider-specific size limits.
|
||||
#[must_use]
|
||||
pub fn estimate_request_body_size(request: &MessageRequest, config: OpenAiCompatConfig) -> usize {
|
||||
let payload = build_chat_completion_request(request, config);
|
||||
estimate_request_body_size_for_base_url(request, config, &read_base_url(config))
|
||||
}
|
||||
|
||||
fn estimate_request_body_size_for_base_url(
|
||||
request: &MessageRequest,
|
||||
config: OpenAiCompatConfig,
|
||||
base_url: &str,
|
||||
) -> usize {
|
||||
let payload = build_chat_completion_request_for_base_url(request, config, base_url);
|
||||
// serde_json::to_vec gives us the exact byte size of the serialized JSON
|
||||
serde_json::to_vec(&payload).map_or(0, |v| v.len())
|
||||
}
|
||||
@@ -826,7 +970,15 @@ pub fn check_request_body_size(
|
||||
request: &MessageRequest,
|
||||
config: OpenAiCompatConfig,
|
||||
) -> Result<(), ApiError> {
|
||||
let estimated_bytes = estimate_request_body_size(request, config);
|
||||
check_request_body_size_for_base_url(request, config, &read_base_url(config))
|
||||
}
|
||||
|
||||
fn check_request_body_size_for_base_url(
|
||||
request: &MessageRequest,
|
||||
config: OpenAiCompatConfig,
|
||||
base_url: &str,
|
||||
) -> Result<(), ApiError> {
|
||||
let estimated_bytes = estimate_request_body_size_for_base_url(request, config, base_url);
|
||||
let max_bytes = config.max_request_body_bytes;
|
||||
|
||||
if estimated_bytes > max_bytes {
|
||||
@@ -842,9 +994,18 @@ pub fn check_request_body_size(
|
||||
|
||||
/// Builds a chat completion request payload from a `MessageRequest`.
|
||||
/// Public for benchmarking purposes.
|
||||
#[must_use]
|
||||
pub fn build_chat_completion_request(
|
||||
request: &MessageRequest,
|
||||
config: OpenAiCompatConfig,
|
||||
) -> Value {
|
||||
build_chat_completion_request_for_base_url(request, config, &read_base_url(config))
|
||||
}
|
||||
|
||||
fn build_chat_completion_request_for_base_url(
|
||||
request: &MessageRequest,
|
||||
config: OpenAiCompatConfig,
|
||||
base_url: &str,
|
||||
) -> Value {
|
||||
let mut messages = Vec::new();
|
||||
if let Some(system) = request.system.as_ref().filter(|value| !value.is_empty()) {
|
||||
@@ -853,8 +1014,10 @@ pub fn build_chat_completion_request(
|
||||
"content": system,
|
||||
}));
|
||||
}
|
||||
// Strip routing prefix (e.g., "openai/gpt-4" → "gpt-4") for the wire.
|
||||
let wire_model = strip_routing_prefix(&request.model);
|
||||
// Resolve the transport routing prefix into the wire model. Custom
|
||||
// OpenAI-compatible gateways may require slash-containing slugs intact.
|
||||
let wire_model = wire_model_for_base_url(&request.model, config, base_url);
|
||||
let wire_model = wire_model.as_ref();
|
||||
for message in &request.messages {
|
||||
messages.extend(translate_message(message, wire_model));
|
||||
}
|
||||
@@ -923,9 +1086,29 @@ pub fn build_chat_completion_request(
|
||||
payload["reasoning_effort"] = json!(effort);
|
||||
}
|
||||
|
||||
for (key, value) in &request.extra_body {
|
||||
if is_protected_extra_body_key(key) {
|
||||
continue;
|
||||
}
|
||||
payload[key] = value.clone();
|
||||
}
|
||||
|
||||
payload
|
||||
}
|
||||
|
||||
fn is_protected_extra_body_key(key: &str) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
"model"
|
||||
| "messages"
|
||||
| "stream"
|
||||
| "tools"
|
||||
| "tool_choice"
|
||||
| "max_tokens"
|
||||
| "max_completion_tokens"
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true for models that do NOT support the `is_error` field in tool results.
|
||||
/// kimi models (via Moonshot AI/Dashscope) reject this field with 400 Bad Request.
|
||||
/// Returns true for models that do NOT support the `is_error` field in tool results.
|
||||
@@ -948,10 +1131,14 @@ pub fn translate_message(message: &InputMessage, model: &str) -> Vec<Value> {
|
||||
match message.role.as_str() {
|
||||
"assistant" => {
|
||||
let mut text = String::new();
|
||||
let mut reasoning = String::new();
|
||||
let mut tool_calls = Vec::new();
|
||||
for block in &message.content {
|
||||
match block {
|
||||
InputContentBlock::Text { text: value } => text.push_str(value),
|
||||
InputContentBlock::Thinking {
|
||||
thinking: value, ..
|
||||
} => reasoning.push_str(value),
|
||||
InputContentBlock::ToolUse { id, name, input } => tool_calls.push(json!({
|
||||
"id": id,
|
||||
"type": "function",
|
||||
@@ -963,13 +1150,18 @@ pub fn translate_message(message: &InputMessage, model: &str) -> Vec<Value> {
|
||||
InputContentBlock::ToolResult { .. } => {}
|
||||
}
|
||||
}
|
||||
if text.is_empty() && tool_calls.is_empty() {
|
||||
let include_reasoning =
|
||||
model_requires_reasoning_content_in_history(model) && !reasoning.is_empty();
|
||||
if text.is_empty() && tool_calls.is_empty() && !include_reasoning {
|
||||
Vec::new()
|
||||
} else {
|
||||
let mut msg = serde_json::json!({
|
||||
"role": "assistant",
|
||||
"content": (!text.is_empty()).then_some(text),
|
||||
});
|
||||
if include_reasoning {
|
||||
msg["reasoning_content"] = json!(reasoning);
|
||||
}
|
||||
// Only include tool_calls when non-empty: some providers reject
|
||||
// assistant messages with an explicit empty tool_calls array.
|
||||
if !tool_calls.is_empty() {
|
||||
@@ -1003,7 +1195,7 @@ pub fn translate_message(message: &InputMessage, model: &str) -> Vec<Value> {
|
||||
}
|
||||
Some(msg)
|
||||
}
|
||||
InputContentBlock::ToolUse { .. } => None,
|
||||
InputContentBlock::Thinking { .. } | InputContentBlock::ToolUse { .. } => None,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
@@ -1182,6 +1374,16 @@ fn normalize_response(
|
||||
"chat completion response missing choices",
|
||||
))?;
|
||||
let mut content = Vec::new();
|
||||
if let Some(thinking) = choice
|
||||
.message
|
||||
.reasoning_content
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
content.push(OutputContentBlock::Thinking {
|
||||
thinking,
|
||||
signature: None,
|
||||
});
|
||||
}
|
||||
if let Some(text) = choice.message.content.filter(|value| !value.is_empty()) {
|
||||
content.push(OutputContentBlock::Text { text });
|
||||
}
|
||||
@@ -1203,18 +1405,10 @@ fn normalize_response(
|
||||
.finish_reason
|
||||
.map(|value| normalize_finish_reason(&value)),
|
||||
stop_sequence: None,
|
||||
usage: Usage {
|
||||
input_tokens: response
|
||||
.usage
|
||||
.as_ref()
|
||||
.map_or(0, |usage| usage.prompt_tokens),
|
||||
cache_creation_input_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
output_tokens: response
|
||||
.usage
|
||||
.as_ref()
|
||||
.map_or(0, |usage| usage.completion_tokens),
|
||||
},
|
||||
usage: response
|
||||
.usage
|
||||
.as_ref()
|
||||
.map_or_else(Usage::default, OpenAiUsage::normalized),
|
||||
request_id: None,
|
||||
})
|
||||
}
|
||||
@@ -1413,15 +1607,18 @@ impl StringExt for String {
|
||||
mod tests {
|
||||
use super::{
|
||||
build_chat_completion_request, chat_completions_endpoint, is_reasoning_model,
|
||||
normalize_finish_reason, openai_tool_choice, parse_tool_arguments, OpenAiCompatClient,
|
||||
OpenAiCompatConfig,
|
||||
model_requires_reasoning_content_in_history, normalize_finish_reason, normalize_response,
|
||||
openai_tool_choice, parse_tool_arguments, OpenAiCompatClient, OpenAiCompatConfig,
|
||||
StreamState,
|
||||
};
|
||||
use crate::error::ApiError;
|
||||
use crate::types::{
|
||||
InputContentBlock, InputMessage, MessageRequest, ToolChoice, ToolDefinition,
|
||||
ToolResultContentBlock,
|
||||
ContentBlockDelta, ContentBlockDeltaEvent, ContentBlockStartEvent, ContentBlockStopEvent,
|
||||
InputContentBlock, InputMessage, MessageRequest, OutputContentBlock, StreamEvent,
|
||||
ToolChoice, ToolDefinition, ToolResultContentBlock,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
#[test]
|
||||
@@ -1465,6 +1662,188 @@ mod tests {
|
||||
assert_eq!(payload["tool_choice"], json!("auto"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_requires_reasoning_content_in_history_detects_deepseek_v4_models() {
|
||||
// Given DeepSeek V4 and non-V4 model names.
|
||||
let positive = [
|
||||
"deepseek-v4-flash",
|
||||
"deepseek-v4-pro",
|
||||
"openai/deepseek-v4-pro",
|
||||
"deepseek/deepseek-v4-flash",
|
||||
];
|
||||
let negative = [
|
||||
"deepseek-reasoner",
|
||||
"deepseek-chat",
|
||||
"gpt-4o",
|
||||
"claude-sonnet-4-6",
|
||||
];
|
||||
|
||||
// When checking whether history reasoning_content is required.
|
||||
// Then only DeepSeek V4 variants require it.
|
||||
for model in positive {
|
||||
assert!(model_requires_reasoning_content_in_history(model));
|
||||
}
|
||||
for model in negative {
|
||||
assert!(!model_requires_reasoning_content_in_history(model));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_deepseek_reasoner_request_omits_reasoning_content_for_assistant_history() {
|
||||
// Given an assistant history turn containing thinking.
|
||||
let request = assistant_history_with_thinking_request("deepseek-reasoner");
|
||||
|
||||
// When serializing for legacy deepseek-reasoner.
|
||||
let payload = build_chat_completion_request(&request, OpenAiCompatConfig::openai());
|
||||
|
||||
// Then reasoning_content is omitted.
|
||||
let assistant = &payload["messages"][0];
|
||||
assert_eq!(assistant["role"], json!("assistant"));
|
||||
assert!(assistant.get("reasoning_content").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deepseek_v4_pro_request_includes_reasoning_content_for_assistant_history() {
|
||||
// Given an assistant history turn containing thinking.
|
||||
let request = assistant_history_with_thinking_request("openai/deepseek-v4-pro");
|
||||
|
||||
// When serializing for DeepSeek V4 Pro.
|
||||
let payload = build_chat_completion_request(&request, OpenAiCompatConfig::openai());
|
||||
|
||||
// Then reasoning_content is included on the assistant message.
|
||||
let assistant = &payload["messages"][0];
|
||||
assert_eq!(assistant["reasoning_content"], json!("prior reasoning"));
|
||||
assert_eq!(assistant["content"], json!("answer"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deepseek_v4_flash_request_includes_reasoning_content_for_assistant_history() {
|
||||
// Given an assistant history turn containing thinking.
|
||||
let request = assistant_history_with_thinking_request("deepseek-v4-flash");
|
||||
|
||||
// When serializing for DeepSeek V4 Flash.
|
||||
let payload = build_chat_completion_request(&request, OpenAiCompatConfig::openai());
|
||||
|
||||
// Then reasoning_content is included on the assistant message.
|
||||
let assistant = &payload["messages"][0];
|
||||
assert_eq!(assistant["reasoning_content"], json!("prior reasoning"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_streaming_response_with_reasoning_content_emits_thinking_block_first() {
|
||||
// Given a non-streaming OpenAI-compatible response with reasoning_content.
|
||||
let response = super::ChatCompletionResponse {
|
||||
id: "chatcmpl_reasoning".to_string(),
|
||||
model: "deepseek-v4-pro".to_string(),
|
||||
choices: vec![super::ChatChoice {
|
||||
message: super::ChatMessage {
|
||||
role: "assistant".to_string(),
|
||||
content: Some("final answer".to_string()),
|
||||
reasoning_content: Some("hidden thought".to_string()),
|
||||
tool_calls: Vec::new(),
|
||||
},
|
||||
finish_reason: Some("stop".to_string()),
|
||||
}],
|
||||
usage: None,
|
||||
};
|
||||
|
||||
// When normalizing the provider response.
|
||||
let normalized = normalize_response("deepseek-v4-pro", response).expect("normalized");
|
||||
|
||||
// Then Thinking is the first content block, before text.
|
||||
assert_eq!(
|
||||
normalized.content,
|
||||
vec![
|
||||
OutputContentBlock::Thinking {
|
||||
thinking: "hidden thought".to_string(),
|
||||
signature: None,
|
||||
},
|
||||
OutputContentBlock::Text {
|
||||
text: "final answer".to_string(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn streaming_chunks_with_reasoning_content_emit_thinking_block_events_before_text() {
|
||||
// Given streaming chunks with reasoning_content followed by text.
|
||||
let mut state = StreamState::new("deepseek-v4-pro".to_string());
|
||||
let mut events = state
|
||||
.ingest_chunk(super::ChatCompletionChunk {
|
||||
id: "chatcmpl_stream_reasoning".to_string(),
|
||||
model: Some("deepseek-v4-pro".to_string()),
|
||||
choices: vec![super::ChunkChoice {
|
||||
delta: super::ChunkDelta {
|
||||
content: None,
|
||||
reasoning_content: Some("think".to_string()),
|
||||
tool_calls: Vec::new(),
|
||||
},
|
||||
finish_reason: None,
|
||||
}],
|
||||
usage: None,
|
||||
})
|
||||
.expect("reasoning chunk");
|
||||
events.extend(
|
||||
state
|
||||
.ingest_chunk(super::ChatCompletionChunk {
|
||||
id: "chatcmpl_stream_reasoning".to_string(),
|
||||
model: None,
|
||||
choices: vec![super::ChunkChoice {
|
||||
delta: super::ChunkDelta {
|
||||
content: Some(" answer".to_string()),
|
||||
reasoning_content: None,
|
||||
tool_calls: Vec::new(),
|
||||
},
|
||||
finish_reason: Some("stop".to_string()),
|
||||
}],
|
||||
usage: None,
|
||||
})
|
||||
.expect("text chunk"),
|
||||
);
|
||||
events.extend(state.finish().expect("finish"));
|
||||
|
||||
// When reading normalized stream events.
|
||||
// Then Thinking starts at index 0, text is offset to index 1.
|
||||
assert!(matches!(events[0], StreamEvent::MessageStart(_)));
|
||||
assert!(matches!(
|
||||
events[1],
|
||||
StreamEvent::ContentBlockStart(ContentBlockStartEvent {
|
||||
index: 0,
|
||||
content_block: OutputContentBlock::Thinking { .. },
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
events[2],
|
||||
StreamEvent::ContentBlockDelta(ContentBlockDeltaEvent {
|
||||
index: 0,
|
||||
delta: ContentBlockDelta::ThinkingDelta { .. },
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
events[3],
|
||||
StreamEvent::ContentBlockStop(ContentBlockStopEvent { index: 0 })
|
||||
));
|
||||
assert!(matches!(
|
||||
events[4],
|
||||
StreamEvent::ContentBlockStart(ContentBlockStartEvent {
|
||||
index: 1,
|
||||
content_block: OutputContentBlock::Text { .. },
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
events[5],
|
||||
StreamEvent::ContentBlockDelta(ContentBlockDeltaEvent {
|
||||
index: 1,
|
||||
delta: ContentBlockDelta::TextDelta { .. },
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
events[6],
|
||||
StreamEvent::ContentBlockStop(ContentBlockStopEvent { index: 1 })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_schema_object_gets_strict_fields_for_responses_endpoint() {
|
||||
// OpenAI /responses endpoint rejects object schemas missing
|
||||
@@ -1624,6 +2003,27 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
fn assistant_history_with_thinking_request(model: &str) -> MessageRequest {
|
||||
MessageRequest {
|
||||
model: model.to_string(),
|
||||
max_tokens: 100,
|
||||
messages: vec![InputMessage {
|
||||
role: "assistant".to_string(),
|
||||
content: vec![
|
||||
InputContentBlock::Thinking {
|
||||
thinking: "prior reasoning".to_string(),
|
||||
signature: None,
|
||||
},
|
||||
InputContentBlock::Text {
|
||||
text: "answer".to_string(),
|
||||
},
|
||||
],
|
||||
}],
|
||||
stream: false,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
|
||||
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
LOCK.get_or_init(|| Mutex::new(()))
|
||||
@@ -1653,6 +2053,7 @@ mod tests {
|
||||
presence_penalty: Some(0.3),
|
||||
stop: Some(vec!["\n".to_string()]),
|
||||
reasoning_effort: None,
|
||||
extra_body: BTreeMap::new(),
|
||||
};
|
||||
let payload = build_chat_completion_request(&request, OpenAiCompatConfig::openai());
|
||||
assert_eq!(payload["temperature"], 0.7);
|
||||
@@ -1662,6 +2063,39 @@ mod tests {
|
||||
assert_eq!(payload["stop"], json!(["\n"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_body_params_are_passed_through_without_overriding_core_fields() {
|
||||
let mut extra_body = BTreeMap::new();
|
||||
extra_body.insert(
|
||||
"web_search_options".to_string(),
|
||||
json!({"search_context_size": "medium"}),
|
||||
);
|
||||
extra_body.insert("parallel_tool_calls".to_string(), json!(false));
|
||||
extra_body.insert("model".to_string(), json!("bad-override"));
|
||||
extra_body.insert("messages".to_string(), json!([]));
|
||||
extra_body.insert("max_tokens".to_string(), json!(1));
|
||||
|
||||
let payload = build_chat_completion_request(
|
||||
&MessageRequest {
|
||||
model: "gpt-4o".to_string(),
|
||||
max_tokens: 1024,
|
||||
messages: vec![InputMessage::user_text("hello")],
|
||||
extra_body,
|
||||
..Default::default()
|
||||
},
|
||||
OpenAiCompatConfig::openai(),
|
||||
);
|
||||
|
||||
assert_eq!(payload["model"], json!("gpt-4o"));
|
||||
assert_eq!(payload["max_tokens"], json!(1024));
|
||||
assert_eq!(payload["messages"].as_array().map(Vec::len), Some(1));
|
||||
assert_eq!(
|
||||
payload["web_search_options"],
|
||||
json!({"search_context_size": "medium"})
|
||||
);
|
||||
assert_eq!(payload["parallel_tool_calls"], json!(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_model_strips_tuning_params() {
|
||||
let request = MessageRequest {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use runtime::{pricing_for_model, TokenUsage, UsageCostEstimate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
@@ -31,6 +33,14 @@ pub struct MessageRequest {
|
||||
/// Silently ignored by backends that do not support it.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reasoning_effort: Option<String>,
|
||||
/// Provider-specific OpenAI-compatible request body parameters. These are
|
||||
/// copied into the final JSON payload after core fields are populated so
|
||||
/// users can opt into gateway features such as `web_search_options`,
|
||||
/// `parallel_tool_calls`, or custom local-server switches without waiting
|
||||
/// for first-class typed fields. Core protocol keys are protected and cannot
|
||||
/// be overridden through this map.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub extra_body: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
impl MessageRequest {
|
||||
@@ -81,6 +91,11 @@ pub enum InputContentBlock {
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
Thinking {
|
||||
thinking: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
signature: Option<String>,
|
||||
},
|
||||
ToolUse {
|
||||
id: String,
|
||||
name: String,
|
||||
@@ -268,8 +283,9 @@ pub enum StreamEvent {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use runtime::format_usd;
|
||||
use serde_json::json;
|
||||
|
||||
use super::{MessageResponse, Usage};
|
||||
use super::{InputContentBlock, MessageResponse, Usage};
|
||||
|
||||
#[test]
|
||||
fn usage_total_tokens_includes_cache_tokens() {
|
||||
@@ -307,4 +323,33 @@ mod tests {
|
||||
assert_eq!(format_usd(cost.total_cost_usd()), "$54.6750");
|
||||
assert_eq!(response.total_tokens(), 1_800_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_content_block_thinking_serializes_with_snake_case_type() {
|
||||
// given
|
||||
let block = InputContentBlock::Thinking {
|
||||
thinking: "pondering".to_string(),
|
||||
signature: Some("sig_123".to_string()),
|
||||
};
|
||||
|
||||
// when
|
||||
let serialized = serde_json::to_value(&block).unwrap();
|
||||
let deserialized: InputContentBlock = serde_json::from_value(json!({
|
||||
"type": "thinking",
|
||||
"thinking": "pondering",
|
||||
"signature": "sig_123"
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
serialized,
|
||||
json!({
|
||||
"type": "thinking",
|
||||
"thinking": "pondering",
|
||||
"signature": "sig_123"
|
||||
})
|
||||
);
|
||||
assert_eq!(deserialized, block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Mutex as StdMutex, OnceLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use api::{
|
||||
ApiError, ContentBlockDelta, ContentBlockDeltaEvent, ContentBlockStartEvent,
|
||||
ContentBlockStopEvent, InputContentBlock, InputMessage, MessageDeltaEvent, MessageRequest,
|
||||
OpenAiCompatClient, OpenAiCompatConfig, OutputContentBlock, ProviderClient, StreamEvent,
|
||||
ToolChoice, ToolDefinition,
|
||||
build_http_client_with, ApiError, ContentBlockDelta, ContentBlockDeltaEvent,
|
||||
ContentBlockStartEvent, ContentBlockStopEvent, InputContentBlock, InputMessage,
|
||||
MessageDeltaEvent, MessageRequest, OpenAiCompatClient, OpenAiCompatConfig, OutputContentBlock,
|
||||
ProviderClient, ProxyConfig, StreamEvent, ToolChoice, ToolDefinition,
|
||||
};
|
||||
use serde_json::json;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
@@ -25,7 +26,7 @@ async fn send_message_uses_openai_compatible_endpoint_and_auth() {
|
||||
"\"message\":{\"role\":\"assistant\",\"content\":\"Hello from Grok\",\"tool_calls\":[]},",
|
||||
"\"finish_reason\":\"stop\"",
|
||||
"}],",
|
||||
"\"usage\":{\"prompt_tokens\":11,\"completion_tokens\":5}",
|
||||
"\"usage\":{\"prompt_tokens\":11,\"completion_tokens\":5,\"prompt_tokens_details\":{\"cached_tokens\":3}}",
|
||||
"}"
|
||||
);
|
||||
let server = spawn_server(
|
||||
@@ -42,6 +43,9 @@ async fn send_message_uses_openai_compatible_endpoint_and_auth() {
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.model, "grok-3");
|
||||
assert_eq!(response.usage.input_tokens, 8);
|
||||
assert_eq!(response.usage.cache_read_input_tokens, 3);
|
||||
assert_eq!(response.usage.output_tokens, 5);
|
||||
assert_eq!(response.total_tokens(), 16);
|
||||
assert_eq!(
|
||||
response.content,
|
||||
@@ -63,6 +67,153 @@ async fn send_message_uses_openai_compatible_endpoint_and_auth() {
|
||||
assert_eq!(body["tools"][0]["type"], json!("function"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_message_passes_optional_openai_compatible_parameters_on_wire() {
|
||||
let state = Arc::new(Mutex::new(Vec::<CapturedRequest>::new()));
|
||||
let body = concat!(
|
||||
"{",
|
||||
"\"id\":\"chatcmpl_params\",",
|
||||
"\"model\":\"gpt-4o\",",
|
||||
"\"choices\":[{",
|
||||
"\"message\":{\"role\":\"assistant\",\"content\":\"Parameters preserved\",\"tool_calls\":[]},",
|
||||
"\"finish_reason\":\"stop\"",
|
||||
"}],",
|
||||
"\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":2}",
|
||||
"}"
|
||||
);
|
||||
let server = spawn_server(
|
||||
state.clone(),
|
||||
vec![http_response("200 OK", "application/json", body)],
|
||||
)
|
||||
.await;
|
||||
|
||||
let client = OpenAiCompatClient::new("openai-test-key", OpenAiCompatConfig::openai())
|
||||
.with_base_url(server.base_url());
|
||||
let response = client
|
||||
.send_message(&MessageRequest {
|
||||
model: "gpt-4o".to_string(),
|
||||
temperature: Some(0.2),
|
||||
top_p: Some(0.8),
|
||||
frequency_penalty: Some(0.15),
|
||||
presence_penalty: Some(0.25),
|
||||
stop: Some(vec!["END".to_string()]),
|
||||
reasoning_effort: Some("low".to_string()),
|
||||
..sample_request(false)
|
||||
})
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.total_tokens(), 5);
|
||||
|
||||
let captured = state.lock().await;
|
||||
let request = captured.first().expect("server should capture request");
|
||||
let body: serde_json::Value = serde_json::from_str(&request.body).expect("json body");
|
||||
assert_eq!(body["model"], json!("gpt-4o"));
|
||||
assert_eq!(body["temperature"], json!(0.2));
|
||||
assert_eq!(body["top_p"], json!(0.8));
|
||||
assert_eq!(body["frequency_penalty"], json!(0.15));
|
||||
assert_eq!(body["presence_penalty"], json!(0.25));
|
||||
assert_eq!(body["stop"], json!(["END"]));
|
||||
assert_eq!(body["reasoning_effort"], json!("low"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_message_preserves_deepseek_reasoning_content_before_text() {
|
||||
let state = Arc::new(Mutex::new(Vec::<CapturedRequest>::new()));
|
||||
let body = concat!(
|
||||
"{",
|
||||
"\"id\":\"chatcmpl_deepseek_reasoning\",",
|
||||
"\"model\":\"deepseek-v4-pro\",",
|
||||
"\"choices\":[{",
|
||||
"\"message\":{\"role\":\"assistant\",\"reasoning_content\":\"Think first\",\"content\":\"Answer second\",\"tool_calls\":[]},",
|
||||
"\"finish_reason\":\"stop\"",
|
||||
"}],",
|
||||
"\"usage\":{\"prompt_tokens\":11,\"completion_tokens\":5}",
|
||||
"}"
|
||||
);
|
||||
let server = spawn_server(
|
||||
state.clone(),
|
||||
vec![http_response("200 OK", "application/json", body)],
|
||||
)
|
||||
.await;
|
||||
|
||||
let client = OpenAiCompatClient::new("openai-test-key", OpenAiCompatConfig::openai())
|
||||
.with_base_url(server.base_url());
|
||||
let response = client
|
||||
.send_message(&MessageRequest {
|
||||
model: "openai/deepseek-v4-pro".to_string(),
|
||||
..sample_request(false)
|
||||
})
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(
|
||||
response.content,
|
||||
vec![
|
||||
OutputContentBlock::Thinking {
|
||||
thinking: "Think first".to_string(),
|
||||
signature: None,
|
||||
},
|
||||
OutputContentBlock::Text {
|
||||
text: "Answer second".to_string(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn custom_openai_gateway_preserves_slash_model_ids_and_extra_body_params() {
|
||||
let state = Arc::new(Mutex::new(Vec::<CapturedRequest>::new()));
|
||||
let body = concat!(
|
||||
"{",
|
||||
"\"id\":\"chatcmpl_slash_model\",",
|
||||
"\"model\":\"openai/gpt-4.1-mini\",",
|
||||
"\"choices\":[{",
|
||||
"\"message\":{\"role\":\"assistant\",\"content\":\"Gateway accepted slug\",\"tool_calls\":[]},",
|
||||
"\"finish_reason\":\"stop\"",
|
||||
"}],",
|
||||
"\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":2}",
|
||||
"}"
|
||||
);
|
||||
let server = spawn_server(
|
||||
state.clone(),
|
||||
vec![http_response("200 OK", "application/json", body)],
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut extra_body = std::collections::BTreeMap::new();
|
||||
extra_body.insert(
|
||||
"web_search_options".to_string(),
|
||||
json!({"search_context_size": "low"}),
|
||||
);
|
||||
extra_body.insert("parallel_tool_calls".to_string(), json!(false));
|
||||
extra_body.insert("model".to_string(), json!("malicious-override"));
|
||||
|
||||
let client = OpenAiCompatClient::new("openai-test-key", OpenAiCompatConfig::openai())
|
||||
.with_base_url(server.base_url());
|
||||
let response = client
|
||||
.send_message(&MessageRequest {
|
||||
model: "openai/gpt-4.1-mini".to_string(),
|
||||
extra_body,
|
||||
..sample_request(false)
|
||||
})
|
||||
.await
|
||||
.expect("gateway request should succeed");
|
||||
|
||||
assert_eq!(response.model, "openai/gpt-4.1-mini");
|
||||
assert_eq!(response.total_tokens(), 5);
|
||||
|
||||
let captured = state.lock().await;
|
||||
let request = captured.first().expect("captured request");
|
||||
let body: serde_json::Value = serde_json::from_str(&request.body).expect("json body");
|
||||
assert_eq!(body["model"], json!("openai/gpt-4.1-mini"));
|
||||
assert_eq!(
|
||||
body["web_search_options"],
|
||||
json!({"search_context_size": "low"})
|
||||
);
|
||||
assert_eq!(body["parallel_tool_calls"], json!(false));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_message_blocks_oversized_xai_requests_before_the_http_call() {
|
||||
let state = Arc::new(Mutex::new(Vec::<CapturedRequest>::new()));
|
||||
@@ -233,6 +384,65 @@ async fn stream_message_normalizes_text_and_multiple_tool_calls() {
|
||||
assert!(request.body.contains("\"stream\":true"));
|
||||
}
|
||||
|
||||
#[allow(clippy::await_holding_lock)]
|
||||
#[tokio::test]
|
||||
async fn stream_message_retries_retryable_sse_handshake_failures() {
|
||||
let state = Arc::new(Mutex::new(Vec::<CapturedRequest>::new()));
|
||||
let sse = concat!(
|
||||
"data: {\"id\":\"chatcmpl_stream_retry\",\"model\":\"gpt-4o\",\"choices\":[{\"delta\":{\"content\":\"Recovered\"}}]}\n\n",
|
||||
"data: {\"id\":\"chatcmpl_stream_retry\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\"}]}\n\n",
|
||||
"data: [DONE]\n\n"
|
||||
);
|
||||
let server = spawn_server(
|
||||
state.clone(),
|
||||
vec![
|
||||
http_response(
|
||||
"500 Internal Server Error",
|
||||
"application/json",
|
||||
"{\"error\":{\"message\":\"try again\",\"type\":\"server_error\",\"code\":500}}",
|
||||
),
|
||||
http_response_with_headers(
|
||||
"200 OK",
|
||||
"text/event-stream",
|
||||
sse,
|
||||
&[("x-request-id", "req_stream_retry")],
|
||||
),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
let client = OpenAiCompatClient::new("openai-test-key", OpenAiCompatConfig::openai())
|
||||
.with_base_url(server.base_url())
|
||||
.with_retry_policy(1, Duration::ZERO, Duration::ZERO);
|
||||
let mut stream = client
|
||||
.stream_message(&MessageRequest {
|
||||
model: "gpt-4o".to_string(),
|
||||
..sample_request(false)
|
||||
})
|
||||
.await
|
||||
.expect("stream should retry once then start");
|
||||
|
||||
assert_eq!(stream.request_id(), Some("req_stream_retry"));
|
||||
let mut events = Vec::new();
|
||||
while let Some(event) = stream.next_event().await.expect("event should parse") {
|
||||
events.push(event);
|
||||
}
|
||||
assert!(events.iter().any(|event| matches!(
|
||||
event,
|
||||
StreamEvent::ContentBlockDelta(ContentBlockDeltaEvent {
|
||||
delta: ContentBlockDelta::TextDelta { text },
|
||||
..
|
||||
}) if text == "Recovered"
|
||||
)));
|
||||
|
||||
let captured = state.lock().await;
|
||||
assert_eq!(captured.len(), 2, "one original request plus one retry");
|
||||
for request in captured.iter() {
|
||||
let body: serde_json::Value = serde_json::from_str(&request.body).expect("json body");
|
||||
assert_eq!(body["stream"], json!(true));
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::await_holding_lock)]
|
||||
#[tokio::test]
|
||||
async fn openai_streaming_requests_opt_into_usage_chunks() {
|
||||
@@ -240,7 +450,7 @@ async fn openai_streaming_requests_opt_into_usage_chunks() {
|
||||
let sse = concat!(
|
||||
"data: {\"id\":\"chatcmpl_openai_stream\",\"model\":\"gpt-5\",\"choices\":[{\"delta\":{\"content\":\"Hi\"}}]}\n\n",
|
||||
"data: {\"id\":\"chatcmpl_openai_stream\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\"}]}\n\n",
|
||||
"data: {\"id\":\"chatcmpl_openai_stream\",\"choices\":[],\"usage\":{\"prompt_tokens\":9,\"completion_tokens\":4}}\n\n",
|
||||
"data: {\"id\":\"chatcmpl_openai_stream\",\"choices\":[],\"usage\":{\"prompt_tokens\":9,\"completion_tokens\":4,\"prompt_tokens_details\":{\"cached_tokens\":2}}}\n\n",
|
||||
"data: [DONE]\n\n"
|
||||
);
|
||||
let server = spawn_server(
|
||||
@@ -295,8 +505,10 @@ async fn openai_streaming_requests_opt_into_usage_chunks() {
|
||||
|
||||
match &events[4] {
|
||||
StreamEvent::MessageDelta(MessageDeltaEvent { usage, .. }) => {
|
||||
assert_eq!(usage.input_tokens, 9);
|
||||
assert_eq!(usage.input_tokens, 7);
|
||||
assert_eq!(usage.cache_read_input_tokens, 2);
|
||||
assert_eq!(usage.output_tokens, 4);
|
||||
assert_eq!(usage.total_tokens(), 13);
|
||||
}
|
||||
other => panic!("expected message delta, got {other:?}"),
|
||||
}
|
||||
@@ -309,6 +521,44 @@ async fn openai_streaming_requests_opt_into_usage_chunks() {
|
||||
assert_eq!(body["stream_options"], json!({"include_usage": true}));
|
||||
}
|
||||
|
||||
#[allow(clippy::await_holding_lock)]
|
||||
#[tokio::test]
|
||||
async fn openai_compatible_client_honors_http_proxy_for_requests() {
|
||||
let _lock = env_lock();
|
||||
let state = Arc::new(Mutex::new(Vec::<CapturedRequest>::new()));
|
||||
let proxy = spawn_server(
|
||||
state.clone(),
|
||||
vec![http_response(
|
||||
"200 OK",
|
||||
"application/json",
|
||||
"{\"id\":\"chatcmpl_proxy\",\"model\":\"gpt-4o\",\"choices\":[{\"message\":{\"role\":\"assistant\",\"content\":\"Via proxy\",\"tool_calls\":[]},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":4,\"completion_tokens\":3}}",
|
||||
)],
|
||||
)
|
||||
.await;
|
||||
let proxied_http = build_http_client_with(&ProxyConfig::from_proxy_url(proxy.base_url()))
|
||||
.expect("proxy client should build");
|
||||
|
||||
let client = OpenAiCompatClient::new("openai-test-key", OpenAiCompatConfig::openai())
|
||||
.with_http_client(proxied_http)
|
||||
.with_base_url("http://origin.invalid/v1");
|
||||
let response = client
|
||||
.send_message(&MessageRequest {
|
||||
model: "gpt-4o".to_string(),
|
||||
..sample_request(false)
|
||||
})
|
||||
.await
|
||||
.expect("proxy should return the OpenAI-compatible response");
|
||||
|
||||
assert_eq!(response.total_tokens(), 7);
|
||||
let captured = state.lock().await;
|
||||
let request = captured.first().expect("proxy should capture request");
|
||||
assert_eq!(request.path, "http://origin.invalid/v1/chat/completions");
|
||||
assert_eq!(
|
||||
request.headers.get("authorization").map(String::as_str),
|
||||
Some("Bearer openai-test-key")
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::await_holding_lock)]
|
||||
#[tokio::test]
|
||||
async fn provider_client_dispatches_xai_requests_from_env() {
|
||||
|
||||
@@ -221,11 +221,11 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
|
||||
SlashCommandSpec {
|
||||
name: "session",
|
||||
aliases: &[],
|
||||
summary: "List, switch, fork, or delete managed local sessions",
|
||||
summary: "List, check, switch, fork, or delete managed local sessions",
|
||||
argument_hint: Some(
|
||||
"[list|switch <session-id>|fork [branch-name]|delete <session-id> [--force]]",
|
||||
"[list|exists <session-id>|switch <session-id>|fork [branch-name]|delete <session-id> [--force]]",
|
||||
),
|
||||
resume_supported: false,
|
||||
resume_supported: true,
|
||||
},
|
||||
SlashCommandSpec {
|
||||
name: "plugin",
|
||||
@@ -1590,7 +1590,17 @@ fn parse_session_command(args: &[&str]) -> Result<SlashCommand, SlashCommandPars
|
||||
action: Some("list".to_string()),
|
||||
target: None,
|
||||
}),
|
||||
["list", ..] => Err(usage_error("session", "[list|switch <session-id>|fork [branch-name]|delete <session-id> [--force]]")),
|
||||
["list", ..] => Err(usage_error("session", "[list|exists <session-id>|switch <session-id>|fork [branch-name]|delete <session-id> [--force]]")),
|
||||
["exists"] => Err(usage_error("session exists", "<session-id>")),
|
||||
["exists", target] => Ok(SlashCommand::Session {
|
||||
action: Some("exists".to_string()),
|
||||
target: Some((*target).to_string()),
|
||||
}),
|
||||
["exists", ..] => Err(command_error(
|
||||
"Unexpected arguments for /session exists.",
|
||||
"session",
|
||||
"/session exists <session-id>",
|
||||
)),
|
||||
["switch"] => Err(usage_error("session switch", "<session-id>")),
|
||||
["switch", target] => Ok(SlashCommand::Session {
|
||||
action: Some("switch".to_string()),
|
||||
@@ -1637,10 +1647,10 @@ fn parse_session_command(args: &[&str]) -> Result<SlashCommand, SlashCommandPars
|
||||
)),
|
||||
[action, ..] => Err(command_error(
|
||||
&format!(
|
||||
"Unknown /session action '{action}'. Use list, switch <session-id>, fork [branch-name], or delete <session-id> [--force]."
|
||||
"Unknown /session action '{action}'. Use list, exists <session-id>, switch <session-id>, fork [branch-name], or delete <session-id> [--force]."
|
||||
),
|
||||
"session",
|
||||
"/session [list|switch <session-id>|fork [branch-name]|delete <session-id> [--force]]",
|
||||
"/session [list|exists <session-id>|switch <session-id>|fork [branch-name]|delete <session-id> [--force]]",
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -2371,6 +2381,40 @@ pub fn handle_skills_slash_command(args: Option<&str>, cwd: &Path) -> std::io::R
|
||||
let skills = load_skills_from_roots(&roots)?;
|
||||
Ok(render_skills_report(&skills))
|
||||
}
|
||||
Some(args) if args.starts_with("list ") => {
|
||||
let filter = args["list ".len()..].trim().to_lowercase();
|
||||
let roots = discover_skill_roots(cwd);
|
||||
let skills = load_skills_from_roots(&roots)?;
|
||||
let filtered: Vec<_> = skills
|
||||
.into_iter()
|
||||
.filter(|s| s.name.to_lowercase().contains(&filter))
|
||||
.collect();
|
||||
Ok(render_skills_report(&filtered))
|
||||
}
|
||||
Some("show" | "info" | "describe") => {
|
||||
let roots = discover_skill_roots(cwd);
|
||||
let skills = load_skills_from_roots(&roots)?;
|
||||
Ok(render_skills_report(&skills))
|
||||
}
|
||||
Some(args)
|
||||
if args.starts_with("show ")
|
||||
|| args.starts_with("info ")
|
||||
|| args.starts_with("describe ") =>
|
||||
{
|
||||
let name = args
|
||||
.split_once(' ')
|
||||
.map(|(_, name)| name)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_lowercase();
|
||||
let roots = discover_skill_roots(cwd);
|
||||
let skills = load_skills_from_roots(&roots)?;
|
||||
let matched: Vec<_> = skills
|
||||
.into_iter()
|
||||
.filter(|s| s.name.to_lowercase() == name)
|
||||
.collect();
|
||||
Ok(render_skills_report(&matched))
|
||||
}
|
||||
Some("install") => Ok(render_skills_usage(Some("install"))),
|
||||
Some(args) if args.starts_with("install ") => {
|
||||
let target = args["install ".len()..].trim();
|
||||
@@ -2402,6 +2446,40 @@ pub fn handle_skills_slash_command_json(args: Option<&str>, cwd: &Path) -> std::
|
||||
let skills = load_skills_from_roots(&roots)?;
|
||||
Ok(render_skills_report_json(&skills))
|
||||
}
|
||||
Some(args) if args.starts_with("list ") => {
|
||||
let filter = args["list ".len()..].trim().to_lowercase();
|
||||
let roots = discover_skill_roots(cwd);
|
||||
let skills = load_skills_from_roots(&roots)?;
|
||||
let filtered: Vec<_> = skills
|
||||
.into_iter()
|
||||
.filter(|s| s.name.to_lowercase().contains(&filter))
|
||||
.collect();
|
||||
Ok(render_skills_report_json(&filtered))
|
||||
}
|
||||
Some("show" | "info" | "describe") => {
|
||||
let roots = discover_skill_roots(cwd);
|
||||
let skills = load_skills_from_roots(&roots)?;
|
||||
Ok(render_skills_report_json(&skills))
|
||||
}
|
||||
Some(args)
|
||||
if args.starts_with("show ")
|
||||
|| args.starts_with("info ")
|
||||
|| args.starts_with("describe ") =>
|
||||
{
|
||||
let name = args
|
||||
.split_once(' ')
|
||||
.map(|(_, name)| name)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_lowercase();
|
||||
let roots = discover_skill_roots(cwd);
|
||||
let skills = load_skills_from_roots(&roots)?;
|
||||
let matched: Vec<_> = skills
|
||||
.into_iter()
|
||||
.filter(|s| s.name.to_lowercase() == name)
|
||||
.collect();
|
||||
Ok(render_skills_report_json(&matched))
|
||||
}
|
||||
Some("install") => Ok(render_skills_usage_json(Some("install"))),
|
||||
Some(args) if args.starts_with("install ") => {
|
||||
let target = args["install ".len()..].trim();
|
||||
@@ -2419,10 +2497,27 @@ pub fn handle_skills_slash_command_json(args: Option<&str>, cwd: &Path) -> std::
|
||||
#[must_use]
|
||||
pub fn classify_skills_slash_command(args: Option<&str>) -> SkillSlashDispatch {
|
||||
match normalize_optional_args(args) {
|
||||
None | Some("list" | "help" | "-h" | "--help") => SkillSlashDispatch::Local,
|
||||
None | Some("list" | "help" | "-h" | "--help" | "show" | "info" | "describe") => {
|
||||
SkillSlashDispatch::Local
|
||||
}
|
||||
Some(args)
|
||||
if args
|
||||
.split_whitespace()
|
||||
.any(|part| matches!(part, "-h" | "--help")) =>
|
||||
{
|
||||
SkillSlashDispatch::Local
|
||||
}
|
||||
Some(args) if args == "install" || args.starts_with("install ") => {
|
||||
SkillSlashDispatch::Local
|
||||
}
|
||||
Some(args)
|
||||
if args.starts_with("list ")
|
||||
|| args.starts_with("show ")
|
||||
|| args.starts_with("info ")
|
||||
|| args.starts_with("describe ") =>
|
||||
{
|
||||
SkillSlashDispatch::Local
|
||||
}
|
||||
Some(args) => SkillSlashDispatch::Invoke(format!("${}", args.trim_start_matches('/'))),
|
||||
}
|
||||
}
|
||||
@@ -2537,6 +2632,7 @@ pub fn resolve_skill_path(cwd: &Path, skill: &str) -> std::io::Result<PathBuf> {
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn render_mcp_report_for(
|
||||
loader: &ConfigLoader,
|
||||
cwd: &Path,
|
||||
@@ -2596,10 +2692,45 @@ fn render_mcp_report_for(
|
||||
)),
|
||||
}
|
||||
}
|
||||
Some(args) if args.split_whitespace().next() == Some("list") && args.contains(' ') => {
|
||||
// `mcp list <filter>` — list does not accept arguments; treat as unsupported action.
|
||||
Ok(render_mcp_unsupported_action_text(
|
||||
args,
|
||||
"list accepts no filter argument; use `claw mcp list`",
|
||||
))
|
||||
}
|
||||
Some(args) if matches!(args.split_whitespace().next(), Some("info" | "describe")) => {
|
||||
Ok(render_mcp_unsupported_action_text(
|
||||
args,
|
||||
"use `claw mcp show <server>` to inspect a server",
|
||||
))
|
||||
}
|
||||
Some(args) => Ok(render_mcp_usage(Some(args))),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_mcp_unsupported_action_text(action: &str, hint: &str) -> String {
|
||||
format!(
|
||||
"MCP\n Error unsupported action '{action}'\n Hint {hint}\n Usage /mcp [list|show <server>|help]"
|
||||
)
|
||||
}
|
||||
|
||||
fn render_mcp_unsupported_action_json(action: &str, hint: &str) -> Value {
|
||||
json!({
|
||||
"kind": "mcp",
|
||||
"action": "error",
|
||||
"ok": false,
|
||||
"error_kind": "unsupported_action",
|
||||
"requested_action": action,
|
||||
"hint": hint,
|
||||
"usage": {
|
||||
"slash_command": "/mcp [list|show <server>|help]",
|
||||
"direct_cli": "claw mcp [list|show <server>|help]",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn render_mcp_report_json_for(
|
||||
loader: &ConfigLoader,
|
||||
cwd: &Path,
|
||||
@@ -2680,6 +2811,18 @@ fn render_mcp_report_json_for(
|
||||
})),
|
||||
}
|
||||
}
|
||||
Some(args) if args.split_whitespace().next() == Some("list") && args.contains(' ') => {
|
||||
Ok(render_mcp_unsupported_action_json(
|
||||
args,
|
||||
"list accepts no filter argument; use `claw mcp list`",
|
||||
))
|
||||
}
|
||||
Some(args) if matches!(args.split_whitespace().next(), Some("info" | "describe")) => {
|
||||
Ok(render_mcp_unsupported_action_json(
|
||||
args,
|
||||
"use `claw mcp show <server>` to inspect a server",
|
||||
))
|
||||
}
|
||||
Some(args) => Ok(render_mcp_usage_json(Some(args))),
|
||||
}
|
||||
}
|
||||
@@ -3659,6 +3802,7 @@ fn render_mcp_server_report(
|
||||
format!(" Working directory {}", cwd.display()),
|
||||
format!(" Name {server_name}"),
|
||||
format!(" Scope {}", config_source_label(server.scope)),
|
||||
format!(" Required {}", server.required),
|
||||
format!(
|
||||
" Transport {}",
|
||||
mcp_transport_label(&server.config)
|
||||
@@ -4057,6 +4201,7 @@ fn mcp_server_details_json(config: &McpServerConfig) -> Value {
|
||||
fn mcp_server_json(name: &str, server: &ScopedMcpServerConfig) -> Value {
|
||||
json!({
|
||||
"name": name,
|
||||
"required": server.required,
|
||||
"scope": config_source_json(server.scope),
|
||||
"transport": mcp_transport_json(&server.config),
|
||||
"summary": mcp_server_summary(&server.config),
|
||||
@@ -4184,8 +4329,8 @@ mod tests {
|
||||
DefinitionSource, SkillOrigin, SkillRoot, SkillSlashDispatch, SlashCommand,
|
||||
};
|
||||
use plugins::{
|
||||
PluginError, PluginKind, PluginLoadFailure, PluginManager, PluginManagerConfig,
|
||||
PluginMetadata, PluginSummary,
|
||||
PluginError, PluginKind, PluginLifecycle, PluginLoadFailure, PluginManager,
|
||||
PluginManagerConfig, PluginMetadata, PluginSummary,
|
||||
};
|
||||
use runtime::{
|
||||
CompactionConfig, ConfigLoader, ContentBlock, ConversationMessage, MessageRole, Session,
|
||||
@@ -4459,6 +4604,13 @@ mod tests {
|
||||
target: Some("abc123".to_string())
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
SlashCommand::parse("/session exists abc123"),
|
||||
Ok(Some(SlashCommand::Session {
|
||||
action: Some("exists".to_string()),
|
||||
target: Some("abc123".to_string())
|
||||
}))
|
||||
);
|
||||
assert_eq!(
|
||||
SlashCommand::parse("/plugins install demo"),
|
||||
Ok(Some(SlashCommand::Plugins {
|
||||
@@ -4619,6 +4771,32 @@ mod tests {
|
||||
assert!(agents_error.contains(" Usage /agents [list|help]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skills_show_and_list_filter_do_not_invoke_model() {
|
||||
// `show`, `info`, `list <filter>` must route to Local, not Invoke.
|
||||
// Regression for: `claw skills show plan` unexpectedly spawned a model session.
|
||||
for token in &["show", "info", "describe"] {
|
||||
assert_eq!(
|
||||
classify_skills_slash_command(Some(token)),
|
||||
SkillSlashDispatch::Local,
|
||||
"`skills {token}` alone must be Local"
|
||||
);
|
||||
}
|
||||
for prefix in &["show ", "info ", "list ", "describe "] {
|
||||
let arg = format!("{prefix}plan");
|
||||
assert_eq!(
|
||||
classify_skills_slash_command(Some(&arg)),
|
||||
SkillSlashDispatch::Local,
|
||||
"`skills {arg}` must be Local, not Invoke"
|
||||
);
|
||||
}
|
||||
// Bare invocable tokens still dispatch to Invoke.
|
||||
assert_eq!(
|
||||
classify_skills_slash_command(Some("plan")),
|
||||
SkillSlashDispatch::Invoke("$plan".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_skills_invocation_arguments_for_prompt_dispatch() {
|
||||
assert_eq!(
|
||||
@@ -4641,6 +4819,38 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_unsupported_actions_return_typed_error_not_generic_help() {
|
||||
// `mcp info <name>` and `mcp list <filter>` must return typed errors, not raw help.
|
||||
// Regression for #504: these previously fell through to render_mcp_usage with
|
||||
// unexpected=arg, giving no machine-readable error_kind.
|
||||
use crate::handle_mcp_slash_command_json;
|
||||
use std::path::PathBuf;
|
||||
let cwd = PathBuf::from("/tmp");
|
||||
|
||||
let info_json = handle_mcp_slash_command_json(Some("info nonexistent"), &cwd)
|
||||
.expect("info nonexistent should not error at IO level");
|
||||
assert_eq!(info_json["kind"], "mcp");
|
||||
assert_eq!(info_json["ok"], false);
|
||||
assert_eq!(info_json["error_kind"], "unsupported_action");
|
||||
assert!(info_json["hint"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.contains("show"));
|
||||
|
||||
let list_filter_json = handle_mcp_slash_command_json(Some("list nonexistent"), &cwd)
|
||||
.expect("list nonexistent should not error at IO level");
|
||||
assert_eq!(list_filter_json["kind"], "mcp");
|
||||
assert_eq!(list_filter_json["ok"], false);
|
||||
assert_eq!(list_filter_json["error_kind"], "unsupported_action");
|
||||
|
||||
let describe_json = handle_mcp_slash_command_json(Some("describe myserver"), &cwd)
|
||||
.expect("describe myserver should not error at IO level");
|
||||
assert_eq!(describe_json["kind"], "mcp");
|
||||
assert_eq!(describe_json["ok"], false);
|
||||
assert_eq!(describe_json["error_kind"], "unsupported_action");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_mcp_arguments() {
|
||||
let show_error = parse_error_message("/mcp show alpha beta");
|
||||
@@ -4938,6 +5148,7 @@ mod tests {
|
||||
root: None,
|
||||
},
|
||||
enabled: true,
|
||||
lifecycle: PluginLifecycle::default(),
|
||||
},
|
||||
PluginSummary {
|
||||
metadata: PluginMetadata {
|
||||
@@ -4951,6 +5162,7 @@ mod tests {
|
||||
root: None,
|
||||
},
|
||||
enabled: false,
|
||||
lifecycle: PluginLifecycle::default(),
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -4977,6 +5189,7 @@ mod tests {
|
||||
root: None,
|
||||
},
|
||||
enabled: true,
|
||||
lifecycle: PluginLifecycle::default(),
|
||||
}],
|
||||
&[PluginLoadFailure::new(
|
||||
PathBuf::from("/tmp/broken-plugin"),
|
||||
@@ -5391,6 +5604,7 @@ mod tests {
|
||||
"command": "uvx",
|
||||
"args": ["alpha-server"],
|
||||
"env": {"ALPHA_TOKEN": "secret"},
|
||||
"required": true,
|
||||
"toolCallTimeoutMs": 1200
|
||||
},
|
||||
"remote": {
|
||||
@@ -5436,6 +5650,7 @@ mod tests {
|
||||
let show = super::render_mcp_report_for(&loader, &workspace, Some("show alpha"))
|
||||
.expect("mcp show report should render");
|
||||
assert!(show.contains("Name alpha"));
|
||||
assert!(show.contains("Required true"));
|
||||
assert!(show.contains("Command uvx"));
|
||||
assert!(show.contains("Args alpha-server"));
|
||||
assert!(show.contains("Env keys ALPHA_TOKEN"));
|
||||
@@ -5468,6 +5683,7 @@ mod tests {
|
||||
"command": "uvx",
|
||||
"args": ["alpha-server"],
|
||||
"env": {"ALPHA_TOKEN": "secret"},
|
||||
"required": true,
|
||||
"toolCallTimeoutMs": 1200
|
||||
},
|
||||
"remote": {
|
||||
@@ -5504,6 +5720,7 @@ mod tests {
|
||||
assert_eq!(list["action"], "list");
|
||||
assert_eq!(list["configured_servers"], 2);
|
||||
assert_eq!(list["servers"][0]["name"], "alpha");
|
||||
assert_eq!(list["servers"][0]["required"], true);
|
||||
assert_eq!(list["servers"][0]["transport"]["id"], "stdio");
|
||||
assert_eq!(list["servers"][0]["details"]["command"], "uvx");
|
||||
assert_eq!(list["servers"][1]["name"], "remote");
|
||||
@@ -5519,6 +5736,7 @@ mod tests {
|
||||
assert_eq!(show["action"], "show");
|
||||
assert_eq!(show["found"], true);
|
||||
assert_eq!(show["server"]["name"], "alpha");
|
||||
assert_eq!(show["server"]["required"], true);
|
||||
assert_eq!(show["server"]["details"]["env_keys"][0], "ALPHA_TOKEN");
|
||||
assert_eq!(show["server"]["details"]["tool_call_timeout_ms"], 1200);
|
||||
|
||||
|
||||
@@ -648,6 +648,7 @@ impl RegisteredPlugin {
|
||||
PluginSummary {
|
||||
metadata: self.metadata().clone(),
|
||||
enabled: self.enabled,
|
||||
lifecycle: self.definition.lifecycle().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -656,6 +657,18 @@ impl RegisteredPlugin {
|
||||
pub struct PluginSummary {
|
||||
pub metadata: PluginMetadata,
|
||||
pub enabled: bool,
|
||||
pub lifecycle: PluginLifecycle,
|
||||
}
|
||||
|
||||
impl PluginSummary {
|
||||
#[must_use]
|
||||
pub fn lifecycle_state(&self) -> &'static str {
|
||||
if self.enabled {
|
||||
"ready"
|
||||
} else {
|
||||
"disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -3319,7 +3332,7 @@ mod tests {
|
||||
let config_home = temp_dir("installed-report-home");
|
||||
let bundled_root = temp_dir("installed-report-bundled");
|
||||
let install_root = config_home.join("plugins").join("installed");
|
||||
write_external_plugin(&install_root.join("valid"), "installed-valid", "1.0.0");
|
||||
write_lifecycle_plugin(&install_root.join("valid"), "installed-valid", "1.0.0");
|
||||
write_broken_plugin(&install_root.join("broken"), "installed-broken");
|
||||
|
||||
let mut config = PluginManagerConfig::new(&config_home);
|
||||
@@ -3334,6 +3347,14 @@ mod tests {
|
||||
|
||||
// then
|
||||
assert!(report.registry().contains("installed-valid@external"));
|
||||
let summaries = report.summaries();
|
||||
let valid = summaries
|
||||
.iter()
|
||||
.find(|summary| summary.metadata.id == "installed-valid@external")
|
||||
.expect("valid plugin summary should be present");
|
||||
assert_eq!(valid.lifecycle_state(), "disabled");
|
||||
assert_eq!(valid.lifecycle.init.len(), 1);
|
||||
assert_eq!(valid.lifecycle.shutdown.len(), 1);
|
||||
assert_eq!(report.failures().len(), 1);
|
||||
assert!(report.failures()[0]
|
||||
.plugin_root
|
||||
|
||||
502
rust/crates/runtime/src/approval_tokens.rs
Normal file
502
rust/crates/runtime/src/approval_tokens.rs
Normal file
@@ -0,0 +1,502 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Machine-readable policy exception scope that an approval token may override.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ApprovalScope {
|
||||
pub policy: String,
|
||||
pub action: String,
|
||||
pub repository: Option<String>,
|
||||
pub branch: Option<String>,
|
||||
}
|
||||
|
||||
impl ApprovalScope {
|
||||
#[must_use]
|
||||
pub fn new(policy: impl Into<String>, action: impl Into<String>) -> Self {
|
||||
Self {
|
||||
policy: policy.into(),
|
||||
action: action.into(),
|
||||
repository: None,
|
||||
branch: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_repository(mut self, repository: impl Into<String>) -> Self {
|
||||
self.repository = Some(repository.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_branch(mut self, branch: impl Into<String>) -> Self {
|
||||
self.branch = Some(branch.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Actor/session hop recorded when an approval is delegated or consumed.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ApprovalDelegationHop {
|
||||
pub actor: String,
|
||||
pub session_id: Option<String>,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
impl ApprovalDelegationHop {
|
||||
#[must_use]
|
||||
pub fn new(actor: impl Into<String>, reason: impl Into<String>) -> Self {
|
||||
Self {
|
||||
actor: actor.into(),
|
||||
session_id: None,
|
||||
reason: reason.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
|
||||
self.session_id = Some(session_id.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Current lifecycle state for a policy-exception approval token.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ApprovalTokenStatus {
|
||||
Pending,
|
||||
Granted,
|
||||
Consumed,
|
||||
Expired,
|
||||
Revoked,
|
||||
}
|
||||
|
||||
impl ApprovalTokenStatus {
|
||||
#[must_use]
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Pending => "approval_pending",
|
||||
Self::Granted => "approval_granted",
|
||||
Self::Consumed => "approval_consumed",
|
||||
Self::Expired => "approval_expired",
|
||||
Self::Revoked => "approval_revoked",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Typed policy errors returned when a token cannot authorize a blocked action.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ApprovalTokenError {
|
||||
NoApproval,
|
||||
ApprovalPending,
|
||||
ApprovalExpired,
|
||||
ApprovalRevoked,
|
||||
ApprovalAlreadyConsumed,
|
||||
ScopeMismatch {
|
||||
expected: Box<ApprovalScope>,
|
||||
actual: Box<ApprovalScope>,
|
||||
},
|
||||
UnauthorizedDelegate {
|
||||
expected: String,
|
||||
actual: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl ApprovalTokenError {
|
||||
#[must_use]
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::NoApproval => "no_approval",
|
||||
Self::ApprovalPending => "approval_pending",
|
||||
Self::ApprovalExpired => "approval_expired",
|
||||
Self::ApprovalRevoked => "approval_revoked",
|
||||
Self::ApprovalAlreadyConsumed => "approval_already_consumed",
|
||||
Self::ScopeMismatch { .. } => "approval_scope_mismatch",
|
||||
Self::UnauthorizedDelegate { .. } => "approval_unauthorized_delegate",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Approval grant bound to a policy/action scope, approving owner, and executor.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ApprovalTokenGrant {
|
||||
pub token: String,
|
||||
pub scope: ApprovalScope,
|
||||
pub approving_actor: String,
|
||||
pub approved_executor: String,
|
||||
pub status: ApprovalTokenStatus,
|
||||
pub expires_at_epoch_seconds: Option<u64>,
|
||||
pub max_uses: u32,
|
||||
pub uses: u32,
|
||||
delegation_chain: Vec<ApprovalDelegationHop>,
|
||||
}
|
||||
|
||||
impl ApprovalTokenGrant {
|
||||
#[must_use]
|
||||
pub fn pending(
|
||||
token: impl Into<String>,
|
||||
scope: ApprovalScope,
|
||||
approving_actor: impl Into<String>,
|
||||
approved_executor: impl Into<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
token: token.into(),
|
||||
scope,
|
||||
approving_actor: approving_actor.into(),
|
||||
approved_executor: approved_executor.into(),
|
||||
status: ApprovalTokenStatus::Pending,
|
||||
expires_at_epoch_seconds: None,
|
||||
max_uses: 1,
|
||||
uses: 0,
|
||||
delegation_chain: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn granted(
|
||||
token: impl Into<String>,
|
||||
scope: ApprovalScope,
|
||||
approving_actor: impl Into<String>,
|
||||
approved_executor: impl Into<String>,
|
||||
) -> Self {
|
||||
Self::pending(token, scope, approving_actor, approved_executor).approve()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn approve(mut self) -> Self {
|
||||
self.status = ApprovalTokenStatus::Granted;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn expires_at(mut self, epoch_seconds: u64) -> Self {
|
||||
self.expires_at_epoch_seconds = Some(epoch_seconds);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_max_uses(mut self, max_uses: u32) -> Self {
|
||||
self.max_uses = max_uses.max(1);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_delegation_hop(mut self, hop: ApprovalDelegationHop) -> Self {
|
||||
self.delegation_chain.push(hop);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn delegation_chain(&self) -> &[ApprovalDelegationHop] {
|
||||
&self.delegation_chain
|
||||
}
|
||||
}
|
||||
|
||||
/// Auditable result of verifying or consuming an approval token.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ApprovalTokenAudit {
|
||||
pub token: String,
|
||||
pub scope: ApprovalScope,
|
||||
pub approving_actor: String,
|
||||
pub executing_actor: String,
|
||||
pub status: ApprovalTokenStatus,
|
||||
pub delegated_execution: bool,
|
||||
pub delegation_chain: Vec<ApprovalDelegationHop>,
|
||||
pub uses: u32,
|
||||
pub max_uses: u32,
|
||||
}
|
||||
|
||||
/// In-memory approval-token ledger with one-time-use and replay protection.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct ApprovalTokenLedger {
|
||||
grants: BTreeMap<String, ApprovalTokenGrant>,
|
||||
}
|
||||
|
||||
impl ApprovalTokenLedger {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, grant: ApprovalTokenGrant) {
|
||||
self.grants.insert(grant.token.clone(), grant);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get(&self, token: &str) -> Option<&ApprovalTokenGrant> {
|
||||
self.grants.get(token)
|
||||
}
|
||||
|
||||
pub fn revoke(&mut self, token: &str) -> Result<ApprovalTokenAudit, ApprovalTokenError> {
|
||||
let grant = self
|
||||
.grants
|
||||
.get_mut(token)
|
||||
.ok_or(ApprovalTokenError::NoApproval)?;
|
||||
grant.status = ApprovalTokenStatus::Revoked;
|
||||
Ok(Self::audit_for(grant, &grant.approved_executor))
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
token: &str,
|
||||
scope: &ApprovalScope,
|
||||
executing_actor: &str,
|
||||
now_epoch_seconds: u64,
|
||||
) -> Result<ApprovalTokenAudit, ApprovalTokenError> {
|
||||
let grant = self
|
||||
.grants
|
||||
.get(token)
|
||||
.ok_or(ApprovalTokenError::NoApproval)?;
|
||||
Self::validate_grant(grant, scope, executing_actor, now_epoch_seconds)?;
|
||||
Ok(Self::audit_for(grant, executing_actor))
|
||||
}
|
||||
|
||||
pub fn consume(
|
||||
&mut self,
|
||||
token: &str,
|
||||
scope: &ApprovalScope,
|
||||
executing_actor: &str,
|
||||
now_epoch_seconds: u64,
|
||||
) -> Result<ApprovalTokenAudit, ApprovalTokenError> {
|
||||
let grant = self
|
||||
.grants
|
||||
.get_mut(token)
|
||||
.ok_or(ApprovalTokenError::NoApproval)?;
|
||||
Self::validate_grant(grant, scope, executing_actor, now_epoch_seconds)?;
|
||||
grant.uses += 1;
|
||||
if grant.uses >= grant.max_uses {
|
||||
grant.status = ApprovalTokenStatus::Consumed;
|
||||
}
|
||||
Ok(Self::audit_for(grant, executing_actor))
|
||||
}
|
||||
|
||||
fn validate_grant(
|
||||
grant: &ApprovalTokenGrant,
|
||||
scope: &ApprovalScope,
|
||||
executing_actor: &str,
|
||||
now_epoch_seconds: u64,
|
||||
) -> Result<(), ApprovalTokenError> {
|
||||
match grant.status {
|
||||
ApprovalTokenStatus::Pending => return Err(ApprovalTokenError::ApprovalPending),
|
||||
ApprovalTokenStatus::Consumed => {
|
||||
return Err(ApprovalTokenError::ApprovalAlreadyConsumed)
|
||||
}
|
||||
ApprovalTokenStatus::Expired => return Err(ApprovalTokenError::ApprovalExpired),
|
||||
ApprovalTokenStatus::Revoked => return Err(ApprovalTokenError::ApprovalRevoked),
|
||||
ApprovalTokenStatus::Granted => {}
|
||||
}
|
||||
|
||||
if grant
|
||||
.expires_at_epoch_seconds
|
||||
.is_some_and(|expires_at| now_epoch_seconds > expires_at)
|
||||
{
|
||||
return Err(ApprovalTokenError::ApprovalExpired);
|
||||
}
|
||||
|
||||
if grant.uses >= grant.max_uses {
|
||||
return Err(ApprovalTokenError::ApprovalAlreadyConsumed);
|
||||
}
|
||||
|
||||
if grant.scope != *scope {
|
||||
return Err(ApprovalTokenError::ScopeMismatch {
|
||||
expected: Box::new(grant.scope.clone()),
|
||||
actual: Box::new(scope.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
if grant.approved_executor != executing_actor {
|
||||
return Err(ApprovalTokenError::UnauthorizedDelegate {
|
||||
expected: grant.approved_executor.clone(),
|
||||
actual: executing_actor.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn audit_for(grant: &ApprovalTokenGrant, executing_actor: &str) -> ApprovalTokenAudit {
|
||||
let mut delegation_chain = grant.delegation_chain.clone();
|
||||
if delegation_chain.is_empty() {
|
||||
delegation_chain.push(ApprovalDelegationHop::new(
|
||||
grant.approving_actor.clone(),
|
||||
"approval granted",
|
||||
));
|
||||
}
|
||||
if grant.approving_actor != executing_actor
|
||||
&& !delegation_chain
|
||||
.iter()
|
||||
.any(|hop| hop.actor == executing_actor)
|
||||
{
|
||||
delegation_chain.push(ApprovalDelegationHop::new(
|
||||
executing_actor.to_string(),
|
||||
"delegated execution",
|
||||
));
|
||||
}
|
||||
|
||||
ApprovalTokenAudit {
|
||||
token: grant.token.clone(),
|
||||
scope: grant.scope.clone(),
|
||||
approving_actor: grant.approving_actor.clone(),
|
||||
executing_actor: executing_actor.to_string(),
|
||||
status: grant.status,
|
||||
delegated_execution: grant.approving_actor != executing_actor,
|
||||
delegation_chain,
|
||||
uses: grant.uses,
|
||||
max_uses: grant.max_uses,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
ApprovalDelegationHop, ApprovalScope, ApprovalTokenError, ApprovalTokenGrant,
|
||||
ApprovalTokenLedger, ApprovalTokenStatus,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn approval_token_blocks_until_owner_grants_policy_exception() {
|
||||
let mut ledger = ApprovalTokenLedger::new();
|
||||
let scope = ApprovalScope::new("main_push_forbidden", "git push")
|
||||
.with_repository("sisyphus/claw-code")
|
||||
.with_branch("main");
|
||||
ledger.insert(ApprovalTokenGrant::pending(
|
||||
"tok-pending",
|
||||
scope.clone(),
|
||||
"repo-owner",
|
||||
"release-bot",
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
ledger.verify("tok-missing", &scope, "release-bot", 10),
|
||||
Err(ApprovalTokenError::NoApproval)
|
||||
));
|
||||
assert!(matches!(
|
||||
ledger.verify("tok-pending", &scope, "release-bot", 10),
|
||||
Err(ApprovalTokenError::ApprovalPending)
|
||||
));
|
||||
|
||||
ledger.insert(ApprovalTokenGrant::granted(
|
||||
"tok-granted",
|
||||
scope.clone(),
|
||||
"repo-owner",
|
||||
"release-bot",
|
||||
));
|
||||
let audit = ledger
|
||||
.verify("tok-granted", &scope, "release-bot", 10)
|
||||
.expect("owner approval should verify");
|
||||
|
||||
assert_eq!(audit.status, ApprovalTokenStatus::Granted);
|
||||
assert_eq!(audit.approving_actor, "repo-owner");
|
||||
assert_eq!(audit.executing_actor, "release-bot");
|
||||
assert!(audit.delegated_execution);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_token_is_one_time_use_and_rejects_replay() {
|
||||
let mut ledger = ApprovalTokenLedger::new();
|
||||
let scope = ApprovalScope::new("release_requires_owner", "release publish")
|
||||
.with_repository("sisyphus/claw-code");
|
||||
ledger.insert(ApprovalTokenGrant::granted(
|
||||
"tok-once",
|
||||
scope.clone(),
|
||||
"owner",
|
||||
"release-bot",
|
||||
));
|
||||
|
||||
let first = ledger
|
||||
.consume("tok-once", &scope, "release-bot", 10)
|
||||
.expect("first use should consume token");
|
||||
assert_eq!(first.status, ApprovalTokenStatus::Consumed);
|
||||
assert_eq!(first.uses, 1);
|
||||
|
||||
assert!(matches!(
|
||||
ledger.consume("tok-once", &scope, "release-bot", 11),
|
||||
Err(ApprovalTokenError::ApprovalAlreadyConsumed)
|
||||
));
|
||||
assert_eq!(
|
||||
ledger.get("tok-once").map(|grant| grant.status),
|
||||
Some(ApprovalTokenStatus::Consumed)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_token_rejects_scope_expansion_expiry_and_revocation() {
|
||||
let mut ledger = ApprovalTokenLedger::new();
|
||||
let scope = ApprovalScope::new("main_push_forbidden", "git push")
|
||||
.with_repository("sisyphus/claw-code")
|
||||
.with_branch("main");
|
||||
let dev_scope = ApprovalScope::new("main_push_forbidden", "git push")
|
||||
.with_repository("sisyphus/claw-code")
|
||||
.with_branch("dev");
|
||||
|
||||
ledger.insert(
|
||||
ApprovalTokenGrant::granted("tok-expiring", scope.clone(), "owner", "bot")
|
||||
.expires_at(20),
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
ledger.verify("tok-expiring", &dev_scope, "bot", 10),
|
||||
Err(ApprovalTokenError::ScopeMismatch { .. })
|
||||
));
|
||||
assert!(matches!(
|
||||
ledger.verify("tok-expiring", &scope, "bot", 21),
|
||||
Err(ApprovalTokenError::ApprovalExpired)
|
||||
));
|
||||
|
||||
ledger.insert(ApprovalTokenGrant::granted(
|
||||
"tok-revoked",
|
||||
scope.clone(),
|
||||
"owner",
|
||||
"bot",
|
||||
));
|
||||
let revoked = ledger
|
||||
.revoke("tok-revoked")
|
||||
.expect("revocation should be audited");
|
||||
assert_eq!(revoked.status, ApprovalTokenStatus::Revoked);
|
||||
assert!(matches!(
|
||||
ledger.verify("tok-revoked", &scope, "bot", 10),
|
||||
Err(ApprovalTokenError::ApprovalRevoked)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_token_preserves_delegation_traceability() {
|
||||
let mut ledger = ApprovalTokenLedger::new();
|
||||
let scope = ApprovalScope::new("deploy_requires_owner", "deploy prod");
|
||||
ledger.insert(
|
||||
ApprovalTokenGrant::granted("tok-delegated", scope.clone(), "owner", "deploy-bot")
|
||||
.with_delegation_hop(
|
||||
ApprovalDelegationHop::new("owner", "owner approval")
|
||||
.with_session_id("session-owner"),
|
||||
)
|
||||
.with_delegation_hop(
|
||||
ApprovalDelegationHop::new("lead-agent", "handoff to deploy bot")
|
||||
.with_session_id("session-lead"),
|
||||
),
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
ledger.verify("tok-delegated", &scope, "unexpected-bot", 10),
|
||||
Err(ApprovalTokenError::UnauthorizedDelegate { expected, actual })
|
||||
if expected == "deploy-bot" && actual == "unexpected-bot"
|
||||
));
|
||||
|
||||
let audit = ledger
|
||||
.consume("tok-delegated", &scope, "deploy-bot", 10)
|
||||
.expect("approved delegate should consume token");
|
||||
let actors = audit
|
||||
.delegation_chain
|
||||
.iter()
|
||||
.map(|hop| hop.actor.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(audit.delegated_execution);
|
||||
assert_eq!(actors, vec!["owner", "lead-agent", "deploy-bot"]);
|
||||
assert_eq!(
|
||||
audit.delegation_chain[0].session_id.as_deref(),
|
||||
Some("session-owner")
|
||||
);
|
||||
assert_eq!(
|
||||
audit.delegation_chain[1].session_id.as_deref(),
|
||||
Some("session-lead")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use std::process::{Command, Stdio};
|
||||
use std::time::Duration;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use tokio::process::Command as TokioCommand;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::time::timeout;
|
||||
@@ -176,27 +177,10 @@ async fn execute_bash_async(
|
||||
let mut command = prepare_tokio_command(&input.command, &cwd, &sandbox_status, true);
|
||||
|
||||
let output_result = if let Some(timeout_ms) = input.timeout {
|
||||
match timeout(Duration::from_millis(timeout_ms), command.output()).await {
|
||||
Ok(result) => (result?, false),
|
||||
Err(_) => {
|
||||
return Ok(BashCommandOutput {
|
||||
stdout: String::new(),
|
||||
stderr: format!("Command exceeded timeout of {timeout_ms} ms"),
|
||||
raw_output_path: None,
|
||||
interrupted: true,
|
||||
is_image: None,
|
||||
background_task_id: None,
|
||||
backgrounded_by_user: None,
|
||||
assistant_auto_backgrounded: None,
|
||||
dangerously_disable_sandbox: input.dangerously_disable_sandbox,
|
||||
return_code_interpretation: Some(String::from("timeout")),
|
||||
no_output_expected: Some(true),
|
||||
structured_content: None,
|
||||
persisted_output_path: None,
|
||||
persisted_output_size: None,
|
||||
sandbox_status: Some(sandbox_status),
|
||||
});
|
||||
}
|
||||
if let Ok(result) = timeout(Duration::from_millis(timeout_ms), command.output()).await {
|
||||
(result?, false)
|
||||
} else {
|
||||
return Ok(timeout_output(&input, timeout_ms, sandbox_status));
|
||||
}
|
||||
} else {
|
||||
(command.output().await?, false)
|
||||
@@ -233,6 +217,67 @@ async fn execute_bash_async(
|
||||
})
|
||||
}
|
||||
|
||||
fn timeout_output(
|
||||
input: &BashCommandInput,
|
||||
timeout_ms: u64,
|
||||
sandbox_status: SandboxStatus,
|
||||
) -> BashCommandOutput {
|
||||
let is_test = is_test_command(&input.command);
|
||||
let return_code_interpretation = if is_test { "test.hung" } else { "timeout" };
|
||||
BashCommandOutput {
|
||||
stdout: String::new(),
|
||||
stderr: format!("Command exceeded timeout of {timeout_ms} ms"),
|
||||
raw_output_path: None,
|
||||
interrupted: true,
|
||||
is_image: None,
|
||||
background_task_id: None,
|
||||
backgrounded_by_user: None,
|
||||
assistant_auto_backgrounded: None,
|
||||
dangerously_disable_sandbox: input.dangerously_disable_sandbox,
|
||||
return_code_interpretation: Some(String::from(return_code_interpretation)),
|
||||
no_output_expected: Some(true),
|
||||
structured_content: Some(vec![test_timeout_provenance(
|
||||
&input.command,
|
||||
timeout_ms,
|
||||
is_test,
|
||||
)]),
|
||||
persisted_output_path: None,
|
||||
persisted_output_size: None,
|
||||
sandbox_status: Some(sandbox_status),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_test_command(command: &str) -> bool {
|
||||
let normalized = command
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
.to_ascii_lowercase();
|
||||
normalized.contains("cargo test")
|
||||
|| normalized.contains("cargo nextest")
|
||||
|| normalized.contains("npm test")
|
||||
|| normalized.contains("pnpm test")
|
||||
|| normalized.contains("yarn test")
|
||||
|| normalized.contains("pytest")
|
||||
}
|
||||
|
||||
fn test_timeout_provenance(
|
||||
command: &str,
|
||||
timeout_ms: u64,
|
||||
classified_as_test_hang: bool,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"event": if classified_as_test_hang { "test.hung" } else { "command.timeout" },
|
||||
"failureClass": if classified_as_test_hang { "test_hang" } else { "timeout" },
|
||||
"data": {
|
||||
"command": command,
|
||||
"timeoutMs": timeout_ms,
|
||||
"provenance": "bash.timeout",
|
||||
"classification": if classified_as_test_hang { "test.hung" } else { "timeout" }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn sandbox_status_for_input(input: &BashCommandInput, cwd: &std::path::Path) -> SandboxStatus {
|
||||
let config = ConfigLoader::default_for(cwd).load().map_or_else(
|
||||
|_| SandboxConfig::default(),
|
||||
@@ -349,6 +394,31 @@ mod tests {
|
||||
|
||||
assert!(!output.sandbox_status.expect("sandbox status").enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timed_out_test_command_is_classified_as_hung_test_with_provenance() {
|
||||
let output = execute_bash(BashCommandInput {
|
||||
command: String::from("sleep 1 # cargo test slow_case"),
|
||||
timeout: Some(1),
|
||||
description: None,
|
||||
run_in_background: Some(false),
|
||||
dangerously_disable_sandbox: Some(false),
|
||||
namespace_restrictions: Some(false),
|
||||
isolate_network: Some(false),
|
||||
filesystem_mode: Some(FilesystemIsolationMode::WorkspaceOnly),
|
||||
allowed_mounts: None,
|
||||
})
|
||||
.expect("bash command should return structured timeout");
|
||||
|
||||
assert!(output.interrupted);
|
||||
assert_eq!(
|
||||
output.return_code_interpretation.as_deref(),
|
||||
Some("test.hung")
|
||||
);
|
||||
let structured = output.structured_content.expect("structured content");
|
||||
assert_eq!(structured[0]["event"], "test.hung");
|
||||
assert_eq!(structured[0]["data"]["provenance"], "bash.timeout");
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum output bytes before truncation (16 KiB, matching upstream).
|
||||
|
||||
@@ -212,7 +212,7 @@ fn summarize_messages(messages: &[ConversationMessage]) -> String {
|
||||
.filter_map(|block| match block {
|
||||
ContentBlock::ToolUse { name, .. } => Some(name.as_str()),
|
||||
ContentBlock::ToolResult { tool_name, .. } => Some(tool_name.as_str()),
|
||||
ContentBlock::Text { .. } => None,
|
||||
ContentBlock::Text { .. } | ContentBlock::Thinking { .. } => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
tool_names.sort_unstable();
|
||||
@@ -317,6 +317,9 @@ fn merge_compact_summaries(existing_summary: Option<&str>, new_summary: &str) ->
|
||||
fn summarize_block(block: &ContentBlock) -> String {
|
||||
let raw = match block {
|
||||
ContentBlock::Text { text } => text.clone(),
|
||||
ContentBlock::Thinking { thinking, .. } => {
|
||||
format!("thinking ({} chars)", thinking.chars().count())
|
||||
}
|
||||
ContentBlock::ToolUse { name, input, .. } => format!("tool_use {name}({input})"),
|
||||
ContentBlock::ToolResult {
|
||||
tool_name,
|
||||
@@ -378,6 +381,7 @@ fn collect_key_files(messages: &[ConversationMessage]) -> Vec<String> {
|
||||
ContentBlock::Text { text } => text.as_str(),
|
||||
ContentBlock::ToolUse { input, .. } => input.as_str(),
|
||||
ContentBlock::ToolResult { output, .. } => output.as_str(),
|
||||
ContentBlock::Thinking { thinking, .. } => thinking.as_str(),
|
||||
})
|
||||
.flat_map(extract_file_candidates)
|
||||
.collect::<Vec<_>>();
|
||||
@@ -400,6 +404,7 @@ fn first_text_block(message: &ConversationMessage) -> Option<&str> {
|
||||
ContentBlock::Text { text } if !text.trim().is_empty() => Some(text.as_str()),
|
||||
ContentBlock::ToolUse { .. }
|
||||
| ContentBlock::ToolResult { .. }
|
||||
| ContentBlock::Thinking { .. }
|
||||
| ContentBlock::Text { .. } => None,
|
||||
})
|
||||
}
|
||||
@@ -450,6 +455,10 @@ fn estimate_message_tokens(message: &ConversationMessage) -> usize {
|
||||
ContentBlock::ToolResult {
|
||||
tool_name, output, ..
|
||||
} => (tool_name.len() + output.len()) / 4 + 1,
|
||||
ContentBlock::Thinking {
|
||||
thinking,
|
||||
signature,
|
||||
} => thinking.len() / 4 + signature.as_ref().map_or(0, |value| value.len() / 4 + 1),
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ pub struct McpConfigCollection {
|
||||
/// MCP server config paired with the scope that defined it.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ScopedMcpServerConfig {
|
||||
pub required: bool,
|
||||
pub scope: ConfigSource,
|
||||
pub config: McpServerConfig,
|
||||
}
|
||||
@@ -414,6 +415,17 @@ impl RuntimeConfig {
|
||||
pub fn trusted_roots(&self) -> &[String] {
|
||||
&self.feature_config.trusted_roots
|
||||
}
|
||||
|
||||
/// Merge config-level default trusted roots with per-call roots.
|
||||
///
|
||||
/// Config roots are defaults and are kept first; per-call roots extend the
|
||||
/// allowlist for a specific worker/session creation request. Duplicates are
|
||||
/// removed without reordering the first occurrence so evidence remains
|
||||
/// deterministic while avoiding repeated trust checks.
|
||||
#[must_use]
|
||||
pub fn trusted_roots_with_overrides(&self, per_call_roots: &[String]) -> Vec<String> {
|
||||
merge_trusted_roots(self.trusted_roots(), per_call_roots)
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeFeatureConfig {
|
||||
@@ -483,6 +495,22 @@ impl RuntimeFeatureConfig {
|
||||
pub fn trusted_roots(&self) -> &[String] {
|
||||
&self.trusted_roots
|
||||
}
|
||||
|
||||
/// Merge this config's default trusted roots with per-call roots.
|
||||
#[must_use]
|
||||
pub fn trusted_roots_with_overrides(&self, per_call_roots: &[String]) -> Vec<String> {
|
||||
merge_trusted_roots(self.trusted_roots(), per_call_roots)
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_trusted_roots(config_roots: &[String], per_call_roots: &[String]) -> Vec<String> {
|
||||
let mut merged = Vec::with_capacity(config_roots.len() + per_call_roots.len());
|
||||
for root in config_roots.iter().chain(per_call_roots.iter()) {
|
||||
if !merged.contains(root) {
|
||||
merged.push(root.clone());
|
||||
}
|
||||
}
|
||||
merged
|
||||
}
|
||||
|
||||
impl ProviderFallbackConfig {
|
||||
@@ -725,6 +753,12 @@ fn merge_mcp_servers(
|
||||
target.insert(
|
||||
name.clone(),
|
||||
ScopedMcpServerConfig {
|
||||
required: optional_bool(
|
||||
expect_object(value, &format!("{}: mcpServers.{name}", path.display()))?,
|
||||
"required",
|
||||
&format!("{}: mcpServers.{name}", path.display()),
|
||||
)?
|
||||
.unwrap_or(false),
|
||||
scope: source,
|
||||
config: parsed,
|
||||
},
|
||||
@@ -1245,8 +1279,8 @@ fn push_unique(target: &mut Vec<String>, value: String) {
|
||||
mod tests {
|
||||
use super::{
|
||||
deep_merge_objects, parse_permission_mode_label, ConfigLoader, ConfigSource,
|
||||
McpServerConfig, McpTransport, ResolvedPermissionMode, RuntimeHookConfig,
|
||||
RuntimePluginConfig, CLAW_SETTINGS_SCHEMA_NAME,
|
||||
McpServerConfig, McpTransport, ResolvedPermissionMode, RuntimeFeatureConfig,
|
||||
RuntimeHookConfig, RuntimePluginConfig, CLAW_SETTINGS_SCHEMA_NAME,
|
||||
};
|
||||
use crate::json::JsonValue;
|
||||
use crate::sandbox::FilesystemIsolationMode;
|
||||
@@ -1502,6 +1536,51 @@ mod tests {
|
||||
fs::remove_dir_all(root).expect("cleanup temp dir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_roots_with_overrides_preserves_config_defaults_and_adds_per_call_roots() {
|
||||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
fs::write(
|
||||
home.join("settings.json"),
|
||||
r#"{"trustedRoots": ["/tmp/config-default", "/tmp/shared"]}"#,
|
||||
)
|
||||
.expect("write settings");
|
||||
|
||||
// when
|
||||
let loaded = ConfigLoader::new(&cwd, &home)
|
||||
.load()
|
||||
.expect("config should load");
|
||||
let merged = loaded.trusted_roots_with_overrides(&[
|
||||
"/tmp/per-call".to_string(),
|
||||
"/tmp/shared".to_string(),
|
||||
]);
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
merged,
|
||||
["/tmp/config-default", "/tmp/shared", "/tmp/per-call"]
|
||||
);
|
||||
|
||||
fs::remove_dir_all(root).expect("cleanup temp dir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_feature_trusted_roots_with_overrides_matches_runtime_config_merge() {
|
||||
let config = RuntimeFeatureConfig {
|
||||
trusted_roots: vec!["/tmp/config".to_string()],
|
||||
..RuntimeFeatureConfig::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
config.trusted_roots_with_overrides(&["/tmp/per-call".to_string()]),
|
||||
["/tmp/config", "/tmp/per-call"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_roots_default_is_empty_when_unset() {
|
||||
// given
|
||||
@@ -1538,7 +1617,8 @@ mod tests {
|
||||
"stdio-server": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-server"],
|
||||
"env": {"TOKEN": "secret"}
|
||||
"env": {"TOKEN": "secret"},
|
||||
"required": true
|
||||
},
|
||||
"remote-server": {
|
||||
"type": "http",
|
||||
@@ -1587,6 +1667,7 @@ mod tests {
|
||||
.get("stdio-server")
|
||||
.expect("stdio server should exist");
|
||||
assert_eq!(stdio_server.scope, ConfigSource::User);
|
||||
assert!(stdio_server.required);
|
||||
assert_eq!(stdio_server.transport(), McpTransport::Stdio);
|
||||
|
||||
let remote_server = loaded
|
||||
@@ -1594,6 +1675,7 @@ mod tests {
|
||||
.get("remote-server")
|
||||
.expect("remote server should exist");
|
||||
assert_eq!(remote_server.scope, ConfigSource::Local);
|
||||
assert!(!remote_server.required);
|
||||
assert_eq!(remote_server.transport(), McpTransport::Ws);
|
||||
match &remote_server.config {
|
||||
McpServerConfig::Ws(config) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user