markform 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Markform
2
2
 
3
+ [![CI](https://github.com/jlevy/markform/actions/workflows/ci.yml/badge.svg)](https://github.com/jlevy/markform/actions/workflows/ci.yml)
4
+ ![Coverage](./badges/coverage-total.svg)
5
+
3
6
  **Markform** is a text format for defining structured forms that humans can read,
4
7
  machines can parse, and agents can fill via tool calls.
5
8
 
@@ -113,21 +116,23 @@ Fields have types defined by the attributes.
113
116
  Values are filled in incrementally, just like any form.
114
117
  Once filled in, values appear directly inside the tags, in Markdown format:
115
118
 
116
- ```jinja
119
+ ````jinja
117
120
  {% field kind="string" id="movie" label="Movie" role="user"
118
121
  required=true minLength=1 maxLength=300 %}
122
+ ```value
119
123
  The Shawshank Redemption
124
+ ```
120
125
  {% /field %}
121
126
 
122
127
  {% field kind="single_select" id="mpaa_rating" role="agent" label="MPAA Rating" %}
123
128
  - [ ] G {% #g %}
124
129
  - [ ] PG {% #pg %}
125
130
  - [ ] PG-13 {% #pg_13 %}
126
- - [X] R {% #r %}
131
+ - [x] R {% #r %}
127
132
  - [ ] NC-17 {% #nc_17 %}
128
133
  - [ ] NR/Unrated {% #nr %}
129
134
  {% /field %}
130
- ```
135
+ ````
131
136
 
132
137
  Note fields can have a `role="user"` to indicate they are filled interactively by the
133
138
  user, or a `role="agent"` to indicate an agent should fill them in.
@@ -283,7 +288,10 @@ Enter the movie title (add year or details for disambiguation).
283
288
  {% /field %}
284
289
 
285
290
  {% instructions ref="ratings_table" %}
286
- Fill in scores and vote counts from each source:IMDB: Rating (1.0-10.0 scale), vote countRT Critics: Tomatometer (0-100%), review countRT Audience: Audience Score (0-100%), rating count
291
+ Fill in scores and vote counts from each source:
292
+ - IMDB: Rating (1.0-10.0 scale), vote count
293
+ - RT Critics: Tomatometer (0-100%), review count
294
+ - RT Audience: Audience Score (0-100%), rating count
287
295
  {% /instructions %}
288
296
 
289
297
  {% /group %}
@@ -371,14 +379,37 @@ View them with `markform examples --list`, copy with `markform examples`, and ru
371
379
  - [`movie-research-demo.form.md`](https://github.com/jlevy/markform/blob/main/packages/markform/examples/movie-research/movie-research-demo.form.md)
372
380
  \- The quick example above.
373
381
 
374
- - [`movie-research-basic.form.md`](https://github.com/jlevy/markform/blob/main/packages/markform/examples/movie-research/movie-research-basic.form.md)
375
- \- Standard movie research with IMDB, Rotten Tomatoes, Metacritic.
382
+ - [`movie-deep-research.form.md`](https://github.com/jlevy/markform/blob/main/packages/markform/examples/movie-research/movie-deep-research.form.md)
383
+ \- Comprehensive movie analysis with streaming, box office, technical specs.
376
384
 
377
- - [`movie-research-deep.form.md`](https://github.com/jlevy/markform/blob/main/packages/markform/examples/movie-research/movie-research-deep.form.md)
378
- \- Comprehensive movie analysis with streaming, box office, analysis.
385
+ ### Why a New Format?
386
+
387
+ The closest alternatives I’ve seen are:
388
+
389
+ - Plain Markdown docs can be used as templates and filled in by agents.
390
+ These are more expressive, but it is hard to edit them programmatically or use LLMs to
391
+ update them reliably.
392
+
393
+ - JSON + JSON Schema which are good for struture but terrible for additional
394
+ unstructured context like instructions Markdown.
395
+
396
+ - Agent to-do lists are part of many chat or coding interfaces and are programmatically
397
+ edited by agents. But these are limited to simple checklists, not forms with other
398
+ fields.
399
+
400
+ - Numerous tools like Typeform, Google Forms, PDF forms, and Docusign offer
401
+ human-friendly UI. But these do not have a human-friendly text format for use by
402
+ agents as well as humans.
403
+
404
+ | Approach | Usable GUI editor | Human-readable source format | Agent-editable | APIs and validation rules |
405
+ | --- | :---: | :---: | :---: | :---: |
406
+ | Plain Markdown | ✅<br>IDEs/editors | ✅ | ⚠️<br>fragile | ❌ |
407
+ | JSON + JSON Schema | ✅<br>IDEs/editors | ⚠️<br>no free text | ✅ | ✅ |
408
+ | SaaS tools (Typeform, Docusign, PDF forms) | ✅ | ⚠️<br>rarely | ⚠️<br>sometimes | ⚠️<br>sometimes |
409
+ | HTML/web Forms | ✅<br>IDEs/editors | ⚠️<br>HTML+code | ⚠️<br>coding agent | ✅ |
410
+ | Excel/Google Sheets | ✅<br>app | ❌<br>.csv/.xlsx | ⚠️<br>with tools | ✅<br>with some coding |
411
+ | **Markform** | ✅<br>IDEs/editors | ✅ | ✅<br>with this package | ✅<br>with this package |
379
412
 
380
- - [`earnings-analysis.form.md`](https://github.com/jlevy/markform/blob/main/packages/markform/examples/earnings-analysis/earnings-analysis.form.md)
381
- \- Financial analysis form.
382
413
  ## Architecture
383
414
 
384
415
  This repo has a specification and an implementation.
@@ -453,10 +484,8 @@ flowchart LR
453
484
  ```bash
454
485
  # Copy all bundled examples to ./forms/
455
486
  markform examples
456
-
457
487
  # List available examples
458
488
  markform examples --list
459
-
460
489
  # Copy a specific example
461
490
  markform examples --name movie-research-demo
462
491
  ```
@@ -466,7 +495,6 @@ markform examples --name movie-research-demo
466
495
  ```bash
467
496
  # Browse forms in ./forms/ and run one interactively
468
497
  markform run
469
-
470
498
  # Run a specific form directly
471
499
  markform run forms/movie-research-demo.form.md
472
500
  ```
@@ -483,7 +511,6 @@ markform status my-form.form.md
483
511
  ```bash
484
512
  # View form structure, progress, and validation issues
485
513
  markform inspect my-form.form.md
486
-
487
514
  # Output as JSON
488
515
  markform inspect my-form.form.md --format=json
489
516
  ```
@@ -493,10 +520,8 @@ markform inspect my-form.form.md --format=json
493
520
  ```bash
494
521
  # Interactive mode: fill user-role fields via prompts
495
522
  markform fill my-form.form.md --interactive
496
-
497
523
  # Agent mode: use an LLM to fill agent-role fields
498
524
  markform fill my-form.form.md --model=anthropic/claude-sonnet-4-5
499
-
500
525
  # Mock agent for testing (uses pre-filled form as source)
501
526
  markform fill my-form.form.md --mock --mock-source filled.form.md
502
527
  ```
@@ -506,13 +531,10 @@ markform fill my-form.form.md --mock --mock-source filled.form.md
506
531
  ```bash
507
532
  # Export as readable markdown (strips Markdoc tags)
508
533
  markform export my-form.form.md --format=markdown
509
-
510
534
  # Export values as JSON
511
535
  markform export my-form.form.md --format=json
512
-
513
536
  # Export values as YAML
514
537
  markform export my-form.form.md --format=yaml
515
-
516
538
  # Dump just the current values
517
539
  markform dump my-form.form.md
518
540
  ```
@@ -522,10 +544,8 @@ markform dump my-form.form.md
522
544
  ```bash
523
545
  # Export form structure as JSON Schema (for validation, code generation, etc.)
524
546
  markform schema my-form.form.md
525
-
526
547
  # Pure JSON Schema without Markform extensions
527
548
  markform schema my-form.form.md --pure
528
-
529
549
  # Specify JSON Schema draft version
530
550
  markform schema my-form.form.md --draft draft-07
531
551
  ```
@@ -549,19 +569,14 @@ markform serve my-form.form.md
549
569
  ```bash
550
570
  # Quick reference for writing forms (agent-friendly)
551
571
  markform docs
552
-
553
572
  # Full specification
554
573
  markform spec
555
-
556
574
  # TypeScript and AI SDK API documentation
557
575
  markform apis
558
-
559
576
  # This README
560
577
  markform readme
561
-
562
578
  # See supported AI providers and example models
563
579
  markform models
564
-
565
580
  # See all commands
566
581
  markform --help
567
582
  ```
@@ -594,17 +609,17 @@ applications.
594
609
  ### Basic Parsing
595
610
 
596
611
  ```typescript
597
- import { parseForm, serializeForm } from "markform";
612
+ import { parseForm, serialize } from "markform";
598
613
 
599
614
  // Parse a .form.md file
600
615
  const form = parseForm(markdownContent);
601
616
 
602
617
  // Access schema and values
603
618
  console.log(form.schema.title);
604
- console.log(form.values);
619
+ console.log(form.responsesByFieldId);
605
620
 
606
621
  // Serialize back to markdown
607
- const output = serializeForm(form);
622
+ const output = serialize(form);
608
623
  ```
609
624
 
610
625
  ### AI SDK Integration
@@ -763,32 +778,6 @@ This enables powerful AI workflows that assemble information in a defined struct
763
778
  - An **agent execution harness** for step-by-step form filling, enabling deep research
764
779
  agents that assemble validated output in a structured format.
765
780
 
766
- ### Does anything like this already exist?
767
-
768
- Not that I have seen.
769
- The closest alternatives are:
770
-
771
- - Plain Markdown docs can be used as templates and filled in by agents.
772
- These are more expressive, but it is hard to edit them programmatically or use LLMs to
773
- update them reliably.
774
-
775
- - Agent to-do lists are part of many chat or coding interfaces and are programmatically
776
- edited by agents. But these are limited to simple checklists, not forms with other
777
- fields.
778
-
779
- - Numerous tools like Typeform, Google Forms, PDF forms, and Docusign offer
780
- human-friendly UI. But these do not have a human-friendly text format for use by
781
- agents as well as humans.
782
-
783
- | Approach | Usable GUI editor | Human-readable source format | Agent-editable | APIs and validation rules |
784
- | --- | :---: | :---: | :---: | :---: |
785
- | Plain Markdown | ✅ IDEs/editors | ✅ | ⚠️ fragile | ❌ |
786
- | JSON + JSON Schema | ✅ IDEs/editors | ⚠️ no free text | ✅ | ✅ |
787
- | SaaS tools (Typeform, Docusign, PDF forms) | ✅ | ⚠️ rarely | ⚠️ sometimes | ⚠️ sometimes |
788
- | HTML/web Forms | ✅ IDEs/editors | ⚠️ HTML+code | ⚠️ coding agent | ✅ |
789
- | Excel/Google Sheets | ✅ app | ❌ .csv/.xlsx | ⚠️ with tools | ✅ with some coding |
790
- | **Markform** | ✅ IDEs/editors | ✅ | ✅ with this package | ✅ with this package |
791
-
792
781
  ### What are example use cases?
793
782
 
794
783
  - Deep research tools where agents need to follow codified processes to assemble
package/dist/ai-sdk.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { At as PatchSchema, Dt as ParsedForm, Ot as Patch, Q as Id, V as FieldResponse, dr as ValidatorRegistry, q as FormSchema, r as ApplyResult, rt as InspectResult } from "./coreTypes-BSPJ9H27.mjs";
2
+ import { At as PatchSchema, Dt as ParsedForm, Ot as Patch, Q as Id, V as FieldResponse, dr as ValidatorRegistry, q as FormSchema, r as ApplyResult, rt as InspectResult } from "./coreTypes-JCPm418M.mjs";
3
3
  import { z } from "zod";
4
4
 
5
5
  //#region src/integrations/toolTypes.d.ts
package/dist/ai-sdk.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
- import { L as PatchSchema } from "./coreTypes-DJtu8OOp.mjs";
3
- import { d as serialize, i as inspect, t as applyPatches } from "./apply-BUU2QcJ2.mjs";
2
+ import { L as PatchSchema } from "./coreTypes-B1oI7qvV.mjs";
3
+ import { d as serialize, i as inspect, t as applyPatches } from "./apply-B2kt6C2z.mjs";
4
4
  import { z } from "zod";
5
5
 
6
6
  //#region src/integrations/vercelAiSdkTools.ts
@@ -789,6 +789,10 @@ function serializeField(field, responses) {
789
789
  case "date": return serializeDateField(field, response);
790
790
  case "year": return serializeYearField(field, response);
791
791
  case "table": return serializeTableField(field, response);
792
+ default: {
793
+ const _exhaustive = field;
794
+ throw new Error(`Unhandled field kind: ${_exhaustive.kind}`);
795
+ }
792
796
  }
793
797
  }
794
798
  /**
@@ -1250,6 +1254,10 @@ function isFieldSubmitted(field, value) {
1250
1254
  }
1251
1255
  case "year": return value.value !== null;
1252
1256
  case "table": return value.rows.length > 0;
1257
+ default: {
1258
+ const _exhaustive = field;
1259
+ throw new Error(`Unhandled field kind: ${_exhaustive.kind}`);
1260
+ }
1253
1261
  }
1254
1262
  }
1255
1263
  /**
@@ -1982,6 +1990,10 @@ function validateField(field, responses) {
1982
1990
  case "date": return validateDateField(field, value);
1983
1991
  case "year": return validateYearField(field, value);
1984
1992
  case "table": return validateTableField(field, value);
1993
+ default: {
1994
+ const _exhaustive = field;
1995
+ throw new Error(`Unhandled field kind: ${_exhaustive.kind}`);
1996
+ }
1985
1997
  }
1986
1998
  }
1987
1999
  /**
@@ -2130,7 +2142,7 @@ function inspect(form, options = {}) {
2130
2142
  const structureSummary = computeStructureSummary(form.schema);
2131
2143
  const progressSummary = computeProgressSummary(form.schema, form.responsesByFieldId, form.notes, validationInspectIssues);
2132
2144
  const formState = computeFormState(progressSummary);
2133
- const issues = filterIssuesByRole(sortAndAssignPriorities(addOptionalEmptyIssues(validationInspectIssues, form, progressSummary.fields), form), form, options.targetRoles);
2145
+ const issues = filterIssuesByRole(sortAndAssignPriorities(addOptionalUnansweredIssues(validationInspectIssues, form, progressSummary.fields), form), form, options.targetRoles);
2134
2146
  return {
2135
2147
  structureSummary,
2136
2148
  progressSummary,
@@ -2153,19 +2165,29 @@ function convertValidationIssues(validationIssues, form) {
2153
2165
  }));
2154
2166
  }
2155
2167
  /**
2156
- * Add issues for empty optional fields that don't already have issues.
2157
- * Fields that have been explicitly skipped do not get optional_empty issues.
2168
+ * Add issues for unanswered optional fields.
2169
+ *
2170
+ * An `optional_unanswered` issue is only added when:
2171
+ * - The field is optional (not required)
2172
+ * - The field has no value (empty=true)
2173
+ * - The field is unanswered (answerState='unanswered')
2174
+ *
2175
+ * Fields that have been addressed (answered, skipped, or aborted) do NOT get
2176
+ * optional_unanswered issues, even if their value is empty. For example:
2177
+ * - A multi_select answered with no selections (selected=[])
2178
+ * - A string_list answered with no items (items=[])
2179
+ * These are intentional "none" answers, not missing data.
2158
2180
  */
2159
- function addOptionalEmptyIssues(existingIssues, form, fieldProgress) {
2181
+ function addOptionalUnansweredIssues(existingIssues, form, fieldProgress) {
2160
2182
  const issues = [...existingIssues];
2161
2183
  const fieldsWithIssues = new Set(existingIssues.map((i) => i.ref));
2162
2184
  for (const [fieldId, progress] of Object.entries(fieldProgress)) {
2163
- if (progress.answerState === "skipped" || progress.answerState === "aborted") continue;
2185
+ if (progress.answerState !== "unanswered") continue;
2164
2186
  if (progress.empty && !fieldsWithIssues.has(fieldId) && !isRequiredField(fieldId, form)) issues.push({
2165
2187
  ref: fieldId,
2166
2188
  scope: "field",
2167
- reason: "optional_empty",
2168
- message: "Optional field has no value",
2189
+ reason: "optional_unanswered",
2190
+ message: "Optional field not yet addressed",
2169
2191
  severity: "recommended",
2170
2192
  priority: 0
2171
2193
  });
@@ -2211,7 +2233,7 @@ function isRequiredField(fieldId, form) {
2211
2233
  * - validation_error: 2
2212
2234
  * - checkbox_incomplete: 3 (when required), 2 (when recommended)
2213
2235
  * - min_items_not_met: 2
2214
- * - optional_empty: 1
2236
+ * - optional_unanswered: 1
2215
2237
  *
2216
2238
  * Total score = field_priority_weight + issue_type_score
2217
2239
  *
@@ -2232,7 +2254,7 @@ const ISSUE_TYPE_SCORES = {
2232
2254
  validation_error: 2,
2233
2255
  checkbox_incomplete: 2,
2234
2256
  min_items_not_met: 2,
2235
- optional_empty: 1
2257
+ optional_unanswered: 1
2236
2258
  };
2237
2259
  /**
2238
2260
  * Calculate the priority tier (1-5) from a score.
@@ -2257,7 +2279,7 @@ function getIssueTypeScore(reason, severity) {
2257
2279
  *
2258
2280
  * Priority is computed as a tier (1-5, P1-P5) based on:
2259
2281
  * - Field priority weight (high=3, medium=2, low=1)
2260
- * - Issue type score (required_missing=3, validation_error=2, optional_empty=1)
2282
+ * - Issue type score (required_missing=3, validation_error=2, optional_unanswered=1)
2261
2283
  *
2262
2284
  * Within each tier, issues are sorted by severity (required first) then by ref.
2263
2285
  */
@@ -2860,7 +2882,7 @@ function convertToInspectIssues(form) {
2860
2882
  for (const vi of result.issues) issues.push({
2861
2883
  ref: vi.ref ?? "",
2862
2884
  scope: "field",
2863
- reason: vi.severity === "error" ? "validation_error" : "optional_empty",
2885
+ reason: vi.severity === "error" ? "validation_error" : "optional_unanswered",
2864
2886
  message: vi.message,
2865
2887
  severity: vi.severity === "error" ? "required" : "recommended",
2866
2888
  priority: priority++
@@ -2911,4 +2933,4 @@ function applyPatches(form, patches) {
2911
2933
  }
2912
2934
 
2913
2935
  //#endregion
2914
- export { deriveSchemaPath as A, DEFAULT_ROLES as C, USER_ROLE as D, REPORT_EXTENSION as E, formatSuggestedLlms as F, getWebSearchConfig as I, hasWebSearchSupport as L, parseRolesFlag as M, SUGGESTED_LLMS as N, deriveExportPath as O, WEB_SEARCH_CONFIG as P, parseModelIdForDisplay as R, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as S, MAX_FORMS_IN_MENU as T, DEFAULT_MAX_PATCHES_PER_TURN as _, validate as a, DEFAULT_PRIORITY as b, computeProgressSummary as c, serialize as d, serializeRawMarkdown as f, DEFAULT_MAX_ISSUES_PER_TURN as g, DEFAULT_FORMS_DIR as h, inspect as i, detectFileType as j, deriveReportPath as k, computeStructureSummary as l, AGENT_ROLE as m, getAllFields as n, computeAllSummaries as o, serializeReportMarkdown as p, getFieldsForRoles as r, computeFormState as s, applyPatches as t, isFormComplete as u, DEFAULT_MAX_TURNS as v, DEFAULT_ROLE_INSTRUCTIONS as w, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as x, DEFAULT_PORT as y };
2936
+ export { deriveReportPath as A, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as C, REPORT_EXTENSION as D, MAX_FORMS_IN_MENU as E, WEB_SEARCH_CONFIG as F, formatSuggestedLlms as I, getWebSearchConfig as L, detectFileType as M, parseRolesFlag as N, USER_ROLE as O, SUGGESTED_LLMS as P, hasWebSearchSupport as R, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as S, DEFAULT_ROLE_INSTRUCTIONS as T, DEFAULT_MAX_ISSUES_PER_TURN as _, validate as a, DEFAULT_PORT as b, computeProgressSummary as c, serialize as d, serializeRawMarkdown as f, DEFAULT_FORMS_DIR as g, ALL_EXTENSIONS as h, inspect as i, deriveSchemaPath as j, deriveExportPath as k, computeStructureSummary as l, AGENT_ROLE as m, getAllFields as n, computeAllSummaries as o, serializeReportMarkdown as p, getFieldsForRoles as r, computeFormState as s, applyPatches as t, isFormComplete as u, DEFAULT_MAX_PATCHES_PER_TURN as v, DEFAULT_ROLES as w, DEFAULT_PRIORITY as x, DEFAULT_MAX_TURNS as y, parseModelIdForDisplay as z };
package/dist/bin.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
 
4
- import { t as runCli } from "./cli-BZh25bvy.mjs";
4
+ import { t as runCli } from "./cli-Dt_PlYi_.mjs";
5
5
  import { resolve } from "node:path";
6
6
  import { existsSync } from "node:fs";
7
7
  import { config } from "dotenv";