perttool 0.1.0-alpha.2
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/CHANGELOG.md +53 -0
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/dist/analysis/graph.d.ts +41 -0
- package/dist/analysis/graph.d.ts.map +1 -0
- package/dist/analysis/graph.js +199 -0
- package/dist/analysis/graph.js.map +1 -0
- package/dist/analysis/precedence.d.ts +52 -0
- package/dist/analysis/precedence.d.ts.map +1 -0
- package/dist/analysis/precedence.js +187 -0
- package/dist/analysis/precedence.js.map +1 -0
- package/dist/analysis/resource.d.ts +96 -0
- package/dist/analysis/resource.d.ts.map +1 -0
- package/dist/analysis/resource.js +522 -0
- package/dist/analysis/resource.js.map +1 -0
- package/dist/application/analyze.d.ts +32 -0
- package/dist/application/analyze.d.ts.map +1 -0
- package/dist/application/analyze.js +144 -0
- package/dist/application/analyze.js.map +1 -0
- package/dist/application/check.d.ts +24 -0
- package/dist/application/check.d.ts.map +1 -0
- package/dist/application/check.js +43 -0
- package/dist/application/check.js.map +1 -0
- package/dist/application/format.d.ts +21 -0
- package/dist/application/format.d.ts.map +1 -0
- package/dist/application/format.js +52 -0
- package/dist/application/format.js.map +1 -0
- package/dist/application/mutate.d.ts +4 -0
- package/dist/application/mutate.d.ts.map +1 -0
- package/dist/application/mutate.js +175 -0
- package/dist/application/mutate.js.map +1 -0
- package/dist/application/next.d.ts +86 -0
- package/dist/application/next.d.ts.map +1 -0
- package/dist/application/next.js +310 -0
- package/dist/application/next.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2103 -0
- package/dist/cli.js.map +1 -0
- package/dist/conversion/mermaid-import.d.ts +26 -0
- package/dist/conversion/mermaid-import.d.ts.map +1 -0
- package/dist/conversion/mermaid-import.js +655 -0
- package/dist/conversion/mermaid-import.js.map +1 -0
- package/dist/conversion/mermaid.d.ts +38 -0
- package/dist/conversion/mermaid.d.ts.map +1 -0
- package/dist/conversion/mermaid.js +362 -0
- package/dist/conversion/mermaid.js.map +1 -0
- package/dist/editing/unified-diff.d.ts +7 -0
- package/dist/editing/unified-diff.d.ts.map +1 -0
- package/dist/editing/unified-diff.js +81 -0
- package/dist/editing/unified-diff.js.map +1 -0
- package/dist/formatter/source-formatter.d.ts +17 -0
- package/dist/formatter/source-formatter.d.ts.map +1 -0
- package/dist/formatter/source-formatter.js +210 -0
- package/dist/formatter/source-formatter.js.map +1 -0
- package/dist/help/registry.d.ts +37 -0
- package/dist/help/registry.d.ts.map +1 -0
- package/dist/help/registry.js +657 -0
- package/dist/help/registry.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/io/document-file.d.ts +9 -0
- package/dist/io/document-file.d.ts.map +1 -0
- package/dist/io/document-file.js +19 -0
- package/dist/io/document-file.js.map +1 -0
- package/dist/io/safe-write.d.ts +33 -0
- package/dist/io/safe-write.d.ts.map +1 -0
- package/dist/io/safe-write.js +280 -0
- package/dist/io/safe-write.js.map +1 -0
- package/dist/model/diagnostics.d.ts +43 -0
- package/dist/model/diagnostics.d.ts.map +1 -0
- package/dist/model/diagnostics.js +47 -0
- package/dist/model/diagnostics.js.map +1 -0
- package/dist/model/rational.d.ts +21 -0
- package/dist/model/rational.d.ts.map +1 -0
- package/dist/model/rational.js +84 -0
- package/dist/model/rational.js.map +1 -0
- package/dist/model/syntax.d.ts +67 -0
- package/dist/model/syntax.d.ts.map +1 -0
- package/dist/model/syntax.js +15 -0
- package/dist/model/syntax.js.map +1 -0
- package/dist/model/units.d.ts +18 -0
- package/dist/model/units.d.ts.map +1 -0
- package/dist/model/units.js +29 -0
- package/dist/model/units.js.map +1 -0
- package/dist/mutation/advance.d.ts +26 -0
- package/dist/mutation/advance.d.ts.map +1 -0
- package/dist/mutation/advance.js +261 -0
- package/dist/mutation/advance.js.map +1 -0
- package/dist/mutation/diagnostics.d.ts +10 -0
- package/dist/mutation/diagnostics.d.ts.map +1 -0
- package/dist/mutation/diagnostics.js +10 -0
- package/dist/mutation/diagnostics.js.map +1 -0
- package/dist/mutation/entity-editor.d.ts +16 -0
- package/dist/mutation/entity-editor.d.ts.map +1 -0
- package/dist/mutation/entity-editor.js +103 -0
- package/dist/mutation/entity-editor.js.map +1 -0
- package/dist/mutation/milestone.d.ts +5 -0
- package/dist/mutation/milestone.d.ts.map +1 -0
- package/dist/mutation/milestone.js +194 -0
- package/dist/mutation/milestone.js.map +1 -0
- package/dist/mutation/resource.d.ts +5 -0
- package/dist/mutation/resource.d.ts.map +1 -0
- package/dist/mutation/resource.js +145 -0
- package/dist/mutation/resource.js.map +1 -0
- package/dist/mutation/source.d.ts +22 -0
- package/dist/mutation/source.d.ts.map +1 -0
- package/dist/mutation/source.js +155 -0
- package/dist/mutation/source.js.map +1 -0
- package/dist/mutation/task.d.ts +10 -0
- package/dist/mutation/task.d.ts.map +1 -0
- package/dist/mutation/task.js +644 -0
- package/dist/mutation/task.js.map +1 -0
- package/dist/mutation/text-edits.d.ts +8 -0
- package/dist/mutation/text-edits.d.ts.map +1 -0
- package/dist/mutation/text-edits.js +33 -0
- package/dist/mutation/text-edits.js.map +1 -0
- package/dist/mutation/types.d.ts +153 -0
- package/dist/mutation/types.d.ts.map +1 -0
- package/dist/mutation/types.js +2 -0
- package/dist/mutation/types.js.map +1 -0
- package/dist/parser/document-parser.d.ts +6 -0
- package/dist/parser/document-parser.d.ts.map +1 -0
- package/dist/parser/document-parser.js +735 -0
- package/dist/parser/document-parser.js.map +1 -0
- package/dist/recommendation/explanation-types.d.ts +226 -0
- package/dist/recommendation/explanation-types.d.ts.map +1 -0
- package/dist/recommendation/explanation-types.js +2 -0
- package/dist/recommendation/explanation-types.js.map +1 -0
- package/dist/recommendation/explanation-validation.d.ts +4 -0
- package/dist/recommendation/explanation-validation.d.ts.map +1 -0
- package/dist/recommendation/explanation-validation.js +745 -0
- package/dist/recommendation/explanation-validation.js.map +1 -0
- package/dist/recommendation/explanation-values.d.ts +7 -0
- package/dist/recommendation/explanation-values.d.ts.map +1 -0
- package/dist/recommendation/explanation-values.js +283 -0
- package/dist/recommendation/explanation-values.js.map +1 -0
- package/dist/recommendation/explanation.d.ts +8 -0
- package/dist/recommendation/explanation.d.ts.map +1 -0
- package/dist/recommendation/explanation.js +1022 -0
- package/dist/recommendation/explanation.js.map +1 -0
- package/dist/recommendation/failure.d.ts +3 -0
- package/dist/recommendation/failure.d.ts.map +1 -0
- package/dist/recommendation/failure.js +4 -0
- package/dist/recommendation/failure.js.map +1 -0
- package/dist/recommendation/json.d.ts +3 -0
- package/dist/recommendation/json.d.ts.map +1 -0
- package/dist/recommendation/json.js +252 -0
- package/dist/recommendation/json.js.map +1 -0
- package/dist/recommendation/override-types.d.ts +117 -0
- package/dist/recommendation/override-types.d.ts.map +1 -0
- package/dist/recommendation/override-types.js +2 -0
- package/dist/recommendation/override-types.js.map +1 -0
- package/dist/recommendation/override.d.ts +7 -0
- package/dist/recommendation/override.d.ts.map +1 -0
- package/dist/recommendation/override.js +717 -0
- package/dist/recommendation/override.js.map +1 -0
- package/dist/recommendation/ranking.d.ts +7 -0
- package/dist/recommendation/ranking.d.ts.map +1 -0
- package/dist/recommendation/ranking.js +391 -0
- package/dist/recommendation/ranking.js.map +1 -0
- package/dist/recommendation/types.d.ts +83 -0
- package/dist/recommendation/types.d.ts.map +1 -0
- package/dist/recommendation/types.js +2 -0
- package/dist/recommendation/types.js.map +1 -0
- package/dist/semantic/validator.d.ts +4 -0
- package/dist/semantic/validator.d.ts.map +1 -0
- package/dist/semantic/validator.js +490 -0
- package/dist/semantic/validator.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { checkDocument } from "../application/check.js";
|
|
3
|
+
import { compareStableStrings, normalizeMaxDiagnostics } from "../model/diagnostics.js";
|
|
4
|
+
import { serializeTags, serializeTextField } from "../mutation/source.js";
|
|
5
|
+
import { exportMermaid, } from "./mermaid.js";
|
|
6
|
+
class ImportFailure extends Error {
|
|
7
|
+
code;
|
|
8
|
+
span;
|
|
9
|
+
constructor(code, message, span) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.span = span;
|
|
13
|
+
this.name = "ImportFailure";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const identifierPattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
17
|
+
const digestPattern = /^sha256:[0-9a-f]{64}$/;
|
|
18
|
+
const durationPattern = /^(?:0|[1-9][0-9]*)(?:\.[0-9]*[1-9])?[dhp]$/;
|
|
19
|
+
const velocityPattern = /^(?:0|[1-9][0-9]*)(?:\.[0-9]*[1-9])?p\/(?:0|[1-9][0-9]*)(?:\.[0-9]*[1-9])?[dh]$/;
|
|
20
|
+
const kinds = ["project", "resource", "milestone", "task", "gate"];
|
|
21
|
+
function sha256(text) {
|
|
22
|
+
return `sha256:${createHash("sha256").update(text, "utf8").digest("hex")}`;
|
|
23
|
+
}
|
|
24
|
+
function sourceLines(text) {
|
|
25
|
+
const lines = [];
|
|
26
|
+
let start = 0;
|
|
27
|
+
let line = 0;
|
|
28
|
+
while (start < text.length) {
|
|
29
|
+
const newline = text.indexOf("\n", start);
|
|
30
|
+
const end = newline === -1 ? text.length : newline;
|
|
31
|
+
lines.push({ text: text.slice(start, end), start, line });
|
|
32
|
+
if (newline === -1)
|
|
33
|
+
break;
|
|
34
|
+
start = newline + 1;
|
|
35
|
+
line += 1;
|
|
36
|
+
}
|
|
37
|
+
return lines;
|
|
38
|
+
}
|
|
39
|
+
function lineSpan(line) {
|
|
40
|
+
return {
|
|
41
|
+
start: { offset: line.start, line: line.line, column: 0 },
|
|
42
|
+
end: { offset: line.start + line.text.length, line: line.line, column: line.text.length },
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function diagnostic(failure) {
|
|
46
|
+
return {
|
|
47
|
+
code: failure.code,
|
|
48
|
+
severity: "error",
|
|
49
|
+
message: failure.message,
|
|
50
|
+
...(failure.span === undefined ? {} : { span: failure.span }),
|
|
51
|
+
helpTopic: "mermaid",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function emptyResult(profile, failure, maxDiagnostics) {
|
|
55
|
+
const checked = checkDocument("", { maxDiagnostics });
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
document: checked.document,
|
|
59
|
+
documentId: null,
|
|
60
|
+
diagnostics: [diagnostic(failure)],
|
|
61
|
+
diagnosticsTruncated: false,
|
|
62
|
+
profile,
|
|
63
|
+
analysis: "none",
|
|
64
|
+
capacityOverrides: new Map(),
|
|
65
|
+
artifact: null,
|
|
66
|
+
artifactDigest: null,
|
|
67
|
+
lossReport: { lossless: false, records: [] },
|
|
68
|
+
generatedIds: [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function isObject(value) {
|
|
72
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
73
|
+
}
|
|
74
|
+
function parseCanonicalObject(json, code, span) {
|
|
75
|
+
let value;
|
|
76
|
+
try {
|
|
77
|
+
value = JSON.parse(json);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new ImportFailure(code, "Mermaid profile JSONが不正です", span);
|
|
81
|
+
}
|
|
82
|
+
if (!isObject(value) || JSON.stringify(value) !== json) {
|
|
83
|
+
throw new ImportFailure(code, "Mermaid profile JSONがcanonical objectではありません", span);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function assertKeys(value, expected, span) {
|
|
88
|
+
if (Object.keys(value).length !== expected.length ||
|
|
89
|
+
Object.keys(value).some((key, index) => key !== expected[index])) {
|
|
90
|
+
throw new ImportFailure("PTCNV-102", "Mermaid profile recordのkeyまたはkey順が不正です", span);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function requiredString(value, key, span) {
|
|
94
|
+
const result = value[key];
|
|
95
|
+
if (typeof result !== "string") {
|
|
96
|
+
throw new ImportFailure("PTCNV-102", `${key}はstringでなければなりません`, span);
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
function nullableString(value, key, span) {
|
|
101
|
+
const result = value[key];
|
|
102
|
+
if (result !== null && typeof result !== "string") {
|
|
103
|
+
throw new ImportFailure("PTCNV-102", `${key}はstringまたはnullでなければなりません`, span);
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
function safeInteger(value, key, span) {
|
|
108
|
+
const result = value[key];
|
|
109
|
+
if (!Number.isSafeInteger(result)) {
|
|
110
|
+
throw new ImportFailure("PTCNV-102", `${key}はsafe integerでなければなりません`, span);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
function stringArray(value, key, span) {
|
|
115
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
116
|
+
throw new ImportFailure("PTCNV-102", `${key}はstring arrayでなければなりません`, span);
|
|
117
|
+
}
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
function parseHeader(value, span) {
|
|
121
|
+
assertKeys(value, [
|
|
122
|
+
"schema_version",
|
|
123
|
+
"profile",
|
|
124
|
+
"source_fidelity",
|
|
125
|
+
"record_count",
|
|
126
|
+
"metadata_digest",
|
|
127
|
+
"projection_digest",
|
|
128
|
+
"projection",
|
|
129
|
+
], span);
|
|
130
|
+
if (value["schema_version"] !== "Perttool.MermaidProfile.v1") {
|
|
131
|
+
throw new ImportFailure("PTCNV-101", "Mermaid profile schema/versionが未対応です", span);
|
|
132
|
+
}
|
|
133
|
+
if (value["profile"] !== "perttool" || value["source_fidelity"] !== "semantic-v1") {
|
|
134
|
+
throw new ImportFailure("PTCNV-102", "Mermaid profile headerが不正です", span);
|
|
135
|
+
}
|
|
136
|
+
const recordCount = safeInteger(value, "record_count", span);
|
|
137
|
+
if (recordCount < 1) {
|
|
138
|
+
throw new ImportFailure("PTCNV-102", "record_countは1以上でなければなりません", span);
|
|
139
|
+
}
|
|
140
|
+
const metadataDigest = requiredString(value, "metadata_digest", span);
|
|
141
|
+
const projectionDigest = requiredString(value, "projection_digest", span);
|
|
142
|
+
if (!digestPattern.test(metadataDigest) || !digestPattern.test(projectionDigest)) {
|
|
143
|
+
throw new ImportFailure("PTCNV-102", "profile digestの形式が不正です", span);
|
|
144
|
+
}
|
|
145
|
+
const projection = value["projection"];
|
|
146
|
+
if (!isObject(projection)) {
|
|
147
|
+
throw new ImportFailure("PTCNV-102", "projection snapshotがobjectではありません", span);
|
|
148
|
+
}
|
|
149
|
+
assertKeys(projection, ["schema_version", "direction", "analysis", "capacity_overrides"], span);
|
|
150
|
+
if (projection["schema_version"] !== "Perttool.MermaidProjection.v1" ||
|
|
151
|
+
projection["direction"] !== "LR") {
|
|
152
|
+
throw new ImportFailure("PTCNV-101", "Mermaid projection schema/versionが未対応です", span);
|
|
153
|
+
}
|
|
154
|
+
const analysis = projection["analysis"];
|
|
155
|
+
if (!new Set(["none", "precedence", "resource", "both"]).has(analysis)) {
|
|
156
|
+
throw new ImportFailure("PTCNV-102", "projection analysisが不正です", span);
|
|
157
|
+
}
|
|
158
|
+
const rawOverrides = projection["capacity_overrides"];
|
|
159
|
+
if (!Array.isArray(rawOverrides)) {
|
|
160
|
+
throw new ImportFailure("PTCNV-102", "capacity_overridesがarrayではありません", span);
|
|
161
|
+
}
|
|
162
|
+
const overrides = new Map();
|
|
163
|
+
let previous = "";
|
|
164
|
+
for (const raw of rawOverrides) {
|
|
165
|
+
if (!isObject(raw)) {
|
|
166
|
+
throw new ImportFailure("PTCNV-102", "capacity overrideがobjectではありません", span);
|
|
167
|
+
}
|
|
168
|
+
assertKeys(raw, ["resource_id", "capacity"], span);
|
|
169
|
+
const resourceId = requiredString(raw, "resource_id", span);
|
|
170
|
+
const capacity = safeInteger(raw, "capacity", span);
|
|
171
|
+
if (!identifierPattern.test(resourceId) || capacity < 1 || compareStableStrings(previous, resourceId) >= 0) {
|
|
172
|
+
throw new ImportFailure("PTCNV-102", "capacity overrideのID、capacity、順序が不正です", span);
|
|
173
|
+
}
|
|
174
|
+
previous = resourceId;
|
|
175
|
+
overrides.set(resourceId, capacity);
|
|
176
|
+
}
|
|
177
|
+
if (overrides.size > 0 && analysis !== "resource" && analysis !== "both") {
|
|
178
|
+
throw new ImportFailure("PTCNV-102", "capacity overrideにはresource analysisが必要です", span);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
recordCount,
|
|
182
|
+
metadataDigest,
|
|
183
|
+
projectionDigest,
|
|
184
|
+
analysis: analysis,
|
|
185
|
+
capacityOverrides: overrides,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function validateRecordShape(record) {
|
|
189
|
+
const { kind, value } = record;
|
|
190
|
+
const span = lineSpan(record.line);
|
|
191
|
+
const keys = {
|
|
192
|
+
project: ["id", "version", "title", "description", "as_of", "duration_unit", "velocity", "finish", "critical_epsilon", "target_duration"],
|
|
193
|
+
resource: ["id", "title", "description", "capacity", "tags"],
|
|
194
|
+
milestone: ["id", "title", "description", "state", "tags"],
|
|
195
|
+
task: ["id", "from", "to", "title", "description", "estimate", "status", "priority", "requires", "owner", "tags", "blocked_reason", "source"],
|
|
196
|
+
gate: ["id", "from", "to", "reason"],
|
|
197
|
+
};
|
|
198
|
+
assertKeys(value, keys[kind], span);
|
|
199
|
+
for (const key of kind === "project"
|
|
200
|
+
? ["id", "title", "finish", "critical_epsilon"]
|
|
201
|
+
: kind === "resource" || kind === "milestone"
|
|
202
|
+
? ["id", "title"]
|
|
203
|
+
: kind === "task"
|
|
204
|
+
? ["id", "from", "to", "title", "status"]
|
|
205
|
+
: ["id", "from", "to", "reason"]) {
|
|
206
|
+
requiredString(value, key, span);
|
|
207
|
+
}
|
|
208
|
+
if (kind === "project") {
|
|
209
|
+
if (safeInteger(value, "version", span) !== 1) {
|
|
210
|
+
throw new ImportFailure("PTCNV-101", "project versionが未対応です", span);
|
|
211
|
+
}
|
|
212
|
+
nullableString(value, "description", span);
|
|
213
|
+
nullableString(value, "as_of", span);
|
|
214
|
+
const unit = requiredString(value, "duration_unit", span);
|
|
215
|
+
if (!new Set(["day", "hour", "point"]).has(unit)) {
|
|
216
|
+
throw new ImportFailure("PTCNV-102", "duration_unitが不正です", span);
|
|
217
|
+
}
|
|
218
|
+
const velocity = nullableString(value, "velocity", span);
|
|
219
|
+
if (velocity !== null && !velocityPattern.test(velocity)) {
|
|
220
|
+
throw new ImportFailure("PTCNV-102", "velocity tokenがcanonicalではありません", span);
|
|
221
|
+
}
|
|
222
|
+
for (const key of ["critical_epsilon", "target_duration"]) {
|
|
223
|
+
const token = key === "target_duration"
|
|
224
|
+
? nullableString(value, key, span)
|
|
225
|
+
: requiredString(value, key, span);
|
|
226
|
+
if (token !== null && !durationPattern.test(token)) {
|
|
227
|
+
throw new ImportFailure("PTCNV-102", `${key} tokenがcanonicalではありません`, span);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else if (kind === "resource") {
|
|
232
|
+
nullableString(value, "description", span);
|
|
233
|
+
if (safeInteger(value, "capacity", span) < 1) {
|
|
234
|
+
throw new ImportFailure("PTCNV-102", "resource capacityが不正です", span);
|
|
235
|
+
}
|
|
236
|
+
stringArray(value["tags"], "tags", span);
|
|
237
|
+
}
|
|
238
|
+
else if (kind === "milestone") {
|
|
239
|
+
nullableString(value, "description", span);
|
|
240
|
+
if (!new Set(["planned", "reached"]).has(value["state"])) {
|
|
241
|
+
throw new ImportFailure("PTCNV-102", "milestone stateが不正です", span);
|
|
242
|
+
}
|
|
243
|
+
stringArray(value["tags"], "tags", span);
|
|
244
|
+
}
|
|
245
|
+
else if (kind === "task") {
|
|
246
|
+
nullableString(value, "description", span);
|
|
247
|
+
nullableString(value, "owner", span);
|
|
248
|
+
nullableString(value, "blocked_reason", span);
|
|
249
|
+
nullableString(value, "source", span);
|
|
250
|
+
if (!new Set(["planned", "active", "blocked", "done"]).has(value["status"])) {
|
|
251
|
+
throw new ImportFailure("PTCNV-102", "task statusが不正です", span);
|
|
252
|
+
}
|
|
253
|
+
safeInteger(value, "priority", span);
|
|
254
|
+
stringArray(value["tags"], "tags", span);
|
|
255
|
+
const estimate = value["estimate"];
|
|
256
|
+
if (!isObject(estimate)) {
|
|
257
|
+
throw new ImportFailure("PTCNV-102", "task estimateがobjectではありません", span);
|
|
258
|
+
}
|
|
259
|
+
if (estimate["kind"] === "deterministic") {
|
|
260
|
+
assertKeys(estimate, ["kind", "duration"], span);
|
|
261
|
+
if (!durationPattern.test(requiredString(estimate, "duration", span))) {
|
|
262
|
+
throw new ImportFailure("PTCNV-102", "duration tokenがcanonicalではありません", span);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (estimate["kind"] === "pert") {
|
|
266
|
+
assertKeys(estimate, ["kind", "optimistic", "most_likely", "pessimistic"], span);
|
|
267
|
+
for (const key of ["optimistic", "most_likely", "pessimistic"]) {
|
|
268
|
+
if (!durationPattern.test(requiredString(estimate, key, span))) {
|
|
269
|
+
throw new ImportFailure("PTCNV-102", "PERT tokenがcanonicalではありません", span);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
throw new ImportFailure("PTCNV-102", "task estimate kindが不正です", span);
|
|
275
|
+
}
|
|
276
|
+
const requirements = value["requires"];
|
|
277
|
+
if (!Array.isArray(requirements)) {
|
|
278
|
+
throw new ImportFailure("PTCNV-102", "requiresがarrayではありません", span);
|
|
279
|
+
}
|
|
280
|
+
let previous = "";
|
|
281
|
+
for (const raw of requirements) {
|
|
282
|
+
if (!isObject(raw)) {
|
|
283
|
+
throw new ImportFailure("PTCNV-102", "requirementがobjectではありません", span);
|
|
284
|
+
}
|
|
285
|
+
assertKeys(raw, ["resource_id", "units"], span);
|
|
286
|
+
const resourceId = requiredString(raw, "resource_id", span);
|
|
287
|
+
const units = safeInteger(raw, "units", span);
|
|
288
|
+
if (units < 1 || compareStableStrings(previous, resourceId) >= 0) {
|
|
289
|
+
throw new ImportFailure("PTCNV-102", "requirementの値または順序が不正です", span);
|
|
290
|
+
}
|
|
291
|
+
previous = resourceId;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function textField(name, value) {
|
|
296
|
+
return typeof value === "string" ? [serializeTextField(name, value, "\n")] : [];
|
|
297
|
+
}
|
|
298
|
+
function serializeProfileRecords(records) {
|
|
299
|
+
const declarations = [];
|
|
300
|
+
for (const { kind, value } of records) {
|
|
301
|
+
if (kind === "project") {
|
|
302
|
+
declarations.push([
|
|
303
|
+
`project ${value["id"]}:`,
|
|
304
|
+
` version ${value["version"]}`,
|
|
305
|
+
` title ${JSON.stringify(value["title"])}`,
|
|
306
|
+
...textField("description", value["description"]),
|
|
307
|
+
...(value["as_of"] === null ? [] : [` as_of ${value["as_of"]}`]),
|
|
308
|
+
` duration_unit ${value["duration_unit"]}`,
|
|
309
|
+
...(value["velocity"] === null ? [] : [` velocity ${value["velocity"]}`]),
|
|
310
|
+
` finish ${value["finish"]}`,
|
|
311
|
+
` critical_epsilon ${value["critical_epsilon"]}`,
|
|
312
|
+
...(value["target_duration"] === null ? [] : [` target_duration ${value["target_duration"]}`]),
|
|
313
|
+
].join("\n"));
|
|
314
|
+
}
|
|
315
|
+
else if (kind === "resource") {
|
|
316
|
+
const tags = value["tags"];
|
|
317
|
+
declarations.push([
|
|
318
|
+
`resource ${value["id"]}:`,
|
|
319
|
+
` title ${JSON.stringify(value["title"])}`,
|
|
320
|
+
...textField("description", value["description"]),
|
|
321
|
+
` capacity ${value["capacity"]}`,
|
|
322
|
+
...(tags.length === 0 ? [] : [` tags ${serializeTags(tags)}`]),
|
|
323
|
+
].join("\n"));
|
|
324
|
+
}
|
|
325
|
+
else if (kind === "milestone") {
|
|
326
|
+
const tags = value["tags"];
|
|
327
|
+
declarations.push([
|
|
328
|
+
`milestone ${value["id"]}:`,
|
|
329
|
+
` title ${JSON.stringify(value["title"])}`,
|
|
330
|
+
...textField("description", value["description"]),
|
|
331
|
+
` state ${value["state"]}`,
|
|
332
|
+
...(tags.length === 0 ? [] : [` tags ${serializeTags(tags)}`]),
|
|
333
|
+
].join("\n"));
|
|
334
|
+
}
|
|
335
|
+
else if (kind === "task") {
|
|
336
|
+
const estimate = value["estimate"];
|
|
337
|
+
const requirements = value["requires"];
|
|
338
|
+
const tags = value["tags"];
|
|
339
|
+
declarations.push([
|
|
340
|
+
`task ${value["id"]} ${value["from"]} -> ${value["to"]}:`,
|
|
341
|
+
` title ${JSON.stringify(value["title"])}`,
|
|
342
|
+
...textField("description", value["description"]),
|
|
343
|
+
...(estimate["kind"] === "deterministic"
|
|
344
|
+
? [` duration ${estimate["duration"]}`]
|
|
345
|
+
: [
|
|
346
|
+
" estimate:",
|
|
347
|
+
` optimistic ${estimate["optimistic"]}`,
|
|
348
|
+
` most_likely ${estimate["most_likely"]}`,
|
|
349
|
+
` pessimistic ${estimate["pessimistic"]}`,
|
|
350
|
+
]),
|
|
351
|
+
` status ${value["status"]}`,
|
|
352
|
+
` priority ${value["priority"]}`,
|
|
353
|
+
...(requirements.length === 0
|
|
354
|
+
? []
|
|
355
|
+
: [
|
|
356
|
+
" requires:",
|
|
357
|
+
...requirements.map((requirement) => ` ${requirement["resource_id"]} ${requirement["units"]}`),
|
|
358
|
+
]),
|
|
359
|
+
...(value["owner"] === null ? [] : [` owner ${JSON.stringify(value["owner"])}`]),
|
|
360
|
+
...(tags.length === 0 ? [] : [` tags ${serializeTags(tags)}`]),
|
|
361
|
+
...textField("blocked_reason", value["blocked_reason"]),
|
|
362
|
+
...(value["source"] === null ? [] : [` source ${JSON.stringify(value["source"])}`]),
|
|
363
|
+
].join("\n"));
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
declarations.push([
|
|
367
|
+
`gate ${value["id"]} ${value["from"]} -> ${value["to"]}:`,
|
|
368
|
+
...textField("reason", value["reason"]),
|
|
369
|
+
].join("\n"));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return `${declarations.join("\n\n")}\n`;
|
|
373
|
+
}
|
|
374
|
+
function validateRecordOrder(records, expectedCount) {
|
|
375
|
+
if (records.length !== expectedCount || records.length === 0 || records[0].kind !== "project") {
|
|
376
|
+
throw new ImportFailure("PTCNV-103", "semantic record countまたはproject recordが不正です");
|
|
377
|
+
}
|
|
378
|
+
let previousRank = -1;
|
|
379
|
+
let previousId = "";
|
|
380
|
+
let projects = 0;
|
|
381
|
+
for (const record of records) {
|
|
382
|
+
const rank = kinds.indexOf(record.kind);
|
|
383
|
+
const id = requiredString(record.value, "id", lineSpan(record.line));
|
|
384
|
+
if (rank < previousRank || (rank === previousRank && compareStableStrings(previousId, id) >= 0)) {
|
|
385
|
+
throw new ImportFailure("PTCNV-103", "semantic record kindまたはID順が不正です", lineSpan(record.line));
|
|
386
|
+
}
|
|
387
|
+
if (record.kind === "project")
|
|
388
|
+
projects += 1;
|
|
389
|
+
if (rank !== previousRank)
|
|
390
|
+
previousId = "";
|
|
391
|
+
previousRank = rank;
|
|
392
|
+
previousId = id;
|
|
393
|
+
}
|
|
394
|
+
if (projects !== 1) {
|
|
395
|
+
throw new ImportFailure("PTCNV-103", "project recordはexactly one必要です");
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function profileDetected(lines) {
|
|
399
|
+
const nonempty = lines.filter(({ text }) => text !== "");
|
|
400
|
+
const first = nonempty[0]?.text.replace(/^\uFEFF/, "").replace(/\r$/, "");
|
|
401
|
+
const second = nonempty[1]?.text.replace(/\r$/, "").trimStart();
|
|
402
|
+
return first === "flowchart LR" && second?.startsWith("%% perttool:profile ") === true;
|
|
403
|
+
}
|
|
404
|
+
function importProfile(text, lines, maxDiagnostics) {
|
|
405
|
+
if (text.startsWith("\uFEFF") || text.includes("\r") || !text.endsWith("\n")) {
|
|
406
|
+
throw new ImportFailure("PTCNV-102", "profile artifactはBOMなし、LF、末尾newlineを必要とします");
|
|
407
|
+
}
|
|
408
|
+
if (lines[0]?.text !== "flowchart LR" || lines[1]?.text.startsWith(" %% perttool:profile ") !== true) {
|
|
409
|
+
throw new ImportFailure("PTCNV-102", "profile artifact構造が不正です");
|
|
410
|
+
}
|
|
411
|
+
const lastNonempty = lines.findLastIndex(({ text }) => text !== "");
|
|
412
|
+
if (lastNonempty === -1 || lines[lastNonempty].text !== " %% perttool:projection-end") {
|
|
413
|
+
throw new ImportFailure("PTCNV-102", "projection-end markerがありません");
|
|
414
|
+
}
|
|
415
|
+
if (lines.slice(lastNonempty + 1).some(({ text: line }) => line !== "")) {
|
|
416
|
+
throw new ImportFailure("PTCNV-102", "projection-end後にstatementがあります");
|
|
417
|
+
}
|
|
418
|
+
const headerLine = lines[1];
|
|
419
|
+
const headerJson = headerLine.text.slice(" %% perttool:profile ".length);
|
|
420
|
+
const header = parseHeader(parseCanonicalObject(headerJson, "PTCNV-102", lineSpan(headerLine)), lineSpan(headerLine));
|
|
421
|
+
const beginIndexes = lines.flatMap((line, index) => line.text === " %% perttool:projection-begin" ? [index] : []);
|
|
422
|
+
const endIndexes = lines.flatMap((line, index) => line.text === " %% perttool:projection-end" ? [index] : []);
|
|
423
|
+
if (beginIndexes.length !== 1 || endIndexes.length !== 1 || beginIndexes[0] <= 1 || endIndexes[0] <= beginIndexes[0] + 1) {
|
|
424
|
+
throw new ImportFailure("PTCNV-102", "projection marker構造が不正です");
|
|
425
|
+
}
|
|
426
|
+
const begin = beginIndexes[0];
|
|
427
|
+
const end = endIndexes[0];
|
|
428
|
+
const records = [];
|
|
429
|
+
const recordPattern = /^ %% perttool:(project|resource|milestone|task|gate) (.*)$/;
|
|
430
|
+
for (const line of lines.slice(2, begin)) {
|
|
431
|
+
const match = recordPattern.exec(line.text);
|
|
432
|
+
if (match === null) {
|
|
433
|
+
throw new ImportFailure("PTCNV-102", "semantic record lineが不正です", lineSpan(line));
|
|
434
|
+
}
|
|
435
|
+
const json = match[2];
|
|
436
|
+
records.push({
|
|
437
|
+
kind: match[1],
|
|
438
|
+
value: parseCanonicalObject(json, "PTCNV-103", lineSpan(line)),
|
|
439
|
+
json,
|
|
440
|
+
line,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
validateRecordOrder(records, header.recordCount);
|
|
444
|
+
for (const record of records)
|
|
445
|
+
validateRecordShape(record);
|
|
446
|
+
const metadataBody = records.map(({ kind, json }) => `${kind} ${json}\n`).join("");
|
|
447
|
+
if (sha256(metadataBody) !== header.metadataDigest) {
|
|
448
|
+
throw new ImportFailure("PTCNV-104", "metadata digestが一致しません");
|
|
449
|
+
}
|
|
450
|
+
const projectionBody = lines.slice(begin + 1, end).map(({ text: line }) => `${line}\n`).join("");
|
|
451
|
+
if (sha256(projectionBody) !== header.projectionDigest) {
|
|
452
|
+
throw new ImportFailure("PTCNV-105", "projection digestが一致しません");
|
|
453
|
+
}
|
|
454
|
+
const artifact = serializeProfileRecords(records);
|
|
455
|
+
const checked = checkDocument(artifact, { maxDiagnostics });
|
|
456
|
+
if (!checked.ok) {
|
|
457
|
+
throw new ImportFailure("PTCNV-106", "metadataから復元したDSLが意味検査に失敗しました");
|
|
458
|
+
}
|
|
459
|
+
const reproduced = exportMermaid(artifact, {
|
|
460
|
+
analysis: header.analysis,
|
|
461
|
+
capacityOverrides: header.capacityOverrides,
|
|
462
|
+
maxDiagnostics,
|
|
463
|
+
});
|
|
464
|
+
if (!reproduced.ok || reproduced.artifact !== text) {
|
|
465
|
+
throw new ImportFailure("PTCNV-105", "projectionがmetadataのnode/edgeまたは解析snapshotと一致しません");
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
ok: true,
|
|
469
|
+
document: checked.document,
|
|
470
|
+
documentId: checked.documentId,
|
|
471
|
+
diagnostics: checked.diagnostics,
|
|
472
|
+
diagnosticsTruncated: checked.diagnosticsTruncated,
|
|
473
|
+
profile: "perttool",
|
|
474
|
+
analysis: header.analysis,
|
|
475
|
+
capacityOverrides: header.capacityOverrides,
|
|
476
|
+
artifact,
|
|
477
|
+
artifactDigest: sha256(artifact),
|
|
478
|
+
lossReport: { lossless: true, records: [] },
|
|
479
|
+
generatedIds: [],
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function decodeLabel(value) {
|
|
483
|
+
return value.replace(/#([0-9]+);/g, (match, digits) => {
|
|
484
|
+
const codePoint = Number(digits);
|
|
485
|
+
return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff &&
|
|
486
|
+
!(codePoint >= 0xd800 && codePoint <= 0xdfff)
|
|
487
|
+
? String.fromCodePoint(codePoint)
|
|
488
|
+
: match;
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
function labelTitle(label, fallback) {
|
|
492
|
+
const decoded = decodeLabel(label);
|
|
493
|
+
const colon = decoded.indexOf(": ");
|
|
494
|
+
const body = colon === -1 ? decoded : decoded.slice(colon + 2);
|
|
495
|
+
const annotation = body.indexOf(" / ");
|
|
496
|
+
const title = (annotation === -1 ? body : body.slice(0, annotation)).trim();
|
|
497
|
+
return title === "" ? fallback : title;
|
|
498
|
+
}
|
|
499
|
+
function loss(code, message, elementId, span) {
|
|
500
|
+
return { code, severity: "warning", message, elementId, span, lossy: true };
|
|
501
|
+
}
|
|
502
|
+
function importPlain(text, lines, maxDiagnostics) {
|
|
503
|
+
const first = lines.find(({ text: line }) => line.trim() !== "");
|
|
504
|
+
if (first?.text.trim() !== "flowchart LR") {
|
|
505
|
+
throw new ImportFailure("PTCNV-102", "plain Mermaid v1はflowchart LRを必要とします", first === undefined ? undefined : lineSpan(first));
|
|
506
|
+
}
|
|
507
|
+
const nodes = new Map();
|
|
508
|
+
const edges = [];
|
|
509
|
+
const unsupported = [];
|
|
510
|
+
const nodePattern = /^\s*([A-Za-z][A-Za-z0-9_-]*)\(\("(.*)"\)\)\s*;?$/;
|
|
511
|
+
const edgePattern = /^\s*([A-Za-z][A-Za-z0-9_-]*)\s+(?:-->|-\.->)\|"(.*)"\|\s+([A-Za-z][A-Za-z0-9_-]*)\s*;?$/;
|
|
512
|
+
const stylePattern = /^\s*(?:classDef|class|linkStyle)\b/;
|
|
513
|
+
const unsafePattern = /^\s*(?:---|%%\{|click\b|link\b|callback\b|<)|<\/?[A-Za-z!][^>]*>/i;
|
|
514
|
+
let edgeIndex = 0;
|
|
515
|
+
for (const line of lines) {
|
|
516
|
+
const trimmed = line.text.trim();
|
|
517
|
+
if (trimmed === "" || line === first || stylePattern.test(line.text))
|
|
518
|
+
continue;
|
|
519
|
+
if (unsafePattern.test(line.text)) {
|
|
520
|
+
throw new ImportFailure("PTCNV-102", "実行可能directiveまたはraw HTMLはimportできません", lineSpan(line));
|
|
521
|
+
}
|
|
522
|
+
const nodeMatch = nodePattern.exec(line.text);
|
|
523
|
+
if (nodeMatch !== null) {
|
|
524
|
+
const rawId = nodeMatch[1];
|
|
525
|
+
if (!nodes.has(rawId)) {
|
|
526
|
+
nodes.set(rawId, { rawId, title: labelTitle(nodeMatch[2], rawId), line });
|
|
527
|
+
}
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
const edgeMatch = edgePattern.exec(line.text);
|
|
531
|
+
if (edgeMatch !== null) {
|
|
532
|
+
const from = edgeMatch[1];
|
|
533
|
+
const to = edgeMatch[3];
|
|
534
|
+
edges.push({ from, to, title: labelTitle(edgeMatch[2], `Imported task ${edgeIndex + 1}`), line, index: edgeIndex });
|
|
535
|
+
edgeIndex += 1;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
unsupported.push(line);
|
|
539
|
+
}
|
|
540
|
+
for (const edge of edges) {
|
|
541
|
+
if (!nodes.has(edge.from))
|
|
542
|
+
nodes.set(edge.from, { rawId: edge.from, title: edge.from, line: edge.line });
|
|
543
|
+
if (!nodes.has(edge.to))
|
|
544
|
+
nodes.set(edge.to, { rawId: edge.to, title: edge.to, line: edge.line });
|
|
545
|
+
}
|
|
546
|
+
if (nodes.size === 0) {
|
|
547
|
+
throw new ImportFailure("PTCNV-102", "import可能なnodeがありません");
|
|
548
|
+
}
|
|
549
|
+
const sortedNodes = [...nodes.values()].sort((left, right) => compareStableStrings(left.rawId, right.rawId));
|
|
550
|
+
const generatedIds = [];
|
|
551
|
+
const milestoneIds = new Map();
|
|
552
|
+
sortedNodes.forEach((node, index) => {
|
|
553
|
+
const generatedId = `MILESTONE_${String(index + 1).padStart(3, "0")}`;
|
|
554
|
+
milestoneIds.set(node.rawId, generatedId);
|
|
555
|
+
generatedIds.push({ sourceElement: `node:${node.rawId}`, generatedId });
|
|
556
|
+
});
|
|
557
|
+
edges.forEach((edge, index) => {
|
|
558
|
+
generatedIds.push({ sourceElement: `edge:${edge.index + 1}`, generatedId: `TASK_${String(index + 1).padStart(3, "0")}` });
|
|
559
|
+
});
|
|
560
|
+
const indegree = new Map(sortedNodes.map(({ rawId }) => [rawId, 0]));
|
|
561
|
+
const outdegree = new Map(sortedNodes.map(({ rawId }) => [rawId, 0]));
|
|
562
|
+
for (const edge of edges) {
|
|
563
|
+
indegree.set(edge.to, indegree.get(edge.to) + 1);
|
|
564
|
+
outdegree.set(edge.from, outdegree.get(edge.from) + 1);
|
|
565
|
+
}
|
|
566
|
+
const roots = new Set(sortedNodes.filter(({ rawId }) => indegree.get(rawId) === 0).map(({ rawId }) => rawId));
|
|
567
|
+
const sinks = sortedNodes.filter(({ rawId }) => outdegree.get(rawId) === 0);
|
|
568
|
+
const syntheticFinish = sinks.length !== 1;
|
|
569
|
+
const finish = syntheticFinish ? "MERMAID_FINISH" : milestoneIds.get(sinks[0].rawId);
|
|
570
|
+
if (syntheticFinish)
|
|
571
|
+
generatedIds.push({ sourceElement: "synthetic:finish", generatedId: finish });
|
|
572
|
+
const declarations = [[
|
|
573
|
+
"project IMPORTED_MERMAID:",
|
|
574
|
+
" version 1",
|
|
575
|
+
" title \"Imported Mermaid\"",
|
|
576
|
+
" duration_unit day",
|
|
577
|
+
` finish ${finish}`,
|
|
578
|
+
].join("\n")];
|
|
579
|
+
for (const node of sortedNodes) {
|
|
580
|
+
declarations.push([
|
|
581
|
+
`milestone ${milestoneIds.get(node.rawId)}:`,
|
|
582
|
+
` title ${JSON.stringify(node.title)}`,
|
|
583
|
+
...(roots.has(node.rawId) ? [" state reached"] : []),
|
|
584
|
+
].join("\n"));
|
|
585
|
+
}
|
|
586
|
+
if (syntheticFinish) {
|
|
587
|
+
declarations.push(["milestone MERMAID_FINISH:", " title \"Imported finish\""].join("\n"));
|
|
588
|
+
}
|
|
589
|
+
edges.forEach((edge, index) => {
|
|
590
|
+
declarations.push([
|
|
591
|
+
`task TASK_${String(index + 1).padStart(3, "0")} ${milestoneIds.get(edge.from)} -> ${milestoneIds.get(edge.to)}:`,
|
|
592
|
+
` title ${JSON.stringify(edge.title)}`,
|
|
593
|
+
" duration 1d",
|
|
594
|
+
].join("\n"));
|
|
595
|
+
});
|
|
596
|
+
if (syntheticFinish) {
|
|
597
|
+
sinks.forEach((sink, index) => {
|
|
598
|
+
const id = `FINISH_GATE_${String(index + 1).padStart(3, "0")}`;
|
|
599
|
+
generatedIds.push({ sourceElement: `synthetic:sink:${sink.rawId}`, generatedId: id });
|
|
600
|
+
declarations.push([
|
|
601
|
+
`gate ${id} ${milestoneIds.get(sink.rawId)} -> MERMAID_FINISH:`,
|
|
602
|
+
" reason \"Generated finish connector\"",
|
|
603
|
+
].join("\n"));
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
const artifact = `${declarations.join("\n\n")}\n`;
|
|
607
|
+
const checked = checkDocument(artifact, { maxDiagnostics });
|
|
608
|
+
if (!checked.ok) {
|
|
609
|
+
throw new ImportFailure("PTCNV-106", "plain MermaidからvalidなAoA DAGを構成できません");
|
|
610
|
+
}
|
|
611
|
+
const records = [
|
|
612
|
+
loss("PTCNV-202", "project fieldをplain Mermaid既定値で生成しました", "IMPORTED_MERMAID", lineSpan(first)),
|
|
613
|
+
loss("PTCNV-204", "resource、estimate以外のtask fieldをplain Mermaidから復元できません", null, null),
|
|
614
|
+
...sortedNodes.map((node) => loss("PTCNV-201", `${node.rawId}へstable milestone IDを生成しました`, milestoneIds.get(node.rawId), lineSpan(node.line))),
|
|
615
|
+
...edges.flatMap((edge, index) => [
|
|
616
|
+
loss("PTCNV-201", `edge ${edge.index + 1}へstable task IDを生成しました`, `TASK_${String(index + 1).padStart(3, "0")}`, lineSpan(edge.line)),
|
|
617
|
+
loss("PTCNV-203", "plain Mermaid edgeのtask/gate kindを確定できないためtaskとして生成しました", `TASK_${String(index + 1).padStart(3, "0")}`, lineSpan(edge.line)),
|
|
618
|
+
]),
|
|
619
|
+
...(syntheticFinish
|
|
620
|
+
? [loss("PTCNV-201", "複数sinkを統合するfinishとgate IDを生成しました", "MERMAID_FINISH", null)]
|
|
621
|
+
: []),
|
|
622
|
+
...unsupported.map((line) => loss("PTCNV-205", "未対応Mermaid statementを無視しました", null, lineSpan(line))),
|
|
623
|
+
];
|
|
624
|
+
return {
|
|
625
|
+
ok: true,
|
|
626
|
+
document: checked.document,
|
|
627
|
+
documentId: checked.documentId,
|
|
628
|
+
diagnostics: checked.diagnostics,
|
|
629
|
+
diagnosticsTruncated: checked.diagnosticsTruncated,
|
|
630
|
+
profile: "plain",
|
|
631
|
+
analysis: "none",
|
|
632
|
+
capacityOverrides: new Map(),
|
|
633
|
+
artifact,
|
|
634
|
+
artifactDigest: sha256(artifact),
|
|
635
|
+
lossReport: { lossless: false, records },
|
|
636
|
+
generatedIds,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
export function importMermaid(text, options = {}) {
|
|
640
|
+
const maxDiagnostics = normalizeMaxDiagnostics(options.maxDiagnostics);
|
|
641
|
+
const lines = sourceLines(text);
|
|
642
|
+
const profile = profileDetected(lines) ? "perttool" : "plain";
|
|
643
|
+
try {
|
|
644
|
+
return profile === "perttool"
|
|
645
|
+
? importProfile(text, lines, maxDiagnostics)
|
|
646
|
+
: importPlain(text, lines, maxDiagnostics);
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
if (error instanceof ImportFailure) {
|
|
650
|
+
return emptyResult(profile, error, maxDiagnostics);
|
|
651
|
+
}
|
|
652
|
+
throw error;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
//# sourceMappingURL=mermaid-import.js.map
|