agent-coord-mcp 0.17.1 → 0.19.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 +8 -1
- package/dist/build.js +113 -0
- package/dist/build.js.map +1 -0
- package/dist/roles.js +92 -0
- package/dist/roles.js.map +1 -0
- package/dist/server.js +30 -14
- package/dist/server.js.map +1 -1
- package/dist/store.js +54 -1
- package/dist/store.js.map +1 -1
- package/dist/tools/admin.js +5 -3
- package/dist/tools/admin.js.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/messaging.js +187 -8
- package/dist/tools/messaging.js.map +1 -1
- package/dist/tools/registry.js +54 -3
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/render.js +84 -0
- package/dist/tools/render.js.map +1 -0
- package/dist/tools/scopes.js +126 -0
- package/dist/tools/scopes.js.map +1 -0
- package/dist/tools/shared.js +18 -0
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/transport.js +393 -40
- package/dist/tools/transport.js.map +1 -1
- package/dist/tools/work.js +209 -0
- package/dist/tools/work.js.map +1 -0
- package/dist/work.js +260 -0
- package/dist/work.js.map +1 -0
- package/hooks/marker.mjs +18 -0
- package/hooks/roles.mjs +79 -0
- package/hooks/submit.mjs +393 -0
- package/hooks/tier.mjs +104 -11
- package/hooks/tmux-pusher.mjs +137 -51
- package/package.json +5 -4
- package/scripts/check-self-dependency.mjs +54 -0
- package/scripts/check-test-count.mjs +83 -0
- package/scripts/coord-pusher.mjs +150 -33
- package/src/build.ts +111 -0
- package/src/roles.ts +111 -0
- package/src/server.ts +79 -14
- package/src/store.ts +60 -1
- package/src/tools/admin.ts +10 -4
- package/src/tools/index.ts +2 -0
- package/src/tools/messaging.ts +206 -7
- package/src/tools/registry.ts +63 -4
- package/src/tools/render.ts +80 -0
- package/src/tools/scopes.ts +177 -0
- package/src/tools/shared.ts +120 -0
- package/src/tools/transport.ts +413 -40
- package/src/tools/work.ts +265 -0
- package/src/work.ts +329 -0
package/dist/work.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// Work state as data (Phase 8 Task 5): queue items, done entries and board
|
|
2
|
+
// rows as typed records, with markdown as a first-class EXPORT rather than the
|
|
3
|
+
// datastore.
|
|
4
|
+
//
|
|
5
|
+
// WHY THIS SHAPE. `docs/QUEUE.md` / `docs/DONE.md` were parsed under a contract
|
|
6
|
+
// requiring exact `—` (U+2014) and ` · ` (U+00B7). A parser once returned ZERO
|
|
7
|
+
// items on the real file while passing every synthetic test, and the consuming
|
|
8
|
+
// UI rendered an empty panel — no error, no signal. The files themselves are
|
|
9
|
+
// good: David edits them, git diffs them, they grep. So markdown stays the
|
|
10
|
+
// INTERFACE and stops being the DATASTORE.
|
|
11
|
+
//
|
|
12
|
+
// MARKDOWN REMAINS AUTHORITATIVE. Delete the store and the documents still
|
|
13
|
+
// stand alone: every field here is recoverable from the file it came from, the
|
|
14
|
+
// exporter is byte-exact, and nothing downstream is required to read records.
|
|
15
|
+
// The store is a derived index until a consumer chooses otherwise.
|
|
16
|
+
//
|
|
17
|
+
// The round-trip guarantee is structural, not incidental: everything this
|
|
18
|
+
// module does NOT model — prose, the write-rule paragraphs, the Cross-project
|
|
19
|
+
// section, fenced examples — is captured verbatim as a `text` block and
|
|
20
|
+
// replayed in place. Only lines it genuinely understands are re-rendered, so
|
|
21
|
+
// an unmodelled construct can never be silently dropped.
|
|
22
|
+
import { createHash } from "node:crypto";
|
|
23
|
+
// ---------- the glyph contract ----------
|
|
24
|
+
//
|
|
25
|
+
// Pinned in docs/DONE.md and consumed by external parsers: the ref splits on
|
|
26
|
+
// the LAST ` — ` (space, U+2014, space) and the date is a trailing ` · `
|
|
27
|
+
// (space, U+00B7, space) + ISO date. Written as explicit escapes so a source
|
|
28
|
+
// file normalization or a copy-paste through an ASCII-mangling tool can never
|
|
29
|
+
// change them silently — the constants are the contract.
|
|
30
|
+
export const EM_DASH_SEP = " — ";
|
|
31
|
+
export const MIDDOT_SEP = " · ";
|
|
32
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
33
|
+
// Deterministic ids: same content in → same id out, so import→export→import is
|
|
34
|
+
// stable and no id has to be written into the markdown (which would change the
|
|
35
|
+
// bytes the round trip is measured on).
|
|
36
|
+
function idFor(kind, seed) {
|
|
37
|
+
return `${kind}-${createHash("sha1").update(seed).digest("hex").slice(0, 8)}`;
|
|
38
|
+
}
|
|
39
|
+
// ---------- line-level recognizers ----------
|
|
40
|
+
const QUEUE_LINE = /^- \[( |x)\] \((P1|P2|P3)\) (.*)$/;
|
|
41
|
+
const DONE_LINE = /^- \[( |x)\] (.*)$/;
|
|
42
|
+
export function parseQueueLine(line, section) {
|
|
43
|
+
const m = QUEUE_LINE.exec(line);
|
|
44
|
+
if (!m)
|
|
45
|
+
return null;
|
|
46
|
+
const [, mark, priority, text] = m;
|
|
47
|
+
return {
|
|
48
|
+
id: idFor("q", text ?? ""),
|
|
49
|
+
priority: priority,
|
|
50
|
+
text: text ?? "",
|
|
51
|
+
done: mark === "x",
|
|
52
|
+
...(section ? { section } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Split on the LAST ` — `, then peel a trailing ` · YYYY-MM-DD`. Both parts are
|
|
56
|
+
// optional: an entry with neither is still a done entry, it just carries no
|
|
57
|
+
// verifiable ref — which is a fact about the entry, not a parse failure. The
|
|
58
|
+
// old parser treated the same line as nothing at all.
|
|
59
|
+
export function parseDoneLine(line, section) {
|
|
60
|
+
const m = DONE_LINE.exec(line);
|
|
61
|
+
if (!m)
|
|
62
|
+
return null;
|
|
63
|
+
let body = m[2] ?? "";
|
|
64
|
+
let ref;
|
|
65
|
+
let date;
|
|
66
|
+
const cut = body.lastIndexOf(EM_DASH_SEP);
|
|
67
|
+
if (cut !== -1) {
|
|
68
|
+
const tail = body.slice(cut + EM_DASH_SEP.length);
|
|
69
|
+
const dot = tail.lastIndexOf(MIDDOT_SEP);
|
|
70
|
+
if (dot !== -1) {
|
|
71
|
+
const maybeDate = tail.slice(dot + MIDDOT_SEP.length);
|
|
72
|
+
if (ISO_DATE.test(maybeDate)) {
|
|
73
|
+
date = maybeDate;
|
|
74
|
+
ref = tail.slice(0, dot);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// A tail with no date is still a ref, as long as it isn't prose with
|
|
78
|
+
// spaces — refs are `owner/repo#N` or `owner/repo@sha`.
|
|
79
|
+
if (date === undefined && /^\S+$/.test(tail))
|
|
80
|
+
ref = tail;
|
|
81
|
+
if (ref !== undefined)
|
|
82
|
+
body = body.slice(0, cut);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
id: idFor("d", `${body}|${ref ?? ""}|${date ?? ""}`),
|
|
86
|
+
text: body,
|
|
87
|
+
...(ref !== undefined ? { ref } : {}),
|
|
88
|
+
...(date !== undefined ? { date } : {}),
|
|
89
|
+
...(section ? { section } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export function renderQueueLine(item) {
|
|
93
|
+
return `- [${item.done ? "x" : " "}] (${item.priority}) ${item.text}`;
|
|
94
|
+
}
|
|
95
|
+
export function renderDoneLine(entry) {
|
|
96
|
+
let line = `- [x] ${entry.text}`;
|
|
97
|
+
if (entry.ref !== undefined)
|
|
98
|
+
line += `${EM_DASH_SEP}${entry.ref}`;
|
|
99
|
+
if (entry.date !== undefined)
|
|
100
|
+
line += `${MIDDOT_SEP}${entry.date}`;
|
|
101
|
+
return line;
|
|
102
|
+
}
|
|
103
|
+
// ---------- board table ----------
|
|
104
|
+
const TABLE_ROW = /^\|(.*)\|\s*$/;
|
|
105
|
+
const TABLE_ALIGN = /^\|[\s:|-]+\|\s*$/;
|
|
106
|
+
function splitRow(line) {
|
|
107
|
+
const m = TABLE_ROW.exec(line);
|
|
108
|
+
if (!m)
|
|
109
|
+
return [];
|
|
110
|
+
return (m[1] ?? "").split("|").map((c) => c.trim());
|
|
111
|
+
}
|
|
112
|
+
function renderRow(cells) {
|
|
113
|
+
return `| ${cells.join(" | ")} |`;
|
|
114
|
+
}
|
|
115
|
+
function boardCells(r) {
|
|
116
|
+
return [r.lane, r.owner, r.state, r.currentSlice, r.nextGo];
|
|
117
|
+
}
|
|
118
|
+
// Replay the original line when the fields still say what it said; re-render
|
|
119
|
+
// once anything actually changed.
|
|
120
|
+
export function renderBoardRow(r) {
|
|
121
|
+
if (r.raw !== undefined) {
|
|
122
|
+
const cells = splitRow(r.raw);
|
|
123
|
+
if (cells.length === 5 && cells.every((c, i) => c === boardCells(r)[i]))
|
|
124
|
+
return r.raw;
|
|
125
|
+
}
|
|
126
|
+
return renderRow(boardCells(r));
|
|
127
|
+
}
|
|
128
|
+
// Which record kind a `## Heading` opens. `## Queue` → queue items,
|
|
129
|
+
// `## Done` → done entries; anything else closes the record region, so the
|
|
130
|
+
// Cross-project section's bullets stay verbatim prose.
|
|
131
|
+
function sectionKindOf(heading) {
|
|
132
|
+
const h = heading.replace(/^#+\s*/, "").trim().toLowerCase();
|
|
133
|
+
if (h === "queue")
|
|
134
|
+
return "queue";
|
|
135
|
+
if (h === "done")
|
|
136
|
+
return "done";
|
|
137
|
+
return "none";
|
|
138
|
+
}
|
|
139
|
+
export function parseWorkDoc(source) {
|
|
140
|
+
const trailingNewline = source.endsWith("\n");
|
|
141
|
+
const lines = source.split("\n");
|
|
142
|
+
if (trailingNewline)
|
|
143
|
+
lines.pop(); // the split's empty tail, restored on render
|
|
144
|
+
const blocks = [];
|
|
145
|
+
let text = [];
|
|
146
|
+
let kind = "none";
|
|
147
|
+
let subsection;
|
|
148
|
+
let inFence = false;
|
|
149
|
+
const flushText = () => {
|
|
150
|
+
if (text.length)
|
|
151
|
+
blocks.push({ kind: "text", lines: text });
|
|
152
|
+
text = [];
|
|
153
|
+
};
|
|
154
|
+
// Consecutive item lines coalesce into one block; anything in between (a
|
|
155
|
+
// blank line, a subsection heading) flushes as text first and therefore
|
|
156
|
+
// starts a new one, which is what keeps rendering order faithful.
|
|
157
|
+
const pushItem = (item) => {
|
|
158
|
+
flushText();
|
|
159
|
+
const last = blocks[blocks.length - 1];
|
|
160
|
+
if (last?.kind === "queue")
|
|
161
|
+
last.items.push(item);
|
|
162
|
+
else
|
|
163
|
+
blocks.push({ kind: "queue", items: [item] });
|
|
164
|
+
};
|
|
165
|
+
const pushEntry = (entry) => {
|
|
166
|
+
flushText();
|
|
167
|
+
const last = blocks[blocks.length - 1];
|
|
168
|
+
if (last?.kind === "done")
|
|
169
|
+
last.entries.push(entry);
|
|
170
|
+
else
|
|
171
|
+
blocks.push({ kind: "done", entries: [entry] });
|
|
172
|
+
};
|
|
173
|
+
for (let i = 0; i < lines.length; i++) {
|
|
174
|
+
const line = lines[i] ?? "";
|
|
175
|
+
// Fenced blocks are never interpreted — docs/DONE.md pins the done-line
|
|
176
|
+
// FORMAT inside a fence, and parsing that example as an entry would
|
|
177
|
+
// invent a record out of documentation.
|
|
178
|
+
if (/^\s*```/.test(line)) {
|
|
179
|
+
inFence = !inFence;
|
|
180
|
+
text.push(line);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (inFence) {
|
|
184
|
+
text.push(line);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (/^#{1,6}\s/.test(line)) {
|
|
188
|
+
const level = (line.match(/^#+/) ?? [""])[0].length;
|
|
189
|
+
if (level <= 2) {
|
|
190
|
+
kind = sectionKindOf(line);
|
|
191
|
+
subsection = undefined;
|
|
192
|
+
}
|
|
193
|
+
else if (kind !== "none") {
|
|
194
|
+
subsection = line.replace(/^#+\s*/, "").trim();
|
|
195
|
+
}
|
|
196
|
+
text.push(line);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (kind === "queue") {
|
|
200
|
+
const item = parseQueueLine(line, subsection);
|
|
201
|
+
if (item) {
|
|
202
|
+
pushItem(item);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (kind === "done") {
|
|
207
|
+
const entry = parseDoneLine(line, subsection);
|
|
208
|
+
if (entry) {
|
|
209
|
+
pushEntry(entry);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Lanes table: a header row followed by an alignment row.
|
|
214
|
+
if (TABLE_ROW.test(line) && TABLE_ALIGN.test(lines[i + 1] ?? "")) {
|
|
215
|
+
const rows = [];
|
|
216
|
+
let j = i + 2;
|
|
217
|
+
for (; j < lines.length && TABLE_ROW.test(lines[j] ?? ""); j++) {
|
|
218
|
+
const raw = lines[j] ?? "";
|
|
219
|
+
const [lane = "", owner = "", state = "", currentSlice = "", nextGo = ""] = splitRow(raw);
|
|
220
|
+
rows.push({ id: idFor("b", lane), lane, owner, state, currentSlice, nextGo, raw });
|
|
221
|
+
}
|
|
222
|
+
flushText();
|
|
223
|
+
blocks.push({ kind: "board", header: line, align: lines[i + 1] ?? "", rows });
|
|
224
|
+
i = j - 1;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
text.push(line);
|
|
228
|
+
}
|
|
229
|
+
flushText();
|
|
230
|
+
return { trailingNewline, blocks };
|
|
231
|
+
}
|
|
232
|
+
export function renderWorkDoc(doc) {
|
|
233
|
+
const out = [];
|
|
234
|
+
for (const block of doc.blocks) {
|
|
235
|
+
if (block.kind === "text")
|
|
236
|
+
out.push(...block.lines);
|
|
237
|
+
else if (block.kind === "queue")
|
|
238
|
+
out.push(...block.items.map(renderQueueLine));
|
|
239
|
+
else if (block.kind === "done")
|
|
240
|
+
out.push(...block.entries.map(renderDoneLine));
|
|
241
|
+
else {
|
|
242
|
+
out.push(block.header);
|
|
243
|
+
out.push(block.align);
|
|
244
|
+
for (const r of block.rows)
|
|
245
|
+
out.push(renderBoardRow(r));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return out.join("\n") + (doc.trailingNewline ? "\n" : "");
|
|
249
|
+
}
|
|
250
|
+
// ---------- record extraction ----------
|
|
251
|
+
export function queueItemsOf(doc) {
|
|
252
|
+
return doc.blocks.flatMap((b) => (b.kind === "queue" ? b.items : []));
|
|
253
|
+
}
|
|
254
|
+
export function doneEntriesOf(doc) {
|
|
255
|
+
return doc.blocks.flatMap((b) => (b.kind === "done" ? b.entries : []));
|
|
256
|
+
}
|
|
257
|
+
export function boardRowsOf(doc) {
|
|
258
|
+
return doc.blocks.flatMap((b) => (b.kind === "board" ? b.rows : []));
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=work.js.map
|
package/dist/work.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work.js","sourceRoot":"","sources":["../src/work.ts"],"names":[],"mappings":"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"}
|
package/hooks/marker.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The transport-marker rewrite contract, extracted so it is testable.
|
|
2
|
+
//
|
|
3
|
+
// The marker has TWO writers with different authority: attach_agent CREATES
|
|
4
|
+
// it (authoritative — a fresh attach owns every field, including provenance
|
|
5
|
+
// stamps like serverBuildMtime that only the server can know), and the pusher
|
|
6
|
+
// REWRITES it at startup (partial — it owns its pid, target, and the
|
|
7
|
+
// scriptMtime of the code it actually loaded, and must not touch the rest).
|
|
8
|
+
//
|
|
9
|
+
// A rewrite that rebuilds the marker from scratch drops every field the other
|
|
10
|
+
// writer owns. That is not hypothetical: an earlier from-scratch rewrite
|
|
11
|
+
// dropped scriptMtime and silently disabled doctor's stale-pusher-script
|
|
12
|
+
// check for every local pusher — a marker field a rewrite can drop is a
|
|
13
|
+
// health check that can be switched off by accident.
|
|
14
|
+
export function mergeTransportMarker(existing, own) {
|
|
15
|
+
// Spread order IS the contract: fields this writer does not own survive;
|
|
16
|
+
// fields it does own win.
|
|
17
|
+
return { ...(existing && typeof existing === "object" ? existing : {}), ...own };
|
|
18
|
+
}
|
package/hooks/roles.mjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Canonical role identity (Phase 8 Task 4). Dependency-free and
|
|
2
|
+
// side-effect-free: hooks/tier.mjs imports it on the delivery hot path, and it
|
|
3
|
+
// must keep working from a bare checkout with no build step.
|
|
4
|
+
//
|
|
5
|
+
// The problem this exists for: a role's identity used to BE its display string,
|
|
6
|
+
// so renaming it (curator → liaison → aide) churned every id, skill and script
|
|
7
|
+
// that named it. Now the id is frozen and only the name moves.
|
|
8
|
+
//
|
|
9
|
+
// MIRROR: src/roles.ts carries the same logic for the server side — tsconfig's
|
|
10
|
+
// rootDir is `src`, so TypeScript cannot import this file. test/roles.test.mjs
|
|
11
|
+
// asserts the two stay in lockstep; if you change a rule or an id here, change
|
|
12
|
+
// it there too.
|
|
13
|
+
|
|
14
|
+
// Display text → role id. Lowercase, non-alphanumerics collapse to "-".
|
|
15
|
+
export function slugifyRole(role) {
|
|
16
|
+
return String(role ?? "")
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
20
|
+
.replace(/^-+|-+$/g, "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A gate runner is the agent that consumes DONE: reports (QA / coordinator).
|
|
24
|
+
export const GATE_RUNNER_ROLE_IDS = new Set(["qa", "quality", "coordinator", "gate"]);
|
|
25
|
+
// Coordinators countersign work orders (`go`) and in-flight contract
|
|
26
|
+
// amendments (`scope`).
|
|
27
|
+
export const COORDINATOR_ROLE_IDS = new Set(["coordinator"]);
|
|
28
|
+
|
|
29
|
+
// Normalize any role shape into {roleId, displayName, explicit}.
|
|
30
|
+
//
|
|
31
|
+
// `explicit` records whether the id was DECLARED (registry entry carries a
|
|
32
|
+
// frozen roleId) or DERIVED from display text. It is the difference between
|
|
33
|
+
// "this agent's identity is `gate`" and "this agent's name happens to contain
|
|
34
|
+
// the word gate" — declared ids match exactly, derived ones fall back to the
|
|
35
|
+
// legacy word match below so un-migrated registries keep behaving as they did.
|
|
36
|
+
//
|
|
37
|
+
// Accepts: a plain string, {roleId, displayName}, or an AgentEntry ({role,
|
|
38
|
+
// roleId}) passed straight from the registry.
|
|
39
|
+
export function resolveRole(role) {
|
|
40
|
+
if (role === null || role === undefined) return undefined;
|
|
41
|
+
if (typeof role === "string") {
|
|
42
|
+
const roleId = slugifyRole(role);
|
|
43
|
+
return roleId ? { roleId, displayName: role, explicit: false } : undefined;
|
|
44
|
+
}
|
|
45
|
+
if (typeof role === "object") {
|
|
46
|
+
const declared = typeof role.roleId === "string" ? slugifyRole(role.roleId) : "";
|
|
47
|
+
const name =
|
|
48
|
+
typeof role.displayName === "string" && role.displayName
|
|
49
|
+
? role.displayName
|
|
50
|
+
: typeof role.role === "string" && role.role
|
|
51
|
+
? role.role
|
|
52
|
+
: undefined;
|
|
53
|
+
if (declared) return { roleId: declared, displayName: name ?? declared, explicit: true };
|
|
54
|
+
if (name) return resolveRole(name);
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Does this role carry one of `allowedIds`?
|
|
60
|
+
//
|
|
61
|
+
// Declared ids must match exactly — that is the point of freezing them. A
|
|
62
|
+
// derived id additionally matches on any of its hyphen-separated words, which
|
|
63
|
+
// reproduces the pre-Task-4 prose regex (/\b(qa|quality|coordinator|gate)\b/i)
|
|
64
|
+
// for registries that never declared a roleId.
|
|
65
|
+
export function roleMatches(role, allowedIds) {
|
|
66
|
+
const resolved = resolveRole(role);
|
|
67
|
+
if (!resolved) return false;
|
|
68
|
+
if (allowedIds.has(resolved.roleId)) return true;
|
|
69
|
+
if (resolved.explicit) return false;
|
|
70
|
+
return resolved.roleId.split("-").some((word) => allowedIds.has(word));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isGateRunner(role) {
|
|
74
|
+
return roleMatches(role, GATE_RUNNER_ROLE_IDS);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isCoordinator(role) {
|
|
78
|
+
return roleMatches(role, COORDINATOR_ROLE_IDS);
|
|
79
|
+
}
|