frontier-mcp 0.5.0 → 1.0.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.
Files changed (85) hide show
  1. package/README.md +66 -15
  2. package/dist/domain.d.ts +69 -25
  3. package/dist/domain.d.ts.map +1 -1
  4. package/dist/domain.js +9 -0
  5. package/dist/domain.js.map +1 -1
  6. package/dist/frontier.d.ts +8 -0
  7. package/dist/frontier.d.ts.map +1 -1
  8. package/dist/frontier.js +33 -4
  9. package/dist/frontier.js.map +1 -1
  10. package/dist/server.d.ts.map +1 -1
  11. package/dist/server.js +26 -9
  12. package/dist/server.js.map +1 -1
  13. package/dist/storage/driver.d.ts +61 -6
  14. package/dist/storage/driver.d.ts.map +1 -1
  15. package/dist/storage/driver.js +67 -5
  16. package/dist/storage/driver.js.map +1 -1
  17. package/dist/storage/markdown/create.d.ts +2 -3
  18. package/dist/storage/markdown/create.d.ts.map +1 -1
  19. package/dist/storage/markdown/create.js +2 -11
  20. package/dist/storage/markdown/create.js.map +1 -1
  21. package/dist/storage/markdown/driver.d.ts.map +1 -1
  22. package/dist/storage/markdown/driver.js +155 -36
  23. package/dist/storage/markdown/driver.js.map +1 -1
  24. package/dist/storage/markdown/frontmatter.js +1 -1
  25. package/dist/storage/markdown/header-doc.d.ts.map +1 -1
  26. package/dist/storage/markdown/header-doc.js +13 -11
  27. package/dist/storage/markdown/header-doc.js.map +1 -1
  28. package/dist/storage/markdown/legacy.d.ts +8 -4
  29. package/dist/storage/markdown/legacy.d.ts.map +1 -1
  30. package/dist/storage/markdown/legacy.js +20 -15
  31. package/dist/storage/markdown/legacy.js.map +1 -1
  32. package/dist/storage/markdown/migrate.d.ts +13 -3
  33. package/dist/storage/markdown/migrate.d.ts.map +1 -1
  34. package/dist/storage/markdown/migrate.js +69 -79
  35. package/dist/storage/markdown/migrate.js.map +1 -1
  36. package/dist/storage/markdown/schema.d.ts +9 -0
  37. package/dist/storage/markdown/schema.d.ts.map +1 -0
  38. package/dist/storage/markdown/schema.js +21 -0
  39. package/dist/storage/markdown/schema.js.map +1 -0
  40. package/dist/storage/markdown/serialize.d.ts +1 -1
  41. package/dist/storage/markdown/serialize.d.ts.map +1 -1
  42. package/dist/storage/markdown/serialize.js +110 -24
  43. package/dist/storage/markdown/serialize.js.map +1 -1
  44. package/dist/storage/markdown/ticket.d.ts +29 -4
  45. package/dist/storage/markdown/ticket.d.ts.map +1 -1
  46. package/dist/storage/markdown/ticket.js +102 -30
  47. package/dist/storage/markdown/ticket.js.map +1 -1
  48. package/dist/storage/markdown/watcher.d.ts +1 -1
  49. package/dist/storage/markdown/watcher.d.ts.map +1 -1
  50. package/dist/storage/markdown/watcher.js +91 -11
  51. package/dist/storage/markdown/watcher.js.map +1 -1
  52. package/dist/tools/create-tickets.d.ts +12 -1
  53. package/dist/tools/create-tickets.d.ts.map +1 -1
  54. package/dist/tools/create-tickets.js +23 -6
  55. package/dist/tools/create-tickets.js.map +1 -1
  56. package/dist/tools/edit-map.d.ts.map +1 -1
  57. package/dist/tools/edit-map.js +6 -2
  58. package/dist/tools/edit-map.js.map +1 -1
  59. package/dist/tools/get-board.d.ts.map +1 -1
  60. package/dist/tools/get-board.js +34 -7
  61. package/dist/tools/get-board.js.map +1 -1
  62. package/dist/tools/get-tickets.d.ts.map +1 -1
  63. package/dist/tools/get-tickets.js +42 -11
  64. package/dist/tools/get-tickets.js.map +1 -1
  65. package/dist/tools/list-efforts.d.ts +7 -0
  66. package/dist/tools/list-efforts.d.ts.map +1 -1
  67. package/dist/tools/list-efforts.js +15 -0
  68. package/dist/tools/list-efforts.js.map +1 -1
  69. package/dist/tools/migrate-effort.d.ts +6 -1
  70. package/dist/tools/migrate-effort.d.ts.map +1 -1
  71. package/dist/tools/migrate-effort.js +62 -20
  72. package/dist/tools/migrate-effort.js.map +1 -1
  73. package/dist/tools/update-ticket.d.ts +2 -0
  74. package/dist/tools/update-ticket.d.ts.map +1 -1
  75. package/dist/tools/update-ticket.js +14 -7
  76. package/dist/tools/update-ticket.js.map +1 -1
  77. package/dist/tools/workspace-line.d.ts +2 -2
  78. package/dist/tools/workspace-line.js +2 -2
  79. package/dist/workspace.d.ts +11 -0
  80. package/dist/workspace.d.ts.map +1 -1
  81. package/dist/workspace.js +17 -5
  82. package/dist/workspace.js.map +1 -1
  83. package/docs/agents/issue-tracker.md +60 -27
  84. package/package.json +11 -11
  85. package/scripts/frontier-setup.cjs +451 -18
