@nonameolsson/nightjar 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +405 -2
- package/dist/account.d.ts +12 -0
- package/dist/account.js +57 -0
- package/dist/account.js.map +1 -0
- package/dist/adapter-module.d.ts +18 -0
- package/dist/adapter-module.js +83 -0
- package/dist/adapter-module.js.map +1 -0
- package/dist/adapters.d.ts +15 -0
- package/dist/adapters.js +160 -0
- package/dist/adapters.js.map +1 -0
- package/dist/args.d.ts +13 -0
- package/dist/args.js +84 -0
- package/dist/args.js.map +1 -0
- package/dist/ask.d.ts +6 -0
- package/dist/ask.js +27 -0
- package/dist/ask.js.map +1 -0
- package/dist/azure-devops/api.d.ts +34 -0
- package/dist/azure-devops/api.js +98 -0
- package/dist/azure-devops/api.js.map +1 -0
- package/dist/azure-devops/code-host.d.ts +36 -0
- package/dist/azure-devops/code-host.js +185 -0
- package/dist/azure-devops/code-host.js.map +1 -0
- package/dist/azure-devops/fake-azure-devops.d.ts +90 -0
- package/dist/azure-devops/fake-azure-devops.js +728 -0
- package/dist/azure-devops/fake-azure-devops.js.map +1 -0
- package/dist/azure-devops/index.d.ts +8 -0
- package/dist/azure-devops/index.js +11 -0
- package/dist/azure-devops/index.js.map +1 -0
- package/dist/azure-devops/sign-in.d.ts +11 -0
- package/dist/azure-devops/sign-in.js +68 -0
- package/dist/azure-devops/sign-in.js.map +1 -0
- package/dist/azure-devops/text.d.ts +1 -0
- package/dist/azure-devops/text.js +41 -0
- package/dist/azure-devops/text.js.map +1 -0
- package/dist/azure-devops/tracker.d.ts +44 -0
- package/dist/azure-devops/tracker.js +333 -0
- package/dist/azure-devops/tracker.js.map +1 -0
- package/dist/build.d.ts +75 -0
- package/dist/build.js +698 -0
- package/dist/build.js.map +1 -0
- package/dist/check.d.ts +8 -0
- package/dist/check.js +25 -0
- package/dist/check.js.map +1 -0
- package/dist/checks.d.ts +45 -0
- package/dist/checks.js +243 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -0
- package/dist/code-host.d.ts +19 -0
- package/dist/code-host.js +2 -0
- package/dist/code-host.js.map +1 -0
- package/dist/config.d.ts +86 -0
- package/dist/config.js +294 -0
- package/dist/config.js.map +1 -0
- package/dist/contract/case.d.ts +4 -0
- package/dist/contract/case.js +2 -0
- package/dist/contract/case.js.map +1 -0
- package/dist/contract/code-host.d.ts +24 -0
- package/dist/contract/code-host.js +108 -0
- package/dist/contract/code-host.js.map +1 -0
- package/dist/contract/command.d.ts +2 -0
- package/dist/contract/command.js +36 -0
- package/dist/contract/command.js.map +1 -0
- package/dist/contract/feature-writer.d.ts +3 -0
- package/dist/contract/feature-writer.js +101 -0
- package/dist/contract/feature-writer.js.map +1 -0
- package/dist/contract/run.d.ts +18 -0
- package/dist/contract/run.js +89 -0
- package/dist/contract/run.js.map +1 -0
- package/dist/contract/tracker.d.ts +30 -0
- package/dist/contract/tracker.js +250 -0
- package/dist/contract/tracker.js.map +1 -0
- package/dist/create.d.ts +9 -0
- package/dist/create.js +112 -0
- package/dist/create.js.map +1 -0
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +42 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fakes/code-host.d.ts +21 -0
- package/dist/fakes/code-host.js +42 -0
- package/dist/fakes/code-host.js.map +1 -0
- package/dist/fakes/harness.d.ts +7 -0
- package/dist/fakes/harness.js +68 -0
- package/dist/fakes/harness.js.map +1 -0
- package/dist/fakes/machine.d.ts +11 -0
- package/dist/fakes/machine.js +101 -0
- package/dist/fakes/machine.js.map +1 -0
- package/dist/fakes/tracker.d.ts +40 -0
- package/dist/fakes/tracker.js +116 -0
- package/dist/fakes/tracker.js.map +1 -0
- package/dist/feature-file.d.ts +12 -0
- package/dist/feature-file.js +84 -0
- package/dist/feature-file.js.map +1 -0
- package/dist/git.d.ts +9 -0
- package/dist/git.js +12 -0
- package/dist/git.js.map +1 -0
- package/dist/github/api.d.ts +37 -0
- package/dist/github/api.js +66 -0
- package/dist/github/api.js.map +1 -0
- package/dist/github/code-host.d.ts +19 -0
- package/dist/github/code-host.js +105 -0
- package/dist/github/code-host.js.map +1 -0
- package/dist/github/fake-gh.d.ts +17 -0
- package/dist/github/fake-gh.js +51 -0
- package/dist/github/fake-gh.js.map +1 -0
- package/dist/github/fake-github.d.ts +58 -0
- package/dist/github/fake-github.js +285 -0
- package/dist/github/fake-github.js.map +1 -0
- package/dist/github/labels.d.ts +3 -0
- package/dist/github/labels.js +44 -0
- package/dist/github/labels.js.map +1 -0
- package/dist/github/tracker.d.ts +28 -0
- package/dist/github/tracker.js +208 -0
- package/dist/github/tracker.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +19 -0
- package/dist/init.js +225 -0
- package/dist/init.js.map +1 -0
- package/dist/ledger.d.ts +13 -0
- package/dist/ledger.js +49 -0
- package/dist/ledger.js.map +1 -0
- package/dist/main.d.ts +11 -0
- package/dist/main.js +215 -0
- package/dist/main.js.map +1 -0
- package/dist/person-config.d.ts +9 -0
- package/dist/person-config.js +46 -0
- package/dist/person-config.js.map +1 -0
- package/dist/plan.d.ts +9 -0
- package/dist/plan.js +27 -0
- package/dist/plan.js.map +1 -0
- package/dist/ports.d.ts +16 -0
- package/dist/ports.js +104 -0
- package/dist/ports.js.map +1 -0
- package/dist/prompt.d.ts +166 -0
- package/dist/prompt.js +217 -0
- package/dist/prompt.js.map +1 -0
- package/dist/queue.d.ts +20 -0
- package/dist/queue.js +94 -0
- package/dist/queue.js.map +1 -0
- package/dist/say.d.ts +2 -0
- package/dist/say.js +4 -0
- package/dist/say.js.map +1 -0
- package/dist/serve.d.ts +10 -0
- package/dist/serve.js +85 -0
- package/dist/serve.js.map +1 -0
- package/dist/session.d.ts +39 -0
- package/dist/session.js +144 -0
- package/dist/session.js.map +1 -0
- package/dist/settings.d.ts +10 -0
- package/dist/settings.js +92 -0
- package/dist/settings.js.map +1 -0
- package/dist/signals.d.ts +5 -0
- package/dist/signals.js +13 -0
- package/dist/signals.js.map +1 -0
- package/dist/skills.d.ts +4 -0
- package/dist/skills.js +51 -0
- package/dist/skills.js.map +1 -0
- package/dist/status-shape.d.ts +53 -0
- package/dist/status-shape.js +3 -0
- package/dist/status-shape.js.map +1 -0
- package/dist/status.d.ts +23 -0
- package/dist/status.js +56 -0
- package/dist/status.js.map +1 -0
- package/dist/tracker.d.ts +56 -0
- package/dist/tracker.js +12 -0
- package/dist/tracker.js.map +1 -0
- package/dist/workflow.d.ts +33 -0
- package/dist/workflow.js +53 -0
- package/dist/workflow.js.map +1 -0
- package/dist/worktree.d.ts +44 -0
- package/dist/worktree.js +193 -0
- package/dist/worktree.js.map +1 -0
- package/docs/ticket-contract.md +95 -0
- package/package.json +74 -5
- package/plugin/nightjar-status/.claude-plugin/plugin.json +5 -0
- package/plugin/nightjar-status/hooks/format.ts +48 -0
- package/plugin/nightjar-status/hooks/hooks.json +1 -0
- package/plugin/nightjar-status/hooks/register.ts +51 -0
- package/schema/factory.schema.json +437 -0
- package/skills/nightjar-feature/SKILL.md +73 -0
- package/src/status-shape.ts +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andreas Olsson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,406 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Nightjar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Nightjar builds a repo's Features with nobody at the keyboard. It reads a Feature's Tickets from a Tracker, has a Coding agent (Claude Code) build each one on a single branch, and opens one draft pull request per Feature on a Code host.
|
|
4
|
+
|
|
5
|
+
Today the Tracker is GitHub Issues or Azure DevOps Boards, and the Code host is GitHub or Azure Repos, chosen apart. The words used here (Feature, Ticket, Signal, Go, …) are defined in [GLOSSARY.md](GLOSSARY.md).
|
|
6
|
+
|
|
7
|
+
## Platforms and requirements
|
|
8
|
+
|
|
9
|
+
- macOS, Linux, or WSL2 on Windows. On native Windows every command stops with a message saying so.
|
|
10
|
+
- Node 24 or later.
|
|
11
|
+
- `git`, in a checkout whose `origin` is the Code host's repo, which `git fetch` can read with your own git login.
|
|
12
|
+
- With GitHub as the Tracker or the Code host: [`gh`](https://cli.github.com/), signed in (`gh auth login`).
|
|
13
|
+
- With Azure DevOps Boards as the Tracker or Azure Repos as the Code host: the [Azure CLI](https://learn.microsoft.com/cli/azure/) signed in (`az login`), else a personal access token in `AZURE_DEVOPS_EXT_PAT`. Nightjar asks `az` first, and signs in once for both. The token needs Work Items (Read & write) for either; for the Tracker, unless `factory.json` sets `trusted`, Identity (Read); for Azure Repos, Code (Read & write). Nightjar pushes to Azure Repos with this sign-in as a header for `dev.azure.com` alone; it never writes it into a remote or a git config.
|
|
14
|
+
- [`claude`](https://claude.com/claude-code) (Claude Code), signed in. Nightjar bills your Claude plan. If `ANTHROPIC_API_KEY` is set it refuses to start, unless you pass `--use-api-key`.
|
|
15
|
+
- If your git setup signs commits, the signing key must be unlocked: Nightjar stops when git cannot sign.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Nightjar needs Node, but the repo it builds need not be a Node project. Install it once for your user, so the repo gets no `package.json` or `node_modules`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm i -g @nonameolsson/nightjar
|
|
24
|
+
nightjar init
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or run it without installing, each time through `npx`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx @nonameolsson/nightjar init
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The commands below are written as `nightjar …`; with `npx`, put `npx @nonameolsson/nightjar` in its place. In a repo that is a Node project already, you can also pin the version with the repo, as a dev dependency, and run it as `npx nightjar …`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm i -D @nonameolsson/nightjar
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
From a tarball, built from a clone of this repo:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm ci
|
|
43
|
+
npm pack
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`npm pack` builds `dist/` first and writes `nonameolsson-nightjar-<version>.tgz`. Install that file the same way:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm i -g /path/to/nonameolsson-nightjar-0.0.0.tgz
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Set up: `nightjar init`
|
|
53
|
+
|
|
54
|
+
In the repo you want built, run:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
nightjar init
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
It asks for:
|
|
61
|
+
|
|
62
|
+
- the Tracker: `github`, `azure-devops` or `module` (your own, see [Another Tracker or Code host](#another-tracker-or-code-host)), and what that one needs: the `owner/name` repo on GitHub (it offers the one `origin` names), the organization, project and Feature type on Azure DevOps (and the type of a Ticket, which `nightjar create` needs), or the module's path;
|
|
63
|
+
- the Code host, the same way (on Azure Repos: organization, project and repo);
|
|
64
|
+
- the **check command**, which has no default: Nightjar assumes no language or toolchain, so it asks until you give one;
|
|
65
|
+
- the commands the agent may run, which it offers as the check command alone.
|
|
66
|
+
|
|
67
|
+
Then it writes `factory.json` (below), which you commit, and:
|
|
68
|
+
|
|
69
|
+
- **on GitHub** creates the Signal labels (`factory-go`, `ready-for-agent`, `agent-working`, `needs-info`, `ready-for-human`, or the names `factory.json` gives them), leaving any that exist as they are;
|
|
70
|
+
- **on Azure DevOps** says tags appear on first use, so there is nothing to create;
|
|
71
|
+
- checks every login it needs and says how to fix each one that is missing: `gh auth login`, `az login` or a token in `AZURE_DEVOPS_EXT_PAT`, and `claude auth login`.
|
|
72
|
+
|
|
73
|
+
It reads its answers from standard input, line by line, so a script can pipe them in; there is no need for a terminal. If `factory.json` is already there, `init` keeps it, asks nothing, and only creates the labels and checks the logins: run it that way after you have fixed a login, or when you join a repo that has one.
|
|
74
|
+
|
|
75
|
+
## Configure: `factory.json`
|
|
76
|
+
|
|
77
|
+
`nightjar init` writes this file. You can also write it by hand, at the root of the repo, and commit it. Nightjar refuses to start without one, and names each field that is wrong.
|
|
78
|
+
|
|
79
|
+
A minimal one needs a Tracker, a Code host, the check command, and the commands the agent may run:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"$schema": "https://unpkg.com/@nonameolsson/nightjar/schema/factory.schema.json",
|
|
84
|
+
"tracker": { "type": "github" },
|
|
85
|
+
"codeHost": { "type": "github" },
|
|
86
|
+
"check": "make check",
|
|
87
|
+
"allowedCommands": ["make check", "make test"]
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A full one:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"$schema": "https://unpkg.com/@nonameolsson/nightjar/schema/factory.schema.json",
|
|
96
|
+
"tracker": { "type": "github", "repo": "acme/widgets" },
|
|
97
|
+
"codeHost": { "type": "github", "repo": "acme/widgets" },
|
|
98
|
+
"check": "make check",
|
|
99
|
+
"allowedCommands": ["make check", "make test", "make lint *"],
|
|
100
|
+
"signals": {
|
|
101
|
+
"go": "factory-go",
|
|
102
|
+
"ready": "ready-for-agent",
|
|
103
|
+
"working": "agent-working",
|
|
104
|
+
"needsInfo": "needs-info",
|
|
105
|
+
"human": "ready-for-human"
|
|
106
|
+
},
|
|
107
|
+
"trusted": ["alice", "bob"],
|
|
108
|
+
"setup": "make install",
|
|
109
|
+
"ports": { "count": 5, "env": ["APP_PORT", "DB_PORT"] },
|
|
110
|
+
"rules": "docs/factory-rules.md",
|
|
111
|
+
"model": "sonnet",
|
|
112
|
+
"effort": "high",
|
|
113
|
+
"review": { "enabled": true, "model": "opus", "effort": "high" }
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Key | Required | What it does |
|
|
118
|
+
| ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
119
|
+
| `tracker` | yes | `{ "type": "github" }`: Features are issues, their Tickets sub-issues. `repo` (`owner/name`) is read from the `origin` remote when left out. Or `{ "type": "azure-devops" }` with `organization` (as in `dev.azure.com/<organization>`), `project`, and `featureType`, the work item type that plays the Feature (`Feature`, `Epic`, `Product Backlog Item`, …): its child work items are its Tickets. `ticketType`, optional, is the work item type `nightjar create` makes each Ticket as (`User Story`, `Product Backlog Item`, `Issue`, …). Or `{ "type": "module", "path": "./my-tracker.js" }`, with optional `options` handed to the module: see [Another Tracker or Code host](#another-tracker-or-code-host). |
|
|
120
|
+
| `codeHost` | yes | `{ "type": "github" }`: the branch is pushed to `origin` and a draft pull request opened. `repo` as above. Or `{ "type": "azure-devops" }` with `organization`, `project` and `repo`, the Git repo on Azure Repos the branch is pushed to and the draft pull request opened on. Or a `module`, as for `tracker`. |
|
|
121
|
+
| `check` | yes | The shell command that checks the work in the worktree, run after each Ticket. A non-zero exit means it failed. Nightjar assumes no language or toolchain, so there is no default. |
|
|
122
|
+
| `allowedCommands` | yes | What the agent may run, as Claude Code Bash permission patterns without `Bash( )`, such as `make test` or `make lint *`. Committing, pushing, switching branches, the network and `gh` stay denied whatever you list. The list may be empty. |
|
|
123
|
+
| `signals` | no | The name each Signal has on the Tracker (a label on GitHub, a tag on Azure DevOps). Leave out the ones you do not rename. Defaults are shown above. `needs-triage` and `wontfix` are refused: Nightjar never reads or sets them. |
|
|
124
|
+
| `trusted` | no | The logins whose comments reach the agent (on Azure DevOps, sign-in names such as `ann@contoso.com`, in any case). Replaces the default: on GitHub anyone with write access, on Azure DevOps the members of the project's Contributors group. |
|
|
125
|
+
| `setup` | no | A shell command run once in each new worktree before the first session, such as an install. If it fails, the Feature stops with a comment saying why. |
|
|
126
|
+
| `ports` | no | Reserve `count` (1 to 100) ports per run, held until the process ends so two runs never share one. `env` names the variables that carry them: the first name gets the first port, the next name the next, and so on. At most `count` names. |
|
|
127
|
+
| `rules` | no | A text file, relative to the repo root, added to the agent's prompt. Conventions normally live in `CLAUDE.md` or `AGENTS.md`. |
|
|
128
|
+
| `model`, `effort` | no | The default model and effort of every session. |
|
|
129
|
+
| `review` | no | The read-only review of the whole diff, run before the draft pull request opens. `enabled: false` switches it off (on by default); `model` and `effort` are the review's own, else the defaults above. |
|
|
130
|
+
| `workflows`, `defaultWorkflow` | no | Work the repo adds to the build loop: see [Workflows](#workflows). A Feature picks a Workflow with a `**Workflow:**` line; `defaultWorkflow` is the one a Feature without the line gets. Left out, a build runs no Step. |
|
|
131
|
+
|
|
132
|
+
Model and effort are chosen, strongest first: the `--model` / `--effort` option, the Ticket's own `**Model:**` / `**Effort:**` line, then `factory.json`.
|
|
133
|
+
|
|
134
|
+
With Azure DevOps Boards as the Tracker, and the code on GitHub:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"$schema": "https://unpkg.com/@nonameolsson/nightjar/schema/factory.schema.json",
|
|
139
|
+
"tracker": {
|
|
140
|
+
"type": "azure-devops",
|
|
141
|
+
"organization": "contoso",
|
|
142
|
+
"project": "Fabrikam",
|
|
143
|
+
"featureType": "Feature"
|
|
144
|
+
},
|
|
145
|
+
"codeHost": { "type": "github" },
|
|
146
|
+
"check": "make check",
|
|
147
|
+
"allowedCommands": ["make check", "make test"]
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Fully on Azure DevOps, with the code on Azure Repos:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"$schema": "https://unpkg.com/@nonameolsson/nightjar/schema/factory.schema.json",
|
|
156
|
+
"tracker": {
|
|
157
|
+
"type": "azure-devops",
|
|
158
|
+
"organization": "contoso",
|
|
159
|
+
"project": "Fabrikam",
|
|
160
|
+
"featureType": "Feature"
|
|
161
|
+
},
|
|
162
|
+
"codeHost": {
|
|
163
|
+
"type": "azure-devops",
|
|
164
|
+
"organization": "contoso",
|
|
165
|
+
"project": "Fabrikam",
|
|
166
|
+
"repo": "fabrikam-app"
|
|
167
|
+
},
|
|
168
|
+
"check": "make check",
|
|
169
|
+
"allowedCommands": ["make check", "make test"]
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The JSON Schema for editor completion is `schema/factory.schema.json` in the package, published at the `$schema` URL above, so it works whether or not the repo installs Nightjar.
|
|
174
|
+
|
|
175
|
+
## Your own settings: `~/.config/nightjar/config.json`
|
|
176
|
+
|
|
177
|
+
What is yours rather than the repo's lives outside it, in `config.json` under `$XDG_CONFIG_HOME/nightjar/` (`~/.config/nightjar/` when `XDG_CONFIG_HOME` is not set). The file is optional, and so is each key:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"dailyUsd": 25,
|
|
182
|
+
"assignee": "alice"
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| Key | Default | What it does |
|
|
187
|
+
| ---------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
188
|
+
| `dailyUsd` | `60` | The estimated spend per local day in a repo, from its `.factory/runs.jsonl`, at which a run pauses. `--watch` looks again after 30 minutes. |
|
|
189
|
+
| `assignee` | the signed-in person | Whose Features to build: `--once`, `--watch` and `doctor` take the Features assigned to this login with the Go, and `nightjar <id>` refuses one assigned to anyone else. |
|
|
190
|
+
| `profile` | none, the terminal's own login | Which Claude login the sessions use: a name (`work` is `~/.claude-work`), a path to the config folder, or `default` for `~/.claude`. `--profile` beats it. |
|
|
191
|
+
|
|
192
|
+
Nightjar refuses to start when the file is not JSON or a key is wrong or unknown, and names each one. `doctor` lists what it read.
|
|
193
|
+
|
|
194
|
+
## Another Tracker or Code host
|
|
195
|
+
|
|
196
|
+
Name your own Adapter in `factory.json` as a module, resolved from the repo's root, and check it before a build:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"tracker": { "type": "module", "path": "./tools/my-tracker.js" },
|
|
201
|
+
"codeHost": { "type": "github" }
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
nightjar contract ./tools/my-tracker.contract.js
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`nightjar contract` runs the same contract suite the built-in Adapters meet over the harnesses a module exports, prints every behaviour, names each one that fails, and exits 1 if any does. Nightjar refuses to start when a named module cannot be loaded or lacks a method of the interface. [docs/custom-adapters.md](docs/custom-adapters.md) has a minimal module and a test harness.
|
|
210
|
+
|
|
211
|
+
## Workflows
|
|
212
|
+
|
|
213
|
+
A repo can add its own work to a build without Nightjar knowing what that work is. A **Workflow** is a named list of **Steps** in `factory.json`. A Step is a prompt given to the agent, and the prompt may name any skill. Nightjar's loop stays fixed: it keeps the branch, the commits, the Signals and the pull request, and a Workflow can only add Steps to it.
|
|
214
|
+
|
|
215
|
+
A Feature picks a Workflow with a line anywhere in its body, in the same shape as a Ticket's Model line:
|
|
216
|
+
|
|
217
|
+
```markdown
|
|
218
|
+
**Workflow:** design-review
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
With no such line, the Feature gets `defaultWorkflow` if `factory.json` sets one, else no Step runs and the build is as it was. A name no Workflow has marks the Feature as needing info, with a comment naming the Workflows that exist.
|
|
222
|
+
|
|
223
|
+
A design review built on a skill, in this case [Impeccable](https://github.com/pbakaus/impeccable) (any skill the agent can load works the same way):
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"ports": { "count": 1, "env": ["APP_PORT"] },
|
|
228
|
+
"workflows": {
|
|
229
|
+
"design-review": {
|
|
230
|
+
"serve": "npm run dev -- --port $APP_PORT",
|
|
231
|
+
"localNetwork": true,
|
|
232
|
+
"allowedCommands": ["npx playwright test *"],
|
|
233
|
+
"steps": [
|
|
234
|
+
{
|
|
235
|
+
"name": "critique",
|
|
236
|
+
"prompt": "/impeccable critique the screens this Feature changed. Write what is wrong, worst first, to the notes folder.",
|
|
237
|
+
"point": "afterAllTickets",
|
|
238
|
+
"model": "opus"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"name": "polish",
|
|
242
|
+
"prompt": "/impeccable polish the screens, fixing what the critique in the notes folder found.",
|
|
243
|
+
"point": "afterAllTickets",
|
|
244
|
+
"until": "the critique's list is empty or only things a person must decide remain",
|
|
245
|
+
"maxRounds": 3,
|
|
246
|
+
"budgetUsd": 8
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"defaultWorkflow": "design-review"
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
A Step has:
|
|
256
|
+
|
|
257
|
+
| Key | What it does |
|
|
258
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
259
|
+
| `name`, `prompt` | The Step's name, which its commit and the pull request body use, and what the agent is told. Nightjar adds the rule that nobody is at the keyboard: the agent decides, never asks in chat, and a question that truly blocks it goes into its structured answer, which makes the Step "blocked". |
|
|
260
|
+
| `point` | `afterEachTicket` runs after each Ticket is built and checked. `afterAllTickets` runs once, after every Ticket, before the review; it runs only when every Ticket of the Feature is built, and gets the Feature's diff against the base branch, so the agent picks its own targets. |
|
|
261
|
+
| `model`, `effort` | The Step's own; else the defaults. `--model` and `--effort` win over both. |
|
|
262
|
+
| `budgetUsd` | The most one run of the Step may cost; a Ticket's budget when left out. |
|
|
263
|
+
| `until`, `maxRounds` | A condition in plain words. After each round the agent reports whether it holds, and Nightjar runs the Step again, in the same session, until it does or `maxRounds` (3 by default) is reached. What is left goes into the pull request body. |
|
|
264
|
+
|
|
265
|
+
A Workflow can also have `serve`, a command started in the background in the worktree before its Steps and stopped after them (it reads its port from the variables of `ports.env`, so `ports` is required); `allowedCommands` and `allowedTools`, which its Steps may use besides the usual ones; and `localNetwork`, which lets its Steps reach localhost and nothing else. The fixed deny rules (committing, pushing, switching branches, the outside network, `gh`) cannot be loosened by any of these.
|
|
266
|
+
|
|
267
|
+
Steps share one notes folder, `.factory/notes/` in the worktree, which is never committed. Nightjar passes nothing else from one Step to the next, so a Step that needs what an earlier one found reads it there.
|
|
268
|
+
|
|
269
|
+
What a Step changes goes through the check command and its fix rounds, then is committed on its own, with a message naming the Workflow and the Step. Steps count against the daily spend limit and show in the cost of the pull request. A Step that hits its budget or time limit, cannot get the check green, answers in a shape not known, or reports itself blocked has its changes thrown away; the later Steps still run, and the pull request opens with that Step listed as not done. A Step the branch already holds is not run again when a later run goes on.
|
|
270
|
+
|
|
271
|
+
`nightjar doctor` looks up each `/skill` a Step's prompt names among the skills a headless Claude session loads. A skill that is missing is an error for that Workflow only: a Feature that picks it stops with a comment, and the others build as usual. (A word like `/tmp/notes` is a path, not a skill; a bare `/word` in a prompt is read as one.)
|
|
272
|
+
|
|
273
|
+
## Write a Feature
|
|
274
|
+
|
|
275
|
+
What a Ticket must hold (acceptance criteria, blocking links, optional Model and Effort lines) is the [Ticket contract](docs/ticket-contract.md). Write a Feature and its Tickets by hand on the Tracker, as in [Give the Go](#give-the-go), or as one Markdown file in the contract's shape and let Nightjar make them through the configured Tracker:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
nightjar create --dry-run plans/escape.md # says what it would make
|
|
279
|
+
nightjar create plans/escape.md
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`nightjar create` makes the Feature with no Signal and each Ticket ready, linked to its blockers. It works with GitHub Issues, with Azure DevOps Boards once `tracker.ticketType` is set, and with a Tracker module that makes Features ([docs/custom-adapters.md](docs/custom-adapters.md)).
|
|
283
|
+
|
|
284
|
+
The package ships a Claude Code skill, `nightjar-feature`, that writes that file from an idea agreed in the conversation and runs `nightjar create`; it needs no other skill. Copy it into the repo to use it:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
mkdir -p .claude/skills
|
|
288
|
+
cp -R "$(npm root -g)/@nonameolsson/nightjar/skills/nightjar-feature" .claude/skills/
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
With Nightjar as a dev dependency, the package is at `node_modules/@nonameolsson/nightjar` instead, here and below.
|
|
292
|
+
|
|
293
|
+
## The Ticket contract
|
|
294
|
+
|
|
295
|
+
Every Ticket holds acceptance criteria (a checklist under an `Acceptance criteria` heading), optional blocking links to the Tickets that must be done first, and optional `**Model:**` and `**Effort:**` lines. The full contract, which holds on any Tracker, is in [docs/ticket-contract.md](docs/ticket-contract.md); the package ships it.
|
|
296
|
+
|
|
297
|
+
## Give the Go
|
|
298
|
+
|
|
299
|
+
Nightjar builds a Feature that is **assigned to you** and carries the **Go** Signal (the label `factory-go` by default). Nothing else starts a build.
|
|
300
|
+
|
|
301
|
+
1. Write the Feature as an issue, with its spec in the body.
|
|
302
|
+
2. Add its Tickets as **sub-issues**. Each Ticket holds its acceptance criteria in the body.
|
|
303
|
+
3. Mark the dependencies between Tickets with GitHub's **blocked by** links. Tickets are built in that order, then in the Feature's own child order. A Ticket is built only when the Signal ready (`ready-for-agent` by default) is on it and everything blocking it is closed or already built on the branch.
|
|
304
|
+
4. Optionally, give a Ticket its own model and effort, in lines in the body:
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
**Model:** opus
|
|
308
|
+
**Effort:** high
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
A Ticket without them uses the defaults from `factory.json`.
|
|
312
|
+
5. Assign the Feature to yourself and add the Go label.
|
|
313
|
+
|
|
314
|
+
On Azure DevOps Boards the same steps read:
|
|
315
|
+
|
|
316
|
+
1. Write the Feature as a work item of the type `featureType` names, with its spec in the Description.
|
|
317
|
+
2. Add its Tickets as **child** work items. The Build order is: Predecessor links first (step 3), then backlog rank (the Stack Rank or Backlog Priority your process keeps), then, for the children nobody has ranked, the order they were linked to the Feature.
|
|
318
|
+
3. Mark the dependencies with **Predecessor** and **Successor** links: a Ticket waits for its Predecessors.
|
|
319
|
+
4. The `**Model:**` and `**Effort:**` lines work in the Markdown and the HTML editor alike.
|
|
320
|
+
5. Assign the Feature to yourself and add the Go tag. Signals are tags, which appear on first use, so there is nothing to create first.
|
|
321
|
+
|
|
322
|
+
A work item counts as closed once Azure DevOps has stamped its Closed Date, which it does when the item reaches a closed state of your process; Nightjar reads that date and never the state ([ADR 0002](docs/adr/0002-signals-are-labels-or-tags-never-states.md)). A known limit follows: a **Removed** work item has no Closed Date, so Nightjar counts it as open. A removed Ticket that still carries the ready Signal is built, and one that blocks others keeps them waiting. Take the ready Signal off a Ticket before you remove it, and the Predecessor links from it.
|
|
323
|
+
|
|
324
|
+
While it works, Nightjar puts the working Signal (`agent-working`) on the Feature. When it cannot build a Ticket it takes the ready Signal off, puts on needs info (`needs-info`) or needs a person (`ready-for-human`), and comments with why. Answer by commenting on the Ticket, put the ready Signal back on it, and give the Go again: the next run continues on the same branch and pull request. Only Trusted comments reach the agent. Nightjar never closes a Ticket and never sets a tracker state.
|
|
325
|
+
|
|
326
|
+
## Run
|
|
327
|
+
|
|
328
|
+
From anywhere inside the repo:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
nightjar doctor
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
| Command | What it does |
|
|
335
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
336
|
+
| `init` | Asks for the Tracker and Code host, writes `factory.json`, creates the Signal labels, checks the logins. See [Set up](#set-up-nightjar-init). |
|
|
337
|
+
| `doctor` | Checks this machine and `factory.json`, lists the Features it would build, builds nothing. Start here, and run it any time something is wrong. `--dry-run` is its old name and still works. |
|
|
338
|
+
| `--once` | Builds the oldest Feature assigned to you with the Go, then exits. |
|
|
339
|
+
| `--watch` | Builds the Features assigned to you with the Go, oldest first, and looks again every 2 minutes. |
|
|
340
|
+
| `<id>` | Builds that Feature now, by its id on the Tracker (`nightjar 42`, `#42`, or `nightjar PROJ-123` from a Tracker module). It refuses a Feature not assigned to you, without the Go, or already carrying the working Signal. |
|
|
341
|
+
| `--use-api-key` | Lets the sessions bill `ANTHROPIC_API_KEY` instead of your Claude plan. |
|
|
342
|
+
| `--profile <name>` | Runs the sessions as that Claude login: `~/.claude-NAME`, a path as the folder itself, or `default` for `~/.claude`. It drops `CLAUDE_CODE_OAUTH_TOKEN`, which would win over the folder. `doctor` and every build name the account, organization, plan and config folder first. |
|
|
343
|
+
| `--model <model>` | The model of every session, over a Ticket's own line and `factory.json`. |
|
|
344
|
+
| `--effort <level>` | The effort of every session, over a Ticket's own line and `factory.json`. |
|
|
345
|
+
|
|
346
|
+
Pick exactly one of `doctor`, `--once`, `--watch` or a Feature id. `--use-api-key`, `--profile`, `--model` and `--effort` combine with any of them. `nightjar create [--dry-run] FILE` makes a Feature from a file ([Write a Feature](#write-a-feature)), and `nightjar contract MODULE` checks an Adapter module.
|
|
347
|
+
|
|
348
|
+
Before building, Nightjar checks the API-key choice, Node, `factory.json`, the Tracker and Code host logins (`gh`, or the Azure DevOps sign-in, and the Azure Repos repo when it is the Code host), that `claude` is installed and signed in, and commit signing, and prints what it found. A build stops at the first check that fails; `doctor` runs every check and lists each result as `ok`, `FAIL` (with how to fix it) or `skip` (when `factory.json` could not be read, so the logins it names are not tried). `doctor` refuses a repo with no check command. Exit code 2 means it refused to start, or a check failed; the message says why.
|
|
349
|
+
|
|
350
|
+
Ctrl-C once stops the running session and hands its Ticket back; Ctrl-C again quits at once. On macOS, Nightjar keeps the machine awake while it runs.
|
|
351
|
+
|
|
352
|
+
## What you get
|
|
353
|
+
|
|
354
|
+
- **One branch** per Feature, `factory/<id>-<title-slug>`, with one commit per Ticket, pushed to `origin` on GitHub, or to the repo `factory.json` names on Azure Repos.
|
|
355
|
+
- **One draft pull request** per Feature. On GitHub each built GitHub issue is placed as `Closes #N`, so GitHub closes it on merge; any other Ticket by its plain reference, `AB#N` for an Azure DevOps work item (which the [Azure Boards app](https://learn.microsoft.com/azure/devops/boards/github/) links once it is installed on the repo) or a Tracker module's own text. On Azure Repos each built Ticket from Azure DevOps Boards in the same organization is attached as a linked work item and named `#N`; a Ticket from anywhere else is named in the description by a link to its page, because Azure Repos reads `#N` as one of its own work items. Azure Repos keeps 4000 characters of a description, so a longer one is cut, never its references. When every Ticket is built and the check passes, the Feature itself is placed as a reference too, the same way, so it closes or links with its Tickets. Nightjar never completes, abandons or publishes the pull request. When the review is on, its summary is the pull request's first comment.
|
|
356
|
+
- **`.factory/status.json`**: what Nightjar is doing now and what it did last, rewritten at every change.
|
|
357
|
+
- **`.factory/runs.jsonl`**: one line per session, with its outcome and cost. Each session's log is in `.factory/logs/`.
|
|
358
|
+
- **Worktrees** under `~/.nightjar/worktrees/`, outside your checkout, which is never touched. Add `.factory/` to the repo's `.gitignore`.
|
|
359
|
+
|
|
360
|
+
## Limits
|
|
361
|
+
|
|
362
|
+
Built in; only the spend per day is yours to set, as `dailyUsd` in [your config.json](#your-own-settings-confignightjarconfigjson):
|
|
363
|
+
|
|
364
|
+
| Limit | Value |
|
|
365
|
+
| ------------------------- | ------------ |
|
|
366
|
+
| Spend per Ticket | $15 |
|
|
367
|
+
| Minutes per Ticket | 60 |
|
|
368
|
+
| Fix rounds per Ticket | 3 |
|
|
369
|
+
| Question rounds per Ticket | 2 |
|
|
370
|
+
| Spend per review | $3 |
|
|
371
|
+
| Minutes per review | 10 |
|
|
372
|
+
| Spend per day | $60, or `dailyUsd` |
|
|
373
|
+
|
|
374
|
+
A Ticket still unclear after its question rounds goes to a person.
|
|
375
|
+
|
|
376
|
+
Also:
|
|
377
|
+
|
|
378
|
+
- Claude Code is the only Coding agent.
|
|
379
|
+
- GitHub Issues and Azure DevOps Boards are the Trackers built in, and GitHub and Azure Repos the Code hosts; others are [custom Adapter modules](#another-tracker-or-code-host).
|
|
380
|
+
- The agent never commits, pushes, switches branches, reaches the network, or runs `gh`. Nightjar does that, and `factory.json` cannot loosen it.
|
|
381
|
+
- One run builds one Feature at a time.
|
|
382
|
+
- On Azure DevOps Boards a Removed work item counts as open, because it has no Closed Date and Nightjar never reads a state (see [Give the Go](#give-the-go)).
|
|
383
|
+
- When a Feature is complete, its draft pull request references the Feature as well as its Tickets: on GitHub `Closes #N` closes it on merge too.
|
|
384
|
+
|
|
385
|
+
## Status line plugin (optional)
|
|
386
|
+
|
|
387
|
+
The package ships `nightjar-status`, a Claude Code plugin that shows Nightjar in the status line (what it builds now, the day's spend) and toasts when it builds a Ticket, opens a pull request or needs a person. It reads `.factory/status.json`, so it works in any session opened in a checkout Nightjar runs in, and Nightjar works the same without it. Load it from the installed package:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
claude --plugin-dir "$(npm root -g)/@nonameolsson/nightjar/plugin/nightjar-status"
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The shape of the file is defined once, in `src/status-shape.ts`, which the package ships beside the plugin.
|
|
394
|
+
|
|
395
|
+
## Develop
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
npm ci
|
|
399
|
+
npm run check # typecheck, lint, format check, every spec, then the build
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
See [CLAUDE.md](CLAUDE.md) for the layout and [docs/adr/](docs/adr/) for the decisions.
|
|
403
|
+
|
|
404
|
+
## Licence
|
|
405
|
+
|
|
406
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Env } from './git.js';
|
|
2
|
+
export declare function profileEnv(profile: string | undefined, env: Env): Env;
|
|
3
|
+
export interface Account {
|
|
4
|
+
readonly loggedIn: boolean;
|
|
5
|
+
readonly email?: string;
|
|
6
|
+
readonly organization?: string;
|
|
7
|
+
readonly plan?: string;
|
|
8
|
+
readonly method?: string;
|
|
9
|
+
readonly folder?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseAccount(json: string): Account | null;
|
|
12
|
+
export declare function accountLine(a: Account): string;
|
package/dist/account.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Which Claude login the sessions use: the environment a profile gives them, and the account `claude auth status --json` names.
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
// The sessions' environment under `profile`: its config folder, and no token, which would win over that folder.
|
|
5
|
+
export function profileEnv(profile, env) {
|
|
6
|
+
if (profile === undefined)
|
|
7
|
+
return env;
|
|
8
|
+
const { CLAUDE_CONFIG_DIR: _dir, CLAUDE_CODE_OAUTH_TOKEN: _token, ...rest } = env;
|
|
9
|
+
if (profile === 'default')
|
|
10
|
+
return rest;
|
|
11
|
+
const home = env.HOME ?? homedir();
|
|
12
|
+
let folder = join(home, `.claude-${profile}`);
|
|
13
|
+
// a value with a slash is the folder itself; `~/` is the home folder
|
|
14
|
+
if (profile.startsWith('~/'))
|
|
15
|
+
folder = join(home, profile.slice(2));
|
|
16
|
+
else if (profile.includes('/'))
|
|
17
|
+
folder = profile;
|
|
18
|
+
return { ...rest, CLAUDE_CONFIG_DIR: folder };
|
|
19
|
+
}
|
|
20
|
+
const text = (value) => typeof value === 'string' && value !== '' ? value : undefined;
|
|
21
|
+
// The account in `claude auth status --json`, which has spelled its fields differently across versions; null when it is not JSON.
|
|
22
|
+
export function parseAccount(json) {
|
|
23
|
+
let value;
|
|
24
|
+
try {
|
|
25
|
+
value = JSON.parse(json);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
31
|
+
return null;
|
|
32
|
+
const f = value;
|
|
33
|
+
const fields = {
|
|
34
|
+
email: text(f.email) ?? text(f.emailAddress),
|
|
35
|
+
organization: text(f.orgName) ?? text(f.organizationName),
|
|
36
|
+
plan: text(f.subscriptionType),
|
|
37
|
+
method: text(f.authMethod),
|
|
38
|
+
folder: text(f.configDirectory)
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
loggedIn: f.loggedIn === true,
|
|
42
|
+
...Object.fromEntries(Object.entries(fields).filter((e) => e[1] !== undefined))
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// The account as one line, leaving out what the claude did not say.
|
|
46
|
+
export function accountLine(a) {
|
|
47
|
+
return [
|
|
48
|
+
a.email,
|
|
49
|
+
a.organization,
|
|
50
|
+
a.plan === undefined ? undefined : `${a.plan} plan`,
|
|
51
|
+
a.method === undefined ? undefined : `${a.method} login`,
|
|
52
|
+
a.folder === undefined ? undefined : `config ${a.folder}`
|
|
53
|
+
]
|
|
54
|
+
.filter((part) => part !== undefined)
|
|
55
|
+
.join(', ');
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CodeHost } from './code-host.js';
|
|
2
|
+
import type { SignalNames } from './signals.js';
|
|
3
|
+
import { type Tracker } from './tracker.js';
|
|
4
|
+
export interface AdapterContext {
|
|
5
|
+
readonly root: string;
|
|
6
|
+
readonly options: Readonly<Record<string, unknown>>;
|
|
7
|
+
readonly signals: SignalNames;
|
|
8
|
+
readonly trusted?: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export type TrackerFactory = (context: AdapterContext) => Tracker | Promise<Tracker>;
|
|
11
|
+
export type CodeHostFactory = (context: AdapterContext) => CodeHost | Promise<CodeHost>;
|
|
12
|
+
export interface ModuleSpec {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly options?: Readonly<Record<string, unknown>> | undefined;
|
|
15
|
+
}
|
|
16
|
+
export declare function importModule(path: string, base: string): Promise<Record<string, unknown>>;
|
|
17
|
+
export declare function trackerFromModule(spec: ModuleSpec, context: Omit<AdapterContext, 'options'>): Promise<Tracker>;
|
|
18
|
+
export declare function codeHostFromModule(spec: ModuleSpec, context: Omit<AdapterContext, 'options'>): Promise<CodeHost>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// A repo's own Adapter, from the module factory.json names (docs/custom-adapters.md); one that falls short is refused at start.
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { CONFIG_FILE, ConfigError } from './config.js';
|
|
5
|
+
import { FEATURE_WRITER_METHODS } from './tracker.js';
|
|
6
|
+
import { why } from './say.js';
|
|
7
|
+
// Typed as records, so a method added to an interface fails to compile until it is listed here.
|
|
8
|
+
const TRACKER_METHODS = {
|
|
9
|
+
me: true,
|
|
10
|
+
assignedFeatures: true,
|
|
11
|
+
feature: true,
|
|
12
|
+
standing: true,
|
|
13
|
+
tickets: true,
|
|
14
|
+
comments: true,
|
|
15
|
+
addSignal: true,
|
|
16
|
+
removeSignal: true,
|
|
17
|
+
comment: true,
|
|
18
|
+
reference: true
|
|
19
|
+
};
|
|
20
|
+
const CODE_HOST_METHODS = {
|
|
21
|
+
push: true,
|
|
22
|
+
openPull: true,
|
|
23
|
+
createDraft: true,
|
|
24
|
+
editBody: true,
|
|
25
|
+
placeReferences: true,
|
|
26
|
+
comment: true
|
|
27
|
+
};
|
|
28
|
+
const TRACKER = {
|
|
29
|
+
field: 'tracker',
|
|
30
|
+
noun: 'Tracker',
|
|
31
|
+
factory: 'createTracker',
|
|
32
|
+
methods: Object.keys(TRACKER_METHODS),
|
|
33
|
+
optional: FEATURE_WRITER_METHODS
|
|
34
|
+
};
|
|
35
|
+
const CODE_HOST = {
|
|
36
|
+
field: 'codeHost',
|
|
37
|
+
noun: 'Code host',
|
|
38
|
+
factory: 'createCodeHost',
|
|
39
|
+
methods: Object.keys(CODE_HOST_METHODS)
|
|
40
|
+
};
|
|
41
|
+
export async function importModule(path, base) {
|
|
42
|
+
return (await import(/* @vite-ignore */ pathToFileURL(resolve(base, path)).href));
|
|
43
|
+
}
|
|
44
|
+
async function build(kind, spec, context) {
|
|
45
|
+
const where = `${CONFIG_FILE}: ${kind.field}:`;
|
|
46
|
+
let exports;
|
|
47
|
+
try {
|
|
48
|
+
exports = await importModule(spec.path, context.root);
|
|
49
|
+
}
|
|
50
|
+
catch (fault) {
|
|
51
|
+
throw new ConfigError(`${where} cannot load ${spec.path}: ${why(fault)}`);
|
|
52
|
+
}
|
|
53
|
+
const factory = exports[kind.factory];
|
|
54
|
+
if (typeof factory !== 'function') {
|
|
55
|
+
throw new ConfigError(`${where} ${spec.path} does not export ${kind.factory}, a function that builds the ${kind.noun}`);
|
|
56
|
+
}
|
|
57
|
+
let built;
|
|
58
|
+
try {
|
|
59
|
+
built = await factory(context);
|
|
60
|
+
}
|
|
61
|
+
catch (fault) {
|
|
62
|
+
throw new ConfigError(`${where} ${kind.factory} in ${spec.path} failed: ${why(fault)}`);
|
|
63
|
+
}
|
|
64
|
+
const have = (built ?? {});
|
|
65
|
+
const missing = kind.methods.filter((name) => typeof have[name] !== 'function');
|
|
66
|
+
if (missing.length > 0) {
|
|
67
|
+
throw new ConfigError(`${where} ${kind.factory} in ${spec.path} returned a ${kind.noun} without these methods: ${missing.join(', ')}`);
|
|
68
|
+
}
|
|
69
|
+
// the optional methods come all together or not at all
|
|
70
|
+
const optional = kind.optional ?? [];
|
|
71
|
+
const lacking = optional.filter((name) => typeof have[name] !== 'function');
|
|
72
|
+
if (lacking.length > 0 && lacking.length < optional.length) {
|
|
73
|
+
throw new ConfigError(`${where} ${kind.factory} in ${spec.path} returned a ${kind.noun} that makes Features without these methods: ${lacking.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
return built;
|
|
76
|
+
}
|
|
77
|
+
export async function trackerFromModule(spec, context) {
|
|
78
|
+
return (await build(TRACKER, spec, { ...context, options: spec.options ?? {} }));
|
|
79
|
+
}
|
|
80
|
+
export async function codeHostFromModule(spec, context) {
|
|
81
|
+
return (await build(CODE_HOST, spec, { ...context, options: spec.options ?? {} }));
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=adapter-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-module.js","sourceRoot":"","sources":["../src/adapter-module.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type AzureDevOpsRequest } from './azure-devops/index.js';
|
|
2
|
+
import type { CodeHost } from './code-host.js';
|
|
3
|
+
import { type Config } from './config.js';
|
|
4
|
+
import { type Env } from './git.js';
|
|
5
|
+
import { type FeatureWriter, type Tracker } from './tracker.js';
|
|
6
|
+
export interface Adapters {
|
|
7
|
+
readonly tracker: Tracker;
|
|
8
|
+
readonly codeHost: CodeHost;
|
|
9
|
+
readonly repo: string;
|
|
10
|
+
readonly checked: (person: string) => string;
|
|
11
|
+
readonly reachCodeHost: () => Promise<void>;
|
|
12
|
+
readonly prepareSignals: () => Promise<readonly string[]>;
|
|
13
|
+
readonly writer: (Tracker & FeatureWriter) | string;
|
|
14
|
+
}
|
|
15
|
+
export declare function makeAdapters(config: Config, root: string, env: Env, request?: AzureDevOpsRequest): Promise<Adapters>;
|