feat(tools-marketplace-inventory): wire resolver into arch graph - #1074
Conversation
There was a problem hiding this comment.
Sorry @cuioss-oliver, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: cuioss/coderabbit/.coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (16)
🚧 Files skipped from review as they are similar to previous changes (13)
📝 WalkthroughWalkthroughThe change adds exclusion-aware mypy verification. It also materializes component references during discovery and adds pure Python and Markdown derivation resolvers with shared suppression-note aggregation, provenance, deterministic edges, and end-to-end graph validation. ChangesResolver derivation and verification
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Reviewer Guide 🔍(Review updated until commit 7f2bcd8)
|
Triage dispositionsIn reply to comment_id:
|
d3aea0f to
1decffb
Compare
|
@coderabbitai review The branch was rebased onto the current Reviewer note on what is most worth scrutiny in this change set:
|
|
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (6)
test/pm-plugin-development/plan-marshall-plugin/test_component_ref_materialization.py (1)
149-155: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove the fixture marketplace into a pytest fixture.
Nine tests repeat the same three-line
tempfile.TemporaryDirectory()plus_build_fixture_marketplace(root)scaffolding. A module-scopedtmp_path_factoryfixture removes the duplication and builds the tree once instead of nine times. The tests only read the tree, so sharing it is safe.♻️ Sketch
import pytest `@pytest.fixture`(scope='module') def marketplace_root(tmp_path_factory) -> Path: """Build the fixture marketplace once for the whole module.""" root = tmp_path_factory.mktemp('marketplace-fixture') _build_fixture_marketplace(root) return root def test_refs_are_produced_for_the_fixture_bundle_tree(marketplace_root): refs = _modules_by_name(marketplace_root)['alpha']['component_refs'] ...
test_missing_bundles_directory_yields_no_refsneeds its own temporary root and stays as it is.Also applies to: 163-167, 181-185
marketplace/bundles/pm-dev-python/skills/plan-marshall-plugin/extension.py (1)
212-232: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffConsider lifting the reference-join loop onto
DerivationResolverBase.This loop is identical to the markdown resolver's loop in
marketplace/bundles/pm-plugin-development/skills/plan-marshall-plugin/extension.py. Only the accepteddep_typeset differs. The three suppression predicates, the candidate-string format, and the seeded category order are the same in both.
DerivationResolverBasealready owns_aggregate_notesfor exactly this reason. A protected helper such as_join_component_refs(derived_by_name, accepted_dep_types)would single-source the suppression semantics without creating coupling between the two bundles, because both already depend on the shared base rather than on each other.This is optional. The current duplication is small and each copy is well documented. Raise it only if a third reference-join resolver is planned.
test/pm-dev-python/plan-marshall-plugin/test_python_derivation_resolver.py (1)
58-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDerive
MARKDOWN_DEP_TYPESfromDependencyType.This tuple mirrors the four non-
importmembers of theDependencyTypeenum. Three tests sweep over it to prove that no markdown kind leaks into the python resolver. If a sixth dependency type is added, the sweep keeps passing and the new type goes untested.Derive the population as the enum values minus the resolver's own
PYTHON_DEP_TYPE, and add a non-vacuity guard.♻️ Proposed change
-MARKDOWN_DEP_TYPES = ('script', 'skill', 'path', 'implements') +MARKDOWN_DEP_TYPES = tuple( + sorted({member.value for member in DependencyType} - {_extension_module.PYTHON_DEP_TYPE}) +) +assert MARKDOWN_DEP_TYPES, 'no non-import dependency types; the sweeps below would be vacuous'Based on learnings: "For every set-guarding detector, derive the population from its authoritative source at build time or runtime rather than maintaining hard-coded lists that mirror parser arguments, registry entries, build goals, enum constants, registered handlers, or dispatch tables. When a derived population is required for a sweep, add a non-vacuity guard to ensure the detector does not pass trivially over an empty set."
Sources: Path instructions, Learnings
test/pm-plugin-development/plan-marshall-plugin/test_markdown_derivation_resolver.py (1)
93-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider mirroring the unconditional-registration tests for the markdown resolver.
test/pm-dev-python/plan-marshall-plugin/test_python_derivation_resolver.pylines 374-425 assert two properties for the python resolver: the resolver is discovered even when its domain does not apply, anddiscover_derivation_resolvers()never callsapplies_to_module. This file asserts neither for the markdown resolver.The markdown
Extension.applies_to_modulereturns not-applicable for any tree without a marketplace structure, so the same independence property is load-bearing here. Add the equivalent pair so a future applicability change cannot silently drop the markdown resolver from the roster.Do you want me to draft those two tests?
test/pm-plugin-development/plan-marshall-plugin/test_graph_family_bundle_project.py (1)
143-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSingle-source the resolver count from
EXPECTED_RESOLVER_IDS.The roster size
3is repeated at line 145 and line 321, whileEXPECTED_RESOLVER_IDSat line 46 already states it. Registering a fourth resolver requires four coordinated edits, and a missed one fails in a place that does not name the cause.Line 46 itself is a deliberate pin rather than a mirrored list, because deriving it from
discover_derivation_resolvers()would maketest_three_resolvers_are_discoveredtautological. Keep it as the single source and derive the counts from it.♻️ Proposed change
-def test_resolver_count_is_three(): +def test_resolver_count_matches_the_expected_roster(): """resolver_count is the anti-vacuity numerator carried on every response.""" - assert len(_pipeline()['resolvers']) == 3 + assert len(_pipeline()['resolvers']) == len(EXPECTED_RESOLVER_IDS)assert ran_edges == [] - assert len(ran_reports) == 3 + assert len(ran_reports) == len(EXPECTED_RESOLVER_IDS) assert len(ran_reports) != len(no_resolver_reports)Separately, on the PR's third review request: the marketplace precondition is asserted independently.
test_gate_precondition_marketplace_is_plan_marshallat lines 87-97 calls_is_plan_marshall_marketplacedirectly and does not touch_pipeline(), so a non-marketplace tree fails there with its own message before any edge claim is read.test_gate_precondition_discovery_returns_modulesandtest_gate_precondition_modules_carry_component_refsadd the non-zero module count and the populated-field check as separate failures. An empty fixture cannot produce vacuous success across those three gates.As per path instructions: "Treat a hardcoded list that must mirror a set defined elsewhere — build goals, enum constants, registered handlers, dispatch tables — as a defect unless it is derived from that source at build or run time."
Also applies to: 319-322
Source: Path instructions
test/default/test_build_verify.py (1)
232-256: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider a failure-propagation test for
cmd_test_compilemirroring thecmd_compileone.
test_scoped_compile_propagates_mypy_failure_when_files_survive(Lines 247-256) confirms the guard never maps a realmypyfailure to success forcmd_compile.cmd_test_compileapplies the identical guard-then-run pattern but has no analogous failure-propagation test in this file. Adding one would close the same regression class for the test-compile path.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: cuioss/coderabbit/.coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 4330706b-1220-4858-b4fb-fdc844b69b6c
📒 Files selected for processing (16)
build.pydoc/concepts/extension-architecture.adocmarketplace/bundles/plan-marshall/skills/extension-api/standards/ext-point-derivation-resolver.mdmarketplace/bundles/plan-marshall/skills/extension-api/standards/module-discovery.mdmarketplace/bundles/plan-marshall/skills/script-shared/scripts/extension/extension_base.pymarketplace/bundles/pm-dev-python/skills/plan-marshall-plugin/SKILL.mdmarketplace/bundles/pm-dev-python/skills/plan-marshall-plugin/extension.pymarketplace/bundles/pm-plugin-development/skills/plan-marshall-plugin/SKILL.mdmarketplace/bundles/pm-plugin-development/skills/plan-marshall-plugin/extension.pymarketplace/bundles/pm-plugin-development/skills/plan-marshall-plugin/scripts/plugin_discover.pymarketplace/bundles/pm-plugin-development/skills/tools-marketplace-inventory/SKILL.mdtest/default/test_build_verify.pytest/pm-dev-python/plan-marshall-plugin/test_python_derivation_resolver.pytest/pm-plugin-development/plan-marshall-plugin/test_component_ref_materialization.pytest/pm-plugin-development/plan-marshall-plugin/test_graph_family_bundle_project.pytest/pm-plugin-development/plan-marshall-plugin/test_markdown_derivation_resolver.py
Triage dispositionsIn reply to comment_id:
|
|
/review Requesting a re-review of the current head Since the earlier Reviewer Guide was published against
|
…rived data
Extend discover_plugin_modules() so every bundle module carries a
component_refs list of {target_bundle, dep_type, resolved} entries.
The marketplace dependency-detection engine is reused by import — no
detection logic is duplicated. Its component-granular vocabulary
(bundle:skill:script) is projected onto the bundle granularity the
architecture graph keys on, and each reference is stamped resolved so
unresolved targets are surfaced rather than dropped. Entries are
deduplicated on the (target_bundle, dep_type, resolved) triple, keeping
the stored field proportional to the bundle count.
The engine reads from disk, so it runs once here at discovery time: an
Axis-C derivation resolver is a pure function of its arguments and must
read this pre-materialized field instead of the filesystem.
Materialization is unconditional — no flag, environment variable, or
feature gate guards it.
Co-Authored-By: Claude <noreply@anthropic.com>
Opt the pm-plugin-development Extension into Axis-C by multiple inheritance, the same shape build-maven ships on Axis-B. derive_edges is a pure join over the component_refs field discovery materializes: the four markdown reference kinds (script, skill, path, implements) contribute edges, while import entries belong to the sibling python resolver and are skipped without a note, since they are outside this resolver's scope rather than suppressed by it. Unresolved targets, unknown endpoints, and self-edges are suppressed and reported as aggregated notes — one per category with a count and a bounded sample. Aggregation is load-bearing: merge_resolver_edges appends one note per dropped candidate, so per-reference notes would make the per-resolver report unreadable and defeat the reporting obligation instead of satisfying it. Document the two new faces in the plan-marshall-plugin manifest's Extension API table, which now indexes every face the extension implements, and record in tools-marketplace-inventory that the detection engine has a second consumer plus the component-to-module vocabulary projection, cross-referencing the resolver roster rather than restating it. Co-Authored-By: Claude <noreply@anthropic.com>
Opt the pm-dev-python Extension into Axis-C by multiple inheritance. Python-import edges are Python-language knowledge, so they belong to the Python domain bundle rather than to a build-system bundle. derive_edges is a pure join over the same component_refs field module discovery materializes, selecting only import entries. The four markdown reference kinds belong to the sibling markdown resolver on pm-plugin-development and are skipped without a note. Unresolved targets, unknown endpoints, and self-edges are suppressed and reported as aggregated notes, one per category with a count and a bounded sample. The resolver reads the pre-materialized field only — it imports nothing from the bundle that materializes it, so the second registration creates no coupling between the two bundles. Document both new faces plus the already-shipped provides_arch_gate in the manifest, and record that Axis-C registration is discovered unconditionally: applies_to_module gates skill loading, not resolver registration. Two tests pin that independence, one of them by making applies_to_module fatal during discovery so the property is proven positively rather than inferred from a result that came out right. Co-Authored-By: Claude <noreply@anthropic.com>
…ed edges End-to-end test running the whole shipped pipeline against the plan-marshall marketplace itself: real module discovery materializing component_refs, real resolver discovery spanning both hierarchies, and the real core merge. The gate is asserted as three separately-failing preconditions. discover_plugin_modules() early-returns [] for any tree that is not the plan-marshall marketplace, so without them every edge assertion would pass vacuously over an empty map — the failure would read as a broken graph rather than a broken gate. Characterization the live tree surfaced, pinned rather than smoothed over: the python resolver never produces an edge ALONE. Every module pair its import join derives is also derived by the markdown join, because a bundle whose Python imports a plan-marshall module invariably references that bundle in markdown too. Its contribution is real but fully corroborated, which is precisely what per-edge provenance exists to make visible. The test therefore asserts that python contributes, that its own edge_count is non-zero (counted before the producer union, so it stays honest under collapse), and that every python edge on this tree is corroborated — so a future pair markdown cannot see fails the test as a finding rather than passing silently. The collapse property is proven twice: once over the live tree and once over a controlled module map driven through the same two real resolvers and the same real merge, so a tree that stopped containing a corroborated pair could not be mistaken for a broken union. Co-Authored-By: Claude <noreply@anthropic.com>
ext-point-derivation-resolver.md carried a single-implementor status clause and an "Out of scope — not implemented" block naming the markdown and python resolvers as future work. Both are now shipped, so the block is deleted rather than amended and the roster table carries one row per resolver with its axis and owner. Also rewrites the framings the second and third implementors made stale: the header status clause, the hierarchy diagram (which connected only the build side to Axis-C), the ambiguous-identity-key obligation's future tense, and the union-semantics passage that described markdown/python corroboration as hypothetical when it is now the common case on this tree. module-discovery.md gains component_refs as an explicitly optional, extension-specific field — array or absent — with its element shape, the five dep_type values, the stamped-not-dropped rule for unresolved targets, and the empty-array-not-missing-key rule. It is deliberately NOT promoted into the shared required field set: only extensions shipping an Axis-C resolver that joins over it populate it, and consumers must read absence as "no references known" rather than as an error. Co-Authored-By: Claude <noreply@anthropic.com>
The extension-architecture concept doc described Axis-C as subclassed by "whichever bundle owns the relevant domain knowledge" and argued the three-axis split by reference to a "future markdown resolver". Both framings predate the second and third resolvers. The Axis-C bullet now names the shipped roster and the hierarchy each resolver opts in from — maven on Axis-B, markdown and python on Axis-A — which is what makes the span-both-hierarchies argument concrete rather than anticipated. It states explicitly that build-pyproject registers no resolver: Python-import edges are language knowledge owned by the Python domain bundle, so naming the build bundle here would invert exactly the ownership split ADR-004 protects. The per-resolver roster is cross-referenced rather than restated, so the contract doc stays the single source for it. Co-Authored-By: Claude <noreply@anthropic.com>
Scoped `compile {bundle}` and `quality-gate {bundle}` were unsatisfiable on any
tree for a "thin" bundle whose only .py file is removed by the [tool.mypy]
exclude patterns: mypy collected nothing and exited 2 with "There are no .py[i]
files in directory". Confirmed on pm-dev-python and pm-dev-frontend-cui; the
affected set is derived from the real tree by the tests rather than hand-listed,
because a listed pair is a sample, not an enumeration.
Chose Option B (treat an empty COLLECTED file set as a legitimate no-op) over
Option A (narrow or remove the exclude). Option A's recorded objection in
pyproject.toml still holds against the current tree: 10
plan-marshall-plugin/extension.py files plus 4 build-*/scripts/extension.py
files all resolve to the bare module name "extension" under
explicit_package_bases, so type-checking them yields duplicate-module errors.
Option B weakens no type coverage — those files are excluded tree-wide, so the
whole-tree run does not check them either; the scoped run now reports that same
truth instead of a defect that does not exist.
The predicate is exclude-AWARE. _mypy_collects_any asks "does any file survive
mypy's configured excludes", reading the patterns from the single [tool.mypy]
declaration and matching them the way mypy's own crawl does. The exclude-blind
any(*.py) shape cannot detect this class — pm-dev-python plainly contains a .py
file. The pre-existing .claude/ guard in the whole-tree branch carried exactly
that blind shape and is tightened to the same predicate: it would pass for a
.claude/ whose Python lives only under the excluded .claude/worktrees/.
cmd_test_compile carries the same guard. test/pm-dev-python holds only
plan-marshall-plugin/ files, which the excludes drop wholly, so verify {bundle}
stayed unsatisfiable without it.
Exit 2 is not blanket-suppressed and no bundle is special-cased: mypy still
runs, and its non-zero exit still propagates, whenever any file survives the
excludes. An unreadable or malformed config fails open to running mypy.
_mypy_exclude_patterns hardcoded a "compile:" prefix on its two stderr
diagnostics, but is reachable from cmd_test_compile via
_skip_empty_mypy_scope('test-compile', path). An operator running
test-compile against a malformed pyproject.toml was sent to the wrong
command.
Thread a label from the calling command through _mypy_exclude_patterns /
_mypy_collects_any / _skip_empty_mypy_scope, defaulting to the
command-neutral "mypy" so a caller that cannot name a command emits an
honest prefix rather than a wrong one. cmd_compile passes "compile"
explicitly for the whole-tree .claude probe.
Pinned by test_exclude_patterns_diagnostic_names_the_calling_command,
which asserts BOTH the test-compile and compile prefixes — asserting one
in isolation would pass against a hardcoded literal.
Found by pre-submission-self-review (finding 226af8).
Co-Authored-By: Claude <noreply@anthropic.com>
NOTE_SAMPLE_LIMIT and the ~23-line _aggregate_notes body were byte-identical duplicates across the pm-dev-python and pm-plugin-development plan-marshall-plugin extensions, with no mechanism keeping them in sync. Hoist both into DerivationResolverBase, which already declares the obligation they discharge and which both bundles already import from, so no bundle-to-bundle coupling is introduced. The base version iterates the suppressed mapping's own key order instead of a fixed category tuple, so it needs no category vocabulary of its own. That is deliberate: the three categories are the vocabulary of a component_refs join, and the third shipped resolver (maven) suppresses on coordinate collision instead — a SUPPRESSION_CATEGORIES constant on the universal Axis-C anchor would have read as "the Axis-C suppression vocabulary" while being wrong for one of the three resolvers. Each resolver keeps its own one-line tuple. Both test modules now read NOTE_SAMPLE_LIMIT from extension_base rather than off the extension module; no re-export shim is left behind. The two derive_edges bodies are also near-identical but are NOT collapsed: doing so would require inventing a parameterized component_refs-join helper that exists nowhere today and putting a marketplace-specific data shape on the shared base that maven would inherit unused. Reported rather than edited. Co-Authored-By: Claude <noreply@anthropic.com>
Two statements in the Axis-C anchor's docstrings went stale the moment this branch shipped the markdown and Python resolvers: - The class docstring still called them "a future markdown or Python import resolver" while both now ship. The identical phrasing was already corrected in the four sibling contract docs, so the anchor was the last place still describing them as unbuilt. Reworded to name the three shipped joins by what each joins over. - derive_edges' Args block enumerated the derived-data fields as metadata / dependencies / paths / packages / stats, omitting component_refs — the sixth field this branch adds and the one both new resolvers actually read. It is now named, marked optional, and xrefs module-discovery.md for the element schema and the absent-vs-empty-array contract rather than restating them. Found by pre-submission-self-review on its HEAD-dependent re-fire over the simplify commit (findings bf3a67, 062dc2). Co-Authored-By: Claude <noreply@anthropic.com>
A non-str, non-list exclude (e.g. the typo `exclude = true`) slipped past
the `isinstance(raw, str)` branch in `_mypy_exclude_patterns` and reached
`for entry in raw`, raising an unhandled TypeError out through
`_mypy_collects_any` into BOTH `cmd_compile` and `cmd_test_compile`.
`pyproject.toml` is externally-sourced config and the helper's own
docstring documents a fail-open contract, so add the missing type guard:
emit the same style of `{label}: ... - assuming none` diagnostic (naming
the offending type) and treat the value as no exclusions.
Also close the symmetric-pair test gap: `cmd_compile` carried a
failure-propagation test proving a genuine mypy failure stays red, while
`cmd_test_compile` applied the identical guard-then-run pattern with no
analogous test. Adds the mirrored test plus a parametrised
malformed-exclude test spanning bool/int/dict.
Co-Authored-By: Claude <noreply@anthropic.com>
…ative sources Every set-guarding sweep in the derivation-resolver tests restated its population as literals, so a sixth `DependencyType` would leave all four sweeps green while the new kind went untested and the markdown-vs-import resolver split went unverified for it. - `DependencyType` is now imported from `_dep_detection` (the authoritative enum) at all four sites. The python resolver's out-of-scope population is the enum minus its own `PYTHON_DEP_TYPE`; the markdown resolver's owned population is read from its own `MARKDOWN_DEP_TYPES` declaration and its out-of-scope population is the enum remainder, so a new kind lands in one of the two sweeps automatically. Each file carries a non-vacuity + partition guard so an empty or invented population fails loudly instead of sweeping nothing. - Both overflow fixtures now derive BOTH ends from `NOTE_SAMPLE_LIMIT` (`NOTE_SAMPLE_LIMIT + NOTE_OVERFLOW` entries, asserting the total and the `(+N more)` remainder). The previous half-derived form stopped exercising the overflow branch entirely once the cap reached 5. - The resolver-count assertions now use `len(EXPECTED_RESOLVER_IDS)` rather than a second literal `3`; `EXPECTED_RESOLVER_IDS` stays a hand-written pin. - `test_markdown_derivation_resolver.py`'s `spec_from_file_location` loader is guarded like its python sibling, so a wrong `EXTENSION_FILE` names the real cause instead of an `AttributeError` on `NoneType.loader`. Co-Authored-By: Claude <noreply@anthropic.com>
7f2bcd8 to
188fcbe
Compare
Summary
Wires the marketplace's existing cross-file dependency-derivation engine into the
manage-architecturegraph family through the shipped Axis-C derivation-resolver seam, soarchitecture impact/architecture graphanswer from real derived edges for theplan-marshall marketplace instead of returning an empty graph.
Two resolvers are registered against the seam, each with its own stable provenance id so every
derived edge names which one produced it:
pm-plugin-development— script notation, skill references,relative-path xrefs, and
implements:entries;pm-dev-python— AST-parsed imports.The two-resolver split is required rather than an optimization: collapsing them into one would
forfeit the per-edge markdown-vs-import provenance that is the point of the change.
Changes
Derived-edge production
marketplace/bundles/pm-plugin-development/skills/plan-marshall-plugin/scripts/plugin_discover.py— materialize
component_refsinto each module's derived data, the substrate both resolversread from.
marketplace/bundles/pm-plugin-development/skills/plan-marshall-plugin/extension.py— registerthe markdown derivation resolver.
marketplace/bundles/pm-dev-python/skills/plan-marshall-plugin/extension.py— register thepython-import derivation resolver.
marketplace/bundles/plan-marshall/skills/script-shared/scripts/extension/extension_base.py—single-source the Axis-C suppression-note renderer shared by the resolver base.
Documentation and contract
marketplace/bundles/plan-marshall/skills/extension-api/standards/ext-point-derivation-resolver.md— record the shipped resolvers and correct
DerivationResolverBasecontract drift.marketplace/bundles/plan-marshall/skills/extension-api/standards/module-discovery.md,doc/concepts/extension-architecture.adoc— Axis-C now has a live implementor on each hierarchy.marketplace/bundles/pm-plugin-development/skills/plan-marshall-plugin/SKILL.md,marketplace/bundles/pm-dev-python/skills/plan-marshall-plugin/SKILL.md, andmarketplace/bundles/pm-plugin-development/skills/tools-marketplace-inventory/SKILL.md.Build truthfulness (surfaced while verifying the new modules)
build.py— make a scoped compile truthful for fully mypy-excluded modules, and attributemypy-exclude diagnostics to the calling command rather than to the excluded module.
Tests
test/pm-plugin-development/plan-marshall-plugin/test_markdown_derivation_resolver.pytest/pm-dev-python/plan-marshall-plugin/test_python_derivation_resolver.pytest/pm-plugin-development/plan-marshall-plugin/test_component_ref_materialization.pytest/pm-plugin-development/plan-marshall-plugin/test_graph_family_bundle_project.py— provesthe graph family answers from derived edges rather than returning empty.
test/default/test_build_verify.py— covers the two build-truthfulness fixes.Test Plan
python3 .plan/execute-script.py plan-marshall:build-pyproject:pyproject_build run --command-args "verify")Related Issues
None.
Intent
The problem. A working cross-file dependency-derivation engine already existed in
pm-plugin-development, but themanage-architecturegraph family could not see it:architecture impact/architecture graphreturned an empty graph for the plan-marshallmarketplace. The capability and the consumer were both present; the wire between them was not.
The chosen approach. Consume the already-shipped Axis-C derivation-resolver seam rather than
touch
manage-architectureitself, and register two resolvers against it — a markdown resolveron
pm-plugin-development(script notation, skill references, relative-path xrefs,implements:)and a python-import resolver on
pm-dev-python(AST-parsed imports). The split is load-bearing, notan optimization: one merged resolver would still produce the edges but would forfeit the per-edge
markdown-vs-import provenance the seam's contract requires.
component_refsis materialized insidediscover_plugin_modules()so the derived data both resolvers read from is produced exactly wherethe discovery gate already runs.
Explicit non-goals.
discover_plugin_modules()early-returns[]unless the tree carriesmarketplace/.claude-plugin/marketplace.jsonwithname == "plan-marshall", andcomponent_refsis materialized[Intent truncated — 1392 of 2294 characters shown; full outline in the plan workspace]
Summary by CodeRabbit
New Features
Bug Fixes
Documentation