{"slug":"assess","title":"assess","summary":"Assess a codebase's readiness for AI agent contributors using the layered contract model, and generate a complexity hotspot SVG treemap (size = LOC, hue = cyclomatic complexity, saturation = recent git churn). TRIGGER when the user types /assess, asks for an AI-readiness review, ","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-05T10:19:04.242843Z","repo":{"url":"https://github.com/bjcoombs/ai-native-toolkit","stars":31,"forks":5,"license":"Apache-2.0","updatedAt":"2026-09-20T13:49:18Z"},"bodyHtml":"<h1>skills/assess/scripts/lib</h1>\n<p>Deterministic library modules for the <code>/assess</code> engine. No LLM calls anywhere in this\npackage - every function is a pure transform of filesystem, git, or pre-computed signal\ndata. The LLM reads <code>run-context.json</code> after the core finishes; it does not call into\nthese modules.</p>\n<h2>The assess_core.py -&gt; lib seam</h2>\n<p><code>assess_core.py</code> is the orchestrator. It imports from almost every module here, calls\neach one in sequence, and assembles the results into <code>run-context.json</code>. When the\norchestrator grows a new signal, reorganizes the context schema, or changes what data\nblocks it needs, the lib modules that supply that data move in the same commit. This\nis cohesion by design: the lib modules exist to serve the orchestrator's data contract,\nso their shape is coupled to it by construction.</p>\n<p>Two modules are the identified co-change hotspots in the git history:</p>\n<ul>\n<li><p><strong><code>doc_graph.py</code></strong> - the foundation for Layer 0 navigability. Both <code>doc_staleness.py</code>\nand the understanding analysis in <code>keyhole_signals.py</code> depend on its doc-&gt;code\nassociation edges. When the core adds a navigability signal or changes how it\nrepresents doc reachability, <code>doc_graph.py</code> is the first file that moves.</p>\n</li>\n<li><p><strong><code>keyhole_signals.py</code></strong> - the integration barrier between the individual signal\nmodules and the orchestrator. It assembles all five run-context blocks\n(<code>behaviour</code>, <code>documentation</code>, <code>understanding</code>, <code>runtime</code>, <code>structure</code>) and emits\nthe six named derived findings. Because it touches every upstream signal, it\nco-changes with the core on almost every schema or signal-set change.</p>\n</li>\n</ul>\n<p>Seeing these two files in the same commit as <code>assess_core.py</code> is expected, not a\ndefect. If the core is later decomposed, treat this seam as the natural boundary -\n<code>doc_graph</code> and <code>keyhole_signals</code> are where the cut-line already lives.</p>\n<h2>The wider co-change seams (cohesion, not entanglement)</h2>\n<p><code>/assess</code>'s own hotspot scan flags several directories as historically co-changing:\n<code>skills/assess/scripts</code>, <code>skills/assess/scripts/lib</code>, <code>skills/assess/tests</code>,\n<code>skills/assess/tests/fixtures</code>, and <code>scripts</code> (+ <code>scripts/tests</code>). That is the static\nimport map saying \"separate directories\" while git says \"they move together.\" Here the\ncoupling is genuine subsystem cohesion, and naming it is the point - so a reader (or an\nagent) trusts the boundary deliberately rather than being surprised by the seam:</p>\n<ul>\n<li><strong><code>scripts/lib</code> modules &lt;-&gt; <code>skills/assess/tests</code>.</strong> Each deterministic module is\npinned by a test in <code>skills/assess/tests/</code>, and the contract in <code>CLAUDE.md</code> is explicit\n(\"Add a test alongside any change to a deterministic module\"). So a module and its test\nare <em>meant</em> to move in the same commit - the test is the module's behavioural contract,\nnot a separable concern. A reviewer seeing <code>doc_graph.py</code> and <code>test_doc_graph.py</code> in one\ndiff is seeing the intended unit of change.</li>\n<li><strong><code>scripts/</code> (standalone build) &lt;-&gt; <code>skills/</code>.</strong> The standalone-skill build under\n<code>scripts/</code> vendors and transforms the very skills it packages, so a change to a skill's\nshape and the build that ships it co-change by construction. That seam is documented in\n<code>CLAUDE.md</code>'s \"Standalone skill pipeline\" section; it is cohesion between a packager and\nthe thing it packages, not a leak.</li>\n</ul>\n<p>None of these is a refactor task. They are recorded here so the seam is owned: if any is\never cut, this note is where the intended boundary is written down.</p>\n<hr>\n<h2>Module reference</h2>\n<h3>Data collection</h3>\n<p><strong><code>git_churn.py</code></strong>\nShared git-churn machinery: per-file commit counts over a configurable window, plus\n<code>git_commit_info</code> for snapshotting the exact SHA and timestamp at run time. Used by the\ncode heatmap, the doc-staleness heatmap, and <code>doc_staleness.py</code> - churn is computed\none way, not three. Pure subprocess + stdlib, no heavy dependencies.</p>\n<p><strong><code>change_coupling.py</code></strong>\nThree signals derived from <code>git log</code>:</p>\n<ul>\n<li>B1 change-coupling pairs: file pairs that co-change across commits (hidden edges\nthe import graph cannot see).</li>\n<li>B2 containment ratio: fraction of commits to a module that touch only that module\n(high = an island safe for keyhole edits).</li>\n<li>B4 authorship: human/agent/mixed/unknown classification, e-mail-based and\ndeliberately conservative (never labels a human's work \"agent\" on weak evidence).</li>\n</ul>\n<p>All results are JSON-serialisable so <code>assess_core</code> can drop them straight into\n<code>run-context.json</code>.</p>\n<h3>Static analysis</h3>\n<p><strong><code>structure_graph.py</code></strong>\nPython import-graph analysis (Signals A1-A4) via <code>grimp</code> + <code>networkx</code>:</p>\n<ul>\n<li>A1 comprehension footprint: direct-dependency surface area for a unit.</li>\n<li>A2 blob vs modular: strongly-connected components and Newman modularity Q.</li>\n<li>A3 contracts: front-door vs burrow (internals-reaching) inbound edges.</li>\n<li>A4 breakup candidates: packages whose sub-modules form separable clusters.</li>\n</ul>\n<p>Degrades to <code>available: False</code> when <code>grimp</code>/<code>networkx</code> are absent rather than\nblocking the run.</p>\n<h3>Document analysis</h3>\n<p><strong><code>doc_graph.py</code></strong> <em>(co-change hotspot)</em>\nDoc link-graph for Layer 0 navigability. Parses <code>[[wikilinks]]</code> and\n<code>[text](relative/path)</code> links, resolves them to real files, and builds a directed\ngraph. Derives PageRank centrality, orphan rate, connectivity, MOC validation, and\ndoc-&gt;code association edges. The doc-&gt;code edges are reused by <code>doc_staleness.py</code> and\n<code>understanding_analysis.py</code>, making this module a shared dependency for the document\nanalysis layer. Folds in vault-native navigation edges from <code>vault_queries.py</code> (<code>.base</code>\nhubs become entry nodes; <code>dataview</code> query blocks emit edges from their declaring note),\nso a vault navigated by dynamic queries doesn't score as orphaned. <code>doc-graph-svg.py</code>\nrenders this exact graph, so the two artifacts always agree. Obsidian-vault detection\n(<code>_vault_detected</code>) walks the repo subtree, pruning <code>EXCLUDE_DIRS</code>, to find a <code>.obsidian/</code>\ndirectory anywhere under <code>repo_root</code>, not just at the root - a vault kept as a subdirectory\n(<code>repo/notes/.obsidian/</code>) sits below the <code>git rev-parse --show-toplevel</code> scan target and was\npreviously reported as no vault, silently disabling downstream vault accommodations (#179).\nPruning <code>EXCLUDE_DIRS</code> keeps a vendored or build-artifact <code>.obsidian/</code> from tripping a false\npositive.</p>\n<p><strong><code>raw_source.py</code></strong>\nRaw-source subtree detection (issue #225). Threshold-based, IO-free classifier:\ngiven per-doc graph signals (in/out degree + a machine-extraction-link count), it\nidentifies maximal directory subtrees that are large, almost entirely\nlink-isolated, and carry the machine-extraction fingerprint - a dump of raw,\nmachine-extracted source documents (a disclosure / SAR export of converted\n<code>.msg</code>/<code>.pdf</code> files). <code>doc_graph.py</code> gathers the signals from the link graph it\nalready builds and excludes the qualifying subtrees from the headline read-side\nmetrics (orphan rate, reachability, broken links), reporting them separately so\nthe curated-wiki signal isn't drowned. Pure - no <code>doc_graph</code> import - so\n<code>doc_graph.py</code> owns the graph and consumes this module's verdict. Co-changes with\n<code>doc_graph.py</code> (its consumer) and its test <code>tests/test_raw_source.py</code>.</p>\n<p><strong><code>vault_queries.py</code></strong>\nStatic parser for Obsidian dynamic-navigation hubs: <code>.base</code> view files and\n<code>dataview</code> query blocks. Resolves the folder / tag / frontmatter-field\npredicates it can evaluate from the committed files alone (no running Obsidian) into the\nset of notes a hub surfaces. Pure - no filesystem walk, no <code>doc_graph</code> import - so\n<code>doc_graph.py</code> owns discovery/excludes and consumes this module's parse + selection. Add\na fixture-backed test here alongside any predicate change.</p>\n<p><strong><code>doc_staleness.py</code></strong>\nDoc-staleness metric for Layer 0. Associates each doc with the code it describes via\nnearest-ancestor base-doc rules, computes code churn relative to doc maintenance, and\nemits a signed ratio (high = decaying map). The association logic reuses <code>doc_graph</code>'s\ncode-link edges. For <em>generated</em> docs it reads <code>doc_provenance</code> (per-doc and via the\n<code>[[generated]]</code> config map) and replaces the churn ratio with a source-vs-doc verdict.</p>\n<p><strong><code>doc_provenance.py</code></strong>\nProvenance-aware staleness for generated docs (issue #178). Parses a doc's YAML\nfrontmatter <code>source:</code> / <code>generated_by:</code> (no YAML dependency - a minimal stdlib parser),\nresolves the <code>[[generated]]</code> folder-&gt;source config mapping, and computes <code>source_newer</code>\n(is any declared source's last change - git commit time, else mtime - more recent than\nthe doc?). <code>doc_staleness.py</code> carries that verdict and <code>doc_complexity_join.py</code> signs\nfreshness from it, so an accurate generated doc is never a <code>lying_map</code>. Co-changes with\n<code>doc_staleness.py</code> (its consumer) and its test <code>tests/test_doc_provenance.py</code>.</p>\n<p><strong><code>doc_complexity_join.py</code></strong>\nSignal C: crosses the complexity treemap against doc staleness to produce a signed\n<code>doc_value</code> per unit. Positive = the doc relieves load on the agent's context window;\nnegative = the doc is a lying map over complex code (worse than no doc). The join\nmultiplies complexity by a signed freshness score so trivial code generates near-zero\nsignal regardless of doc state. When a doc carries a <code>doc_provenance</code> verdict, freshness\ncomes from the source-vs-doc comparison (a direct, high-confidence signal that bypasses\nthe churn-ratio confidence guards) instead of the ratio.</p>\n<h3>Ownership and structure drift</h3>\n<p><strong><code>ownership_parser.py</code></strong>\nParse half for Layer 0 structure-drift. Ownership is <em>declared</em> in two places an LLM\ncontributor reads as authoritative boundaries: a GitHub <code>CODEOWNERS</code> (glob -&gt; owner,\nhonoured at the root, <code>.github/</code>, or <code>docs/</code> by GitHub's precedence) and a\nboundary-declaring <code>ARCHITECTURE.md</code> / <code>DESIGN.md</code> - or a seam-mapping <code>README.md</code>\nadmitted only when its prose carries the ownership/seam vocabulary, so a generic project\nREADME is skipped. <code>parse_codeowners</code> resolves each glob against the git-tracked,\nnon-excluded file set into <code>{glob_pattern: {matched_files}}</code>; <code>parse_architecture_md</code>\nsections each doc by markdown header and attributes the path references in a section's\nbody (read from inline code, wikilinks, and bare prose paths) to the module the header\nnames, keyed <code>&lt;doc&gt;::&lt;header&gt;</code> so two docs declaring the same section don't collide, and\nkeeping only references that resolve to real files. <code>find_empty_globs</code> flags the CODEOWNERS\npatterns that match zero tracked files - the cheapest drift, a boundary the filesystem has\nleft behind - and <code>parse_ownership</code> runs both with the module-shape degradation contract\n(<code>available: False</code>, reason <code>\"no ownership map\"</code> when neither map exists). It mirrors\n<code>doc_graph.py</code> deliberately: the same <code>EXCLUDE_DIRS</code> / <code>is_excluded_path</code> resolution and\n<code>tracked_files</code> filtering, the same honest-degrade-over-crash shape, every collection\nsorted at the boundary for byte-identical output. The one inversion is that it <em>reads</em>\ninline-code path spans (a path written as code is the boundary declaration we want) where\nthe doc graph strips them. This is the parsing foundation the structure-drift signals\nconsume; it owns no drift logic itself.</p>\n<p><strong><code>structure_drift.py</code></strong>\nThe Layer 0 structure-drift signal, built entirely on <code>ownership_parser</code>'s parse + resolve\nprimitives (it re-implements no parsing). A declaration is a self-description under no\npressure to stay true: a directory is renamed, a module's files scatter, a pattern is\ntypo'd - and the map becomes a <em>lying map of ownership</em>, the same defect as a stale doc\n(behaviour) or an aged TODO (intent). <strong>Tier 0</strong> - <code>detect_path_existence_drift()</code> - is the\nzero-threshold cut: the enumerate-both-sides shape <code>doc_graph.py</code> uses for broken links,\nwhere side A is every declared boundary (CODEOWNERS globs + architecture-doc path\nreferences) and side B the tracked, non-excluded file set, and the finding is the\ndeclarations whose match set is empty. Binary, no statistics; a pattern matching only\nexcluded files counts as empty (the excludes are not part of the navigable repo). It emits\na JSON-serialisable <code>structure_drift</code> run-context block (<code>empty_ownership_patterns</code>, a\ncoverage ratio, and legacy-shape <code>empty_globs</code> mirrors for the orchestrator's\nenumerate-both-sides view), degrades to <code>available: False</code> reason <code>\"no ownership map\"</code>, and\nsorts every list by <code>(pattern, declared_in)</code>. <strong>Tier 1</strong> - <code>detect_grouping_disagreement()</code> -\nis the next cut up: not \"does the boundary still match any file?\" but \"do the files a\nboundary groups still belong together?\". Three lenses each induce a grouping - the declared\none (an owner / architecture module groups its files), the static one (an import-graph\ncommunity groups co-dependent modules), and the historical one (files that keep co-changing).\nA grouping is reported as its <strong>co-membership equivalence relation</strong> over canonical file-pairs\n<code>(min(a, b), max(a, b))</code>, so disagreement is set algebra over pairs and <strong>invariant to\ncommunity relabeling by construction</strong> - relabel or reorder the groups and nothing changes,\nbecause the relation carries pairs, never labels. The six metrics are set differences /\nintersections of the three relations (human-vs-static splits/fuses, human-vs-cochange, and\nthe two agreement sets). Known-good architectural seams (the lib&lt;-&gt;tests and standalone-build\n&lt;-&gt;skills seams documented above) are an allowlist subtracted from the <em>denominator</em> - correct\nby construction, a seam can only suppress an owned boundary, never manufacture drift. Tier 1\ndegrades to <code>available: False</code> reason <code>\"no ownership map\"</code> and emits its own <code>tier_1_available</code>\nfield. <code>keyhole_signals.py</code> orchestrates Tier 1 (feeding it the behaviour block's already-parsed\nco-change pairs) and folds the hidden-seam direction (<code>human_split_but_cochange</code>) into the\nexisting <code>hidden_coupling</code> finding - a recurring directory pair, not the version hot-file's\nrepo-wide couplings; <code>assess_core.py</code> serialises the Tier 0 + Tier 1 result into the\n<code>structure_drift</code> run-context block. Co-changes with <code>ownership_parser.py</code> (its parse foundation),\n<code>structure_graph.py</code> and <code>change_coupling.py</code> (the static and historical lenses it consumes),\n<code>keyhole_signals.py</code> / <code>assess_core.py</code> (its orchestrator and serialiser), and its test\n<code>tests/test_structure_drift.py</code>.</p>\n<h3>Signal integration</h3>\n<p><strong><code>keyhole_signals.py</code></strong> <em>(co-change hotspot)</em>\nIntegration barrier between the individual signal modules and <code>assess_core</code>. Derives\nthe per-directory containment view from the commit-file sets the orchestrator parses\nonce and passes in, then assembles all five run-context blocks from the upstream signal\noutputs. Emits the named derived findings as a fixed-order structured array (the single\nsource of order is <code>FINDING_ORDER</code>): <code>hidden_coupling</code>, <code>lying_map</code>,\n<code>unexplained_complexity</code>, <code>untrusted_hotspot</code>, <code>self_referential_tests</code>,\n<code>unactioned_intent</code>, <code>accretion_ratchet</code>, <code>orphaned_understanding</code>,\n<code>candidate_dead_weight</code>, <code>override_contradicts_signals</code> (archetype marker disagrees with\nthe deterministic signals - fed the <code>archetype</code> block), and the one positive\n<code>refactor_boundary</code> last. After assembly <code>apply_config_excludes</code> drops any finding path a\nuser config exclude (<code>exclude_dirs</code>/<code>exclude_patterns</code>) covers - the git-log-derived\nfindings never saw the scan-level filter - and returns the dropped paths as\n<code>excluded_finding_paths</code> so <code>assess_core</code> can serialise the <code>excluded_by_config</code>\ndisclosure (a suppressed finding is counted and named, never silently vanished). Each\nblock build is wrapped in a catch-all so one signal's failure degrades that block to\n<code>available: False</code> rather than crashing the run. It also runs <code>structure_drift.py</code>'s Tier 1\ngrouping disagreement (fed the behaviour block's co-change pairs so no second git-log parse\nhappens) and folds its hidden-seam direction into the <code>hidden_coupling</code> finding, returning the\nTier 1 result for <code>assess_core</code> to serialise into the <code>structure_drift</code> run-context block - so\nstructure-drift findings flow through this barrier rather than being assembled in the orchestrator.</p>\n<p><strong><code>coupling_analysis.py</code></strong>\nB3 static-vs-historical disagreement cross: compares the import-graph view\n(<code>structure_graph</code>) against the commit-history view (<code>change_coupling</code>) to surface\nhidden coupling (looks modular, bleeds historically), bleeding modules (no static graph\navailable), and refactor boundaries (high containment + low external coupling, a safe\nzone for keyhole edits). Looks-coupled-but-never-co-changes is suppressed - the static\ngraph already surfaces it.</p>\n<p><strong><code>understanding_analysis.py</code></strong>\nSignals B4 + D2. Per module: human anchor (has a confirmed human authored it?), intent\nsource (is there an externalised spec/doc?), authorship class, and the velocity clock\n(days since the last comprehension event - a human-authored commit - rather than\ncalendar age). Primary finding: orphaned understanding - high complexity with no human\nanchor and no externalised spec. Reuses <code>change_coupling.authorship_analysis</code> so the\nconservative agent/human classification is defined one way.</p>\n<h3>Configuration</h3>\n<p><strong><code>assess_config.py</code></strong>\nReads the optional per-repo <code>.assess/config.toml</code>: <code>exclude_dirs</code> / <code>exclude_patterns</code>\n(the same two lists feed every scan - heatmap, doc graph, staleness, liveness - so\nexclusion is consistent), the <code>[gate]</code> and <code>[structure]</code> sections, and the <code>[[generated]]</code>\nfolder-&gt;source provenance map (issue #178) consumed by <code>doc_provenance.py</code>. <code>resolve_excludes</code>\nis the single shared path that combines config excludes with CLI <code>--exclude</code>; both the treemap\nCLI and <code>doc-graph-svg.py</code> call it, so every artifact computes over the identical doc/code set.\nDegrades silently on missing or malformed config rather than blocking the run.</p>\n<h3>Output and formatting</h3>\n<p><strong><code>wiki_writer.py</code></strong>\nRenders and writes the <code>.assess/</code> wiki files (<code>index.md</code>, <code>log.md</code>,\n<code>hotspots/*.md</code>) from string templates. No LLM calls. Pure string formatting + file IO.\nAn optional <code>run_id</code>/<code>schema_version</code> prepends a non-rendering HTML-comment\nprovenance stamp to each file (omitted -&gt; byte-identical legacy output).\nAlso guards wiki integrity: <code>prune_orphan_hotspots(assess_dir, repo_root)</code> stamps\nany hotspot page whose source file left the tree as <code>retired - file deleted</code>\n(history preserved, no active page lies about a live file); <code>append_log_entry</code>\nchains each <code>log.md</code> entry with a <code>&lt;!-- chain:&lt;hash&gt; --&gt;</code> marker and\n<code>verify_log_chain(assess_dir)</code> returns <code>(valid, broken_at_entry)</code> so a later edit\nof a prior entry is detected and disclosed. Both are additive and back-compat -\na legacy wiki (no markers, live files) is untouched and reads valid.</p>\n<p><strong><code>treemap_render.py</code></strong>\nShared treemap layout and SVG primitives for the code heatmap and the doc-staleness\nheatmap. Pulls <code>matplotlib</code>, <code>squarify</code>, and <code>numpy</code>. Must not be imported by the\ndeterministic core (which runs with <code>networkx</code> alone) - only by the treemap scripts.</p>\n<p><strong><code>ci_workflow.py</code></strong>\nRenders the frozen-harness GitHub Action from <code>templates/assess-gate.yml.template</code>\nusing <code>string.Template</code>. Bakes in the toolchain discovered during the current run so\nthe workflow is a reproducible contract, not a norm. The emitted workflow pins its\nsupply chain (actions to commit SHAs, tools to exact releases) and degrades infra\nfailures - toolkit fetch, tool installs, uv setup - to a skip notice so the gate's\nwarn-only contract survives a flaky network or a missing tag.</p>\n<p><strong><code>stats_diff.py</code></strong>\nCompares current complexity stats against a prior run and classifies hotspot\ntransitions: graduated (was in top list, now absent), regressed (worsened), new, and\npersistent. Pure set operations + arithmetic, no LLM.</p>\n<h3>Scoring</h3>\n<p><strong><code>agent_instructions_grader.py</code></strong>\nHeuristic scoring of agent instruction files (CLAUDE.md, AGENTS.md, GEMINI.md,\n.cursorrules, .github/copilot-instructions.md) on signals that correlate with LLM\nusefulness: positive directives, tradeoff phrases, path references, verifiable\noutcomes, and freshness. Pure regex + arithmetic, filename-agnostic.</p>\n<p><strong><code>liveness_scan.py</code></strong>\nLayer 1 liveness inputs, three tiers:</p>\n<ul>\n<li>Dead-code tier: runs a language-appropriate static dead-code tool (vulture, ts-prune,\nstaticcheck, etc.) to flag candidate-dead exports within the repo boundary.</li>\n<li>Observability tier: scores three rungs - instrumented (telemetry emitted), discoverable\n(runbook present), reachable (agent has an invokable path to runtime state). The\nreachability rung decides the Layer 1 score.</li>\n<li>Capability-offer tier: delegates to <code>jvm_capabilities.py</code> to detect JVM/Maven build\nsystems and report, per analysis capability, whether a serving tool is already\nconfigured, could be run/installed in-session, or honest-degrades with a named\ncandidate. Surfaced so a non-enumerated ecosystem proposes a tool rather than\nsilently reading \"absent\".</li>\n</ul>\n<p><strong><code>jvm_capabilities.py</code></strong>\nJVM/Maven capability-driven analysis offers (issue #113, v1 bounded). Generalises\n<code>/assess</code>'s tool mapping from a hardcoded per-language allowlist (vulture for Python,\nts-prune for TS, staticcheck for Go) to a <em>capability-driven detect-or-propose</em> model,\nproven on one capability (liveness) in one build system (Maven). Reports each capability\nin one of four states - <code>served</code>, <code>offer</code> (with a run-or-install <code>consent</code> shape),\n<code>credited</code> (a configured pom.xml plugin already serves it), or <code>honest_degrade</code> (nothing\nserves it yet; the report names the capability and a candidate tool). Imported by\n<code>liveness_scan.py</code>, never by the orchestrator - it is an inward dependency of the\nliveness tier.</p>\n<p><strong><code>promissory_markers.py</code></strong>\nWrite-side erosion instrument: detects the four families of promissory markers\n(TODO/FIXME, deprecations, lint suppressions, disabled tests) via one rg pass per\nfamily, then ages each marker by <em>survived touches</em> - the number of commits to its\nfile since the marker's introducing commit (batched <code>git blame</code> + one git-log\npass). A marker that survived many edits to an actively-maintained file is\nunactioned intent; calendar age alone can't tell that from dormancy. Classifies\nmarkers as tracked (issue/ticket/URL/date reference, or a justified suppression)\nvs bare, and each introducing commit as agent/human (reusing <code>change_coupling</code>'s\nconservative B4 identity rules). Honours the shared excludes and the generated-file\nfilter (codegen <code>ignore_for_file</code> boilerplate is not debt), and degrades aging to\n<code>aging_reliable: False</code> on degenerate history (same verdict as <code>git_churn</code>).\nFeeds the <code>unactioned_intent</code> derived finding, the hotspot pages' marker-debt\nsentence, and the Layer 3/5/8 erosion rules. New ecosystem marker syntaxes need a\nfixture in <code>tests/test_promissory_markers.py</code> - absence is a silent miss.</p>\n<p><strong><code>agent_ops.py</code></strong>\nLayer 8 workflow-maturity evidence: scans the repo-observable agent-operations\nguardrails - <code>.claude/settings.json</code> / <code>.claude/settings.local.json</code> permission\n<code>allow</code>/<code>deny</code>/<code>ask</code> counts, hook events, sandbox config; scripts under\n<code>.claude/hooks/</code>; routine definitions under <code>.claude/workflows/</code> /\n<code>.claude/routines/</code>. Summary booleans (<code>permissions_encoded</code>, <code>hooks_present</code>,\n<code>routines_present</code>) credit <strong>git-tracked</strong> evidence only, mirroring the Layer 0\nrule (an uncommitted settings file reaches no clone). Deliberately excludes\n<code>.claude/agents/</code> and <code>.claude/skills/</code> - those are Layer 0's evidence - so the\ntwo layers never double-count. Pure stdlib JSON/filesystem reads plus\n<code>git_churn.tracked_files</code>.</p>\n<p><strong><code>accretion_ratchet.py</code></strong>\nWrite-side accretion instrument: detects files that only ever grow. Walks each\nfile's full numstat history in author-time order (one <code>git log --no-merges --no-renames --numstat</code> pass, sorted explicitly by <code>%at</code> then SHA so the\naccumulation order is clone-independent) and flags a file when its running\nnet-delta is non-decreasing <em>and</em> its deletion fraction (deletions over total\nchurn) stays below a threshold - growth with almost no deletion pressure, the\nfingerprint of pure append-only accretion rather than ordinary maintenance. A\nmulti-commit gate drops single-touch rename artifacts; binary files (numstat\n<code>-</code>) are skipped. Compensates the <em>Accretion</em> contributor tendency named in the\nrepo north star. Degrades to <code>available: False</code> on git failure and\n<code>reliable: False</code> on degenerate history (same verdict as <code>git_churn</code>). Reuses\n<code>git_churn</code>'s <code>GIT_TIMEOUT_SECONDS</code> and <code>churn_is_degenerate</code>; imports no\norchestrator. Add a fixture-backed test in <code>tests/test_accretion_ratchet.py</code>\nalongside any change to the flagging rule.</p>\n<p><strong><code>archetype.py</code></strong>\nRepository archetype detection (issue #224). Classifies a repo as <code>software</code> or\n<code>knowledge-base</code> from the code-file ratio + the absence of a runtime surface\n(<code>package.json</code>/<code>pyproject.toml</code>/<code>go.mod</code>/<code>Dockerfile</code>/...), with an\n<code>assess-archetype:</code> marker in any instruction file that <strong>forces or suppresses</strong>\nthe verdict. A knowledge base has no code surface for the write-side layers, so\nthe block names L2-L7 as <code>na_layers</code> (rendered N/A, excluded from the\ndenominator) and renormalises the denominator over the applicable layers (L0,\nL1, L8 → 3). Also detects the Karpathy LLM-wiki maintenance pattern\n(<code>kb_maintenance</code> - immutable sources, schema-as-product, ingest, query-as-filing,\nperiodic consolidation) as a Layer 0 read-side signal and always carries the\ngist pointer. When an <code>assess-archetype:</code> marker forces a classification the\ndeterministic signals disagree with, the override still wins but the block sets\n<code>override_contradicts_signals</code> + <code>contradiction_details</code> (and records the marker's\n<code>override_source</code> file) so <code>keyhole_signals</code> can fire the visible\n<code>override_contradicts_signals</code> finding rather than swallowing the override\nsilently. The IO-free <code>classify_archetype</code> is the tested core; <code>analyze_archetype</code>\ngathers the inputs. Imports <code>doc_graph</code> (extensions) and <code>git_churn</code>\n(<code>tracked_files</code>), never an orchestrator. Structured as an extensible dispatch\n(one archetype today, YAGNI on a general framework). Read by <code>assess_core</code>\n(writes the <code>archetype</code> block to <code>run-context.json</code>), the <code>assess-layer-scorer</code>\nagent (N/A scoring + denominator), and the <code>assess-findings</code> skill (renders N/A</p>\n<ul>\n<li>renormalised headline). Co-changes with <code>badge.py</code>/<code>assess_finalize.py</code> (the\ndenominator) and its test <code>tests/test_archetype.py</code>.</li>\n</ul>\n<p><strong><code>badge.py</code></strong>\nShields.io endpoint badge for the wiki (<code>.assess/badge.json</code>). The shipped badge\nis deterministic by default: <code>assess_core</code> always writes the findings-count form\n(<code>fallback_badge</code>, \"2 findings · 0 stale markers\", colour banded from the counts)\nand stamps a <code>link</code> to <code>assess-report.md</code>, so a badge-clicker lands on the full\nreport. The LLM-derived headline (<code>score_badge</code>, \"7.0/8 · AI-Native\",\nrenormalised over its <code>denominator</code> - 8 for software, the applicable-layer count\nfor a knowledge-base archetype, e.g. \"2.5/3 · Knowledge Base · Solid\") is <em>not</em>\nwritten to the badge; it appears inside <code>assess-report.md</code>, so the badge never\nclaims a grade a deterministic run cannot reproduce. Pure threshold functions,\nfixture-tested. Also exposes <code>maturity_band</code>\n(the same score/denominator fraction mapped to the named tier ladder), the\nsingle source of truth <code>assess_finalize</code> reconciles the LLM's <code>maturity_label</code>\nagainst. Both producers accept an optional <code>run_id</code> provenance stamp.</p>\n<p><strong><code>anomaly_detector.py</code></strong>\nInspects a run-context dict for suspicious results (e.g. zero files scored, implausible\nCCN) and returns typed <code>Anomaly</code> records. Detail strings are sanitised (counts and\ngrades only, no paths or code) so they are safe to include in self-feedback issues.</p>\n<p><strong><code>coverage_report.py</code></strong>\nParses an <em>existing</em> coverage report into the shape the <code>test_pressure</code> scan's\n<code>coverage_data=</code> param consumes (<code>{_overall, per_file: {relpath: line_rate}}</code>).\nTwo formats: Cobertura <code>coverage.xml</code> (<code>_overall</code> from the root <code>line-rate</code>,\nper-file from each <code>&lt;class&gt;</code> element's <code>filename</code>/<code>line-rate</code>; one <code>iter(\"class\")</code>\nwalk handles both the flat and nested <code>&lt;packages&gt;</code> schemas) and <code>lcov.info</code>\n(per-file <code>LH/LF</code>, overall <code>sum(LH)/sum(LF)</code>). <code>/assess</code> never runs the suite, so a\nreport the project already generated is the only honest line-coverage source - the\nparser reads it without taking a coverage.py runtime dependency. <code>detect_coverage_report</code>\nsearches the repo root, <code>./coverage/</code>, and <code>./.coverage/</code> (a <code>.coverage</code> SQLite <em>file</em>\nis out of scope - reading it needs the coverage.py lib). Honest-degrade is the hard\ncontract: a missing or malformed report returns <code>None</code>, never raises, never blocks the\nassessment; <code>assess_core.py</code> records provenance (\"none found\" vs. the file/format read)\nseparately. Stdlib only, imports no orchestrator. Add fixtures + cases in\n<code>tests/test_coverage_report.py</code> alongside any change to a parse rule.</p>\n<p><strong><code>test_focus.py</code></strong>\nCross-joins three already-collected signals - the hotspot risk band (position in\n<code>complexity_stats.top_hotspots</code>), the parsed coverage report, and the\n<code>test_pressure</code> cheap heuristics - into one ranked focus list. <code>compute_test_focus</code>\nclassifies each top-10 hot file (<code>no_covering_test</code> / <code>covered_but_hollow</code> /\n<code>covered_clean</code> / <code>unknown_no_coverage</code>), filters out <code>covered_clean</code>, and ranks by\nrisk band then signal severity. Honest-degrade is the contract: <code>coverage_data is None</code> makes every file <code>unknown_no_coverage</code> (never <code>covered_clean</code>) and records\n<code>coverage_present: False</code>; it never raises. Pure data - inputs are passed in\n(<code>hot_files</code>, <code>coverage_data</code>, <code>cheap_heuristics</code>), no file I/O, stdlib only,\nimports no orchestrator. This block is the SINGLE source both the report focus\ntable and the mutation offer consume - it is the contract, not duplicated\ndownstream. Add cases in <code>tests/test_test_focus.py</code> alongside any change to a\nclassification or ranking rule.</p>\n<p><strong><code>test_pressure/</code></strong>\nLayer 1 write-side truth pressure. Two tiers:</p>\n<ul>\n<li>Mutation tier: runs a mutation-testing tool (mutmut for Python) over a sample of the\ncodebase to measure whether the test suite actually catches changes. For mutmut the\nrun is two-step - <code>mutmut run</code> then <code>mutmut junitxml</code> - because the run's stdout lists\nonly survivors (no totals); the junitxml report carries every mutant, so per-file\nkilled/survived/total (and a real survivor density) can be derived. Falls back to the\nsurvivor-only stdout parse when junitxml is absent (e.g. mutmut 3.x) or empty.</li>\n<li>Cheap heuristics: test/source ratio, assertion density, and the coverage gap signal -\nfast proxies that run without a mutation tool.</li>\n</ul>\n","files":[{"path":"pyproject.toml","sizeBytes":3273,"isText":true},{"path":"references/actions-schema.md","sizeBytes":5088,"isText":true},{"path":"references/consent-lifecycle.md","sizeBytes":6298,"isText":true},{"path":"references/monorepo-scoping.md","sizeBytes":4157,"isText":true},{"path":"references/uninstall.md","sizeBytes":4444,"isText":true},{"path":"scripts/assess_core.py","sizeBytes":87049,"isText":true},{"path":"scripts/assess_emit_workflow.py","sizeBytes":11714,"isText":true},{"path":"scripts/assess_finalize.py","sizeBytes":31041,"isText":true},{"path":"scripts/assess_gate.py","sizeBytes":12815,"isText":true},{"path":"scripts/assess_report.py","sizeBytes":23374,"isText":true},{"path":"scripts/complexity-treemap.py","sizeBytes":62919,"isText":true},{"path":"scripts/doc-graph-svg.py","sizeBytes":26980,"isText":true},{"path":"scripts/lib/accretion_ratchet.py","sizeBytes":15713,"isText":true},{"path":"scripts/lib/agent_instructions_grader.py","sizeBytes":17732,"isText":true},{"path":"scripts/lib/agent_ops.py","sizeBytes":5773,"isText":true},{"path":"scripts/lib/anomaly_detector.py","sizeBytes":2958,"isText":true},{"path":"scripts/lib/archetype.py","sizeBytes":16518,"isText":true},{"path":"scripts/lib/assess_config.py","sizeBytes":16032,"isText":true},{"path":"scripts/lib/badge.py","sizeBytes":7599,"isText":true},{"path":"scripts/lib/change_coupling.py","sizeBytes":23438,"isText":true},{"path":"scripts/lib/ci_workflow.py","sizeBytes":9523,"isText":true},{"path":"scripts/lib/config_drift.py","sizeBytes":17866,"isText":true},{"path":"scripts/lib/coupling_analysis.py","sizeBytes":10918,"isText":true},{"path":"scripts/lib/coverage_report.py","sizeBytes":8778,"isText":true},{"path":"scripts/lib/dart_capabilities.py","sizeBytes":8925,"isText":true},{"path":"scripts/lib/dart_complexity.py","sizeBytes":12589,"isText":true},{"path":"scripts/lib/decline_markers.py","sizeBytes":7700,"isText":true},{"path":"scripts/lib/doc_complexity_join.py","sizeBytes":16243,"isText":true},{"path":"scripts/lib/doc_graph.py","sizeBytes":56983,"isText":true},{"path":"scripts/lib/doc_provenance.py","sizeBytes":9401,"isText":true},{"path":"scripts/lib/doc_staleness.py","sizeBytes":22927,"isText":true},{"path":"scripts/lib/evidence_check.py","sizeBytes":14728,"isText":true},{"path":"scripts/lib/gap_actions.py","sizeBytes":4292,"isText":true},{"path":"scripts/lib/gate_cost.py","sizeBytes":3901,"isText":true},{"path":"scripts/lib/generated_files.py","sizeBytes":6734,"isText":true},{"path":"scripts/lib/gh_cli.py","sizeBytes":6234,"isText":true},{"path":"scripts/lib/git_churn.py","sizeBytes":24000,"isText":true},{"path":"scripts/lib/__init__.py","sizeBytes":446,"isText":true},{"path":"scripts/lib/instruction_claims.py","sizeBytes":19721,"isText":true},{"path":"scripts/lib/interactivity.py","sizeBytes":4479,"isText":true},{"path":"scripts/lib/jvm_capabilities.py","sizeBytes":21806,"isText":true},{"path":"scripts/lib/keyhole_signals.py","sizeBytes":63025,"isText":true},{"path":"scripts/lib/liveness_scan.py","sizeBytes":33861,"isText":true},{"path":"scripts/lib/ownership_parser.py","sizeBytes":19251,"isText":true},{"path":"scripts/lib/promissory_markers.py","sizeBytes":21014,"isText":true},{"path":"scripts/lib/raw_source.py","sizeBytes":16886,"isText":true},{"path":"scripts/lib/README.md","sizeBytes":61870,"isText":true},{"path":"scripts/lib/review_reality.py","sizeBytes":12016,"isText":true},{"path":"scripts/lib/sibling_tests.py","sizeBytes":16121,"isText":true},{"path":"scripts/lib/stats_diff.py","sizeBytes":3282,"isText":true},{"path":"scripts/lib/structure_drift.py","sizeBytes":30262,"isText":true},{"path":"scripts/lib/structure_graph.py","sizeBytes":21060,"isText":true},{"path":"scripts/lib/test_focus.py","sizeBytes":16191,"isText":true},{"path":"scripts/lib/test_pressure/aggregate.py","sizeBytes":4010,"isText":true},{"path":"scripts/lib/test_pressure/common.py","sizeBytes":1846,"isText":true},{"path":"scripts/lib/test_pressure/heuristics.py","sizeBytes":18386,"isText":true},{"path":"scripts/lib/test_pressure/__init__.py","sizeBytes":2784,"isText":true},{"path":"scripts/lib/test_pressure/mutation.py","sizeBytes":19599,"isText":true},{"path":"scripts/lib/treemap_render.py","sizeBytes":12993,"isText":true},{"path":"scripts/lib/understanding_analysis.py","sizeBytes":11227,"isText":true},{"path":"scripts/lib/vault_queries.py","sizeBytes":9692,"isText":true},{"path":"scripts/lib/wiki_writer.py","sizeBytes":29957,"isText":true},{"path":"SKILL.md","sizeBytes":52668,"isText":true},{"path":"templates/assess-gate.yml.template","sizeBytes":2281,"isText":false},{"path":"templates/hotspot.md.template","sizeBytes":547,"isText":false},{"path":"templates/index.md.template","sizeBytes":815,"isText":false},{"path":"templates/log_entry.md.template","sizeBytes":352,"isText":false},{"path":"tests/conftest.py","sizeBytes":3745,"isText":true},{"path":"tests/fixtures/bad_instructions.md","sizeBytes":210,"isText":true},{"path":"tests/fixtures/coverage.xml","sizeBytes":855,"isText":false},{"path":"tests/fixtures/current_stats.json","sizeBytes":595,"isText":true},{"path":"tests/fixtures/.gitkeep","sizeBytes":0,"isText":false},{"path":"tests/fixtures/golden/assess-report-baseline.md","sizeBytes":22164,"isText":true},{"path":"tests/fixtures/golden/decomposition-parity-report.md","sizeBytes":893,"isText":true},{"path":"tests/fixtures/golden-doc-repo/FIXTURE.md","sizeBytes":2112,"isText":true},{"path":"tests/fixtures/golden-doc-repo/guide.md","sizeBytes":113,"isText":true},{"path":"tests/fixtures/golden-doc-repo/old.md","sizeBytes":120,"isText":true},{"path":"tests/fixtures/golden-doc-repo/README.md","sizeBytes":157,"isText":true},{"path":"tests/fixtures/golden-doc-repo/src/app.py","sizeBytes":29,"isText":true},{"path":"tests/fixtures/golden/run-context-baseline.json","sizeBytes":124826,"isText":true},{"path":"tests/fixtures/golden-svg-repo/complex_stable.py","sizeBytes":932,"isText":true},{"path":"tests/fixtures/golden-svg-repo/hot.py","sizeBytes":932,"isText":true},{"path":"tests/fixtures/golden-svg-repo/README.md","sizeBytes":2322,"isText":true},{"path":"tests/fixtures/golden-svg-repo/simple_active.py","sizeBytes":252,"isText":true},{"path":"tests/fixtures/golden-svg-repo/simple_stable.py","sizeBytes":252,"isText":true},{"path":"tests/fixtures/good_instructions.md","sizeBytes":664,"isText":true},{"path":"tests/fixtures/hollow_test_repo/src/processor.py","sizeBytes":753,"isText":true},{"path":"tests/fixtures/hollow_test_repo/tests/test_processor.py","sizeBytes":557,"isText":true},{"path":"tests/fixtures/honest_test_repo/src/processor.py","sizeBytes":753,"isText":true},{"path":"tests/fixtures/honest_test_repo/tests/test_processor.py","sizeBytes":546,"isText":true},{"path":"tests/fixtures/lcov.info","sizeBytes":113,"isText":false},{"path":"tests/fixtures/lean_with_skills/CLAUDE.md","sizeBytes":1000,"isText":true},{"path":"tests/fixtures/lean_with_skills/.claude/skills/go-conventions/SKILL.md","sizeBytes":366,"isText":true},{"path":"tests/fixtures/lean_with_skills/.claude/skills/java-conventions/SKILL.md","sizeBytes":374,"isText":true},{"path":"tests/fixtures/maven_project/dependency-analyze-output.txt","sizeBytes":794,"isText":true},{"path":"tests/fixtures/maven_project/pom.xml","sizeBytes":2126,"isText":false},{"path":"tests/fixtures/maven_project/Sample.java","sizeBytes":240,"isText":false},{"path":"tests/fixtures/monolithic_instructions.md","sizeBytes":53618,"isText":true},{"path":"tests/fixtures/mutmut-junitxml.xml","sizeBytes":1129,"isText":false},{"path":"tests/fixtures/prior_stats.json","sizeBytes":601,"isText":true},{"path":"tests/fixtures/structure_drift/ARCHITECTURE_modules.md","sizeBytes":271,"isText":true},{"path":"tests/fixtures/structure_drift/codeowners_empty","sizeBytes":242,"isText":false},{"path":"tests/fixtures/structure_drift/codeowners_mixed","sizeBytes":201,"isText":false},{"path":"tests/golden.py","sizeBytes":4608,"isText":true},{"path":"tests/__init__.py","sizeBytes":0,"isText":true},{"path":"tests/README.md","sizeBytes":5473,"isText":true},{"path":"tests/test_accretion_ratchet.py","sizeBytes":31014,"isText":true},{"path":"tests/test_accretion_ratchet_threshold.py","sizeBytes":2097,"isText":true},{"path":"tests/test_action_contract.py","sizeBytes":7257,"isText":true},{"path":"tests/test_agent_instructions_grader.py","sizeBytes":10398,"isText":true},{"path":"tests/test_agent_ops.py","sizeBytes":4560,"isText":true},{"path":"tests/test_anomaly_detector.py","sizeBytes":6223,"isText":true},{"path":"tests/test_archetype.py","sizeBytes":11335,"isText":true},{"path":"tests/test_assess_config.py","sizeBytes":6119,"isText":true},{"path":"tests/test_assess_core.py","sizeBytes":101740,"isText":true},{"path":"tests/test_assess_finalize.py","sizeBytes":38890,"isText":true},{"path":"tests/test_assess_gate.py","sizeBytes":15846,"isText":true},{"path":"tests/test_assess_report.py","sizeBytes":27072,"isText":true},{"path":"tests/test_badge.py","sizeBytes":4693,"isText":true},{"path":"tests/test_change_coupling.py","sizeBytes":20387,"isText":true},{"path":"tests/test_ci_workflow.py","sizeBytes":10187,"isText":true},{"path":"tests/test_complexity_treemap.py","sizeBytes":57638,"isText":true},{"path":"tests/test_config_drift.py","sizeBytes":21751,"isText":true},{"path":"tests/test_coupling_analysis.py","sizeBytes":9685,"isText":true},{"path":"tests/test_coverage_report.py","sizeBytes":7537,"isText":true},{"path":"tests/test_dart_capabilities.py","sizeBytes":10405,"isText":true},{"path":"tests/test_dart_complexity.py","sizeBytes":4603,"isText":true},{"path":"tests/test_decline_markers.py","sizeBytes":6606,"isText":true},{"path":"tests/test_decomposition_parity.py","sizeBytes":5123,"isText":true},{"path":"tests/test_doc_complexity_join.py","sizeBytes":11871,"isText":true},{"path":"tests/test_doc_graph.py","sizeBytes":45446,"isText":true},{"path":"tests/test_doc_graph_svg.py","sizeBytes":8694,"isText":true},{"path":"tests/test_doc_provenance.py","sizeBytes":4905,"isText":true},{"path":"tests/test_doc_staleness.py","sizeBytes":25317,"isText":true},{"path":"tests/test_emit_workflow.py","sizeBytes":13953,"isText":true},{"path":"tests/test_evidence_check.py","sizeBytes":18469,"isText":true},{"path":"tests/test_gap_actions.py","sizeBytes":3517,"isText":true},{"path":"tests/test_gate_cost.py","sizeBytes":6024,"isText":true},{"path":"tests/test_generated_files.py","sizeBytes":6951,"isText":true},{"path":"tests/test_git_churn.py","sizeBytes":2787,"isText":true},{"path":"tests/test_git_commit_info.py","sizeBytes":3942,"isText":true},{"path":"tests/test_golden_baseline.py","sizeBytes":16628,"isText":true},{"path":"tests/test_golden_svg_render.py","sizeBytes":14294,"isText":true},{"path":"tests/test_hotspot_orphan_invariant.py","sizeBytes":13870,"isText":true},{"path":"tests/test_instruction_bloat.py","sizeBytes":7039,"isText":true},{"path":"tests/test_instruction_claims.py","sizeBytes":21038,"isText":true},{"path":"tests/test_interactivity.py","sizeBytes":3541,"isText":true},{"path":"tests/test_jvm_capabilities.py","sizeBytes":17075,"isText":true},{"path":"tests/test_keyhole_signals.py","sizeBytes":55389,"isText":true},{"path":"tests/test_liveness_scan.py","sizeBytes":21995,"isText":true},{"path":"tests/test_log_supersede.py","sizeBytes":11438,"isText":true},{"path":"tests/test_no_contributions_scan.py","sizeBytes":15852,"isText":true},{"path":"tests/test_ownership_parser.py","sizeBytes":14895,"isText":true},{"path":"tests/test_promissory_markers.py","sizeBytes":14628,"isText":true},{"path":"tests/test_raw_source.py","sizeBytes":15446,"isText":true},{"path":"tests/test_review_reality.py","sizeBytes":10089,"isText":true},{"path":"tests/test_scope.py","sizeBytes":11542,"isText":true},{"path":"tests/test_scorer_evidence.py","sizeBytes":7504,"isText":true},{"path":"tests/test_self_architecture.py","sizeBytes":4216,"isText":true},{"path":"tests/test_self_dogfood.py","sizeBytes":13457,"isText":true},{"path":"tests/test_sibling_tests.py","sizeBytes":13030,"isText":true},{"path":"tests/test_smoke.py","sizeBytes":374,"isText":true},{"path":"tests/test_stats_diff.py","sizeBytes":3085,"isText":true},{"path":"tests/test_structure_drift.py","sizeBytes":36068,"isText":true},{"path":"tests/test_structure_graph.py","sizeBytes":10309,"isText":true},{"path":"tests/test_test_focus.py","sizeBytes":21913,"isText":true},{"path":"tests/test_test_pressure.py","sizeBytes":33832,"isText":true},{"path":"tests/test_understanding_analysis.py","sizeBytes":11406,"isText":true},{"path":"tests/test_uninstall.py","sizeBytes":3125,"isText":true},{"path":"tests/test_vault_queries.py","sizeBytes":4031,"isText":true},{"path":"tests/test_wiki_writer.py","sizeBytes":23234,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"notes-only","suspicious":0,"notes":3,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-20T13:52:05.885003Z","sha256":"680F3EF8AC14468AA5B57AE088B3C1BA29B444465F0F44A6A7CBFC5F22118005","sizeBytes":734526},"review":null,"source":{"repositoryUrl":"https://github.com/bjcoombs/ai-native-toolkit","path":"skills/assess","license":"Apache-2.0","commit":"ef322b23e9b271d415ab759034c9c4fc6fe9a4ce","subtreeSha":"740077E71080CDAF2189C5F96BC26352C539BD7E853D3E4A9D306675CE50DA66","lastSyncedAt":"2026-09-20T13:50:50.142586Z"},"reviewedAt":"2026-09-20T14:00:50.536107Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/bjcoombs/ai-native-toolkit/tree/main/skills/assess"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install bjcoombs-ai-native-toolkit@llmmart"},{"target":"git","command":"git clone https://github.com/bjcoombs/ai-native-toolkit.git"}]}