LearnNewsExamplesServices
Frontmatter
id10993
titleMigrate github-workflow services to flat SDK boundary
stateClosed
labels
enhancementairefactoringarchitecture
assigneesneo-opus-4-7
createdAtMay 8, 2026, 11:02 PM
updatedAtMay 15, 2026, 2:47 PM
githubUrlhttps://github.com/neomjs/neo/issues/10993
authorneo-opus-4-7
commentsCount0
parentIssue10986
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 9, 2026, 12:35 AM

Migrate github-workflow services to flat SDK boundary

Closedenhancementairefactoringarchitecture
neo-opus-4-7
neo-opus-4-7 commented on May 8, 2026, 11:02 PM

Context

Filed 2026-05-08 as M6 sub-issue 2 (github-workflow) — second per-server SDK migration in the M6 epic #10986 sequence (KB → GH-WF → NL → MC). Sequenced after KB (#10991) per parent epic Out-of-Scope rationale: each migration touches ai/services.mjs import paths, so concurrent PRs would merge-conflict at the SDK aggregator. Linear sequencing keeps the file under single-edit lock.

Per parent M6 epic #10986 §The Fix and learn/agentos/v13-path.md §3 D4: services move from ai/mcp/server/github-workflow/services/*.mjsai/services/github-workflow/*.mjs. ai/services.mjs becomes a re-export aggregator with stable <Server>_<ServiceName> alias contracts (consumer-side unchanged). M2 BaseServer substrate (#10965) provides the extension contract; per-server Server.mjs getDependentServices() override updated to import from new SDK paths.

The Problem

GH-WF services currently live under ai/mcp/server/github-workflow/services/ — 10 service files + 2 helper subdirectories (queries/, sync/) + toolService.mjs (MCP tool router). 7 of the 10 services are exposed via ai/services.mjs's aliasing layer (GH_HealthService, GH_IssueService, GH_LabelService, GH_LocalFileService, GH_PullRequestService, GH_RepositoryService, GH_SyncService). 3 services (AgentStateService, DiscussionService, GraphqlService) are internal — not currently SDK-exported.

