mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-26 21:54:56 +08:00
feat: #173 — wrap_json_envelope() applied to all 13 clawable commands (LOOP CLOSED)
Completes the coverage → enforcement → documentation → alignment cycle.
Every clawable command now emits the canonical JSON envelope per SCHEMAS.md:
Common fields (now real in output):
- timestamp (ISO 8601 UTC)
- command (argv[1])
- exit_code (0/1/2)
- output_format ('json')
- schema_version ('1.0')
13 commands wrapped:
- list-sessions, delete-session, load-session, flush-transcript
- show-command, show-tool
- exec-command, exec-tool, route, bootstrap
- command-graph, tool-pool, bootstrap-graph
Implementation:
- Added wrap_json_envelope() helper in src/main.py
- Wrapped all 18 JSON output paths (13 success + 5 error paths)
- Applied exit_code=1 to error/not-found envelopes
- Kept text mode byte-identical (backward compat preserved)
Test updates:
- 3 skipped common-field tests now pass automatically
- 3 existing tests updated to verify common envelope fields while preserving command-specific field checks
- test_list_sessions_cli_runs, test_delete_session_cli_idempotent,
test_load_session_cli::test_json_mode_on_success
Full suite: 179 → 182 passing (+3 activated from skipped), zero regression.
Loop completion:
Coverage (#167-#170) ✅ All 13 commands accept --output-format
Enforcement (#171) ✅ CI blocks new commands without --output-format
Documentation (#172) ✅ SCHEMAS.md defines envelope contract
Alignment (#173 this) ✅ Actual output matches SCHEMAS.md contract
Example output now:
$ claw list-sessions --output-format json
{
"timestamp": "2026-04-22T10:34:12Z",
"command": "list-sessions",
"exit_code": 0,
"output_format": "json",
"schema_version": "1.0",
"sessions": ["alpha", "bravo"],
"count": 2
}
Closes ROADMAP #173. Protocol is now documented AND real.
Claws can build ONE error handler, ONE timestamp parser, ONE version check
instead of 13 special cases.
This commit is contained in:
@@ -200,7 +200,13 @@ class PortingWorkspaceTests(unittest.TestCase):
|
||||
check=True, capture_output=True, text=True,
|
||||
)
|
||||
data = json.loads(json_result.stdout)
|
||||
self.assertEqual(data, {'sessions': ['alpha', 'bravo'], 'count': 2})
|
||||
# Verify common envelope fields (SCHEMAS.md contract)
|
||||
self.assertIn('timestamp', data)
|
||||
self.assertEqual(data['command'], 'list-sessions')
|
||||
self.assertEqual(data['schema_version'], '1.0')
|
||||
# Verify command-specific fields
|
||||
self.assertEqual(data['sessions'], ['alpha', 'bravo'])
|
||||
self.assertEqual(data['count'], 2)
|
||||
|
||||
def test_delete_session_cli_idempotent(self) -> None:
|
||||
"""#160: delete-session CLI is idempotent (not-found is exit 0, status=not_found)."""
|
||||
@@ -221,10 +227,16 @@ class PortingWorkspaceTests(unittest.TestCase):
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
self.assertEqual(first.returncode, 0)
|
||||
self.assertEqual(
|
||||
json.loads(first.stdout),
|
||||
{'session_id': 'once', 'deleted': True, 'status': 'deleted'},
|
||||
)
|
||||
envelope_first = json.loads(first.stdout)
|
||||
# Verify common envelope fields (SCHEMAS.md contract)
|
||||
self.assertIn('timestamp', envelope_first)
|
||||
self.assertEqual(envelope_first['command'], 'delete-session')
|
||||
self.assertEqual(envelope_first['exit_code'], 0)
|
||||
self.assertEqual(envelope_first['schema_version'], '1.0')
|
||||
# Verify command-specific fields
|
||||
self.assertEqual(envelope_first['session_id'], 'once')
|
||||
self.assertEqual(envelope_first['deleted'], True)
|
||||
self.assertEqual(envelope_first['status'], 'deleted')
|
||||
# second delete: idempotent, still exit 0
|
||||
second = subprocess.run(
|
||||
[sys.executable, '-m', 'src.main', 'delete-session', 'once',
|
||||
@@ -232,10 +244,10 @@ class PortingWorkspaceTests(unittest.TestCase):
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
self.assertEqual(second.returncode, 0)
|
||||
self.assertEqual(
|
||||
json.loads(second.stdout),
|
||||
{'session_id': 'once', 'deleted': False, 'status': 'not_found'},
|
||||
)
|
||||
envelope_second = json.loads(second.stdout)
|
||||
self.assertEqual(envelope_second['session_id'], 'once')
|
||||
self.assertEqual(envelope_second['deleted'], False)
|
||||
self.assertEqual(envelope_second['status'], 'not_found')
|
||||
|
||||
def test_delete_session_cli_partial_failure_exit_1(self) -> None:
|
||||
"""#160: partial-failure (permission error) surfaces as exit 1 + typed JSON error."""
|
||||
|
||||
Reference in New Issue
Block a user