{"record":{"id":"449516cead7f134d","repo":"Graphify-Labs/graphify","slug":"module-name-r-has-no-attribute-name-r","errorCode":null,"errorMessage":"module {__name__!r} has no attribute {name!r}","messagePattern":"module (.+?) has no attribute (.+?)","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"graphify/__main__.py","lineNumber":155,"sourceCode":"_ALWAYS_ON_ALIASES = {\n    \"_CLAUDE_MD_SECTION\": \"claude-md\",\n    \"_AGENTS_MD_SECTION\": \"agents-md\",\n    \"_GEMINI_MD_SECTION\": \"gemini-md\",\n    \"_VSCODE_INSTRUCTIONS_SECTION\": \"vscode-instructions\",\n    \"_ANTIGRAVITY_RULES\": \"antigravity-rules\",\n    \"_KIRO_STEERING\": \"kiro-steering\",\n}\n\n\ndef __getattr__(name: str) -> str:\n    # PEP 562: lazily resolve the legacy always-on section constants for external\n    # importers (e.g. the install-string tests). In-module code calls _always_on()\n    # directly; nothing is read at import time, so a missing block can no longer\n    # brick the CLI on `import graphify.__main__` (#1121 follow-up).\n    base = _ALWAYS_ON_ALIASES.get(name)\n    if base is not None:\n        return _always_on(base)\n    raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")\n\n\n\n\n\n\n\n\ndef _check_skill_version(skill_dst: Path) -> None:\n    \"\"\"Warn if the installed skill is from an older graphify version.\"\"\"\n    version_file = skill_dst.parent / \".graphify_version\"\n    try:\n        if not version_file.exists():\n            return\n    except OSError:\n        return\n    try:\n        skill_exists = skill_dst.exists()","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/graphify/__main__.py#L137-L173","documentation":"A deliberate shrink-guard (#479) in the graphify export step (tools/skillgen/expected/graphify__skill-vscode.md:433). graphify.export.to_json(G, communities, 'graphify-out/graph.json') compares the new graph's node count against the existing graph.json and returns False (writing nothing) when the new graph is smaller. The script then exits 1 so that GRAPH_REPORT.md and .graphify_analysis.json are never written for a graph that graph.json does not actually contain (#1392).","triggerScenarios":"Calling to_json() on a graph whose node count is lower than the node count stored in the already-existing graphify-out/graph.json. Typical trigger: an incremental --update re-extract that found fewer files (or skipped some) than the original full build, or the corpus genuinely shrank because files were deleted.","commonSituations":"Running graphify update after deleting source files; partial re-extraction where some files were skipped; stale graph.json from a previous larger corpus left in graphify-out/; switching INPUT_PATH to a smaller subtree without clearing the old output.","solutions":["If the shrink is intentional (files were deleted from the corpus), re-run a full build with --force as the error message instructs, so graph.json is overwritten regardless of node count.","If the shrink is NOT intentional, inspect why extraction produced fewer nodes: skipped files, changed skip rules, wrong INPUT_PATH, or a partial extraction.","Compare node counts before deciding: read the 'nodes' length in the existing graphify-out/graph.json versus G.number_of_nodes() printed by the pipeline.","If graphify-out is stale from another project, delete graphify-out/ entirely and rebuild from scratch."],"exampleFix":"# before: incremental update refuses to shrink\n$(cat graphify-out/.graphify_python) graphify update .\n# ERROR: refused to shrink ... (#479)\n\n# after: intentional shrink -> force full rebuild\n$(cat graphify-out/.graphify_python) graphify build --force .","handlingStrategy":"validation","validationCode":"import json\nfrom pathlib import Path\n\nexisting = Path('graphify-out/graph.json')\nif existing.exists():\n    old_n = len(json.loads(existing.read_text(encoding='utf-8')).get('nodes', []))\n    extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding='utf-8'))\n    new_n = len(extract.get('nodes') or [])\n    if new_n < old_n:\n        print(f'planned shrink {old_n} -> {new_n}: pass --force if intentional, else investigate skipped files')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Compare new vs existing node counts before export; pass --force only when the corpus shrink is intentional.","After deleting files from the corpus, always follow with a full --force rebuild rather than a plain update.","Inspect .graphify_detect.json for skipped files whenever the node count drops unexpectedly.","Never reuse a graphify-out/ directory across different repositories or INPUT_PATH values."],"tags":["graphify","shrink-guard","export","data-integrity","skillgen"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}