Skip to content

Add read-only Gira work-map import#19

Open
StatPan wants to merge 4 commits into
mainfrom
issue-18-gira-work-map-import
Open

Add read-only Gira work-map import#19
StatPan wants to merge 4 commits into
mainfrom
issue-18-gira-work-map-import

Conversation

@StatPan

@StatPan StatPan commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Closes #18

What

  • Adds a read-only POST /api/work-map/import/gira endpoint that maps gira workspace status --json / workspace-queues/v1 input into agentree-work-map/v1.
  • Adds a server-side Gira workspace importer that preserves repo/issue/branch identity, PR/check/review state, next actions, source links, queue overlap, conservative edges, and unknown/low-confidence attribution.
  • Adds a sample Gira workspace status fixture, focused mapper/route tests, and a docs note for the work-map import boundary and follow-up UI integration.

Why

Agentree needs a first Gira/GitHub read model so it can render a work map while Gira and GitHub remain canonical. This slice intentionally avoids DB writes, GitHub/Gira mutation, hosted runtime changes, credentials, and worker execution control.

Test plan

  • npm exec --yes pnpm@10.24.0 -- vitest run src/server/work-map/giraWorkspaceImport.test.ts src/server/routes/workMap.test.ts passes
  • npm exec --yes pnpm@10.24.0 -- test passes
  • npm exec --yes pnpm@10.24.0 -- run build passes
  • git diff --check passes
  • Manually verified in browser (not applicable; this PR adds server/domain import path, not UI rendering)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a read-only Gira work-map import path, introducing the POST /api/work-map/import/gira endpoint along with associated parsing logic, unit tests, and documentation. The review feedback highlights two key areas for improvement: resolving asymmetric branch matching in findNodeForRef to ensure consistent fallback behavior, and enhancing edge import logic by providing more descriptive warnings for skipped edges and filtering out self-referencing edges.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +518 to +522
