markform 0.1.17 → 0.1.19

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.
Files changed (38) hide show
  1. package/README.md +27 -2
  2. package/dist/ai-sdk.d.mts +1 -2
  3. package/dist/ai-sdk.mjs +2 -2
  4. package/dist/ai-sdk.mjs.map +1 -1
  5. package/dist/{apply-DgDJBscb.mjs → apply-Dalpt-D6.mjs} +422 -31
  6. package/dist/apply-Dalpt-D6.mjs.map +1 -0
  7. package/dist/bin.mjs +20 -2
  8. package/dist/bin.mjs.map +1 -1
  9. package/dist/{cli-DAl8LQzI.mjs → cli-tpvFNqFY.mjs} +325 -73
  10. package/dist/cli-tpvFNqFY.mjs.map +1 -0
  11. package/dist/cli.mjs +1 -1
  12. package/dist/{coreTypes-DiCddBKu.mjs → coreTypes-CPKXf2dc.mjs} +9 -4
  13. package/dist/coreTypes-CPKXf2dc.mjs.map +1 -0
  14. package/dist/{coreTypes-CnEea7Kh.d.mts → coreTypes-CkxML8g2.d.mts} +128 -10
  15. package/dist/index.d.mts +642 -22
  16. package/dist/index.mjs +5 -5
  17. package/dist/{session-XDrocA3j.mjs → session-CK0x28RO.mjs} +2 -2
  18. package/dist/session-CK0x28RO.mjs.map +1 -0
  19. package/dist/{session-B7aR6hno.mjs → session-ZHBi3LVQ.mjs} +1 -1
  20. package/dist/{shared-fUKfJ1UA.mjs → shared-BTR35aMz.mjs} +1 -1
  21. package/dist/{shared-CCq4haEV.mjs → shared-DwdyWmvE.mjs} +1 -3
  22. package/dist/shared-DwdyWmvE.mjs.map +1 -0
  23. package/dist/{src-CHVJLGKt.mjs → src-BTyz-wS6.mjs} +2009 -584
  24. package/dist/src-BTyz-wS6.mjs.map +1 -0
  25. package/docs/markform-apis.md +112 -0
  26. package/docs/markform-reference.md +27 -0
  27. package/docs/markform-spec.md +115 -0
  28. package/examples/movie-research/movie-deep-research-mock-filled.form.md +1 -1
  29. package/examples/movie-research/movie-deep-research.form.md +1 -1
  30. package/examples/parallel/parallel-research.form.md +57 -0
  31. package/examples/startup-deep-research/startup-deep-research.form.md +1 -1
  32. package/package.json +16 -14
  33. package/dist/apply-DgDJBscb.mjs.map +0 -1
  34. package/dist/cli-DAl8LQzI.mjs.map +0 -1
  35. package/dist/coreTypes-DiCddBKu.mjs.map +0 -1
  36. package/dist/session-XDrocA3j.mjs.map +0 -1
  37. package/dist/shared-CCq4haEV.mjs.map +0 -1
  38. package/dist/src-CHVJLGKt.mjs.map +0 -1
@@ -1,16 +1,16 @@
1
1
 
2
- import { L as PatchSchema } from "./coreTypes-DiCddBKu.mjs";
3
- import { A as MAX_FORMS_IN_MENU, B as formatSuggestedLlms, C as DEFAULT_MAX_TURNS, D as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN, E as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, F as deriveSchemaPath, H as hasWebSearchSupport, I as detectFileType, L as parseRolesFlag, M as USER_ROLE, N as deriveExportPath, P as deriveReportPath, R as SUGGESTED_LLMS, U as parseModelIdForDisplay, _ as validateSyntaxConsistency, b as DEFAULT_MAX_ISSUES_PER_TURN, d as serializeForm, f as serializeRawMarkdown, i as inspect, j as REPORT_EXTENSION, m as friendlyUrlAbbrev, n as getAllFields, p as serializeReport, t as applyPatches, v as AGENT_ROLE, w as DEFAULT_PORT, x as DEFAULT_MAX_PATCHES_PER_TURN, y as DEFAULT_FORMS_DIR, z as WEB_SEARCH_CONFIG } from "./apply-DgDJBscb.mjs";
4
- import { D as parseForm, E as formToJsonSchema, a as resolveHarnessConfig, c as getProviderNames, d as createLiveAgent, h as createHarness, i as runResearch, l as resolveModel, n as isResearchForm, o as fillForm, p as createMockAgent, s as getProviderInfo, t as VERSION, u as buildMockWireFormat } from "./src-CHVJLGKt.mjs";
5
- import { n as serializeSession } from "./session-XDrocA3j.mjs";
6
- import { _ as writeFile, a as formatPath, c as logError, d as logTiming, f as logVerbose, g as stripHtmlComments, h as shouldUseColors, i as formatOutput, l as logInfo, m as readFile$1, n as createSpinner, o as getCommandContext, p as logWarn, r as ensureFormsDir, s as logDryRun, t as OUTPUT_FORMATS, u as logSuccess } from "./shared-CCq4haEV.mjs";
2
+ import { L as PatchSchema } from "./coreTypes-CPKXf2dc.mjs";
3
+ import { $ as WEB_SEARCH_CONFIG, B as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, F as DEFAULT_MAX_PATCHES_PER_TURN, G as REPORT_EXTENSION, J as deriveReportPath, K as USER_ROLE, L as DEFAULT_MAX_TURNS, M as DEFAULT_FORMS_DIR, N as DEFAULT_MAX_ISSUES_PER_TURN, Q as SUGGESTED_LLMS, R as DEFAULT_PORT, V as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN, W as MAX_FORMS_IN_MENU, X as detectFileType, Y as deriveSchemaPath, Z as parseRolesFlag, c as computeProgressSummary, d as serializeForm, et as formatSuggestedLlms, f as serializeRawMarkdown, h as friendlyUrlAbbrev, i as inspect, j as AGENT_ROLE, l as computeStructureSummary, m as formatBareUrlsAsHtmlLinks, n as getAllFields, nt as hasWebSearchSupport, p as serializeReport, q as deriveExportPath, rt as parseModelIdForDisplay, t as applyPatches, v as validateSyntaxConsistency } from "./apply-Dalpt-D6.mjs";
4
+ import { C as resolveModel, D as computeExecutionPlan, E as FillRecordCollector, H as formToJsonSchema, S as getProviderNames, T as createLiveAgent, U as parseForm, _ as fillForm, g as resolveHarnessConfig, h as formatFillRecordSummary, i as runResearch, j as createHarness, k as createMockAgent, m as stripUnstableFillRecordFields, n as isResearchForm, t as VERSION, w as buildMockWireFormat, x as getProviderInfo } from "./src-BTyz-wS6.mjs";
5
+ import { n as serializeSession } from "./session-CK0x28RO.mjs";
6
+ import { _ as writeFile, a as formatPath, c as logError, d as logTiming, f as logVerbose, g as stripHtmlComments, h as shouldUseColors, i as formatOutput, l as logInfo, m as readFile$1, n as createSpinner, o as getCommandContext, p as logWarn, r as ensureFormsDir, s as logDryRun, t as OUTPUT_FORMATS, u as logSuccess } from "./shared-DwdyWmvE.mjs";
7
7
  import Markdoc from "@markdoc/markdoc";
