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
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// countStructuralElements — a family-agnostic faithfulness oracle.
|
|
3
|
-
//
|
|
4
|
-
// "100% parse success is not faithfulness" (lessons-learned, Loop 17): a parser
|
|
5
|
-
// can accept a source and the serializer can round-trip byte-stably while a
|
|
6
|
-
// relationship is silently dropped (the ER `}o` bug shipped inside a fully
|
|
7
|
-
// gated corpus for exactly this reason). Byte-stability proves serialize∘parse
|
|
8
|
-
// is idempotent; it does NOT prove parse preserved the source's content.
|
|
9
|
-
//
|
|
10
|
-
// This counter projects every renderable family onto a small {nodes, edges,
|
|
11
|
-
// groups} tally drawn from the structured IR. Comparing the count of a source
|
|
12
|
-
// against the count of its re-parsed serialization turns "did we drop a node or
|
|
13
|
-
// edge on round-trip?" into a deterministic assertion across all twelve
|
|
14
|
-
// families — without a golden image and without a human. It backs both the
|
|
15
|
-
// corpus count-oracle (eval) and the CONTENT_DROPPED_ON_ROUNDTRIP verify lint.
|
|
16
|
-
//
|
|
17
|
-
// Opaque bodies have no structured arrays to count, so they return null and the
|
|
18
|
-
// caller skips them (their faithfulness contract is byte-verbatim preservation,
|
|
19
|
-
// which the round-trip-stability gate already covers).
|
|
20
|
-
// ============================================================================
|
|
21
|
-
|
|
22
|
-
import type { ValidDiagram, StateNode, LayoutWarning } from './types.ts'
|
|
23
|
-
import type { MermaidSubgraph } from '../types.ts'
|
|
24
|
-
|
|
25
|
-
export interface StructuralCount {
|
|
26
|
-
/** Primary entities: nodes, participants, states, classes, entities, slices… */
|
|
27
|
-
nodes: number
|
|
28
|
-
/** Relations: edges, messages, transitions, relations. 0 for relationless families. */
|
|
29
|
-
edges: number
|
|
30
|
-
/** Containers: subgraphs, sections, groups. 0 when the family has none. */
|
|
31
|
-
groups: number
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function countStates(states: StateNode[]): { nodes: number; edges: number } {
|
|
35
|
-
let nodes = 0, edges = 0
|
|
36
|
-
for (const s of states) {
|
|
37
|
-
nodes++
|
|
38
|
-
if (s.states && s.states.length) {
|
|
39
|
-
const inner = countStates(s.states)
|
|
40
|
-
nodes += inner.nodes
|
|
41
|
-
edges += inner.edges
|
|
42
|
-
}
|
|
43
|
-
if (s.transitions) edges += s.transitions.length
|
|
44
|
-
}
|
|
45
|
-
return { nodes, edges }
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function countSubgraphs(subgraphs: MermaidSubgraph[]): number {
|
|
49
|
-
let groups = 0
|
|
50
|
-
for (const sg of subgraphs) {
|
|
51
|
-
groups++
|
|
52
|
-
groups += countSubgraphs(sg.children)
|
|
53
|
-
}
|
|
54
|
-
return groups
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Count the structured elements of a diagram, or null for opaque bodies (which
|
|
59
|
-
* carry no structured arrays — their faithfulness is byte-verbatim, covered by
|
|
60
|
-
* the round-trip gate).
|
|
61
|
-
*/
|
|
62
|
-
export function countStructuralElements(d: ValidDiagram): StructuralCount | null {
|
|
63
|
-
const body = d.body
|
|
64
|
-
switch (body.kind) {
|
|
65
|
-
case 'flowchart': {
|
|
66
|
-
const g = body.graph
|
|
67
|
-
return { nodes: g.nodes.size, edges: g.edges.length, groups: countSubgraphs(g.subgraphs) }
|
|
68
|
-
}
|
|
69
|
-
case 'sequence':
|
|
70
|
-
return { nodes: body.participants.length, edges: body.messages.length, groups: 0 }
|
|
71
|
-
case 'state': {
|
|
72
|
-
const top = countStates(body.states)
|
|
73
|
-
return { nodes: top.nodes, edges: top.edges + body.transitions.length, groups: 0 }
|
|
74
|
-
}
|
|
75
|
-
case 'class':
|
|
76
|
-
return { nodes: body.classes.length, edges: body.relations.length, groups: 0 }
|
|
77
|
-
case 'er':
|
|
78
|
-
return { nodes: body.entities.length, edges: body.relations.length, groups: 0 }
|
|
79
|
-
case 'timeline': {
|
|
80
|
-
let nodes = 0
|
|
81
|
-
for (const sec of body.sections) for (const p of sec.periods) nodes += 1 + p.events.length
|
|
82
|
-
return { nodes, edges: 0, groups: body.sections.length }
|
|
83
|
-
}
|
|
84
|
-
case 'journey': {
|
|
85
|
-
let nodes = 0
|
|
86
|
-
for (const sec of body.sections) nodes += sec.tasks.length
|
|
87
|
-
return { nodes, edges: 0, groups: body.sections.length }
|
|
88
|
-
}
|
|
89
|
-
case 'architecture':
|
|
90
|
-
return {
|
|
91
|
-
nodes: body.services.length + body.junctions.length,
|
|
92
|
-
edges: body.edges.length,
|
|
93
|
-
groups: body.groups.length,
|
|
94
|
-
}
|
|
95
|
-
case 'xychart':
|
|
96
|
-
return { nodes: body.series.length, edges: 0, groups: 0 }
|
|
97
|
-
case 'pie':
|
|
98
|
-
return { nodes: body.slices.length, edges: 0, groups: 0 }
|
|
99
|
-
case 'quadrant':
|
|
100
|
-
return { nodes: body.points.length, edges: 0, groups: 0 }
|
|
101
|
-
case 'gantt': {
|
|
102
|
-
let nodes = 0
|
|
103
|
-
for (const sec of body.sections) nodes += sec.tasks.length
|
|
104
|
-
return { nodes, edges: 0, groups: body.sections.length }
|
|
105
|
-
}
|
|
106
|
-
case 'opaque':
|
|
107
|
-
return null
|
|
108
|
-
default: {
|
|
109
|
-
// Exhaustiveness guard: a new family must declare its count here.
|
|
110
|
-
const _never: never = body
|
|
111
|
-
void _never
|
|
112
|
-
return null
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function countsEqual(a: StructuralCount, b: StructuralCount): boolean {
|
|
118
|
-
return a.nodes === b.nodes && a.edges === b.edges && a.groups === b.groups
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Pure faithfulness verdict over the before/after counts of a round-trip
|
|
123
|
-
* (Move 3): the decision logic of the CONTENT_DROPPED_ON_ROUNDTRIP verify lint,
|
|
124
|
-
* separated from the parse/serialize I/O so it lives in the mutation-gated
|
|
125
|
-
* counter module and is directly unit-testable with constructed counts.
|
|
126
|
-
*
|
|
127
|
-
* before = null → opaque body, nothing to check (no warning).
|
|
128
|
-
* after = null → the serialization did not re-parse: total loss.
|
|
129
|
-
* counts differ → a node/edge/group was dropped or duplicated.
|
|
130
|
-
* counts equal → faithful (no warning).
|
|
131
|
-
*/
|
|
132
|
-
export function faithfulnessWarning(
|
|
133
|
-
before: StructuralCount | null,
|
|
134
|
-
after: StructuralCount | null,
|
|
135
|
-
): LayoutWarning[] {
|
|
136
|
-
if (!before) return []
|
|
137
|
-
if (!after) return [{ code: 'CONTENT_DROPPED_ON_ROUNDTRIP', before, after: { nodes: 0, edges: 0, groups: 0 } }]
|
|
138
|
-
if (!countsEqual(before, after)) return [{ code: 'CONTENT_DROPPED_ON_ROUNDTRIP', before, after }]
|
|
139
|
-
return []
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Boolean view of {@link faithfulnessWarning} for the differential gates
|
|
144
|
-
* (corpus / seqbench / upstream), which only need "did content drop?" — reads
|
|
145
|
-
* better than `faithfulnessWarning(...).length > 0` at the call sites and is
|
|
146
|
-
* independently testable.
|
|
147
|
-
*/
|
|
148
|
-
export function isDrop(before: StructuralCount | null, after: StructuralCount | null): boolean {
|
|
149
|
-
return faithfulnessWarning(before, after).length > 0
|
|
150
|
-
}
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Timeline structured body: parse / serialize / mutate (FamilyPlugin hooks).
|
|
3
|
-
//
|
|
4
|
-
// Structured-or-null parse, same pattern as sequence: any unmodeled line
|
|
5
|
-
// makes the caller fall back to a lossless opaque body.
|
|
6
|
-
// ============================================================================
|
|
7
|
-
|
|
8
|
-
import { unknownOpMessage } from './mutation-ops.ts'
|
|
9
|
-
import type {
|
|
10
|
-
TimelineBody, TimelineSection, TimelinePeriod, TimelineEvent,
|
|
11
|
-
TimelineMutationOp, MutationError, Result,
|
|
12
|
-
} from './types.ts'
|
|
13
|
-
import { ok, err } from './types.ts'
|
|
14
|
-
|
|
15
|
-
// ---- Parser -----------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
const TL_TITLE_RE = /^title\s+(.+)$/i
|
|
18
|
-
const TL_SECTION_RE = /^section\s+(.+)$/i
|
|
19
|
-
const TL_PERIOD_RE = /^([^:]+?)\s*:\s*(.*)$/
|
|
20
|
-
const TL_CONT_RE = /^:\s*(.+)$/ // continuation of previous period
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Parse the body lines of a timeline diagram. Returns a structured body only
|
|
24
|
-
* if EVERY non-blank, non-comment line is one of: title, section, period
|
|
25
|
-
* (`<label> : <event>` and `: <event>` continuations), or a multi-event line
|
|
26
|
-
* with extra `:` separators. Otherwise returns null so caller falls back to
|
|
27
|
-
* a lossless opaque body.
|
|
28
|
-
*
|
|
29
|
-
* Mirrors the legacy parser's accepted syntax (src/timeline/parser.ts).
|
|
30
|
-
*/
|
|
31
|
-
function normalizeTimelineText(value: string): string {
|
|
32
|
-
return value.split(/\r?\n/).map(part => part.trim()).filter(Boolean).join(' ')
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function validTimelineText(value: string, opts: { allowColon: boolean }): boolean {
|
|
36
|
-
return value.length > 0 && (opts.allowColon || !value.includes(':'))
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function parseTimelineEventSegments(raw: string): string[] | null {
|
|
40
|
-
if (raw.trim().length === 0) return []
|
|
41
|
-
const segments = raw.split(':').map(normalizeTimelineText)
|
|
42
|
-
if (segments.some(segment => !validTimelineText(segment, { allowColon: false }))) return null
|
|
43
|
-
return segments
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function parseTimelineBody(lines: string[]): TimelineBody | null {
|
|
47
|
-
const body: TimelineBody = { kind: 'timeline', sections: [] }
|
|
48
|
-
let currentSection: TimelineSection | undefined
|
|
49
|
-
let currentPeriod: TimelinePeriod | undefined
|
|
50
|
-
let sIdx = 0, pIdx = 0, eIdx = 0
|
|
51
|
-
|
|
52
|
-
const implicitSection = (): TimelineSection => {
|
|
53
|
-
if (!currentSection) {
|
|
54
|
-
currentSection = { id: `section-${sIdx++}`, periods: [] }
|
|
55
|
-
body.sections.push(currentSection)
|
|
56
|
-
}
|
|
57
|
-
return currentSection
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
for (const raw of lines) {
|
|
61
|
-
const line = raw.trim()
|
|
62
|
-
if (!line) continue
|
|
63
|
-
if (line.startsWith('%%')) continue
|
|
64
|
-
|
|
65
|
-
const tm = line.match(TL_TITLE_RE)
|
|
66
|
-
if (tm) {
|
|
67
|
-
const title = normalizeTimelineText(tm[1]!)
|
|
68
|
-
if (!validTimelineText(title, { allowColon: true })) return null
|
|
69
|
-
body.title = title
|
|
70
|
-
continue
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const sm = line.match(TL_SECTION_RE)
|
|
74
|
-
if (sm) {
|
|
75
|
-
const label = normalizeTimelineText(sm[1]!)
|
|
76
|
-
if (!validTimelineText(label, { allowColon: true })) return null
|
|
77
|
-
currentSection = { id: `section-${sIdx++}`, label, periods: [] }
|
|
78
|
-
body.sections.push(currentSection)
|
|
79
|
-
currentPeriod = undefined
|
|
80
|
-
continue
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// `: <continuation>` — extra event on the previous period.
|
|
84
|
-
const cont = line.match(TL_CONT_RE)
|
|
85
|
-
if (cont && !line.match(TL_PERIOD_RE)) {
|
|
86
|
-
if (!currentPeriod) return null
|
|
87
|
-
const text = normalizeTimelineText(cont[1]!)
|
|
88
|
-
if (!validTimelineText(text, { allowColon: false })) return null
|
|
89
|
-
currentPeriod.events.push({ id: `event-${eIdx++}`, text })
|
|
90
|
-
continue
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// `<label> : <event> [ : <event2> : <event3> …]`
|
|
94
|
-
const pm = line.match(TL_PERIOD_RE)
|
|
95
|
-
if (pm) {
|
|
96
|
-
const label = normalizeTimelineText(pm[1]!)
|
|
97
|
-
if (!validTimelineText(label, { allowColon: false })) return null
|
|
98
|
-
const restRaw = pm[2]!
|
|
99
|
-
// Multi-event lines allow `: extra` segments to add more events to the same period.
|
|
100
|
-
const eventTexts = parseTimelineEventSegments(restRaw)
|
|
101
|
-
if (!eventTexts) return null
|
|
102
|
-
const events: TimelineEvent[] = eventTexts.map(text => ({
|
|
103
|
-
id: `event-${eIdx++}`, text,
|
|
104
|
-
}))
|
|
105
|
-
const period: TimelinePeriod = { id: `period-${pIdx++}`, label, events }
|
|
106
|
-
implicitSection().periods.push(period)
|
|
107
|
-
currentPeriod = period
|
|
108
|
-
continue
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Unmodeled line → opaque fallback.
|
|
112
|
-
return null
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return body
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ---- Serializer -------------------------------------------------------------
|
|
119
|
-
|
|
120
|
-
export function renderTimeline(body: TimelineBody): string {
|
|
121
|
-
const lines: string[] = ['timeline']
|
|
122
|
-
if (body.title) lines.push(` title ${body.title}`)
|
|
123
|
-
for (const section of body.sections) {
|
|
124
|
-
if (section.label !== undefined) lines.push(` section ${section.label}`)
|
|
125
|
-
for (const period of section.periods) {
|
|
126
|
-
// First event on the same line as the period label; extra events on
|
|
127
|
-
// continuation lines (`: text`). Matches Mermaid timeline syntax.
|
|
128
|
-
if (period.events.length === 0) {
|
|
129
|
-
lines.push(` ${period.label} :`)
|
|
130
|
-
continue
|
|
131
|
-
}
|
|
132
|
-
lines.push(` ${period.label} : ${period.events[0]!.text}`)
|
|
133
|
-
for (const e of period.events.slice(1)) lines.push(` : ${e.text}`)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return lines.join('\n') + '\n'
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ---- Mutator ----------------------------------------------------------------
|
|
140
|
-
|
|
141
|
-
function cloneTimeline(b: TimelineBody): TimelineBody {
|
|
142
|
-
return {
|
|
143
|
-
kind: 'timeline',
|
|
144
|
-
title: b.title,
|
|
145
|
-
sections: b.sections.map(s => ({
|
|
146
|
-
id: s.id, label: s.label,
|
|
147
|
-
periods: s.periods.map(p => ({ id: p.id, label: p.label, events: p.events.map(e => ({ id: e.id, text: e.text })) })),
|
|
148
|
-
})),
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function makeTimelineIdAllocator(body: TimelineBody): (prefix: 'section' | 'period' | 'event') => string {
|
|
153
|
-
const seen = new Set<string>()
|
|
154
|
-
for (const s of body.sections) {
|
|
155
|
-
seen.add(s.id)
|
|
156
|
-
for (const p of s.periods) { seen.add(p.id); for (const e of p.events) seen.add(e.id) }
|
|
157
|
-
}
|
|
158
|
-
return prefix => {
|
|
159
|
-
let n = 0
|
|
160
|
-
while (seen.has(`${prefix}-${n}`)) n++
|
|
161
|
-
const id = `${prefix}-${n}`
|
|
162
|
-
seen.add(id)
|
|
163
|
-
return id
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function normalizeTimelineOpText(value: string, opts: { field: string; allowColon?: boolean }): Result<string, MutationError> {
|
|
168
|
-
if (typeof value !== 'string') return err({ code: 'INVALID_OP', message: `Timeline ${opts.field} must be a string` })
|
|
169
|
-
const normalized = normalizeTimelineText(value)
|
|
170
|
-
if (!validTimelineText(normalized, { allowColon: opts.allowColon ?? true })) {
|
|
171
|
-
return err({ code: 'INVALID_OP', message: `Timeline ${opts.field} must be non-empty${opts.allowColon === false ? ' and must not contain :' : ''}` })
|
|
172
|
-
}
|
|
173
|
-
return ok(normalized)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function mutateTimeline(input: TimelineBody, op: TimelineMutationOp): Result<TimelineBody, MutationError> {
|
|
177
|
-
const body = cloneTimeline(input)
|
|
178
|
-
const nextTimelineId = makeTimelineIdAllocator(body)
|
|
179
|
-
|
|
180
|
-
const getSection = (i: number): TimelineSection | undefined => body.sections[i]
|
|
181
|
-
const getPeriod = (si: number, pi: number): TimelinePeriod | undefined => getSection(si)?.periods[pi]
|
|
182
|
-
|
|
183
|
-
switch (op.kind) {
|
|
184
|
-
case 'set_title': {
|
|
185
|
-
if (op.title === null) delete body.title
|
|
186
|
-
else {
|
|
187
|
-
const title = normalizeTimelineOpText(op.title, { field: 'title' })
|
|
188
|
-
if (!title.ok) return title
|
|
189
|
-
body.title = title.value
|
|
190
|
-
}
|
|
191
|
-
break
|
|
192
|
-
}
|
|
193
|
-
case 'add_section': {
|
|
194
|
-
const label = normalizeTimelineOpText(op.label, { field: 'section label' })
|
|
195
|
-
if (!label.ok) return label
|
|
196
|
-
body.sections.push({ id: nextTimelineId('section'), label: label.value, periods: [] })
|
|
197
|
-
break
|
|
198
|
-
}
|
|
199
|
-
case 'remove_section': {
|
|
200
|
-
if (!getSection(op.index)) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.index}` })
|
|
201
|
-
body.sections.splice(op.index, 1)
|
|
202
|
-
break
|
|
203
|
-
}
|
|
204
|
-
case 'set_section_label': {
|
|
205
|
-
const s = getSection(op.index)
|
|
206
|
-
if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.index}` })
|
|
207
|
-
const label = normalizeTimelineOpText(op.label, { field: 'section label' })
|
|
208
|
-
if (!label.ok) return label
|
|
209
|
-
s.label = label.value
|
|
210
|
-
break
|
|
211
|
-
}
|
|
212
|
-
case 'add_period': {
|
|
213
|
-
const s = getSection(op.sectionIndex)
|
|
214
|
-
if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.sectionIndex}` })
|
|
215
|
-
const label = normalizeTimelineOpText(op.label, { field: 'period label', allowColon: false })
|
|
216
|
-
if (!label.ok) return label
|
|
217
|
-
const events: TimelinePeriod['events'] = []
|
|
218
|
-
for (const raw of op.events ?? []) {
|
|
219
|
-
const text = normalizeTimelineOpText(raw, { field: 'event text', allowColon: false })
|
|
220
|
-
if (!text.ok) return text
|
|
221
|
-
events.push({ id: nextTimelineId('event'), text: text.value })
|
|
222
|
-
}
|
|
223
|
-
const period: TimelinePeriod = {
|
|
224
|
-
id: nextTimelineId('period'),
|
|
225
|
-
label: label.value,
|
|
226
|
-
events,
|
|
227
|
-
}
|
|
228
|
-
s.periods.push(period)
|
|
229
|
-
break
|
|
230
|
-
}
|
|
231
|
-
case 'remove_period': {
|
|
232
|
-
const s = getSection(op.sectionIndex)
|
|
233
|
-
if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.sectionIndex}` })
|
|
234
|
-
if (!s.periods[op.periodIndex]) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at index ${op.periodIndex}` })
|
|
235
|
-
s.periods.splice(op.periodIndex, 1)
|
|
236
|
-
if (s.label === undefined && s.periods.length === 0) body.sections.splice(op.sectionIndex, 1)
|
|
237
|
-
break
|
|
238
|
-
}
|
|
239
|
-
case 'set_period_label': {
|
|
240
|
-
const p = getPeriod(op.sectionIndex, op.periodIndex)
|
|
241
|
-
if (!p) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at (${op.sectionIndex},${op.periodIndex})` })
|
|
242
|
-
const label = normalizeTimelineOpText(op.label, { field: 'period label', allowColon: false })
|
|
243
|
-
if (!label.ok) return label
|
|
244
|
-
p.label = label.value
|
|
245
|
-
break
|
|
246
|
-
}
|
|
247
|
-
case 'add_event': {
|
|
248
|
-
const p = getPeriod(op.sectionIndex, op.periodIndex)
|
|
249
|
-
if (!p) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at (${op.sectionIndex},${op.periodIndex})` })
|
|
250
|
-
const text = normalizeTimelineOpText(op.text, { field: 'event text', allowColon: false })
|
|
251
|
-
if (!text.ok) return text
|
|
252
|
-
p.events.push({ id: nextTimelineId('event'), text: text.value })
|
|
253
|
-
break
|
|
254
|
-
}
|
|
255
|
-
case 'remove_event': {
|
|
256
|
-
const p = getPeriod(op.sectionIndex, op.periodIndex)
|
|
257
|
-
if (!p) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at (${op.sectionIndex},${op.periodIndex})` })
|
|
258
|
-
if (!p.events[op.eventIndex]) return err({ code: 'EVENT_NOT_FOUND', message: `No event at index ${op.eventIndex}` })
|
|
259
|
-
p.events.splice(op.eventIndex, 1)
|
|
260
|
-
const section = getSection(op.sectionIndex)
|
|
261
|
-
if (section && section.label === undefined && section.periods.length === 0) body.sections.splice(op.sectionIndex, 1)
|
|
262
|
-
break
|
|
263
|
-
}
|
|
264
|
-
case 'set_event_text': {
|
|
265
|
-
const p = getPeriod(op.sectionIndex, op.periodIndex)
|
|
266
|
-
if (!p) return err({ code: 'PERIOD_NOT_FOUND', message: `No period at (${op.sectionIndex},${op.periodIndex})` })
|
|
267
|
-
const e = p.events[op.eventIndex]
|
|
268
|
-
if (!e) return err({ code: 'EVENT_NOT_FOUND', message: `No event at index ${op.eventIndex}` })
|
|
269
|
-
const text = normalizeTimelineOpText(op.text, { field: 'event text', allowColon: false })
|
|
270
|
-
if (!text.ok) return text
|
|
271
|
-
e.text = text.value
|
|
272
|
-
break
|
|
273
|
-
}
|
|
274
|
-
default: {
|
|
275
|
-
const _x: never = op
|
|
276
|
-
return err({ code: 'INVALID_OP', message: unknownOpMessage('timeline', _x) })
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return ok(body)
|
|
280
|
-
}
|
package/src/agent/trace-log.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// Shared OBSERVED tool-use sink. When the AM_TRACE_LOG env var names a file,
|
|
2
|
-
// every agent-facing entry point — the `am` CLI, the library functions
|
|
3
|
-
// (verifyMermaid / mutate / buildMermaid / …), and the hosted MCP verify/mutate/
|
|
4
|
-
// build tools — appends one `{verb}` JSON line here. The agent-usage eval reads
|
|
5
|
-
// this to grade traceOk from the calls an agent ACTUALLY made, regardless of
|
|
6
|
-
// which channel it drove, instead of inferring tool use from its Trace prose
|
|
7
|
-
// (see eval/agent-usage/RUNBOOK.md).
|
|
8
|
-
//
|
|
9
|
-
// Portability: this module is bundled for the browser (the website editor runs
|
|
10
|
-
// the library), so it must NOT statically import `node:fs`. It reaches fs at
|
|
11
|
-
// runtime via `process.getBuiltinModule` — absent in browsers/Workers, so it
|
|
12
|
-
// degrades to a no-op there. Logging is best-effort and never throws into the
|
|
13
|
-
// caller: a diagram operation must never fail because its trace line couldn't
|
|
14
|
-
// be written.
|
|
15
|
-
|
|
16
|
-
type AppendFn = (path: string, data: string) => void
|
|
17
|
-
|
|
18
|
-
let appendFn: AppendFn | null | undefined // undefined = not yet probed
|
|
19
|
-
|
|
20
|
-
function resolveAppend(): AppendFn | null {
|
|
21
|
-
if (appendFn !== undefined) return appendFn
|
|
22
|
-
appendFn = null
|
|
23
|
-
try {
|
|
24
|
-
const proc = (globalThis as { process?: { getBuiltinModule?: (m: string) => unknown } }).process
|
|
25
|
-
const fs = proc?.getBuiltinModule?.('node:fs') as { appendFileSync?: AppendFn } | undefined
|
|
26
|
-
if (typeof fs?.appendFileSync === 'function') appendFn = fs.appendFileSync
|
|
27
|
-
} catch { /* no node fs available (browser / Worker) */ }
|
|
28
|
-
return appendFn
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Append `{verb}` (or `{verb, ok}`) to $AM_TRACE_LOG if set. No-op when unset or
|
|
33
|
-
* off-node. Pass `ok` for verbs whose outcome is meaningful (mutate/build): an
|
|
34
|
-
* `{ok:false}` line records a FAILED op attempt, so the eval can measure a run's
|
|
35
|
-
* error rate from observed failures rather than inferring retries from call
|
|
36
|
-
* counts. Verbs with no natural pass/fail (verify, capabilities) omit it.
|
|
37
|
-
*/
|
|
38
|
-
export function logToolInvocation(verb: string, ok?: boolean): void {
|
|
39
|
-
const path = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.AM_TRACE_LOG
|
|
40
|
-
if (!path) return
|
|
41
|
-
const record = ok === undefined ? { verb } : { verb, ok }
|
|
42
|
-
try { resolveAppend()?.(path, JSON.stringify(record) + '\n') } catch { /* best-effort */ }
|
|
43
|
-
}
|