Initial extension contribution#3
Conversation
# [1.1.0](elvince/bmad-dashboard-extension@v1.0.0...v1.1.0) (2026-02-19) ### Features * add workflow sequencing with mandatory/optional action kinds ([ee8498d](elvince/bmad-dashboard-extension@ee8498d)) * fall back to epics.md when clicking unwritten story in epic dropdown ([77f1b03](elvince/bmad-dashboard-extension@77f1b03))
# [1.2.0](elvince/bmad-dashboard-extension@v1.1.0...v1.2.0) (2026-02-20) ### Bug Fixes * file naming from story/epic name parsing with dot ([39f7c28](elvince/bmad-dashboard-extension@39f7c28)) * split story like 5.5a/b/c are now properly managed ([b85aec9](elvince/bmad-dashboard-extension@b85aec9)) ### Features * 5-5a-editor-panel-infrastructure-and-build-setup ([93458e9](elvince/bmad-dashboard-extension@93458e9)) * 5-5b-navigation-shell-breadcrumbs-dashboard-view-and-click-behavior ([2cfbbb7](elvince/bmad-dashboard-extension@2cfbbb7)) * 5-6-epics-browser-and-story-detail-views ([e28ec4c](elvince/bmad-dashboard-extension@e28ec4c)) * 5-7-stories-table-and-kanban-board-views ([b49ee37](elvince/bmad-dashboard-extension@b49ee37)) * 5-8-document-library-and-markdown-viewer ([707f9c8](elvince/bmad-dashboard-extension@707f9c8)) * add viewer menu entry ([31df0ba](elvince/bmad-dashboard-extension@31df0ba))
## [1.2.1](elvince/bmad-dashboard-extension@v1.2.0...v1.2.1) (2026-02-20) ### Bug Fixes * story criteria not properly displayed ([185efd1](elvince/bmad-dashboard-extension@185efd1))
## [1.2.2](elvince/bmad-dashboard-extension@v1.2.1...v1.2.2) (2026-02-23) ### Bug Fixes * next action optional retro is no more proposed if the epic is done ([1afc482](elvince/bmad-dashboard-extension@1afc482))
|
Important Review skippedToo many files! This PR contains 152 files, which is 2 over the limit of 150. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (152)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
|
Thanks for doing this! A few things, can you update the PR description with what it is, what it does, and what commands to build and install it locally? Also the generated bmad stuff, the Claude suggests If that doesn't work I'd make a copy as a backup then |
|
Coderabbit says:
LOL! So close. I turned Codex loose on it since they are giving 2x usage until April with Codex Desktop and it called out these things. Can you take a look and see if you agree with them or not? P1 Re-run BMAD detection before parsing artifacts P2 Honor P2 Do not append the current story id to |
…behavior + P2 create-story) - Re-run BMAD detection when outputRoot is null on refresh, so users don't need a full reload after project init - Planning artifact links now respect bmad.defaultClickBehavior setting - Remove create-story from story-aware workflows to avoid appending the current story ID to new-story commands - Add unit tests for buildCommandWithStory
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Automated Code Review -- Generated by Claude Code
Summary
This is a thorough automated review of the full 154-file, 49K-line initial extension contribution. The codebase is generally well-structured with strong patterns (discriminated union types, "never throw" parsers, clean disposal patterns, strict CSP). However, there are packaging, security, concurrency, and accessibility issues that should be addressed.
Inline comments are placed on specific files below. Here is the full priority breakdown:
| Priority | Count | Key Items |
|---|---|---|
| CRITICAL | 4 | VSIX packaging gaps, dual lock files, missing marketplace icon |
| HIGH | 5 | Path traversal, concurrent state mutation, async race condition, a11y keyboard nav, deps classification |
| MEDIUM | 9 | Message race, disposal leak, stale closure, Tailwind syntax typo, ARIA gaps, dead config, etc. |
| LOW | 18 | Nonce RNG, silent catches, duplicated code, regex inconsistencies, no protocol versioning, etc. |
Additional LOW severity findings (not annotated inline)
- L2.
state-manager.ts:230, 266-- Silentcatchblocks swallow errors in parsing logic after successful file reads. - L3.
state-manager.ts:700,file-tree-scanner.ts:78,workflow-discovery.ts:393--readFilereturns null for ALL errors (no distinction between missing file vs permission denied). - L4.
state-manager.ts:384--detectPlanningArtifactsreads full file content just to check existence. Usevscode.workspace.fs.stat(). - L5.
extension.ts:8--BmadDetectornever pushed tocontext.subscriptions; breaks the disposal pattern. - L7.
epic-parser.ts:20vsstory-parser.ts:27--USER_STORY_REGEXinconsistency (one requires trailing period, the other doesn't). - L8.
epic-parser.ts:106-- Story content boundary uses hardcodedSTORY_HEADER_PREFIX_LENGTH = 20; breaks for multi-digit numbers. - L9.
epic-parser.ts:77, 231-- Global regexes with/gmneed manuallastIndexreset (fragile).story-parser.tsuses safermatchAll(). - L10.
epic.ts:27--EpicStoryEntry.statususes inline literal union instead ofStoryStatusValuetype; will drift. - L11.
messages.ts-- No version field in message protocol; extension/webview version mismatch is undetectable. - L12.
messages.ts-- No request/response correlation ID onREQUEST_DOCUMENT_CONTENT/DOCUMENT_CONTENT. - L13.
epic-parser.ts:258--parseEpicssilently drops unparseable epic sections with no visibility to callers. - L14. Both Zustand stores --
setErrorpushes to an unbounded array that is never displayed or capped. - L15.
.gitignorecontains boilerplate entries for unused tools (Playwright, Turbo, Vercel, Prisma, Next.js, etc.). - L16.
package.json--"workspaceContains:**/_bmad/**"deep glob scans entire workspace on startup; could delay activation in large workspaces. - L17. Neither Zustand store integrates with
vscodeApi.setState/getState, causing loading flash on webview recreation. - L18.
editor-panel-provider.ts:162--scanAndSendFileTreehas no error handling; unhandled promise rejection if scan fails.
Test Suite Notes
Strengths: Parser tests are excellent (realistic fixtures, edge cases, perf regression). Security-conscious provider tests (shell metacharacter rejection, command injection). Good isolation with beforeEach/afterEach and sinon sandboxes.
Gaps to address:
extension.test.tshas only 2 smoke tests -- no command/provider registration verification.EditorPanelProvidertests don't cover OPEN_DOCUMENT, EXECUTE_WORKFLOW, or other message handlers.messages.test.tsconstant checks are tautological (expect(X).toBe('X')).StateManagernever exercises the epic parsing path with valid content.- CSS class assertions in component tests test implementation details, not behavior.
Architecture Positives
- Clean discriminated union
ParseResult<T>with type guards and factory functions - "Never throw" parser pattern throughout
- Two-tier disposable management in FileWatcher
- Strict CSP on webviews with nonce-based script loading
react-markdowninstead ofdangerouslySetInnerHTML- Clean separation: detection -> watching -> state management -> workflow discovery -> providers
| "vite": "^7.3.0", | ||
| "vitest": "^4.0.18" | ||
| }, | ||
| "dependencies": { |
There was a problem hiding this comment.
[HIGH] H5: All dependencies should be devDependencies
Everything is bundled (esbuild for extension, Vite for webview). Having react, zustand, gray-matter, etc. in dependencies is semantically misleading. While --no-dependencies on vsce package prevents them from being included in the VSIX, a future contributor omitting that flag would bloat the package.
Move all entries from dependencies to devDependencies.
| @@ -0,0 +1,172 @@ | |||
| { | |||
There was a problem hiding this comment.
[CRITICAL] C3: Missing marketplace icon
No root-level icon field exists. The VS Code Marketplace requires a root "icon" field pointing to a PNG (128x128 or 256x256). The existing resources/bmad-icon.svg is only the activity bar icon, and SVG is not accepted for the marketplace listing.
Add a PNG icon and declare "icon": "resources/bmad-icon.png".
| @@ -0,0 +1,16274 @@ | |||
| { | |||
There was a problem hiding this comment.
[CRITICAL] C2: Dual lock files
Both package-lock.json (npm) and pnpm-lock.yaml are committed. The project declares "packageManager": "pnpm@10.26.2" and uses pnpm throughout all scripts.
Delete this file and add package-lock.json to .gitignore to prevent CI/contributor confusion and non-deterministic builds.
| <tr | ||
| key={story.key} | ||
| data-testid={`story-table-row-${story.key}`} | ||
| role="button" |
There was a problem hiding this comment.
[MEDIUM] M7: <tr role="button"> overrides table semantics
Adding role="button" to a <tr> overrides the implicit row role. Screen readers will announce these as buttons rather than table rows, losing table navigation semantics. Consider using a click handler without overriding the role, or moving the interactive element to a cell.
| "sourceMap": true, | ||
| "baseUrl": ".", | ||
| "paths": { | ||
| "@shared/*": ["src/shared/*"] |
There was a problem hiding this comment.
[MEDIUM] M8: Dead @shared/* path alias
This path alias is declared but esbuild (used for the extension build) does not resolve TypeScript path aliases without an esbuild plugin. Extension code currently uses relative imports so this works by accident, but if anyone adds an @shared/ import the build will break silently.
Either remove this alias or add an esbuild alias plugin.
| [ | ||
| "@semantic-release/exec", | ||
| { | ||
| "prepareCmd": "pnpm build && pnpm vscode:package" |
There was a problem hiding this comment.
[MEDIUM] M9: Double build during release
prepareCmd runs pnpm build && pnpm vscode:package, but vscode:package invokes npx @vscode/vsce package, which triggers the vscode:prepublish lifecycle script, which also runs pnpm build. The full build executes twice per release.
Either remove pnpm build from prepareCmd or skip prepublish in the package command.
| let text = ''; | ||
| const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
| for (let i = 0; i < 32; i++) { | ||
| text += possible.charAt(Math.floor(Math.random() * possible.length)); |
There was a problem hiding this comment.
[LOW] L1: Nonce generated with Math.random()
Math.random() is not a CSPRNG. For a VS Code extension where the nonce is generated server-side this is acceptable in practice, but crypto.randomBytes(16).toString('hex') would be more correct for a CSP nonce.
| * @param title - The story title to convert (e.g., "Story File Parser") | ||
| * @returns Kebab-case string suitable for use as a key (e.g., "story-file-parser") | ||
| */ | ||
| function toKebabCase(title: string): string { |
There was a problem hiding this comment.
[LOW] L6: toKebabCase is duplicated
This identical function also exists in epic-parser.ts:32. Both are used to generate story keys for joining epic story entries with their corresponding story files. If one copy is updated without the other, key generation will diverge and join operations will silently break.
Extract to a shared utility.
Resolve critical, high, medium, and low priority findings from PR review. Critical: fix .vscodeignore packaging gaps, remove dual lock file, move bundled deps to devDependencies, fix double build in release. High: add path traversal validation in editor panel, cap unbounded error arrays in Zustand stores. Medium: fix tr role="button" accessibility violation, remove dead @shared/* path alias, fix double build in .releaserc.json. Low: use crypto.randomBytes for CSP nonce, extract duplicated toKebabCase to shared utility, align USER_STORY_REGEX across parsers, replace hardcoded header prefix length, add debug logging to silent catch blocks.
|
I fixed all items except the icon that should be set later on by BMAD team |
pbean
left a comment
There was a problem hiding this comment.
Automated Code Review (Follow-Up Verification) -- Generated by Claude Code
Re-Review After Fix Commits
Verified findings after commits 7fa6620 and 16ad2f9 which addressed the initial review. Of the original 36 findings, 15 are fully fixed, 2 are partially fixed, and 19 remain open (mostly LOW severity).
What Was Fixed (Thank You!)
| ID | Finding | Fix |
|---|---|---|
| C1/C4 | VSIX packaging gaps | .vscodeignore now has !out/** and excludes dev files |
| C2 | Dual lock files | package-lock.json deleted and git-ignored |
| H1 | Path traversal in readAndSendDocumentContent |
path.resolve + startsWith guard added |
| H5 | dependencies vs devDependencies |
All moved to devDependencies |
| M2 | (see inline -- still has an issue) | |
| M7 | <tr role="button"> |
Role removed, proper tabIndex/aria-label/onKeyDown used |
| M8 | Dead @shared/* path alias |
Removed from tsconfig.extension.json |
| M9 | Double build during release | prepareCmd now only runs pnpm vscode:package |
| L1 | Nonce with Math.random() |
Now uses crypto.randomBytes(16).toString('hex') |
| L2 | Silent catch blocks | Now log via console.debug |
| L6 | Duplicated toKebabCase |
Extracted to shared parsers/utils.ts |
| L7 | USER_STORY_REGEX inconsistency |
Both parsers now use identical regex with trailing period |
| L8 | Hardcoded story header offset | Now uses actual match.index for boundary calculation |
| L14 | Unbounded error array | Both stores cap at 50 via .slice(-50) |
Remaining Open Items (annotated inline below)
HIGH (3): H2 concurrent state mutation, H3 unguarded async handler, H4 dropdown keyboard a11y
MEDIUM (5): M1 webview ready race, M2 disposal leak (revised), M3 fragile path matching, M4 stale closure, M5 Tailwind typo
MEDIUM-PARTIAL (1): M6 file tree ARIA (roles added, but missing aria-expanded/aria-selected/keyboard nav)
LOW items still open (not annotated inline)
- L3.
readFilereturns null for all error types (state-manager, file-tree-scanner, workflow-discovery) - L4.
detectPlanningArtifactsreads full files just to check existence - L5.
BmadDetectornever pushed tocontext.subscriptions - L9.
epic-parser.tsstill uses manuallastIndexreset instead ofmatchAll() - L10.
EpicStoryEntry.statusstill uses inline literal union instead ofStoryStatusValue - L11. No message protocol versioning
- L12. No request/response correlation ID
- L13.
parseEpicssilently drops unparseable sections - L15. Boilerplate
.gitignorewith unused tool entries - L16. Deep glob activation event may slow startup in large workspaces
- L17. No webview state persistence via
vscodeApi.setState/getState - L18.
scanAndSendFileTreehas no error handling (unhandled rejection)
New Observations
- N1. The H1 path traversal security fix has no test coverage (see inline)
- N2. The shared
toKebabCaseinparsers/utils.tshas no dedicated unit tests - N3.
import * as path from 'path'(editor-panel-provider.ts:2) uses bare specifier while rest of codebase usesnode:pathprefix -- minor inconsistency
| const updatePromises = updateChanges.map(async ([filePath]) => | ||
| this.handleFileUpdate(filePath, paths.outputRoot!) | ||
| ); | ||
| await Promise.all(updatePromises); |
There was a problem hiding this comment.
[HIGH] H2: Concurrent state mutation via Promise.all -- Still Open
This was flagged in the initial review and remains unchanged. handleFileUpdate calls run in parallel here, and each one mutates this._state via object spread independently. If two updates run concurrently (e.g., sprint-status.yaml and epics.md both changed), the second to finish overwrites the first's state changes (last-write-wins).
Suggested fix: either process updates sequentially (for...of with await), or have each handleFileUpdate return a partial state object and merge them all after Promise.all resolves.
| // Subscribe to file watcher events | ||
| this.disposables.push( | ||
| this.fileWatcher.onDidChange((event) => { | ||
| void this.handleFileChanges(event); |
There was a problem hiding this comment.
[HIGH] H3: Race condition -- unguarded async handler -- Still Open
This was flagged in the initial review and remains unchanged. void this.handleFileChanges(event) fires and forgets -- if two debounced file-change events arrive in quick succession, two handleFileChanges invocations run concurrently, both reading and mutating this._state without coordination. The _initializing guard only protects during the initial parseAll().
Suggested fix: add a promise chain (e.g., this._changeQueue = this._changeQueue.then(() => this.handleFileChanges(event))) to serialize calls.
|
|
||
| {isOpen && ( | ||
| <div | ||
| role="menu" |
There was a problem hiding this comment.
[HIGH] H4: Dropdown menu lacks keyboard navigation -- Still Open
This was flagged in the initial review and remains unchanged. The menu has role="menu" and role="menuitem" but no arrow-key navigation, no focus management (focus doesn't move into the menu on open), and no roving tabindex. Only Escape dismissal and click-outside are handled.
Per WAI-ARIA menu pattern, role="menu" requires arrow-key focus movement among role="menuitem" children. Either implement the full pattern or downgrade to a simpler disclosure widget (role="list" / role="listitem") which has fewer keyboard requirements.
| onNavigateEditorPanel: (view, params) => { | ||
| if (this.stateManager) { | ||
| EditorPanelProvider.createOrShow(this.extensionUri, this.stateManager); | ||
| EditorPanelProvider.postNavigateToView(view, params); |
There was a problem hiding this comment.
[MEDIUM] M1: Race -- postNavigateToView before webview ready -- Still Open
createOrShow() on line 47 followed immediately by postNavigateToView() on line 48. If the editor panel is being created for the first time, the webview JS hasn't loaded yet and the postMessage call is silently lost.
Common fix: have the webview send a "ready" message on load, and queue outbound messages until that acknowledgment arrives.
|
|
||
| // Subscribe to state changes and forward to webview | ||
| if (this.stateManager) { | ||
| this.disposables.push( |
There was a problem hiding this comment.
[MEDIUM] M2: onStateChange subscription leak -- Still Open (revised)
The stateManager.onStateChange subscription is pushed into this.disposables here, but nothing ever iterates and disposes that array when the webview view is destroyed. The third-arg pattern on onDidReceiveMessage (line 54) and onDidChangeVisibility (line 78) only registers those specific listener disposables for tracking -- VS Code does NOT auto-dispose all items in the array on view destruction.
If resolveWebviewView is called again after the view is recreated, the old onStateChange subscription leaks.
Fix: add webviewView.onDidDispose(() => { this.disposables.forEach(d => d.dispose()); this.disposables.length = 0; }).
|
|
||
| if (fileName === 'sprint-status.yaml') { | ||
| await this.parseSprintStatus(outputRoot); | ||
| } else if (filePath.includes('planning-artifacts') && fileName === 'epics.md') { |
There was a problem hiding this comment.
[MEDIUM] M3: Fragile path matching via String.includes -- Still Open
filePath.includes('planning-artifacts') is a plain substring match. A workspace path containing planning-artifacts in an unrelated segment (e.g., /home/user/planning-artifacts-old/...) would match incorrectly.
Consider comparing against a known base path: filePath.startsWith(path.join(outputRoot, 'planning-artifacts') + path.sep).
| return () => { | ||
| clearStoryDetail(); | ||
| }; | ||
| }, [storyKey, summary?.filePath]); // eslint-disable-line react-hooks/exhaustive-deps |
There was a problem hiding this comment.
[MEDIUM] M4: Stale closure -- suppressed exhaustive-deps -- Still Open
The eslint-disable here masks a real dependency gap. The effect body reads storyDetail?.key (line 61) in the early-return guard, but storyDetail is not in the dependency array. If storyDetail updates to match storyKey after the effect has already run and skipped the fetch, the guard won't re-evaluate, leaving stale data visible.
| <li key={ac.number} className="list-decimal"> | ||
| <span className="font-medium">{ac.title}</span> | ||
| {ac.content && ( | ||
| <pre className="mt-1 text-xs whitespace-pre-wrap text-(--vscode-descriptionForeground)"> |
There was a problem hiding this comment.
[MEDIUM] M5: Likely invalid Tailwind class syntax -- Still Open
text-(--vscode-descriptionForeground) uses parentheses. Every other occurrence in this file (lines 74, 86, 101, 123, 140, 149, 159, 180, etc.) uses the bracket form: text-[var(--vscode-descriptionForeground)].
This is almost certainly a typo -- the parenthesis form is not valid Tailwind syntax and the style won't be applied.
| ); | ||
|
|
||
| return ( | ||
| <div role="treeitem"> |
There was a problem hiding this comment.
[MEDIUM] M6: File tree ARIA attributes -- Partially Fixed
Good: role="tree", role="treeitem", and role="group" are now present.
Still missing:
aria-expandedon directory treeitem elements (should reflect open/closed state)aria-selectedon the currently selected file- Keyboard navigation (Arrow Up/Down/Left/Right per WAI-ARIA tree pattern)
These are needed for screen reader and keyboard-only users to navigate the tree.
| const documentUri = vscode.Uri.joinPath(workspaceFolder.uri, relativePath); | ||
|
|
||
| // Prevent path traversal: resolved path must stay within workspace | ||
| const resolved = path.resolve(documentUri.fsPath); |
There was a problem hiding this comment.
[LOW] N1: Path traversal fix lacks test coverage -- New Finding
This security guard was added to fix H1 and the implementation is correct. However, editor-panel-provider.test.ts does not test readAndSendDocumentContent at all. A test confirming that ../../etc/passwd (or similar) returns empty content would guard against future regressions on this security boundary.
…/N3) - Prevent concurrent state mutation by processing file updates sequentially - Add error handling to command handlers and scanAndSendFileTree - Add keyboard navigation (arrow keys, Home/End) to overflow menu dropdown - Fix DashboardViewProvider disposal leak via onDidDispose cleanup - Normalize path separators before includes() checks for cross-platform reliability - Fix stale closure in story-detail-view useEffect dependencies - Add aria-selected and aria-expanded to file tree items - Cap error array at 50 entries in state-manager collectError - Use node:path import for consistency
Addressed Remaining Code Review FindingsFixes 9 of the 21 open items from the automated re-review. The remaining 12 were triaged as not applicable, intentional design decisions, or not worth the churn. Fixed
Triaged as Won't Fix
VerificationLint (0 errors), build, and all 706 tests pass. |




Initial upload for vscode extension:
BMAD Dashboard
A VS Code extension that acts as a real-time GPS for BMAD V6 projects. It monitors workflow artifacts, tracks sprint progress, and recommends next actions — all without leaving the editor.
Features
Sidebar Dashboard
Auto-activates when the workspace contains a
_bmad/directory. Appears as a custom icon in the Activity Bar.Header Toolbar
bmad helpto clipboardSprint Progress
Epic List
epics.mdin text editorActive Story Card
Next Action Recommendation
State-machine-driven suggestion with mandatory/optional action kinds:
Each action has a Play button (execute in terminal) and a Copy button (clipboard).
Other Actions — Secondary workflow buttons that change based on project state (e.g., Correct Course, Create Story).
Planning Artifact Links — Quick links to PRD and Architecture docs. Click opens markdown preview; Shift+Click opens in text editor.
About Section — Displays BMAD version, last-updated date, and installed modules (from
manifest.yaml).Editor Panel
A multi-view editor panel (
BMAD: Open Editor Panelcommand) with breadcrumb navigation:Real-Time Updates
_bmad-output/**/*.{yaml,md}with 500 ms debouncesprint-status.yamlor story files triggers a full state recompute and UI refreshConfiguration
bmad.outputRoot_bmad-outputbmad.cliPrefixclaudeclaude,aider,copilot)bmad.defaultClickBehaviormarkdown-previewmarkdown-previeworeditor-panelbmad.docLibraryPaths["planning-artifacts", "implementation-artifacts", "docs"]Prerequisites
corepack enable && corepack prepare pnpm@10.26.2)Building
The extension uses a dual build system — esbuild for the extension host, Vite for the React webview.
To package as a
.vsix:pnpm vscode:package # produces out/bmad-dashboard-*.vsixTesting
Webview tests (Vitest)
Runs in a jsdom environment using
@testing-library/react.Extension host tests (Mocha)
Runs under
@vscode/test-electronfor tests that need VS Code APIs.pnpm test:extension # run extension integration testsLinting & type checking
Release Process
Versioning and releases are fully automated via semantic-release and a GitHub Actions workflow.
How it works
maintriggers the release workflow (.github/workflows/release.yml)@semantic-release/commit-analyzerdetermines the next version from Conventional Commits:fix:→ patch bump (1.2.x)feat:→ minor bump (1.x.0)BREAKING CHANGE:/feat!:→ major bump (x.0.0)CHANGELOG.mdis updated automatically.vsixpackage.jsonandCHANGELOG.mdare committed withchore(release): <version> [skip ci].vsixattached as a downloadable assetCI pipeline
Pull requests to
mainrun the CI workflow (.github/workflows/ci.yml):Local dry run
pnpm release:dry # preview what the next release would beProject Structure
License
MIT