Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions extensions/java-modernization-studio/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
*.log
.DS_Store
artifacts/
92 changes: 92 additions & 0 deletions extensions/java-modernization-studio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Java Modernization Studio

An interactive GitHub Copilot **canvas** that turns the [GitHub Copilot App Modernization for Java](https://learn.microsoft.com/en-us/azure/developer/java/migration/migrate-github-copilot-app-modernization-for-java) CLI workflow into a visible, steerable dashboard — assess a legacy Java app, drive a prioritized remediation plan, run validation gates, and dispatch Microsoft predefined tasks, all grounded in the repo's real artifacts.

The Copilot App Modernization for Java tooling stays the engine. This canvas is the cockpit on top of it: it reads what the workflow produces and turns each step into an agent-driving button.

## What it does

- **Overview** — at-a-glance modernization status (phase, % complete, finding counts) scanned from the repo.
- **Readiness (Environment Doctor)** — checks JDK, Maven (or `./mvnw`), Git, Docker, and Azure CLI on PATH and flags what's missing before you start.
- **Assessment** — renders structured findings from `.appmod/assessment.json` (stack summary, severity-ordered findings, strengths), each with a one-click action.
- **Plan & Progress** — renders `plan.md` / `progress.md` as live checklists (`- [ ]` / `- [x]`).
- **Validation** — runs the workflow's quality gates (CVE validation, test generation) before and after changes.
- **Tasks** — dispatches Microsoft predefined modernization tasks (managed identity for DB, secrets → Key Vault, message-broker → Service Bus, S3 → Blob, cache → Redis, Entra ID auth, and more) relevant to the detected stack.
- **Summary** — surfaces `summary.md` when the run is complete.
- **Autopilot** — an optional phase-ordered, hands-free loop that advances assessment → remediation → validation and updates the dashboard as the agent makes progress.

Buttons don't execute logic in the canvas — they dispatch a grounded prompt to your Copilot agent (action kinds: `run_task`, `generate_plan`, `run_cve`, `generate_tests`, `fix_finding`). The agent does the work; the canvas reflects the result.

## Prerequisites

- **GitHub Copilot app** (the canvas host).
- **GitHub Copilot App Modernization for Java** tooling — the underlying workflow this canvas drives.
- **JDK 17+** and **Maven** (or a `./mvnw` wrapper) for the Java project you're modernizing.
- *Optional:* **Azure CLI** (`az`) for cloud-readiness and Azure migration tasks; **Docker** for container checks.

## Install

This is an in-repo canvas extension. Copy the `java-modernization-studio/` folder into one of:

- `~/.copilot/extensions/` — **user scope** (just you), or
- `.github/extensions/` — **project scope** (shared with your repo's team).

Then reload extensions (or restart the app) so Copilot discovers it. No build step is required — the Copilot CLI resolves `@github/copilot-sdk` automatically.

## Usage

Point the canvas at a Java repository and let the agent drive it:

```text
Open the Java Modernization Studio canvas for /path/to/my-java-app and run a readiness check.
```

The canvas resolves the target repo from its `repoPath` input, falling back to the session's working directory. From there:

1. **Readiness first** — resolve any missing JDK/Maven/Azure CLI the Doctor flags.
2. **Assess** — generate `.appmod/assessment.json` + a prioritized `plan.md` / `progress.md`.
3. **Remediate** — work findings in severity order (P0 first), using task buttons and "Help me fix this".
4. **Validate** — run the CVE and test-generation gates.
5. **Ship** — when the work is genuinely complete, write `summary.md`.

### Suggested agent instructions

```text
When a user modernizes a Java project with the Java Modernization Studio canvas:
1) Open the canvas pointed at the repo (repoPath) and run the Environment Doctor first.
2) Run an assessment; write findings to .appmod/assessment.json and a prioritized plan.md / progress.md.
Start plan.md, progress.md, and summary.md with the exact first line <!-- appmod-cockpit --> so the
canvas recognizes them as modernization artifacts.
3) Work findings in severity order (P0 first); run the validation gates (CVE scan, test generation)
before and after code changes.
4) Keep plan.md / progress.md updated as - [ ] / - [x] checklists. Only write summary.md when the
work is truly complete.
```

## How it stays grounded

The canvas renders **real repo state**, never invented status:

- Structured findings come from `.appmod/assessment.json`.
- Plan/progress/summary come from root `plan.md` / `progress.md` / `summary.md`.
- To avoid mistaking an unrelated repo's `plan.md` for modernization output, root markdown is trusted **only** when `.appmod/` exists **or** the file's first line is the provenance marker `<!-- appmod-cockpit -->`.
- Stack detection (build tool, Java version, framework, container) is parsed from `pom.xml` / Gradle / Dockerfile and drives which tasks are shown.

## Agent-callable actions

| Action | Description |
|---|---|
| `get_state` | Return the current modernization snapshot scanned from the repo (assessment, plan/progress, gates, tasks). |
| `refresh` | Re-scan the repo and push a fresh snapshot to the open canvas. |

## Development

The grounding/parsing logic is pure and unit-tested independently of the canvas runtime:

```bash
node --test test/cockpit.test.mjs
```

## License

MIT
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
162 changes: 162 additions & 0 deletions extensions/java-modernization-studio/autopilot.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// autopilot.mjs — Sequencing engine for "Run on autopilot".
//
// Given the live plan, Autopilot drives the agent through the checklist one
// step at a time: pick the next eligible step (respecting phase ordering),
// hand it to the agent, wait for the turn to finish, re-scan, and repeat —
// streaming progress to the canvas after every step.
//
// All I/O is injected (`snapshot`, `runTurn`, `buildStepPrompt`, `onProgress`)
// so the loop is unit-testable without a live session or HTTP server. The pure
// helpers below are the same selection logic the renderer uses for "Continue
// here", which keeps the visible recommendation and the automated run in sync.

// How long to wait for a single step's turn to go idle. Generous: one
// modernization step can involve edits, a build, and tests. The wait does not
// abort in-flight agent work; it only bounds how long Autopilot blocks before
// treating the step as failed.
export const AUTOPILOT_TURN_TIMEOUT_MS = 30 * 60 * 1000;

export const AUTOPILOT_MAX_STEPS = 25;

/** The checklist Autopilot follows: progress.md when present, else plan.md. */
export function currentSteps(state) {
if (!state) return [];
if (state.progress && state.progress.steps && state.progress.steps.length) return state.progress.steps;
if (state.plan && state.plan.steps) return state.plan.steps;
return [];
}

/** Stable identity for a step across re-scans (phase + title). */
export function stepKey(step) {
if (!step) return "";
return (step.section || "") + "::" + (step.title || "");
}

/**
* The next step Autopilot should run: the first not-done step in the active
* phase, falling back to the first not-done step overall. Mirrors the renderer's
* "Continue here" so the automated run never jumps ahead of the safe next step.
* @returns {object|null}
*/
export function selectNextStep(state) {
const steps = currentSteps(state);
if (!steps.length) return null;
const ord = (state && state.ordering) || { activeRank: null };
const inPhase = steps.find(
(x) => x.status !== "done" && (ord.activeRank == null || x.rank === ord.activeRank)
);
return inPhase || steps.find((x) => x.status !== "done") || null;
}

/** Whether the given step is now checked off in a freshly scanned state. */
export function isStepDone(state, step) {
const k = stepKey(step);
const match = currentSteps(state).find((s) => stepKey(s) === k);
return !!(match && match.status === "done");
}

/** Construct the mutable, serializable run record broadcast to the canvas. */
export function makeRun({ scope, maxSteps, startRank } = {}) {
return {
running: true,
cancelled: false,
scope: scope === "all" ? "all" : "phase",
maxSteps: maxSteps || AUTOPILOT_MAX_STEPS,
startRank: startRank == null ? null : startRank,
status: "running",
current: null,
completed: [],
startedAt: Date.now(),
finishedAt: null,
};
}

/**
* Drive the run to completion. Mutates `run` in place and calls
* `deps.onProgress(run, state|null)` whenever something changes so the caller
* can broadcast. Resolves with the final `run`.
*
* @param {object} run from makeRun()
* @param {{
* snapshot: () => Promise<object>,
* runTurn: (prompt: string) => Promise<any>,
* buildStepPrompt: (step: object) => string,
* onProgress: (run: object, state: object|null) => void,
* log?: Function,
* }} deps
*/
export async function runAutopilot(run, deps) {
const onProgress = deps.onProgress || (() => {});
let lastKey = null;
try {
while (true) {
if (run.cancelled) {
run.status = "cancelled";
break;
}
if (run.completed.length >= run.maxSteps) {
run.status = "capped";
break;
}
const state = await deps.snapshot();
const step = selectNextStep(state);
if (!step) {
run.status = "completed";
break;
}
// Phase scope: stop once the active phase advances past where we began.
if (run.scope === "phase" && run.startRank != null && step.rank != null && step.rank > run.startRank) {
run.status = "phase_done";
break;
}
const key = stepKey(step);
// Selecting the same step twice running means the previous attempt did
// not check it off — the agent is stuck or waiting on a decision. Stop
// and hand control back rather than loop on it.
if (key === lastKey) {
run.status = "stuck";
run.stuck = step.title;
break;
}
lastKey = key;

run.current = { title: step.title, section: step.section || null };
onProgress(run, state);
if (deps.log) deps.log("Autopilot → " + step.title, { ephemeral: true });

let stepError = null;
try {
await deps.runTurn(deps.buildStepPrompt(step));
} catch (e) {
stepError = (e && e.message) || String(e);
}

const after = await deps.snapshot();
const done = isStepDone(after, step);
run.completed.push({
title: step.title,
section: step.section || null,
done,
error: stepError,
at: Date.now(),
});
run.current = null;
onProgress(run, after);

if (stepError) {
run.status = "error";
run.error = stepError;
break;
}
}
} catch (e) {
run.status = "error";
run.error = (e && e.message) || String(e);
} finally {
if (run.status === "running") run.status = "completed";
run.running = false;
run.finishedAt = Date.now();
onProgress(run, null);
}
return run;
}
29 changes: 29 additions & 0 deletions extensions/java-modernization-studio/canvas.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"id": "appmod-cockpit",
"name": "Java Modernization Studio",
"description": "Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts.",
"version": "1.0.0",
"author": {
"name": "Ayan Gupta",
"url": "https://github.com/ayangupt"
},
"keywords": [
"app-modernization",
"assessment-dashboard",
"azure-migration",
"java-modernization",
"legacy-java",
"modernization-cockpit",
"validation-gates"
],
"screenshots": {
"icon": {
"path": "assets/preview.png",
"type": "image/png"
},
"gallery": {
"path": "assets/preview.png",
"type": "image/png"
}
}
}
Loading
Loading