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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { STATUSES } from '../../domain.js';
|
|
2
|
+
import { parseLegacyBody } from './legacy.js';
|
|
3
|
+
import { splitFrontmatter } from './frontmatter.js';
|
|
4
|
+
/** `<NN>-<rest>.md` — `NN` carries sort order and nothing else. */
|
|
5
|
+
const FILENAME_ORDER = /^(\d+)/;
|
|
6
|
+
/**
|
|
7
|
+
* Parse one Ticket file. Schema-conformant files read from their frontmatter;
|
|
8
|
+
* everything else falls through to the Legacy parser and is flagged, because a
|
|
9
|
+
* server that is useless until its repo is migrated never gets installed.
|
|
10
|
+
*/
|
|
11
|
+
export function parseTicket(effort, file, fallbackOrder, revision) {
|
|
12
|
+
const { fields, body } = splitFrontmatter(file.contents);
|
|
13
|
+
const order = readOrder(file.filename) ?? fallbackOrder;
|
|
14
|
+
if (fields === undefined)
|
|
15
|
+
return parseLegacy(effort, order, file.contents, revision);
|
|
16
|
+
const id = text(fields['id']);
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
handle: handleFor(id, effort, order),
|
|
20
|
+
title: text(fields['title']) ?? '(untitled)',
|
|
21
|
+
kind: readKind(fields['kind']),
|
|
22
|
+
type: text(fields['type']),
|
|
23
|
+
status: readStatus(text(fields['status'])) ?? 'open',
|
|
24
|
+
triage: text(fields['triage']),
|
|
25
|
+
answerGist: text(fields['answer_gist']),
|
|
26
|
+
droppedReason: text(fields['dropped_reason']),
|
|
27
|
+
claimedBy: text(fields['claimed_by']),
|
|
28
|
+
claimedAt: text(fields['claimed_at']),
|
|
29
|
+
blockedBy: readEdgeList(fields['blocked_by']),
|
|
30
|
+
effort,
|
|
31
|
+
order,
|
|
32
|
+
legacy: false,
|
|
33
|
+
unrecognizedStatus: undefined,
|
|
34
|
+
collapsedRefs: [],
|
|
35
|
+
revision,
|
|
36
|
+
body: body.trim(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function parseLegacy(effort, order, contents, revision) {
|
|
40
|
+
const inferred = parseLegacyBody(contents);
|
|
41
|
+
return {
|
|
42
|
+
id: inferred.id,
|
|
43
|
+
handle: handleFor(inferred.id, effort, order),
|
|
44
|
+
title: inferred.title,
|
|
45
|
+
kind: inferred.kind,
|
|
46
|
+
type: inferred.type,
|
|
47
|
+
status: inferred.status,
|
|
48
|
+
triage: inferred.triage,
|
|
49
|
+
// A Legacy file records its answer as prose in the body, not as a field.
|
|
50
|
+
answerGist: undefined,
|
|
51
|
+
droppedReason: undefined,
|
|
52
|
+
claimedBy: undefined,
|
|
53
|
+
claimedAt: undefined,
|
|
54
|
+
blockedBy: inferred.blockedBy,
|
|
55
|
+
effort,
|
|
56
|
+
order,
|
|
57
|
+
legacy: true,
|
|
58
|
+
unrecognizedStatus: inferred.unrecognizedStatus,
|
|
59
|
+
collapsedRefs: inferred.collapsedRefs,
|
|
60
|
+
revision,
|
|
61
|
+
body: contents.trim(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A Ticket with no id still has to be nameable, or a whole Effort of Legacy Tickets
|
|
66
|
+
* has no route to its own bodies. `<effort>#<order>` is that fallback — an
|
|
67
|
+
* address, deliberately not an id: it is not repo-stable and never appears as
|
|
68
|
+
* an Edge.
|
|
69
|
+
*/
|
|
70
|
+
function handleFor(id, effort, order) {
|
|
71
|
+
return id ?? `${effort}#${String(order)}`;
|
|
72
|
+
}
|
|
73
|
+
/** `<NN>-...` gives sort order. A file with no numeric prefix has none of its own. */
|
|
74
|
+
function readOrder(filename) {
|
|
75
|
+
const match = FILENAME_ORDER.exec(filename);
|
|
76
|
+
return match?.[1] === undefined ? undefined : Number(match[1]);
|
|
77
|
+
}
|
|
78
|
+
function readKind(value) {
|
|
79
|
+
return text(value) === 'decision' ? 'decision' : 'build';
|
|
80
|
+
}
|
|
81
|
+
function readStatus(value) {
|
|
82
|
+
return STATUSES.find(status => status === value);
|
|
83
|
+
}
|
|
84
|
+
/** A `blocked_by` list. Anything that is not a list of scalars reads as no Edges. */
|
|
85
|
+
function readEdgeList(value) {
|
|
86
|
+
if (!Array.isArray(value))
|
|
87
|
+
return [];
|
|
88
|
+
return value.map(entry => text(entry)).filter((entry) => entry !== undefined);
|
|
89
|
+
}
|
|
90
|
+
/** YAML scalars arrive as numbers and booleans too; only non-empty strings count. */
|
|
91
|
+
function text(value) {
|
|
92
|
+
if (value === null || value === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
const asText = String(value).trim();
|
|
95
|
+
return asText === '' ? undefined : asText;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=ticket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticket.js","sourceRoot":"","sources":["../../../src/storage/markdown/ticket.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A revision token: the file's modification time and size as read. Opaque above
|
|
3
|
+
* the seam — only this module builds one or compares two.
|
|
4
|
+
*/
|
|
5
|
+
export declare function revisionOf(mtimeMs: number, size: number): string;
|
|
6
|
+
export declare function currentRevision(path: string): Promise<string | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* Take an exclusive guard on one revision of one file, or fail.
|
|
9
|
+
*
|
|
10
|
+
* `open(..., 'wx')` is an atomic create-if-absent — the same primitive the spec
|
|
11
|
+
* names for id allocation. Racing processes all read the same revision, so they
|
|
12
|
+
* all derive the same guard name and exactly one creation succeeds. That is the
|
|
13
|
+
* compare-and-set an optimistic mtime check cannot provide across processes:
|
|
14
|
+
* without it, two sessions genuinely do both come away believing they hold the
|
|
15
|
+
* same Ticket.
|
|
16
|
+
*
|
|
17
|
+
* The guard is keyed to the revision it guards, so it cannot wedge anything. Any
|
|
18
|
+
* successful write changes the revision, which changes the name, so a guard left
|
|
19
|
+
* behind by a crash names a revision that will never be current again. It is
|
|
20
|
+
* swept on sight anyway once it is older than {@link GUARD_STALE_MS}.
|
|
21
|
+
*
|
|
22
|
+
* See ADR 0004 for why this is a guard rather than a lock file.
|
|
23
|
+
*/
|
|
24
|
+
export declare function withGuard<T>(path: string, revision: string, write: () => Promise<T>): Promise<T>;
|
|
25
|
+
/** Raised when another process holds the guard for this exact revision. */
|
|
26
|
+
export declare class GuardHeld extends Error {
|
|
27
|
+
constructor();
|
|
28
|
+
}
|
|
29
|
+
export declare function writeAtomically(path: string, contents: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* The first half of {@link writeAtomically}, for a caller that has several files
|
|
32
|
+
* to land together: write the contents beside the target and hand back the
|
|
33
|
+
* temporary path, leaving the rename to the caller.
|
|
34
|
+
*
|
|
35
|
+
* Everything that can fail — a full disk, a bad path, a name too long — fails
|
|
36
|
+
* here, where nothing is visible yet. That is what lets a batch stage every file
|
|
37
|
+
* before renaming any, and so be all-or-none rather than merely per-file atomic.
|
|
38
|
+
*/
|
|
39
|
+
export declare function stage(path: string, contents: string): Promise<string>;
|
|
40
|
+
/** Discard a staged file. Never throws: it runs on the path where something already went wrong. */
|
|
41
|
+
export declare function unstage(temporary: string): Promise<void>;
|
|
42
|
+
//# sourceMappingURL=write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/storage/markdown/write.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO/E;AAqBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAED,2EAA2E;AAC3E,qBAAa,SAAU,SAAQ,KAAK;IAClC,cAGC;CACF;AAsBD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASnF;AAED;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW3E;AAED,mGAAmG;AACnG,wBAAsB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9D"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { rename, stat, writeFile, unlink } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* A revision token: the file's modification time and size as read. Opaque above
|
|
5
|
+
* the seam — only this module builds one or compares two.
|
|
6
|
+
*/
|
|
7
|
+
export function revisionOf(mtimeMs, size) {
|
|
8
|
+
return `${String(mtimeMs)}:${String(size)}`;
|
|
9
|
+
}
|
|
10
|
+
export async function currentRevision(path) {
|
|
11
|
+
try {
|
|
12
|
+
const info = await stat(path);
|
|
13
|
+
return revisionOf(info.mtimeMs, info.size);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Write to a temporary file in the same directory, then rename over the target.
|
|
21
|
+
* Rename within a directory is atomic, so a reader sees either the old file or
|
|
22
|
+
* the new one and an interrupted write cannot leave a partial Ticket.
|
|
23
|
+
*
|
|
24
|
+
* The temporary name carries the process id and a counter rather than a random
|
|
25
|
+
* suffix, so a crashed write leaves something identifiable rather than a name
|
|
26
|
+
* nobody can attribute. There is no lock file: a crashed session must never be
|
|
27
|
+
* able to wedge the tracker.
|
|
28
|
+
*/
|
|
29
|
+
let sequence = 0;
|
|
30
|
+
/**
|
|
31
|
+
* How long a guard may sit before it is assumed to belong to a crashed process.
|
|
32
|
+
* Only ever consulted for a guard whose revision is still current, which is a
|
|
33
|
+
* window of milliseconds in practice.
|
|
34
|
+
*/
|
|
35
|
+
const GUARD_STALE_MS = 30_000;
|
|
36
|
+
/**
|
|
37
|
+
* Take an exclusive guard on one revision of one file, or fail.
|
|
38
|
+
*
|
|
39
|
+
* `open(..., 'wx')` is an atomic create-if-absent — the same primitive the spec
|
|
40
|
+
* names for id allocation. Racing processes all read the same revision, so they
|
|
41
|
+
* all derive the same guard name and exactly one creation succeeds. That is the
|
|
42
|
+
* compare-and-set an optimistic mtime check cannot provide across processes:
|
|
43
|
+
* without it, two sessions genuinely do both come away believing they hold the
|
|
44
|
+
* same Ticket.
|
|
45
|
+
*
|
|
46
|
+
* The guard is keyed to the revision it guards, so it cannot wedge anything. Any
|
|
47
|
+
* successful write changes the revision, which changes the name, so a guard left
|
|
48
|
+
* behind by a crash names a revision that will never be current again. It is
|
|
49
|
+
* swept on sight anyway once it is older than {@link GUARD_STALE_MS}.
|
|
50
|
+
*
|
|
51
|
+
* See ADR 0004 for why this is a guard rather than a lock file.
|
|
52
|
+
*/
|
|
53
|
+
export async function withGuard(path, revision, write) {
|
|
54
|
+
const guard = guardPath(path, revision);
|
|
55
|
+
try {
|
|
56
|
+
await writeFile(guard, `${String(process.pid)}\n`, { flag: 'wx' });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error?.code !== 'EEXIST')
|
|
60
|
+
throw error;
|
|
61
|
+
if (!(await breakIfStale(guard)))
|
|
62
|
+
return Promise.reject(new GuardHeld());
|
|
63
|
+
return withGuard(path, revision, write);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
return await write();
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await unlink(guard).catch(() => { });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Raised when another process holds the guard for this exact revision. */
|
|
73
|
+
export class GuardHeld extends Error {
|
|
74
|
+
constructor() {
|
|
75
|
+
super('another session is writing this Ticket right now');
|
|
76
|
+
this.name = 'GuardHeld';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function breakIfStale(guard) {
|
|
80
|
+
try {
|
|
81
|
+
const info = await stat(guard);
|
|
82
|
+
if (Date.now() - info.mtimeMs < GUARD_STALE_MS)
|
|
83
|
+
return false;
|
|
84
|
+
await unlink(guard);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// It vanished under us, which means the holder finished. Retrying is right.
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Beside the file, hidden, and not `.md`, so no scan ever mistakes it for a
|
|
94
|
+
* Ticket. The revision is sanitized because it goes into a filename.
|
|
95
|
+
*/
|
|
96
|
+
function guardPath(path, revision) {
|
|
97
|
+
return join(dirname(path), `.${basename(path)}.${revision.replace(/[^\w.-]/g, '_')}.guard`);
|
|
98
|
+
}
|
|
99
|
+
export async function writeAtomically(path, contents) {
|
|
100
|
+
const temporary = await stage(path, contents);
|
|
101
|
+
try {
|
|
102
|
+
await rename(temporary, path);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
await unstage(temporary);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The first half of {@link writeAtomically}, for a caller that has several files
|
|
111
|
+
* to land together: write the contents beside the target and hand back the
|
|
112
|
+
* temporary path, leaving the rename to the caller.
|
|
113
|
+
*
|
|
114
|
+
* Everything that can fail — a full disk, a bad path, a name too long — fails
|
|
115
|
+
* here, where nothing is visible yet. That is what lets a batch stage every file
|
|
116
|
+
* before renaming any, and so be all-or-none rather than merely per-file atomic.
|
|
117
|
+
*/
|
|
118
|
+
export async function stage(path, contents) {
|
|
119
|
+
sequence += 1;
|
|
120
|
+
const temporary = join(dirname(path), `.frontier-${String(process.pid)}-${String(sequence)}.tmp`);
|
|
121
|
+
try {
|
|
122
|
+
await writeFile(temporary, contents, 'utf8');
|
|
123
|
+
return temporary;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
await unstage(temporary);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Discard a staged file. Never throws: it runs on the path where something already went wrong. */
|
|
131
|
+
export async function unstage(temporary) {
|
|
132
|
+
await unlink(temporary).catch(() => { });
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../../src/storage/markdown/write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Kind, Ticket, TicketDraft, TriageRole } from '../domain.ts';
|
|
3
|
+
export declare const createTicketsInputSchema: {
|
|
4
|
+
effort: z.ZodString;
|
|
5
|
+
create: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
tickets: z.ZodArray<z.ZodObject<{
|
|
7
|
+
key: z.ZodOptional<z.ZodString>;
|
|
8
|
+
title: z.ZodString;
|
|
9
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
10
|
+
build: "build";
|
|
11
|
+
decision: "decision";
|
|
12
|
+
}>>;
|
|
13
|
+
type: z.ZodOptional<z.ZodString>;
|
|
14
|
+
triage: z.ZodOptional<z.ZodEnum<{
|
|
15
|
+
"needs-info": "needs-info";
|
|
16
|
+
"needs-triage": "needs-triage";
|
|
17
|
+
"ready-for-agent": "ready-for-agent";
|
|
18
|
+
"ready-for-human": "ready-for-human";
|
|
19
|
+
wontfix: "wontfix";
|
|
20
|
+
}>>;
|
|
21
|
+
blocked_by: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
22
|
+
body: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, z.core.$strip>>;
|
|
24
|
+
root: z.ZodOptional<z.ZodString>;
|
|
25
|
+
};
|
|
26
|
+
export declare const createTicketsDescription: string;
|
|
27
|
+
export interface DraftRequest {
|
|
28
|
+
readonly key?: string | undefined;
|
|
29
|
+
readonly title: string;
|
|
30
|
+
readonly kind?: Kind | undefined;
|
|
31
|
+
readonly type?: string | undefined;
|
|
32
|
+
readonly triage?: TriageRole | undefined;
|
|
33
|
+
readonly blocked_by?: readonly string[] | undefined;
|
|
34
|
+
readonly body?: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
export interface PlannedBatch {
|
|
37
|
+
readonly drafts: readonly TicketDraft[];
|
|
38
|
+
/**
|
|
39
|
+
* The cycle check, re-run against the ids the batch is actually given and the
|
|
40
|
+
* workspace as it looks under the driver's reservations. A Ticket already on
|
|
41
|
+
* disk can declare an Edge on an id nobody has minted yet, and minting exactly
|
|
42
|
+
* that id is the moment that dangling Edge becomes a loop — which is invisible
|
|
43
|
+
* until the ids are real.
|
|
44
|
+
*
|
|
45
|
+
* It takes the Tickets rather than closing over the ones read here, because
|
|
46
|
+
* the read that produced those came before the reservations and may have
|
|
47
|
+
* missed the very Ticket whose dangling Edge is about to close.
|
|
48
|
+
*/
|
|
49
|
+
readonly validate: (ids: readonly string[], tickets: readonly Ticket[]) => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Turn the request into drafts, refusing anything the schema or the graph does
|
|
53
|
+
* not allow. Everything resolvable without an id is settled here, so by the time
|
|
54
|
+
* the driver mints one the only rule left to check is the one that needed it.
|
|
55
|
+
*/
|
|
56
|
+
export declare function planBatch(requests: readonly DraftRequest[], existing: readonly Ticket[]): PlannedBatch;
|
|
57
|
+
/**
|
|
58
|
+
* One line per Ticket: the id it was minted and the Edges as resolved — the two
|
|
59
|
+
* things a caller cannot work out for itself. The filename is deliberately not
|
|
60
|
+
* here: it is cosmetic, and the id is what everything addresses a Ticket by.
|
|
61
|
+
*/
|
|
62
|
+
export declare function renderCreated(effort: string, created: readonly Ticket[]): string;
|
|
63
|
+
//# sourceMappingURL=create-tickets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-tickets.d.ts","sourceRoot":"","sources":["../../src/tools/create-tickets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK1E,eAAO,MAAM,wBAAwB;IACnC,MAAM;IACN,MAAM;IAMN,OAAO;;;;;;;;;;;;;;;;;;IA8BP,IAAI;CACL,CAAC;AAEF,eAAO,MAAM,wBAAwB,QAGmD,CAAC;AAEzF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IACxC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;CACjF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,YAAY,CAgBd;AAgHD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAgBhF"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { MINTED_ID, TRIAGE_ROLES } from '../domain.js';
|
|
3
|
+
import { cycleThrough, renderCycle, resolveDraftEdges } from '../edges.js';
|
|
4
|
+
import { indexById } from '../frontier.js';
|
|
5
|
+
export const createTicketsInputSchema = {
|
|
6
|
+
effort: z.string().describe('Effort slug to create them in.'),
|
|
7
|
+
create: z
|
|
8
|
+
.boolean()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe('Start the Effort if the workspace does not hold it. Otherwise an unknown slug fails.'),
|
|
11
|
+
tickets: z
|
|
12
|
+
.array(z.object({
|
|
13
|
+
key: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Your own temporary name for this Ticket, so siblings can declare Edges on it ' +
|
|
18
|
+
'before it has an id. Never stored. May not look like T<n>.'),
|
|
19
|
+
title: z.string().min(1),
|
|
20
|
+
kind: z.enum(['build', 'decision']).optional().describe('Defaults to build.'),
|
|
21
|
+
type: z
|
|
22
|
+
.string()
|
|
23
|
+
.min(1)
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('research / prototype / grilling / task. Only on a decision Ticket.'),
|
|
26
|
+
triage: z.enum(TRIAGE_ROLES).optional(),
|
|
27
|
+
blocked_by: z
|
|
28
|
+
.array(z.string().min(1))
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Edges: a real Ticket id from anywhere in the repo, or a key declared in this call.'),
|
|
31
|
+
body: z.string().optional().describe('The prose, written verbatim.'),
|
|
32
|
+
}))
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe('The whole set, in the order they should be numbered.'),
|
|
35
|
+
root: z.string().optional().describe('Workspace directory. Defaults to the session workspace.'),
|
|
36
|
+
};
|
|
37
|
+
export const createTicketsDescription = 'Publish a whole breakdown in one call. Edges may name a sibling by a temporary key you choose; ' +
|
|
38
|
+
'the server mints repo-unique ids, numbers the files, and resolves the keys atomically — a ' +
|
|
39
|
+
'partial failure creates nothing. Cycles are refused. An unknown Effort needs create.';
|
|
40
|
+
/**
|
|
41
|
+
* Turn the request into drafts, refusing anything the schema or the graph does
|
|
42
|
+
* not allow. Everything resolvable without an id is settled here, so by the time
|
|
43
|
+
* the driver mints one the only rule left to check is the one that needed it.
|
|
44
|
+
*/
|
|
45
|
+
export function planBatch(requests, existing) {
|
|
46
|
+
const drafts = requests.map(request => toDraft(request));
|
|
47
|
+
const keys = declaredKeys(drafts);
|
|
48
|
+
const provisional = drafts.map((draft, index) => draft.key ?? placeholderFor(index, keys));
|
|
49
|
+
checkEdges(drafts, keys, existing);
|
|
50
|
+
// Named by their keys, so a loop between two drafts reads in the caller's own
|
|
51
|
+
// vocabulary rather than in ids they have not seen yet.
|
|
52
|
+
checkCycles(drafts, provisional, existing);
|
|
53
|
+
return {
|
|
54
|
+
drafts,
|
|
55
|
+
validate: (ids, tickets) => {
|
|
56
|
+
checkCycles(drafts, ids, tickets, resolveDraftEdges(drafts, ids));
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function toDraft(request) {
|
|
61
|
+
if (request.type !== undefined && (request.kind ?? 'build') !== 'decision') {
|
|
62
|
+
throw new Error(`"${request.title}" is a build Ticket, so it has no type — that field records which ` +
|
|
63
|
+
'wayfinder shape a decision Ticket is.');
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
key: request.key,
|
|
67
|
+
title: request.title,
|
|
68
|
+
kind: request.kind ?? 'build',
|
|
69
|
+
type: request.type,
|
|
70
|
+
triage: request.triage,
|
|
71
|
+
blockedBy: request.blocked_by ?? [],
|
|
72
|
+
body: request.body,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function declaredKeys(drafts) {
|
|
76
|
+
const keys = new Set();
|
|
77
|
+
for (const draft of drafts) {
|
|
78
|
+
if (draft.key === undefined)
|
|
79
|
+
continue;
|
|
80
|
+
// A key shaped like an id is ambiguous: an Edge naming `T8` could mean the
|
|
81
|
+
// sibling draft or the Ticket already answering to it, and the server would
|
|
82
|
+
// have to guess which.
|
|
83
|
+
if (MINTED_ID.test(draft.key)) {
|
|
84
|
+
throw new Error(`Temporary key "${draft.key}" looks like a Ticket id. Pick a name that does not, ` +
|
|
85
|
+
'so an Edge naming it is never ambiguous.');
|
|
86
|
+
}
|
|
87
|
+
if (keys.has(draft.key))
|
|
88
|
+
throw new Error(`Temporary key "${draft.key}" is used twice.`);
|
|
89
|
+
keys.add(draft.key);
|
|
90
|
+
}
|
|
91
|
+
return keys;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* An Edge is either a real id — which may point at nothing yet, since a dangling
|
|
95
|
+
* Edge is a Board warning rather than a refusal — or a key declared in this same
|
|
96
|
+
* call. Anything else is a typo, and letting it through would quietly turn a
|
|
97
|
+
* mistyped key into a dangling Edge nobody notices.
|
|
98
|
+
*/
|
|
99
|
+
function checkEdges(drafts, keys, existing) {
|
|
100
|
+
const ids = indexById(existing);
|
|
101
|
+
for (const draft of drafts) {
|
|
102
|
+
for (const edge of draft.blockedBy) {
|
|
103
|
+
if (keys.has(edge) || ids.has(edge) || MINTED_ID.test(edge))
|
|
104
|
+
continue;
|
|
105
|
+
throw new Error(`"${draft.title}" is blocked by "${edge}", which is neither a Ticket id nor a key ` +
|
|
106
|
+
`declared in this call. Declared keys: ${[...keys].join(', ') || '(none)'}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The graph the batch would produce: existing Tickets by id, plus the drafts
|
|
112
|
+
* under `names` — their temporary keys before allocation, their real ids after.
|
|
113
|
+
*
|
|
114
|
+
* A draft with no key is unreachable by any sibling, but still needs checking:
|
|
115
|
+
* once it has an id, its own Edges can lead back to it through the Tickets
|
|
116
|
+
* already on disk.
|
|
117
|
+
*/
|
|
118
|
+
function checkCycles(drafts, names, existing, resolve = edge => edge) {
|
|
119
|
+
const ids = indexById(existing);
|
|
120
|
+
const nodes = new Map();
|
|
121
|
+
const edges = new Map();
|
|
122
|
+
drafts.forEach((draft, index) => {
|
|
123
|
+
const name = names[index] ?? '';
|
|
124
|
+
nodes.set(name, draft);
|
|
125
|
+
edges.set(name, draft.blockedBy.map(resolve));
|
|
126
|
+
});
|
|
127
|
+
const edgesOf = id => edges.get(id) ?? ids.get(id)?.blockedBy ?? [];
|
|
128
|
+
for (const [node, draft] of nodes) {
|
|
129
|
+
const cycle = cycleThrough(node, edgesOf);
|
|
130
|
+
if (cycle !== undefined) {
|
|
131
|
+
throw new Error(`"${draft.title}" would close a cycle in the Edge graph: ${renderCycle(cycle)}. ` +
|
|
132
|
+
'Nothing in a cycle is ever takeable, so nothing was created.');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** A name for a keyless draft that no Edge can collide with. */
|
|
137
|
+
function placeholderFor(index, keys) {
|
|
138
|
+
let name = `#${String(index)}`;
|
|
139
|
+
while (keys.has(name))
|
|
140
|
+
name = `${name}.`;
|
|
141
|
+
return name;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* One line per Ticket: the id it was minted and the Edges as resolved — the two
|
|
145
|
+
* things a caller cannot work out for itself. The filename is deliberately not
|
|
146
|
+
* here: it is cosmetic, and the id is what everything addresses a Ticket by.
|
|
147
|
+
*/
|
|
148
|
+
export function renderCreated(effort, created) {
|
|
149
|
+
const lines = [`${effort}: ${String(created.length)} created`];
|
|
150
|
+
for (const ticket of created) {
|
|
151
|
+
lines.push([
|
|
152
|
+
ticket.id ?? ticket.handle,
|
|
153
|
+
ticket.title,
|
|
154
|
+
ticket.blockedBy.length === 0 ? undefined : `blocked_by=${ticket.blockedBy.join(',')}`,
|
|
155
|
+
]
|
|
156
|
+
.filter(part => part !== undefined)
|
|
157
|
+
.join(' '));
|
|
158
|
+
}
|
|
159
|
+
return lines.join('\n');
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=create-tickets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-tickets.js","sourceRoot":"","sources":["../../src/tools/create-tickets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { MapDocument, MapEdit } from '../domain.ts';
|
|
3
|
+
export declare const editMapInputSchema: {
|
|
4
|
+
effort: z.ZodString;
|
|
5
|
+
create: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
destination: z.ZodOptional<z.ZodString>;
|
|
7
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
8
|
+
add_fog: z.ZodOptional<z.ZodString>;
|
|
9
|
+
graduate_fog: z.ZodOptional<z.ZodString>;
|
|
10
|
+
rule_out: z.ZodOptional<z.ZodString>;
|
|
11
|
+
expected_revision: z.ZodOptional<z.ZodString>;
|
|
12
|
+
root: z.ZodOptional<z.ZodString>;
|
|
13
|
+
};
|
|
14
|
+
export declare const editMapDescription: string;
|
|
15
|
+
export interface EditMapRequest {
|
|
16
|
+
readonly destination?: string | undefined;
|
|
17
|
+
readonly notes?: string | undefined;
|
|
18
|
+
readonly add_fog?: string | undefined;
|
|
19
|
+
readonly graduate_fog?: string | undefined;
|
|
20
|
+
readonly rule_out?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Turn the tool args into the edit the driver applies. */
|
|
23
|
+
export declare function mapEditFor(request: EditMapRequest): MapEdit;
|
|
24
|
+
/**
|
|
25
|
+
* Destination and Notes for orientation, then the fog and out-of-scope lists.
|
|
26
|
+
* Never the whole Map body, and never Decisions-so-far — that is derived.
|
|
27
|
+
*/
|
|
28
|
+
export declare function renderMap(effort: string, document: MapDocument): string;
|
|
29
|
+
//# sourceMappingURL=edit-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit-map.d.ts","sourceRoot":"","sources":["../../src/tools/edit-map.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEzD,eAAO,MAAM,kBAAkB;IAC7B,MAAM;IACN,MAAM;IAQN,WAAW;IACX,KAAK;IACL,OAAO;IACP,YAAY;IAKZ,QAAQ;IAKR,iBAAiB;IAQjB,IAAI;CACL,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAIyE,CAAC;AAEzG,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAQ3D;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,GAAG,MAAM,CAwBvE"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const editMapInputSchema = {
|
|
3
|
+
effort: z.string().describe('Effort slug whose Map to read or edit.'),
|
|
4
|
+
create: z
|
|
5
|
+
.boolean()
|
|
6
|
+
.optional()
|
|
7
|
+
.describe('Start a missing Effort and its Map. Only for Effort/Map that do not exist yet — ' +
|
|
8
|
+
'not a flag to send on every call. Does not change revision rules for mutations on an ' +
|
|
9
|
+
'existing Map; create with no section fields reads an existing Map like a plain read.'),
|
|
10
|
+
destination: z.string().optional().describe('Replace the Destination section.'),
|
|
11
|
+
notes: z.string().optional().describe('Replace the Notes section.'),
|
|
12
|
+
add_fog: z.string().min(1).optional().describe('Append a fog patch under Not yet specified.'),
|
|
13
|
+
graduate_fog: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Remove a fog patch matched on its text. Fails when none match.'),
|
|
18
|
+
rule_out: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Append a hand-ruled item under Out of scope. Never touches Decisions-so-far.'),
|
|
23
|
+
expected_revision: z
|
|
24
|
+
.string()
|
|
25
|
+
.min(1)
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Revision from a prior edit_map read. Required when overwriting an existing Map; ' +
|
|
28
|
+
'omitted when creating one.'),
|
|
29
|
+
root: z.string().optional().describe('Workspace directory. Defaults to the session workspace.'),
|
|
30
|
+
};
|
|
31
|
+
export const editMapDescription = "Read a Map's Destination and Notes, or edit one typed section — set Destination or Notes, " +
|
|
32
|
+
'add or graduate a fog patch, rule something out of scope — without rewriting the others. ' +
|
|
33
|
+
'Decisions-so-far is generated from resolved Tickets and never accepted as input. ' +
|
|
34
|
+
'Mutations pass expected_revision from a prior read so concurrent sessions cannot clobber each other.';
|
|
35
|
+
/** Turn the tool args into the edit the driver applies. */
|
|
36
|
+
export function mapEditFor(request) {
|
|
37
|
+
return {
|
|
38
|
+
...(request.destination !== undefined ? { destination: request.destination } : {}),
|
|
39
|
+
...(request.notes !== undefined ? { notes: request.notes } : {}),
|
|
40
|
+
...(request.add_fog !== undefined ? { addFog: request.add_fog } : {}),
|
|
41
|
+
...(request.graduate_fog !== undefined ? { graduateFog: request.graduate_fog } : {}),
|
|
42
|
+
...(request.rule_out !== undefined ? { ruleOut: request.rule_out } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Destination and Notes for orientation, then the fog and out-of-scope lists.
|
|
47
|
+
* Never the whole Map body, and never Decisions-so-far — that is derived.
|
|
48
|
+
*/
|
|
49
|
+
export function renderMap(effort, document) {
|
|
50
|
+
const lines = [`effort: ${effort}`, `map revision: ${document.revision}`];
|
|
51
|
+
if (document.destination !== undefined) {
|
|
52
|
+
lines.push(labelled('destination', document.destination));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
lines.push('destination: (none)');
|
|
56
|
+
}
|
|
57
|
+
if (document.notes !== undefined) {
|
|
58
|
+
lines.push(labelled('notes', document.notes));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
lines.push('notes: (none)');
|
|
62
|
+
}
|
|
63
|
+
lines.push('', 'not yet specified:');
|
|
64
|
+
if (document.notYetSpecified.length === 0)
|
|
65
|
+
lines.push(' (none)');
|
|
66
|
+
else
|
|
67
|
+
for (const patch of document.notYetSpecified)
|
|
68
|
+
lines.push(` - ${patch}`);
|
|
69
|
+
lines.push('', 'out of scope:');
|
|
70
|
+
if (document.outOfScope.length === 0)
|
|
71
|
+
lines.push(' (none)');
|
|
72
|
+
else
|
|
73
|
+
for (const item of document.outOfScope)
|
|
74
|
+
lines.push(` - ${item}`);
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
77
|
+
function labelled(label, prose) {
|
|
78
|
+
const [first, ...rest] = prose.split('\n');
|
|
79
|
+
return [`${label}: ${first}`, ...rest.map(line => ` ${line}`)].join('\n');
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=edit-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit-map.js","sourceRoot":"","sources":["../../src/tools/edit-map.ts"],"names":[],"mappings":"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"}
|