Greenlit A.I.
Powered by a Copilot Studio connector for product approvals. It drafts applications, flags duplicates, and helps reviewers approve or reject on platforms like Jira Service Management.
What Inspired Us
Every organization adopting AI tools faces the same invisible bottleneck: someone wants to use a new AI-powered product, but there's no clear, consistent process for getting it approved. Teams submit redundant requests. Reviewers make decisions without enough context. And nobody knows what's already been approved — so the same tool gets proposed six times by six different people.
We wanted to build something that fixes all three of those problems at once, using AI to make the approval process itself smarter. The kicker: we wanted the duplicate-detection to go beyond just checking a ticketing system. We wanted it to check whether someone is already building the thing being proposed — which is exactly what GitLab Orbit makes possible.
What We Built
Greenlit A.I. is an AI copilot for product approvals with three core capabilities:
1. Draft Assistance (for submitters)
A submitter describes their idea in plain language to a Copilot Studio bot. Greenlit calls Claude to generate a structured application draft — title, description, likely data accessed, customer-facing status, and clarifying questions the reviewer will need answered.
2. Duplicate Detection (the Orbit-powered differentiator)
Before submission, Greenlit checks for overlap from two directions:
- Jira Service Management: existing pending/approved requests that describe a similar product
- GitLab Orbit (
query_graph): related projects, repos, and work items already in the organization's GitLab knowledge graph — catching duplicate effort even before a formal request was ever filed
Claude reasons over both result sets together to produce a single
recommendation: "Likely duplicate of JSM-42 — and there's an active GitLab
project in ops/ai-tools doing the same thing. Recommend reaching out
before proceeding."
3. Reviewer Recommendation
When a reviewer opens a pending request, Greenlit generates a structured risk assessment: risk level (low / medium / high), specific risk factors, policy gaps the submitter didn't address, and a final recommendation (approve / reject / escalate) with rationale. The reviewer makes the call — Greenlit fires the Jira workflow transition and, on approval, auto-publishes a summary page to a Confluence "Approved AI Tools" registry.
How We Built It
Stack
| Layer | Technology |
|---|---|
| Conversational front end | Microsoft Copilot Studio (custom connector) |
| Backend API | Node.js + Express (local dev) / Azure Functions v4 (deployed) |
| AI reasoning | Anthropic Claude (claude-sonnet-4-6) via the Anthropic SDK |
| Knowledge graph | GitLab Orbit REST API (/api/v4/orbit/query) |
| System of record | Jira Service Management (Service Desk API) |
| Approved tools registry | Confluence (REST API v2) |
Architecture
Submitter / Reviewer | v Jira Service Management <-- system of record (requests, approval workflow) Confluence <-- approved-tools registry (published on approval) | v Copilot Studio bot <-- conversational layer (custom connector -> backend) | v Node.js backend <-- AI reasoning + orchestration | +---> Claude API (draft suggestions, duplicate reasoning, risk scoring) | +---> GitLab Orbit (query_graph: related projects + work items) | +---> Jira Service Mgmt (create / read / transition requests) | +---> Confluence (publish approved-tool pages)
Key design decisions
Graceful degradation everywhere. Every external integration (Jira, Orbit, Confluence) fails soft. If Jira credentials aren't configured, the backend falls back to an in-memory mock store. If Orbit is unreachable, duplicate detection still runs against Jira alone. If Confluence publishing fails, the approval decision still succeeds. Nothing blocks the core flow.
One service layer, two hosting targets. The same src/services/*.js
logic runs under both the local Express app (npm run dev) and the Azure
Functions v4 entry point (src/functionsApp.js). Only the HTTP routing layer
differs — the AI reasoning, Orbit queries, and Jira/Confluence calls are
identical in both environments.
Environment-driven configuration. Every integration detail (Jira field IDs, workflow transition names, GitLab group path, Confluence space ID) is injectable via environment variables with sensible defaults, so the same codebase works across dev, staging, and production without code changes.
Challenges We Faced
ES module import hoisting vs. dotenv. We discovered mid-build that
dotenv.config() was running after our service modules had already
evaluated their module-level const ANTHROPIC_API_KEY = process.env.X
reads — because ES module import statements are hoisted above top-level
code in the importing file. The fix was adding src/index.js as a preload
entry point that loads env vars first via a dynamic await import('./server.js'),
deferring the rest of the module tree until after dotenv has run.
GitLab Orbit's MCP vs. REST surface. The primary Orbit documentation
focuses on the MCP-over-OAuth flow (glab orbit remote), which requires an
interactive browser login — not practical for a server-side backend. We
discovered Orbit also exposes a plain REST query endpoint
(POST /api/v4/orbit/query) that accepts a personal access token as a
PRIVATE-TOKEN header, making server-to-server integration straightforward.
Jira workflow transitions vs. field updates. We initially assumed
approving/rejecting a Jira request was a simple field update — it's not.
JSM models approvals as workflow transitions, each with a specific
transition ID that varies per-site. We had to implement a lookup step:
GET /rest/api/3/issue/{id}/transitions to find the right transition ID,
then fire it via POST /rest/api/3/issue/{id}/transitions. We also made
the transition names configurable via env vars (JIRA_TRANSITION_APPROVE,
JIRA_TRANSITION_REJECT) since different orgs name theirs differently.
Azure Functions v4 and the Express bridge problem. Existing libraries
for wrapping Express apps in Azure Functions (e.g. azure-function-express)
only support the older v3 function.json model. v4 uses fetch-standard
Request/Response types that don't map cleanly onto Node's
http.IncomingMessage/ServerResponse. Rather than maintain a fragile
hand-rolled shim, we rewrote the routing layer natively using app.http()
while keeping the service layer (all the AI logic) completely unchanged —
clean separation that paid off immediately.
What We Learned
GitLab Orbit is genuinely useful as a deduplication signal. Checking whether someone is already building something in GitLab before approving a formal request for it is a real, concrete use case — not a demo contrivance. The Orbit knowledge graph makes this query fast and organizational-scope-aware in a way that a plain GitLab search API wouldn't.
Graceful degradation is a feature, not a fallback. Building every integration to fail-soft independently made local development, testing, and the demo significantly smoother. It also makes the product more credible for real enterprise deployment where not every system is reachable from every environment.
Copilot Studio's custom connector model is straightforward to target. Any HTTPS REST API with predictable request/response shapes works as a connector action — you don't need Copilot Studio-specific SDK support on the backend. This means the backend is also usable as a plain API by any other orchestration layer (Power Automate, n8n, a custom bot, etc.).
What's Next
- OpenAPI spec for the custom connector (currently defined manually in Copilot Studio; generating it from the route definitions would make onboarding faster)
- Embeddings-based similarity for duplicate detection (replacing the current keyword extraction + Claude reasoning approach with vector similarity against stored application embeddings for higher recall)
- Webhook-driven Confluence updates (currently Confluence is written on approval; a future version could keep the registry page updated whenever an application's status changes)
- Multi-platform support (ServiceNow, Monday.com, Linear) — the Jira client is already isolated behind a consistent interface, making alternative backends a one-file swap
Built With
- anthropic
- api
- atlassian
- azure
- claude
- confluence
- connected-via-your-backend-(no-direct-atlassian-connector-needed)-|-v-your-node.js-backend-(hosted-on-azure-functions)->-drafting
- copilot
- dotenv
- duplicate-check
- express.js
- functions
- gitlab
- javascript
- jira
- management
- microsoft
- node.js
- orbit
- rest
- risk-assessment
- risk-scoring-logic-|-|->-claude-api->-reasoning-(drafts
- service
- studio
Log in or sign up for Devpost to join the conversation.