frontier-mcp 0.1.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/README.md +104 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +10 -0
- package/dist/bin.js.map +1 -0
- package/dist/domain.d.ts +230 -0
- package/dist/domain.d.ts.map +1 -0
- package/dist/domain.js +23 -0
- package/dist/domain.js.map +1 -0
- package/dist/edges.d.ts +35 -0
- package/dist/edges.d.ts.map +1 -0
- package/dist/edges.js +61 -0
- package/dist/edges.js.map +1 -0
- package/dist/frontier.d.ts +12 -0
- package/dist/frontier.d.ts.map +1 -0
- package/dist/frontier.js +34 -0
- package/dist/frontier.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +29 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +210 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/driver.d.ts +131 -0
- package/dist/storage/driver.d.ts.map +1 -0
- package/dist/storage/driver.js +43 -0
- package/dist/storage/driver.js.map +1 -0
- package/dist/storage/markdown/create.d.ts +52 -0
- package/dist/storage/markdown/create.d.ts.map +1 -0
- package/dist/storage/markdown/create.js +298 -0
- package/dist/storage/markdown/create.js.map +1 -0
- package/dist/storage/markdown/driver.d.ts +7 -0
- package/dist/storage/markdown/driver.d.ts.map +1 -0
- package/dist/storage/markdown/driver.js +492 -0
- package/dist/storage/markdown/driver.js.map +1 -0
- package/dist/storage/markdown/frontmatter.d.ts +17 -0
- package/dist/storage/markdown/frontmatter.d.ts.map +1 -0
- package/dist/storage/markdown/frontmatter.js +36 -0
- package/dist/storage/markdown/frontmatter.js.map +1 -0
- package/dist/storage/markdown/header-doc.d.ts +56 -0
- package/dist/storage/markdown/header-doc.d.ts.map +1 -0
- package/dist/storage/markdown/header-doc.js +287 -0
- package/dist/storage/markdown/header-doc.js.map +1 -0
- package/dist/storage/markdown/legacy.d.ts +16 -0
- package/dist/storage/markdown/legacy.d.ts.map +1 -0
- package/dist/storage/markdown/legacy.js +152 -0
- package/dist/storage/markdown/legacy.js.map +1 -0
- package/dist/storage/markdown/migrate.d.ts +27 -0
- package/dist/storage/markdown/migrate.d.ts.map +1 -0
- package/dist/storage/markdown/migrate.js +158 -0
- package/dist/storage/markdown/migrate.js.map +1 -0
- package/dist/storage/markdown/serialize.d.ts +37 -0
- package/dist/storage/markdown/serialize.d.ts.map +1 -0
- package/dist/storage/markdown/serialize.js +321 -0
- package/dist/storage/markdown/serialize.js.map +1 -0
- package/dist/storage/markdown/ticket.d.ts +12 -0
- package/dist/storage/markdown/ticket.d.ts.map +1 -0
- package/dist/storage/markdown/ticket.js +97 -0
- package/dist/storage/markdown/ticket.js.map +1 -0
- package/dist/storage/markdown/write.d.ts +42 -0
- package/dist/storage/markdown/write.d.ts.map +1 -0
- package/dist/storage/markdown/write.js +134 -0
- package/dist/storage/markdown/write.js.map +1 -0
- package/dist/tools/create-tickets.d.ts +63 -0
- package/dist/tools/create-tickets.d.ts.map +1 -0
- package/dist/tools/create-tickets.js +161 -0
- package/dist/tools/create-tickets.js.map +1 -0
- package/dist/tools/edit-map.d.ts +29 -0
- package/dist/tools/edit-map.d.ts.map +1 -0
- package/dist/tools/edit-map.js +81 -0
- package/dist/tools/edit-map.js.map +1 -0
- package/dist/tools/get-board.d.ts +29 -0
- package/dist/tools/get-board.d.ts.map +1 -0
- package/dist/tools/get-board.js +190 -0
- package/dist/tools/get-board.js.map +1 -0
- package/dist/tools/get-tickets.d.ts +14 -0
- package/dist/tools/get-tickets.d.ts.map +1 -0
- package/dist/tools/get-tickets.js +56 -0
- package/dist/tools/get-tickets.js.map +1 -0
- package/dist/tools/list-efforts.d.ts +16 -0
- package/dist/tools/list-efforts.d.ts.map +1 -0
- package/dist/tools/list-efforts.js +38 -0
- package/dist/tools/list-efforts.js.map +1 -0
- package/dist/tools/migrate-effort.d.ts +11 -0
- package/dist/tools/migrate-effort.d.ts.map +1 -0
- package/dist/tools/migrate-effort.js +54 -0
- package/dist/tools/migrate-effort.js.map +1 -0
- package/dist/tools/spec.d.ts +12 -0
- package/dist/tools/spec.d.ts.map +1 -0
- package/dist/tools/spec.js +27 -0
- package/dist/tools/spec.js.map +1 -0
- package/dist/tools/update-ticket.d.ts +62 -0
- package/dist/tools/update-ticket.d.ts.map +1 -0
- package/dist/tools/update-ticket.js +164 -0
- package/dist/tools/update-ticket.js.map +1 -0
- package/dist/tracker-doc.d.ts +10 -0
- package/dist/tracker-doc.d.ts.map +1 -0
- package/dist/tracker-doc.js +16 -0
- package/dist/tracker-doc.js.map +1 -0
- package/dist/workspace-index.d.ts +39 -0
- package/dist/workspace-index.d.ts.map +1 -0
- package/dist/workspace-index.js +83 -0
- package/dist/workspace-index.js.map +1 -0
- package/dist/workspace-watcher.d.ts +24 -0
- package/dist/workspace-watcher.d.ts.map +1 -0
- package/dist/workspace-watcher.js +87 -0
- package/dist/workspace-watcher.js.map +1 -0
- package/dist/workspace.d.ts +23 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +70 -0
- package/dist/workspace.js.map +1 -0
- package/docs/agents/issue-tracker.md +141 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frontier-mcp
|
|
2
|
+
|
|
3
|
+
An MCP server that serves the markdown issue tracker under `.scratch/` as a queryable graph — so agents
|
|
4
|
+
stop re-parsing prose to learn what is open, blocked, or takeable.
|
|
5
|
+
|
|
6
|
+
## Requirements
|
|
7
|
+
|
|
8
|
+
- Node 24 or later
|
|
9
|
+
- npx to run it; pnpm for development of this package
|
|
10
|
+
|
|
11
|
+
## Install once (user scope)
|
|
12
|
+
|
|
13
|
+
Register FrontierMCP once in your **user-level** MCP config, pinned to a released version. Every
|
|
14
|
+
repository you open then gets the server automatically — no per-repo `mcp.json` file.
|
|
15
|
+
|
|
16
|
+
In Cursor, edit `~/.cursor/mcp.json` (create the file if it does not exist); other MCP clients take the
|
|
17
|
+
same command and arguments in their own user-scope server list:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"frontier": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "frontier-mcp@x.y.z"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Replace `x.y.z` with a published version — see the
|
|
31
|
+
[releases](https://github.com/51ck/frontier-mcp/releases). The pin is the version you get; nothing
|
|
32
|
+
bumps it for you.
|
|
33
|
+
|
|
34
|
+
Restart your editor after saving.
|
|
35
|
+
|
|
36
|
+
## First use in a repository
|
|
37
|
+
|
|
38
|
+
1. Open the repository in your editor. FrontierMCP resolves the workspace from the session working
|
|
39
|
+
directory — walking upward to the nearest `.scratch/` or `.git/` — so opening the project is the
|
|
40
|
+
only setup step.
|
|
41
|
+
2. Read the tracker configuration document once: fetch MCP resource `frontier://tracker-doc`, or read
|
|
42
|
+
[`docs/agents/issue-tracker.md`](docs/agents/issue-tracker.md) in a repo that vendors it.
|
|
43
|
+
3. Call `list_efforts` to see whether `.scratch/` exists yet.
|
|
44
|
+
4. Call `get_board` on an Effort to see the Frontier, then `get_tickets` only for the ids you work.
|
|
45
|
+
|
|
46
|
+
A repository with no `.scratch/` directory is not an error. Create the first Effort with
|
|
47
|
+
`create_tickets`, `edit_map`, or `spec` and `create: true`.
|
|
48
|
+
|
|
49
|
+
## Override the workspace
|
|
50
|
+
|
|
51
|
+
Optional, per call or per session:
|
|
52
|
+
|
|
53
|
+
- Pass `root` on any tool call to read another directory.
|
|
54
|
+
- Set `FRONTIER_ROOT` in the server environment for a non-standard layout.
|
|
55
|
+
|
|
56
|
+
## Tools
|
|
57
|
+
|
|
58
|
+
Eight tools: `list_efforts`, `get_board`, `get_tickets`, `create_tickets`, `update_ticket`,
|
|
59
|
+
`edit_map`, `spec`, `migrate_effort`. See the tracker configuration document for when to use each.
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pnpm install
|
|
65
|
+
pnpm test
|
|
66
|
+
pnpm run check
|
|
67
|
+
pnpm run build
|
|
68
|
+
node src/bin.ts
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Releasing
|
|
72
|
+
|
|
73
|
+
Publishing is CI-only via [release-it](https://github.com/release-it/release-it). There is no local
|
|
74
|
+
publish script, and adding one would mean minting the long-lived npm token that the CI setup exists
|
|
75
|
+
to avoid.
|
|
76
|
+
|
|
77
|
+
1. Merge the work you want to ship to `master`.
|
|
78
|
+
2. On GitHub: **Actions → Release → Run workflow**, pick `patch` / `minor` / `major`.
|
|
79
|
+
3. The workflow runs checks + tests, bumps `package.json`, updates `CHANGELOG.md`, tags
|
|
80
|
+
`v*`, creates a GitHub Release, and publishes to npm with `pnpm`.
|
|
81
|
+
4. Bump the pinned version in your user MCP config (`frontier-mcp@x.y.z`) when you want the
|
|
82
|
+
new build — pins stay manual on purpose.
|
|
83
|
+
|
|
84
|
+
The workflow is dispatchable from any branch but refuses to run off `master`: release-it commits,
|
|
85
|
+
tags and pushes before it publishes, so a release from a feature branch would rewrite that branch.
|
|
86
|
+
|
|
87
|
+
Local dry-run (no tag, no publish). It needs a clean working tree and an upstream branch, so commit
|
|
88
|
+
first:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pnpm run release:dry
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
npm publishing uses **Trusted Publishing** — GitHub Actions OIDC, configured for this repo on
|
|
95
|
+
npmjs.com, so there is no `NPM_TOKEN` to rotate.
|
|
96
|
+
|
|
97
|
+
Before changing `.release-it.json` or the workflow, read the release-it bullet in
|
|
98
|
+
[AGENTS.md](./AGENTS.md) Work Guidance. Several settings there look removable and are not — the
|
|
99
|
+
absent `registry-url`, `npm.skipChecks`, and the pnpm 10 pin each exist for a reason recorded in one
|
|
100
|
+
place so it cannot drift.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createFrontierMCP } from './server.js';
|
|
4
|
+
const frontier = createFrontierMCP();
|
|
5
|
+
// The index is built by a full scan before the transport connects, so the first
|
|
6
|
+
// call is served warm. A workspace that cannot be scanned yet is not fatal:
|
|
7
|
+
// every call resolves its own workspace regardless.
|
|
8
|
+
await frontier.warmUp().catch(() => { });
|
|
9
|
+
await frontier.server.connect(new StdioServerTransport());
|
|
10
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;AAErC,gFAAgF;AAChF,4EAA4E;AAC5E,oDAAoD;AACpD,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AAExC,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC"}
|
package/dist/domain.d.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary from CONTEXT.md, as types. Everything above the storage driver
|
|
3
|
+
* speaks in these terms.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Which Header doc an Effort holds. An Effort has zero, one, or two — a Map, a
|
|
7
|
+
* Spec, or a Map that later gained a Spec.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately not named for a "kind": CONTEXT.md reserves Kind for a Ticket's
|
|
10
|
+
* `build`/`decision` shape.
|
|
11
|
+
*/
|
|
12
|
+
export type HeaderDoc = 'map' | 'spec';
|
|
13
|
+
/** Which of the two shapes a Ticket is. */
|
|
14
|
+
export type Kind = 'build' | 'decision';
|
|
15
|
+
/**
|
|
16
|
+
* The shape of a minted id: `T<n>`, visibly an id so it is never mistaken for a
|
|
17
|
+
* sort position. Ids preserved verbatim through migration need not match — those
|
|
18
|
+
* occupy their name without being part of the counter.
|
|
19
|
+
*
|
|
20
|
+
* Both sides of the storage seam test against this, so it lives with the rest of
|
|
21
|
+
* the vocabulary rather than in either of them.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MINTED_ID: RegExp;
|
|
24
|
+
/** A Ticket's position in the graph's lifecycle. Owned by the server. */
|
|
25
|
+
export type Status = 'open' | 'claimed' | 'resolved' | 'dropped';
|
|
26
|
+
export declare const STATUSES: readonly Status[];
|
|
27
|
+
/**
|
|
28
|
+
* A Ticket without its body — everything a Board line needs. Bodies are fetched
|
|
29
|
+
* separately, so a Board never pays for prose it does not show.
|
|
30
|
+
*/
|
|
31
|
+
export interface TicketSummary {
|
|
32
|
+
/** `T<n>`, unique repo-wide. Absent on a Legacy Ticket that carries no id. */
|
|
33
|
+
readonly id: string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* How to name this Ticket in a call. The id when it has one; otherwise
|
|
36
|
+
* `<effort>#<order>`, which is the only handle an id-less Legacy Ticket has
|
|
37
|
+
* until migration mints it a real one.
|
|
38
|
+
*/
|
|
39
|
+
readonly handle: string;
|
|
40
|
+
readonly title: string;
|
|
41
|
+
readonly kind: Kind;
|
|
42
|
+
/** `research` / `prototype` / `grilling` / `task`. Only when `kind` is `decision`. */
|
|
43
|
+
readonly type: string | undefined;
|
|
44
|
+
readonly status: Status;
|
|
45
|
+
/** A `/triage` label. A separate field from {@link status}. */
|
|
46
|
+
readonly triage: string | undefined;
|
|
47
|
+
/** Required when resolved — the one line that makes a Board of finished work readable. */
|
|
48
|
+
readonly answerGist: string | undefined;
|
|
49
|
+
/** Required when dropped. What the Map's Out-of-scope section renders. */
|
|
50
|
+
readonly droppedReason: string | undefined;
|
|
51
|
+
/** Who holds the claim, and when they took it. Set together. */
|
|
52
|
+
readonly claimedBy: string | undefined;
|
|
53
|
+
readonly claimedAt: string | undefined;
|
|
54
|
+
/** Edges: the Tickets that must finish first, as plain ids resolved repo-wide. */
|
|
55
|
+
readonly blockedBy: readonly string[];
|
|
56
|
+
/** The slug of the Effort that owns this Ticket. */
|
|
57
|
+
readonly effort: string;
|
|
58
|
+
/** Sort order within its Effort. Order only — never identity. */
|
|
59
|
+
readonly order: number;
|
|
60
|
+
/**
|
|
61
|
+
* A Ticket predating the schema, parsed best-effort. Its title, status, and
|
|
62
|
+
* Edges are inferences an agent should distrust.
|
|
63
|
+
*/
|
|
64
|
+
readonly legacy: boolean;
|
|
65
|
+
/** The raw `Status:` text of a Legacy Ticket whose value did not map. */
|
|
66
|
+
readonly unrecognizedStatus: string | undefined;
|
|
67
|
+
/** Sub-slice references (`T38.1`) coarsened to the Ticket that exists. */
|
|
68
|
+
readonly collapsedRefs: readonly string[];
|
|
69
|
+
/**
|
|
70
|
+
* An opaque token for what was read. A write passes back the one it read and
|
|
71
|
+
* is refused if the stored Ticket has moved on. The markdown driver builds it
|
|
72
|
+
* from the file's modification time and size; another driver would use
|
|
73
|
+
* whatever its store offers, so nothing above the seam may parse it.
|
|
74
|
+
*/
|
|
75
|
+
readonly revision: string;
|
|
76
|
+
}
|
|
77
|
+
/** A Ticket with its prose. */
|
|
78
|
+
export interface Ticket extends TicketSummary {
|
|
79
|
+
readonly body: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* One Effort: enough to choose one without opening it. Frontier size is not a
|
|
83
|
+
* field — it is computed from the Tickets, like the Frontier itself.
|
|
84
|
+
*/
|
|
85
|
+
export interface Effort {
|
|
86
|
+
/** The Effort's identity. Unique within a workspace. */
|
|
87
|
+
readonly slug: string;
|
|
88
|
+
/** Which Header docs the Effort holds, `map` before `spec`. */
|
|
89
|
+
readonly headerDocs: readonly HeaderDoc[];
|
|
90
|
+
/** How many Tickets the Effort holds. */
|
|
91
|
+
readonly ticketCount: number;
|
|
92
|
+
/**
|
|
93
|
+
* Where this line of enquiry is going, from the Map's Destination. Absent
|
|
94
|
+
* when the Effort has no Map, or a Map that has not been given one.
|
|
95
|
+
*/
|
|
96
|
+
readonly destination: string | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* The opening of the Effort's Spec. Not a Destination — a Spec has no such
|
|
99
|
+
* section — but it is the orienting text a Spec-only Effort does have, and a
|
|
100
|
+
* Board that opens with nothing leaves every Ticket line below uninterpretable.
|
|
101
|
+
*/
|
|
102
|
+
readonly specOpening: string | undefined;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The mutation points on a Ticket, in the vocabulary rather than the storage
|
|
106
|
+
* format. A field left out is left alone; `null` clears one that was set.
|
|
107
|
+
*
|
|
108
|
+
* `answer` is a body mutation point rather than a field — a Ticket body is
|
|
109
|
+
* opaque apart from the three places the schema says are edited.
|
|
110
|
+
*/
|
|
111
|
+
export interface TicketEdit {
|
|
112
|
+
readonly status?: Status;
|
|
113
|
+
readonly triage?: TriageRole;
|
|
114
|
+
readonly claimedBy?: string | null;
|
|
115
|
+
readonly claimedAt?: string | null;
|
|
116
|
+
readonly answerGist?: string | null;
|
|
117
|
+
readonly droppedReason?: string | null;
|
|
118
|
+
readonly answer?: string;
|
|
119
|
+
/** Replaces the Edge list outright. An empty list clears it. */
|
|
120
|
+
readonly blockedBy?: readonly string[];
|
|
121
|
+
/** Appended to the comment log, verbatim. Nothing is added around it. */
|
|
122
|
+
readonly comment?: string;
|
|
123
|
+
/** Acceptance criteria to tick, addressed by their text. */
|
|
124
|
+
readonly tick?: readonly string[];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A Ticket asked for but not yet minted an id.
|
|
128
|
+
*
|
|
129
|
+
* `key` is the caller's own temporary name for this draft, and exists only for
|
|
130
|
+
* the length of the call: sibling drafts declare Edges on it before any id
|
|
131
|
+
* exists, which is what removes the create-then-wire second pass. It is never
|
|
132
|
+
* stored, and never appears in a file.
|
|
133
|
+
*/
|
|
134
|
+
export interface TicketDraft {
|
|
135
|
+
/**
|
|
136
|
+
* The caller's temporary name. Never `T<n>` — a key shaped like an id could
|
|
137
|
+
* not be told apart from the real one an Edge might mean.
|
|
138
|
+
*/
|
|
139
|
+
readonly key: string | undefined;
|
|
140
|
+
readonly title: string;
|
|
141
|
+
readonly kind: Kind;
|
|
142
|
+
readonly type: string | undefined;
|
|
143
|
+
readonly triage: TriageRole | undefined;
|
|
144
|
+
/** Edges, each either a real Ticket id or a sibling draft's {@link key}. */
|
|
145
|
+
readonly blockedBy: readonly string[];
|
|
146
|
+
/** The prose, written verbatim. A draft with none produces a Ticket with none. */
|
|
147
|
+
readonly body: string | undefined;
|
|
148
|
+
}
|
|
149
|
+
/** The five roles `/triage` writes. A Status is never one of these. */
|
|
150
|
+
export declare const TRIAGE_ROLES: readonly ['needs-triage', 'needs-info', 'ready-for-agent', 'ready-for-human', 'wontfix'];
|
|
151
|
+
export type TriageRole = (typeof TRIAGE_ROLES)[number];
|
|
152
|
+
/**
|
|
153
|
+
* A Map's typed sections, without Decisions-so-far — that section is derived
|
|
154
|
+
* from resolved Tickets and never read from the file.
|
|
155
|
+
*/
|
|
156
|
+
export interface MapDocument {
|
|
157
|
+
readonly destination: string | undefined;
|
|
158
|
+
readonly notes: string | undefined;
|
|
159
|
+
/** Fog patches under Not yet specified, in file order. */
|
|
160
|
+
readonly notYetSpecified: readonly string[];
|
|
161
|
+
/** Hand-ruled Out-of-scope items. Dropped Tickets are not here — they render. */
|
|
162
|
+
readonly outOfScope: readonly string[];
|
|
163
|
+
readonly revision: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A section mutation on a Map. A field left out is left alone. Decisions-so-far
|
|
167
|
+
* is never a field — it is regenerated from Tickets on every write.
|
|
168
|
+
*/
|
|
169
|
+
export interface MapEdit {
|
|
170
|
+
readonly destination?: string;
|
|
171
|
+
readonly notes?: string;
|
|
172
|
+
/** Append a fog patch to Not yet specified. */
|
|
173
|
+
readonly addFog?: string;
|
|
174
|
+
/** Remove a fog patch matched on its text. Fails when none match. */
|
|
175
|
+
readonly graduateFog?: string;
|
|
176
|
+
/** Append a hand-ruled item to Out of scope. */
|
|
177
|
+
readonly ruleOut?: string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* A Spec as an opaque document. Nothing edits it section by section; the driver
|
|
181
|
+
* stores the bytes and the tool layer may present them with framing the store
|
|
182
|
+
* itself does not know about.
|
|
183
|
+
*/
|
|
184
|
+
export interface SpecDocument {
|
|
185
|
+
/** Opaque Spec document as stored. */
|
|
186
|
+
readonly body: string;
|
|
187
|
+
readonly revision: string;
|
|
188
|
+
}
|
|
189
|
+
/** How a migration call may rewrite filenames. */
|
|
190
|
+
export interface MigrateOptions {
|
|
191
|
+
/**
|
|
192
|
+
* Report every change and write nothing. Planned mint ids come from the
|
|
193
|
+
* current scan (`max + 1`) without taking allocation guards.
|
|
194
|
+
*/
|
|
195
|
+
readonly preview: boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Rename Ticket files to `<NN>-T<n>-<slug>.md`. Off by default so relative
|
|
198
|
+
* links in Maps and Tickets keep resolving.
|
|
199
|
+
*/
|
|
200
|
+
readonly rename: boolean;
|
|
201
|
+
}
|
|
202
|
+
/** One Ticket a migration would touch, or did. */
|
|
203
|
+
export interface MigrationChange {
|
|
204
|
+
/** Handle before the call — the id when it had one, else `<effort>#<order>`. */
|
|
205
|
+
readonly beforeHandle: string;
|
|
206
|
+
/** Id after migration — preserved or newly minted. */
|
|
207
|
+
readonly id: string;
|
|
208
|
+
readonly title: string;
|
|
209
|
+
/** True when this call (or preview) assigns a fresh id. */
|
|
210
|
+
readonly minted: boolean;
|
|
211
|
+
/** True when the file was Legacy and gains schema frontmatter. */
|
|
212
|
+
readonly normalized: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Cosmetic Ticket filenames (basenames only) when rename would move the file.
|
|
215
|
+
* Absent when filenames stay put. Not filesystem paths — those stay below the
|
|
216
|
+
* storage seam.
|
|
217
|
+
*/
|
|
218
|
+
readonly fromName: string | undefined;
|
|
219
|
+
readonly toName: string | undefined;
|
|
220
|
+
/** Edges as they will be written after bare sort-order remapping. */
|
|
221
|
+
readonly blockedBy: readonly string[];
|
|
222
|
+
}
|
|
223
|
+
/** The result of migrating one Effort — enough for the tool to render a report. */
|
|
224
|
+
export interface MigrationReport {
|
|
225
|
+
readonly effort: string;
|
|
226
|
+
readonly preview: boolean;
|
|
227
|
+
readonly renamed: boolean;
|
|
228
|
+
readonly changes: readonly MigrationChange[];
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=domain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"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"}
|
package/dist/domain.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary from CONTEXT.md, as types. Everything above the storage driver
|
|
3
|
+
* speaks in these terms.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The shape of a minted id: `T<n>`, visibly an id so it is never mistaken for a
|
|
7
|
+
* sort position. Ids preserved verbatim through migration need not match — those
|
|
8
|
+
* occupy their name without being part of the counter.
|
|
9
|
+
*
|
|
10
|
+
* Both sides of the storage seam test against this, so it lives with the rest of
|
|
11
|
+
* the vocabulary rather than in either of them.
|
|
12
|
+
*/
|
|
13
|
+
export const MINTED_ID = /^T(\d+)$/;
|
|
14
|
+
export const STATUSES = ['open', 'claimed', 'resolved', 'dropped'];
|
|
15
|
+
/** The five roles `/triage` writes. A Status is never one of these. */
|
|
16
|
+
export const TRIAGE_ROLES = [
|
|
17
|
+
'needs-triage',
|
|
18
|
+
'needs-info',
|
|
19
|
+
'ready-for-agent',
|
|
20
|
+
'ready-for-human',
|
|
21
|
+
'wontfix',
|
|
22
|
+
];
|
|
23
|
+
//# sourceMappingURL=domain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.js","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,UAAU,CAAC;AAKpC,MAAM,CAAC,MAAM,QAAQ,GAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;AAiItF,uEAAuE;AACvE,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,cAAc;IACd,YAAY;IACZ,iBAAiB;IACjB,iBAAiB;IACjB,SAAS;CACD,CAAC"}
|
package/dist/edges.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-graph validation, above the storage seam because a cycle is a rule about
|
|
3
|
+
* the domain rather than about storage — the same reason the Status model is
|
|
4
|
+
* checked in the tool layer.
|
|
5
|
+
*/
|
|
6
|
+
import type { TicketDraft } from './domain.ts';
|
|
7
|
+
/**
|
|
8
|
+
* How an Edge on a batch of drafts reads once the ids are real: a temporary key
|
|
9
|
+
* becomes the id its draft was given, and anything else is already an id.
|
|
10
|
+
*
|
|
11
|
+
* Both sides of the seam need this and must agree — the tool layer resolves to
|
|
12
|
+
* check for cycles, the driver resolves to write the frontmatter, and a graph
|
|
13
|
+
* approved under one resolution and written under another would be neither.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveDraftEdges(drafts: readonly TicketDraft[], ids: readonly string[]): (edge: string) => string;
|
|
16
|
+
/** The Edges leaving one node, by id. A node nothing knows about has none. */
|
|
17
|
+
export type EdgesOf = (id: string) => readonly string[];
|
|
18
|
+
/**
|
|
19
|
+
* The cycle running through `node`, as the path back to itself, or undefined
|
|
20
|
+
* when there is none.
|
|
21
|
+
*
|
|
22
|
+
* A cycle is rejected at write time because it makes the Frontier undefined:
|
|
23
|
+
* nothing in one is ever takeable, and no reader can tell which Edge was the
|
|
24
|
+
* mistake. It is checked on creation and on Edge updates alike, since the
|
|
25
|
+
* second is the easier way to close a loop.
|
|
26
|
+
*
|
|
27
|
+
* Only a cycle through `node` counts. A cycle elsewhere in the workspace is
|
|
28
|
+
* somebody's hand edit and not this caller's to answer for — refusing it here
|
|
29
|
+
* would make an unrelated Effort unwritable, and the Board already reports the
|
|
30
|
+
* graph damage it causes.
|
|
31
|
+
*/
|
|
32
|
+
export declare function cycleThrough(node: string, edgesOf: EdgesOf): readonly string[] | undefined;
|
|
33
|
+
/** How a refused cycle reads: the loop, in the direction the Edges point. */
|
|
34
|
+
export declare function renderCycle(cycle: readonly string[]): string;
|
|
35
|
+
//# sourceMappingURL=edges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edges.d.ts","sourceRoot":"","sources":["../src/edges.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,GAAG,EAAE,SAAS,MAAM,EAAE,GACrB,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAS1B;AAED,8EAA8E;AAC9E,MAAM,MAAM,OAAO,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAoB1F;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAE5D"}
|
package/dist/edges.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-graph validation, above the storage seam because a cycle is a rule about
|
|
3
|
+
* the domain rather than about storage — the same reason the Status model is
|
|
4
|
+
* checked in the tool layer.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* How an Edge on a batch of drafts reads once the ids are real: a temporary key
|
|
8
|
+
* becomes the id its draft was given, and anything else is already an id.
|
|
9
|
+
*
|
|
10
|
+
* Both sides of the seam need this and must agree — the tool layer resolves to
|
|
11
|
+
* check for cycles, the driver resolves to write the frontmatter, and a graph
|
|
12
|
+
* approved under one resolution and written under another would be neither.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveDraftEdges(drafts, ids) {
|
|
15
|
+
const byKey = new Map();
|
|
16
|
+
drafts.forEach((draft, index) => {
|
|
17
|
+
const id = ids[index];
|
|
18
|
+
if (draft.key !== undefined && id !== undefined)
|
|
19
|
+
byKey.set(draft.key, id);
|
|
20
|
+
});
|
|
21
|
+
return edge => byKey.get(edge) ?? edge;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The cycle running through `node`, as the path back to itself, or undefined
|
|
25
|
+
* when there is none.
|
|
26
|
+
*
|
|
27
|
+
* A cycle is rejected at write time because it makes the Frontier undefined:
|
|
28
|
+
* nothing in one is ever takeable, and no reader can tell which Edge was the
|
|
29
|
+
* mistake. It is checked on creation and on Edge updates alike, since the
|
|
30
|
+
* second is the easier way to close a loop.
|
|
31
|
+
*
|
|
32
|
+
* Only a cycle through `node` counts. A cycle elsewhere in the workspace is
|
|
33
|
+
* somebody's hand edit and not this caller's to answer for — refusing it here
|
|
34
|
+
* would make an unrelated Effort unwritable, and the Board already reports the
|
|
35
|
+
* graph damage it causes.
|
|
36
|
+
*/
|
|
37
|
+
export function cycleThrough(node, edgesOf) {
|
|
38
|
+
// Reachability is monotone, so a node explored once and found not to reach
|
|
39
|
+
// `node` never will. That also stops a pre-existing cycle downstream from
|
|
40
|
+
// spinning forever.
|
|
41
|
+
const explored = new Set([node]);
|
|
42
|
+
const walk = (from, trail) => {
|
|
43
|
+
for (const next of edgesOf(from)) {
|
|
44
|
+
if (next === node)
|
|
45
|
+
return [...trail, node];
|
|
46
|
+
if (explored.has(next))
|
|
47
|
+
continue;
|
|
48
|
+
explored.add(next);
|
|
49
|
+
const found = walk(next, [...trail, next]);
|
|
50
|
+
if (found !== undefined)
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
55
|
+
return walk(node, [node]);
|
|
56
|
+
}
|
|
57
|
+
/** How a refused cycle reads: the loop, in the direction the Edges point. */
|
|
58
|
+
export function renderCycle(cycle) {
|
|
59
|
+
return cycle.join(' -> ');
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=edges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edges.js","sourceRoot":"","sources":["../src/edges.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA8B,EAC9B,GAAsB;IAEtB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IAExC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC9B,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;YAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AACzC,CAAC;AAKD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAAgB;IACzD,2EAA2E;IAC3E,0EAA0E;IAC1E,oBAAoB;IACpB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAEzC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,KAAwB,EAAiC,EAAE;QACrF,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC;YAC3C,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YACjC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAEnB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;YAC3C,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAC;QACxC,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IAEF,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,WAAW,CAAC,KAAwB;IAClD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TicketSummary } from './domain.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The Frontier: the Tickets takeable right now — open, unblocked, unclaimed.
|
|
4
|
+
* Computed on every read, never stored, because a stored frontier is a second
|
|
5
|
+
* place for the truth to live.
|
|
6
|
+
*
|
|
7
|
+
* Edges resolve repo-wide, so this takes every Ticket in the workspace rather
|
|
8
|
+
* than one Effort's: a Ticket can be blocked by one that lives elsewhere.
|
|
9
|
+
*/
|
|
10
|
+
export declare function frontierOf(all: readonly TicketSummary[]): ReadonlySet<TicketSummary>;
|
|
11
|
+
export declare function indexById(all: readonly TicketSummary[]): ReadonlyMap<string, TicketSummary>;
|
|
12
|
+
//# sourceMappingURL=frontier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontier.d.ts","sourceRoot":"","sources":["../src/frontier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,SAAS,aAAa,EAAE,GAAG,WAAW,CAAC,aAAa,CAAC,CAIpF;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,SAAS,aAAa,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAU3F"}
|
package/dist/frontier.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Frontier: the Tickets takeable right now — open, unblocked, unclaimed.
|
|
3
|
+
* Computed on every read, never stored, because a stored frontier is a second
|
|
4
|
+
* place for the truth to live.
|
|
5
|
+
*
|
|
6
|
+
* Edges resolve repo-wide, so this takes every Ticket in the workspace rather
|
|
7
|
+
* than one Effort's: a Ticket can be blocked by one that lives elsewhere.
|
|
8
|
+
*/
|
|
9
|
+
export function frontierOf(all) {
|
|
10
|
+
const byId = indexById(all);
|
|
11
|
+
return new Set(all.filter(ticket => isTakeable(ticket, byId)));
|
|
12
|
+
}
|
|
13
|
+
export function indexById(all) {
|
|
14
|
+
const byId = new Map();
|
|
15
|
+
for (const ticket of all) {
|
|
16
|
+
// A Legacy Ticket may carry no id at all, and the first id wins so that a
|
|
17
|
+
// duplicate cannot silently displace the Ticket already answering to it.
|
|
18
|
+
if (ticket.id !== undefined && !byId.has(ticket.id))
|
|
19
|
+
byId.set(ticket.id, ticket);
|
|
20
|
+
}
|
|
21
|
+
return byId;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Status and Edges decide this, and nothing else. A Triage role never does:
|
|
25
|
+
* `/triage` and the graph must not compete for one field, so a Ticket labelled
|
|
26
|
+
* `wontfix` still counts as takeable here and the Board says so in its
|
|
27
|
+
* warnings, leaving the judgement to the reader.
|
|
28
|
+
*/
|
|
29
|
+
function isTakeable(ticket, byId) {
|
|
30
|
+
if (ticket.status !== 'open')
|
|
31
|
+
return false;
|
|
32
|
+
return ticket.blockedBy.every(id => byId.get(id)?.status === 'resolved');
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=frontier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontier.js","sourceRoot":"","sources":["../src/frontier.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,GAA6B;IACtD,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAE5B,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAA6B;IACrD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE9C,KAAK,MAAM,MAAM,IAAI,GAAG,EAAE,CAAC;QACzB,0EAA0E;QAC1E,yEAAyE;QACzE,IAAI,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACnF,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,MAAqB,EAAE,IAAwC;IACjF,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAE3C,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC;AAC3E,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createFrontierMCP, SERVER_NAME, SERVER_VERSION } from './server.ts';
|
|
2
|
+
export type { CreateServerOptions, FrontierMCP } from './server.ts';
|
|
3
|
+
export type { Effort, HeaderDoc } from './domain.ts';
|
|
4
|
+
export type { StorageDriver } from './storage/driver.ts';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACpE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { StorageDriver } from './storage/driver.ts';
|
|
3
|
+
export declare const SERVER_NAME = "frontier";
|
|
4
|
+
export declare const SERVER_VERSION = "0.1.0";
|
|
5
|
+
export interface CreateServerOptions {
|
|
6
|
+
/** The server process's working directory. */
|
|
7
|
+
readonly cwd?: string;
|
|
8
|
+
readonly env?: Readonly<Partial<Record<string, string>>>;
|
|
9
|
+
/**
|
|
10
|
+
* The storage driver to bind to a workspace. Markdown in v1; the seam is here
|
|
11
|
+
* so a SQLite driver can be swapped in without touching the tool layer.
|
|
12
|
+
*/
|
|
13
|
+
readonly createDriver?: (root: string) => StorageDriver;
|
|
14
|
+
/** Debounce filesystem watcher events. Tests may shorten this. */
|
|
15
|
+
readonly watcherDebounceMs?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface FrontierMCP {
|
|
18
|
+
readonly server: McpServer;
|
|
19
|
+
/**
|
|
20
|
+
* Build the index for the session's own workspace. Calls resolve their own
|
|
21
|
+
* workspace anyway, so a failure here is not fatal — it only forfeits the
|
|
22
|
+
* warm start.
|
|
23
|
+
*/
|
|
24
|
+
warmUp(): Promise<void>;
|
|
25
|
+
/** Stop watchers and close the transport. */
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare function createFrontierMCP(options?: CreateServerOptions): FrontierMCP;
|
|
29
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMpE,OAAO,KAAK,EAAE,aAAa,EAAqB,MAAM,qBAAqB,CAAC;AAwC5E,eAAO,MAAM,WAAW,aAAa,CAAC;AACtC,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACzD;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,CAAC;IACxD,kEAAkE;IAClE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B;;;;OAIG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,6CAA6C;IAC7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAOD,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,mBAAwB,GAAG,WAAW,CAmRhF"}
|