package/dist/workspace.js CHANGED
@@ -41,15 +41,26 @@ const ROOT_MARKERS = [
41
41
  * A workspace with no `.scratch/` is not — that is a fresh project.
42
42
  */
43
43
  export function resolveWorkspace(root, context) {
44
+ return locateWorkspace(root, context).root;
45
+ }
46
+ /** {@link resolveWorkspace}, saying which source won. */
47
+ export function locateWorkspace(root, context) {
44
48
  const argument = given(root);
45
49
  if (argument !== undefined) {
46
- return requireDirectory(resolve(context.cwd, argument), 'root argument');
50
+ return {
51
+ root: requireDirectory(resolve(context.cwd, argument), 'root argument'),
52
+ resolvedBy: 'root argument',
53
+ };
47
54
  }
48
55
  const fromEnv = given(context.env[WORKSPACE_ENV_VAR]);
49
56
  if (fromEnv !== undefined) {
50
- return requireDirectory(resolve(context.cwd, fromEnv), WORKSPACE_ENV_VAR);
57
+ return {
58
+ root: requireDirectory(resolve(context.cwd, fromEnv), WORKSPACE_ENV_VAR),
59
+ resolvedBy: `$${WORKSPACE_ENV_VAR}`,
60
+ };
51
61
  }
52
- return findRootMarker(resolve(context.cwd)) ?? resolve(context.cwd);
62
+ const cwd = resolve(context.cwd);
63
+ return (findRootMarker(cwd) ?? { root: cwd, resolvedBy: 'working directory, no root marker found' });
53
64
  }
54
65
  /**
55
66
  * An empty override was not supplied. Falling through to the next source is the
@@ -67,8 +78,9 @@ function given(value) {
67
78
  function findRootMarker(from) {
68
79
  let dir = from;
69
80
  for (;;) {
70
- if (ROOT_MARKERS.some(marker => marker.matches(join(dir, marker.name))))
71
- return dir;
81
+ const marker = ROOT_MARKERS.find(candidate => candidate.matches(join(dir, candidate.name)));
82
+ if (marker !== undefined)
83
+ return { root: dir, resolvedBy: marker.name };
72
84
  const parent = dirname(dir);
73
85
  if (parent === dir)
74
86
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,iFAAiF;AACjF,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAEjD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,YAAY,GAAG;IACnB,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE;IAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;CAClC,CAAC;AAQF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAwB,EAAE,OAAyB;IAClF,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,eAAe,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACtD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,SAAS,KAAK,CAAC,KAAyB;IACtC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACjE,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,GAAG,GAAG,IAAI,CAAC;IAEf,SAAS,CAAC;QACR,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QAEpF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,SAAS,CAAC;QACrC,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,MAAc;IACpD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,kBAAkB,MAAM,wBAAwB,IAAI,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,qFAAqF;AACrF,SAAS,WAAW,CAAC,IAAY;IAC/B,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,12 @@
1
1
  # Issue tracker — FrontierMCP and file fallback
2
2
 
3
+ Tracker doc revision 7. To check a vendored copy, compare this number with the one in the
4
+ `frontier://tracker-doc` resource of the server you run; a lower number, or no revision line, is
5
+ stale.
6
+
3
7
  Use **FrontierMCP** as the issue tracker. Its canonical storage is markdown under `.scratch/`.
4
8
  Vocabulary is defined in the project's `CONTEXT.md` when present — Effort, Board, Header doc, Map,
5
- Spec, Ticket, Edge, Frontier.
9
+ Spec, Ticket, Edge, Frontier, Handle, Temporary key.
6
10
 
7
11
  When **FrontierMCP** (server name `frontier`) is loaded in your session, use the tools below. They
8
12
  own the schema, compute the graph, and answer orientation questions for a fraction of the token cost
@@ -30,7 +34,7 @@ session.** Moving to another worktree mid-session does not retarget it: pass `ro
30
34
  reports no Efforts — that is the worktree being served correctly, not the tracker going missing.
31
35
 
32
36
  **Every call that writes opens with `root: <path>`**, naming the repository it just changed. Read it
33
- — a Ticket id is not repo-unique, so `T10 updated` looks the same whichever repository answered, and
37
+ — a Ticket id is unique within a repository, not across them, so `T10 updated` looks the same whichever repository answered, and
34
38
  this line is the only part of the result that tells you. Reads do not carry it, and neither do the
35
39
  read paths of write-capable tools: `edit_map` with no section fields, `spec` with no `content`, and
36
40
  `migrate_effort` with `preview`.
@@ -44,14 +48,14 @@ Eight tools permanently. Optional arguments extend an existing tool; there is no
44
48
 
45
49
  | Tool | Use when |
46
50
  | --- | --- |
47
- | `list_efforts` | Orient — list Efforts with Ticket counts, header doc kinds, and Frontier size. |
51
+ | `list_efforts` | Orient — list Efforts with Ticket counts, header doc kinds, and Frontier size. A repository with no `.scratch/` at all gets `no tracker here`, the marker that located the root, and a `start one` line to copy; an empty `.scratch/` still reads `(no efforts)`. |
48
52
  | `get_board` | See the whole Effort cheaply — Destination, one summary line per Ticket, Frontier marked. Never returns bodies. |
49
53
  | `get_tickets` | Fetch full bodies for specific ids after the Board tells you which Tickets matter. |
50
54
  | `create_tickets` | Publish a breakdown in one call. Declare Edges with temporary keys; the server assigns ids and numbers. |
51
- | `update_ticket` | Claim, resolve, drop, reopen, release, set title/kind/type, set triage, replace Edges, comment, or tick acceptance criteria. One Ticket per call. |
55
+ | `update_ticket` | Claim, resolve, drop, reopen, release, set title/kind/type, set triage, replace Edges, comment, tick acceptance criteria, or `drop_keys` to delete Unimported frontmatter keys. One Ticket per call. |
52
56
  | `edit_map` | Edit Map sections — Destination, Notes, fog, Out of scope. Decisions-so-far regenerates from resolved Tickets. |
53
57
  | `spec` | Read or write a Spec as a whole document. |
54
- | `migrate_effort` | Normalize Legacy Tickets in an Effort. Preview writes nothing; filename rename is opt-in. |
58
+ | `migrate_effort` | Normalize Foreign Tickets — those whose file does not conform to the schema — in an Effort. Filenames never change. Preview writes nothing and names a Ticket it would mint by its handle (`<effort>#<order>`), not a predicted id. |
55
59
 
56
60
  ### Typical agent flow
57
61
 
@@ -76,7 +80,21 @@ Eight tools permanently. Optional arguments extend an existing tool; there is no
76
80
  | release a stale claim | `update_ticket` with `{ release: true }` |
77
81
  | edit the Map | `edit_map` |
78
82
  | write or read the Spec | `spec` |
79
- | normalize legacy files | `migrate_effort` |
83
+ | normalize foreign files | `migrate_effort` |
84
+
85
+ Two Ticket files carrying one id are listed in each Board's warnings, with claimant files for the
86
+ first few, and every write addressed to that id is refused. Fix it by hand, giving all but one a new id; the server never
87
+ renumbers. Under a pattern whose only variable token is `<N>`, every `create_tickets` result carries
88
+ a standing warning that the pattern collides across branches — it describes the configuration, not
89
+ that call; the remedy is a mixed pattern such as `T<N>-<b36{4}>`. When a Ticket's frontmatter carries
90
+ keys outside the schema, `get_tickets` appends them as a fenced `yaml` block under `## Unimported
91
+ frontmatter` — the whole fence for a Ticket with no `id`, only the unimported entries otherwise. The
92
+ read leaves the file unchanged, and no ordinary write removes them: pass `update_ticket` `drop_keys`
93
+ with the names to delete. A schema key or a key the fence does not carry fails the call and writes
94
+ nothing; the rest of the fence stays byte-identical. Dropping the last one from a Ticket with no `id`
95
+ leaves it no longer Foreign, still listed under the Board's `no id` warning. A Board counts `resolved` Tickets
96
+ with no `answer_gist`, `dropped` with no `dropped_reason` and `claimed` with no `claimed_by` or `claimed_at` in its
97
+ warnings, so set the field with the Status.
80
98
 
81
99
  Claims are compare-and-set — a parallel session that already holds the Ticket wins. Writes carry the
82
100
  `revision` from the last read; a mismatch is refused, never retried over the caller's edit.
@@ -87,41 +105,56 @@ These conventions are canonical whether FrontierMCP is loaded or not. FrontierMC
87
105
  the same files; a session without the server still produces valid Tickets by following this section.
88
106
 
89
107
  **Canonical is not licence to allocate ids by hand.** When FrontierMCP is loaded, ids come from
90
- `create_tickets` and are never derived by scanning. The server allocates each candidate under a
91
- repo-global `.scratch/.frontier-id-T<n>.guard` and re-scans while holding every guard; a file
92
- written straight to disk takes part in none of that, so it is the naive `max + 1` that
93
- [ADR 0005](../adr/0005-ids-are-allocated-under-a-guard-and-a-rescan.md) was written against —
94
- measured there, four processes creating three Tickets each produced thirteen files carrying four
95
- distinct ids, and duplicates are silent. A *guessed* id passed to `create_tickets` costs nothing;
96
- the server allocates and discards the guess. Only the hand-written file breaks.
108
+ `create_tickets`, and you never scan for one: the server mints every id it is present for. It knows
109
+ the repo's id pattern, and under a pattern containing `<N>` it counts under a guard
110
+ (`.scratch/.frontier-id-N-<n>.guard`, keyed on the counter value) that a file written straight to
111
+ disk cannot take — ADR 0006 in the frontier-mcp repository. Hand-writing while the server is loaded
112
+ bypasses the mint whatever the pattern.
97
113
 
98
114
  ### Layout
99
115
 
100
- - One Effort per directory: `.scratch/<effort-slug>/`
116
+ - One Effort per directory: `.scratch/<effort-slug>/`.
117
+ - `.scratch/frontier.yml`, beside the Effort directories, is the one other file FrontierMCP reads.
118
+ Its one key, `id_pattern`, shapes Ticket ids. The server reads it once at start; restart a running
119
+ server after editing it.
101
120
  - The header doc is `spec.md` (from `/to-spec`) or `map.md` (from `/wayfinder`). An Effort charted as a
102
121
  Map may later gain a Spec — that is wayfinder's handoff, not a mistake.
103
122
  - Tickets are one file per Ticket under `.scratch/<effort-slug>/issues/` — never a single combined
104
123
  tickets file.
105
- - Ticket filenames are `<NN>-T<n>-<slug>.md`. `NN` is sort order only and may be rewritten. `T<n>` is
106
- the identity and never changes.
107
- - **Ticket ids are `T<n>`, unique across the whole repo.** With FrontierMCP loaded, `create_tickets`
108
- assigns them and you never scan for the next one. Only when the server is absent: before creating
109
- a Ticket by hand, scan every `.scratch/*/issues/` for the highest existing `T` number and continue
110
- from there — ids do not restart per Effort. A bare `T47` therefore resolves anywhere in the repo,
111
- including in commit messages.
124
+ - Ticket filenames are `<NN>-<id>-<slug>.md`. `NN` is sort order only and may be rewritten. The id
125
+ is the identity and never changes.
126
+ - **Ticket ids are unique across the whole repo**, so a bare id resolves anywhere in it, including
127
+ in commit messages. With FrontierMCP loaded, `create_tickets` assigns them. Only when the server is
128
+ absent, mint one by hand:
129
+ 1. Read `id_pattern` from `.scratch/frontier.yml`. No file or no key means `T<b36{6}>`.
130
+ 2. Check the pattern is legal: it begins with a literal letter or underscore; literals are only
131
+ `A-Z a-z 0-9 . _ -`; the tokens are `<N>`, `<b36{n}>` and `<b16{n}>` with n from 1 to 64, and
132
+ `<effort>` is refused; at most one `<N>`; at least one `<N>` or random token; a minted id is at
133
+ most 64 characters, counting `<N>` as one. If the file is not valid YAML, is not a mapping,
134
+ `id_pattern` is not a string, or the pattern breaks a rule, **do not mint** — fix the file first.
135
+ Never fall back to the default: an id minted in a format nobody asked for is permanent.
136
+ 3. Render it. Every `<b36{n}>` is exactly n characters from `0-9a-z`, every `<b16{n}>` exactly n
137
+ from `0-9a-f`, lowercase always. `<N>` is decimal with no leading zeros. Anything outside `<>`
138
+ is literal.
139
+ 4. **No `<N>` in the pattern:** draw the random tokens. Do not scan for a highest id.
140
+ 5. **`<N>` in the pattern:** scan every `.scratch/*/issues/` for the highest `<N>` among ids that
141
+ match the whole pattern and add one; under a mixed pattern, count first, then draw the random
142
+ part. A hand-writer cannot take the server's guard, so if any session might be minting at the
143
+ same time you can both reach the same number unseen.
144
+ 6. Check the id is not already in use anywhere in `.scratch/`.
112
145
  - Formal metadata goes in YAML frontmatter. Prose stays prose.
113
146
 
114
147
  ### Ticket frontmatter
115
148
 
116
149
  ```yaml
117
150
  ---
118
- id: T12 # T<n>, repo-unique, never reused
119
- title: Parse legacy tickets without frontmatter
151
+ id: T12 # shaped by id_pattern; repo-unique, never reused or changed
152
+ title: Parse tickets without frontmatter
120
153
  kind: build # build | decision
121
154
  type: research # decision only: research | prototype | grilling | task
122
155
  status: open # open | claimed | resolved | dropped
123
156
  triage: ready-for-agent # see triage-labels.md
124
- blocked_by: [T09, T11] # plain ids, resolved repo-wide
157
+ blocked_by: [T9, T11] # plain ids, resolved repo-wide
125
158
  claimed_by: # required when status: claimed
126
159
  claimed_at: # timestamp, set with claimed_by
127
160
  answer_gist: # required when status: resolved — one line, every kind
@@ -164,8 +197,8 @@ When FrontierMCP is loaded, prefer the MCP calls in the table above over hand-ed
164
197
 
165
198
  **Everything below holds only while FrontierMCP is absent from the session.** With the server
166
199
  loaded, `create_tickets` publishes the breakdown and is the only thing that may allocate an id;
167
- hand-writing the file bypasses the guards, per the File conventions preamble.
200
+ hand-writing the file bypasses the mint, per the File conventions preamble.
168
201
 
169
202
  When a skill says "publish to the issue tracker" and FrontierMCP is not loaded, create
170
- `.scratch/<effort-slug>/issues/<NN>-T<n>-<slug>.md` (creating directories as needed), assign the next
171
- repo-global `T<n>` by scanning as described above, and write frontmatter matching the template above.
203
+ `.scratch/<effort-slug>/issues/<NN>-<id>-<slug>.md` (creating directories as needed), mint the id
204
+ as the Layout steps above describe, and write frontmatter matching the template above.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontier-mcp",
3
- "version": "0.5.0",
3
+ "version": "1.0.0",
4
4
  "description": "An MCP server exposing the markdown issue tracker under a repo's .scratch/ as a queryable graph",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,19 +25,19 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@modelcontextprotocol/server": "^2.0.0",
29
- "yaml": "^2.9.0",
30
- "zod": "^4.4.3"
28
+ "@modelcontextprotocol/server": "^2.4.0",
29
+ "yaml": "^2.9.1",
30
+ "zod": "^4.6.7"
31
31
  },
32
32
  "devDependencies": {
33
- "@modelcontextprotocol/client": "^2.0.0",
34
- "@release-it/conventional-changelog": "^12.0.0",
35
- "@types/node": "^24.10.1",
36
- "oxfmt": "^0.62.0",
37
- "oxlint": "^1.77.0",
38
- "release-it": "^21.0.1",
33
+ "@modelcontextprotocol/client": "^2.4.0",
34
+ "@release-it/conventional-changelog": "^12.0.2",
35
+ "@types/node": "^24.19.2",
36
+ "oxfmt": "^0.78.0",
37
+ "oxlint": "^1.93.0",
38
+ "release-it": "^21.1.2",
39
39
  "typescript": "^7.0.2",
40
- "vitest": "^4.1.10"
40
+ "vitest": "^5.1.0"
41
41
  },
42
42
  "engines": {
43
43
  "node": "^20.20.2 || ^22.17.1 || ^24.15.0"