Improve `sentry init` runtime harness with Dirac-inspired patterns
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Quiet
- Tech stack
- bun, typescript
- Domain
- cli, developer-experience, tooling
Research direction
Start with the recommended build order, beginning in src/lib/init/local-ops.ts and src/lib/init/types.ts, and run the phase-specific tests listed under Verification. Review the named AST files and wizard-runner.ts before taking on later phases. Done means the phase tests, integration command, build, typecheck, lint, and full suite pass.
Written by the indexing model from the issue text.
Description
Summary
Adopt three patterns from the Dirac coding agent to strengthen the sentry init local runtime:
- Content hashing (from
line-hashing.ts) — FNV-1a file hashing for stale-read detection - Atomic patchsets with rollback (simplified from
CheckpointTracker) — in-memory backup before writes - Tree-sitter AST infrastructure (from
services/tree-sitter/) — WASM-based code parsing for local framework detection, entry point discovery, and post-patch syntax validation
What We're NOT Picking
- Hash-anchored line edits: Requires Mastra protocol changes (server must produce hash-referenced diffs). Out of scope.
- React Ink TUI: We use Stricli + @clack/prompts. Different paradigm.
- Full eval harness: The Dirac eval system is benchmark-focused, not applicable.
Phase 1: Content Hashing & Staleness Detection (no new deps)
Goal: Detect when files change between read-files and apply-patchset operations.
New file: src/lib/init/content-hash.ts
fnv1a(content: string): string— FNV-1a 32-bit hash → 8-char hex (5 lines, from Dirac'sline-hashing.ts)FileSnapshottype —Map<string, string>of path→hashcreateSnapshot(),recordFile(),isStale()helpers
Modify: src/lib/init/local-ops.ts
- Add module-scoped
FileSnapshotinstance, reset per wizard run - In
readFiles(): record content hash for each file after reading - In
applyEdits(): before applying edits, re-read the file and check staleness. If stale,log.warn()but continue (the fuzzy replacer may still succeed) - Return
contentHashesin theread-filesresult data so the Mastra workflow can optionally use it later
Modify: src/lib/init/types.ts
- Add optional
contentHashes?: Record<string, string>to read-files result shape
New tests
test/lib/init/content-hash.test.ts— basic hash consistencytest/lib/init/content-hash.property.test.ts— determinism, collision resistance
Phase 2: Atomic Patchsets with Rollback (no new deps)
Goal: If any patch in a patchset fails, restore all previously-applied files.
Modify: src/lib/init/local-ops.ts
Restructure applyPatchset() with in-memory backup:
type FileBackup =
| { type: 'existed'; path: string; absPath: string; content: string }
| { type: 'created'; path: string; absPath: string };
- Before each patch: snapshot the file's current content (or record it as nonexistent)
- On failure: restore all backups in reverse order (write-back for modify/delete, unlink for create)
- On success: discard backups
Why in-memory, not git stash?
git stashhas side effects on user's stash stack- The wizard already checks for clean git state in
git.ts— users cangit checkout .for ultimate recovery - Patchsets are small (typically <10 files, <100KB total)
Modify: src/lib/init/types.ts
- Add
rolledBack?: booleanandrollbackErrors?: string[]toLocalOpResult
New test: test/lib/init/local-ops-rollback.test.ts
- 3-patch patchset where patch #3 fails → verify patches #1 and #2 rolled back
- Modify + rollback restores original content
- Create + rollback deletes the file
Phase 3: Tree-sitter AST Infrastructure
Goal: Add WASM-based tree-sitter parsing, lazy-loaded and cached.
New dependency
bun add -d web-tree-sitter
NOT adding tree-sitter-wasms as a dependency. Grammar WASMs downloaded on demand (~250-810KB each, cached in ~/.sentry/grammars/). The web-tree-sitter JS runtime (~60KB) bundles via esbuild. Its WASM runtime (~2.5MB) is also downloaded on demand alongside grammars.
New file: src/lib/ast/parser.ts
Adapted from Dirac's languageParser.ts:
EXTENSION_TO_LANGUAGEmap —.js→javascript,.ts→typescript,.tsx→tsx,.py→python,.go→go,.rb→ruby,.php→php,.java→javagetParser(language)— lazy init of web-tree-sitter, loads grammar WASM on demandparseFile(content, filePath)— returnsParser.Tree | nullisAstSupported(filePath)— extension check- Two-level cache: language→Parser cache + grammar→WASM cache (same as Dirac)
New file: src/lib/ast/grammar-loader.ts
ensureGrammar(language)— checks~/.sentry/grammars/<version>/, downloads from CDN if missingensureRuntime()— same for thetree-sitter.wasmruntime- CDN source: unpkg.com or GitHub-hosted (configurable)
- Downloads use
fetch()with timeout, write viaBun.write()
New file: src/lib/ast/queries.ts
S-expression queries per language, inspired by Dirac's queries/ directory:
type FrameworkSignal = {
framework: string; // 'nextjs', 'express', 'django', etc.
confidence: number; // 0.0–1.0
evidence: string; // human-readable
file: string;
line: number;
};
type EntryPoint = {
file: string;
line: number;
kind: 'server-start' | 'app-export' | 'main-function' | 'sdk-init';
pattern: string;
};
Functions: detectFramework(), findEntryPoints(), findSentryConfig(), generateOutline()
New file: src/lib/ast/errors.ts
AstError extends CliError— for grammar load failures, parse errors
New file: src/lib/ast/index.ts
Barrel re-export.
Build impact
web-tree-sitterJS: ~60KB bundled (esbuild handles it)- WASM runtime: NOT bundled, downloaded on demand (~2.5MB, cached)
- Grammar WASMs: NOT bundled, downloaded on demand (~250-810KB each, cached)
- Net binary size increase: ~60KB (just the JS loader)
New test: test/lib/ast/parser.test.ts
- Mock grammar download, parse a JS fixture, verify tree exists
- Unsupported extension returns null
Phase 4: AST-Based Intelligence for Init
Goal: Use tree-sitter to send richer context to the Mastra workflow.
New file: src/lib/init/ast-context.ts
type ProjectAstContext = {
frameworks: FrameworkSignal[];
entryPoints: EntryPoint[];
existingSentry: SentryConfig[];
fileOutlines: Record<string, string>;
};
async function buildProjectAstContext(
cwd: string,
dirListing: DirEntry[]
): Promise<ProjectAstContext | null>
File selection heuristic (scan a targeted subset, not the whole tree):
- Known entry point filenames:
src/index.ts,src/app.ts,app.py,manage.py,main.go,pages/_app.tsx,app/layout.tsx,next.config.js, etc. - Files matching
*sentry* - Fallback: first 5 source files in
dirListing
Modify: src/lib/init/wizard-runner.ts
After precomputeDirListing(), before run.startAsync():
let astContext = null;
try {
const { buildProjectAstContext } = await import("./ast-context.js");
astContext = await buildProjectAstContext(directory, dirListing);
} catch {
// AST unavailable — continue without it
}
Pass astContext in inputData to the workflow.
New local-op: parse-files
Register as a new operation type in local-ops.ts for the Mastra workflow to request on-demand AST analysis.
Modify: src/lib/init/types.ts
- Add
ParseFilesPayloadtype - Add to
LocalOpPayloadunion
Phase 5: AST Validation of Applied Patches
Goal: Verify modified files are syntactically valid after patching.
New file: src/lib/init/ast-validation.ts
type ValidationResult = {
valid: boolean;
errors: Array<{ line: number; column: number; message: string }>;
};
async function validateSyntax(content: string, filePath: string): Promise<ValidationResult>
Implementation: parse with tree-sitter, walk CST for ERROR/MISSING nodes.
Modify: src/lib/init/local-ops.ts
In applySinglePatch() after writing a modify patch:
const result = await validateSyntax(content, patch.path);
if (!result.valid) {
log.warn(`Syntax issues in ${patch.path}: ${result.errors.length} error(s)`);
}
Advisory only (warn, don't fail). Validation errors are included in the patchset result metadata so the Mastra workflow can decide whether to retry.
Recommended Build Order
Phase 1 (content hashing) ─── 1 day, no deps, immediate value
↓
Phase 2 (atomic rollback) ─── 1.5 days, no deps, immediate value
↓
Phase 3 (tree-sitter infra) ─── 3-4 days, adds web-tree-sitter
↓
Phase 4 (AST intelligence) ─── 3-4 days, uses Phase 3
↓
Phase 5 (AST validation) ─── 2 days, uses Phase 3
Phases 1-2 are dependency-free quick wins. Phase 3 is the risky foundation. Phases 4-5 build on it.
Total: ~11-13 days
Key Files Modified
| File | Phase | Change |
|---|---|---|
src/lib/init/local-ops.ts |
1,2,4 | Content hashing, atomic rollback, parse-files handler |
src/lib/init/types.ts |
1,2,4 | New payload types, result fields |
src/lib/init/wizard-runner.ts |
4 | Pass astContext to workflow |
package.json |
3 | Add web-tree-sitter devDependency |
Key Files Created
| File | Phase |
|---|---|
src/lib/init/content-hash.ts |
1 |
src/lib/ast/parser.ts |
3 |
src/lib/ast/grammar-loader.ts |
3 |
src/lib/ast/queries.ts |
3 |
src/lib/ast/errors.ts |
3 |
src/lib/ast/index.ts |
3 |
src/lib/init/ast-context.ts |
4 |
src/lib/init/ast-validation.ts |
5 |
Risks
| Risk | Level | Mitigation |
|---|---|---|
web-tree-sitter WASM loading in Bun compiled binary |
High | Download WASM on demand (not embedded). Test early in Phase 3. |
| Grammar CDN unreliable | Medium | Host grammars on GitHub Releases or bundle JS/TS grammars as fallback (~2MB) |
| Tree-sitter adds complexity for limited init-specific value | Medium | Phases 1-2 deliver value without tree-sitter. AST phases are additive, not critical path. |
Mastra workflow ignores astContext initially |
Low | Context is advisory — server can adopt it incrementally. Local value (validation) is standalone. |
Verification
- Phase 1:
bun test test/lib/init/content-hash— property tests pass - Phase 2:
bun test test/lib/init/local-ops-rollback— rollback tests pass - Phase 3:
bun test test/lib/ast/parser— JS/TS parsing works - Phase 4:
bun test test/lib/init/ast-context— framework detection returns signals - Phase 5:
bun test test/lib/init/ast-validation— catches broken syntax - Integration:
bun run dev -- init ./test-project --dry-run— wizard completes with AST context - Build:
bun run build— binary size increase < 100KB (only JS loader bundled) - Full suite:
bun run typecheck && bun run lint && bun test
- Dominant language
- TypeScript
- Stars
- 121
- Forks
- 14
- Avg merge
- 22h 3m
- Merged PRs (30d)
- 94
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from getsentry/cli
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
Difficulty 3/5 1-2 days Newbie friendliness 68/100
-
Difficulty 3/5 1-2 days Newbie friendliness 65/100
-
bug jared
-
jared
Difficulty 4/5 3-5 days Newbie friendliness 52/100
Similar issues
-
comp/desktop P3 type/bug
Difficulty 1/5 Under an hour Newbie friendliness 92/100
NousResearch/hermes-agent#118866 ·
-
needs-triage🔍
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Browser Waiting for: Product Owner
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
getsentry/sentry-javascript#24577 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
agilepathway/label-checker#640 ·
-
Plugin stuck at "loading" on DSH 0.1.6-alpha.2 — turnTail list slot registration missing options.id Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100