Troubleshoot help system
Before you start
The help system has two layers that can fail independently: the manifest layer (feature discovery, features.yaml, staleness tracking) and the template layer (population, progressive depth, cross-link resolution, rendering). Identify which layer the failure is in before diving into code.
Symptom table
| If you observe | Check |
|---|---|
populate() or populate_progressive() returns None |
Confirm the template ID exists: call search_by_tag() or inspect the generated directory for the matching .md file |
generate_feature_templates() raises ValueError: Invalid feature name |
Verify Feature.name contains no path separators or unsupported characters before passing it to generate_feature_templates() |
check_staleness() reports every feature as stale after no code change |
Compare FeatureStaleness.current_hash vs FeatureStaleness.stored_hash; a missing or corrupt features.yaml resets all stored hashes |
run_maintenance() shows MaintenanceResult.regenerated_count of 0 but stale features remain |
Check MaintenanceResult.skipped_manual and MaintenanceResult.failed; manually edited templates are skipped by default |
get_precursor_warnings() returns an empty list for a file you expect to match |
Confirm match_files_to_features() maps that file to at least one feature; files outside the manifest's tracked paths produce no warnings |
get_workflow_help() returns irrelevant templates |
Call resolve_topic() with the same workflow_name to see which feature the manifest resolves to; the mismatch is usually in FeatureManifest.features |
| Cross-link resolution silently drops links | Call invalidate_cross_links_cache() to force a cache rebuild, then re-run populate() |
get_template_confidence() returns an unexpected score |
Inspect feedback.json in your generated directory; stale or corrupt feedback entries skew the score |
Renderers (render_cli, render_claude_code, render_marketplace) produce empty output |
Confirm populate() returned a non-None PopulatedTemplate before passing it to the renderer |
Step-by-step diagnosis
-
Reproduce the failure with a minimal call. Strip the failing call to its required arguments and run it in isolation. For example, if
populate()misbehaves, call it directly with onlytemplate_idand a bareAudienceProfile()— no extra context — and confirm the failure still occurs. -
Check the manifest is valid. Load the manifest explicitly and inspect it:
from help.manifest import load_manifest manifest = load_manifest("your/help_dir") print(manifest.version, list(manifest.features.keys()))A
KeyErroror emptyfeaturesdict meansfeatures.yamlis missing, malformed, or was never written bysave_manifest(). -
Check staleness state. Run a dry-run maintenance pass to see what the system considers stale without making changes:
from help.maintenance import run_maintenance result = run_maintenance("your/help_dir", "your/project_root", dry_run=True) print(result.stale_count, result.staleness.stale_features())If
stale_features()lists features you didn't change, compareFeatureStaleness.current_hashwithFeatureStaleness.stored_hashto find which source files changed. -
Inspect changed files.
from help.maintenance import get_changed_files, format_status_report from help.staleness import check_staleness from help.manifest import load_manifest manifest = load_manifest("your/help_dir") changed = get_changed_files("your/project_root") report = check_staleness(manifest, "your/help_dir", "your/project_root") print(format_status_report(report))This confirms which files triggered staleness and which features they map to via
match_files_to_features(). -
Reset session state if depth is stuck. Progressive depth is stateful. If
populate_progressive()always returns the same depth level, callreset_session()and retry:from help.engine import reset_session reset_session() -
Run the help system tests.
pytest -k "help" -v --tb=shortIf a test covers the failing path, its fixtures give you a reproducible starting point. Pay particular attention to tests covering
populate(),check_staleness(), andrun_maintenance().
Common fixes
-
Missing or stale
features.yaml. Regenerate it from a fresh scan:from help.bootstrap import scan_project, proposals_to_manifest from help.manifest import save_manifest proposals = scan_project("your/project_root") manifest = proposals_to_manifest(proposals) save_manifest(manifest, "your/help_dir") -
Template not found after generation. If
populate()returnsNonefor a template you just generated, confirmoverwrite=Truewas passed togenerate_feature_templates()when regenerating, and that thehelp_dirpath you pass topopulate()matches the one used during generation. -
Stale cross-link cache. Call
invalidate_cross_links_cache()fromhelp.templatesafter adding or removing templates. The cache is not invalidated automatically when files change on disk. -
Corrupt or missing
feedback.json. Ifget_template_confidence()raises or returns nonsensical values, deletefeedback.jsonfrom the generated directory. The file is recreated on the nextrecord_template_feedback()call. -
Dependency version mismatch. If behavior changed after a dependency upgrade, run
pip show <package>to confirm the installed version. The manifest format (features.yamlversion field:FeatureManifest.version) can also vary between releases — check that the version integer matches what your installed code expects. -
Manually edited templates blocking regeneration.
run_maintenance()skips templates listed inMaintenanceResult.skipped_manual. To force regeneration, callgenerate_feature_templates()directly withoverwrite=True.
Source files
src/attune/help/**packages/attune-help/src/attune_help/**
Tags: help, templates, docs