agentic-mermaid 0.1.1 → 0.2.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/AGENT_NATIVE.md +155 -91
- package/CHANGELOG.md +163 -4
- package/Instructions_for_agents.md +10 -10
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +31 -38
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/assets/hero.png +0 -0
- package/dist/agent-core.d.ts +463 -0
- package/dist/agent-core.js +397 -0
- package/dist/agent.d.ts +73 -1868
- package/dist/agent.js +336 -37325
- package/dist/agentic-mermaid-mcp.js +7 -38399
- package/dist/am.js +33626 -40446
- package/dist/chunk-2P5FKUQC.js +3485 -0
- package/dist/chunk-2S3VSS6I.js +149 -0
- package/dist/chunk-2SILJDW2.js +37 -0
- package/dist/chunk-BJYMBT4I.js +61963 -0
- package/dist/chunk-FDM43XU7.js +615 -0
- package/dist/chunk-MYPDVZCO.js +4702 -0
- package/dist/index-DWPa2MJs.d.ts +6780 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +310 -33014
- package/dist/mcp-cli-5J47WEY3.js +17 -0
- package/docs/README.md +39 -31
- package/docs/agent-api-cookbook.md +44 -35
- package/docs/agent-mutation-policy.md +1 -1
- package/docs/agent-workflow-examples.md +2 -2
- package/docs/api.md +284 -77
- package/docs/ascii.md +26 -16
- package/docs/code-mode-impact.md +214 -0
- package/docs/comparison.md +23 -11
- package/docs/computational-aesthetics-prototype-plan.md +252 -0
- package/docs/config.md +26 -10
- package/docs/custom-fonts.md +265 -0
- package/docs/custom-style-cookbook.md +130 -9
- package/docs/diagram-families.md +48 -23
- package/docs/features.md +59 -52
- package/docs/fork-differences.md +6 -3
- package/docs/getting-started.md +23 -16
- package/docs/issue-derived-test-cases.md +29 -22
- package/docs/mcp-code-mode-rationale.md +13 -6
- package/docs/mcp-http-transport.md +20 -13
- package/docs/mermaid-layout-complaints.md +66 -52
- package/docs/mutation-testing.md +145 -66
- package/docs/quality.md +23 -14
- package/docs/react.md +2 -2
- package/docs/schemas/style-spec.schema.json +1221 -30
- package/docs/style-authoring.md +241 -38
- package/docs/svg-semantic-contract.md +49 -0
- package/docs/testing-strategy.md +166 -58
- package/docs/theming.md +19 -32
- package/examples/styles/catalog.json +54 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
- package/examples/styles/cupertino-prototype.mmd +10 -0
- package/examples/styles/cupertino-prototype.style.json +63 -0
- package/examples/styles/mid-century-report.style.json +2 -2
- package/examples/styles/star-chart-atlas.style.json +2 -1
- package/examples/styles/transit-route-map.style.json +2 -2
- package/examples/styles/vercel-inspired-prototype.mmd +6 -0
- package/examples/styles/vercel-inspired-prototype.style.json +87 -0
- package/package.json +85 -45
- package/server.json +2 -2
- package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
- package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
- package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
- package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
- package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
- package/TODO.md +0 -684
- package/dist/index-wjRjbsP-.d.ts +0 -1102
- package/docs/assets/improvements/box-start-connector.png +0 -0
- package/docs/assets/improvements/comment-dropped-lint.png +0 -0
- package/docs/assets/improvements/er-content-loss.png +0 -0
- package/docs/assets/improvements/fan-in-trunks.png +0 -0
- package/docs/assets/improvements/gantt-family.png +0 -0
- package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
- package/docs/assets/improvements/gantt-verify-seam.png +0 -0
- package/docs/assets/improvements/subgraph-container-edges.png +0 -0
- package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
- package/docs/assets/improvements/wrapper-init-directive.png +0 -0
- package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
- package/docs/assets/style-cookbook/mid-century-report.png +0 -0
- package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
- package/docs/assets/style-cookbook/transit-route-map.png +0 -0
- package/docs/contributing/adding-diagram-types.md +0 -144
- package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
- package/docs/contributing/diagram-family-citizenship.md +0 -82
- package/docs/contributing/harvesting-upstream-tests.md +0 -126
- package/docs/contributing/lessons-learned.md +0 -276
- package/docs/contributing/releasing.md +0 -82
- package/docs/contributing/visual-review-evidence.md +0 -53
- package/docs/design/families/README.md +0 -26
- package/docs/design/families/architecture-beta.md +0 -48
- package/docs/design/families/flowchart-parser-conformance.md +0 -22
- package/docs/design/families/gantt-research.md +0 -137
- package/docs/design/families/gantt.md +0 -329
- package/docs/design/families/journey-agentic-cards-before.png +0 -0
- package/docs/design/families/journey-experience-curve-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
- package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
- package/docs/design/families/journey-migration-parity.md +0 -439
- package/docs/design/families/journey-section-overlap-after.png +0 -0
- package/docs/design/families/journey-section-overlap-before.png +0 -0
- package/docs/design/families/journey-usage-research.md +0 -423
- package/docs/design/families/journey.md +0 -153
- package/docs/design/families/xychart.md +0 -85
- package/docs/design/system/README.md +0 -45
- package/docs/design/system/abstraction-audit.md +0 -224
- package/docs/design/system/abstraction-recommendations.md +0 -655
- package/docs/design/system/architecture.mmd +0 -29
- package/docs/design/system/architecture.svg +0 -137
- package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
- package/docs/design/system/layout-rubric.md +0 -138
- package/docs/design/system/route-contracts.md +0 -848
- package/docs/design/system/source-preservation-ladder.md +0 -52
- package/docs/design/system/styles-rollout.md +0 -216
- package/docs/design/system/ugly-layouts.md +0 -119
- package/docs/layout-characterization/README.md +0 -245
- package/docs/layout-characterization/contact-sheet-families.md +0 -498
- package/docs/layout-characterization/contact-sheet.md +0 -638
- package/docs/layout-characterization/properties.md +0 -281
- package/docs/layout-characterization/visual-quality.md +0 -160
- package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
- package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
- package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
- package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
- package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
- package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
- package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
- package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
- package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
- package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
- package/docs/project/divergences.md +0 -98
- package/docs/project/dogfooding-docs-strategy.md +0 -66
- package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
- package/docs/project/lessons-learned.md +0 -1019
- package/docs/project/remove-role-styling-plan.md +0 -176
- package/examples/agent-improve-auth-flow.ts +0 -206
- package/examples/agent-loop.ts +0 -53
- package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
- package/llms.txt +0 -104
- package/skill-evals/README.md +0 -77
- package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
- package/skill-evals/fixtures/class-payment/input.mmd +0 -4
- package/skill-evals/fixtures/er-orders/input.mmd +0 -10
- package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
- package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
- package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
- package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
- package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
- package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
- package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
- package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
- package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
- package/skill-evals/shared-benchmark.json +0 -510
- package/src/agent/analyze.ts +0 -157
- package/src/agent/apply.ts +0 -146
- package/src/agent/architecture-body.ts +0 -458
- package/src/agent/certificates.ts +0 -110
- package/src/agent/class-body.ts +0 -319
- package/src/agent/core.ts +0 -207
- package/src/agent/create.ts +0 -106
- package/src/agent/describe.ts +0 -368
- package/src/agent/er-body.ts +0 -224
- package/src/agent/facts.ts +0 -360
- package/src/agent/families-builtin.ts +0 -920
- package/src/agent/families.ts +0 -232
- package/src/agent/family-layouts.ts +0 -832
- package/src/agent/flowchart-body.ts +0 -372
- package/src/agent/flowchart-unsupported.ts +0 -243
- package/src/agent/font-coverage.ts +0 -288
- package/src/agent/gantt-body.ts +0 -495
- package/src/agent/index.ts +0 -10
- package/src/agent/journey-body.ts +0 -374
- package/src/agent/label-metrics.ts +0 -28
- package/src/agent/layout-to-rendered.ts +0 -63
- package/src/agent/mutate.ts +0 -97
- package/src/agent/mutation-ops.ts +0 -51
- package/src/agent/op-schema.ts +0 -443
- package/src/agent/parse.ts +0 -223
- package/src/agent/pie-body.ts +0 -270
- package/src/agent/png.ts +0 -171
- package/src/agent/quadrant-body.ts +0 -336
- package/src/agent/quality.ts +0 -363
- package/src/agent/readability-audit.ts +0 -92
- package/src/agent/rendered-route-audit.ts +0 -61
- package/src/agent/sequence-body.ts +0 -345
- package/src/agent/serialize.ts +0 -214
- package/src/agent/state-body.ts +0 -589
- package/src/agent/structural-count.ts +0 -150
- package/src/agent/timeline-body.ts +0 -280
- package/src/agent/trace-log.ts +0 -43
- package/src/agent/types.ts +0 -995
- package/src/agent/verify.ts +0 -603
- package/src/agent/xychart-body.ts +0 -472
- package/src/architecture/config.ts +0 -282
- package/src/architecture/layout.ts +0 -496
- package/src/architecture/parser.ts +0 -375
- package/src/architecture/renderer.ts +0 -611
- package/src/architecture/types.ts +0 -113
- package/src/ascii/ansi.ts +0 -423
- package/src/ascii/architecture.ts +0 -210
- package/src/ascii/canvas.ts +0 -443
- package/src/ascii/class-diagram.ts +0 -697
- package/src/ascii/converter.ts +0 -475
- package/src/ascii/draw.ts +0 -1693
- package/src/ascii/edge-bundling.ts +0 -328
- package/src/ascii/edge-routing.ts +0 -330
- package/src/ascii/er-diagram.ts +0 -439
- package/src/ascii/gantt.ts +0 -276
- package/src/ascii/grid.ts +0 -747
- package/src/ascii/index.ts +0 -196
- package/src/ascii/journey.ts +0 -169
- package/src/ascii/meta.ts +0 -244
- package/src/ascii/multiline-utils.ts +0 -80
- package/src/ascii/pathfinder.ts +0 -301
- package/src/ascii/pie.ts +0 -67
- package/src/ascii/quadrant.ts +0 -158
- package/src/ascii/reverse.ts +0 -216
- package/src/ascii/sequence.ts +0 -499
- package/src/ascii/shapes/circle.ts +0 -27
- package/src/ascii/shapes/corners.ts +0 -143
- package/src/ascii/shapes/diamond.ts +0 -27
- package/src/ascii/shapes/hexagon.ts +0 -27
- package/src/ascii/shapes/index.ts +0 -106
- package/src/ascii/shapes/rectangle.ts +0 -168
- package/src/ascii/shapes/rounded.ts +0 -27
- package/src/ascii/shapes/special.ts +0 -332
- package/src/ascii/shapes/stadium.ts +0 -110
- package/src/ascii/shapes/state.ts +0 -192
- package/src/ascii/shapes/types.ts +0 -73
- package/src/ascii/timeline.ts +0 -141
- package/src/ascii/types.ts +0 -312
- package/src/ascii/validate.ts +0 -120
- package/src/ascii/width.ts +0 -81
- package/src/ascii/wrap.ts +0 -131
- package/src/ascii/xychart.ts +0 -919
- package/src/browser.ts +0 -29
- package/src/class/layout.ts +0 -240
- package/src/class/parser.ts +0 -332
- package/src/class/renderer.ts +0 -638
- package/src/class/types.ts +0 -129
- package/src/cli/agent-instructions.ts +0 -1
- package/src/cli/am-bin.ts +0 -6
- package/src/cli/exit-codes.ts +0 -18
- package/src/cli/index.ts +0 -1132
- package/src/cli/init-agent.ts +0 -147
- package/src/cli/run-entrypoint.ts +0 -10
- package/src/color-resolver.ts +0 -152
- package/src/elk-instance.ts +0 -114
- package/src/er/layout.ts +0 -187
- package/src/er/parser.ts +0 -226
- package/src/er/renderer.ts +0 -792
- package/src/er/types.ts +0 -99
- package/src/family-rubric.ts +0 -425
- package/src/font-manifest.ts +0 -31
- package/src/gantt/layout.ts +0 -365
- package/src/gantt/parser.ts +0 -282
- package/src/gantt/pipeline.ts +0 -27
- package/src/gantt/renderer.ts +0 -414
- package/src/gantt/schedule.ts +0 -513
- package/src/gantt/types.ts +0 -255
- package/src/index.ts +0 -368
- package/src/journey/layout.ts +0 -554
- package/src/journey/parse-core.ts +0 -251
- package/src/journey/parser.ts +0 -101
- package/src/journey/renderer.ts +0 -917
- package/src/journey/types.ts +0 -144
- package/src/layout/geometry.ts +0 -182
- package/src/layout/pass.ts +0 -142
- package/src/layout/passes/index.ts +0 -3916
- package/src/layout-engine.ts +0 -1565
- package/src/layout-rubric.ts +0 -659
- package/src/mcp/artifacts.ts +0 -137
- package/src/mcp/dynamic-harness.ts +0 -31
- package/src/mcp/dynamic-modules.d.ts +0 -7
- package/src/mcp/facade.ts +0 -608
- package/src/mcp/harness-runtime.ts +0 -203
- package/src/mcp/hosted-server.ts +0 -468
- package/src/mcp/mcp-bin.ts +0 -3
- package/src/mcp/mcp-cli.ts +0 -82
- package/src/mcp/protocol.ts +0 -13
- package/src/mcp/sandbox.ts +0 -195
- package/src/mcp/sdk-decl.ts +0 -391
- package/src/mcp/server.ts +0 -362
- package/src/mcp/tool-surface.ts +0 -146
- package/src/mermaid-source.ts +0 -655
- package/src/multiline-utils.ts +0 -228
- package/src/parser.ts +0 -1031
- package/src/pie/layout.ts +0 -168
- package/src/pie/parser.ts +0 -136
- package/src/pie/renderer.ts +0 -234
- package/src/pie/types.ts +0 -82
- package/src/quadrant/layout.ts +0 -191
- package/src/quadrant/parser.ts +0 -183
- package/src/quadrant/renderer.ts +0 -322
- package/src/quadrant/types.ts +0 -113
- package/src/render-family-hooks.ts +0 -244
- package/src/renderer.ts +0 -1099
- package/src/route-contracts.ts +0 -2723
- package/src/scene/backend.ts +0 -76
- package/src/scene/fidelity.ts +0 -183
- package/src/scene/hybrid-backend.ts +0 -174
- package/src/scene/ir.ts +0 -181
- package/src/scene/marks.ts +0 -127
- package/src/scene/rough-backend.ts +0 -378
- package/src/scene/seed.ts +0 -46
- package/src/scene/style-registry.ts +0 -556
- package/src/sequence/layout.ts +0 -456
- package/src/sequence/parser.ts +0 -255
- package/src/sequence/renderer.ts +0 -604
- package/src/sequence/types.ts +0 -154
- package/src/shape-clipping.ts +0 -351
- package/src/shared/accessibility-directives.ts +0 -22
- package/src/shared/batched.ts +0 -61
- package/src/shared/color-math.ts +0 -93
- package/src/shared/svg-a11y.ts +0 -29
- package/src/shared/syntax-error.ts +0 -17
- package/src/shared/unicode-ranges.ts +0 -135
- package/src/styles.ts +0 -318
- package/src/svg-paths.ts +0 -28
- package/src/text-metrics.ts +0 -248
- package/src/theme.ts +0 -710
- package/src/timeline/layout.ts +0 -381
- package/src/timeline/parser.ts +0 -215
- package/src/timeline/renderer.ts +0 -538
- package/src/timeline/types.ts +0 -105
- package/src/types.ts +0 -462
- package/src/website-worker-modules.d.ts +0 -18
- package/src/xychart/axis-utils.ts +0 -109
- package/src/xychart/colors.ts +0 -124
- package/src/xychart/config.ts +0 -88
- package/src/xychart/layout.ts +0 -656
- package/src/xychart/parser.ts +0 -504
- package/src/xychart/renderer.ts +0 -640
- package/src/xychart/types.ts +0 -282
package/src/agent/gantt-body.ts
DELETED
|
@@ -1,495 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Gantt structured body: parse / serialize / mutate / verify (FamilyPlugin
|
|
3
|
-
// hooks) — docs/design/families/gantt.md §"First-release syntax matrix".
|
|
4
|
-
//
|
|
5
|
-
// Segment-preserving structured body (the BUILD-18 sequence pattern): typed
|
|
6
|
-
// ops cover the modeled statements — title, sections, tasks — while calendar
|
|
7
|
-
// directives (dateFormat, axisFormat, excludes, includes, weekend, weekday,
|
|
8
|
-
// todayMarker, tickInterval, inclusiveEndDates, topAxis), click lines,
|
|
9
|
-
// accTitle/accDescr, and comments ride along VERBATIM as opaque-block
|
|
10
|
-
// segments in their original position. Never dropped; edited at the source
|
|
11
|
-
// level only. Promoting a directive to a typed op is future work gated on the
|
|
12
|
-
// scheduler being able to re-resolve it.
|
|
13
|
-
//
|
|
14
|
-
// Whole-opaque fallback (return null) only for structure-level failures:
|
|
15
|
-
// header suffix, unclosed accDescr block, or duplicate Mermaid task ids
|
|
16
|
-
// (identity becomes unsafe to model).
|
|
17
|
-
//
|
|
18
|
-
// Mutation validation is correctness-by-construction: every value an op
|
|
19
|
-
// writes is rendered to its canonical line and re-parsed; the op is rejected
|
|
20
|
-
// unless the round-trip reproduces the intended statement exactly. That keeps
|
|
21
|
-
// structured bodies serialize-idempotent by construction.
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
import { unknownOpMessage } from './mutation-ops.ts'
|
|
25
|
-
import type {
|
|
26
|
-
GanttBody, GanttBodySection, GanttBodyTask, GanttBodyTaskTag,
|
|
27
|
-
GanttStatement, GanttMutationOp, MutationError, Result, VerifyOptions, LayoutWarning,
|
|
28
|
-
} from './types.ts'
|
|
29
|
-
import { ok, err } from './types.ts'
|
|
30
|
-
import { labelOverflowWarning } from './label-metrics.ts'
|
|
31
|
-
import { parseGanttTaskMeta, renderGanttTaskMeta, GANTT_TASK_TAGS, type ParsedTaskMeta } from '../gantt/parser.ts'
|
|
32
|
-
|
|
33
|
-
// Directive openers that are NEVER task lines even though they may contain `:`
|
|
34
|
-
// (accTitle:/accDescr:). Everything matched here becomes an opaque segment.
|
|
35
|
-
const DIRECTIVE_LINE_RE = /^(dateFormat|axisFormat|tickInterval|inclusiveEndDates|topAxis|excludes|includes|todayMarker|weekday|weekend|click|accTitle|accDescr)\b/i
|
|
36
|
-
const ACC_DESCR_BLOCK_OPEN_RE = /^accDescr\s*\{\s*$/i
|
|
37
|
-
const TITLE_RE = /^title\s+(.+)$/i
|
|
38
|
-
const SECTION_RE = /^section\s+(.+)$/i
|
|
39
|
-
const TASK_ID_RE = /^[\w-]+$/
|
|
40
|
-
|
|
41
|
-
// ---- Parser -----------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Parse gantt body lines into a segment-preserving structured body.
|
|
45
|
-
* `trimmedLines` are normalized body lines; `rawLines` keep original
|
|
46
|
-
* indentation for verbatim opaque segments (pass the same array when raw
|
|
47
|
-
* lines are unavailable). Returns null for structure-level failures so the
|
|
48
|
-
* caller falls back to a lossless whole-body opaque body.
|
|
49
|
-
*/
|
|
50
|
-
export function parseGanttBody(trimmedLines: string[], rawLines?: string[]): GanttBody | null {
|
|
51
|
-
const raw = rawLines ?? trimmedLines
|
|
52
|
-
const body: GanttBody = { kind: 'gantt', sections: [], statements: [] }
|
|
53
|
-
const statements = body.statements!
|
|
54
|
-
const seenTaskIds = new Set<string>()
|
|
55
|
-
let currentSection = -1 // -1 = no section yet; an implicit one is created on demand
|
|
56
|
-
let sIdx = 0
|
|
57
|
-
let tIdx = 0
|
|
58
|
-
|
|
59
|
-
const implicitSection = (): number => {
|
|
60
|
-
if (currentSection === -1) {
|
|
61
|
-
body.sections.push({ id: `section-${sIdx++}`, tasks: [] })
|
|
62
|
-
currentSection = body.sections.length - 1
|
|
63
|
-
}
|
|
64
|
-
return currentSection
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let i = 0
|
|
68
|
-
while (i < raw.length) {
|
|
69
|
-
const rawLine = raw[i]!
|
|
70
|
-
const line = rawLine.trim()
|
|
71
|
-
if (!line || line.startsWith('%%')) {
|
|
72
|
-
// Comments are unmodeled lines: preserve them verbatim in position.
|
|
73
|
-
if (line.startsWith('%%')) appendOpaque(statements, [rawLine])
|
|
74
|
-
i++
|
|
75
|
-
continue
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (ACC_DESCR_BLOCK_OPEN_RE.test(line)) {
|
|
79
|
-
// Capture the whole block verbatim; inner lines may contain `:` and must
|
|
80
|
-
// not be misread as tasks. Unclosed block → whole-opaque fallback.
|
|
81
|
-
const blockLines: string[] = [rawLine]
|
|
82
|
-
i++
|
|
83
|
-
let closed = false
|
|
84
|
-
while (i < raw.length) {
|
|
85
|
-
const inner = raw[i]!
|
|
86
|
-
blockLines.push(inner)
|
|
87
|
-
i++
|
|
88
|
-
if (/^\}\s*$/.test(inner.trim())) { closed = true; break }
|
|
89
|
-
}
|
|
90
|
-
if (!closed) return null
|
|
91
|
-
appendOpaque(statements, blockLines)
|
|
92
|
-
continue
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (DIRECTIVE_LINE_RE.test(line)) {
|
|
96
|
-
appendOpaque(statements, [rawLine])
|
|
97
|
-
i++
|
|
98
|
-
continue
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let m: RegExpMatchArray | null
|
|
102
|
-
if ((m = line.match(TITLE_RE))) {
|
|
103
|
-
body.title = m[1]!.trim()
|
|
104
|
-
// Re-declared titles keep their statement position (last wins for the value).
|
|
105
|
-
statements.push({ kind: 'title' })
|
|
106
|
-
i++
|
|
107
|
-
continue
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if ((m = line.match(SECTION_RE))) {
|
|
111
|
-
body.sections.push({ id: `section-${sIdx++}`, label: m[1]!.trim(), tasks: [] })
|
|
112
|
-
currentSection = body.sections.length - 1
|
|
113
|
-
statements.push({ kind: 'section', ref: currentSection })
|
|
114
|
-
i++
|
|
115
|
-
continue
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Task line: `<label> : <metadata>`.
|
|
119
|
-
const colon = line.indexOf(':')
|
|
120
|
-
if (colon > 0 && colon < line.length - 1) {
|
|
121
|
-
const label = line.slice(0, colon).trim()
|
|
122
|
-
const meta = parseGanttTaskMeta(line.slice(colon + 1).trim())
|
|
123
|
-
if (label && meta) {
|
|
124
|
-
if (meta.id !== undefined) {
|
|
125
|
-
if (seenTaskIds.has(meta.id)) return null // duplicate ids → identity unsafe
|
|
126
|
-
seenTaskIds.add(meta.id)
|
|
127
|
-
}
|
|
128
|
-
const section = implicitSection()
|
|
129
|
-
const task: GanttBodyTask = {
|
|
130
|
-
id: `task-${tIdx++}`,
|
|
131
|
-
taskId: meta.id,
|
|
132
|
-
label,
|
|
133
|
-
tags: canonicalTags(meta.tags),
|
|
134
|
-
start: meta.start ? (meta.start.kind === 'after' ? `after ${meta.start.refs.join(' ')}` : meta.start.raw) : undefined,
|
|
135
|
-
end: meta.end.kind === 'until' ? `until ${meta.end.refs.join(' ')}` : meta.end.raw,
|
|
136
|
-
}
|
|
137
|
-
body.sections[section]!.tasks.push(task)
|
|
138
|
-
statements.push({ kind: 'task', section, ref: body.sections[section]!.tasks.length - 1 })
|
|
139
|
-
i++
|
|
140
|
-
continue
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Any other unmodeled line rides along verbatim.
|
|
145
|
-
appendOpaque(statements, [rawLine])
|
|
146
|
-
i++
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return body
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function canonicalTags(tags: GanttBodyTaskTag[]): GanttBodyTaskTag[] {
|
|
153
|
-
return GANTT_TASK_TAGS.filter(t => tags.includes(t)) as GanttBodyTaskTag[]
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function appendOpaque(statements: GanttStatement[], lines: string[]): void {
|
|
157
|
-
const last = statements[statements.length - 1]
|
|
158
|
-
if (last && last.kind === 'opaque-block') last.lines.push(...lines)
|
|
159
|
-
else statements.push({ kind: 'opaque-block', lines: [...lines] })
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ---- Serializer ---------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
function taskMeta(task: GanttBodyTask): ParsedTaskMeta {
|
|
165
|
-
return {
|
|
166
|
-
tags: canonicalTags(task.tags),
|
|
167
|
-
id: task.taskId,
|
|
168
|
-
start: task.start === undefined
|
|
169
|
-
? undefined
|
|
170
|
-
: task.start.startsWith('after ')
|
|
171
|
-
? { kind: 'after', refs: task.start.slice('after '.length).split(/\s+/).filter(Boolean) }
|
|
172
|
-
: { kind: 'date', raw: task.start },
|
|
173
|
-
end: task.end.startsWith('until ')
|
|
174
|
-
? { kind: 'until', refs: task.end.slice('until '.length).split(/\s+/).filter(Boolean) }
|
|
175
|
-
: { kind: 'date', raw: task.end }, // duration vs date is irrelevant for emission
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function renderTaskLine(task: GanttBodyTask): string {
|
|
180
|
-
return ` ${task.label} :${renderGanttTaskMeta(taskMeta(task))}`
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function renderGantt(body: GanttBody): string {
|
|
184
|
-
const lines: string[] = ['gantt']
|
|
185
|
-
|
|
186
|
-
if (body.statements && body.statements.length > 0) {
|
|
187
|
-
for (const st of body.statements) {
|
|
188
|
-
if (st.kind === 'opaque-block') {
|
|
189
|
-
for (const l of st.lines) lines.push(l)
|
|
190
|
-
} else if (st.kind === 'title') {
|
|
191
|
-
if (body.title !== undefined) lines.push(` title ${body.title}`)
|
|
192
|
-
} else if (st.kind === 'section') {
|
|
193
|
-
const s = body.sections[st.ref]
|
|
194
|
-
if (s && s.label !== undefined) lines.push(` section ${s.label}`)
|
|
195
|
-
} else {
|
|
196
|
-
const t = body.sections[st.section]?.tasks[st.ref]
|
|
197
|
-
if (t) lines.push(renderTaskLine(t))
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return lines.join('\n') + '\n'
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Synthesized path (no statements): title, then sections in order.
|
|
204
|
-
if (body.title !== undefined) lines.push(` title ${body.title}`)
|
|
205
|
-
for (const s of body.sections) {
|
|
206
|
-
if (s.label !== undefined) lines.push(` section ${s.label}`)
|
|
207
|
-
for (const t of s.tasks) lines.push(renderTaskLine(t))
|
|
208
|
-
}
|
|
209
|
-
return lines.join('\n') + '\n'
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ---- Mutator --------------------------------------------------------------------
|
|
213
|
-
|
|
214
|
-
function cloneBody(b: GanttBody): GanttBody {
|
|
215
|
-
return {
|
|
216
|
-
kind: 'gantt',
|
|
217
|
-
title: b.title,
|
|
218
|
-
sections: b.sections.map(s => ({ id: s.id, label: s.label, tasks: s.tasks.map(t => ({ ...t, tags: [...t.tags] })) })),
|
|
219
|
-
statements: (b.statements ?? deriveStatements(b)).map(cloneStatement),
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function cloneStatement(s: GanttStatement): GanttStatement {
|
|
224
|
-
return s.kind === 'opaque-block' ? { kind: 'opaque-block', lines: [...s.lines] } : { ...s }
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function deriveStatements(b: GanttBody): GanttStatement[] {
|
|
228
|
-
const out: GanttStatement[] = []
|
|
229
|
-
if (b.title !== undefined) out.push({ kind: 'title' })
|
|
230
|
-
b.sections.forEach((s, si) => {
|
|
231
|
-
if (s.label !== undefined) out.push({ kind: 'section', ref: si })
|
|
232
|
-
s.tasks.forEach((_, ti) => out.push({ kind: 'task', section: si, ref: ti }))
|
|
233
|
-
})
|
|
234
|
-
return out
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function makeIdAllocator(body: GanttBody, prefix: 'section' | 'task'): () => string {
|
|
238
|
-
const seen = new Set<string>()
|
|
239
|
-
for (const s of body.sections) { seen.add(s.id); for (const t of s.tasks) seen.add(t.id) }
|
|
240
|
-
return () => {
|
|
241
|
-
let n = 0
|
|
242
|
-
while (seen.has(`${prefix}-${n}`)) n++
|
|
243
|
-
const id = `${prefix}-${n}`
|
|
244
|
-
seen.add(id)
|
|
245
|
-
return id
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Correctness by construction: render the candidate task to its canonical
|
|
250
|
-
// line and re-parse; reject unless the round-trip reproduces it exactly.
|
|
251
|
-
function validateTask(task: GanttBodyTask): MutationError | null {
|
|
252
|
-
if (!task.label || /[:\r\n]/.test(task.label)) {
|
|
253
|
-
return { code: 'INVALID_OP', message: 'Gantt task label must be non-empty and must not contain ":" or newlines' }
|
|
254
|
-
}
|
|
255
|
-
if (task.taskId !== undefined && !TASK_ID_RE.test(task.taskId)) {
|
|
256
|
-
return { code: 'INVALID_OP', message: `Gantt task id "${task.taskId}" must match ${TASK_ID_RE}` }
|
|
257
|
-
}
|
|
258
|
-
for (const field of [task.start, task.end]) {
|
|
259
|
-
if (field !== undefined && /[,:#\r\n]/.test(field)) {
|
|
260
|
-
return { code: 'INVALID_OP', message: 'Gantt task dates must not contain "," ":" "#" or newlines' }
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
if (!task.end) return { code: 'INVALID_OP', message: 'Gantt task end (date, duration, or "until id") is required' }
|
|
264
|
-
const line = renderTaskLine(task).trim()
|
|
265
|
-
const colon = line.indexOf(':')
|
|
266
|
-
const label = colon > 0 ? line.slice(0, colon).trim() : ''
|
|
267
|
-
const meta = colon > 0 ? parseGanttTaskMeta(line.slice(colon + 1).trim()) : null
|
|
268
|
-
const reparsesAsDirective = TITLE_RE.test(line) || SECTION_RE.test(line) || DIRECTIVE_LINE_RE.test(line)
|
|
269
|
-
if (!meta || label !== task.label || reparsesAsDirective) {
|
|
270
|
-
return { code: 'INVALID_OP', message: `Gantt task does not round-trip through its canonical line ("${line}")` }
|
|
271
|
-
}
|
|
272
|
-
const renderedBack = renderGanttTaskMeta(meta)
|
|
273
|
-
if (renderedBack !== renderGanttTaskMeta(taskMeta(task))) {
|
|
274
|
-
return { code: 'INVALID_OP', message: `Gantt task metadata is ambiguous ("${renderedBack}")` }
|
|
275
|
-
}
|
|
276
|
-
return null
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function validateSectionLabel(label: string): MutationError | null {
|
|
280
|
-
if (!label.trim() || /[:\r\n]/.test(label)) {
|
|
281
|
-
return { code: 'INVALID_OP', message: 'Gantt section label must be non-empty and must not contain ":" or newlines' }
|
|
282
|
-
}
|
|
283
|
-
const line = `section ${label.trim()}`
|
|
284
|
-
if (!SECTION_RE.test(line)) return { code: 'INVALID_OP', message: 'Gantt section label does not round-trip' }
|
|
285
|
-
return null
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function allTaskIds(body: GanttBody): Set<string> {
|
|
289
|
-
const ids = new Set<string>()
|
|
290
|
-
for (const s of body.sections) for (const t of s.tasks) if (t.taskId !== undefined) ids.add(t.taskId)
|
|
291
|
-
return ids
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
export function mutateGantt(input: GanttBody, op: GanttMutationOp): Result<GanttBody, MutationError> {
|
|
295
|
-
const body = cloneBody(input)
|
|
296
|
-
const statements = body.statements!
|
|
297
|
-
const nextSectionId = makeIdAllocator(body, 'section')
|
|
298
|
-
const nextTaskId = makeIdAllocator(body, 'task')
|
|
299
|
-
|
|
300
|
-
const getSection = (i: number): GanttBodySection | undefined => body.sections[i]
|
|
301
|
-
const getTask = (si: number, ti: number): GanttBodyTask | undefined => getSection(si)?.tasks[ti]
|
|
302
|
-
|
|
303
|
-
switch (op.kind) {
|
|
304
|
-
case 'set_title': {
|
|
305
|
-
if (op.title === null) {
|
|
306
|
-
body.title = undefined
|
|
307
|
-
removeAll(statements, s => s.kind === 'title')
|
|
308
|
-
break
|
|
309
|
-
}
|
|
310
|
-
const title = op.title.trim()
|
|
311
|
-
if (!title || /[\r\n]/.test(title)) return err({ code: 'INVALID_OP', message: 'Gantt title must be a non-empty single line' })
|
|
312
|
-
const hadTitle = body.title !== undefined
|
|
313
|
-
body.title = title
|
|
314
|
-
if (!hadTitle) statements.unshift({ kind: 'title' })
|
|
315
|
-
break
|
|
316
|
-
}
|
|
317
|
-
case 'add_section': {
|
|
318
|
-
const bad = validateSectionLabel(op.label)
|
|
319
|
-
if (bad) return err(bad)
|
|
320
|
-
body.sections.push({ id: nextSectionId(), label: op.label.trim(), tasks: [] })
|
|
321
|
-
statements.push({ kind: 'section', ref: body.sections.length - 1 })
|
|
322
|
-
break
|
|
323
|
-
}
|
|
324
|
-
case 'rename_section': {
|
|
325
|
-
const s = getSection(op.index)
|
|
326
|
-
if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.index}` })
|
|
327
|
-
if (s.label === undefined) return err({ code: 'INVALID_OP', message: 'Cannot rename the implicit (unlabeled) section' })
|
|
328
|
-
const bad = validateSectionLabel(op.label)
|
|
329
|
-
if (bad) return err(bad)
|
|
330
|
-
s.label = op.label.trim()
|
|
331
|
-
break
|
|
332
|
-
}
|
|
333
|
-
case 'remove_section': {
|
|
334
|
-
if (!getSection(op.index)) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.index}` })
|
|
335
|
-
body.sections.splice(op.index, 1)
|
|
336
|
-
removeAll(statements, s =>
|
|
337
|
-
(s.kind === 'section' && s.ref === op.index) || (s.kind === 'task' && s.section === op.index))
|
|
338
|
-
for (const s of statements) {
|
|
339
|
-
if (s.kind === 'section' && s.ref > op.index) s.ref--
|
|
340
|
-
if (s.kind === 'task' && s.section > op.index) s.section--
|
|
341
|
-
}
|
|
342
|
-
break
|
|
343
|
-
}
|
|
344
|
-
case 'add_task': {
|
|
345
|
-
const s = getSection(op.sectionIndex)
|
|
346
|
-
if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.sectionIndex}` })
|
|
347
|
-
if (op.taskId !== undefined && allTaskIds(body).has(op.taskId)) {
|
|
348
|
-
return err({ code: 'DUPLICATE_TASK', message: `Task id "${op.taskId}" already exists` })
|
|
349
|
-
}
|
|
350
|
-
const task: GanttBodyTask = {
|
|
351
|
-
id: nextTaskId(),
|
|
352
|
-
taskId: op.taskId,
|
|
353
|
-
label: op.label?.trim() ?? '',
|
|
354
|
-
tags: canonicalTags(op.tags ?? []),
|
|
355
|
-
start: op.start?.trim() || undefined,
|
|
356
|
-
end: (op.end ?? '').trim(),
|
|
357
|
-
}
|
|
358
|
-
const bad = validateTask(task)
|
|
359
|
-
if (bad) return err(bad)
|
|
360
|
-
s.tasks.push(task)
|
|
361
|
-
insertTaskStatement(statements, op.sectionIndex, s.tasks.length - 1)
|
|
362
|
-
break
|
|
363
|
-
}
|
|
364
|
-
case 'remove_task': {
|
|
365
|
-
const s = getSection(op.sectionIndex)
|
|
366
|
-
if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.sectionIndex}` })
|
|
367
|
-
if (!s.tasks[op.taskIndex]) return err({ code: 'TASK_NOT_FOUND', message: `No task at index ${op.taskIndex}` })
|
|
368
|
-
s.tasks.splice(op.taskIndex, 1)
|
|
369
|
-
removeAll(statements, st => st.kind === 'task' && st.section === op.sectionIndex && st.ref === op.taskIndex)
|
|
370
|
-
for (const st of statements) {
|
|
371
|
-
if (st.kind === 'task' && st.section === op.sectionIndex && st.ref > op.taskIndex) st.ref--
|
|
372
|
-
}
|
|
373
|
-
break
|
|
374
|
-
}
|
|
375
|
-
case 'rename_task': {
|
|
376
|
-
const t = getTask(op.sectionIndex, op.taskIndex)
|
|
377
|
-
if (!t) return err({ code: 'TASK_NOT_FOUND', message: `No task at (${op.sectionIndex},${op.taskIndex})` })
|
|
378
|
-
const candidate = { ...t, label: op.label?.trim() ?? '' }
|
|
379
|
-
const bad = validateTask(candidate)
|
|
380
|
-
if (bad) return err(bad)
|
|
381
|
-
t.label = candidate.label
|
|
382
|
-
break
|
|
383
|
-
}
|
|
384
|
-
case 'set_task_status': {
|
|
385
|
-
const t = getTask(op.sectionIndex, op.taskIndex)
|
|
386
|
-
if (!t) return err({ code: 'TASK_NOT_FOUND', message: `No task at (${op.sectionIndex},${op.taskIndex})` })
|
|
387
|
-
const structural = t.tags.filter(tag => tag === 'milestone' || tag === 'vert')
|
|
388
|
-
const next = op.status === null ? structural : canonicalTags([op.status, ...structural])
|
|
389
|
-
if (op.status !== null && !['active', 'done', 'crit'].includes(op.status)) {
|
|
390
|
-
return err({ code: 'INVALID_OP', message: `Invalid task status "${String(op.status)}"` })
|
|
391
|
-
}
|
|
392
|
-
t.tags = next
|
|
393
|
-
break
|
|
394
|
-
}
|
|
395
|
-
case 'set_task_dates': {
|
|
396
|
-
const t = getTask(op.sectionIndex, op.taskIndex)
|
|
397
|
-
if (!t) return err({ code: 'TASK_NOT_FOUND', message: `No task at (${op.sectionIndex},${op.taskIndex})` })
|
|
398
|
-
const candidate = { ...t, tags: [...t.tags] }
|
|
399
|
-
if (op.start !== undefined) candidate.start = op.start === null ? undefined : op.start.trim()
|
|
400
|
-
if (op.end !== undefined) candidate.end = op.end.trim()
|
|
401
|
-
const bad = validateTask(candidate)
|
|
402
|
-
if (bad) return err(bad)
|
|
403
|
-
t.start = candidate.start
|
|
404
|
-
t.end = candidate.end
|
|
405
|
-
break
|
|
406
|
-
}
|
|
407
|
-
default: {
|
|
408
|
-
const _x: never = op
|
|
409
|
-
return err({ code: 'INVALID_OP', message: unknownOpMessage('gantt', _x) })
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
return ok(body)
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function removeAll(statements: GanttStatement[], pred: (s: GanttStatement) => boolean): void {
|
|
416
|
-
for (let i = statements.length - 1; i >= 0; i--) if (pred(statements[i]!)) statements.splice(i, 1)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Insert a new task statement right after the last statement belonging to its
|
|
420
|
-
// section (the section header or its last task), so serialization keeps tasks
|
|
421
|
-
// under the right `section` line even with directives interleaved.
|
|
422
|
-
function insertTaskStatement(statements: GanttStatement[], sectionIndex: number, taskRef: number): void {
|
|
423
|
-
let at = -1
|
|
424
|
-
for (let i = 0; i < statements.length; i++) {
|
|
425
|
-
const s = statements[i]!
|
|
426
|
-
if ((s.kind === 'section' && s.ref === sectionIndex) || (s.kind === 'task' && s.section === sectionIndex)) at = i
|
|
427
|
-
}
|
|
428
|
-
const st: GanttStatement = { kind: 'task', section: sectionIndex, ref: taskRef }
|
|
429
|
-
if (at >= 0) statements.splice(at + 1, 0, st)
|
|
430
|
-
else statements.push(st)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// ---- Verify --------------------------------------------------------------------
|
|
434
|
-
|
|
435
|
-
const AFTER_OR_UNTIL_RE = /^(?:after|until)\s+(.+)$/
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Source-level structural verification (spec §Verification): EMPTY_DIAGRAM,
|
|
439
|
-
* LABEL_OVERFLOW on title/section/task labels, EDGE_MISANCHORED for
|
|
440
|
-
* after/until references to unknown task ids. Reference checking also scans
|
|
441
|
-
* opaque segments for task-shaped lines so a partially-modeled body does not
|
|
442
|
-
* produce false positives.
|
|
443
|
-
*/
|
|
444
|
-
export function verifyGantt(body: GanttBody, opts: VerifyOptions): LayoutWarning[] {
|
|
445
|
-
const warnings: LayoutWarning[] = []
|
|
446
|
-
const cap = opts.labelCharCap ?? 40
|
|
447
|
-
|
|
448
|
-
const tasks = body.sections.flatMap(s => s.tasks)
|
|
449
|
-
const opaqueLines = (body.statements ?? [])
|
|
450
|
-
.filter((s): s is Extract<GanttStatement, { kind: 'opaque-block' }> => s.kind === 'opaque-block')
|
|
451
|
-
.flatMap(s => s.lines.map(l => l.trim()))
|
|
452
|
-
.filter(l => l.length > 0)
|
|
453
|
-
|
|
454
|
-
if (tasks.length === 0 && body.title === undefined && opaqueLines.length === 0) {
|
|
455
|
-
return [{ code: 'EMPTY_DIAGRAM' }]
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const overflow = (target: string, text: string) => {
|
|
459
|
-
const w = labelOverflowWarning(target, text, cap)
|
|
460
|
-
if (w) warnings.push(w)
|
|
461
|
-
}
|
|
462
|
-
if (body.title !== undefined) overflow('title', body.title)
|
|
463
|
-
for (const s of body.sections) {
|
|
464
|
-
if (s.label !== undefined) overflow(s.id, s.label)
|
|
465
|
-
for (const t of s.tasks) overflow(t.id, t.label)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Known ids: structured tasks + task-shaped opaque lines (a malformed task
|
|
469
|
-
// line still "defines" its id for reference purposes — better to miss a
|
|
470
|
-
// warning than to flag a reference that renders fine).
|
|
471
|
-
const known = allTaskIds(body)
|
|
472
|
-
for (const line of opaqueLines) {
|
|
473
|
-
const colon = line.indexOf(':')
|
|
474
|
-
if (colon <= 0) continue
|
|
475
|
-
const items = line.slice(colon + 1).split(',').map(s => s.trim()).filter(Boolean)
|
|
476
|
-
let i = 0
|
|
477
|
-
while (i < items.length && (GANTT_TASK_TAGS as readonly string[]).includes(items[i]!)) i++
|
|
478
|
-
const candidate = items[i]
|
|
479
|
-
if (candidate !== undefined && /^[\w-]+$/.test(candidate)) known.add(candidate)
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
for (const t of tasks) {
|
|
483
|
-
for (const expr of [t.start, t.end]) {
|
|
484
|
-
const m = expr?.match(AFTER_OR_UNTIL_RE)
|
|
485
|
-
if (!m) continue
|
|
486
|
-
for (const ref of m[1]!.split(/\s+/).filter(Boolean)) {
|
|
487
|
-
if (!known.has(ref)) {
|
|
488
|
-
warnings.push({ code: 'EDGE_MISANCHORED', edge: `${t.id}:${expr!.split(/\s+/)[0]}->${ref}` })
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return warnings
|
|
495
|
-
}
|
package/src/agent/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// agentic-mermaid — public agent surface (v4)
|
|
3
|
-
// Re-exports the runtime-neutral core plus the Node-native PNG renderer.
|
|
4
|
-
// Workerd/browser bundles must import `./core.ts` instead: `renderMermaidPNG`
|
|
5
|
-
// loads the napi resvg addon, which only exists on Node/Bun.
|
|
6
|
-
// ============================================================================
|
|
7
|
-
|
|
8
|
-
export * from './core.ts'
|
|
9
|
-
export { renderMermaidPNG } from './png.ts'
|
|
10
|
-
export type { PngOptions } from './png.ts'
|