markform 0.1.22 → 0.1.24

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 (43) hide show
  1. package/README.md +53 -16
  2. package/dist/ai-sdk.d.mts +1 -1
  3. package/dist/ai-sdk.mjs +48 -6
  4. package/dist/ai-sdk.mjs.map +1 -1
  5. package/dist/bin.mjs +1 -1
  6. package/dist/{cli-C8F9yDsv.mjs → cli-B1DhFYBS.mjs} +642 -96
  7. package/dist/cli-B1DhFYBS.mjs.map +1 -0
  8. package/dist/cli.d.mts +1 -1
  9. package/dist/cli.mjs +1 -1
  10. package/dist/{coreTypes-CTLr-NGd.mjs → coreTypes-CctFK6uE.mjs} +38 -2
  11. package/dist/coreTypes-CctFK6uE.mjs.map +1 -0
  12. package/dist/{coreTypes-BlsJkU1w.d.mts → coreTypes-GxzWNXap.d.mts} +137 -3
  13. package/dist/{fillRecord-DTl5lnK0.d.mts → fillRecord-DeqI2pQ5.d.mts} +25 -1
  14. package/dist/{fillRecordRenderer-CruJrLkj.mjs → fillRecordRenderer-VBQ2vwPV.mjs} +2 -5
  15. package/dist/fillRecordRenderer-VBQ2vwPV.mjs.map +1 -0
  16. package/dist/index.d.mts +52 -29
  17. package/dist/index.mjs +5 -5
  18. package/dist/{apply-C7mO7VkZ.mjs → prompts-BCnYaH4_.mjs} +969 -8
  19. package/dist/prompts-BCnYaH4_.mjs.map +1 -0
  20. package/dist/render.d.mts +2 -2
  21. package/dist/render.mjs +1 -1
  22. package/dist/{session-BCcltrLA.mjs → session-BLjN3BkJ.mjs} +2 -2
  23. package/dist/{session-BCcltrLA.mjs.map → session-BLjN3BkJ.mjs.map} +1 -1
  24. package/dist/{session-VeSkVrck.mjs → session-D7C7IlEv.mjs} +1 -1
  25. package/dist/{shared-CsdT2T7k.mjs → shared-CuSRYcIB.mjs} +3 -3
  26. package/dist/shared-CuSRYcIB.mjs.map +1 -0
  27. package/dist/{shared-fb0nkzQi.mjs → shared-DtorFV21.mjs} +1 -1
  28. package/dist/{src-CbRnGzMK.mjs → src-C5OWf1dL.mjs} +114 -826
  29. package/dist/src-C5OWf1dL.mjs.map +1 -0
  30. package/docs/markform-apis.md +6 -0
  31. package/docs/markform-reference.md +26 -1
  32. package/docs/markform-spec.md +11 -3
  33. package/docs/skill/SKILL.md +119 -0
  34. package/examples/rejection-test/rejection-test.session.yaml +52 -0
  35. package/examples/simple/simple-with-skips.session.yaml +78 -0
  36. package/examples/simple/simple.session.yaml +78 -0
  37. package/package.json +2 -2
  38. package/dist/apply-C7mO7VkZ.mjs.map +0 -1
  39. package/dist/cli-C8F9yDsv.mjs.map +0 -1
  40. package/dist/coreTypes-CTLr-NGd.mjs.map +0 -1
  41. package/dist/fillRecordRenderer-CruJrLkj.mjs.map +0 -1
  42. package/dist/shared-CsdT2T7k.mjs.map +0 -1
  43. package/dist/src-CbRnGzMK.mjs.map +0 -1
@@ -1,19 +1,19 @@
1
1
 
2
- import { R as PatchSchema } from "./coreTypes-CTLr-NGd.mjs";
3
- import { $ as WEB_SEARCH_CONFIG, A as DEFAULT_FORMS_DIR, F as DEFAULT_MAX_TURNS, G as deriveExportPath, H as MAX_FORMS_IN_MENU, I as DEFAULT_PORT, J as deriveSchemaPath, K as deriveFillRecordPath, N as DEFAULT_MAX_PATCHES_PER_TURN, Q as SUGGESTED_LLMS, R as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, U as REPORT_EXTENSION, W as USER_ROLE, X as parseRolesFlag, Y as detectFileType, c as computeProgressSummary, d as serializeForm, et as formatSuggestedLlms, f as serializeRawMarkdown, g as validateSyntaxConsistency, i as inspect, j as DEFAULT_MAX_ISSUES_PER_TURN, k as AGENT_ROLE, l as computeStructureSummary, n as getAllFields, nt as hasWebSearchSupport, p as serializeReport, q as deriveReportPath, rt as parseModelIdForDisplay, t as applyPatches, z as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN } from "./apply-C7mO7VkZ.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-CbRnGzMK.mjs";
5
- import { n as serializeSession } from "./session-BCcltrLA.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-CsdT2T7k.mjs";
7
- import { a as renderJsonContent, c as renderViewContent, i as highlightYamlValue, l as renderYamlContent, o as renderMarkdownContent, r as renderFillRecordContent, s as renderSourceContent, u as escapeHtml } from "./fillRecordRenderer-CruJrLkj.mjs";
2
+ import { R as PatchSchema } from "./coreTypes-CctFK6uE.mjs";
3
+ import { $ as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN, E as serializeReport, H as AGENT_ROLE, K as DEFAULT_MAX_PATCHES_PER_TURN, Q as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, S as computeStructureSummary, T as serializeRawMarkdown, U as DEFAULT_FORMS_DIR, W as DEFAULT_MAX_ISSUES_PER_TURN, X as DEFAULT_PORT, Y as DEFAULT_MAX_TURNS, _ as inspect, _t as parseModelIdForDisplay, at as deriveExportPath, c as filterIssuesByOrder, ct as deriveSchemaPath, d as coerceInputContext, f as coerceToFieldPatch, ft as SUGGESTED_LLMS, gt as hasWebSearchSupport, h as getAllFields, it as USER_ROLE, k as validateSyntaxConsistency, l as filterIssuesByScope, lt as detectFileType, m as applyPatches, mt as formatSuggestedLlms, nt as MAX_FORMS_IN_MENU, ot as deriveFillRecordPath, p as findFieldById, pt as WEB_SEARCH_CONFIG, rt as REPORT_EXTENSION, st as deriveReportPath, u as getFieldIdFromRef, ut as parseRolesFlag, w as serializeForm, x as computeProgressSummary } from "./prompts-BCnYaH4_.mjs";
4
+ import { C as getProviderInfo, D as createLiveAgent, E as buildMockWireFormat, H as parseForm, N as createHarness, O as FillRecordCollector, T as resolveModel, V as formToJsonSchema, _ as resolveHarnessConfig, g as formatFillRecordSummary, h as stripUnstableFillRecordFields, i as runResearch, j as createMockAgent, k as computeExecutionPlan, m as isEmptyFillRecord, n as isResearchForm, t as VERSION, v as fillForm, w as getProviderNames } from "./src-C5OWf1dL.mjs";
5
+ import { n as serializeSession } from "./session-BLjN3BkJ.mjs";
6
+ import { _ as writeFile$1, 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-CuSRYcIB.mjs";
7
+ import { a as renderJsonContent, c as renderViewContent, i as highlightYamlValue, l as renderYamlContent, o as renderMarkdownContent, r as renderFillRecordContent, s as renderSourceContent, u as escapeHtml } from "./fillRecordRenderer-VBQ2vwPV.mjs";
8
8
  import Markdoc from "@markdoc/markdoc";
9
9
  import YAML from "yaml";
10
10
  import { Command } from "commander";
11
11
  import pc from "picocolors";
12
12
  import { exec, execSync, spawn } from "node:child_process";
13
13
  import { basename, dirname, join, resolve } from "node:path";
14
- import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
15
14
  import { fileURLToPath } from "node:url";
16
- import { readFile } from "node:fs/promises";
15
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
16
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
17
17
  import * as p from "@clack/prompts";
18
18
  import { createServer } from "node:http";
19
19
 
@@ -72,6 +72,21 @@ const CLI_VERSION = VERSION === "development" ? getGitVersion() : VERSION;
72
72
  * library to remain Node.js-free.
73
73
  */
74
74
  /**
75
+ * Resolve a path relative to the package root, handling both dev and dist modes.
76
+ *
77
+ * In dist mode, commands run from `<pkg>/dist/` (1 level below root).
78
+ * In dev mode, commands run from `<pkg>/src/cli/commands/` or `<pkg>/src/cli/examples/`
79
+ * (3 levels below root).
80
+ *
81
+ * @param callerMetaUrl - The `import.meta.url` of the calling module
82
+ * @param relativePath - Path relative to the package root (e.g. 'docs/markform-reference.md')
83
+ */
84
+ function resolvePackagePath(callerMetaUrl, relativePath) {
85
+ const thisDir = dirname(fileURLToPath(callerMetaUrl));
86
+ if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), relativePath);
87
+ return join(dirname(dirname(dirname(thisDir))), relativePath);
88
+ }
89
+ /**
75
90
  * Resolve the forms directory path to an absolute path.
76
91
  * Uses the provided override or falls back to DEFAULT_FORMS_DIR.
77
92
  *
@@ -86,19 +101,10 @@ function getFormsDir(override, cwd = process.cwd()) {
86
101
  //#endregion
87
102
  //#region src/cli/commands/apis.ts
88
103
  /**
89
- * Get the path to the markform-apis.md file.
90
- * Works both during development and when installed as a package.
91
- */
92
- function getApisPath() {
93
- const thisDir = dirname(fileURLToPath(import.meta.url));
94
- if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "docs", "markform-apis.md");
95
- return join(dirname(dirname(dirname(thisDir))), "docs", "markform-apis.md");
96
- }
97
- /**
98
104
  * Load the APIs documentation content.
99
105
  */
