@cairn-tool/cairn 2.1.0 → 3.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 +4 -3
- package/dist/agent/conditionals.d.ts +43 -0
- package/dist/agent/conditionals.js +314 -0
- package/dist/agent/conditionals.js.map +1 -0
- package/dist/agent/install/config.d.ts +47 -0
- package/dist/agent/install/config.js +156 -0
- package/dist/agent/install/config.js.map +1 -0
- package/dist/agent/install/index.d.ts +95 -14
- package/dist/agent/install/index.js +460 -188
- package/dist/agent/install/index.js.map +1 -1
- package/dist/agent/parser.js +15 -41
- package/dist/agent/parser.js.map +1 -1
- package/dist/agent/render.d.ts +9 -1
- package/dist/agent/render.js +12 -9
- package/dist/agent/render.js.map +1 -1
- package/dist/agent/types.d.ts +2 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/agent/verify/compare.d.ts +10 -1
- package/dist/agent/verify/compare.js +1 -1
- package/dist/agent/verify/compare.js.map +1 -1
- package/dist/agent/verify/config.js +1 -1
- package/dist/agent/verify/config.js.map +1 -1
- package/dist/agent/verify/index.js +20 -4
- package/dist/agent/verify/index.js.map +1 -1
- package/dist/agent/verify/resolve.d.ts +1 -0
- package/dist/agent/verify/resolve.js +2 -2
- package/dist/agent/verify/resolve.js.map +1 -1
- package/dist/cli.js +40 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent-install.d.ts +15 -1
- package/dist/commands/agent-install.js +121 -24
- package/dist/commands/agent-install.js.map +1 -1
- package/dist/commands/jira.d.ts +17 -0
- package/dist/commands/jira.js +195 -0
- package/dist/commands/jira.js.map +1 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/contract/registry.js +39 -2
- package/dist/contract/registry.js.map +1 -1
- package/dist/contract/schemas/index.js +2 -0
- package/dist/contract/schemas/index.js.map +1 -1
- package/dist/contract/schemas/jira.d.ts +2 -0
- package/dist/contract/schemas/jira.js +102 -0
- package/dist/contract/schemas/jira.js.map +1 -0
- package/dist/jira/adf/diagnostics.d.ts +77 -0
- package/dist/jira/adf/diagnostics.js +100 -0
- package/dist/jira/adf/diagnostics.js.map +1 -0
- package/dist/jira/adf/from-markdown.d.ts +6 -0
- package/dist/jira/adf/from-markdown.js +650 -0
- package/dist/jira/adf/from-markdown.js.map +1 -0
- package/dist/jira/adf/inspect.d.ts +11 -0
- package/dist/jira/adf/inspect.js +56 -0
- package/dist/jira/adf/inspect.js.map +1 -0
- package/dist/jira/adf/profile.d.ts +101 -0
- package/dist/jira/adf/profile.js +248 -0
- package/dist/jira/adf/profile.js.map +1 -0
- package/dist/jira/adf/read.d.ts +33 -0
- package/dist/jira/adf/read.js +250 -0
- package/dist/jira/adf/read.js.map +1 -0
- package/dist/jira/adf/serialize.d.ts +5 -0
- package/dist/jira/adf/serialize.js +54 -0
- package/dist/jira/adf/serialize.js.map +1 -0
- package/dist/jira/adf/to-markdown.d.ts +14 -0
- package/dist/jira/adf/to-markdown.js +512 -0
- package/dist/jira/adf/to-markdown.js.map +1 -0
- package/dist/jira/adf/types.d.ts +73 -0
- package/dist/jira/adf/types.js +2 -0
- package/dist/jira/adf/types.js.map +1 -0
- package/dist/jira/adf/validate.d.ts +7 -0
- package/dist/jira/adf/validate.js +182 -0
- package/dist/jira/adf/validate.js.map +1 -0
- package/dist/mapping-quality.d.ts +18 -0
- package/dist/mapping-quality.js +12 -0
- package/dist/mapping-quality.js.map +1 -0
- package/package.json +13 -10
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { CODES, diagnostic } from "./diagnostics.js";
|
|
4
|
+
/**
|
|
5
|
+
* Matches the snippet reader's cap. ADF documents are prose, not archives, and
|
|
6
|
+
* an issue description past two megabytes is a sign of something other than an
|
|
7
|
+
* issue description.
|
|
8
|
+
*/
|
|
9
|
+
export const MAX_INPUT_BYTES = 2 * 1024 * 1024;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum ADF nesting depth.
|
|
12
|
+
*
|
|
13
|
+
* ADF is a recursive tree and both converters walk it recursively, so this is a
|
|
14
|
+
* correctness guard rather than hygiene: without it a document nested a few
|
|
15
|
+
* hundred thousand deep exits with a stack overflow and no diagnostic, which for
|
|
16
|
+
* input arriving off a network is the wrong failure.
|
|
17
|
+
*/
|
|
18
|
+
export const MAX_DEPTH = 200;
|
|
19
|
+
function contained(root, candidate) {
|
|
20
|
+
const relative = path.relative(root, candidate);
|
|
21
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reads a conversion input, from a path or from stdin when `source` is `-`.
|
|
25
|
+
*
|
|
26
|
+
* The guards mirror `readSnippetSource` in src/snippets.ts, which bounds reads
|
|
27
|
+
* of files named by analyzed content. Here the content itself arrives from a
|
|
28
|
+
* REST API, so the same treatment applies: resolve through symlinks, refuse
|
|
29
|
+
* anything that is not a regular file, cap the size, and reject NUL.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Reads stdin to completion.
|
|
33
|
+
*
|
|
34
|
+
* Not `fs.readFileSync(0)`: when stdin is a pipe whose writer has not produced
|
|
35
|
+
* anything yet, that throws `EAGAIN` rather than waiting — and
|
|
36
|
+
* `curl | jq | cairn jira adf to-markdown -` is the primary documented workflow, so
|
|
37
|
+
* the failure would land squarely on the common case. Streaming the descriptor
|
|
38
|
+
* waits properly.
|
|
39
|
+
*/
|
|
40
|
+
async function readStdin() {
|
|
41
|
+
const chunks = [];
|
|
42
|
+
for await (const chunk of process.stdin)
|
|
43
|
+
chunks.push(chunk);
|
|
44
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
45
|
+
}
|
|
46
|
+
export async function readInput(source, containmentRoot) {
|
|
47
|
+
if (source === "-") {
|
|
48
|
+
const content = await readStdin();
|
|
49
|
+
return content.includes("\0")
|
|
50
|
+
? {
|
|
51
|
+
ok: false,
|
|
52
|
+
diagnostic: diagnostic({
|
|
53
|
+
code: CODES.notJson,
|
|
54
|
+
severity: "error",
|
|
55
|
+
quality: "unsupported",
|
|
56
|
+
message: "Input contains a NUL byte, so it is not text",
|
|
57
|
+
}),
|
|
58
|
+
}
|
|
59
|
+
: { ok: true, content };
|
|
60
|
+
}
|
|
61
|
+
const resolved = path.resolve(source);
|
|
62
|
+
let real;
|
|
63
|
+
try {
|
|
64
|
+
real = fs.realpathSync(resolved);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
diagnostic: diagnostic({
|
|
70
|
+
code: CODES.failure,
|
|
71
|
+
severity: "error",
|
|
72
|
+
quality: "unsupported",
|
|
73
|
+
message: `Input file not found: ${source}`,
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (containmentRoot !== undefined) {
|
|
78
|
+
let realRoot = containmentRoot;
|
|
79
|
+
try {
|
|
80
|
+
realRoot = fs.realpathSync(containmentRoot);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// An unresolvable root can only make the check stricter.
|
|
84
|
+
}
|
|
85
|
+
if (!contained(realRoot, real))
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
diagnostic: diagnostic({
|
|
89
|
+
code: CODES.failure,
|
|
90
|
+
severity: "error",
|
|
91
|
+
quality: "unsupported",
|
|
92
|
+
message: `Input file is outside the permitted root: ${source}`,
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Checked on the open descriptor, not on the path, so the file cannot be
|
|
97
|
+
// swapped between the check and the read. O_NONBLOCK is what makes that safe
|
|
98
|
+
// to do here: opening a FIFO without it blocks until a writer appears, which
|
|
99
|
+
// is the very hazard the regular-file check exists to prevent.
|
|
100
|
+
let descriptor;
|
|
101
|
+
try {
|
|
102
|
+
descriptor = fs.openSync(real, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
diagnostic: diagnostic({
|
|
108
|
+
code: CODES.failure,
|
|
109
|
+
severity: "error",
|
|
110
|
+
quality: "unsupported",
|
|
111
|
+
message: `Input file could not be opened: ${source}`,
|
|
112
|
+
}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const stat = fs.fstatSync(descriptor);
|
|
117
|
+
if (!stat.isFile())
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
diagnostic: diagnostic({
|
|
121
|
+
code: CODES.failure,
|
|
122
|
+
severity: "error",
|
|
123
|
+
quality: "unsupported",
|
|
124
|
+
message: `Input path is not a regular file: ${source}`,
|
|
125
|
+
}),
|
|
126
|
+
};
|
|
127
|
+
if (stat.size > MAX_INPUT_BYTES)
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
diagnostic: diagnostic({
|
|
131
|
+
code: CODES.tooLarge,
|
|
132
|
+
severity: "error",
|
|
133
|
+
quality: "unsupported",
|
|
134
|
+
message: `Input is larger than ${MAX_INPUT_BYTES} bytes`,
|
|
135
|
+
remediation: "Split the document, or convert the field you actually need.",
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
const content = fs.readFileSync(descriptor, "utf8");
|
|
139
|
+
if (content.includes("\0"))
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
diagnostic: diagnostic({
|
|
143
|
+
code: CODES.notJson,
|
|
144
|
+
severity: "error",
|
|
145
|
+
quality: "unsupported",
|
|
146
|
+
message: `Input contains a NUL byte, so it is not text: ${source}`,
|
|
147
|
+
}),
|
|
148
|
+
};
|
|
149
|
+
return { ok: true, content };
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
fs.closeSync(descriptor);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Keys a Jira REST response uses for a rich-text field, in the order to suggest. */
|
|
156
|
+
const WRAPPER_FIELDS = ["fields.description", "fields.summary", "body", "comment.body"];
|
|
157
|
+
function depthOf(node, depth = 0) {
|
|
158
|
+
if (depth > MAX_DEPTH)
|
|
159
|
+
return depth;
|
|
160
|
+
if (Array.isArray(node))
|
|
161
|
+
return node.reduce((max, item) => Math.max(max, depthOf(item, depth + 1)), depth);
|
|
162
|
+
if (!node || typeof node !== "object")
|
|
163
|
+
return depth;
|
|
164
|
+
let max = depth;
|
|
165
|
+
for (const value of Object.values(node))
|
|
166
|
+
max = Math.max(max, depthOf(value, depth + 1));
|
|
167
|
+
return max;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Finds a plausible ADF document nested inside an arbitrary object.
|
|
171
|
+
*
|
|
172
|
+
* Used only to write a better error. The tool converts a bare ADF document and
|
|
173
|
+
* knows nothing about the Jira response wrapper, so this never *extracts* the
|
|
174
|
+
* document — it only lets AD002 say which field to pull out, which is the
|
|
175
|
+
* likeliest first mistake anyone makes and the one place a good message replaces
|
|
176
|
+
* a whole option.
|
|
177
|
+
*/
|
|
178
|
+
function locateNested(value, trail = [], found = []) {
|
|
179
|
+
if (found.length > 3 || !value || typeof value !== "object")
|
|
180
|
+
return found;
|
|
181
|
+
if (Array.isArray(value))
|
|
182
|
+
return found;
|
|
183
|
+
const record = value;
|
|
184
|
+
if (record.type === "doc" && Array.isArray(record.content))
|
|
185
|
+
found.push(trail.join("."));
|
|
186
|
+
for (const [key, child] of Object.entries(record))
|
|
187
|
+
locateNested(child, [...trail, key], found);
|
|
188
|
+
return found;
|
|
189
|
+
}
|
|
190
|
+
/** Parses and shape-checks an ADF document, without validating its content model. */
|
|
191
|
+
export function parseAdfDocument(content, source) {
|
|
192
|
+
let value;
|
|
193
|
+
try {
|
|
194
|
+
value = JSON.parse(content);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
diagnostic: diagnostic({
|
|
200
|
+
code: CODES.notJson,
|
|
201
|
+
severity: "error",
|
|
202
|
+
quality: "unsupported",
|
|
203
|
+
message: `Input is not valid JSON: ${error.message}`,
|
|
204
|
+
}),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const isDocument = Boolean(value) &&
|
|
208
|
+
typeof value === "object" &&
|
|
209
|
+
!Array.isArray(value) &&
|
|
210
|
+
value.type === "doc";
|
|
211
|
+
if (!isDocument) {
|
|
212
|
+
const nested = locateNested(value).filter(Boolean);
|
|
213
|
+
const suggestion = nested.length
|
|
214
|
+
? `Extract it first, for example: jq .${nested[0]} ${source === "-" ? "" : source}`.trim()
|
|
215
|
+
: `This command converts a bare ADF document — an object with "type": "doc". A Jira REST response nests one under a field such as ${WRAPPER_FIELDS.join(" or ")}; extract it with jq first.`;
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
diagnostic: diagnostic({
|
|
219
|
+
code: CODES.notADocument,
|
|
220
|
+
severity: "error",
|
|
221
|
+
quality: "unsupported",
|
|
222
|
+
message: nested.length
|
|
223
|
+
? `Input is not an ADF document, but one appears at .${nested[0]}`
|
|
224
|
+
: 'Input is not an ADF document: no top-level "type": "doc"',
|
|
225
|
+
remediation: suggestion,
|
|
226
|
+
}),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const depth = depthOf(value);
|
|
230
|
+
if (depth > MAX_DEPTH)
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
diagnostic: diagnostic({
|
|
234
|
+
code: CODES.tooDeep,
|
|
235
|
+
severity: "error",
|
|
236
|
+
quality: "unsupported",
|
|
237
|
+
message: `Input nests deeper than ${MAX_DEPTH} levels`,
|
|
238
|
+
}),
|
|
239
|
+
};
|
|
240
|
+
const record = value;
|
|
241
|
+
return {
|
|
242
|
+
ok: true,
|
|
243
|
+
document: {
|
|
244
|
+
version: typeof record.version === "number" ? record.version : 1,
|
|
245
|
+
type: "doc",
|
|
246
|
+
content: Array.isArray(record.content) ? record.content : [],
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../../src/jira/adf/read.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AdfDocument } from "./types.js";
|
|
2
|
+
/** Returns the document with every key in canonical order. */
|
|
3
|
+
export declare function canonicalize(document: AdfDocument): AdfDocument;
|
|
4
|
+
/** Canonical ADF JSON, two-space indented, one trailing newline. */
|
|
5
|
+
export declare function serializeAdf(document: AdfDocument): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes ADF with a fixed key order.
|
|
3
|
+
*
|
|
4
|
+
* `JSON.stringify` follows insertion order, so key order is contract: a consumer
|
|
5
|
+
* diffing converted ADF in Git sees every reordering. This is the same reason
|
|
6
|
+
* `src/sarif.ts` builds its document in a load-bearing order and
|
|
7
|
+
* `tests/unit/automation.test.ts` asserts byte equality against a fixed input.
|
|
8
|
+
*
|
|
9
|
+
* The order is `version`, `type`, `attrs`, `content`, `marks`, `text` — the
|
|
10
|
+
* order Atlassian's own editor emits, so a converted document diffs cleanly
|
|
11
|
+
* against one round-tripped through Jira.
|
|
12
|
+
*/
|
|
13
|
+
function orderMark(mark) {
|
|
14
|
+
const ordered = { type: mark.type };
|
|
15
|
+
if (mark.attrs !== undefined)
|
|
16
|
+
ordered.attrs = orderAttrs(mark.attrs);
|
|
17
|
+
return ordered;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Attribute order within `attrs` is contract too, and there is no natural one,
|
|
21
|
+
* so it is byte order. Never `localeCompare`: that is ICU-build and locale
|
|
22
|
+
* dependent, so a differently configured runner would reorder the output.
|
|
23
|
+
*/
|
|
24
|
+
function orderAttrs(attrs) {
|
|
25
|
+
const ordered = {};
|
|
26
|
+
for (const key of Object.keys(attrs).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)))
|
|
27
|
+
ordered[key] = attrs[key];
|
|
28
|
+
return ordered;
|
|
29
|
+
}
|
|
30
|
+
function orderNode(node) {
|
|
31
|
+
const ordered = { type: node.type };
|
|
32
|
+
if (node.attrs !== undefined)
|
|
33
|
+
ordered.attrs = orderAttrs(node.attrs);
|
|
34
|
+
if (node.content !== undefined)
|
|
35
|
+
ordered.content = node.content.map(orderNode);
|
|
36
|
+
if (node.marks !== undefined)
|
|
37
|
+
ordered.marks = node.marks.map(orderMark);
|
|
38
|
+
if (node.text !== undefined)
|
|
39
|
+
ordered.text = node.text;
|
|
40
|
+
return ordered;
|
|
41
|
+
}
|
|
42
|
+
/** Returns the document with every key in canonical order. */
|
|
43
|
+
export function canonicalize(document) {
|
|
44
|
+
return {
|
|
45
|
+
version: document.version,
|
|
46
|
+
type: "doc",
|
|
47
|
+
...(document.content === undefined ? {} : { content: document.content.map(orderNode) }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Canonical ADF JSON, two-space indented, one trailing newline. */
|
|
51
|
+
export function serializeAdf(document) {
|
|
52
|
+
return `${JSON.stringify(canonicalize(document), null, 2)}\n`;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=serialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../../src/jira/adf/serialize.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,SAAS,SAAS,CAAC,IAAa;IAC9B,MAAM,OAAO,GAAY,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC7C,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,KAA8B;IAChD,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/E,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,SAAS,CAAC,IAAa;IAC9B,MAAM,OAAO,GAAY,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC7C,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrE,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC9E,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACxE,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACtD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAAC,QAAqB;IAChD,OAAO;QACL,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,IAAI,EAAE,KAAK;QACX,GAAG,CAAC,QAAQ,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;KACxF,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,YAAY,CAAC,QAAqB;IAChD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AdfDocument, ConversionDiagnostic } from "./types.js";
|
|
2
|
+
export interface ToMarkdownResult {
|
|
3
|
+
markdown: string;
|
|
4
|
+
diagnostics: ConversionDiagnostic[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Converts an ADF document to Markdown.
|
|
8
|
+
*
|
|
9
|
+
* Emits no frontmatter, ever, and this is not an option: an ADF document carries
|
|
10
|
+
* no title, key, status, or author, so there is nothing to put there. Provenance
|
|
11
|
+
* belongs in the result payload, the way `agent convert` writes a report rather
|
|
12
|
+
* than inlining one.
|
|
13
|
+
*/
|
|
14
|
+
export declare function toMarkdown(document: AdfDocument): ToMarkdownResult;
|