8
8
  import YAML from "yaml";
9
9
  import { Command } from "commander";
10
10
  import pc from "picocolors";
11
11
  import { exec, execSync, spawn } from "node:child_process";
12
12
  import { basename, dirname, join, resolve } from "node:path";
13
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
13
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import { readFile } from "node:fs/promises";
16
16
  import * as p from "@clack/prompts";
@@ -253,12 +253,12 @@ function registerApplyCommand(program) {
253
253
  const patches = parsedJson;
254
254
  const validatedPatches = [];
255
255
  for (let i = 0; i < patches.length; i++) {
256
- const result$1 = PatchSchema.safeParse(patches[i]);
257
- if (!result$1.success) {
258
- logError(`Invalid patch at index ${i}: ${result$1.error.issues[0]?.message ?? "Unknown error"}`);
256
+ const result = PatchSchema.safeParse(patches[i]);
257
+ if (!result.success) {
258
+ logError(`Invalid patch at index ${i}: ${result.error.issues[0]?.message ?? "Unknown error"}`);
259
259
  process.exit(1);
260
260
  }
261
- validatedPatches.push(result$1.data);
261
+ validatedPatches.push(result.data);
262
262
  }
263
263
  if (ctx.dryRun) {
264
264
  logDryRun(`Would apply ${validatedPatches.length} patches to ${file}`, { patches: validatedPatches });
@@ -418,7 +418,7 @@ async function displayWithPager(content, title) {
418
418
  return;
419
419
  }
420
420
  const header = `${pc.bgCyan(pc.black(` ${title} `))}`;
421
- return new Promise((resolve$1) => {
421
+ return new Promise((resolve) => {
422
422
  const pager = spawn("less", [
423
423
  "-R",
424
424
  "-S",
@@ -435,10 +435,10 @@ async function displayWithPager(content, title) {
435
435
  console.log("");
436
436
  console.log(content);
437
437
  console.log("");
438
- resolve$1();
438
+ resolve();
439
439
  });
440
440
  pager.on("close", () => {
441
- resolve$1();
441
+ resolve();
442
442
  });
443
443
  pager.stdin.write(header + "\n\n");
444
444
  pager.stdin.write(content);
@@ -1353,13 +1353,13 @@ function parseVersionedPath(filePath) {
1353
1353
  function generateVersionedPath(filePath) {
1354
1354
  const parsed = parseVersionedPath(filePath);
1355
1355
  if (!parsed) {
1356
- let candidate$1 = `${filePath}-filled1`;
1357
- let version$1 = 1;
1358
- while (existsSync(candidate$1)) {
1359
- version$1++;
1360
- candidate$1 = `${filePath}-filled${version$1}`;
1356
+ let candidate = `${filePath}-filled1`;
1357
+ let version = 1;
1358
+ while (existsSync(candidate)) {
1359
+ version++;
1360
+ candidate = `${filePath}-filled${version}`;
1361
1361
  }
1362
- return candidate$1;
1362
+ return candidate;
1363
1363
  }
1364
1364
  let version = parsed.version !== null ? parsed.version + 1 : 1;
1365
1365
  let candidate = `${parsed.base}-filled${version}${parsed.extension}`;
@@ -1469,10 +1469,10 @@ async function promptForString(ctx) {
1469
1469
  placeholder: placeholderText,
1470
1470
  initialValue: currentVal ?? "",
1471
1471
  validate: (value) => {
1472
- if (field.required && !value.trim()) return "This field is required";
1473
- if (field.minLength && value.length < field.minLength) return `Minimum ${field.minLength} characters required`;
1474
- if (field.maxLength && value.length > field.maxLength) return `Maximum ${field.maxLength} characters allowed`;
1475
- if (field.pattern && !new RegExp(field.pattern).test(value)) return `Must match pattern: ${field.pattern}`;
1472
+ if (field.required && !value?.trim()) return "This field is required";
1473
+ if (field.minLength && (value?.length ?? 0) < field.minLength) return `Minimum ${field.minLength} characters required`;
1474
+ if (field.maxLength && (value?.length ?? 0) > field.maxLength) return `Maximum ${field.maxLength} characters allowed`;
1475
+ if (field.pattern && value && !new RegExp(field.pattern).test(value)) return `Must match pattern: ${field.pattern}`;
1476
1476
  }
1477
1477
  });
1478
1478
  if (p.isCancel(result)) return null;
@@ -1480,7 +1480,7 @@ async function promptForString(ctx) {
1480
1480
  return {
1481
1481
  op: "set_string",
1482
1482
  fieldId: field.id,
1483
- value: result || null
1483
+ value: result ?? null
1484
1484
  };
1485
1485
  }
1486
1486
  /**
@@ -1495,8 +1495,8 @@ async function promptForNumber(ctx) {
1495
1495
  placeholder: placeholderText,
1496
1496
  initialValue: currentVal !== null ? String(currentVal) : "",
1497
1497
  validate: (value) => {
1498
- if (field.required && !value.trim()) return "This field is required";
1499
- if (!value.trim()) return;
1498
+ if (field.required && !value?.trim()) return "This field is required";
1499
+ if (!value?.trim()) return;
1500
1500
  const num = Number(value);
1501
1501
  if (isNaN(num)) return "Please enter a valid number";
1502
1502
  if (field.integer && !Number.isInteger(num)) return "Please enter a whole number";
@@ -1527,14 +1527,14 @@ async function promptForStringList(ctx) {
1527
1527
  placeholder: hint,
1528
1528
  initialValue: currentItems.join("\n"),
1529
1529
  validate: (value) => {
1530
- const items$1 = value.split("\n").map((s) => s.trim()).filter(Boolean);
1531
- if (field.required && items$1.length === 0) return "At least one item is required";
1532
- if (field.minItems && items$1.length < field.minItems) return `Minimum ${field.minItems} items required`;
1533
- if (field.maxItems && items$1.length > field.maxItems) return `Maximum ${field.maxItems} items allowed`;
1530
+ const items = (value ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
1531
+ if (field.required && items.length === 0) return "At least one item is required";
1532
+ if (field.minItems && items.length < field.minItems) return `Minimum ${field.minItems} items required`;
1533
+ if (field.maxItems && items.length > field.maxItems) return `Maximum ${field.maxItems} items allowed`;
1534
1534
  }
1535
1535
  });
1536
1536
  if (p.isCancel(result)) return null;
1537
- const items = result.split("\n").map((s) => s.trim()).filter(Boolean);
1537
+ const items = (result ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
1538
1538
  if (items.length === 0 && !field.required) return null;
1539
1539
  return {
1540
1540
  op: "set_string_list",
@@ -1612,16 +1612,16 @@ async function promptForCheckboxes(ctx) {
1612
1612
  });
1613
1613
  if (p.isCancel(result)) return null;
1614
1614
  const selected = result;
1615
- const values$1 = {};
1616
- for (const opt of field.options) values$1[opt.id] = selected.includes(opt.id) ? "done" : "todo";
1615
+ const values = {};
1616
+ for (const opt of field.options) values[opt.id] = selected.includes(opt.id) ? "done" : "todo";
1617
1617
  return {
1618
1618
  op: "set_checkboxes",
1619
1619
  fieldId: field.id,
1620
- value: values$1
1620
+ value: values
1621
1621
  };
1622
1622
  }
1623
1623
  if (field.checkboxMode === "explicit") {
1624
- const values$1 = {};
1624
+ const values = {};
1625
1625
  for (const opt of field.options) {
1626
1626
  const current = currentValues[opt.id];
1627
1627
  const result = await p.select({
@@ -1643,12 +1643,12 @@ async function promptForCheckboxes(ctx) {
1643
1643
  initialValue: current === "yes" || current === "no" ? current : "unfilled"
1644
1644
  });
1645
1645
  if (p.isCancel(result)) return null;
1646
- values$1[opt.id] = result;
1646
+ values[opt.id] = result;
1647
1647
  }
1648
1648
  return {
1649
1649
  op: "set_checkboxes",
1650
1650
  fieldId: field.id,
1651
- value: values$1
1651
+ value: values
1652
1652
  };
1653
1653
  }
1654
1654
  const values = {};
@@ -1701,8 +1701,8 @@ async function promptForUrl(ctx) {
1701
1701
  placeholder: placeholderText,
1702
1702
  initialValue: currentVal ?? "",
1703
1703
  validate: (value) => {
1704
- if (field.required && !value.trim()) return "This field is required";
1705
- if (!value.trim()) return;
1704
+ if (field.required && !value?.trim()) return "This field is required";
1705
+ if (!value?.trim()) return;
1706
1706
  try {
1707
1707
  new URL(value);
1708
1708
  } catch {
@@ -1715,7 +1715,7 @@ async function promptForUrl(ctx) {
1715
1715
  return {
1716
1716
  op: "set_url",
1717
1717
  fieldId: field.id,
1718
- value: result || null
1718
+ value: result ?? null
1719
1719
  };
1720
1720
  }
1721
1721
  /**
@@ -1743,8 +1743,8 @@ async function promptForDate(ctx) {
1743
1743
  placeholder: currentVal ?? `YYYY-MM-DD${formatHint}`,
1744
1744
  initialValue: currentVal ?? "",
1745
1745
  validate: (value) => {
1746
- if (field.required && !value.trim()) return "This field is required";
1747
- if (!value.trim()) return;
1746
+ if (field.required && !value?.trim()) return "This field is required";
1747
+ if (!value?.trim()) return;
1748
1748
  if (!isValidDate(value)) return "Please enter a valid date in YYYY-MM-DD format";
1749
1749
  if (field.min && value < field.min) return `Date must be on or after ${field.min}`;
1750
1750
  if (field.max && value > field.max) return `Date must be on or before ${field.max}`;
@@ -1755,7 +1755,7 @@ async function promptForDate(ctx) {
1755
1755
  return {
1756
1756
  op: "set_date",
1757
1757
  fieldId: field.id,
1758
- value: result || null
1758
+ value: result ?? null
1759
1759
  };
1760
1760
  }
1761
1761
  /** Default year range for validation */
@@ -1774,8 +1774,8 @@ async function promptForYear(ctx) {
1774
1774
  placeholder: currentVal !== null ? String(currentVal) : `Year (${minYear}-${maxYear})`,
1775
1775
  initialValue: currentVal !== null ? String(currentVal) : "",
1776
1776
  validate: (value) => {
1777
- if (field.required && !value.trim()) return "This field is required";
1778
- if (!value.trim()) return;
1777
+ if (field.required && !value?.trim()) return "This field is required";
1778
+ if (!value?.trim()) return;
1779
1779
  const num = Number(value);
1780
1780
  if (isNaN(num) || !Number.isInteger(num)) return "Please enter a valid year (e.g., 2025)";
1781
1781
  if (num < minYear) return `Year must be ${minYear} or later`;
@@ -1805,11 +1805,11 @@ async function promptForUrlList(ctx) {
1805
1805
  placeholder: hint,
1806
1806
  initialValue: currentItems.join("\n"),
1807
1807
  validate: (value) => {
1808
- const items$1 = value.split("\n").map((s) => s.trim()).filter(Boolean);
1809
- if (field.required && items$1.length === 0) return "At least one URL is required";
1810
- if (field.minItems && items$1.length < field.minItems) return `Minimum ${field.minItems} URLs required`;
1811
- if (field.maxItems && items$1.length > field.maxItems) return `Maximum ${field.maxItems} URLs allowed`;
1812
- for (const item of items$1) try {
1808
+ const items = (value ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
1809
+ if (field.required && items.length === 0) return "At least one URL is required";
1810
+ if (field.minItems && items.length < field.minItems) return `Minimum ${field.minItems} URLs required`;
1811
+ if (field.maxItems && items.length > field.maxItems) return `Maximum ${field.maxItems} URLs allowed`;
1812
+ for (const item of items) try {
1813
1813
  new URL(item);
1814
1814
  } catch {
1815
1815
  return `Invalid URL: ${item}`;
@@ -1817,7 +1817,7 @@ async function promptForUrlList(ctx) {
1817
1817
  }
1818
1818
  });
1819
1819
  if (p.isCancel(result)) return null;
1820
- const items = result.split("\n").map((s) => s.trim()).filter(Boolean);
1820
+ const items = (result ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
1821
1821
  if (items.length === 0 && !field.required) return null;
1822
1822
  return {
1823
1823
  op: "set_url_list",
@@ -2188,7 +2188,7 @@ async function promptForModel(webSearchRequired) {
2188
2188
  message: "Model ID (provider/model-id):",
2189
2189
  placeholder: "anthropic/claude-sonnet-4-20250514",
2190
2190
  validate: (value) => {
2191
- if (!value.includes("/")) return "Format: provider/model-id (e.g., anthropic/claude-sonnet-4-20250514)";
2191
+ if (!value?.includes("/")) return "Format: provider/model-id (e.g., anthropic/claude-sonnet-4-20250514)";
2192
2192
  }
2193
2193
  });
2194
2194
  if (p.isCancel(customModel)) return null;
@@ -2278,6 +2278,7 @@ async function runAgentFillWorkflow(form, modelId, formsDir, filePath, isResearc
2278
2278
  fillMode: overwrite ? "overwrite" : "continue",
2279
2279
  enableWebSearch: isResearch,
2280
2280
  captureWireFormat: false,
2281
+ recordFill: false,
2281
2282
  callbacks
2282
2283
  });
2283
2284
  if (result.status.ok) p.log.success(pc.green(`Form completed in ${result.turns} turn(s)`));
@@ -2768,7 +2769,7 @@ function formatConsoleSession(transcript, useColors) {
2768
2769
  * Register the fill command.
2769
2770
  */
2770
2771
  function registerFillCommand(program) {
2771
- program.command("fill <file>").description("Run an agent to autonomously fill a form").option("--mock", "Use mock agent (requires --mock-source)").option("--model <id>", "Model ID for live agent (format: provider/model-id, e.g. openai/gpt-5-mini)").option("--mock-source <file>", "Path to completed form for mock agent").option("--record <file>", "Record session transcript to file").option("--max-turns <n>", `Maximum turns (default: ${DEFAULT_MAX_TURNS})`, String(DEFAULT_MAX_TURNS)).option("--max-patches <n>", `Maximum patches per turn (default: ${DEFAULT_MAX_PATCHES_PER_TURN})`, String(DEFAULT_MAX_PATCHES_PER_TURN)).option("--max-issues <n>", `Maximum issues shown per turn (default: ${DEFAULT_MAX_ISSUES_PER_TURN})`, String(DEFAULT_MAX_ISSUES_PER_TURN)).option("--max-fields <n>", "Maximum unique fields per turn (applied before --max-issues)").option("--max-groups <n>", "Maximum unique groups per turn (applied before --max-issues)").option("--roles <roles>", "Target roles to fill (comma-separated, or '*' for all; default: 'agent', or 'user' in --interactive mode)").option("--mode <mode>", "Fill mode: continue (skip filled fields) or overwrite (re-fill; default: continue)").option("-o, --output <file>", "Write final form to file").option("--prompt <file>", "Path to custom system prompt file (appends to default)").option("--instructions <text>", "Inline system prompt (appends to default; takes precedence over --prompt)").option("-i, --interactive", "Interactive mode: prompt user for field values (defaults to user role)").option("--normalize", "Regenerate form without preserving external content").action(async (file, options, cmd) => {
2772
+ program.command("fill <file>").description("Run an agent to autonomously fill a form").option("--mock", "Use mock agent (requires --mock-source)").option("--model <id>", "Model ID for live agent (format: provider/model-id, e.g. openai/gpt-5-mini)").option("--mock-source <file>", "Path to completed form for mock agent").option("--record <file>", "Record session transcript to file").option("--max-turns <n>", `Maximum turns (default: ${DEFAULT_MAX_TURNS})`, String(DEFAULT_MAX_TURNS)).option("--max-patches <n>", `Maximum patches per turn (default: ${DEFAULT_MAX_PATCHES_PER_TURN})`, String(DEFAULT_MAX_PATCHES_PER_TURN)).option("--max-issues <n>", `Maximum issues shown per turn (default: ${DEFAULT_MAX_ISSUES_PER_TURN})`, String(DEFAULT_MAX_ISSUES_PER_TURN)).option("--max-fields <n>", "Maximum unique fields per turn (applied before --max-issues)").option("--max-groups <n>", "Maximum unique groups per turn (applied before --max-issues)").option("--roles <roles>", "Target roles to fill (comma-separated, or '*' for all; default: 'agent', or 'user' in --interactive mode)").option("--mode <mode>", "Fill mode: continue (skip filled fields) or overwrite (re-fill; default: continue)").option("-o, --output <file>", "Write final form to file").option("--prompt <file>", "Path to custom system prompt file (appends to default)").option("--instructions <text>", "Inline system prompt (appends to default; takes precedence over --prompt)").option("-i, --interactive", "Interactive mode: prompt user for field values (defaults to user role)").option("--normalize", "Regenerate form without preserving external content").option("--record-fill", "Write fill record to sidecar .fill.json file").option("--record-fill-stable", "Write fill record without timestamps/durations (for golden tests)").action(async (file, options, cmd) => {
2772
2773
  const ctx = getCommandContext(cmd);
2773
2774
  const filePath = resolve(file);
2774
2775
  try {
@@ -2817,19 +2818,19 @@ function registerFillCommand(program) {
2817
2818
  process.exit(1);
2818
2819
  }
2819
2820
  if (patches.length > 0) applyPatches(form, patches);
2820
- const durationMs$1 = Date.now() - startTime;
2821
- let outputPath$1;
2822
- if (options.output) outputPath$1 = resolve(options.output);
2821
+ const durationMs = Date.now() - startTime;
2822
+ let outputPath;
2823
+ if (options.output) outputPath = resolve(options.output);
2823
2824
  else {
2824
2825
  const formsDir = getFormsDir(ctx.formsDir);
2825
2826
  await ensureFormsDir(formsDir);
2826
- outputPath$1 = generateVersionedPathInFormsDir(filePath, formsDir);
2827
+ outputPath = generateVersionedPathInFormsDir(filePath, formsDir);
2827
2828
  }
2828
2829
  if (ctx.dryRun) {
2829
- logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath$1}`);
2830
+ logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath}`);
2830
2831
  showInteractiveOutro(patches.length, false);
2831
2832
  } else {
2832
- const { reportPath, yamlPath, formPath, schemaPath } = await exportMultiFormat(form, outputPath$1);
2833
+ const { reportPath, yamlPath, formPath, schemaPath } = await exportMultiFormat(form, outputPath);
2833
2834
  showInteractiveOutro(patches.length, false);
2834
2835
  console.log("");
2835
2836
  p.log.success("Outputs:");
@@ -2838,11 +2839,11 @@ function registerFillCommand(program) {
2838
2839
  console.log(` ${formatPath(formPath)} ${pc.dim("(filled markform source)")}`);
2839
2840
  console.log(` ${formatPath(schemaPath)} ${pc.dim("(JSON Schema)")}`);
2840
2841
  }
2841
- logTiming(ctx, "Fill time", durationMs$1);
2842
+ logTiming(ctx, "Fill time", durationMs);
2842
2843
  if (patches.length > 0) {
2843
2844
  console.log("");
2844
2845
  console.log("Next step: fill remaining fields with agent");
2845
- console.log(` markform fill ${formatPath(outputPath$1)} --model=<provider/model>`);
2846
+ console.log(` markform fill ${formatPath(outputPath)} --model=<provider/model>`);
2846
2847
  }
2847
2848
  process.exit(0);
2848
2849
  }
@@ -2867,6 +2868,7 @@ function registerFillCommand(program) {
2867
2868
  fillMode
2868
2869
  });
2869
2870
  const harness = createHarness(form, harnessConfig);
2871
+ let collector;
2870
2872
  let agent;
2871
2873
  let mockPath;
2872
2874
  let agentProvider;
@@ -2877,12 +2879,36 @@ function registerFillCommand(program) {
2877
2879
  mockPath = resolve(options.mockSource);
2878
2880
  logVerbose(ctx, `Reading mock source: ${mockPath}`);
2879
2881
  agent = createMockAgent(parseForm(await readFile$1(mockPath)));
2882
+ const structureSummary = computeStructureSummary(form.schema);
2883
+ collector = new FillRecordCollector({
2884
+ form: {
2885
+ id: form.schema.id,
2886
+ title: form.schema.title,
2887
+ description: form.schema.description,
2888
+ structure: structureSummary
2889
+ },
2890
+ provider: "mock",
2891
+ model: "mock",
2892
+ parallelEnabled: false
2893
+ });
2880
2894
  } else {
2881
2895
  const modelIdString = options.model;
2882
2896
  logVerbose(ctx, `Resolving model: ${modelIdString}`);
2883
2897
  const { model, provider, modelId } = await resolveModel(modelIdString);
2884
2898
  agentProvider = provider;
2885
2899
  agentModelName = modelId;
2900
+ const structureSummary = computeStructureSummary(form.schema);
2901
+ collector = new FillRecordCollector({
2902
+ form: {
2903
+ id: form.schema.id,
2904
+ title: form.schema.title,
2905
+ description: form.schema.description,
2906
+ structure: structureSummary
2907
+ },
2908
+ provider,
2909
+ model: modelIdString,
2910
+ parallelEnabled: false
2911
+ });
2886
2912
  let systemPrompt;
2887
2913
  if (options.instructions) {
2888
2914
  systemPrompt = options.instructions;
@@ -2892,13 +2918,30 @@ function registerFillCommand(program) {
2892
2918
  logVerbose(ctx, `Reading system prompt from: ${promptPath}`);
2893
2919
  systemPrompt = await readFile$1(promptPath);
2894
2920
  }
2895
- const callbacks = createCliToolCallbacks({
2921
+ const cliCallbacks = createCliToolCallbacks({
2896
2922
  message: (msg) => currentSpinner?.message(msg),
2897
2923
  update: (context) => currentSpinner?.update(context),
2898
2924
  stop: (msg) => currentSpinner?.stop(msg),
2899
2925
  error: (msg) => currentSpinner?.error(msg),
2900
2926
  getElapsedMs: () => currentSpinner?.getElapsedMs() ?? 0
2901
2927
  }, ctx);
2928
+ const liveCollector = collector;
2929
+ const callbacks = {
2930
+ onToolStart: (call) => {
2931
+ cliCallbacks.onToolStart?.(call);
2932
+ liveCollector.onToolStart(call);
2933
+ },
2934
+ onToolEnd: (call) => {
2935
+ cliCallbacks.onToolEnd?.(call);
2936
+ liveCollector.onToolEnd(call);
2937
+ },
2938
+ onLlmCallStart: (call) => {
2939
+ liveCollector.onLlmCallStart(call);
2940
+ },
2941
+ onLlmCallEnd: (call) => {
2942
+ liveCollector.onLlmCallEnd(call);
2943
+ }
2944
+ };
2902
2945
  targetRole = targetRoles[0] === "*" ? AGENT_ROLE : targetRoles[0] ?? AGENT_ROLE;
2903
2946
  const liveAgent = createLiveAgent({
2904
2947
  model,
@@ -3012,6 +3055,24 @@ function registerFillCommand(program) {
3012
3055
  await writeFile(outputPath, formMarkdown);
3013
3056
  logSuccess(ctx, `Form written to: ${outputPath}`);
3014
3057
  }
3058
+ const finalInspect = inspect(harness.getForm(), { targetRoles });
3059
+ const progressSummary = computeProgressSummary(form.schema, harness.getForm().responsesByFieldId, harness.getForm().notes, finalInspect.issues);
3060
+ collector.setStatus(stepResult.isComplete ? "completed" : "partial", stepResult.isComplete ? void 0 : "max_turns");
3061
+ const fillRecord = collector.getRecord(progressSummary.counts);
3062
+ if (!ctx.quiet) {
3063
+ console.log("");
3064
+ const summary = formatFillRecordSummary(fillRecord, { verbose: ctx.verbose });
3065
+ console.error(summary);
3066
+ }
3067
+ if (options.recordFill || options.recordFillStable) {
3068
+ const sidecarPath = outputPath.replace(/\.(form\.)?md$/, ".fill.json");
3069
+ const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(fillRecord) : fillRecord;
3070
+ if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write fill record to: ${sidecarPath}`);
3071
+ else {
3072
+ writeFileSync(sidecarPath, JSON.stringify(recordToWrite, null, 2));
3073
+ logSuccess(ctx, `Fill record written to: ${sidecarPath}`);
3074
+ }
3075
+ }
3015
3076
  const transcript = buildSessionTranscript(filePath, options.mock ? "mock" : "live", mockPath, options.model, harnessConfig, harness.getTurns(), stepResult.isComplete, outputPath);
3016
3077
  if (options.record) {
3017
3078
  const recordPath = resolve(options.record);
@@ -3098,7 +3159,6 @@ function formatPriority$1(priority, useColors) {
3098
3159
  case 2: return pc.yellow(label);
3099
3160
  case 3: return pc.cyan(label);
3100
3161
  case 4: return pc.blue(label);
3101
- case 5:
3102
3162
  default: return pc.dim(label);
3103
3163
  }
3104
3164
  }
@@ -3535,6 +3595,191 @@ function registerModelsCommand(program) {
3535
3595
  });
3536
3596
  }
3537
3597
 
3598
+ //#endregion
3599
+ //#region src/cli/commands/plan.ts
3600
+ /**
3601
+ * Get the response status string for a field.
3602
+ */
3603
+ function getFieldStatus(form, fieldId) {
3604
+ const response = form.responsesByFieldId[fieldId];
3605
+ if (!response) return "unanswered";
3606
+ return response.state;
3607
+ }
3608
+ /**
3609
+ * Check if a field still needs work (not yet answered).
3610
+ */
3611
+ function fieldNeedsWork(form, fieldId) {
3612
+ const response = form.responsesByFieldId[fieldId];
3613
+ if (!response) return true;
3614
+ return response.state !== "answered";
3615
+ }
3616
+ /**
3617
+ * Get all field IDs for an execution plan item.
3618
+ */
3619
+ function getFieldIdsForItem(form, item) {
3620
+ if (item.itemType === "field") return [item.itemId];
3621
+ const group = form.schema.groups.find((g) => g.id === item.itemId);
3622
+ if (!group) return [];
3623
+ return group.children.map((f) => f.id);
3624
+ }
3625
+ /**
3626
+ * Get field metadata from the form schema.
3627
+ */
3628
+ function getFieldMeta(form, fieldId) {
3629
+ for (const group of form.schema.groups) {
3630
+ const field = group.children.find((f) => f.id === fieldId);
3631
+ if (field) return field;
3632
+ }
3633
+ }
3634
+ /**
3635
+ * Build a PlanItemJson, including only fields that need work.
3636
+ * Returns undefined if no fields need work.
3637
+ */
3638
+ function buildPlanItem(form, item) {
3639
+ const remainingFieldIds = getFieldIdsForItem(form, item).filter((id) => fieldNeedsWork(form, id));
3640
+ if (remainingFieldIds.length === 0) return;
3641
+ const planItem = {
3642
+ itemId: item.itemId,
3643
+ itemType: item.itemType
3644
+ };
3645
+ if (item.itemType === "group") planItem.fields = remainingFieldIds.map((id) => {
3646
+ const meta = getFieldMeta(form, id);
3647
+ return {
3648
+ fieldId: id,
3649
+ label: meta?.label,
3650
+ status: getFieldStatus(form, id),
3651
+ required: meta?.required ?? false
3652
+ };
3653
+ });
3654
+ return planItem;
3655
+ }
3656
+ function formatConsolePlan(report, useColors) {
3657
+ const lines = [];
3658
+ const bold = useColors ? pc.bold : (s) => s;
3659
+ const dim = useColors ? pc.dim : (s) => s;
3660
+ const cyan = useColors ? pc.cyan : (s) => s;
3661
+ const yellow = useColors ? pc.yellow : (s) => s;
3662
+ const titlePart = report.title ? ` (${report.title})` : "";
3663
+ lines.push(bold(cyan(`Plan: ${report.formId}${titlePart}`)));
3664
+ lines.push("");
3665
+ if (report.orderLevels.length === 0) {
3666
+ lines.push(dim("No remaining work — all fields are complete."));
3667
+ return lines.join("\n");
3668
+ }
3669
+ for (const level of report.orderLevels) {
3670
+ const itemCount = level.looseSerial.length + level.parallelBatches.reduce((sum, b) => sum + b.items.length, 0);
3671
+ lines.push(bold(`Order level ${level.order} (${itemCount} items):`));
3672
+ if (level.looseSerial.length > 0) {
3673
+ lines.push(` Loose serial (primary agent):`);
3674
+ for (const item of level.looseSerial) formatItem(lines, item, dim, yellow, " ");
3675
+ }
3676
+ for (const batch of level.parallelBatches) {
3677
+ lines.push(` Parallel batch "${batch.batchId}" (${batch.items.length} items, ${batch.items.length} agents):`);
3678
+ for (const item of batch.items) formatItem(lines, item, dim, yellow, " ");
3679
+ }
3680
+ lines.push("");
3681
+ }
3682
+ const s = report.summary;
3683
+ lines.push(dim(`Summary: ${s.orderLevelCount} order level${s.orderLevelCount !== 1 ? "s" : ""}, ${s.parallelBatchCount} parallel batch${s.parallelBatchCount !== 1 ? "es" : ""}, ${s.remainingFields} remaining field${s.remainingFields !== 1 ? "s" : ""}`));
3684
+ return lines.join("\n");
3685
+ }
3686
+ function formatItem(lines, item, dim, yellow, indent) {
3687
+ if (item.itemType === "group" && item.fields) {
3688
+ lines.push(`${indent}- ${item.itemId} [group]`);
3689
+ for (const f of item.fields) {
3690
+ const label = f.label ? ` (${f.label})` : "";
3691
+ const req = f.required ? yellow("required") + ", " : "";
3692
+ lines.push(`${indent} ${f.fieldId}${label} — ${req}${dim(f.status)}`);
3693
+ }
3694
+ } else {
3695
+ const meta = item;
3696
+ lines.push(`${indent}- ${meta.itemId}`);
3697
+ }
3698
+ }
3699
+ /**
3700
+ * Register the plan command.
3701
+ */
3702
+ function registerPlanCommand(program) {
3703
+ program.command("plan <file>").description("Show the idealized execution plan for a form (parallel batches, order levels)").action(async (file, _options, cmd) => {
3704
+ const ctx = getCommandContext(cmd);
3705
+ try {
3706
+ logVerbose(ctx, `Reading file: ${file}`);
3707
+ const content = await readFile$1(file);
3708
+ logVerbose(ctx, "Parsing and validating form...");
3709
+ const form = parseForm(content);
3710
+ const parseErrors = inspect(form).issues.filter((i) => i.reason === "validation_error");
3711
+ if (parseErrors.length > 0) {
3712
+ logError(`Form has validation errors. Fix them before planning:\n` + parseErrors.map((e) => ` - ${e.message}`).join("\n"));
3713
+ process.exit(1);
3714
+ }
3715
+ logVerbose(ctx, "Computing execution plan...");
3716
+ const executionPlan = computeExecutionPlan(form);
3717
+ const orderLevels = [];
3718
+ let totalItems = 0;
3719
+ let totalRemainingFields = 0;
3720
+ let totalBatches = 0;
3721
+ for (const order of executionPlan.orderLevels) {
3722
+ const looseItems = [];
3723
+ for (const item of executionPlan.looseSerial) {
3724
+ if (item.order !== order) continue;
3725
+ const planItem = buildPlanItem(form, item);
3726
+ if (planItem) {
3727
+ looseItems.push(planItem);
3728
+ totalItems++;
3729
+ totalRemainingFields += countRemainingFields(form, item);
3730
+ }
3731
+ }
3732
+ const batches = [];
3733
+ for (const batch of executionPlan.parallelBatches) {
3734
+ const batchItems = [];
3735
+ for (const item of batch.items) {
3736
+ if (item.order !== order) continue;
3737
+ const planItem = buildPlanItem(form, item);
3738
+ if (planItem) {
3739
+ batchItems.push(planItem);
3740
+ totalItems++;
3741
+ totalRemainingFields += countRemainingFields(form, item);
3742
+ }
3743
+ }
3744
+ if (batchItems.length > 0) {
3745
+ batches.push({
3746
+ batchId: batch.batchId,
3747
+ items: batchItems
3748
+ });
3749
+ totalBatches++;
3750
+ }
3751
+ }
3752
+ if (looseItems.length > 0 || batches.length > 0) orderLevels.push({
3753
+ order,
3754
+ looseSerial: looseItems,
3755
+ parallelBatches: batches
3756
+ });
3757
+ }
3758
+ const output = formatOutput(ctx, {
3759
+ formId: form.schema.id,
3760
+ title: form.schema.title,
3761
+ orderLevels,
3762
+ summary: {
3763
+ orderLevelCount: orderLevels.length,
3764
+ parallelBatchCount: totalBatches,
3765
+ totalItems,
3766
+ remainingFields: totalRemainingFields
3767
+ }
3768
+ }, (data, useColors) => formatConsolePlan(data, useColors));
3769
+ console.log(output);
3770
+ } catch (error) {
3771
+ logError(error instanceof Error ? error.message : String(error));
3772
+ process.exit(1);
3773
+ }
3774
+ });
3775
+ }
3776
+ /**
3777
+ * Count remaining fields for an execution plan item.
3778
+ */
3779
+ function countRemainingFields(form, item) {
3780
+ return getFieldIdsForItem(form, item).filter((id) => fieldNeedsWork(form, id)).length;
3781
+ }
3782
+
3538
3783
  //#endregion
3539
3784
  //#region src/cli/commands/serve.ts
3540
3785
  /**
@@ -5130,7 +5375,7 @@ function renderViewFieldValue(field, value, isSkipped) {
5130
5375
  case "string": {
5131
5376
  const v = value.kind === "string" ? value.value : null;
5132
5377
  if (v === null || v === "") return "<div class=\"view-field-empty\">(not filled)</div>";
5133
- return `<div class="view-field-value">${escapeHtml(v)}</div>`;
5378
+ return `<div class="view-field-value">${formatBareUrlsAsHtmlLinks(v, escapeHtml)}</div>`;
5134
5379
  }
5135
5380
  case "number": {
5136
5381
  const v = value.kind === "number" ? value.value : null;
@@ -5140,7 +5385,7 @@ function renderViewFieldValue(field, value, isSkipped) {
5140
5385
  case "string_list": {
5141
5386
  const items = value.kind === "string_list" ? value.items : [];
5142
5387
  if (items.length === 0) return "<div class=\"view-field-empty\">(not filled)</div>";
5143
- return `<div class="view-field-value"><ul>${items.map((i) => `<li>${escapeHtml(i)}</li>`).join("")}</ul></div>`;
5388
+ return `<div class="view-field-value"><ul>${items.map((i) => `<li>${formatBareUrlsAsHtmlLinks(i, escapeHtml)}</li>`).join("")}</ul></div>`;
5144
5389
  }
5145
5390
  case "single_select": {
5146
5391
  const selected = value.kind === "single_select" ? value.selected : null;
@@ -5204,7 +5449,7 @@ function renderViewFieldValue(field, value, isSkipped) {
5204
5449
  if (col.type === "url" && cellValue) {
5205
5450
  const domain = friendlyUrlAbbrev(cellValue);
5206
5451
  cellHtml = `<a href="${escapeHtml(cellValue)}" target="_blank" class="url-link" data-url="${escapeHtml(cellValue)}">${escapeHtml(domain)}</a>`;
5207
- } else cellHtml = escapeHtml(cellValue);
5452
+ } else cellHtml = formatBareUrlsAsHtmlLinks(cellValue, escapeHtml);
5208
5453
  }
5209
5454
  tableHtml += `<td>${cellHtml}</td>`;
5210
5455
  }
@@ -5415,6 +5660,7 @@ function renderMarkdownContent(content) {
5415
5660
  }
5416
5661
  /**
5417
5662
  * Format inline markdown (bold, italic, code, links, checkboxes).
5663
+ * Also auto-links bare URLs for consistency.
5418
5664
  */
5419
5665
  function formatInlineMarkdown(text) {
5420
5666
  let result = escapeHtml(text);
@@ -5427,6 +5673,12 @@ function formatInlineMarkdown(text) {
5427
5673
  const cleanUrl = url.replace(/&amp;/g, "&");
5428
5674
  return `<a href="${cleanUrl}" target="_blank" class="url-link" data-url="${cleanUrl}">${linkText}</a>`;
5429
5675
  });
5676
+ result = result.replace(/(?<!href="|data-url="|">|\]\()(?:https?:\/\/|www\.)[^\s<>"]+(?<![.,;:!?'")])/g, (url) => {
5677
+ const cleanUrl = url.replace(/&amp;/g, "&");
5678
+ const fullUrl = cleanUrl.startsWith("www.") ? `https://${cleanUrl}` : cleanUrl;
5679
+ const display = friendlyUrlAbbrev(fullUrl);
5680
+ return `<a href="${escapeHtml(fullUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(fullUrl)}">${escapeHtml(display)}</a>`;
5681
+ });
5430
5682
  return result;
5431
5683
  }
5432
5684
  /**
@@ -5594,7 +5846,7 @@ function registerResearchCommand(program) {
5594
5846
  const modelId = options.model;
5595
5847
  const { provider, model: modelName } = parseModelIdForDisplay(modelId);
5596
5848
  if (!hasWebSearchSupport(provider)) {
5597
- const webSearchProviders = Object.entries(WEB_SEARCH_CONFIG).filter(([, config]) => config.supported).map(([p$1]) => p$1);
5849
+ const webSearchProviders = Object.entries(WEB_SEARCH_CONFIG).filter(([, config]) => config.supported).map(([p]) => p);
5598
5850
  logError(`Model "${modelId}" does not support web search.`);
5599
5851
  console.log("");
5600
5852
  console.log(pc.yellow("Research forms require web search capabilities."));
@@ -5642,6 +5894,7 @@ function registerResearchCommand(program) {
5642
5894
  model: modelId,
5643
5895
  enableWebSearch: true,
5644
5896
  captureWireFormat: false,
5897
+ recordFill: false,
5645
5898
  maxTurnsTotal: maxTurns,
5646
5899
  maxPatchesPerTurn,
5647
5900
  maxIssuesPerTurn,
@@ -5664,10 +5917,10 @@ function registerResearchCommand(program) {
5664
5917
  console.log(` ${formPath} ${pc.dim("(filled markform source)")}`);
5665
5918
  console.log(` ${schemaPath} ${pc.dim("(JSON Schema)")}`);
5666
5919
  if (options.transcript && result.transcript) {
5667
- const { serializeSession: serializeSession$1 } = await import("./session-B7aR6hno.mjs");
5920
+ const { serializeSession } = await import("./session-ZHBi3LVQ.mjs");
5668
5921
  const transcriptPath = outputPath.replace(/\.form\.md$/, ".session.yaml");
5669
- const { writeFile: writeFile$1 } = await import("./shared-fUKfJ1UA.mjs");
5670
- await writeFile$1(transcriptPath, serializeSession$1(result.transcript));
5922
+ const { writeFile } = await import("./shared-BTR35aMz.mjs");
5923
+ await writeFile(transcriptPath, serializeSession(result.transcript));
5671
5924
  logInfo(ctx, `Transcript: ${transcriptPath}`);
5672
5925
  }
5673
5926
  logTiming(ctx, "Research fill", Date.now() - startTime);
@@ -5742,7 +5995,6 @@ function computeFieldStats(form, fields) {
5742
5995
  case "aborted":
5743
5996
  aborted++;
5744
5997
  break;
5745
- case "unanswered":
5746
5998
  default:
5747
5999
  unanswered++;
5748
6000
  break;
@@ -5909,7 +6161,6 @@ function formatPriority(priority, useColors) {
5909
6161
  case 2: return pc.yellow(label);
5910
6162
  case 3: return pc.cyan(label);
5911
6163
  case 4: return pc.blue(label);
5912
- case 5:
5913
6164
  default: return pc.dim(label);
5914
6165
  }
5915
6166
  }
@@ -6053,6 +6304,7 @@ function createProgram() {
6053
6304
  registerFillCommand(program);
6054
6305
  registerInspectCommand(program);
6055
6306
  registerModelsCommand(program);
6307
+ registerPlanCommand(program);
6056
6308
  registerRenderCommand(program);
6057
6309
  registerReportCommand(program);
6058
6310
  registerResearchCommand(program);
@@ -6072,4 +6324,4 @@ async function runCli() {
6072
6324
 
6073
6325
  //#endregion
6074
6326
  export { runCli as t };
6075
- //# sourceMappingURL=cli-DAl8LQzI.mjs.map
6327
+ //# sourceMappingURL=cli-tpvFNqFY.mjs.map