Scope: Technical architecture for the OmniDevX domain across omnidevx-core, omnidevx, omni-github, and devfolio. Product context in PRD.md; sequencing in PLAN.md and ROADMAP.md.
One repository = one Go module = multiple packages. Repositories mark ownership, dependency, and release boundaries; packages mark code and domain boundaries.
plexusone/omnidevx-core Canonical IR, interfaces, aggregation, thin
providers (claudecode, git, survey,
structuredchangelog, genericotel); space/ and
dora/ analytics incubate here pre-extraction
plexusone/omni-openai Existing thick provider repo; adds omnidevx/
package for the Codex CLI collector (needs a
SQLite driver — too heavy for core)
plexusone/omni-github Thick GitHub provider repo; adds omnidevx/ package
alongside existing omnistorage/, omniskill/
plexusone/omnidevx Batteries-included bundle; created at M5 when
there are thick providers to compose; space/ and
dora/ move here from core at that point
plexusone/devfolio Portfolio/profile application (consumer)
Placement rule (per 2026-07-16 decision): a collector lives in omnidevx-core/providers/ when it uses no vendor SDK and no heavy dependency (we build the structs ourselves); it lives in the vendor's existing omni-<vendor>/omnidevx/ package when it needs an official SDK or a large dependency. Applied: Claude Code parsing is stdlib-only JSONL → core; Codex parsing requires SQLite → omni-openai/omnidevx. omni-anthropic/omnidevx is the promotion target if the Claude Code collector ever needs anthropic-sdk-go (e.g., API-side usage/cost lookups).
Future slots (not initial deliverables): omni-gitlab/omnidevx, omni-atlassian/omnidevx, CI-vendor providers case-by-case.
- Provider domain packages depend inward on a shared provider client layer (e.g.,
omni-github/githubclient) and outward on their domain core (omnidevx-core) — never sideways on sibling domain packages. - Analytics packages (
omnidevx/space,omnidevx/dora) depend only onomnidevx-core— never on any provider. - DevFolio depends on
omnidevx(batteries-included) and orchestrates; it owns no collection logic. omnidevx-corestays dependency-light (stdlib + minimal utilities). A provider is promoted out of core into its own repo only when it needs a substantial third-party SDK, heavy auth/API lifecycle, or independent releases.
google/go-github → grokify/gogithub → omni-github/githubclient → omni-github/omnidevx → omnidevx-core events
gogithub keeps generic GitHub ergonomics (and gains RepositoryAdoptionSnapshot); omni-github/omnidevx does GitHub-to-DevX semantic normalization only.
omnidevx-core follows the established registration pattern:
- Root package
omnidevxat repo root; interfaces in aprovider/(or equivalent) sub-package with type aliases re-exported from the root. - Constructor injection is the primary composition mechanism (
omnidevx.New(claudecode.New(...), codex.New(...), ...)). Unlike omnillm, no OmniDevX source currently has both a thin and thick implementation, so the priority-override registry (PriorityThin/PriorityThick) is deferred until a real override need appears. If added later, followomnillm-core/registry.goexactly. - Typed
ProviderNamestring constants;ProviderConfig/ functional options. - Thin implementations under
providers/<name>/. - The batteries-included
omnidevxrepo only re-exports core aliases and composes providers; its creation is deferred until there is more than core to bundle (see PLAN). - Standard repo scaffolding:
CHANGELOG.{md,json},ROADMAP.{md,json},mkdocs.yml,docs/, go-ci/go-lint/go-sast badges.
Reference implementations: omnillm-core/registry.go, omnillm-core/provider/interface.go, omnillm/omnillm.go.
type Collector interface {
Source() SourceDescriptor
Collect(ctx context.Context, req CollectRequest) (*CollectionResult, error)
}
type CollectionResult struct {
Source SourceDescriptor `json:"source"`
Subject SubjectRef `json:"subject"`
Period Period `json:"period"`
Events []Event `json:"events"`
Diagnostics []Diagnostic `json:"diagnostics,omitempty"`
CollectedAt time.Time `json:"collectedAt"`
}type Event struct {
ID string `json:"id"`
Type EventType `json:"type"`
Timestamp time.Time `json:"timestamp"`
Subject SubjectRef `json:"subject"`
Source Source `json:"source"` // provider, product, version
Context EventContext `json:"context"` // sessionId, promptId, repository, workspace
Attributes map[string]any `json:"attributes,omitempty"`
Provenance Provenance `json:"provenance"` // collectionMode, confidence
}- Schema ID:
omnidevx.event/v1. Provenance.CollectionMode∈history | otel | hooks | api | survey;Confidence∈ [0,1]. Historical reconstruction is inherently less certain than observed events and must say so.- Event-type namespaces:
ai.*for agent-session events (ai.session.started,ai.prompt.submitted,ai.tool.completed,ai.patch.generated/applied),devx.*for work semantics (devx.change.committed,devx.change.integrated,devx.review.requested/completed,devx.verification.completed,devx.delivery.deployed,devx.outcome.completed,devx.change.delivered,devx.adoption.*,devx.contribution.*). - Source identifiers stay canonical in the IR (
{"provider": "anthropic", "product": "claude-code"}) even though Go package names are compressed (providers/claudecode).
Canonical person identity is a personId, never a GitHub username:
{
"personId": "person:01J...",
"identities": [
{ "type": "github", "value": "grokify" },
{ "type": "git_email", "valueHash": "sha256:..." },
{ "type": "local_account", "deviceId": "device:mac-studio", "value": "john" }
]
}This prepares for team aggregation without conflating accounts with people. Git emails may be stored hashed.
type DeveloperPeriodReport struct {
SchemaVersion string // omnidevx.developer-period/v1
Subject Subject
Period Period // start, end, timezone, granularity
Sources []SourceCoverage // product, sessions, coverage, collectionModes
Metrics MetricSet // combined + bySource
Quality DataQuality // coverageScore, warnings
}Rules:
- Raw events / daily summaries are the durable store; week is the default user-visible period; month is the canonical reporting cadence; quarterly/annual are synthesized from monthly. Never aggregate into months and discard daily resolution.
Metricsretains bothcombinedandbySourceviews. Some metrics combine safely (sessions, cost, tokens with model retained); others do not (acceptance rate only with matching definitions, AI LOC not reliably). The safe-to-combine table from the ideation doc governs.- Every metric value carries
Measurement{Kind: observed|estimated, Source/Method, Confidence}. - Rollup path: the same report shape scales upward —
DeveloperPeriodReportvalues aggregate into a futureTeamPeriodReport(and organization reports) over the same period grid. Team velocity is computed from individual period reports, never collected separately; this is why person-level identity resolution and daily-resolution storage are mandatory from v0.1.
Measure each stage separately; never collapse them:
generated → applied → retained → committed → merged → verified → operated → delivered
Historical logs recover stages 4–8 well; hooks/OTel are required for 1–3.
Raw canonical events are durable and reprocessable — metric formulas change; recollection should never be required.
- Location:
~/.plexusone/omnidevx/data/(single PlexusOne home; room for sibling domains later). - Layout: daily JSONL per source:
events/YYYY/MM/DD/<product>.jsonl, plusreports/<subject>/<period>.jsonfor generated period reports andidentity/for the person/identity map. Plain files first; add a SQLite index only when query patterns demand it. - Privacy rule (collection side): events capture metadata only by default — event types, durations, counts, models, token/cost figures, repo/branch identifiers, file paths. Never prompt text, response text, or file contents unless a user explicitly opts in per collector. This mirrors the publication-side disclosure model: collect broadly in metadata, never hoard content.
- Team storage: individual raw events never leave the developer's machine. Team rollups consume shared
DeveloperPeriodReportartifacts (already metadata-level), subject to team privacy rules (aggregation minimums, member-detail visibility) defined in Plan Phase 9.
Each provider supports up to three modes:
providers/<name>/
├── collector.go Collector implementation + Options
├── history.go Retrospective importer (local session files)
├── otel.go Native OTel ingestion
├── hooks.go (claudecode only) lifecycle-hook enrichment
├── normalize.go Native records → canonical events
└── types.go Provider-native types (internal where possible)
| Provider | history | otel | hooks | Notes |
|---|---|---|---|---|
| claudecode | yes | yes | yes (selective) | In core. Per-project session JSONL under ~/.claude/projects/ — typed records (user/assistant/progress/system) with sessionId, cwd, gitBranch, model, and full token usage incl. cache tiers (verified 2026-07-16). Hooks limited to: UserPromptSubmit, PostToolUse(+Failure), PermissionRequest, TaskCompleted, Stop, SessionEnd |
| codex | yes | yes | no | In omni-openai/omnidevx. Two historical formats (verified 2026-07-16): legacy rollout JSONL under ~/.codex/sessions/YYYY/MM/DD/ and current SQLite (state_5.sqlite/logs_2.sqlite: threads, agent_jobs, thread_spawn_edges) — importer must read both. Internal formats — treat as unstable; prefer OTel prospectively |
| git | yes | — | — | In core, built on grokify/gogit (gitscan renamed into a generic git base library — discovery, log+trailer parsing, branch/origin; CLI kept at cmd/gitscan). Emits devx.change.committed; canonical KnownAITools AI-attribution registry lives here (ported from devfolio) |
| survey | — | — | — | 1-question session/day micro-surveys; the only reliable Satisfaction source |
| structuredchangelog | yes | — | — | Wraps grokify/structured-changelog/changelog; emits devx.change.delivered with category → value-class mapping |
| genericotel | — | yes | — | Generic OTLP ingestion for tools without a dedicated provider |
Relationship to OmniObserve: omniobserve owns operational telemetry (traces/metrics/logs/GenAI spans); omnidevx owns developer-work semantics. OmniDevX may consume OTel data but must not make OTel its core abstraction.
Frameworks are profiles over canonical events, packaged as space and dora packages (not separate repos, not ai-space/ai-dora packages). They incubate in omnidevx-core while the IR is still churning and move to the batteries-included omnidevx repo at M5:
report, err := space.Calculate(ctx, space.Request{
Profile: space.AIAugmented, // or space.Traditional
Subject: person,
Period: week,
Events: events,
})
type SPACEReport struct {
Dimensions SPACEDimensions
AIExtension *AISPACEExtension `json:"aiExtension,omitempty"`
}
type DORAReport struct {
Metrics DORAMetrics
AIExtension *AIDORAExtension `json:"aiExtension,omitempty"`
}Constraints:
- SPACE subject: person or delivery system. DORA subject: delivery system only (repo/service/product) — reject person-scoped DORA requests.
- AI extensions add attribution, human-control, economics, and quality dimensions around unchanged base metrics. Prefer cohort comparisons (AI-assisted vs. not) over composite AI scores.
- Engines consume canonical observations only; they never call provider APIs.
- Each engine declares required input signals and computes a coverage score so missing collectors degrade gracefully (report what could not be computed, never silently zero).
- structured-changelog's 20 categories map to 9 value classes (Capability, Customer Quality, Trust & Risk, Lifecycle, Operability, Engineering Quality, Maintainability, Enablement, Communication).
grokify/releaselogis a complementary delivery-signal source: its multi-org GitHub-releases JSON IR (already generated for plexusone.dev's/releases/page) can be imported cheaply asdevx.change.delivered/release events, alongside (not instead of) the structured-changelog and omni-github collectors.- Value-density metric family (e.g., capability additions per 100 commits, released changes per AI session) as AI-stable alternatives to LOC.
- Three-stage value model: delivered change → verified value → realized outcome.
gogithub.RepositoryAdoptionSnapshot(stars, forks, watchers, issues, contributors, traffic); prefer snapshot + delta over raw counts; deduplicate contributors across repos; separate demand signals from workload signals.
contributor.ProfilegainsAISpace *AISpaceProfileSummary— a concise summary withreportRefpointers to full period reports. The profile is a presentation document; period reports are the analytical source of truth.- Existing
AICollabStatsco-author detection migrates into the git/github collectors' normalize step;contributor/client.gostops computing AI stats directly. - Disclosure model: canonical private IR (
devfolio/v1) with derived projections (devfolio.public-profile/v1) — metric scope (which repos contribute) is independent of repository disclosure (which identities appear). Five publication profiles;public-portfoliodefault. Projections are derived, never destructive edits of the canonical store. - Publishing:
{username}/devfoliorepo → GitHub Pages; profile-README sections via<!-- DEVFOLIO:START/END -->managed markers; Go templates + embedded assets;--pushalways explicit.
Visualization is a projection consumer, not a new collection or analytics layer — it reads already-computed period reports and never touches provider APIs or raw events.
Every visualized report exports as two separate JSON artifacts, not one:
- Data — the disclosure-safe projection of a
DeveloperPeriodReport/SPACEReport/DORAReport(redacted per the active publication profile from §6). Generated per period by DevFolio. - Dashboard definition — a dashforge
dashboardir.DashboardJSON (Layout,DataSources[]pointing at the data artifact by URL,Widgets[]:metricwidgets for SPACE's five dimensions + AI extension fields,metric/chartwidgets for DORA's four keys,tablefor thebySourcebreakdown). Mostly static — built once per report type, not regenerated per period.
Dashforge is chosen over a Grafana/OTel stack (evaluated 2026-07-19) because it is JSON-IR-first (matches the ecosystem's Go-first schema convention), starts static-file-only with zero infrastructure, and already has a working precedent for this exact pattern (pipelineconductor check -o data.json → viewer/?dashboard=...). This keeps visualization inside the existing "CLI + JSON artifacts + static sites" scope (PRD §Out of Scope) rather than introducing a hosted/real-time service.
ProductBuildersHQ/productbuildershq-frameworks is a taxonomy/threshold catalog (AISpaceFramework, AIDoraFramework: dimension → metric ID → MetricLevels{Elite,High,Medium,Low}), not a report schema — it carries no subject, period, or computed value. It is used only at the projection step, to look up a computed metric's level/tier and label for display (feeding dashforge's MetricConfig.Thresholds/Icon), never inside omnidevx-core's space/dora compute engines. Two reasons: omnidevx-core is committed to staying dependency-light (§1 Dependency rules), and this avoids a hard cross-org dependency (plexusone → ProductBuildersHQ) inside the compute path — if published thresholds change, only presentation shifts, not stored metric values. omnidevx's emitted metric IDs should align with frameworks.SpaceMetric.ID/DoraMetric.ID where the two vocabularies overlap, so the lookup is a direct key match rather than a translation table.
ProductBuildersHQ/visionstudio consumes the exported projection only (data + dashboard-definition JSON), never the canonical private IR or raw period reports. This mirrors the disclosure model in §6: visionstudio is a separate product/org (ProductBuildersHQ, not PlexusOne), so the org boundary gets the same treatment as any other public consumer. Integration is read-only file/URL consumption by visionstudio's Go daemon (rendered via dashforge's static viewer or natively alongside its existing maturity-model/ dashboard component) — no shared database, no live query path into DevFolio's local store.
Per global Go-first convention: Go structs are the source of truth; JSON Schemas are generated (invopop/jsonschema), linted with schemago lint, embedded via //go:embed, and committed alongside the types. Applies to omnidevx.event/v1, omnidevx.developer-period/v1, SPACE/DORA report schemas, and DevFolio projection schemas.
- No universal shell wrapper/proxy around coding agents.
- No nested Go modules within a repo without demonstrated need.
- No collector may compute framework metrics (SPACE/DORA/value) — collectors normalize only.
- No silent metric combination across sources with mismatched definitions.