Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Notable changes to Agent Code Guard are recorded here.

### Added

- Read-only `code-guard doctor` human and JSON diagnostics for the active
installation, bundled skill, configuration, Git context, and parser providers.
- Compact and explicit debug completed-analysis JSON serialization modes while
preserving bare `--json` compatibility.
- Deterministic `code-guard --version` reporting from installed distribution
Expand Down
6 changes: 6 additions & 0 deletions docs/skill-distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ copies only `SKILL.md`, `LICENSE.txt`, `agents/openai.yaml`, and the policy file
under `references/`; the checkout-only `scripts/code_guard.py` compatibility
runner is deliberately excluded.

For read-only troubleshooting, `code-guard doctor` (or `code-guard doctor
--json`) validates the canonical bundled payload alongside the active runtime
and providers without exporting or updating it. Healthy diagnostics exit `0`
and completed unhealthy diagnostics exit `1`. Reports contain resolved paths
and environment details that may be sensitive when shared.

Direct `--skill-path` use is intrinsically version-coupled to the installed
Python distribution. An export is a snapshot and contains a generated
`.agent-code-guard-version` marker with the producing distribution version.
Expand Down
26 changes: 26 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,32 @@ use an `error` object on standard output. If distribution metadata is
unavailable, the error is
`installed distribution metadata is unavailable for agent-code-guard`.

### Installation diagnostics

Inspect the active installation and its immediate local capabilities without
running project analysis:

```bash
code-guard doctor
code-guard doctor --json
```

The human and JSON reports cover the active distribution, Python process,
invoked entry point, bundled skill, current-directory configuration and Git
context, and all supported parser providers. A healthy report exits `0`; a
completed unhealthy report exits `1`; invocation or internal failures that
prevent a report exit `3`. Completed reports use standard output only.

`doctor` is reserved only as the exact first token. Analyze a file or directory
with that name through a qualified spelling such as `./doctor`, `.\doctor`, or
an absolute path. Doctor is read-only: it does not analyze source, repair or
install dependencies, export skills, modify configuration or Git state, access
the network, or persist diagnostics.

Diagnostic output includes resolved launcher, interpreter, skill, Git, and
configuration paths. Treat those paths and other environment details as
potentially sensitive before sharing a report.

### Git changed-only

```bash
Expand Down
1 change: 1 addition & 0 deletions src/agent_code_guard/analysis/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
".hpp": "cpp", ".hh": "cpp", ".hxx": "cpp",
".rs": "rust", ".php": "php", ".swift": "swift", ".dart": "dart",
}
PROVIDER_LANGUAGES = (*dict.fromkeys(LANGUAGE_BY_SUFFIX.values()), "vue")
APPLICABLE_SUFFIXES = frozenset((*LANGUAGE_BY_SUFFIX, ".vue"))


Expand Down
55 changes: 53 additions & 2 deletions src/agent_code_guard/code_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ def parser() -> argparse.ArgumentParser:
prog="code-guard",
description="Run deterministic Code Guard checks.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""Version reporting:
epilog="""Diagnostics:
code-guard doctor
code-guard doctor --json
Inspect the active installation and current directory without running analysis.
Healthy reports exit 0; unhealthy reports exit 1; invocation/internal errors exit 3.

Version reporting:
code-guard --version
Output: agent-code-guard <version>
code-guard --version --json
Expand All @@ -47,7 +53,7 @@ def parser() -> argparse.ArgumentParser:
)
value.add_argument(
"paths", nargs="*", default=[],
help="Files or directories to inspect; bounds files selected by a Git selection mode.",
help="Files or directories to inspect; exact first token 'doctor' selects diagnostics mode.",
)
value.add_argument("--config", help="Path to code-guard.config.json.")
value.add_argument("--warn", type=int, help="Override the global LOC warning threshold.")
Expand Down Expand Up @@ -84,6 +90,38 @@ def parser() -> argparse.ArgumentParser:
return value


def _doctor_mode(args: argparse.Namespace, raw_arguments: list[str]) -> int | None:
if not raw_arguments or raw_arguments[0] != "doctor":
return None
incompatible = (
args.paths != ["doctor"]
or args.config is not None
or args.warn is not None
or args.fail is not None
or args.version
or args.json_mode is not None
or args.ci
or args.changed_only
or args.staged
or args.base_ref is not None
or bool(args.include)
or bool(args.exclude)
or bool(args.scope_exclude)
or args.count_blank_lines
or args.ignore_comment_lines
or args.skill_path
or args.export_skill is not None
)
if incompatible:
raise ValueError("doctor may be combined only with --json")
report = gather_doctor_report()
if args.json:
print(json.dumps(report, indent=2))
else:
print(format_doctor_report(report))
return 0 if report["status"] == "healthy" else 1


def _version_mode(args: argparse.Namespace) -> int | None:
if not args.version:
return None
Expand Down Expand Up @@ -347,6 +385,9 @@ def main() -> int:
try:
if args.json_mode is not None and not args.json:
raise ValueError("--json-mode requires --json")
doctor_result = _doctor_mode(args, raw_arguments)
if doctor_result is not None:
return doctor_result
version_result = _version_mode(args)
if version_result is not None:
return version_result
Expand All @@ -365,5 +406,15 @@ def main() -> int:
return _print_tool_error(str(exc), args.json)


def gather_doctor_report() -> dict[str, object]:
from .doctor import gather_report
return gather_report()


def format_doctor_report(report: dict[str, object]) -> str:
from .doctor import format_human
return format_human(report)


if __name__ == "__main__":
sys.exit(main())
Loading
Loading