The friction (per parent epic #10986 framing): the on-disk shape conflates "service implementation" with "MCP transport endpoint" — services are owned by the server-specific dir rather than by the SDK boundary. This sub-issue normalizes GH-WF to the existing ai/services/ConceptService.mjs flat-shape precedent (and matches the KB migration shape from #10991).

The Architectural Reality

Current state (verified 2026-05-08 via ls ai/mcp/server/github-workflow/services/ + grep on ai/services.mjs):

File / dir SDK alias (ai/services.mjs) SDK-exported
AgentStateService.mjs (not exported — internal)
DiscussionService.mjs (not exported — internal)
GraphqlService.mjs (not exported — internal helper)
HealthService.mjs GH_HealthService
IssueService.mjs GH_IssueService
LabelService.mjs GH_LabelService
LocalFileService.mjs GH_LocalFileService
PullRequestService.mjs GH_PullRequestService
RepositoryService.mjs GH_RepositoryService
SyncService.mjs GH_SyncService
toolService.mjs (not exported — MCP tool router)
queries/ (subdir) n/a helpers
sync/ (subdir) n/a helpers

M2 BaseServer substrate (#10965): GH-WF Server.mjs reduced 226 → 110 LOC during M2 migration (#10974); getDependentServices() override resolves [SyncService] (startup sync against GitHub). Migration updates this override's import paths.

makeSafe() Zod-wrapping (ai/services.mjs:201-207): wraps all 7 SDK-exported services. Migration preserves wrapping; only import paths change above the wrap.

Subdirectories: queries/ and sync/ contain helper modules consumed by their parent services (most likely GraphqlService and SyncService respectively). They migrate alongside their parent services to preserve internal-import resolution.

Existing flat-shape precedent: ai/services/ConceptService.mjs (Concept Ontology) demonstrates the target shape; KB sub-issue #10991 is the just-filed sibling that establishes the per-server pattern.

The Fix

Per-file moves (10 service files + 2 helper subdirs + 1 tool router = 13 entities):

Source path Destination path
ai/mcp/server/github-workflow/services/AgentStateService.mjs ai/services/github-workflow/AgentStateService.mjs
ai/mcp/server/github-workflow/services/DiscussionService.mjs ai/services/github-workflow/DiscussionService.mjs
ai/mcp/server/github-workflow/services/GraphqlService.mjs ai/services/github-workflow/GraphqlService.mjs
ai/mcp/server/github-workflow/services/HealthService.mjs ai/services/github-workflow/HealthService.mjs
ai/mcp/server/github-workflow/services/IssueService.mjs ai/services/github-workflow/IssueService.mjs
ai/mcp/server/github-workflow/services/LabelService.mjs ai/services/github-workflow/LabelService.mjs
ai/mcp/server/github-workflow/services/LocalFileService.mjs ai/services/github-workflow/LocalFileService.mjs
ai/mcp/server/github-workflow/services/PullRequestService.mjs ai/services/github-workflow/PullRequestService.mjs
ai/mcp/server/github-workflow/services/RepositoryService.mjs ai/services/github-workflow/RepositoryService.mjs
ai/mcp/server/github-workflow/services/SyncService.mjs ai/services/github-workflow/SyncService.mjs
ai/mcp/server/github-workflow/services/queries/ (whole subdir) ai/services/github-workflow/queries/
ai/mcp/server/github-workflow/services/sync/ (whole subdir) ai/services/github-workflow/sync/
ai/mcp/server/github-workflow/services/toolService.mjs ai/services/github-workflow/toolService.mjs

Then:

  1. Update ai/services.mjs import paths (lines 17-23) — alias names (GH_*) preserved; only import paths change.
  2. Update ai/mcp/server/github-workflow/Server.mjs getDependentServices() override (returns [SyncService] per #10974) — import path updated.
  3. Update GH-WF config.mjs / openapi.yaml if they reference service paths.
  4. Update internal cross-references: services that import from queries/ or sync/ subdirs (most likely GraphqlService and SyncService) — relative imports continue working because the entire subdir tree moves together.
  5. Update toolService.mjs references if it imports from the same dir tree (relative imports preserved).
  6. Remove the now-empty ai/mcp/server/github-workflow/services/ directory.
  7. Verify operator scripts work unchanged via the preserved GH_* aliases.

Class names UNCHANGED. Service file names UNCHANGED. Only physical locations change.

Neo class-system compatibility note: Neo enforces filepath-to-className matching at setupClass time. GH-WF services likely follow factory-singleton pattern (similar to KB analysis in #10991) — verify during implementation. If any GH-WF service uses Neo.setupClass() with a className matching the old path, the className must be updated alongside the path move.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
ai/services/github-workflow/<ServiceName>.mjs paths This sub-issue + parent epic #10986 Flat SDK location for GH-WF services + helper subdirs None — physical move Brief note in learn/agentos/ post-M6-epic ls ai/services/github-workflow/ confirms move; old ai/mcp/server/github-workflow/services/ removed
ai/services.mjs GH_* re-export aliases This sub-issue Continue exposing all 7 currently-aliased services; only import paths change Aliases preserved → consumer code unchanged JSDoc on ai/services.mjs Operator scripts + tests run unchanged post-migration
GH-WF Server.getDependentServices() override M2 BaseServer extension contract (#10965) Returns [SyncService] from new SDK path Migration touches imports only; method signature unchanged GH-WF Server.mjs JSDoc Per-server unit specs pass
GH-WF unit + integration coverage Existing test substrate Continue working post-migration with no AC delta Test fixture imports updated alongside service moves n/a All 39 existing GH-WF unit specs pass (per #10974 baseline) + integration row green
queries/ + sync/ subdir helpers This sub-issue Migrate as-is (whole subdir tree); preserve internal-import resolution If subdir-internal imports break, halt + investigate n/a Internal services (GraphqlService, SyncService) work unchanged

Acceptance Criteria

  • AC1: All 10 GH-WF service files + queries/ subdir + sync/ subdir + toolService.mjs moved from ai/mcp/server/github-workflow/services/ to ai/services/github-workflow/. Old directory removed.
  • AC2: ai/services.mjs continues exporting all 7 currently-aliased GH-WF services (GH_HealthService, GH_IssueService, GH_LabelService, GH_LocalFileService, GH_PullRequestService, GH_RepositoryService, GH_SyncService); only import paths change.
  • AC3: ai/mcp/server/github-workflow/Server.mjs getDependentServices() override updated to import SyncService from new SDK path.
  • AC4: Operator scripts + daemons + tests consume GH-WF services unchanged (no code changes required at consumer sites; aliases hold).
  • AC5: Internal cross-references between GH-WF services and queries/ / sync/ subdirs work post-migration (relative imports preserved by moving subdirs together).
  • AC6: All 39 GH-WF unit specs pass post-migration (per #10974 PR3 evidence baseline).
  • AC7: Integration row green post-migration on the PR.
  • AC8: Healthcheck JSON shape unchanged (snapshot diff captured in PR evidence).
  • AC9: Neo class-system compatibility: if any GH-WF service uses Neo.setupClass() with a className matching the old path, className updated to the new path (likely N/A — GH-WF services appear factory-singleton-shape — verify during implementation).
  • AC10: PR description includes a brief diff summary table (file count moved, alias-preserved count, consumer-side breakage delta — should be zero).

Out of Scope

  • makeSafe() Zod schema reshape — wrapping pattern preserved as-is.
  • Renaming GH-WF services or class names — physical move only.
  • Touching ai/mcp/server/shared/services/* — shared cross-cutting services unaffected.
  • Other Tier-1 servers — knowledge-base (#10991 — already filed), neural-link, memory-core migrations are separate sub-issues filed per parent epic #10986 sequencing.
  • <Server>_<ServiceName> namespace prefix collapse — preserved through M6 per parent epic Out-of-Scope.
  • Promoting AgentStateService / DiscussionService / GraphqlService to SDK-exported — they're internal for a reason (state machines + GraphQL helpers). External exposure should be a deliberate decision, not a side-effect of physical move.
  • Reshaping queries/ / sync/ subdir architecture — physical move only. Whether helpers belong in their own dirs or inline with services is a future-ticket question.
  • ai/services.mjs re-organization — this sub-issue updates GH-WF import paths only.

Avoided Traps / Gold Standards Rejected

  • Rejected: only move SDK-exported services, leave internal services + subdirs in old location. Splits GH-WF services across two top-level dirs categorized by export-status; creates ongoing cognitive cost. Move all 10 services + subdirs + toolService.mjs together to keep the directory unified.
  • Rejected: flatten queries/ / sync/ subdirs into the parent during migration. Architectural reshape, not migration. Subdirs migrate as-is; reshape is separate ticket if desired.
  • Rejected: rename GH_* aliases to drop prefix during migration. Breaks every operator script + daemon + test consumer. Reshape is future-ticket scope.
  • Rejected: refactor makeSafe Zod wrapping pattern alongside the move. Increases PR risk; breaks "do one thing well per PR".
  • Rejected: collapse GraphqlService into individual services. Architectural reshape, not migration.
  • Rejected: parallel cadence with sibling M6 sub-issues (KB / NL / MC). Per parent epic #10986 Avoided Traps: each migration touches ai/services.mjs import paths — concurrent PRs would merge-conflict. Linear sequencing keeps the SDK aggregator under single-edit lock.
  • Rejected: gradual migration via indirection layer. A re-export shim under the old paths adds complexity; clean cut-over is simpler for a 13-entity scope.

Related

  • Parent epic: #10986 — Migrate Tier-1 MCP services to flat SDK boundary (M6)
  • Sibling sub-issue (predecessor): #10991 — Migrate knowledge-base services to flat SDK boundary (M6 sub-1; this PR's pattern precedent)
  • Sibling sub-issues (successors, to be filed): neural-link, memory-core
  • Predecessor milestone (cleared M6's gate): #10965 — M2 BaseServer common base class (CLOSED)
  • Sibling SDK work: #10103 — SDK-layer config-file migration (config-file dual)
  • Strategic anchor: learn/agentos/v13-path.md §3 D4 + §4 M6
  • M2 per-server migration precedent: #10974 — github-workflow migration to BaseServer (the analogous earlier per-server migration; this M6 sub-issue follows the same single-PR per-server pattern). Same Server.mjs that received M2 BaseServer extension hooks now receives M6's getDependentServices() import-path update.

Origin Session ID: 33ed57b5-10ed-491d-8b8f-ce5f1223ec38

Retrieval Hint: query_raw_memories(query="M6 SDK migration github-workflow services flat shape ai/services/github-workflow GH_ aliases")

tobiu referenced in commit dc43731 - "refactor(github-workflow): migrate services to flat SDK boundary (#10993) (#10997) on May 9, 2026, 12:35 AM
tobiu closed this issue on May 9, 2026, 12:35 AM