function findNodeForRef(ref: WorkIdentityRef, nodesById: Map<string, WorkNode>): WorkNode | undefined {
const exact = nodesById.get(workNodeId(ref))
if (exact || ref.branch !== null) return exact
return [...nodesById.values()].find((node) => node.identity.repo === ref.repo && node.identity.issue === ref.issue)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The branch matching logic in findNodeForRef is asymmetric. If a reference has branch: null but the imported node has a branch, it successfully falls back to matching by repo and issue. However, if the reference has a specific branch (e.g., from a parent link) but the imported node has branch: null (unknown branch), the fallback is skipped and the edge is not resolved. Removing the ref.branch !== null check ensures consistent fallback behavior for both cases.

Suggested change
function findNodeForRef(ref: WorkIdentityRef, nodesById: Map<string, WorkNode>): WorkNode | undefined {
const exact = nodesById.get(workNodeId(ref))
if (exact || ref.branch !== null) return exact
return [...nodesById.values()].find((node) => node.identity.repo === ref.repo && node.identity.issue === ref.issue)
}
function findNodeForRef(ref: WorkIdentityRef, nodesById: Map<string, WorkNode>): WorkNode | undefined {
const exact = nodesById.get(workNodeId(ref))
if (exact) return exact
return [...nodesById.values()].find((node) => node.identity.repo === ref.repo && node.identity.issue === ref.issue)
}

Comment on lines +527 to +533
for (const ref of edgeRefs) {
const source = findNodeForRef(ref.from, nodesById)
const target = findNodeForRef(ref.to, nodesById)
if (!source || !target) {
warnings.push(`skipped ${ref.kind} edge because one endpoint is not present in the imported queue items`)
continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The warning message for skipped edges is very generic and does not specify which endpoints are missing, making it difficult to debug import issues. Additionally, there is no check to prevent self-referencing edges (where a node has an edge to itself), which could cause infinite loops or rendering issues in the UI. We should make the warning more descriptive and filter out self-referencing edges.

  for (const ref of edgeRefs) {
    const source = findNodeForRef(ref.from, nodesById)
    const target = findNodeForRef(ref.to, nodesById)
    if (!source || !target) {
      const missing = !source && !target ? 'both endpoints' : !source ? 'source' : 'target'
      warnings.push(`skipped ${ref.kind} edge from ${ref.from.repo}#${ref.from.issue} to ${ref.to.repo}#${ref.to.issue} because ${missing} not present in the imported queue items`)
      continue
    }

    if (source.id === target.id) {
      warnings.push(`skipped self-referencing ${ref.kind} edge for ${source.id}`)
      continue
    }

@StatPan

StatPan commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

Strict review findings:

  1. High - Ambiguous branchless edge references can attach to the wrong branch-specific node. In src/server/work-map/giraWorkspaceImport.ts:518, findNodeForRef falls back from a missing branch to the first node with the same repo and issue. Since work item identity includes branch, an edge ref like { repo: 'R/O', issue: 1 } is ambiguous when the import contains both R/O#1@a and R/O#1@b, but the mapper silently creates an edge to whichever one was inserted first and emits no warning. I verified this with a tsx probe: the output created a parent edge to branch a while branch b was also present.

  2. Medium - The missing-branch sentinel collides with a real branch named unknown-branch. In src/server/work-map/giraWorkspaceImport.ts:191, both workNodeId and workIdentityKey use ref.branch ?? 'unknown-branch'. That makes { branch: null } and { branch: 'unknown-branch' } produce the same id/key, so nodesById merges two distinct imported work items and drops one without warning. I verified this with a tsx probe that imported both variants and returned only one node.

Verification run:

  • ./node_modules/.bin/vitest run src/server/work-map/giraWorkspaceImport.test.ts src/server/routes/workMap.test.ts passed, 2 files / 7 tests.
  • ./node_modules/.bin/tsc -p tsconfig.json --noEmit passed.
  • git diff --check origin/main...HEAD passed.

Residual risk: I did not run the full test suite or deploy. pnpm is not installed in this environment, so I used the checked-out local binaries under node_modules/.bin.

@StatPan

StatPan commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

Fixed the review findings in 9834c5f.

Changes:

  • Tagged absent branch identities separately from present branch values, so a real unknown-branch branch no longer collides with missing branch data.
  • Branchless edge refs now link only when resolution is exact or uniquely inferred; ambiguous repo+issue refs are skipped with a warning.

Verification:

  • ./node_modules/.bin/vitest run src/server/work-map/giraWorkspaceImport.test.ts src/server/routes/workMap.test.ts
  • ./node_modules/.bin/vitest run
  • ./node_modules/.bin/tsc -p tsconfig.json --noEmit
  • ./node_modules/.bin/tsc -p tsconfig.client.json --noEmit
  • git diff --check

Note: pnpm is not installed in this environment, so I used the checked-out local binaries under node_modules/.bin. I did not run the package build because it writes generated dist/ output.

@StatPan

StatPan commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

Review finding (material):

  • src/server/work-map/giraWorkspaceImport.ts:65 and src/server/work-map/giraWorkspaceImport.ts:499 expose each imported queue item as source.rawItem in the public work-map response. That makes the new endpoint import and echo arbitrary Gira payload fields, including anything outside the documented work-map schema such as productivity/time/token fields or accidental sensitive metadata. This conflicts with the Add read-only Gira work-map import from workspace status JSON #18/[Epic] Gira-based agent work map roadmap #15 boundary and docs/WORK_MAP_IMPORT.md:54, which say the slice should stay limited to work-item state and not import those metrics. Please remove rawItem from agentree-work-map/v1 or replace it with an allowlisted/sanitized source snapshot so downstream UI cannot consume out-of-bound fields.

@StatPan

StatPan commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

Fixed in 8cb33c2.

Changes:

  • Removed source.rawItem from the public agentree-work-map/v1 work node output.
  • Kept only minimal source metadata on imported nodes: schemaVersion and rawQueue.
  • Added a regression test that serializes the exported work-map JSON and verifies arbitrary raw Gira queue item fields/values, including productivity/token/sensitive metadata examples, are not emitted.
  • Updated docs/WORK_MAP_IMPORT.md to document that raw queue items and unmapped payload fields are not echoed.

Verification:

  • ./node_modules/.bin/vitest run src/server/work-map/giraWorkspaceImport.test.ts src/server/routes/workMap.test.ts passed, 2 files / 10 tests.
  • ./node_modules/.bin/tsc -p tsconfig.json --noEmit passed.
  • ./node_modules/.bin/tsc -p tsconfig.client.json --noEmit passed.
  • git diff --check passed.
  • PATH=/home/statpan/.nvm/versions/node/v24.12.0/bin:$PATH ./node_modules/.bin/vitest run passed, 9 files / 98 tests.
  • PATH=/home/statpan/.nvm/versions/node/v24.12.0/bin:$PATH npm run build passed.

Residual risk: no deploy was run. Full Vitest needed Node 24.12.0 because the available shared better-sqlite3 native module was compiled for ABI 137; Node 26.0.0 failed only on that native module ABI mismatch.

@StatPan

StatPan commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

Severity: Low - public contract docs mismatch. docs/WORK_MAP_IMPORT.md:29 says a missing branch uses an unknown-branch identity segment, but the implementation uses missing-branch in branchIdentityPart, and the regression test asserts R/O#1@missing-branch specifically to keep it distinct from a real branch named unknown-branch. Required fix: update the docs to describe missing-branch as the absent-branch identity segment, and keep real branches under branch:<name>.

@StatPan

StatPan commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

Fixed the docs mismatch in docs/WORK_MAP_IMPORT.md: absent branches now document missing-branch, while real branches remain branch:<name>.

Verification:

  • rg -n "unknown-branch|missing-branch|branch:<|branch:" docs src
  • git diff --check
  • PATH=/home/statpan/.nvm/versions/node/v24.12.0/bin:$PATH pnpm exec vitest run src/server/work-map/giraWorkspaceImport.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add read-only Gira work-map import from workspace status JSON

1 participant