Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/auto-author-assign.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ jobs:
steps:
- uses: toshimaru/auto-author-assign@v3.1.0
with:
repo-token: ${{ secrets.GH_PAT }}
# Falls back to the built-in token: GH_PAT is not set on this repo, and the job
# already has the pull-requests: write permission it needs.
repo-token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
14 changes: 0 additions & 14 deletions .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,6 @@ jobs:
eslint_extensions: ts
tsc: true

openapi-lint:
name: Run OpenAPI lint Check
runs-on: ubuntu-latest

steps:
- name: Check out TS Project Git repository
uses: actions/checkout@v7

- name: Init Nodejs
uses: MapColonies/shared-workflows/actions/init-npm@init-npm-v1

- name: OpenAPI Lint Checks
run: npx @redocly/cli lint --format=github-actions openapi3.yaml

helm-lint:
name: Run Helm lint Check
runs-on: ubuntu-latest
Expand Down
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ FROM node:24.10.0-alpine3.22 AS production
RUN apk add dumb-init

ENV NODE_ENV=production
ENV SERVER_PORT=8080


WORKDIR /usr/src/app
Expand All @@ -29,6 +28,6 @@ COPY --chown=node:node --from=build /tmp/buildApp/dist .
COPY --chown=node:node ./config ./config


# Outbound-only worker: nothing to expose.
USER node
EXPOSE 8080
CMD ["dumb-init", "node", "--import", "./instrumentation.mjs", "./index.js"]
175 changes: 82 additions & 93 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,124 +1,113 @@
# Map Colonies typescript service template
# developer-agent-bot

