Skip to content

Commit 6a732b6

Browse files
committed
feat: strengthen onboarding explainers with llm modes and quality gates
1 parent c14699c commit 6a732b6

File tree

11 files changed

+368
-73
lines changed

11 files changed

+368
-73
lines changed

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Outputs include:
66

77
- Crisp top-level overview (`OVERVIEW.md`)
88
- Linked deep explainers (architecture, modules, flows, dependencies, glossary)
9-
- Optional interactive HTML explainer (`html/ONBOARDING.html`)
9+
- Interactive HTML explainer (`html/ONBOARDING.html`)
1010
- Mermaid source diagrams (`.mmd`)
1111
- Rendered SVG and PNG diagrams
1212
- Confidence, attribution, and quality reports (`meta/*.json`)
@@ -151,9 +151,9 @@ python scripts/analyze.py analyze \
151151
--explainer-type onboarding \
152152
--audience nontech \
153153
--overview-length medium \
154-
--enable-llm-descriptions true \
155-
--ask-before-llm-use false \
156-
--prompt-for-llm-key false \
154+
--llm-mode auto \
155+
--ask-before-llm-use true \
156+
--prompt-for-llm-key true \
157157
--enable-web-enrichment true
158158
```
159159

@@ -171,9 +171,11 @@ For LLM-based narrative summaries:
171171

172172
- Set `CODE_EXPLAINER_LLM_API_KEY` (or `OPENAI_API_KEY`)
173173
- Optional: `CODE_EXPLAINER_LLM_BASE_URL`, `CODE_EXPLAINER_LLM_MODEL`
174+
- Control behavior via `--llm-mode auto|required|off`
174175
- Optional interactive controls:
175-
- `--ask-before-llm-use true` (prompt for permission)
176-
- `--prompt-for-llm-key true` (securely prompt for key when missing)
176+
- `--ask-before-llm-use true` (prompt for permission in interactive terminals)
177+
- `--prompt-for-llm-key true` (securely prompt for key when missing in interactive terminals)
178+
- Legacy compatibility: `--enable-llm-descriptions true|false` maps to `auto|off`
177179

178180
## Install From GitHub (For Other Developers)
179181

code-explainer/SKILL.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ python scripts/analyze.py analyze \
4242
--plan-file <path> \
4343
--include-glob <pattern> \
4444
--exclude-glob <pattern> \
45-
--enable-llm-descriptions <true|false> \
45+
--llm-mode <auto|required|off> \
4646
--ask-before-llm-use <true|false> \
4747
--prompt-for-llm-key <true|false> \
4848
--enable-web-enrichment <true|false>
@@ -51,13 +51,13 @@ python scripts/analyze.py analyze \
5151
Defaults:
5252

5353
- `mode=standard`
54-
- `format=markdown`
54+
- `format=both`
5555
- `explainer-type=onboarding`
5656
- `audience=nontech`
5757
- `overview-length=medium`
58-
- `enable-llm-descriptions=true`
59-
- `ask-before-llm-use=false`
60-
- `prompt-for-llm-key=false`
58+
- `llm-mode=auto`
59+
- `ask-before-llm-use=true` (interactive terminals)
60+
- `prompt-for-llm-key=true` (interactive terminals)
6161
- `enable-web-enrichment=true`
6262

6363
## Dependencies
@@ -108,9 +108,11 @@ bash ./scripts/install_runtime.sh
108108
- Without `mmdc`, fallback rendering is used and flagged in reports.
109109
- For LLM narrative summaries, set `CODE_EXPLAINER_LLM_API_KEY` (or `OPENAI_API_KEY`).
110110
- Optional: set `CODE_EXPLAINER_LLM_BASE_URL` and `CODE_EXPLAINER_LLM_MODEL`.
111-
- If you want interactive control, enable:
111+
- Interactive prompts are supported:
112112
- `--ask-before-llm-use true`
113113
- `--prompt-for-llm-key true`
114+
- If you need strict narrative generation, run with `--llm-mode required`.
115+
- Legacy flag remains supported: `--enable-llm-descriptions <true|false>`.
114116
- This skill does not mutate the analyzed target repository.
115117

116118
## Dependency Troubleshooting

code-explainer/references/mode-behavior.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ Goal: Maximum fidelity and audit-ready onboarding.
4242

4343
## LLM Narrative
4444

45-
- Controlled with `--enable-llm-descriptions <true|false>`.
46-
- Optional interactive controls:
47-
- `--ask-before-llm-use true`
48-
- `--prompt-for-llm-key true`
45+
- Controlled with `--llm-mode <auto|required|off>`.
46+
- `auto`: try LLM when possible, otherwise continue with deterministic fallback.
47+
- `required`: fail quality gate unless LLM narrative is successfully used.
48+
- `off`: deterministic-only mode; no LLM attempt.
49+
- Interactive controls (enabled by default in `analyze.py`):
50+
- `--ask-before-llm-use true|false`
51+
- `--prompt-for-llm-key true|false`
52+
- Legacy compatibility: `--enable-llm-descriptions <true|false>` maps to `auto|off`.
4953
- Reads API config from env vars:
5054
- `CODE_EXPLAINER_LLM_API_KEY` (or `OPENAI_API_KEY`)
5155
- `CODE_EXPLAINER_LLM_BASE_URL` (optional)
@@ -56,6 +60,7 @@ Goal: Maximum fidelity and audit-ready onboarding.
5660
- `--format markdown`: generate markdown explainers (overview + deep docs).
5761
- `--format html`: generate a single interactive HTML explainer page.
5862
- `--format both`: generate markdown + interactive HTML.
63+
- Default format is `both`.
5964

6065
## Explainer Type
6166

code-explainer/references/output-contract.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
- `docs_parsed`
5959
- `llm_descriptions_enabled`
6060
- `llm_descriptions_used`
61+
- `llm_mode`
6162
- `llm_model`
6263
- `verification_fact_count`
6364
- `fact_check_passed`
@@ -113,9 +114,13 @@
113114

114115
- `generated_at`
115116
- `enabled`
117+
- `llm_mode`
116118
- `used`
117119
- `asked_before_use`
120+
- `consent_granted`
121+
- `consent_mode`
118122
- `prompted_for_key`
123+
- `api_key_source`
119124
- `provider`
120125
- `model`
121126
- `repo_summary_paragraph`

code-explainer/scripts/analyze.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def _write_manifest(
9393
entry_payload: Dict[str, Any],
9494
docs_payload: Dict[str, Any],
9595
llm_payload: Dict[str, Any],
96+
llm_mode: str,
9697
verification_payload: Dict[str, Any],
9798
html_payload: Dict[str, Any],
9899
fact_check_payload: Dict[str, Any],
@@ -118,6 +119,7 @@ def _write_manifest(
118119
"docs_parsed": docs_payload.get("parsed_count", 0),
119120
"llm_descriptions_enabled": llm_payload.get("enabled", False),
120121
"llm_descriptions_used": llm_payload.get("used", False),
122+
"llm_mode": llm_mode,
121123
"llm_model": llm_payload.get("model", ""),
122124
"verification_fact_count": verification_payload.get("fact_count", 0),
123125
"fact_check_passed": fact_check_payload.get("passed", False),
@@ -139,7 +141,7 @@ def run_pipeline(
139141
output_format: str,
140142
analysis_type: str,
141143
enable_web_enrichment: bool,
142-
enable_llm_descriptions: bool,
144+
llm_mode: str,
143145
ask_before_llm_use: bool = False,
144146
prompt_for_llm_key: bool = False,
145147
include_globs: List[str] | None = None,
@@ -209,7 +211,7 @@ def run_pipeline(
209211
docs_payload=coverage_payload,
210212
context_payload=context_payload,
211213
out_dir=meta_dir,
212-
enabled=enable_llm_descriptions,
214+
llm_mode=llm_mode,
213215
ask_before_use=ask_before_llm_use,
214216
prompt_for_key=prompt_for_llm_key,
215217
)
@@ -289,6 +291,7 @@ def run_pipeline(
289291
entry_payload=entry_payload,
290292
docs_payload=coverage_payload,
291293
llm_payload=llm_payload,
294+
llm_mode=llm_mode,
292295
verification_payload=verification_payload,
293296
html_payload=html_payload,
294297
fact_check_payload=fact_check_payload,
@@ -303,6 +306,7 @@ def run_pipeline(
303306
mode=mode,
304307
output_format=output_format,
305308
analysis_type=analysis_type,
309+
llm_mode=llm_mode,
306310
)
307311

308312
return {
@@ -313,6 +317,7 @@ def run_pipeline(
313317
"output_format": output_format,
314318
"audience": audience,
315319
"overview_length": overview_length,
320+
"llm_mode": llm_mode,
316321
"file_count": index_payload.get("file_count", 0),
317322
"docs_discovered": coverage_payload.get("discovered_count", 0),
318323
"docs_parsed": coverage_payload.get("parsed_count", 0),
@@ -339,7 +344,7 @@ def _parse_args() -> argparse.Namespace:
339344
parser.add_argument("--mode", default="standard", choices=["quick", "standard", "deep"])
340345
parser.add_argument("--audience", default="nontech", choices=["nontech", "mixed", "engineering"])
341346
parser.add_argument("--overview-length", default="medium", choices=["short", "medium", "long"])
342-
parser.add_argument("--format", default="markdown", choices=["markdown", "html", "both"])
347+
parser.add_argument("--format", default="both", choices=["markdown", "html", "both"])
343348
parser.add_argument(
344349
"--explainer-type",
345350
default="onboarding",
@@ -361,9 +366,10 @@ def _parse_args() -> argparse.Namespace:
361366
help="Glob(s) to exclude from indexing.",
362367
)
363368
parser.add_argument("--enable-web-enrichment", default="true")
364-
parser.add_argument("--enable-llm-descriptions", default="true")
365-
parser.add_argument("--ask-before-llm-use", default="false")
366-
parser.add_argument("--prompt-for-llm-key", default="false")
369+
parser.add_argument("--llm-mode", default="auto", choices=["auto", "required", "off"])
370+
parser.add_argument("--enable-llm-descriptions", default="")
371+
parser.add_argument("--ask-before-llm-use", default="true")
372+
parser.add_argument("--prompt-for-llm-key", default="true")
367373
return parser.parse_args()
368374

369375

@@ -375,7 +381,9 @@ def main() -> int:
375381

376382
mode = common.normalize_mode(args.mode)
377383
web_enabled = common.bool_from_string(args.enable_web_enrichment)
378-
llm_enabled = common.bool_from_string(args.enable_llm_descriptions)
384+
llm_mode = (args.llm_mode or "auto").strip().lower()
385+
if args.enable_llm_descriptions.strip():
386+
llm_mode = "auto" if common.bool_from_string(args.enable_llm_descriptions) else "off"
379387
ask_before_llm_use = common.bool_from_string(args.ask_before_llm_use)
380388
prompt_for_llm_key = common.bool_from_string(args.prompt_for_llm_key)
381389
summary = run_pipeline(
@@ -387,7 +395,7 @@ def main() -> int:
387395
output_format=args.format,
388396
analysis_type=args.explainer_type,
389397
enable_web_enrichment=web_enabled,
390-
enable_llm_descriptions=llm_enabled,
398+
llm_mode=llm_mode,
391399
ask_before_llm_use=ask_before_llm_use,
392400
prompt_for_llm_key=prompt_for_llm_key,
393401
include_globs=args.include_glob,
@@ -405,6 +413,7 @@ def main() -> int:
405413
"output_format",
406414
"audience",
407415
"overview_length",
416+
"llm_mode",
408417
"file_count",
409418
"docs_discovered",
410419
"docs_parsed",

code-explainer/scripts/common.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,17 @@ def top_level_modules(files: Iterable[Dict[str, Any]], max_items: int = 60) -> L
199199

200200
top = path.split("/", 1)[0]
201201
if top not in buckets:
202-
buckets[top] = {"name": top, "type": "directory", "file_count": 0, "total_bytes": 0}
202+
buckets[top] = {
203+
"name": top,
204+
"type": "directory",
205+
"file_count": 0,
206+
"total_bytes": 0,
207+
"examples": [],
208+
}
203209
buckets[top]["file_count"] += 1
204210
buckets[top]["total_bytes"] += size
211+
if len(buckets[top]["examples"]) < 10:
212+
buckets[top]["examples"].append(path)
205213

206214
ranked = sorted(
207215
buckets.values(), key=lambda m: (m["file_count"], m["total_bytes"]), reverse=True

0 commit comments

Comments
 (0)