@systemdox/mcp-server 0.1.45 → 0.1.47

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.
@@ -34,6 +34,28 @@ var ResponseCache = class {
34
34
  };
35
35
 
36
36
  // src/lib/api-client.ts
37
+ var SystemDoxApiError = class extends Error {
38
+ constructor(status, text) {
39
+ super(`SystemDox API error ${status}: ${text}`);
40
+ this.status = status;
41
+ this.text = text;
42
+ let body;
43
+ try {
44
+ body = JSON.parse(text);
45
+ } catch {
46
+ body = void 0;
47
+ }
48
+ this.body = body;
49
+ const code = body?.code;
50
+ this.code = typeof code === "string" ? code : /\b(SYD-[A-Z]\d{3})\b/.exec(text)?.[1];
51
+ }
52
+ status;
53
+ text;
54
+ code;
55
+ body;
56
+ };
57
+ var TEAM_SETUP = "/v1/team-setup";
58
+ var seg = (s) => encodeURIComponent(String(s));
37
59
  var SystemDoxApiClient = class {
38
60
  config;
39
61
  cache;
@@ -55,10 +77,7 @@ var SystemDoxApiClient = class {
55
77
  const cached = this.cache.get(cacheKey);
56
78
  if (cached !== void 0) return cached;
57
79
  const res = await this.fetchRaw(path, params);
58
- if (!res.ok) {
59
- const body = await res.text();
60
- throw new Error(`SystemDox API error ${res.status}: ${body}`);
61
- }
80
+ if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
62
81
  const data = await res.json();
63
82
  this.cache.set(cacheKey, data);
64
83
  return data;
@@ -67,10 +86,7 @@ var SystemDoxApiClient = class {
67
86
  * latest now, not the one cached before someone published another. */
68
87
  async getFresh(path) {
69
88
  const res = await this.fetchRaw(path);
70
- if (!res.ok) {
71
- const body = await res.text();
72
- throw new Error(`SystemDox API error ${res.status}: ${body}`);
73
- }
89
+ if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
74
90
  return await res.json();
75
91
  }
76
92
  async post(path, body) {
@@ -92,12 +108,101 @@ var SystemDoxApiClient = class {
92
108
  },
93
109
  body: JSON.stringify(body)
94
110
  });
95
- if (!res.ok) {
96
- const text = await res.text();
97
- throw new Error(`SystemDox API error ${res.status}: ${text}`);
98
- }
111
+ if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
99
112
  return await res.json();
100
113
  }
114
+ // ── Team setup (ADR-217, specs/changes/team-setup/openapi.yaml) ──────────
115
+ // One method per operationId. Every read skips the cache: setup, sync and
116
+ // plugin-path act on what the workspace holds now.
117
+ /** getOwnTeamSetup: the key's own workspace, named in `workspace`. */
118
+ getOwnTeamSetup() {
119
+ return this.getFresh(TEAM_SETUP);
120
+ }
121
+ getTeamSetup(workspace) {
122
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}`);
123
+ }
124
+ getMarketplace(workspace) {
125
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/marketplace.json`);
126
+ }
127
+ getPart(workspace, part) {
128
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/parts/${seg(part)}`);
129
+ }
130
+ publishPartVersion(workspace, part, files) {
131
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/parts/${seg(part)}/versions`, { files });
132
+ }
133
+ getPartVersion(workspace, part, version) {
134
+ return this.getFresh(
135
+ `${TEAM_SETUP}/${seg(workspace)}/parts/${seg(part)}/versions/${seg(version)}`
136
+ );
137
+ }
138
+ getSetupSkills(workspace) {
139
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/skills`);
140
+ }
141
+ setSetupSkills(workspace, skills) {
142
+ return this.put(`${TEAM_SETUP}/${seg(workspace)}/skills`, { skills });
143
+ }
144
+ getPlugin(workspace) {
145
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/plugin`);
146
+ }
147
+ /** getPluginZip: the zip's bytes as served, and its X-Content-SHA256
148
+ * header. The caller checks the BYTES against the hash it was given. */
149
+ async getPluginZip(workspace, version) {
150
+ const res = await fetch(
151
+ this.buildUrl(`${TEAM_SETUP}/${seg(workspace)}/plugin/versions/${seg(version)}/zip`),
152
+ {
153
+ headers: {
154
+ Authorization: `Bearer ${this.config.apiKey}`,
155
+ "X-API-Key": this.config.apiKey,
156
+ Accept: "application/zip"
157
+ }
158
+ }
159
+ );
160
+ if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
161
+ return { bytes: Buffer.from(await res.arrayBuffer()), sha256: res.headers.get("x-content-sha256") };
162
+ }
163
+ listMemoryProjects(workspace) {
164
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/memory`);
165
+ }
166
+ getMemory(workspace, project) {
167
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}`);
168
+ }
169
+ /** pushMemoryVersion: a stale base throws SystemDoxApiError with status 409,
170
+ * code SYD-K010 and `body.currentVersion`. */
171
+ pushMemoryVersion(workspace, project, body) {
172
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/versions`, body);
173
+ }
174
+ getMemoryVersion(workspace, project, version) {
175
+ return this.getFresh(
176
+ `${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/versions/${seg(version)}`
177
+ );
178
+ }
179
+ findMissingBlobs(workspace, project, hashes) {
180
+ return this.post(
181
+ `${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/blobs/missing`,
182
+ { hashes }
183
+ );
184
+ }
185
+ uploadBlobs(workspace, project, blobs) {
186
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/blobs`, { blobs });
187
+ }
188
+ readBlobs(workspace, project, hashes) {
189
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/blobs/read`, { hashes });
190
+ }
191
+ listValueAnswers(workspace) {
192
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/values`);
193
+ }
194
+ lookupValues(workspace, values) {
195
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/values`, { values });
196
+ }
197
+ answerValues(workspace, answers) {
198
+ return this.put(`${TEAM_SETUP}/${seg(workspace)}/values`, { answers });
199
+ }
200
+ fileMemoryPeople(workspace, people) {
201
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/people`, { people });
202
+ }
203
+ resolvePerson(workspace, personId) {
204
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/people/${seg(personId)}`);
205
+ }
101
206
  invalidateCache(path) {
102
207
  if (path) {
103
208
  this.cache.invalidate(this.buildUrl(path).toString());
@@ -1,4 +1,18 @@
1
1
  import type { ServerConfig } from "./config.js";
2
+ import type { BlobRefusal, FileIn, FoundValue, Manifest, Marketplace, MemoryProjectSummary, MemoryVersion, PartDetail, PartName, PartPublished, PartVersionFiles, Person, PluginVersion, TeamSetup, ValueAnswer, ValueAnswerListed, ValueAnswerResults } from "./team-setup-types.js";
3
+ /**
4
+ * A refused API call. The message keeps the form every caller has always
5
+ * shown (`SystemDox API error <status>: <body>`), so an SYD- code in the body
6
+ * reaches the user as the server wrote it; `code` and `body` are there for a
7
+ * caller that acts on one (a 409's `currentVersion`, a 503's SYD-K019).
8
+ */
9
+ export declare class SystemDoxApiError extends Error {
10
+ readonly status: number;
11
+ readonly text: string;
12
+ readonly code: string | undefined;
13
+ readonly body: unknown;
14
+ constructor(status: number, text: string);
15
+ }
2
16
  export declare class SystemDoxApiClient {
3
17
  private config;
4
18
  private cache;
@@ -11,6 +25,73 @@ export declare class SystemDoxApiClient {
11
25
  post<T>(path: string, body: unknown): Promise<T>;
12
26
  put<T>(path: string, body: unknown): Promise<T>;
13
27
  private mutate;
28
+ /** getOwnTeamSetup: the key's own workspace, named in `workspace`. */
29
+ getOwnTeamSetup(): Promise<TeamSetup>;
30
+ getTeamSetup(workspace: string): Promise<TeamSetup>;
31
+ getMarketplace(workspace: string): Promise<Marketplace>;
32
+ getPart(workspace: string, part: PartName): Promise<PartDetail>;
33
+ publishPartVersion(workspace: string, part: PartName, files: FileIn[]): Promise<PartPublished>;
34
+ getPartVersion(workspace: string, part: PartName, version: number | "latest"): Promise<PartVersionFiles>;
35
+ getSetupSkills(workspace: string): Promise<{
36
+ skills: string[];
37
+ }>;
38
+ setSetupSkills(workspace: string, skills: string[]): Promise<{
39
+ skills: string[];
40
+ }>;
41
+ getPlugin(workspace: string): Promise<{
42
+ name: string;
43
+ versions: PluginVersion[];
44
+ }>;
45
+ /** getPluginZip: the zip's bytes as served, and its X-Content-SHA256
46
+ * header. The caller checks the BYTES against the hash it was given. */
47
+ getPluginZip(workspace: string, version: number | "latest"): Promise<{
48
+ bytes: Buffer;
49
+ sha256: string | null;
50
+ }>;
51
+ listMemoryProjects(workspace: string): Promise<{
52
+ projects: MemoryProjectSummary[];
53
+ }>;
54
+ getMemory(workspace: string, project: string): Promise<MemoryVersion>;
55
+ /** pushMemoryVersion: a stale base throws SystemDoxApiError with status 409,
56
+ * code SYD-K010 and `body.currentVersion`. */
57
+ pushMemoryVersion(workspace: string, project: string, body: {
58
+ baseVersion: number;
59
+ manifest: Manifest;
60
+ spaceId?: string;
61
+ }): Promise<MemoryVersion>;
62
+ getMemoryVersion(workspace: string, project: string, version: number | "latest"): Promise<MemoryVersion>;
63
+ findMissingBlobs(workspace: string, project: string, hashes: string[]): Promise<{
64
+ missing: string[];
65
+ }>;
66
+ uploadBlobs(workspace: string, project: string, blobs: {
67
+ sha256: string;
68
+ content: string;
69
+ }[]): Promise<{
70
+ stored: string[];
71
+ refused: BlobRefusal[];
72
+ }>;
73
+ readBlobs(workspace: string, project: string, hashes: string[]): Promise<{
74
+ blobs: {
75
+ sha256: string;
76
+ content: string;
77
+ }[];
78
+ remaining: string[];
79
+ }>;
80
+ listValueAnswers(workspace: string): Promise<{
81
+ answers: ValueAnswerListed[];
82
+ }>;
83
+ lookupValues(workspace: string, values: FoundValue[]): Promise<{
84
+ answers: ValueAnswer[];
85
+ }>;
86
+ answerValues(workspace: string, answers: (FoundValue & {
87
+ answer: "ours" | "member";
88
+ label?: string;
89
+ })[]): Promise<ValueAnswerResults>;
90
+ fileMemoryPeople(workspace: string, people: {
91
+ values: FoundValue[];
92
+ label: string;
93
+ }[]): Promise<ValueAnswerResults>;
94
+ resolvePerson(workspace: string, personId: string): Promise<Person>;
14
95
  invalidateCache(path?: string): void;
15
96
  private buildUrl;
16
97
  private fetchRaw;
@@ -31,6 +31,28 @@ var ResponseCache = class {
31
31
  };
32
32
 
33
33
  // src/lib/api-client.ts
34
+ var SystemDoxApiError = class extends Error {
35
+ constructor(status, text) {
36
+ super(`SystemDox API error ${status}: ${text}`);
37
+ this.status = status;
38
+ this.text = text;
39
+ let body;
40
+ try {
41
+ body = JSON.parse(text);
42
+ } catch {
43
+ body = void 0;
44
+ }
45
+ this.body = body;
46
+ const code = body?.code;
47
+ this.code = typeof code === "string" ? code : /\b(SYD-[A-Z]\d{3})\b/.exec(text)?.[1];
48
+ }
49
+ status;
50
+ text;
51
+ code;
52
+ body;
53
+ };
54
+ var TEAM_SETUP = "/v1/team-setup";
55
+ var seg = (s) => encodeURIComponent(String(s));
34
56
  var SystemDoxApiClient = class {
35
57
  config;
36
58
  cache;
@@ -52,10 +74,7 @@ var SystemDoxApiClient = class {
52
74
  const cached = this.cache.get(cacheKey);
53
75
  if (cached !== void 0) return cached;
54
76
  const res = await this.fetchRaw(path, params);
55
- if (!res.ok) {
56
- const body = await res.text();
57
- throw new Error(`SystemDox API error ${res.status}: ${body}`);
58
- }
77
+ if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
59
78
  const data = await res.json();
60
79
  this.cache.set(cacheKey, data);
61
80
  return data;
@@ -64,10 +83,7 @@ var SystemDoxApiClient = class {
64
83
  * latest now, not the one cached before someone published another. */
65
84
  async getFresh(path) {
66
85
  const res = await this.fetchRaw(path);
67
- if (!res.ok) {
68
- const body = await res.text();
69
- throw new Error(`SystemDox API error ${res.status}: ${body}`);
70
- }
86
+ if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
71
87
  return await res.json();
72
88
  }
73
89
  async post(path, body) {
@@ -89,12 +105,101 @@ var SystemDoxApiClient = class {
89
105
  },
90
106
  body: JSON.stringify(body)
91
107
  });
92
- if (!res.ok) {
93
- const text = await res.text();
94
- throw new Error(`SystemDox API error ${res.status}: ${text}`);
95
- }
108
+ if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
96
109
  return await res.json();
97
110
  }
111
+ // ── Team setup (ADR-217, specs/changes/team-setup/openapi.yaml) ──────────
112
+ // One method per operationId. Every read skips the cache: setup, sync and
113
+ // plugin-path act on what the workspace holds now.
114
+ /** getOwnTeamSetup: the key's own workspace, named in `workspace`. */
115
+ getOwnTeamSetup() {
116
+ return this.getFresh(TEAM_SETUP);
117
+ }
118
+ getTeamSetup(workspace) {
119
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}`);
120
+ }
121
+ getMarketplace(workspace) {
122
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/marketplace.json`);
123
+ }
124
+ getPart(workspace, part) {
125
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/parts/${seg(part)}`);
126
+ }
127
+ publishPartVersion(workspace, part, files) {
128
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/parts/${seg(part)}/versions`, { files });
129
+ }
130
+ getPartVersion(workspace, part, version) {
131
+ return this.getFresh(
132
+ `${TEAM_SETUP}/${seg(workspace)}/parts/${seg(part)}/versions/${seg(version)}`
133
+ );
134
+ }
135
+ getSetupSkills(workspace) {
136
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/skills`);
137
+ }
138
+ setSetupSkills(workspace, skills) {
139
+ return this.put(`${TEAM_SETUP}/${seg(workspace)}/skills`, { skills });
140
+ }
141
+ getPlugin(workspace) {
142
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/plugin`);
143
+ }
144
+ /** getPluginZip: the zip's bytes as served, and its X-Content-SHA256
145
+ * header. The caller checks the BYTES against the hash it was given. */
146
+ async getPluginZip(workspace, version) {
147
+ const res = await fetch(
148
+ this.buildUrl(`${TEAM_SETUP}/${seg(workspace)}/plugin/versions/${seg(version)}/zip`),
149
+ {
150
+ headers: {
151
+ Authorization: `Bearer ${this.config.apiKey}`,
152
+ "X-API-Key": this.config.apiKey,
153
+ Accept: "application/zip"
154
+ }
155
+ }
156
+ );
157
+ if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
158
+ return { bytes: Buffer.from(await res.arrayBuffer()), sha256: res.headers.get("x-content-sha256") };
159
+ }
160
+ listMemoryProjects(workspace) {
161
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/memory`);
162
+ }
163
+ getMemory(workspace, project) {
164
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}`);
165
+ }
166
+ /** pushMemoryVersion: a stale base throws SystemDoxApiError with status 409,
167
+ * code SYD-K010 and `body.currentVersion`. */
168
+ pushMemoryVersion(workspace, project, body) {
169
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/versions`, body);
170
+ }
171
+ getMemoryVersion(workspace, project, version) {
172
+ return this.getFresh(
173
+ `${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/versions/${seg(version)}`
174
+ );
175
+ }
176
+ findMissingBlobs(workspace, project, hashes) {
177
+ return this.post(
178
+ `${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/blobs/missing`,
179
+ { hashes }
180
+ );
181
+ }
182
+ uploadBlobs(workspace, project, blobs) {
183
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/blobs`, { blobs });
184
+ }
185
+ readBlobs(workspace, project, hashes) {
186
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/memory/${seg(project)}/blobs/read`, { hashes });
187
+ }
188
+ listValueAnswers(workspace) {
189
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/values`);
190
+ }
191
+ lookupValues(workspace, values) {
192
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/values`, { values });
193
+ }
194
+ answerValues(workspace, answers) {
195
+ return this.put(`${TEAM_SETUP}/${seg(workspace)}/values`, { answers });
196
+ }
197
+ fileMemoryPeople(workspace, people) {
198
+ return this.post(`${TEAM_SETUP}/${seg(workspace)}/people`, { people });
199
+ }
200
+ resolvePerson(workspace, personId) {
201
+ return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}/people/${seg(personId)}`);
202
+ }
98
203
  invalidateCache(path) {
99
204
  if (path) {
100
205
  this.cache.invalidate(this.buildUrl(path).toString());
@@ -124,5 +229,6 @@ var SystemDoxApiClient = class {
124
229
  }
125
230
  };
126
231
  export {
127
- SystemDoxApiClient
232
+ SystemDoxApiClient,
233
+ SystemDoxApiError
128
234
  };
@@ -0,0 +1,150 @@
1
+ /**
2
+ * The team memory's merge (ADR-217 §2.3, specs/changes/team-setup/contract.md
3
+ * "sync-memory and the MCP tool sync_memory"), pure: no disk, no network.
4
+ * `src/sync-memory.ts` reads the folders and calls the API around it.
5
+ *
6
+ * - **What is shared** (`isShareable`): a `*.md` file whose frontmatter `type`
7
+ * is `feedback`, `project` or `reference`, and `MEMORY.md`; never a file
8
+ * with `share: false` or a `<name>.local-<date>.md` conflict copy.
9
+ * - **The merge** (`decide`, `planMerge`): three-way per path, with the base
10
+ * (the manifest of the last sync), the team's latest version and the local
11
+ * folder, row by row as the contract's table says.
12
+ * - **`MEMORY.md`** (`mergeIndex`): line by line, keyed by the file each line
13
+ * links to; never a `.local-` copy.
14
+ * - **People** (`collectValues`, `rewriteValues`, `personToken`): every value
15
+ * the detector finds, grouped by its normalised form, and the rewrite the
16
+ * member agrees to.
17
+ */
18
+ import { type Finding, type ValueKind } from "./person-values.js";
19
+ export declare const INDEX_FILE = "MEMORY.md";
20
+ export declare const SHAREABLE_TYPES: readonly ["feedback", "project", "reference"];
21
+ /** `{path: sha256}` (openapi Manifest). */
22
+ export type Manifest = Record<string, string>;
23
+ export declare function sha256(text: string): string;
24
+ export declare function isFilePath(path: string): boolean;
25
+ export declare function isConflictCopy(path: string): boolean;
26
+ export interface Frontmatter {
27
+ type?: string;
28
+ /** True when the block says `share: false` (or another YAML false). */
29
+ shareFalse: boolean;
30
+ }
31
+ /** The frontmatter's `type` and `share`, or undefined when the file has no
32
+ * leading block. Only top-level `key: value` lines are read. */
33
+ export declare function readFrontmatter(text: string): Frontmatter | undefined;
34
+ /**
35
+ * Shared with the team: `MEMORY.md` at the folder's root, or a `*.md` file
36
+ * whose frontmatter `type` is feedback, project or reference. Never a file
37
+ * with `share: false`, a `.local-` conflict copy, or anything else.
38
+ */
39
+ export declare function isShareable(path: string, text: string): boolean;
40
+ /** `text` with `share: false` in its frontmatter: the line is added before the
41
+ * block's closing `---` (or replaces a `share:` line), or a block is added. */
42
+ export declare function addShareFalse(text: string): string;
43
+ /** The member's calendar date, `YYYY-MM-DD`. */
44
+ export declare function localDate(now: Date): string;
45
+ /** `a.md` → `a.local-<date>.md`, then `a.local-<date>-2.md`, `-3` … when the
46
+ * name is taken. */
47
+ export declare function conflictCopyName(path: string, date: string, taken: Set<string>): string;
48
+ /**
49
+ * One path's row of the contract's table (base → team, base → local):
50
+ * - `unchanged`: both sides hold the same (or neither holds it);
51
+ * - `local`: the team did not change it and the member changed or added it;
52
+ * also a file the team deleted and the member changed (the member's text
53
+ * is kept, never lost);
54
+ * - `delete-team`: the team did not change it and the member deleted it;
55
+ * - `team`: the member did not change it and the team changed or added it;
56
+ * also a file the member deleted and the team changed (the team's text
57
+ * comes back, never lost);
58
+ * - `delete-local`: the member did not change it and the team deleted it;
59
+ * - `conflict`: both changed it differently;
60
+ * - `index`: `MEMORY.md` changed on both sides, merged line by line.
61
+ */
62
+ export type Row = "unchanged" | "local" | "delete-team" | "team" | "delete-local" | "conflict" | "index";
63
+ export declare function decide(path: string, base: string | undefined, team: string | undefined, local: string | undefined): Row;
64
+ export interface MergeInput {
65
+ base: Manifest;
66
+ team: Manifest;
67
+ /** The local folder's shared files, `{path: text}`. */
68
+ local: Record<string, string>;
69
+ /** Paths left as they are on both sides: a file held back this run, or a
70
+ * local file that is not shared. The team's entry is kept; nothing is
71
+ * written to the path. */
72
+ excluded: Set<string>;
73
+ /** Every file name in the folder (lower case), so a conflict copy never
74
+ * takes a name in use. */
75
+ taken: Set<string>;
76
+ date: string;
77
+ }
78
+ /** The rows by path, and the team hashes whose text the plan needs. */
79
+ export declare function decideAll(input: Omit<MergeInput, "taken" | "date">): {
80
+ rows: Record<string, Row>;
81
+ needed: string[];
82
+ };
83
+ export interface MergePlan {
84
+ /** The manifest to push. */
85
+ manifest: Manifest;
86
+ /** The text of each path whose hash the team does not hold yet. */
87
+ upload: Record<string, string>;
88
+ /** Files to write in the local folder (pulled, conflict pairs, the index). */
89
+ writes: Record<string, string>;
90
+ /** Files to delete in the local folder. */
91
+ deletes: string[];
92
+ pulled: string[];
93
+ pushed: string[];
94
+ deletedHere: string[];
95
+ deletedFromTeam: string[];
96
+ conflicts: {
97
+ path: string;
98
+ copy: string;
99
+ }[];
100
+ /** `MEMORY.md` lines the team changed where the member's line was kept. */
101
+ indexTeamLines: string[];
102
+ }
103
+ /** The plan for the rows of `decideAll`, given the text of every hash it
104
+ * named (`texts`, `{sha256: text}`). A base `MEMORY.md` no longer readable is
105
+ * merged as if empty. */
106
+ export declare function planMerge(input: MergeInput, rows: Record<string, Row>, texts: Record<string, string>): MergePlan;
107
+ /**
108
+ * `MEMORY.md` merged line by line, keyed by the file each line links to
109
+ * (ADR-217 §2.3.3):
110
+ * - a team line for a new file is added under the same heading (after the
111
+ * line it follows in the team's copy);
112
+ * - a line the team removed and the member did not change is removed;
113
+ * - a line with no link takes the team's text unless the member changed it;
114
+ * - when both changed the same line, the member's line is kept and the
115
+ * team's is returned in `teamLines`, to be listed.
116
+ */
117
+ export declare function mergeIndex(base: string | undefined, team: string, local: string): {
118
+ text: string;
119
+ teamLines: string[];
120
+ };
121
+ export interface ValueGroup {
122
+ kind: ValueKind;
123
+ /** As first written. */
124
+ value: string;
125
+ normalised: string;
126
+ files: string[];
127
+ /** Where it first appears: the file, the line number and the line. */
128
+ first: {
129
+ path: string;
130
+ line: number;
131
+ text: string;
132
+ };
133
+ }
134
+ export declare const groupKey: (kind: string, normalised: string) => string;
135
+ /** Every phone number and email address in `files`, grouped by normalised
136
+ * value in the order first found; and the files holding a home-folder path. */
137
+ export declare function collectValues(files: Record<string, string>): {
138
+ groups: ValueGroup[];
139
+ homePaths: string[];
140
+ findings: Record<string, Finding[]>;
141
+ };
142
+ /** `` `<personId>` (<label>) ``: the identifier exactly as the server
143
+ * returned it, and the role label when there is one. */
144
+ export declare function personToken(personId: string, label?: string): string;
145
+ /** `text` with each finding replaced as `replacement` says (undefined leaves
146
+ * it as written). */
147
+ export declare function rewriteValues(text: string, findings: Finding[], replacement: (f: Finding) => string | undefined): string;
148
+ /** The lines that differ between two versions of a file, as `- old` and
149
+ * `+ new` pairs, in order. */
150
+ export declare function lineDiff(before: string, after: string): string[];