Legacy code often cannot build locally by nature — CICS/IMS programs
have no local translator and the real runtime may be a mainframe the
user doesn't have. Stopping transform on a failed legacy smoke compile
would block exactly those systems.
- transform Step 0a: the target toolchain remains required (its tests
cannot run without it); a failed or impossible legacy compile no
longer stops the run — the equivalence strategy switches to recorded
traces / golden-master fixtures, and that downgrade is stated in the
plan and in TRANSFORMATION_NOTES.md so reviewers know the strength of
the proof
- preflight: a red legacy toolchain now yields Ready-with-gaps for
transform/reimagine instead of Not-ready
The previous commit round-tripped the catalog through a JSON serializer,
which escaped non-ASCII characters in every other plugin's description.
Restore the file from main and change only the code-modernization entry.
Viewer (assets/topology-viewer.html):
- inline a minified d3 subset (hierarchy/pack, zoom, selection,
interpolateZoom, ease; ISC license) instead of loading from a CDN —
the page is now fully self-contained and works on air-gapped networks
- handle duplicate node ids (unique-suffix; edges bind to the first
occurrence) and store parent references directly, fixing
level-of-detail and selection corruption with messy generated data
- share one reveal rule between drawing, edge culling, and hit-testing
so edges no longer draw into collapsed containers
- pre-bucket edges by kind and keep a per-node adjacency map; the
hover/selection pass no longer scans every edge each frame
- cancel in-flight fly-to animations when a new one starts; clamp
fly-to zoom to the zoom extent; derive max zoom from the smallest
leaf so deep estates stay reachable
- render dead-end candidates (new deadEnds field) with a dashed
outline and a sidebar badge
- clicking a node during a flow walkthrough exits the walkthrough;
search results clear on selection and Escape; surrogate-safe label
truncation; clearer stats line; explicit empty-topology message
Commands:
- new /modernize-status: read-only progress report — artifact inventory
with timestamps, staleness flags, secrets-hygiene checks, next step
- map: deadEnds in the topology schema; datastore names must be logical
identifiers with credentials stripped from URLs/DSNs
- brief: read topology.json + .mmd files (not the interactive HTML);
staleness check against inputs; effort unit aligned to person-months
- transform: secret-safe characterization-test prompt; diff -y fallback
when delta is missing; credential-safe diff selection
- reimagine: target vision is everything after the first argument (was
silently truncated to one word); masking rules in spec/scaffold/
handoff prompts
- brief/transform/reimagine: human-approval gates phrased as explicit
stop-and-wait instead of 'enter plan mode'
- preflight: delta in the tool table; brief added to the verdict list
- README: preflight/status in the workflow; legacy/ deny list also
covers Write; plugin + marketplace descriptions updated
Fixes from an adversarial review of the new viewer:
- pin d3 to 7.9.0 and load it via dynamic import with an explicit
error panel when the CDN is unreachable (previously a blocked CDN
produced a silent dark page — a real concern for restricted networks)
- coerce ids/names/loc at intake: a single missing name or non-numeric
loc previously threw inside the render loop or propagated NaN through
the pack layout, blanking the canvas with no error
- normalize flows/steps/edges defensively (null entries, missing steps,
numeric ids vs string lookups)
- mirror the level-of-detail reveal rule in the hit test so clicks
can't select nodes that aren't drawn
- scope the Escape shortcut so clearing the search box doesn't reset
the viewport; set zoom clickDistance(4) so trackpad jitter doesn't
swallow selection clicks
- round canvas backing-store size (fractional devicePixelRatio caused
a reallocation every frame on 125%/150% display scaling)
- modernize-map: use braced ${CLAUDE_PLUGIN_ROOT} so substitution
actually happens, assert the injection marker exists in the template,
and correct the documented failure mode
modernize-map previously rendered the call graph and data lineage as
static Mermaid diagrams, which become unreadable once a node has ~10+
edges — exactly the shape of real legacy systems. It now builds an
interactive viewer from a shipped template (assets/topology-viewer.html):
a zoomable circle-pack of domains/modules sized by LOC, rendered to
canvas with level-of-detail reveal, dependency edges with per-kind
toggles, search with fly-to, a per-node detail sidebar, and a flow
walkthrough mode. Small domain-level .mmd exports remain for docs.
- topology.json now has a documented schema (hierarchy + edges + entry
points + observations + flows) consumed by the viewer
- map traces 2-4 business flows anchored to personas (claimant,
operator, auditor), each step in plain business language mapped to
the modules that implement it; the viewer plays them as numbered
paths
- brief gains a Business Walkthroughs section connecting each persona
flow to the phase that replaces it
- new modernize-preflight command: detects the stack, checks analysis
tooling, smoke-compiles a real source file with the legacy toolchain,
inventories missing copybooks/descriptors/binary-only artifacts, and
writes a per-command readiness verdict
- transform now verifies legacy + target toolchains before its plan
gate instead of failing at test time
- README: commands updated, optional-tooling section reframed as 'what
to give Claude'
Refreshes the marketplace listing description for the AWS Startup Advisor
plugin at the maintainer's request.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an additional marketplace entry named `vanta` pointing to the same
source as the existing `vanta-mcp-plugin` entry (VantaInc/vanta-mcp-plugin
at the pinned SHA `345d86b5`). No code change — same upstream the existing
entry already uses; this exposes the plugin under the developer's canonical
plugin name (`vanta`, per the repo's plugin.json) alongside the existing slug.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes#2159. The Stop hook emits feedback via
`hookSpecificOutput: {hookEventName: "Stop", additionalContext}`, but
`Stop`/`SubagentStop` are NOT members of CC's `hookSpecificOutput`
discriminated union (coreSchemas.ts — valid members are PreToolUse,
PostToolUse, UserPromptSubmit, SessionStart, etc.). So the emitted shape
violates CC's documented hook-output schema.
Impact is CC-version-dependent — important nuance, established empirically:
- Reporter (array0224-cloud) on CLI 2.1.150 / 2.1.152: CC rejects the
Stop feedback; the block/reason never reaches the model, so the
auto-rewake/fix loop is lost. (Detection still runs + logs.)
- On CLI 2.1.160 (current) the asyncRewake completion path is lenient:
its gate is `isSyncHookJSONOutput` (hooks.ts) which is just
`!(json.async === true)` — NOT a strict schema parse. So the invalid
hookSpecificOutput is tolerated: metrics + rewakeSummary are still
consumed and the model still receives the findings. I could NOT
reproduce the rejection on 2.1.160, and BQ confirms Stop-path
vulns_found metrics are recorded normally (~21k with-vuln fires / 3d),
i.e. NOT dropped. (An earlier draft of this message claimed metrics
were dropped — that was wrong; corrected after checking telemetry +
repro'ing the old plugin on 2.1.160.)
So this is defensive schema-correctness: the plugin should emit output
that conforms to CC's documented union regardless of how strictly a given
CC version validates it. The reporter's environment validates strictly;
relying on the current version's leniency is fragile.
Fix (CC's documented asyncRewake "clean pattern" — hooks.ts: "error text
on stderr, JSON on stdout"):
- For Stop/SubagentStop, emit_metrics writes guidance to stderr (the
asyncRewake body channel CC delivers via `stderr || stdout`) and sets
top-level `decision: "block"` + `reason` (valid SyncHookJSONOutput
fields; also the documented sync Stop-hook contract for the `-p`
fallback). It does NOT emit a Stop hookSpecificOutput.
- PostToolUse (commit-review, push-sweep) is unchanged — valid union
member, keeps the modern hookSpecificOutput protocol.
Verified locally on macOS Python 3.13:
- py_compile clean.
- 11 new tests (test_2159_stop_hook_schema.py) pin the contract: Stop
output carries no hookSpecificOutput, uses top-level decision/reason,
writes guidance to stderr; the emitted hookEventName (when present) is
a valid union member. 2 existing tests that asserted the buggy
Stop->hookSpecificOutput shape were corrected. Full suite 464/464
pass + 2 skipped.
- END-TO-END in /tmux on CLI 2.1.160:
* FIXED plugin (2.0.3): staged pickle.loads + os.system, benign edit
pulls the file into review_set; Stop LLM review found 2 critical
vulns; CC delivered a clean rewake ("Background security review
found issues" + both findings). All hooks (UPS, PostToolUse[Edit]
pattern, PostToolUse[Bash] commit-review, Stop) fired clean; zero
schema rejections / errors / http_err in the debug log.
* OLD plugin (2.0.2) on the SAME 2.1.160: also delivered Stop feedback
(confirming the no-repro-on-latest finding above) — which proves the
fix carries NO regression risk on current CC while making the output
robust for the stricter versions where it actually breaks.
Version bumped 2.0.2 -> 2.0.3 per the per-PR-bump policy (#2114: a bump is
the only way the fix reaches the existing fleet — relevant for users still
on the CC versions where this breaks).
Closes#2159.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sagemaker-ai was dropped from the marketplace in #1762 (validate-plugins
adoption) due to a manifest/YAML error. AWS has since fixed it; the plugin
validates clean at awslabs/agent-plugins@187edde (claude plugin validate passes).
Re-listed as a git-subdir source SHA-pinned to current monorepo HEAD,
matching its sibling AWS entries (deploy-on-aws, databases-on-aws).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both plugins in awslabs/agent-plugins had their subpaths edited in commit
187edde (after the morning bump cron pinned them to f16aaf2a), so they fell
behind again on merge. Manual catch-up bump to current monorepo HEAD.
- databases-on-aws: 4 files changed under plugins/databases-on-aws/ (v1.1.0)
- deploy-on-aws: 7 files changed under plugins/deploy-on-aws/ (v1.2.0)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #2112's telemetry visibility surfaced an immediate finding from
the first 3h of v2.0.1 data: **2,406 phase=2 / err=99 sessions** —
"venv stage / uncategorized" — dominating BUILD_FAILED. The original
err_kind detection patterns were all pip-flavored (pip_no_match,
dns_fail, ssl_verify, etc.) and didn't catch venv-creation failure
modes, so they all collapsed to the catch-all _uncategorized (99)
bucket.
This PR fills the gap on two axes.
## 1. Five new venv-specific err_kind categories (codes 11-15)
Each gated on `err_phase == "venv"` so the same substring doesn't
mis-fire in pip-phase failures:
- 11 `venv_ensurepip_fail` — Debian/Ubuntu without python3-venv
installed; stderr matches "ensurepip is not available" or
"ensurepip ... returned non-zero". Predicted to be the biggest
chunk based on Linux distro market share.
- 12 `venv_path_too_long` — Windows MAX_PATH (260) or POSIX
ENAMETOOLONG. Triggered when state_dir + venv layout exceeds
the path limit (deep Lib/site-packages/<pkg>/<...> paths).
- 13 `venv_no_module` — `python3 -m venv` itself missing
("No module named 'venv'"). Rare but distinctive.
- 14 `venv_already_exists` — Errno 17 / "file exists" — sentinel
race past O_EXCL or stale dir survived `--clear`.
- 15 `venv_setup_failed` — generic "virtual environment was not
created successfully" catch-all for venv setup failures that
don't match a more specific category.
All 5 occupy reserved slots in SDK_BOOTSTRAP_ERR_CODES per the
APPEND-ONLY contract from PR #2112.
## 2. `sdk_bootstrap_stderr_sig` integer hash
For "other:<tail>" err_kinds (which encode to _uncategorized = 99),
emit a bounded integer hash (0-999) of the first ~30 chars of the
stderr tail. This restores cardinality to the _uncategorized bucket
in BQ aggregation without unbounded keyspace — same stderr message
always maps to the same bucket, so a real failure mode replicating
across thousands of machines clusters cleanly. Bounded at 1000
buckets: well below any "high cardinality" alarm but wide enough to
distinguish ~30 distinct dominant patterns (birthday-paradox
collision probability ~50% at ~37 distinct inputs).
The field auto-omits (`if sig:` gate) when err_kind is categorized
— no key-budget cost on the common-case categorized failures.
## Version bump 2.0.1 → 2.0.2
PR #2114 confirmed the version-bump mechanism is the only way to
propagate code changes to the existing fleet — without a bump, CC's
plugin updater short-circuits on string-equality of installation
version vs marketplace version. Following the policy we established:
**bump patch on every functional PR**.
By 17:31:42Z on 2026-06-01 (1m22s after #2114 merged), v2.0.1 was
already appearing in BQ. v2.0.2 should follow the same propagation
curve — ~30% adoption within 3 hours, full convergence within a few
days.
## Verified locally
- py_compile clean.
- 15 new tests in test_venv_failure_deepdive.py (added to internal
test suite at sg-staging/tests/, not in this PR):
* 5 parametrized: each new err_kind maps to its expected code (11-15).
* 1 APPEND-ONLY regression: existing codes 1-10 + 99 unchanged.
* 6 stderr_sig: non-other inputs → 0; None/empty → 0; deterministic
same-input → same-output; bounded to 0-999; distinct inputs →
distinct hashes (5/5 with P(collision) ≈ 1%); leading-chars focus
(path-varying stderr with shared 30-char prefix collide as designed).
* 1 static-shape catcher: every new `err_kind = "venv_..."` branch
in main() is guarded by `err_phase == "venv"`. Catches the
regression where someone adds a venv pattern without the phase
gate and starts mis-categorizing pip-phase failures.
* 1 map-coverage: all err_kind strings assigned anywhere in
ensure_agent_sdk.main() are present in SDK_BOOTSTRAP_ERR_CODES
(catches new categories added in code but forgotten in the map).
* 1 emit-shape: the metric block uses `_encode_stderr_sig`, the
`sdk_bootstrap_stderr_sig` key is written conditionally on `if
sig:`. Catches the regression where someone removes the
helper or makes the emit unconditional (would pad every
categorized BUILD_FAILED row with a zero-valued field).
- Full suite: 452/452 pass + 2 skipped (live API tests, opt-in).
## What this unblocks in BQ
```sql
-- For the 2,406 sessions/3h that were phase=2/err=99 on v2.0.1,
-- v2.0.2+ will split them across the new categories. Query:
SELECT
CAST(JSON_VALUE(additional_metadata, "$.sdk_bootstrap_err") AS INT64) AS err,
CAST(JSON_VALUE(additional_metadata, "$.sdk_bootstrap_stderr_sig") AS INT64) AS sig,
COUNT(*) AS sessions
FROM `proj-product-data-nhme.raw_events.claude_code_internal_event`
WHERE _PARTITIONTIME >= ...
AND CAST(JSON_VALUE(additional_metadata, "$.sdk_bootstrap") AS INT64) = 3
AND CAST(JSON_VALUE(additional_metadata, "$.sdk_bootstrap_phase") AS INT64) = 2 -- venv
GROUP BY err, sig
ORDER BY sessions DESC
```
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The nvidia-skills entry was added in PR #2088 with:
"source": {
"source": "git-subdir",
"url": "https://github.com/NVIDIA/skills.git",
"path": "plugins/nvidia-skills",
"ref": "main"
}
It's missing the required `sha` field. The marketplace validator
enforces invariant I5 ("source.sha is missing or not a 40-char hex
SHA") on every git-subdir source — without it, the action fails:
##[error]invariant I5: nvidia-skills: source.sha is missing or not
a 40-char hex SHA
This has been silently failing the "Validate Plugins" CI on every
PR that touches marketplace.json since #2088 merged on 2026-05-03.
Confirmed by checking the last 5 completed validate runs on main —
all 5 ❌, including PR #2114 (security-guidance bump that you merged
earlier today). The validator failure was getting swallowed because
all the other PR-level checks (Check MCP URLs, Scan Plugins, Validate
Plugin Licenses) were passing, and humans were `gh pr merge --admin`-ing
through it.
Fix: add the sha field pinned to the current upstream HEAD of
github.com/NVIDIA/skills.git on the `main` branch.
Resolved via: git ls-remote https://github.com/NVIDIA/skills.git refs/heads/main
SHA: 62b685a20ac45285cafd1e22782abbed33172c17
This mirrors the shape of other git-subdir entries with both `ref`
and `sha` (e.g. 42crunch-api-security-testing pins ref="v1.5.5",
sha="b404d99a...", adobe-for-creativity pins ref="main", sha="8d74ee6b...").
Unblocks every in-flight PR that touches marketplace.json — including
PR #2154 (security-guidance venv-deepdive) which is currently
red-blocked on this.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 8 PRs we shipped since 2026-05-26 (#2076, #2077, #2078, #2086,
#2091, #2100, #2101, #2105) all changed plugin code without bumping
the version. CC's plugin updater uses string equality for the
freshness check (pluginOperations.ts:1835):
const isUpToDate =
installation.version === newVersion ||
installation.installPath === versionedPath ||
installation.installPath === zipPath
if (isUpToDate) return { alreadyUpToDate: true }
Users who installed v2.0.0 anywhere between 2026-05-26 and 2026-05-31
have `installation.version === "2.0.0"` in their installed_plugins.json.
The marketplace also advertises "2.0.0" (until this commit), so
isUpToDate returns true and the plugin cache directory is never
refreshed — they keep running whatever 2.0.0 code was current on the
day they installed. The marketplace git pull happens; the per-user
cache install does NOT.
Empirical evidence: in BQ today (5/31) on Windows v2.0.0 fires,
**73% emit sdk_bootstrap outcome 4 (SKIP_WIN32)** — a code path
retired in PR #2055's Windows-enable fix. Those users are running a
plugin tree that pre-dates the fix, even though their telemetry
shows pv=20000.
The fix is a one-line version bump. Once the marketplace advertises
2.0.1, every CC autoupdate cycle sees installation.version (2.0.0)
!= newVersion (2.0.1), installs the new version, and the user's next
session loads the fixed code.
This PR:
1. plugins/security-guidance/.claude-plugin/plugin.json: 2.0.0 → 2.0.1
2. .claude-plugin/marketplace.json security-guidance entry: 2.0.0 → 2.0.1
What 2.0.1 carries (versus 2.0.0 as published 5/26):
- #2076 — Graphite gt commit/push detection
- #2077 — hookSpecificOutput.additionalContext on async-rewake exit-2
- #2078 — CLAUDE_CONFIG_DIR support
- #2086 — core.quotePath=false on diff feeders (Arabic/Hebrew/CJK paths)
- #2091 — fix Bash(...|...) if-clause regression from #2076
- #2100 — drop text=True from subprocess.run, bake PYTHONUTF8=1 (Windows non-cp1252 path crash)
- #2101 — core.quotePath=false on GIT_CMD globally
- #2105 — output_format → output_config.format API migration (#2098)
Verified locally:
- plugin.json + marketplace.json both valid JSON.
- _read_plugin_version_int() returns 20001 (was 20000).
- Existing test suite passes — 408 tests, no regressions caused by
the version bump itself. (29 unrelated failures are from
test_telemetry_failure_signals.py which expects PR #2112's
not-yet-merged code.)
Going forward: bumping `patch` on every functional PR closes this
gap entirely. Without that policy, every fix only reaches NEW
installs, never the existing fleet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fills two failure-visibility gaps in plugin telemetry.
## Gap 1: HTTP errors from _call_claude invisible
Before: a 4xx/5xx response from the LLM API caused `_call_claude` to
return None and produce ZERO fingerprint in tengu_hook_plugin_metrics.
A failed call looked identical to "no review needed". The recent
deprecation-400 outage (PR #2105, output_format → output_config.format,
#2098) was invisible in aggregate dashboards until a user manually
reported errors from their debug log. Cohort-specific or partial
outages would never show up in BQ.
Fix: add `http_err_last` (most recent status) and `http_err_count`
to the existing `_USAGE` accumulator in `_base.py`. `_usage_metrics()`
snapshots them whenever count > 0 (skip-path no-pollute contract
preserved when count == 0). All `_call_claude` error sites now call
the new `_record_http_error()` helper alongside the existing
`_last_call_claude_http_error` module-state assignment.
Now any future API failure category is queryable in BQ in real time:
SELECT
DATE(server_timestamp, "America/Los_Angeles") AS d,
CAST(JSON_VALUE(additional_metadata, "$.http_err_last") AS INT64) AS code,
COUNT(*) AS n
FROM ... WHERE event_name = "tengu_hook_plugin_metrics"
AND JSON_VALUE(additional_metadata, "$.pluginId") LIKE "%security-guidance%"
AND JSON_VALUE(additional_metadata, "$.http_err_count") IS NOT NULL
GROUP BY d, code ORDER BY d, n DESC
## Gap 2: sdk_bootstrap_phase / sdk_bootstrap_err always NULL in BQ
Before: ensure_agent_sdk.py emitted these as strings (e.g. "pip",
"dns_fail"). CC's plugin-metrics pipeline silently drops
plugin-emitted string values — only bool|finite-number plugin metrics
reach BigQuery. (CC-core fields like `subscription_type` are exempt
because they're injected downstream of plugin validation.)
Confirmed empirically: ~185K BUILD_FAILED rows in BQ over the past 2
days had `sdk_bootstrap_phase` = NULL and `sdk_bootstrap_err` = NULL
despite the Python code emitting them. ~28K BUILD_FAILED sessions/day
had no diagnostic split — flying blind on whether the failures are
pip-no-match vs dns-fail vs ssl-verify vs proxy-auth etc.
Fix: encode phase + err_kind as stable integers via
SDK_BOOTSTRAP_PHASE_CODES and SDK_BOOTSTRAP_ERR_CODES. Phase: 1=pre,
2=venv, 3=pip, 4=main. Err: 10 known categories (1-10), 11-98
reserved, 99 = uncategorized catch-all (covers "exc:<X>", "other:<X>",
and unmapped strings). APPEND-ONLY for telemetry stability.
Also corrects the misleading "CC accepts string metric values"
comment in ensure_agent_sdk.py that led to the bug originally.
Verified locally on macOS Python 3.13:
- py_compile clean.
- 32 new tests in test_telemetry_failure_signals.py (added to
internal test suite at sg-staging/tests/, not in this PR):
* 4 HTTP-error tracking unit tests: _record_http_error increments
count + tracks most-recent; handles None/invalid; -1 for
network/timeout.
* 4 _usage_metrics emission tests: empty when no activity;
successful call has no http_err fields; failure-only has http_err
and no api_calls; mixed has both.
* 1 contract test: every emitted value is bool|finite-number
(catches future regression of the string-dropping bug class).
* 13 sdk_bootstrap encoding tests (parametrized over the 10 known
err_kind categories + 5 catch-all shapes): each maps to the
right integer; unknown phase = 0; unknown err = 99.
* 1 static-shape regression catcher: every `err_kind = "..."`
string in ensure_agent_sdk.main() must be in
SDK_BOOTSTRAP_ERR_CODES (otherwise new err_kinds silently
collapse to 99).
* 2 emit-shape regression catchers: the assignments in main() go
through _encode_phase / _encode_err_kind helpers (no raw
strings); no literal string values for sdk_bootstrap_phase/err.
* 1 comment-accuracy: the misleading "CC accepts string metric
values" comment is gone.
- Full suite: 437/437 pass + 2 skipped (live API tests, opt-in).
NOT verified end-to-end against BQ — would require shipping +
observing in production for 24h to confirm the http_err and
sdk_bootstrap_phase/err fields actually appear in
tengu_hook_plugin_metrics rows. The unit tests pin the contract; if
the wire shape is broken, BQ will show NULL for the new fields and we
revisit (with the same diagnostic the BUILD_FAILED bug gave us).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 08:34:35 -07:00
16 changed files with 1536 additions and 204 deletions
@@ -7,7 +7,7 @@ A structured workflow and set of specialist agents for modernizing legacy codeba
Legacy modernization fails most often not because the target technology is wrong, but because teams skip steps: they transform code before understanding it, reimagine architecture before extracting business rules, or ship without a harness that would catch behavior drift. This plugin enforces a sequence:
The discovery commands (`assess`, `map`, `extract-rules`) build artifacts under `analysis/<system>/`. The `brief` command synthesizes them into an approval gate. The build commands (`reimagine`, `transform`) write new code under `modernized/`. The `harden` command audits the legacy system and produces a reviewable remediation patch. Each step has a dedicated slash command, and specialist agents (legacy analyst, business rules extractor, architecture critic, security auditor, test engineer) are invoked from within those commands — or directly — to keep the work honest.
@@ -20,25 +20,36 @@ Commands take a `<system-dir>` argument and assume the system being modernized l
`/modernize-assess` works best with [`scc`](https://github.com/boyter/scc) (LOC + complexity + COCOMO) or [`cloc`](https://github.com/AlDanial/cloc), and falls back to `find`/`wc` if neither is installed. Portfolio mode also benefits from [`lizard`](https://github.com/terryyin/lizard) (cyclomatic complexity). The commands degrade gracefully without them, but the metrics will be coarser.
The commands degrade gracefully, but each of these makes the output meaningfully better — run `/modernize-preflight <system-dir>` to check all of them at once and get a readiness report:
- **Analysis tools**: [`scc`](https://github.com/boyter/scc) (LOC + complexity + COCOMO) or [`cloc`](https://github.com/AlDanial/cloc); [`lizard`](https://github.com/terryyin/lizard) for portfolio mode. Without them, metrics fall back to `find`/`wc` and get coarser.
- **A working build toolchain** for the legacy stack (e.g. GnuCOBOL for COBOL) — required before `/modernize-transform` can prove behavioral equivalence, and verified by preflight with a real smoke compile against your code.
- **The whole system in the tree**: deployment descriptors (JCL, CICS definitions, route configs), copybooks/includes, and DDL/schemas. Entry-point detection and data lineage in `/modernize-map` are guesswork without them.
- **Production telemetry** (optional): an observability MCP server or batch job logs enable the runtime overlay in `/modernize-assess` and timing annotations on critical paths.
## Commands
The commands are designed to be run in order, but each produces a standalone artifact so you can stop, review, and resume.
Environment readiness check, meant to run first: detects the legacy stack, checks analysis tooling, **smoke-compiles a real source file** with the legacy toolchain (the errors this surfaces — missing copybooks, wrong dialect flags — are the ones that otherwise appear mid-transform), inventories missing includes / deployment descriptors / binary-only artifacts, and probes for telemetry. Produces `analysis/<system>/PREFLIGHT.md` with a per-command Ready / Ready-with-gaps / Not-ready verdict.
### `/modernize-assess <system-dir>` — or — `/modernize-assess --portfolio <parent-dir>`
Inventory the legacy codebase: languages, line counts, complexity, build system, integrations, technical debt, security posture, documentation gaps, and a COCOMO-derived effort estimate. Produces `analysis/<system>/ASSESSMENT.md` and `analysis/<system>/ARCHITECTURE.mmd`. Spawns `legacy-analyst` (×2) and `security-auditor` in parallel for deep reads. With `--portfolio`, sweeps every subdirectory of a parent directory and writes a sequencing heat-map to `analysis/portfolio.html`.
### `/modernize-map <system-dir>`
Build a dependency and topology map of the **legacy** system: program/module call graph, data lineage (programs ↔ data stores), entry points, dead-end candidates, and one traced critical-path business flow. Writes a re-runnable extraction script and produces `analysis/<system>/topology.json` (machine-readable), `analysis/<system>/TOPOLOGY.html` (rendered Mermaid + architect observations), and standalone `call-graph.mmd`, `data-lineage.mmd`, and `critical-path.mmd`.

Build a dependency and topology map of the **legacy** system: program/module call graph, data lineage (programs ↔ data stores), entry points, dead-end candidates, and 2–4 traced business flows each anchored to a persona (the claimant, the operator, the auditor — not the maintainer). Writes a re-runnable extraction script and produces `analysis/<system>/topology.json` plus `analysis/<system>/TOPOLOGY.html` — an **interactive zoomable map** (circle-pack of domains/modules sized by LOC, dependency edges with per-kind toggles, search, click-for-details sidebar, and a walkthrough mode that plays each persona flow as a numbered path with a plain-language narrative). Built from a template shipped with the plugin, so it works on systems far too dense for a static diagram. Small domain-level `call-graph.mmd`, `data-lineage.mmd`, and `critical-path.mmd` are still exported for docs and PRs.
Mine the business rules embedded in the legacy code — calculations, validations, eligibility, state transitions, policies — into Given/When/Then "Rule Cards" with `file:line` citations and confidence ratings. Spawns three `business-rules-extractor` agents in parallel (calculations, validations, lifecycle). Produces `analysis/<system>/BUSINESS_RULES.md` and `analysis/<system>/DATA_OBJECTS.md`.
Synthesize the discovery artifacts into a phased **Modernization Brief** — the single document a steering committee approves and engineering executes: target architecture, strangler-fig phase plan with entry/exit criteria, behavior contract, validation strategy, open questions, and an approval block. Reads `ASSESSMENT.md`, `TOPOLOGY.html`, and `BUSINESS_RULES.md` and **stops if any are missing** — run the discovery commands first. Produces `analysis/<system>/MODERNIZATION_BRIEF.md` and enters plan mode as a human-in-the-loop gate.
Synthesize the discovery artifacts into a phased **Modernization Brief** — the single document a steering committee approves and engineering executes: target architecture, strangler-fig phase plan with entry/exit criteria, persona-based business walkthroughs (the section non-technical approvers actually read), behavior contract, validation strategy, open questions, and an approval block. Reads `ASSESSMENT.md`, `TOPOLOGY.html`, and `BUSINESS_RULES.md` and **stops if any are missing** — run the discovery commands first. Produces `analysis/<system>/MODERNIZATION_BRIEF.md` and enters plan mode as a human-in-the-loop gate.
Greenfield rebuild from extracted intent rather than a structural port. Mines a spec (`analysis/<system>/AI_NATIVE_SPEC.md`), designs a target architecture and has it adversarially reviewed (`analysis/<system>/REIMAGINED_ARCHITECTURE.md`), then **scaffolds services with executable acceptance tests** under `modernized/<system>-reimagined/` and writes a `CLAUDE.md` knowledge handoff for the new system. Two human-in-the-loop checkpoints. Spawns `business-rules-extractor`, `legacy-analyst` (×2), `architecture-critic`, and general-purpose scaffolding agents.
@@ -46,6 +57,9 @@ Greenfield rebuild from extracted intent rather than a structural port. Mines a
Surgical, single-module strangler-fig rewrite. Plans first (HITL gate), then writes characterization tests via `test-engineer`, then an idiomatic target implementation under `modernized/<system>/<module>/`, proves equivalence by running the tests, and produces `TRANSFORMATION_NOTES.md` mapping legacy → modern with deliberate deviations called out. Reviewed by `architecture-critic`.
### `/modernize-status <system-dir>`
Read-only progress report: artifact inventory with timestamps per workflow stage, staleness flags (e.g. a brief older than the assessment it was built from), secrets-hygiene checks (quarantine file gitignored and never committed), and the single most useful next command. Run it anytime you come back to a modernization after a break.
### `/modernize-harden <system-dir>`
Security hardening pass on the **legacy** system: OWASP/CWE scan, dependency CVEs, secrets, injection. Spawns `security-auditor`. Produces `analysis/<system>/SECURITY_FINDINGS.md` ranked Critical / High / Medium / Low and a reviewed `analysis/<system>/security_remediation.patch` with minimal fixes for the Critical/High findings. The patch is reviewed by a second `security-auditor` pass before you see it. **Never edits `legacy/`** — you review and apply the patch yourself when ready, then re-run to verify. Useful as a pre-modernization step when the legacy system will keep running in production during the migration.
@@ -81,17 +95,21 @@ This plugin ships commands and agents, but modernization projects benefit from a
"Edit(modernized/**)"
],
"deny":[
"Edit(legacy/**)"
"Edit(legacy/**)",
"Write(legacy/**)"
]
}
}
```
Adjust `legacy/` and `modernized/` to match your actual layout. The key invariants: `Edit` under `legacy/`is denied, and writes are scoped to `analysis/` (for documents) and `modernized/` (for the new code). Every command in this plugin respects this — `/modernize-harden` writes a patch to `analysis/` rather than editing `legacy/` in place.
Adjust `legacy/` and `modernized/` to match your actual layout. The key invariants: `Edit`/`Write` under `legacy/`are denied, and writes are scoped to `analysis/` (for documents) and `modernized/` (for the new code). Note this guards the file tools — shell commands that mutate files (`sed -i`, `git apply`) still go through the normal Bash permission prompt, so review those prompts with the same invariant in mind. Every command in this plugin respects this — `/modernize-harden` writes a patch to `analysis/` rather than editing `legacy/` in place.
## Typical Workflow
```bash
# 0. Check the environment is ready (tools, toolchain, source completeness)
/modernize-preflight billing
# 1. Inventory the legacy system (or sweep a portfolio of them)
/modernize-assess billing
@@ -112,6 +130,9 @@ Adjust `legacy/` and `modernized/` to match your actual layout. The key invarian
# 6. Security-harden the legacy system that's still in production
"description":"Security review for Claude-generated code. Pattern-based warnings on edits, LLM-powered diff review on Stop, and an agentic commit reviewer that catches injection, XSS, SSRF, hardcoded secrets, and 25+ other vulnerability classes.",
distinct from rewakeSummary which is the task-notification one-liner.
@@ -237,10 +256,9 @@ def emit_metrics(
surface; systemMessage adds a per-fire override when the static
rewakeMessage isn't specific enough for the finding being shown.
`hook_event_name` (used only when additional_context is set): which event
the hookSpecificOutput attaches to. Defaults to "PostToolUse"since the
commit-review and push-sweep handlers are the most common callers;
handle_stop_hook explicitly passes "Stop".
`hook_event_name` (used only when additional_context is set): selects the
delivery channel above. Defaults to "PostToolUse"(commit-review and
push-sweep are the most common callers); handle_stop_hook passes "Stop".
"""
head={}
if_PVand"pv"notinmetrics:
@@ -252,14 +270,23 @@ def emit_metrics(
ifrewake_summary:
out["rewakeSummary"]=rewake_summary
ifadditional_context:
# Wrap in hookSpecificOutput per CC's modern hook-output contract.
# Drops the legacy `sys.stderr.write(...) + sys.exit(2)` shape that
# left CC's UI showing "denied with no reason" (#1783) and triggered
# "json output validation failed" on older CC versions (#1375).
out["hookSpecificOutput"]={
"hookEventName":hook_event_name,
"additionalContext":additional_context,
}
ifhook_event_namein("Stop","SubagentStop"):
# Stop is NOT in CC's hookSpecificOutput union — emitting it there
# fails schema validation and drops metrics+rewakeSummary (#2159).
# Clean pattern: guidance on stderr(the asyncRewake body channel,
# delivered via `stderr || stdout`), top-level decision/reason for
# the sync-fallback path. stdout JSON stays valid so metrics +
# rewakeSummary survive.
sys.stderr.write(additional_context)
sys.stderr.flush()
out["decision"]="block"
out["reason"]=additional_context
else:
# PostToolUse et al. — valid union member; modern protocol.
out["hookSpecificOutput"]={
"hookEventName":hook_event_name,
"additionalContext":additional_context,
}
ifsystem_message:
out["systemMessage"]=system_message
print(json.dumps(out),flush=True)
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.