----------------------------------
Pulls `agent-ready` Jira tickets from the MAPCO project, implements them, and opens pull
requests. Designed in [MAPCO-11374](https://mapcolonies.atlassian.net/browse/MAPCO-11374),
substrate decided in [MAPCO-11377](https://mapcolonies.atlassian.net/browse/MAPCO-11377).

This is a basic repo template for building new MapColonies web services in Typescript.
**Current slice: MAPCO-11429.** It polls and reports. It claims nothing, writes nothing to
Jira, and touches no repository.

> [!IMPORTANT]
> To regenerate the types on openapi change run the command `npm run generate:openapi-types`.
## Shape

> [!WARNING]
> After creating a new repo based on this template, you should delete the CODEOWNERS file.
A long-lived process with an internal scheduler — deliberately *not* an OpenShift CronJob.
A ticket in flight belongs to a process that is still alive, which keeps "boot" a rare
event (a crash, a redeploy, an eviction) rather than something that happens every tick.

It is outbound-only. No Service, no Route, no Ingress, no HTTP probes — there is nothing to
probe, which is also how it satisfies MAPCO-11430's requirement that probes neither restart
an idle pod between runs nor kill one mid-run.

## Development
When in development you should use the command `npm run start:dev`. The main benefits are that it enables offline mode for the config package, and source map support for NodeJS errors.

### Template Features:

- eslint configuration by [@map-colonies/eslint-config](https://github.com/MapColonies/eslint-config)

- prettier configuration by [@map-colonies/prettier-config](https://github.com/MapColonies/prettier-config)

- jest

- .nvmrc

- Multi stage production-ready Dockerfile

- commitlint

- git hooks
Jira is the sole source of truth. There is no database and nothing on disk outlives a run.

- logging by [@map-colonies/js-logger](https://github.com/MapColonies/js-logger)
### The seam

- OpenAPI request validation
`runCycle()` in `src/cycle.ts` is one complete run, and it is the single seam the whole
pipeline is built and tested through. The scheduler calls it; so do the tests. Later slices
add cases here rather than standing up harnesses of their own.

- config load with [node-config](https://www.npmjs.com/package/node-config)
### Jira access

- Tracing and metrics by [@map-colonies/telemetry](https://github.com/MapColonies/telemetry)
Through the org's self-hosted `atlassian-write` MCP server, in-cluster — the worker carries
no Jira credentials of its own.

- github templates
Every Jira call is **worker code**, never model tooling. The Agent SDK gets file and test
tools only, so claiming, releasing and the attempt cap are not things the model can decline
to do. Same principle as MAPCO-11436's rule about git: a prompt that says "never push to
master" enforces nothing.

- bug report
## Things that look wrong but aren't

- feature request
Verified against the live Jira instance in MAPCO-11427, and each one bit a first draft:

- pull request
- **Finished work is excluded by status *name*, not `statusCategory`.** `Resolved` reports
category `In Progress` in this instance, so a category filter hands the worker
already-finished tickets.
- **The poll asks for one more ticket than it needs.** The MCP server's `total` is always
`-1`, so a full page is otherwise indistinguishable from an exhausted queue.
- **`labels not in (...)` also excludes unlabelled issues.** Harmless in the poll, because
`labels = agent-ready` already guarantees a non-empty label set. Any query that drops the
agent-ready clause must add `labels is EMPTY OR ...` back.
- **No `project = MAPCO` clause.** The server auto-bounds the query, wrapping it as
`(<ours>) AND project = MAPCO`, so top-level `OR` is safe.
- **Transitions are resolved per issue, never cached.** Transition ids are not portable
across issue types: id `4` starts work on a Tech Requirement and *ends* it on a Task.

- github actions
## Ticket titles

- on pull_request
The repo a ticket is about comes from its title: `<repo-name>: <feature title>`.

- LGTM
Only the part before the **first** colon is the repo name, so a feature title may contain
colons of its own. GitHub matches names case-insensitively, and the worker adopts the
canonical spelling it answers with — the casing in a ticket title never reaches a clone URL
or a branch name.

- test
A title with no prefix, a prefix that is prose rather than a name, or a name that matches no
repo in the org is a **refusal**, never a guess: the worker comments what it looked for,
releases the ticket and bumps the attempt count. Most existing MAPCO tickets have no prefix,
so refusal is the common path until the convention spreads.

- lint
## Configuration

- snyk
| Variable | Default | Meaning |
|---|---|---|
| `MCP_ATLASSIAN_URL` | *required* | Address of the `atlassian-write` MCP server. Transport is picked from the path: `/sse` gets SSE, anything else Streamable HTTP |
| `POLL_INTERVAL_MS` | `300000` | How often a cycle runs |
| `MAX_TICKETS_PER_RUN` | `1` | Tickets one cycle may start |
| `MAX_CONCURRENT_TICKETS` | `1` | Tickets in flight at once |
| `GITHUB_TOKEN` | *optional* | Bearer token for repo lookups. A PAT locally; a short-lived App installation token in the cluster once MAPCO-11428 lands. Unauthenticated works at a lower rate limit |

## API
Checkout the OpenAPI spec [here](/openapi3.yaml)
Raise `MAX_TICKETS_PER_RUN` before ever raising `MAX_CONCURRENT_TICKETS`.

## Installation

Install deps with npm

```bash
npm install
```

## Run Locally

Clone the project

```bash

git clone https://link-to-project

```
## Known gaps

Go to the project directory
- The worker knobs are read from the environment rather than `@map-colonies/config`, which
needs a schema published in `@map-colonies/schemas`. Telemetry still goes through the
library. Registering a real schema is follow-up work.
- **The Jira identity is the shared MCP service account.** It has no per-user attribution,
so the optimistic claim check (MAPCO-11431) cannot distinguish this worker from any other
session using the same MCP, and boot-time orphan release (MAPCO-11432) could release a
ticket someone else is working. A dedicated Jira account is the recommendation.
- `helm lint` needs the private `mclabels` dependency and fails without registry access.

```bash
## Dry run

cd my-project
One cycle against the real MCP server, from a laptop. Read-only. Requires the corporate
VPN — the server is not reachable from outside it.

```sh
MCP_ATLASSIAN_URL="https://atlassian-mcp-write.mapcolonies.net/sse" \
GITHUB_TOKEN="$(gh auth token)" npm run dry-run
```

Install dependencies
It runs the same `runCycle` seam the deployed worker runs, so what it proves is about the
worker rather than about the harness. It skips the scheduler, tracing and
`@map-colonies/config`, which the deployed entry point uses.

```bash

npm install

```

Start the server

```bash

npm run start

```

## Running Tests

To run tests, run the following command

```bash

npm run test

```

To only run unit tests:
```bash
npm run test:unit
```
## Development

To only run integration tests:
```bash
npm run test:integration
```sh
npm ci
npm test
npm run lint
npm run build
```
12 changes: 6 additions & 6 deletions catalog-info.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ts-server-boilerplate
description: A boilerplate github repo for a REST API service in NodeJS for MapColonies
name: developer-agent-bot
description: Pulls agent-ready Jira tickets, implements them, and opens pull requests
annotations:
github.com/project-slug: MapColonies/ts-server-boilerplate
github.com/project-slug: MapColonies/developer-agent-bot
tags:
- nodejs
- typescript
- expressjs
- boilerplate
- agent
- worker
spec:
type: service
lifecycle: production
owner: DevInfra
system: boilerplate
system: agent-delegation
20 changes: 0 additions & 20 deletions config/default.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
{
"openapiConfig": {
"filePath": "./openapi3.yaml",
"basePath": "/docs",
"rawPath": "/api",
"uiPath": "/api"
},
"telemetry": {
"metrics": {},
"tracing": {
Expand All @@ -18,19 +12,5 @@
"enabled": false
}
}
},
"server": {
"port": 8080,
"request": {
"payload": {
"limit": "1mb"
}
},
"response": {
"compression": {
"enabled": true,
"options": null
}
}
}
}
4 changes: 2 additions & 2 deletions helm/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apiVersion: v2
name: ts-server-boilerplate
description: A Helm chart for ts-server-boilerplate service
name: developer-agent-bot
description: A Helm chart for the developer-agent-bot ticket-pulling worker
type: application
version: 1.0.0
appVersion: 1.0.0
Expand Down
Loading
Loading