100
106
  function loadApis() {
101
- const apisPath = getApisPath();
107
+ const apisPath = resolvePackagePath(import.meta.url, "docs/markform-apis.md");
102
108
  try {
103
109
  return readFileSync(apisPath, "utf-8");
104
110
  } catch (error) {
@@ -178,7 +184,7 @@ function registerApisCommand(program) {
178
184
  }
179
185
 
180
186
  //#endregion
181
- //#region src/cli/commands/apply.ts
187
+ //#region src/cli/commands/patch.ts
182
188
  /**
183
189
  * Format state badge for console output.
184
190
  */
@@ -192,16 +198,16 @@ function formatState$2(state, useColors) {
192
198
  return useColors ? colorFn(text) : text;
193
199
  }
194
200
  /**
195
- * Format apply report for console output.
201
+ * Format patch report for console output.
196
202
  */
197
- function formatConsoleReport$3(report, useColors) {
203
+ function formatConsoleReport$5(report, useColors) {
198
204
  const lines = [];
199
205
  const bold = useColors ? pc.bold : (s) => s;
200
206
  const dim = useColors ? pc.dim : (s) => s;
201
207
  const cyan = useColors ? pc.cyan : (s) => s;
202
208
  const green = useColors ? pc.green : (s) => s;
203
209
  const red = useColors ? pc.red : (s) => s;
204
- lines.push(bold(cyan("Apply Result")));
210
+ lines.push(bold(cyan("Patch Result")));
205
211
  lines.push("");
206
212
  const statusColor = report.apply_status === "applied" ? green : red;
207
213
  lines.push(`${bold("Status:")} ${statusColor(report.apply_status)}`);
@@ -225,16 +231,12 @@ function formatConsoleReport$3(report, useColors) {
225
231
  return lines.join("\n");
226
232
  }
227
233
  /**
228
- * Register the apply command.
234
+ * Register the patch command.
229
235
  */
230
- function registerApplyCommand(program) {
231
- program.command("apply <file>").description("Apply patches to a form").option("--patch <json>", "JSON array of patches to apply").option("-o, --output <file>", "Output file (defaults to stdout)").option("--report", "Output apply result report instead of modified form").option("--normalize", "Regenerate form without preserving external content").action(async (file, options, cmd) => {
236
+ function registerPatchCommand(program) {
237
+ program.command("patch <file> <json>").description("Apply raw typed patches to a form").option("-o, --output <file>", "Output file (defaults to stdout)").option("--report", "Output patch result report instead of modified form").option("--normalize", "Regenerate form without preserving external content").action(async (file, json, options, cmd) => {
232
238
  const ctx = getCommandContext(cmd);
233
239
  try {
234
- if (!options.patch) {
235
- logError("--patch option is required");
236
- process.exit(1);
237
- }
238
240
  logVerbose(ctx, `Reading file: ${file}`);
239
241
  const content = await readFile$1(file);
240
242
  logVerbose(ctx, "Parsing form...");
@@ -242,13 +244,13 @@ function registerApplyCommand(program) {
242
244
  logVerbose(ctx, "Parsing patches...");
243
245
  let parsedJson;
244
246
  try {
245
- parsedJson = JSON.parse(options.patch);
247
+ parsedJson = JSON.parse(json);
246
248
  } catch {
247
- logError("Invalid JSON in --patch option");
249
+ logError("Invalid JSON in patch argument");
248
250
  process.exit(1);
249
251
  }
250
252
  if (!Array.isArray(parsedJson)) {
251
- logError("--patch must be a JSON array");
253
+ logError("Patch argument must be a JSON array");
252
254
  process.exit(1);
253
255
  }
254
256
  const patches = parsedJson;
@@ -276,7 +278,7 @@ function registerApplyCommand(program) {
276
278
  structure: result.structureSummary,
277
279
  progress: result.progressSummary,
278
280
  issues: result.issues
279
- }, (data, useColors) => formatConsoleReport$3(data, useColors));
281
+ }, (data, useColors) => formatConsoleReport$5(data, useColors));
280
282
  console.error(output);
281
283
  process.exit(1);
282
284
  }
@@ -288,15 +290,15 @@ function registerApplyCommand(program) {
288
290
  structure: result.structureSummary,
289
291
  progress: result.progressSummary,
290
292
  issues: result.issues
291
- }, (data, useColors) => formatConsoleReport$3(data, useColors));
293
+ }, (data, useColors) => formatConsoleReport$5(data, useColors));
292
294
  if (options.output) {
293
- await writeFile(options.output, output);
295
+ await writeFile$1(options.output, output);
294
296
  logSuccess(ctx, `Report written to ${options.output}`);
295
297
  } else console.log(output);
296
298
  } else {
297
299
  const output = serializeForm(form, { preserveContent: !options.normalize });
298
300
  if (options.output) {
299
- await writeFile(options.output, output);
301
+ await writeFile$1(options.output, output);
300
302
  logSuccess(ctx, `Modified form written to ${options.output}`);
301
303
  } else console.log(output);
302
304
  }
@@ -670,19 +672,10 @@ function registerBrowseCommand(program) {
670
672
  //#endregion
671
673
  //#region src/cli/commands/docs.ts
672
674
  /**
673
- * Get the path to the markform-reference.md file.
674
- * Works both during development and when installed as a package.
675
- */
676
- function getDocsPath() {
677
- const thisDir = dirname(fileURLToPath(import.meta.url));
678
- if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "docs", "markform-reference.md");
679
- return join(dirname(dirname(dirname(thisDir))), "docs", "markform-reference.md");
680
- }
681
- /**
682
675
  * Load the docs content.
683
676
  */
684
677
  function loadDocs() {
685
- const docsPath = getDocsPath();
678
+ const docsPath = resolvePackagePath(import.meta.url, "docs/markform-reference.md");
686
679
  try {
687
680
  return readFileSync(docsPath, "utf-8");
688
681
  } catch (error) {
@@ -909,7 +902,7 @@ function deriveExportPaths(basePath) {
909
902
  async function exportMultiFormat(form, basePath) {
910
903
  const paths = deriveExportPaths(basePath);
911
904
  const reportContent = serializeReport(form);
912
- await writeFile(paths.reportPath, reportContent);
905
+ await writeFile$1(paths.reportPath, reportContent);
913
906
  const values = toStructuredValues(form);
914
907
  const notes = toNotesArray(form);
915
908
  const exportData = {
@@ -917,12 +910,12 @@ async function exportMultiFormat(form, basePath) {
917
910
  ...notes.length > 0 && { notes }
918
911
  };
919
912
  const yamlContent = YAML.stringify(exportData);
920
- await writeFile(paths.yamlPath, yamlContent);
913
+ await writeFile$1(paths.yamlPath, yamlContent);
921
914
  const formContent = serializeForm(form);
922
- await writeFile(paths.formPath, formContent);
915
+ await writeFile$1(paths.formPath, formContent);
923
916
  const schemaResult = formToJsonSchema(form);
924
917
  const schemaContent = JSON.stringify(schemaResult.schema, null, 2) + "\n";
925
- await writeFile(paths.schemaPath, schemaContent);
918
+ await writeFile$1(paths.schemaPath, schemaContent);
926
919
  return paths;
927
920
  }
928
921
 
@@ -1061,9 +1054,7 @@ function getExampleOrder(filename) {
1061
1054
  * Works both during development and when installed as a package.
1062
1055
  */
1063
1056
  function getExamplesDir() {
1064
- const thisDir = dirname(fileURLToPath(import.meta.url));
1065
- if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "examples");
1066
- return join(dirname(dirname(dirname(thisDir))), "examples");
1057
+ return resolvePackagePath(import.meta.url, "examples");
1067
1058
  }
1068
1059
  /**
1069
1060
  * Load the content of an example form.
@@ -1967,6 +1958,12 @@ function formatPatchValue(patch) {
1967
1958
  const rowCount = patch.value?.length ?? 0;
1968
1959
  return rowCount > 0 ? truncate(`[${rowCount} rows]`) : "(empty)";
1969
1960
  }
1961
+ case "append_table": return truncate(`+[${patch.value?.length ?? 0} rows]`);
1962
+ case "delete_table": return `(delete row ${patch.value})`;
1963
+ case "append_string_list": return truncate(`+[${patch.value.join(", ")}]`);
1964
+ case "delete_string_list": return `(delete item ${patch.value})`;
1965
+ case "append_url_list": return truncate(`+[${patch.value.join(", ")}]`);
1966
+ case "delete_url_list": return `(delete item ${patch.value})`;
1970
1967
  case "add_note": return truncate(`note: ${patch.text}`);
1971
1968
  case "remove_note": return `(remove note ${patch.noteId})`;
1972
1969
  }
@@ -1990,6 +1987,12 @@ function formatPatchType(patch) {
1990
1987
  case "set_date": return "date";
1991
1988
  case "set_year": return "year";
1992
1989
  case "set_table": return "table";
1990
+ case "append_table": return "append_table";
1991
+ case "delete_table": return "delete_table";
1992
+ case "append_string_list": return "append_string_list";
1993
+ case "delete_string_list": return "delete_string_list";
1994
+ case "append_url_list": return "append_url_list";
1995
+ case "delete_url_list": return "delete_url_list";
1993
1996
  case "add_note": return "note";
1994
1997
  case "remove_note": return "remove_note";
1995
1998
  }
@@ -2462,7 +2465,7 @@ async function copyExample(exampleId, formsDir, overwrite, _quiet) {
2462
2465
  path: outputPath
2463
2466
  };
2464
2467
  }
2465
- await writeFile(outputPath, loadExampleContent(exampleId));
2468
+ await writeFile$1(outputPath, loadExampleContent(exampleId));
2466
2469
  return {
2467
2470
  copied: true,
2468
2471
  skipped: false,
@@ -2920,7 +2923,7 @@ function registerFillCommand(program) {
2920
2923
  logTiming(ctx, "Fill time", durationMs);
2921
2924
  if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath}`);
2922
2925
  else {
2923
- await writeFile(outputPath, result.markdown);
2926
+ await writeFile$1(outputPath, result.markdown);
2924
2927
  logSuccess(ctx, `Form written to: ${outputPath}`);
2925
2928
  }
2926
2929
  if (result.record && !ctx.quiet) {
@@ -2928,7 +2931,8 @@ function registerFillCommand(program) {
2928
2931
  const summary = formatFillRecordSummary(result.record, { verbose: ctx.verbose });
2929
2932
  console.error(summary);
2930
2933
  }
2931
- if ((options.recordFill || options.recordFillStable) && result.record) {
2934
+ if ((options.recordFill || options.recordFillStable) && result.record) if (isEmptyFillRecord(result.record)) logVerbose(ctx, "Skipping fill record: no turns were executed");
2935
+ else {
2932
2936
  const sidecarPath = deriveFillRecordPath(outputPath);
2933
2937
  const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(result.record) : result.record;
2934
2938
  if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write fill record to: ${sidecarPath}`);
@@ -3142,6 +3146,7 @@ function registerFillCommand(program) {
3142
3146
  requiredIssuesRemaining: stepResult.issues.filter((i) => i.severity === "required").length,
3143
3147
  isComplete: stepResult.isComplete,
3144
3148
  rejectedPatches,
3149
+ coercionWarnings: stepResult.coercionWarnings,
3145
3150
  issues: stepResult.issues,
3146
3151
  patches
3147
3152
  });
@@ -3172,7 +3177,7 @@ function registerFillCommand(program) {
3172
3177
  const formMarkdown = serializeForm(harness.getForm(), { preserveContent: !options.normalize });
3173
3178
  if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath}`);
3174
3179
  else {
3175
- await writeFile(outputPath, formMarkdown);
3180
+ await writeFile$1(outputPath, formMarkdown);
3176
3181
  logSuccess(ctx, `Form written to: ${outputPath}`);
3177
3182
  }
3178
3183
  const finalInspect = inspect(harness.getForm(), { targetRoles });
@@ -3184,7 +3189,8 @@ function registerFillCommand(program) {
3184
3189
  const summary = formatFillRecordSummary(fillRecord, { verbose: ctx.verbose });
3185
3190
  console.error(summary);
3186
3191
  }
3187
- if (options.recordFill || options.recordFillStable) {
3192
+ if (options.recordFill || options.recordFillStable) if (isEmptyFillRecord(fillRecord)) logVerbose(ctx, "Skipping fill record: no turns were executed");
3193
+ else {
3188
3194
  const sidecarPath = deriveFillRecordPath(outputPath);
3189
3195
  const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(fillRecord) : fillRecord;
3190
3196
  if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write fill record to: ${sidecarPath}`);
@@ -3201,7 +3207,7 @@ function registerFillCommand(program) {
3201
3207
  logInfo(ctx, `[DRY RUN] Would write session to: ${recordPath}`);
3202
3208
  console.log(yaml);
3203
3209
  } else {
3204
- await writeFile(recordPath, yaml);
3210
+ await writeFile$1(recordPath, yaml);
3205
3211
  logSuccess(ctx, `Session recorded to: ${recordPath}`);
3206
3212
  }
3207
3213
  } else if (!ctx.quiet) {
@@ -3218,10 +3224,12 @@ function registerFillCommand(program) {
3218
3224
  const progressSummary = computeProgressSummary(form.schema, currentForm.responsesByFieldId, currentForm.notes, finalInspect.issues);
3219
3225
  collector.setStatus("failed", message);
3220
3226
  const fillRecord = collector.getRecord(progressSummary.counts);
3221
- const sidecarPath = deriveFillRecordPath(resolve(options.output));
3222
- const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(fillRecord) : fillRecord;
3223
- writeFileSync(sidecarPath, JSON.stringify(recordToWrite, null, 2));
3224
- logWarn(ctx, `Partial fill record written to: ${sidecarPath}`);
3227
+ if (!isEmptyFillRecord(fillRecord)) {
3228
+ const sidecarPath = deriveFillRecordPath(resolve(options.output));
3229
+ const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(fillRecord) : fillRecord;
3230
+ writeFileSync(sidecarPath, JSON.stringify(recordToWrite, null, 2));
3231
+ logWarn(ctx, `Partial fill record written to: ${sidecarPath}`);
3232
+ }
3225
3233
  } catch {}
3226
3234
  process.exit(1);
3227
3235
  }
@@ -3336,7 +3344,7 @@ function formatFieldValue(value, useColors) {
3336
3344
  /**
3337
3345
  * Format inspect report for console output.
3338
3346
  */
3339
- function formatConsoleReport$2(report, useColors) {
3347
+ function formatConsoleReport$4(report, useColors) {
3340
3348
  const lines = [];
3341
3349
  const bold = useColors ? pc.bold : (s) => s;
3342
3350
  const dim = useColors ? pc.dim : (s) => s;
@@ -3449,7 +3457,7 @@ function registerInspectCommand(program) {
3449
3457
  severity: issue.severity,
3450
3458
  blockedBy: issue.blockedBy
3451
3459
  }))
3452
- }, (data, useColors) => formatConsoleReport$2(data, useColors));
3460
+ }, (data, useColors) => formatConsoleReport$4(data, useColors));
3453
3461
  console.log(output);
3454
3462
  } catch (error) {
3455
3463
  logError(error instanceof Error ? error.message : String(error));
@@ -3461,19 +3469,10 @@ function registerInspectCommand(program) {
3461
3469
  //#endregion
3462
3470
  //#region src/cli/commands/readme.ts
3463
3471
  /**
3464
- * Get the path to the README.md file.
3465
- * Works both during development and when installed as a package.
3466
- */
3467
- function getReadmePath() {
3468
- const thisDir = dirname(fileURLToPath(import.meta.url));
3469
- if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "README.md");
3470
- return join(dirname(dirname(dirname(thisDir))), "README.md");
3471
- }
3472
- /**
3473
3472
  * Load the README content.
3474
3473
  */
3475
3474
  function loadReadme() {
3476
- const readmePath = getReadmePath();
3475
+ const readmePath = resolvePackagePath(import.meta.url, "README.md");
3477
3476
  try {
3478
3477
  return readFileSync(readmePath, "utf-8");
3479
3478
  } catch (error) {
@@ -3570,7 +3569,7 @@ function registerReportCommand(program) {
3570
3569
  if (options.output) {
3571
3570
  let outputPath = options.output;
3572
3571
  if (!outputPath.endsWith(REPORT_EXTENSION) && !outputPath.endsWith(".md")) outputPath = outputPath + REPORT_EXTENSION;
3573
- await writeFile(outputPath, reportContent);
3572
+ await writeFile$1(outputPath, reportContent);
3574
3573
  logVerbose(ctx, `Report written to: ${outputPath}`);
3575
3574
  } else console.log(reportContent);
3576
3575
  } catch (error) {
@@ -3583,19 +3582,10 @@ function registerReportCommand(program) {
3583
3582
  //#endregion
3584
3583
  //#region src/cli/commands/spec.ts
3585
3584
  /**
3586
- * Get the path to the markform-spec.md file.
3587
- * Works both during development and when installed as a package.
3588
- */
3589
- function getSpecPath() {
3590
- const thisDir = dirname(fileURLToPath(import.meta.url));
3591
- if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "docs", "markform-spec.md");
3592
- return join(dirname(dirname(dirname(thisDir))), "docs", "markform-spec.md");
3593
- }
3594
- /**
3595
3585
  * Load the spec content.
3596
3586
  */
3597
3587
  function loadSpec() {
3598
- const specPath = getSpecPath();
3588
+ const specPath = resolvePackagePath(import.meta.url, "docs/markform-spec.md");
3599
3589
  try {
3600
3590
  return readFileSync(specPath, "utf-8");
3601
3591
  } catch (error) {
@@ -3727,6 +3717,201 @@ function registerModelsCommand(program) {
3727
3717
  });
3728
3718
  }
3729
3719
 
3720
+ //#endregion
3721
+ //#region src/cli/commands/next.ts
3722
+ /**
3723
+ * Build field metadata for an issue's field.
3724
+ */
3725
+ function buildFieldMeta(field) {
3726
+ const meta = {
3727
+ kind: field.kind,
3728
+ label: field.label,
3729
+ required: field.required
3730
+ };
3731
+ if (field.kind === "single_select" || field.kind === "multi_select") meta.options = field.options.map((o) => o.id);
3732
+ else if (field.kind === "checkboxes") {
3733
+ meta.options = field.options.map((o) => o.id);
3734
+ meta.checkbox_mode = field.checkboxMode;
3735
+ } else if (field.kind === "table") {
3736
+ meta.columns = field.columns.map((c) => ({
3737
+ id: c.id,
3738
+ type: c.type,
3739
+ required: c.required
3740
+ }));
3741
+ if (field.minRows !== void 0) meta.min_rows = field.minRows;
3742
+ if (field.maxRows !== void 0) meta.max_rows = field.maxRows;
3743
+ }
3744
+ return meta;
3745
+ }
3746
+ /**
3747
+ * Generate a concrete `markform set` example for a field.
3748
+ */
3749
+ function generateSetExample(file, field) {
3750
+ const base = `markform set ${file} ${field.id}`;
3751
+ switch (field.kind) {
3752
+ case "string": return `${base} "example text"`;
3753
+ case "number": return `${base} 42`;
3754
+ case "string_list": return `${base} '["item1", "item2"]'`;
3755
+ case "single_select": return field.options.length > 0 ? `${base} ${field.options[0].id}` : `${base} option_id`;
3756
+ case "multi_select": return field.options.length > 0 ? `${base} '["${field.options.map((o) => o.id).join("\", \"")}"]'` : `${base} '["option1"]'`;
3757
+ case "checkboxes":
3758
+ if (field.checkboxMode === "simple") return `${base} '${JSON.stringify(Object.fromEntries(field.options.map((o) => [o.id, true])))}'`;
3759
+ return `${base} '${JSON.stringify(Object.fromEntries(field.options.map((o) => [o.id, "done"])))}'`;
3760
+ case "url": return `${base} "https://example.com"`;
3761
+ case "url_list": return `${base} '["https://example.com"]'`;
3762
+ case "date": return `${base} "2024-01-15"`;
3763
+ case "year": return `${base} 2024`;
3764
+ case "table":
3765
+ if (field.columns.length > 0) {
3766
+ const example = Object.fromEntries(field.columns.map((c) => [c.id, `example_${c.type}`]));
3767
+ return `${base} --append '${JSON.stringify(example)}'`;
3768
+ }
3769
+ return `${base} --append '{}'`;
3770
+ }
3771
+ }
3772
+ /**
3773
+ * Generate a skip example for optional fields, or null for required fields.
3774
+ */
3775
+ function generateSkipExample(file, fieldId, required) {
3776
+ if (required) return null;
3777
+ return `markform set ${file} ${fieldId} --skip --reason "Not applicable"`;
3778
+ }
3779
+ /**
3780
+ * Convert a FieldValue to a serializable current_value for the report.
3781
+ */
3782
+ function serializeCurrentValue(value) {
3783
+ switch (value.kind) {
3784
+ case "string":
3785
+ case "url":
3786
+ case "date": return value.value;
3787
+ case "number":
3788
+ case "year": return value.value;
3789
+ case "string_list":
3790
+ case "url_list": return value.items;
3791
+ case "single_select": return value.selected;
3792
+ case "multi_select": return value.selected;
3793
+ case "checkboxes": return value.values;
3794
+ case "table": return value.rows;
3795
+ }
3796
+ }
3797
+ /**
3798
+ * Enrich an InspectIssue into a NextIssue with field metadata and examples.
3799
+ */
3800
+ function enrichIssue(issue, form, file) {
3801
+ const fieldId = getFieldIdFromRef(issue.ref, issue.scope);
3802
+ const field = fieldId ? findFieldById(form, fieldId) : void 0;
3803
+ const enriched = {
3804
+ ref: issue.ref,
3805
+ scope: issue.scope,
3806
+ reason: issue.reason,
3807
+ message: issue.message,
3808
+ severity: issue.severity,
3809
+ priority: issue.priority,
3810
+ set_example: field ? generateSetExample(file, field) : `markform set ${file} ${issue.ref} "value"`,
3811
+ skip_example: field ? generateSkipExample(file, field.id, field.required) : null
3812
+ };
3813
+ if (field) {
3814
+ enriched.field = buildFieldMeta(field);
3815
+ const response = form.responsesByFieldId[field.id];
3816
+ if (response?.state === "answered" && response.value) enriched.current_value = serializeCurrentValue(response.value);
3817
+ }
3818
+ return enriched;
3819
+ }
3820
+ /**
3821
+ * Format next report for console output.
3822
+ */
3823
+ function formatConsoleReport$3(report, useColors) {
3824
+ const lines = [];
3825
+ const bold = useColors ? pc.bold : (s) => s;
3826
+ const dim = useColors ? pc.dim : (s) => s;
3827
+ const cyan = useColors ? pc.cyan : (s) => s;
3828
+ const green = useColors ? pc.green : (s) => s;
3829
+ const yellow = useColors ? pc.yellow : (s) => s;
3830
+ const red = useColors ? pc.red : (s) => s;
3831
+ const stateColor = report.form_state === "complete" ? green : report.form_state === "invalid" ? red : yellow;
3832
+ const progressStr = `${report.progress.filled_fields}/${report.progress.total_fields} fields filled, ${report.progress.empty_required_fields} required remaining`;
3833
+ lines.push(`${bold("State:")} ${stateColor(report.form_state)} ${dim(`(${progressStr})`)}`);
3834
+ if (report.is_complete) {
3835
+ lines.push("");
3836
+ lines.push(green(bold("Form is complete!")));
3837
+ return lines.join("\n");
3838
+ }
3839
+ lines.push("");
3840
+ lines.push(bold(`Next fields to fill (${report.issues.length} issues, budget: ${report.step_budget}):`));
3841
+ lines.push("");
3842
+ for (const issue of report.issues) {
3843
+ const prioLabel = `P${issue.priority}`;
3844
+ const prioColor = issue.priority <= 1 ? red : issue.priority <= 2 ? yellow : cyan;
3845
+ const sevLabel = issue.severity === "required" ? "required" : "recommended";
3846
+ const kindStr = issue.field ? ` ${dim(`(${issue.field.kind})`)}` : "";
3847
+ let optionsStr = "";
3848
+ if (issue.field?.options && issue.field.options.length > 0) optionsStr = ` ${dim(`[${issue.field.options.join(", ")}]`)}`;
3849
+ lines.push(` ${prioColor(prioLabel)} ${dim(`[${sevLabel}]`)} ${bold(issue.ref)}${kindStr}${optionsStr}`);
3850
+ lines.push(` ${issue.message}`);
3851
+ lines.push(` ${dim("->")} ${cyan(issue.set_example)}`);
3852
+ if (issue.skip_example) lines.push(` ${dim("->")} ${dim(issue.skip_example)}`);
3853
+ lines.push("");
3854
+ }
3855
+ return lines.join("\n").trimEnd();
3856
+ }
3857
+ const DEFAULT_MAX_ISSUES = 10;
3858
+ /**
3859
+ * Register the next command.
3860
+ */
3861
+ function registerNextCommand(program) {
3862
+ program.command("next <file>").description("Show prioritized next fields to fill (field advisor for CLI form filling)").option("--roles <roles>", "Target roles (comma-separated, or '*' for all; default: all)").option("--max-fields <n>", "Max distinct fields per batch").option("--max-groups <n>", "Max distinct groups per batch").option("--max-issues <n>", `Max issues to return (default: ${DEFAULT_MAX_ISSUES})`).action(async (file, options, cmd) => {
3863
+ const ctx = getCommandContext(cmd);
3864
+ try {
3865
+ let targetRoles;
3866
+ if (options.roles) try {
3867
+ targetRoles = parseRolesFlag(options.roles);
3868
+ if (targetRoles.includes("*")) targetRoles = void 0;
3869
+ } catch (error) {
3870
+ logError(`Invalid --roles: ${error instanceof Error ? error.message : String(error)}`);
3871
+ process.exit(1);
3872
+ }
3873
+ const maxFields = options.maxFields ? parseInt(options.maxFields, 10) : void 0;
3874
+ const maxGroups = options.maxGroups ? parseInt(options.maxGroups, 10) : void 0;
3875
+ const maxIssuesParsed = options.maxIssues ? parseInt(options.maxIssues, 10) : void 0;
3876
+ if (maxFields !== void 0 && isNaN(maxFields) || maxGroups !== void 0 && isNaN(maxGroups) || maxIssuesParsed !== void 0 && isNaN(maxIssuesParsed)) {
3877
+ logError("--max-fields, --max-groups, and --max-issues must be numeric");
3878
+ process.exit(1);
3879
+ }
3880
+ logVerbose(ctx, `Reading file: ${file}`);
3881
+ const content = await readFile$1(file);
3882
+ logVerbose(ctx, "Parsing form...");
3883
+ const form = parseForm(content);
3884
+ const harnessConfig = form.metadata?.harnessConfig;
3885
+ const effectiveMaxIssues = maxIssuesParsed ?? harnessConfig?.maxIssuesPerTurn ?? DEFAULT_MAX_ISSUES;
3886
+ const effectiveMaxFields = maxFields ?? void 0;
3887
+ const effectiveMaxGroups = maxGroups ?? void 0;
3888
+ logVerbose(ctx, "Running inspection...");
3889
+ const result = inspect(form, { targetRoles });
3890
+ const limitedIssues = filterIssuesByScope(filterIssuesByOrder(result.issues, form), form, effectiveMaxFields, effectiveMaxGroups).slice(0, effectiveMaxIssues);
3891
+ const maxPatches = harnessConfig?.maxPatchesPerTurn ?? limitedIssues.length;
3892
+ const stepBudget = Math.min(maxPatches, limitedIssues.length);
3893
+ const enrichedIssues = limitedIssues.map((issue) => enrichIssue(issue, form, file));
3894
+ const counts = result.progressSummary.counts;
3895
+ const output = formatOutput(ctx, {
3896
+ is_complete: result.isComplete,
3897
+ form_state: result.formState,
3898
+ step_budget: stepBudget,
3899
+ progress: {
3900
+ total_fields: counts.totalFields,
3901
+ required_fields: counts.requiredFields,
3902
+ filled_fields: counts.filledFields,
3903
+ empty_required_fields: counts.emptyRequiredFields
3904
+ },
3905
+ issues: enrichedIssues
3906
+ }, (data, useColors) => formatConsoleReport$3(data, useColors));
3907
+ console.log(output);
3908
+ } catch (error) {
3909
+ logError(error instanceof Error ? error.message : String(error));
3910
+ process.exit(1);
3911
+ }
3912
+ });
3913
+ }
3914
+
3730
3915
  //#endregion
3731
3916
  //#region src/cli/commands/plan.ts
3732
3917
  /**
@@ -4326,7 +4511,7 @@ async function handleSave(req, res, form, filePath, ctx, updateForm) {
4326
4511
  }));
4327
4512
  return;
4328
4513
  }
4329
- await writeFile(newPath, content);
4514
+ await writeFile$1(newPath, content);
4330
4515
  logInfo(ctx, pc.green(`Saved to: ${newPath}`));
4331
4516
  res.writeHead(200, { "Content-Type": "application/json" });
4332
4517
  res.end(JSON.stringify({
@@ -4677,6 +4862,12 @@ function renderFormHtml(form, tabs) {
4677
4862
  border-radius: 3px;
4678
4863
  margin-left: 0.5rem;
4679
4864
  }
4865
+ .skip-reason {
4866
+ font-size: 0.85rem;
4867
+ color: #6c757d;
4868
+ font-style: italic;
4869
+ margin-top: 0.25rem;
4870
+ }
4680
4871
  .table-container {
4681
4872
  overflow-x: auto;
4682
4873
  }
@@ -5052,8 +5243,8 @@ function renderFieldHtml(field, value, isSkipped, skipReason) {
5052
5243
  const skipped = isSkipped === true;
5053
5244
  const requiredMark = field.required ? "<span class=\"required\">*</span>" : "";
5054
5245
  const typeLabel = `<span class="type-badge">${field.kind}</span>`;
5055
- const skippedText = skipped && skipReason ? `Skipped: ${escapeHtml(skipReason)}` : "Skipped";
5056
- const skippedBadge = skipped ? `<span class="skipped-badge">${skippedText}</span>` : "";
5246
+ const skippedBadge = skipped ? `<span class="skipped-badge">Skipped</span>` : "";
5247
+ const skipReasonHtml = skipped && skipReason ? `<div class="skip-reason">(skipped: ${escapeHtml(skipReason)})</div>` : "";
5057
5248
  const fieldClass = skipped ? "field field-skipped" : "field";
5058
5249
  const disabledAttr = skipped ? " disabled" : "";
5059
5250
  let inputHtml;
@@ -5105,6 +5296,7 @@ function renderFieldHtml(field, value, isSkipped, skipReason) {
5105
5296
  ${escapeHtml(field.label)} ${requiredMark} ${typeLabel} ${skippedBadge}
5106
5297
  </label>
5107
5298
  ${inputHtml}
5299
+ ${skipReasonHtml}
5108
5300
  ${skipButton}
5109
5301
  </div>`;
5110
5302
  }
@@ -5516,7 +5708,7 @@ function registerRenderCommand(program) {
5516
5708
  logDryRun(`Would write HTML to: ${outputPath}`);
5517
5709
  return;
5518
5710
  }
5519
- await writeFile(outputPath, html);
5711
+ await writeFile$1(outputPath, html);
5520
5712
  logSuccess(ctx, pc.green(`✓ Rendered to ${outputPath}`));
5521
5713
  } catch (error) {
5522
5714
  logError(error instanceof Error ? error.message : String(error));
@@ -5678,9 +5870,9 @@ function registerResearchCommand(program) {
5678
5870
  console.log(` ${formPath} ${pc.dim("(filled markform source)")}`);
5679
5871
  console.log(` ${schemaPath} ${pc.dim("(JSON Schema)")}`);
5680
5872
  if (options.transcript && result.transcript) {
5681
- const { serializeSession } = await import("./session-VeSkVrck.mjs");
5873
+ const { serializeSession } = await import("./session-D7C7IlEv.mjs");
5682
5874
  const transcriptPath = outputPath.replace(/\.form\.md$/, ".session.yaml");
5683
- const { writeFile } = await import("./shared-fb0nkzQi.mjs");
5875
+ const { writeFile } = await import("./shared-DtorFV21.mjs");
5684
5876
  await writeFile(transcriptPath, serializeSession(result.transcript));
5685
5877
  logInfo(ctx, `Transcript: ${transcriptPath}`);
5686
5878
  }
@@ -5727,6 +5919,349 @@ function registerSchemaCommand(program) {
5727
5919
  });
5728
5920
  }
5729
5921
 
5922
+ //#endregion
5923
+ //#region src/cli/commands/set.ts
5924
+ /**
5925
+ * Parse a CLI value string into a RawFieldValue.
5926
+ *
5927
+ * Only detects JSON arrays/objects ([ or { prefix). Everything else
5928
+ * is passed through as a string — the coercion layer handles
5929
+ * type conversion based on the field's kind.
5930
+ */
5931
+ function parseCliValue(raw) {
5932
+ if (raw.startsWith("[") || raw.startsWith("{")) try {
5933
+ return JSON.parse(raw);
5934
+ } catch {
5935
+ return raw;
5936
+ }
5937
+ return raw;
5938
+ }
5939
+ /**
5940
+ * Format set report for console output.
5941
+ */
5942
+ function formatConsoleReport$2(report, useColors) {
5943
+ const lines = [];
5944
+ const bold = useColors ? pc.bold : (s) => s;
5945
+ const dim = useColors ? pc.dim : (s) => s;
5946
+ const cyan = useColors ? pc.cyan : (s) => s;
5947
+ const green = useColors ? pc.green : (s) => s;
5948
+ const red = useColors ? pc.red : (s) => s;
5949
+ lines.push(bold(cyan("Set Result")));
5950
+ lines.push("");
5951
+ const statusColor = report.apply_status === "applied" ? green : red;
5952
+ lines.push(`${bold("Status:")} ${statusColor(report.apply_status)}`);
5953
+ lines.push(`${bold("Form State:")} ${report.form_state}`);
5954
+ lines.push(`${bold("Complete:")} ${report.is_complete ? green("yes") : dim("no")}`);
5955
+ lines.push("");
5956
+ const counts = report.progress.counts;
5957
+ lines.push(bold("Progress:"));
5958
+ lines.push(` Total fields: ${counts.totalFields}`);
5959
+ lines.push(` Filled: ${counts.filledFields}, Empty: ${counts.emptyFields}`);
5960
+ lines.push(` Empty required: ${counts.emptyRequiredFields}`);
5961
+ lines.push("");
5962
+ if (report.issues.length > 0) {
5963
+ lines.push(bold(`Issues (${report.issues.length}):`));
5964
+ for (const issue of report.issues) lines.push(` P${issue.priority} ${dim(issue.ref)}: ${issue.message}`);
5965
+ } else lines.push(dim("No issues."));
5966
+ return lines.join("\n");
5967
+ }
5968
+ /**
5969
+ * Register the set command.
5970
+ */
5971
+ function registerSetCommand(program) {
5972
+ program.command("set <file> [fieldId] [value]").description("Set field values with auto-coercion").option("--values <json>", "Batch set: JSON object of {fieldId: rawValue} pairs").option("--append <value>", "Append item/row to a collection field").option("--delete <n>", "Delete item/row at index (0-based) from a collection field").option("--clear", "Clear the field value").option("--skip", "Skip the field (marks as skipped)").option("--abort", "Abort the field (marks as unable to complete)").option("--role <role>", "Role for skip/abort (default: \"user\")", "user").option("--reason <text>", "Reason for skip/abort").option("-o, --output <file>", "Output file (default: modify in place)").option("--report", "Output JSON report after applying (issues, progress)").option("--normalize", "Regenerate form without preserving external content").action(async (file, fieldId, value, options, cmd) => {
5973
+ const ctx = getCommandContext(cmd);
5974
+ try {
5975
+ logVerbose(ctx, `Reading file: ${file}`);
5976
+ const content = await readFile$1(file);
5977
+ logVerbose(ctx, "Parsing form...");
5978
+ const form = parseForm(content);
5979
+ let patches;
5980
+ if (options.values) {
5981
+ if (fieldId) {
5982
+ logError("Cannot use --values with positional fieldId/value arguments");
5983
+ process.exit(1);
5984
+ }
5985
+ if (options.clear || options.skip || options.abort || options.append !== void 0 || options.delete !== void 0) {
5986
+ logError("Cannot use --values with --clear, --skip, --abort, --append, or --delete");
5987
+ process.exit(1);
5988
+ }
5989
+ let inputContext;
5990
+ try {
5991
+ inputContext = JSON.parse(options.values);
5992
+ } catch {
5993
+ logError("Invalid JSON in --values option");
5994
+ process.exit(1);
5995
+ }
5996
+ if (typeof inputContext !== "object" || Array.isArray(inputContext)) {
5997
+ logError("--values must be a JSON object");
5998
+ process.exit(1);
5999
+ }
6000
+ const result = coerceInputContext(form, inputContext);
6001
+ for (const w of result.warnings) logVerbose(ctx, `Warning: ${w}`);
6002
+ if (result.errors.length > 0) {
6003
+ for (const e of result.errors) logError(e);
6004
+ process.exit(1);
6005
+ }
6006
+ patches = result.patches;
6007
+ } else if (!fieldId) {
6008
+ logError("Either <fieldId> or --values is required");
6009
+ process.exit(1);
6010
+ } else if (options.clear) {
6011
+ if (!findFieldById(form, fieldId)) {
6012
+ logError(`Field "${fieldId}" not found in form`);
6013
+ process.exit(1);
6014
+ }
6015
+ patches = [{
6016
+ op: "clear_field",
6017
+ fieldId
6018
+ }];
6019
+ } else if (options.skip) {
6020
+ if (!findFieldById(form, fieldId)) {
6021
+ logError(`Field "${fieldId}" not found in form`);
6022
+ process.exit(1);
6023
+ }
6024
+ patches = [{
6025
+ op: "skip_field",
6026
+ fieldId,
6027
+ role: options.role,
6028
+ ...options.reason && { reason: options.reason }
6029
+ }];
6030
+ } else if (options.abort) {
6031
+ if (!findFieldById(form, fieldId)) {
6032
+ logError(`Field "${fieldId}" not found in form`);
6033
+ process.exit(1);
6034
+ }
6035
+ patches = [{
6036
+ op: "abort_field",
6037
+ fieldId,
6038
+ role: options.role,
6039
+ ...options.reason && { reason: options.reason }
6040
+ }];
6041
+ } else if (options.append !== void 0) {
6042
+ const field = findFieldById(form, fieldId);
6043
+ if (!field) {
6044
+ logError(`Field "${fieldId}" not found in form`);
6045
+ process.exit(1);
6046
+ }
6047
+ const rawValue = parseCliValue(options.append);
6048
+ if (field.kind === "table") {
6049
+ const rows = Array.isArray(rawValue) ? rawValue : typeof rawValue === "object" && rawValue !== null ? [rawValue] : null;
6050
+ if (!rows) {
6051
+ logError(`--append for table field "${fieldId}" requires a JSON object or array of row objects`);
6052
+ process.exit(1);
6053
+ }
6054
+ patches = [{
6055
+ op: "append_table",
6056
+ fieldId,
6057
+ value: rows
6058
+ }];
6059
+ } else if (field.kind === "string_list") patches = [{
6060
+ op: "append_string_list",
6061
+ fieldId,
6062
+ value: Array.isArray(rawValue) ? rawValue : [typeof rawValue === "string" ? rawValue : JSON.stringify(rawValue)]
6063
+ }];
6064
+ else if (field.kind === "url_list") patches = [{
6065
+ op: "append_url_list",
6066
+ fieldId,
6067
+ value: Array.isArray(rawValue) ? rawValue : [typeof rawValue === "string" ? rawValue : JSON.stringify(rawValue)]
6068
+ }];
6069
+ else {
6070
+ logError(`--append is not supported for ${field.kind} fields (only table, string_list, url_list)`);
6071
+ process.exit(1);
6072
+ }
6073
+ } else if (options.delete !== void 0) {
6074
+ const field = findFieldById(form, fieldId);
6075
+ if (!field) {
6076
+ logError(`Field "${fieldId}" not found in form`);
6077
+ process.exit(1);
6078
+ }
6079
+ const index = parseInt(options.delete, 10);
6080
+ if (isNaN(index) || index < 0) {
6081
+ logError(`--delete requires a non-negative integer index, got "${options.delete}"`);
6082
+ process.exit(1);
6083
+ }
6084
+ if (field.kind === "table") patches = [{
6085
+ op: "delete_table",
6086
+ fieldId,
6087
+ value: index
6088
+ }];
6089
+ else if (field.kind === "string_list") patches = [{
6090
+ op: "delete_string_list",
6091
+ fieldId,
6092
+ value: index
6093
+ }];
6094
+ else if (field.kind === "url_list") patches = [{
6095
+ op: "delete_url_list",
6096
+ fieldId,
6097
+ value: index
6098
+ }];
6099
+ else {
6100
+ logError(`--delete is not supported for ${field.kind} fields (only table, string_list, url_list)`);
6101
+ process.exit(1);
6102
+ }
6103
+ } else if (value !== void 0) {
6104
+ const result = coerceToFieldPatch(form, fieldId, parseCliValue(value));
6105
+ if (!result.ok) {
6106
+ logError(result.error);
6107
+ process.exit(1);
6108
+ }
6109
+ if ("warning" in result && result.warning) logVerbose(ctx, `Warning: ${result.warning}`);
6110
+ patches = [result.patch];
6111
+ } else {
6112
+ logError("No value provided. Use <value>, --clear, --skip, --abort, --append, or --delete");
6113
+ process.exit(1);
6114
+ }
6115
+ if (ctx.dryRun) {
6116
+ logDryRun(`Would apply ${patches.length} patches to ${file}`, { patches });
6117
+ return;
6118
+ }
6119
+ logVerbose(ctx, `Applying ${patches.length} patches...`);
6120
+ const applyResult = applyPatches(form, patches);
6121
+ if (applyResult.applyStatus === "rejected") {
6122
+ logError("Patches rejected");
6123
+ for (const rp of applyResult.rejectedPatches) logError(` ${rp.message}`);
6124
+ process.exit(1);
6125
+ }
6126
+ if (options.report) {
6127
+ const output = formatOutput(ctx, {
6128
+ apply_status: applyResult.applyStatus,
6129
+ form_state: applyResult.formState,
6130
+ is_complete: applyResult.isComplete,
6131
+ structure: applyResult.structureSummary,
6132
+ progress: applyResult.progressSummary,
6133
+ issues: applyResult.issues
6134
+ }, (data, useColors) => formatConsoleReport$2(data, useColors));
6135
+ if (options.output) {
6136
+ await writeFile$1(options.output, output);
6137
+ logSuccess(ctx, `Report written to ${options.output}`);
6138
+ } else console.log(output);
6139
+ } else {
6140
+ const output = serializeForm(form, { preserveContent: !options.normalize });
6141
+ const target = options.output ?? file;
6142
+ await writeFile$1(target, output);
6143
+ logSuccess(ctx, `Form updated: ${target}`);
6144
+ }
6145
+ } catch (error) {
6146
+ logError(error instanceof Error ? error.message : String(error));
6147
+ process.exit(1);
6148
+ }
6149
+ });
6150
+ }
6151
+
6152
+ //#endregion
6153
+ //#region src/cli/commands/setup.ts
6154
+ const DO_NOT_EDIT_MARKER = `<!-- DO NOT EDIT: Generated by markform setup.
6155
+ Run 'markform setup' to update.
6156
+ -->`;
6157
+ /**
6158
+ * Load SKILL.md content and insert the DO NOT EDIT marker after frontmatter.
6159
+ */
6160
+ function loadSkillWithMarker() {
6161
+ const lines = readFileSync(resolvePackagePath(import.meta.url, "docs/skill/SKILL.md"), "utf-8").split("\n");
6162
+ let endOfFrontmatter = -1;
6163
+ if (lines[0] === "---") {
6164
+ for (let i = 1; i < lines.length; i++) if (lines[i] === "---") {
6165
+ endOfFrontmatter = i;
6166
+ break;
6167
+ }
6168
+ }
6169
+ if (endOfFrontmatter >= 0) lines.splice(endOfFrontmatter + 1, 0, "", DO_NOT_EDIT_MARKER);
6170
+ return lines.join("\n");
6171
+ }
6172
+ /**
6173
+ * Install the skill file to .claude/skills/markform/SKILL.md.
6174
+ */
6175
+ async function installSkill(projectDir, ctx) {
6176
+ const skillDir = join(projectDir, ".claude", "skills", "markform");
6177
+ const skillPath = join(skillDir, "SKILL.md");
6178
+ const isUpdate = existsSync(skillPath);
6179
+ const content = loadSkillWithMarker();
6180
+ await mkdir(skillDir, { recursive: true });
6181
+ await writeFile(skillPath, content, "utf-8");
6182
+ logSuccess(ctx, `${isUpdate ? "Updated" : "Installed"} ${pc.bold(".claude/skills/markform/SKILL.md")}`);
6183
+ }
6184
+ /**
6185
+ * Run setup in auto mode (non-interactive, for agents).
6186
+ */
6187
+ async function setupAuto(ctx) {
6188
+ logInfo(ctx, "Installing Markform skill for Claude Code...");
6189
+ await installSkill(process.cwd(), ctx);
6190
+ logInfo(ctx, `Run ${pc.cyan("markform skill")} to view the installed skill content.`);
6191
+ }
6192
+ /**
6193
+ * Run setup in interactive mode (guided, for humans).
6194
+ */
6195
+ async function setupInteractive(ctx) {
6196
+ p.intro(pc.bgCyan(pc.black(" markform setup ")));
6197
+ p.log.info([
6198
+ "This will install Markform as a Claude Code skill in your project.",
6199
+ "",
6200
+ ` ${pc.dim("Creates:")} .claude/skills/markform/SKILL.md`,
6201
+ "",
6202
+ "This teaches Claude Code how to use markform commands when working",
6203
+ "with .form.md files in this project."
6204
+ ].join("\n"));
6205
+ const shouldContinue = await p.confirm({
6206
+ message: "Install the Markform skill?",
6207
+ initialValue: true
6208
+ });
6209
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
6210
+ p.outro("Setup cancelled.");
6211
+ return;
6212
+ }
6213
+ await installSkill(process.cwd(), ctx);
6214
+ p.outro("Markform skill installed! Claude Code will now know how to use markform.");
6215
+ }
6216
+ /**
6217
+ * Register the setup command.
6218
+ */
6219
+ function registerSetupCommand(program) {
6220
+ const cmd = program.command("setup").description("Install Markform as a Claude Code skill in the current project").option("--auto", "Non-interactive setup (for agents and scripts)").option("--interactive", "Guided setup with prompts (for humans)").action(async (options, command) => {
6221
+ const ctx = getCommandContext(command);
6222
+ try {
6223
+ if (!options.auto && !options.interactive) {
6224
+ cmd.help();
6225
+ return;
6226
+ }
6227
+ if (options.auto) await setupAuto(ctx);
6228
+ else await setupInteractive(ctx);
6229
+ } catch (error) {
6230
+ logError(error instanceof Error ? error.message : String(error));
6231
+ process.exit(1);
6232
+ }
6233
+ });
6234
+ }
6235
+
6236
+ //#endregion
6237
+ //#region src/cli/commands/skill.ts
6238
+ /**
6239
+ * Load the SKILL.md content.
6240
+ */
6241
+ function loadSkillContent() {
6242
+ const skillPath = resolvePackagePath(import.meta.url, "docs/skill/SKILL.md");
6243
+ try {
6244
+ return readFileSync(skillPath, "utf-8");
6245
+ } catch (error) {
6246
+ const message = error instanceof Error ? error.message : String(error);
6247
+ throw new Error(`Failed to load SKILL.md from ${skillPath}: ${message}`);
6248
+ }
6249
+ }
6250
+ /**
6251
+ * Register the skill command.
6252
+ */
6253
+ function registerSkillCommand(program) {
6254
+ program.command("skill").description("Output SKILL.md content for Claude Code integration").action(() => {
6255
+ try {
6256
+ const content = loadSkillContent();
6257
+ process.stdout.write(content);
6258
+ } catch (error) {
6259
+ logError(error instanceof Error ? error.message : String(error));
6260
+ process.exit(1);
6261
+ }
6262
+ });
6263
+ }
6264
+
5730
6265
  //#endregion
5731
6266
  //#region src/cli/commands/status.ts
5732
6267
  /**
@@ -6032,7 +6567,7 @@ function registerValidateCommand(program) {
6032
6567
  /**
6033
6568
  * CLI implementation for markform.
6034
6569
  *
6035
- * Provides commands for inspecting, applying patches, exporting,
6570
+ * Provides commands for inspecting, patching, exporting,
6036
6571
  * serving, and running harness loops on .form.md files.
6037
6572
  */
6038
6573
  /**
@@ -6040,6 +6575,7 @@ function registerValidateCommand(program) {
6040
6575
  */
6041
6576
  function withColoredHelp(cmd) {
6042
6577
  cmd.configureHelp({
6578
+ helpWidth: 88,
6043
6579
  styleTitle: (str) => pc.bold(pc.cyan(str)),
6044
6580
  styleCommandText: (str) => pc.green(str),
6045
6581
  styleOptionText: (str) => pc.yellow(str),
@@ -6052,12 +6588,18 @@ function withColoredHelp(cmd) {
6052
6588
  */
6053
6589
  function createProgram() {
6054
6590
  const program = withColoredHelp(new Command());
6055
- program.name("markform").description("Agent-friendly, human-readable, editable forms").version(CLI_VERSION, "--version", "output the version number").showHelpAfterError().option("--verbose", "Enable verbose output").option("--quiet", "Suppress non-essential output").option("--dry-run", "Show what would be done without making changes").option("--format <format>", `Output format: ${OUTPUT_FORMATS.join(", ")}`, "console").option("--forms-dir <dir>", `Directory for form output (default: ${DEFAULT_FORMS_DIR})`).option("--overwrite", "Overwrite existing field values (default: continue/skip filled)");
6591
+ program.name("markform").description("Agent-friendly, human-readable, editable forms").version(CLI_VERSION, "--version", "output the version number").showHelpAfterError().option("--verbose", "Enable verbose output").option("--quiet", "Suppress non-essential output").option("--dry-run", "Show what would be done without making changes").option("--format <format>", `Output format: ${OUTPUT_FORMATS.join(", ")}`, "console").option("--forms-dir <dir>", `Directory for form output (default: ${DEFAULT_FORMS_DIR})`).option("--overwrite", "Overwrite existing field values (default: continue/skip filled)").addHelpText("after", `
6592
+ Skill Setup:
6593
+ To use Markform as a Claude Code skill, run: markform setup --auto
6594
+ To view the skill content: markform skill
6595
+
6596
+ Getting Started:
6597
+ npm install -g markform && markform setup --auto`);
6056
6598
  registerReadmeCommand(program);
6057
6599
  registerDocsCommand(program);
6058
6600
  registerSpecCommand(program);
6059
6601
  registerApisCommand(program);
6060
- registerApplyCommand(program);
6602
+ registerPatchCommand(program);
6061
6603
  registerBrowseCommand(program);
6062
6604
  registerDumpCommand(program);
6063
6605
  registerExamplesCommand(program);
@@ -6065,6 +6607,7 @@ function createProgram() {
6065
6607
  registerFillCommand(program);
6066
6608
  registerInspectCommand(program);
6067
6609
  registerModelsCommand(program);
6610
+ registerNextCommand(program);
6068
6611
  registerPlanCommand(program);
6069
6612
  registerRenderCommand(program);
6070
6613
  registerReportCommand(program);
@@ -6072,6 +6615,9 @@ function createProgram() {
6072
6615
  registerRunCommand(program);
6073
6616
  registerSchemaCommand(program);
6074
6617
  registerServeCommand(program);
6618
+ registerSetCommand(program);
6619
+ registerSetupCommand(program);
6620
+ registerSkillCommand(program);
6075
6621
  registerStatusCommand(program);
6076
6622
  registerValidateCommand(program);
6077
6623
  return program;
@@ -6085,4 +6631,4 @@ async function runCli() {
6085
6631
 
6086
6632
  //#endregion
6087
6633
  export { runCli as t };
6088
- //# sourceMappingURL=cli-C8F9yDsv.mjs.map
6634
+ //# sourceMappingURL=cli-B1DhFYBS.mjs.map