markform 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -29
- package/dist/ai-sdk.d.mts +1 -1
- package/dist/ai-sdk.mjs +48 -6
- package/dist/ai-sdk.mjs.map +1 -1
- package/dist/bin.mjs +1 -1
- package/dist/{cli-ZcOC47KK.mjs → cli-B1T8kMFt.mjs} +709 -125
- package/dist/cli-B1T8kMFt.mjs.map +1 -0
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/{coreTypes-BlsJkU1w.d.mts → coreTypes-CxpqKpBA.d.mts} +181 -4
- package/dist/{coreTypes-CTLr-NGd.mjs → coreTypes-DIv9Aabl.mjs} +56 -6
- package/dist/coreTypes-DIv9Aabl.mjs.map +1 -0
- package/dist/{fillRecord-DTl5lnK0.d.mts → fillRecord-V3vlyobd.d.mts} +29 -1
- package/dist/{fillRecordRenderer-VBQ2vwPV.mjs → fillRecordRenderer-BqRPHPmE.mjs} +47 -15
- package/dist/fillRecordRenderer-BqRPHPmE.mjs.map +1 -0
- package/dist/index.d.mts +34 -30
- package/dist/index.mjs +4 -4
- package/dist/{apply-KzQztrDV.mjs → prompts-DaPKumGY.mjs} +1081 -17
- package/dist/prompts-DaPKumGY.mjs.map +1 -0
- package/dist/render.d.mts +2 -2
- package/dist/render.mjs +1 -1
- package/dist/{session-BCcltrLA.mjs → session-BW9jtYNV.mjs} +2 -2
- package/dist/{session-BCcltrLA.mjs.map → session-BW9jtYNV.mjs.map} +1 -1
- package/dist/{session-VeSkVrck.mjs → session-DHyTMP67.mjs} +1 -1
- package/dist/{shared-fb0nkzQi.mjs → shared-BLh342F5.mjs} +1 -1
- package/dist/{shared-CsdT2T7k.mjs → shared-BszoSkAO.mjs} +10 -10
- package/dist/shared-BszoSkAO.mjs.map +1 -0
- package/dist/{src-B2uFvGli.mjs → src-DrXmaOWl.mjs} +179 -840
- package/dist/src-DrXmaOWl.mjs.map +1 -0
- package/docs/markform-apis.md +25 -7
- package/docs/markform-reference.md +273 -179
- package/docs/markform-spec.md +92 -36
- package/docs/skill/SKILL.md +161 -0
- package/examples/markform-demo-playbook.md +342 -0
- package/examples/parallel/parallel-research.form.md +2 -6
- package/examples/rejection-test/rejection-test.session.yaml +52 -0
- package/examples/simple/simple-mock-filled.report.md +2 -2
- package/examples/simple/simple-skipped-filled.report.md +2 -2
- package/examples/simple/simple-with-skips.session.yaml +78 -0
- package/examples/simple/simple.session.yaml +78 -0
- package/examples/twitter-thread/twitter-thread.form.md +5 -5
- package/package.json +2 -2
- package/dist/apply-KzQztrDV.mjs.map +0 -1
- package/dist/cli-ZcOC47KK.mjs.map +0 -1
- package/dist/coreTypes-CTLr-NGd.mjs.map +0 -1
- package/dist/fillRecordRenderer-VBQ2vwPV.mjs.map +0 -1
- package/dist/shared-CsdT2T7k.mjs.map +0 -1
- package/dist/src-B2uFvGli.mjs.map +0 -1
- package/examples/startup-research/startup-research-mock-filled.form.md +0 -297
- package/examples/startup-research/startup-research.form.md +0 -181
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
|
|
2
|
-
import { R as PatchSchema } from "./coreTypes-
|
|
3
|
-
import { $ as
|
|
4
|
-
import {
|
|
5
|
-
import { n as serializeSession } from "./session-
|
|
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-
|
|
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-
|
|
2
|
+
import { R as PatchSchema } from "./coreTypes-DIv9Aabl.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-DaPKumGY.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-DrXmaOWl.mjs";
|
|
5
|
+
import { n as serializeSession } from "./session-BW9jtYNV.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-BszoSkAO.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-BqRPHPmE.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 {
|
|
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 =
|
|
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/
|
|
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
|
|
201
|
+
* Format patch report for console output.
|
|
196
202
|
*/
|
|
197
|
-
function formatConsoleReport$
|
|
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("
|
|
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
|
|
234
|
+
* Register the patch command.
|
|
229
235
|
*/
|
|
230
|
-
function
|
|
231
|
-
program.command("
|
|
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(
|
|
247
|
+
parsedJson = JSON.parse(json);
|
|
246
248
|
} catch {
|
|
247
|
-
logError("Invalid JSON in
|
|
249
|
+
logError("Invalid JSON in patch argument");
|
|
248
250
|
process.exit(1);
|
|
249
251
|
}
|
|
250
252
|
if (!Array.isArray(parsedJson)) {
|
|
251
|
-
logError("
|
|
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$
|
|
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$
|
|
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 =
|
|
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
|
|
|
@@ -1033,6 +1026,12 @@ const EXAMPLE_DEFINITIONS = [
|
|
|
1033
1026
|
path: "simple/simple.form.md",
|
|
1034
1027
|
type: "fill"
|
|
1035
1028
|
},
|
|
1029
|
+
{
|
|
1030
|
+
id: "twitter-thread",
|
|
1031
|
+
filename: "twitter-thread.form.md",
|
|
1032
|
+
path: "twitter-thread/twitter-thread.form.md",
|
|
1033
|
+
type: "fill"
|
|
1034
|
+
},
|
|
1036
1035
|
{
|
|
1037
1036
|
id: "movie-deep-research",
|
|
1038
1037
|
filename: "movie-deep-research.form.md",
|
|
@@ -1061,9 +1060,7 @@ function getExampleOrder(filename) {
|
|
|
1061
1060
|
* Works both during development and when installed as a package.
|
|
1062
1061
|
*/
|
|
1063
1062
|
function getExamplesDir() {
|
|
1064
|
-
|
|
1065
|
-
if (thisDir.split(/[/\\]/).pop() === "dist") return join(dirname(thisDir), "examples");
|
|
1066
|
-
return join(dirname(dirname(dirname(thisDir))), "examples");
|
|
1063
|
+
return resolvePackagePath(import.meta.url, "examples");
|
|
1067
1064
|
}
|
|
1068
1065
|
/**
|
|
1069
1066
|
* Load the content of an example form.
|
|
@@ -1967,6 +1964,12 @@ function formatPatchValue(patch) {
|
|
|
1967
1964
|
const rowCount = patch.value?.length ?? 0;
|
|
1968
1965
|
return rowCount > 0 ? truncate(`[${rowCount} rows]`) : "(empty)";
|
|
1969
1966
|
}
|
|
1967
|
+
case "append_table": return truncate(`+[${patch.value?.length ?? 0} rows]`);
|
|
1968
|
+
case "delete_table": return `(delete row ${patch.value})`;
|
|
1969
|
+
case "append_string_list": return truncate(`+[${patch.value.join(", ")}]`);
|
|
1970
|
+
case "delete_string_list": return `(delete item ${patch.value})`;
|
|
1971
|
+
case "append_url_list": return truncate(`+[${patch.value.join(", ")}]`);
|
|
1972
|
+
case "delete_url_list": return `(delete item ${patch.value})`;
|
|
1970
1973
|
case "add_note": return truncate(`note: ${patch.text}`);
|
|
1971
1974
|
case "remove_note": return `(remove note ${patch.noteId})`;
|
|
1972
1975
|
}
|
|
@@ -1990,6 +1993,12 @@ function formatPatchType(patch) {
|
|
|
1990
1993
|
case "set_date": return "date";
|
|
1991
1994
|
case "set_year": return "year";
|
|
1992
1995
|
case "set_table": return "table";
|
|
1996
|
+
case "append_table": return "append_table";
|
|
1997
|
+
case "delete_table": return "delete_table";
|
|
1998
|
+
case "append_string_list": return "append_string_list";
|
|
1999
|
+
case "delete_string_list": return "delete_string_list";
|
|
2000
|
+
case "append_url_list": return "append_url_list";
|
|
2001
|
+
case "delete_url_list": return "delete_url_list";
|
|
1993
2002
|
case "add_note": return "note";
|
|
1994
2003
|
case "remove_note": return "remove_note";
|
|
1995
2004
|
}
|
|
@@ -2433,18 +2442,38 @@ function registerRunCommand(program) {
|
|
|
2433
2442
|
*/
|
|
2434
2443
|
/**
|
|
2435
2444
|
* Print non-interactive list of examples.
|
|
2445
|
+
* Supports --format=json and --format=yaml via formatOutput().
|
|
2436
2446
|
*/
|
|
2437
|
-
function printExamplesList() {
|
|
2438
|
-
console.log(pc.bold("Available examples:\n"));
|
|
2447
|
+
function printExamplesList(ctx) {
|
|
2439
2448
|
const examples = getAllExamplesWithMetadata();
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2449
|
+
const output = formatOutput(ctx, examples.map((example) => ({
|
|
2450
|
+
id: example.id,
|
|
2451
|
+
filename: example.filename,
|
|
2452
|
+
type: example.type,
|
|
2453
|
+
title: example.title ?? example.id,
|
|
2454
|
+
description: example.description ?? ""
|
|
2455
|
+
})), (_data, useColors) => {
|
|
2456
|
+
const c = useColors ? pc : {
|
|
2457
|
+
bold: (s) => s,
|
|
2458
|
+
cyan: (s) => s,
|
|
2459
|
+
magenta: (s) => s,
|
|
2460
|
+
blue: (s) => s,
|
|
2461
|
+
dim: (s) => s
|
|
2462
|
+
};
|
|
2463
|
+
const lines = [c.bold("Available examples:"), ""];
|
|
2464
|
+
for (const example of examples) {
|
|
2465
|
+
const typeLabel = example.type === "research" ? c.magenta("[research]") : c.blue("[fill]");
|
|
2466
|
+
lines.push(` ${c.cyan(example.id)} ${typeLabel}`);
|
|
2467
|
+
lines.push(` ${c.bold(example.title ?? example.id)}`);
|
|
2468
|
+
lines.push(` ${example.description ?? "No description"}`);
|
|
2469
|
+
lines.push(` Source: ${formatPath(getExamplePath(example.id))}`);
|
|
2470
|
+
lines.push("");
|
|
2471
|
+
}
|
|
2472
|
+
lines.push(c.dim("Tip: For a comprehensive end-to-end walkthrough, ask your coding agent"));
|
|
2473
|
+
lines.push(c.dim("to run the Markform QA playbook (tests/qa/markform-full-walkthrough.qa.md)."));
|
|
2474
|
+
return lines.join("\n");
|
|
2475
|
+
});
|
|
2476
|
+
console.log(output);
|
|
2448
2477
|
}
|
|
2449
2478
|
/**
|
|
2450
2479
|
* Copy an example form to the forms directory.
|
|
@@ -2462,7 +2491,7 @@ async function copyExample(exampleId, formsDir, overwrite, _quiet) {
|
|
|
2462
2491
|
path: outputPath
|
|
2463
2492
|
};
|
|
2464
2493
|
}
|
|
2465
|
-
await writeFile(outputPath, loadExampleContent(exampleId));
|
|
2494
|
+
await writeFile$1(outputPath, loadExampleContent(exampleId));
|
|
2466
2495
|
return {
|
|
2467
2496
|
copied: true,
|
|
2468
2497
|
skipped: false,
|
|
@@ -2585,7 +2614,7 @@ function registerExamplesCommand(program) {
|
|
|
2585
2614
|
const ctx = getCommandContext(cmd);
|
|
2586
2615
|
try {
|
|
2587
2616
|
if (options.list) {
|
|
2588
|
-
printExamplesList();
|
|
2617
|
+
printExamplesList(ctx);
|
|
2589
2618
|
return;
|
|
2590
2619
|
}
|
|
2591
2620
|
const formsDir = getFormsDir(ctx.formsDir);
|
|
@@ -2920,7 +2949,7 @@ function registerFillCommand(program) {
|
|
|
2920
2949
|
logTiming(ctx, "Fill time", durationMs);
|
|
2921
2950
|
if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath}`);
|
|
2922
2951
|
else {
|
|
2923
|
-
await writeFile(outputPath, result.markdown);
|
|
2952
|
+
await writeFile$1(outputPath, result.markdown);
|
|
2924
2953
|
logSuccess(ctx, `Form written to: ${outputPath}`);
|
|
2925
2954
|
}
|
|
2926
2955
|
if (result.record && !ctx.quiet) {
|
|
@@ -2928,7 +2957,8 @@ function registerFillCommand(program) {
|
|
|
2928
2957
|
const summary = formatFillRecordSummary(result.record, { verbose: ctx.verbose });
|
|
2929
2958
|
console.error(summary);
|
|
2930
2959
|
}
|
|
2931
|
-
if ((options.recordFill || options.recordFillStable) && result.record)
|
|
2960
|
+
if ((options.recordFill || options.recordFillStable) && result.record) if (isEmptyFillRecord(result.record)) logVerbose(ctx, "Skipping fill record: no turns were executed");
|
|
2961
|
+
else {
|
|
2932
2962
|
const sidecarPath = deriveFillRecordPath(outputPath);
|
|
2933
2963
|
const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(result.record) : result.record;
|
|
2934
2964
|
if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write fill record to: ${sidecarPath}`);
|
|
@@ -3142,6 +3172,7 @@ function registerFillCommand(program) {
|
|
|
3142
3172
|
requiredIssuesRemaining: stepResult.issues.filter((i) => i.severity === "required").length,
|
|
3143
3173
|
isComplete: stepResult.isComplete,
|
|
3144
3174
|
rejectedPatches,
|
|
3175
|
+
coercionWarnings: stepResult.coercionWarnings,
|
|
3145
3176
|
issues: stepResult.issues,
|
|
3146
3177
|
patches
|
|
3147
3178
|
});
|
|
@@ -3172,7 +3203,7 @@ function registerFillCommand(program) {
|
|
|
3172
3203
|
const formMarkdown = serializeForm(harness.getForm(), { preserveContent: !options.normalize });
|
|
3173
3204
|
if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath}`);
|
|
3174
3205
|
else {
|
|
3175
|
-
await writeFile(outputPath, formMarkdown);
|
|
3206
|
+
await writeFile$1(outputPath, formMarkdown);
|
|
3176
3207
|
logSuccess(ctx, `Form written to: ${outputPath}`);
|
|
3177
3208
|
}
|
|
3178
3209
|
const finalInspect = inspect(harness.getForm(), { targetRoles });
|
|
@@ -3184,7 +3215,8 @@ function registerFillCommand(program) {
|
|
|
3184
3215
|
const summary = formatFillRecordSummary(fillRecord, { verbose: ctx.verbose });
|
|
3185
3216
|
console.error(summary);
|
|
3186
3217
|
}
|
|
3187
|
-
if (options.recordFill || options.recordFillStable)
|
|
3218
|
+
if (options.recordFill || options.recordFillStable) if (isEmptyFillRecord(fillRecord)) logVerbose(ctx, "Skipping fill record: no turns were executed");
|
|
3219
|
+
else {
|
|
3188
3220
|
const sidecarPath = deriveFillRecordPath(outputPath);
|
|
3189
3221
|
const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(fillRecord) : fillRecord;
|
|
3190
3222
|
if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write fill record to: ${sidecarPath}`);
|
|
@@ -3201,7 +3233,7 @@ function registerFillCommand(program) {
|
|
|
3201
3233
|
logInfo(ctx, `[DRY RUN] Would write session to: ${recordPath}`);
|
|
3202
3234
|
console.log(yaml);
|
|
3203
3235
|
} else {
|
|
3204
|
-
await writeFile(recordPath, yaml);
|
|
3236
|
+
await writeFile$1(recordPath, yaml);
|
|
3205
3237
|
logSuccess(ctx, `Session recorded to: ${recordPath}`);
|
|
3206
3238
|
}
|
|
3207
3239
|
} else if (!ctx.quiet) {
|
|
@@ -3218,10 +3250,12 @@ function registerFillCommand(program) {
|
|
|
3218
3250
|
const progressSummary = computeProgressSummary(form.schema, currentForm.responsesByFieldId, currentForm.notes, finalInspect.issues);
|
|
3219
3251
|
collector.setStatus("failed", message);
|
|
3220
3252
|
const fillRecord = collector.getRecord(progressSummary.counts);
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3253
|
+
if (!isEmptyFillRecord(fillRecord)) {
|
|
3254
|
+
const sidecarPath = deriveFillRecordPath(resolve(options.output));
|
|
3255
|
+
const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(fillRecord) : fillRecord;
|
|
3256
|
+
writeFileSync(sidecarPath, JSON.stringify(recordToWrite, null, 2));
|
|
3257
|
+
logWarn(ctx, `Partial fill record written to: ${sidecarPath}`);
|
|
3258
|
+
}
|
|
3225
3259
|
} catch {}
|
|
3226
3260
|
process.exit(1);
|
|
3227
3261
|
}
|
|
@@ -3336,7 +3370,7 @@ function formatFieldValue(value, useColors) {
|
|
|
3336
3370
|
/**
|
|
3337
3371
|
* Format inspect report for console output.
|
|
3338
3372
|
*/
|
|
3339
|
-
function formatConsoleReport$
|
|
3373
|
+
function formatConsoleReport$4(report, useColors) {
|
|
3340
3374
|
const lines = [];
|
|
3341
3375
|
const bold = useColors ? pc.bold : (s) => s;
|
|
3342
3376
|
const dim = useColors ? pc.dim : (s) => s;
|
|
@@ -3449,7 +3483,7 @@ function registerInspectCommand(program) {
|
|
|
3449
3483
|
severity: issue.severity,
|
|
3450
3484
|
blockedBy: issue.blockedBy
|
|
3451
3485
|
}))
|
|
3452
|
-
}, (data, useColors) => formatConsoleReport$
|
|
3486
|
+
}, (data, useColors) => formatConsoleReport$4(data, useColors));
|
|
3453
3487
|
console.log(output);
|
|
3454
3488
|
} catch (error) {
|
|
3455
3489
|
logError(error instanceof Error ? error.message : String(error));
|
|
@@ -3461,19 +3495,10 @@ function registerInspectCommand(program) {
|
|
|
3461
3495
|
//#endregion
|
|
3462
3496
|
//#region src/cli/commands/readme.ts
|
|
3463
3497
|
/**
|
|
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
3498
|
* Load the README content.
|
|
3474
3499
|
*/
|
|
3475
3500
|
function loadReadme() {
|
|
3476
|
-
const readmePath =
|
|
3501
|
+
const readmePath = resolvePackagePath(import.meta.url, "README.md");
|
|
3477
3502
|
try {
|
|
3478
3503
|
return readFileSync(readmePath, "utf-8");
|
|
3479
3504
|
} catch (error) {
|
|
@@ -3570,7 +3595,7 @@ function registerReportCommand(program) {
|
|
|
3570
3595
|
if (options.output) {
|
|
3571
3596
|
let outputPath = options.output;
|
|
3572
3597
|
if (!outputPath.endsWith(REPORT_EXTENSION) && !outputPath.endsWith(".md")) outputPath = outputPath + REPORT_EXTENSION;
|
|
3573
|
-
await writeFile(outputPath, reportContent);
|
|
3598
|
+
await writeFile$1(outputPath, reportContent);
|
|
3574
3599
|
logVerbose(ctx, `Report written to: ${outputPath}`);
|
|
3575
3600
|
} else console.log(reportContent);
|
|
3576
3601
|
} catch (error) {
|
|
@@ -3583,19 +3608,10 @@ function registerReportCommand(program) {
|
|
|
3583
3608
|
//#endregion
|
|
3584
3609
|
//#region src/cli/commands/spec.ts
|
|
3585
3610
|
/**
|
|
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
3611
|
* Load the spec content.
|
|
3596
3612
|
*/
|
|
3597
3613
|
function loadSpec() {
|
|
3598
|
-
const specPath =
|
|
3614
|
+
const specPath = resolvePackagePath(import.meta.url, "docs/markform-spec.md");
|
|
3599
3615
|
try {
|
|
3600
3616
|
return readFileSync(specPath, "utf-8");
|
|
3601
3617
|
} catch (error) {
|
|
@@ -3727,6 +3743,201 @@ function registerModelsCommand(program) {
|
|
|
3727
3743
|
});
|
|
3728
3744
|
}
|
|
3729
3745
|
|
|
3746
|
+
//#endregion
|
|
3747
|
+
//#region src/cli/commands/next.ts
|
|
3748
|
+
/**
|
|
3749
|
+
* Build field metadata for an issue's field.
|
|
3750
|
+
*/
|
|
3751
|
+
function buildFieldMeta(field) {
|
|
3752
|
+
const meta = {
|
|
3753
|
+
kind: field.kind,
|
|
3754
|
+
label: field.label,
|
|
3755
|
+
required: field.required
|
|
3756
|
+
};
|
|
3757
|
+
if (field.kind === "single_select" || field.kind === "multi_select") meta.options = field.options.map((o) => o.id);
|
|
3758
|
+
else if (field.kind === "checkboxes") {
|
|
3759
|
+
meta.options = field.options.map((o) => o.id);
|
|
3760
|
+
meta.checkbox_mode = field.checkboxMode;
|
|
3761
|
+
} else if (field.kind === "table") {
|
|
3762
|
+
meta.columns = field.columns.map((c) => ({
|
|
3763
|
+
id: c.id,
|
|
3764
|
+
type: c.type,
|
|
3765
|
+
required: c.required
|
|
3766
|
+
}));
|
|
3767
|
+
if (field.minRows !== void 0) meta.min_rows = field.minRows;
|
|
3768
|
+
if (field.maxRows !== void 0) meta.max_rows = field.maxRows;
|
|
3769
|
+
}
|
|
3770
|
+
return meta;
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Generate a concrete `markform set` example for a field.
|
|
3774
|
+
*/
|
|
3775
|
+
function generateSetExample(file, field) {
|
|
3776
|
+
const base = `markform set ${file} ${field.id}`;
|
|
3777
|
+
switch (field.kind) {
|
|
3778
|
+
case "string": return `${base} "example text"`;
|
|
3779
|
+
case "number": return `${base} 42`;
|
|
3780
|
+
case "string_list": return `${base} '["item1", "item2"]'`;
|
|
3781
|
+
case "single_select": return field.options.length > 0 ? `${base} ${field.options[0].id}` : `${base} option_id`;
|
|
3782
|
+
case "multi_select": return field.options.length > 0 ? `${base} '["${field.options.map((o) => o.id).join("\", \"")}"]'` : `${base} '["option1"]'`;
|
|
3783
|
+
case "checkboxes":
|
|
3784
|
+
if (field.checkboxMode === "simple") return `${base} '${JSON.stringify(Object.fromEntries(field.options.map((o) => [o.id, true])))}'`;
|
|
3785
|
+
return `${base} '${JSON.stringify(Object.fromEntries(field.options.map((o) => [o.id, "done"])))}'`;
|
|
3786
|
+
case "url": return `${base} "https://example.com"`;
|
|
3787
|
+
case "url_list": return `${base} '["https://example.com"]'`;
|
|
3788
|
+
case "date": return `${base} "2024-01-15"`;
|
|
3789
|
+
case "year": return `${base} 2024`;
|
|
3790
|
+
case "table":
|
|
3791
|
+
if (field.columns.length > 0) {
|
|
3792
|
+
const example = Object.fromEntries(field.columns.map((c) => [c.id, `example_${c.type}`]));
|
|
3793
|
+
return `${base} --append '${JSON.stringify(example)}'`;
|
|
3794
|
+
}
|
|
3795
|
+
return `${base} --append '{}'`;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
/**
|
|
3799
|
+
* Generate a skip example for optional fields, or null for required fields.
|
|
3800
|
+
*/
|
|
3801
|
+
function generateSkipExample(file, fieldId, required) {
|
|
3802
|
+
if (required) return null;
|
|
3803
|
+
return `markform set ${file} ${fieldId} --skip --reason "Not applicable"`;
|
|
3804
|
+
}
|
|
3805
|
+
/**
|
|
3806
|
+
* Convert a FieldValue to a serializable current_value for the report.
|
|
3807
|
+
*/
|
|
3808
|
+
function serializeCurrentValue(value) {
|
|
3809
|
+
switch (value.kind) {
|
|
3810
|
+
case "string":
|
|
3811
|
+
case "url":
|
|
3812
|
+
case "date": return value.value;
|
|
3813
|
+
case "number":
|
|
3814
|
+
case "year": return value.value;
|
|
3815
|
+
case "string_list":
|
|
3816
|
+
case "url_list": return value.items;
|
|
3817
|
+
case "single_select": return value.selected;
|
|
3818
|
+
case "multi_select": return value.selected;
|
|
3819
|
+
case "checkboxes": return value.values;
|
|
3820
|
+
case "table": return value.rows;
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
/**
|
|
3824
|
+
* Enrich an InspectIssue into a NextIssue with field metadata and examples.
|
|
3825
|
+
*/
|
|
3826
|
+
function enrichIssue(issue, form, file) {
|
|
3827
|
+
const fieldId = getFieldIdFromRef(issue.ref, issue.scope);
|
|
3828
|
+
const field = fieldId ? findFieldById(form, fieldId) : void 0;
|
|
3829
|
+
const enriched = {
|
|
3830
|
+
ref: issue.ref,
|
|
3831
|
+
scope: issue.scope,
|
|
3832
|
+
reason: issue.reason,
|
|
3833
|
+
message: issue.message,
|
|
3834
|
+
severity: issue.severity,
|
|
3835
|
+
priority: issue.priority,
|
|
3836
|
+
set_example: field ? generateSetExample(file, field) : `markform set ${file} ${issue.ref} "value"`,
|
|
3837
|
+
skip_example: field ? generateSkipExample(file, field.id, field.required) : null
|
|
3838
|
+
};
|
|
3839
|
+
if (field) {
|
|
3840
|
+
enriched.field = buildFieldMeta(field);
|
|
3841
|
+
const response = form.responsesByFieldId[field.id];
|
|
3842
|
+
if (response?.state === "answered" && response.value) enriched.current_value = serializeCurrentValue(response.value);
|
|
3843
|
+
}
|
|
3844
|
+
return enriched;
|
|
3845
|
+
}
|
|
3846
|
+
/**
|
|
3847
|
+
* Format next report for console output.
|
|
3848
|
+
*/
|
|
3849
|
+
function formatConsoleReport$3(report, useColors) {
|
|
3850
|
+
const lines = [];
|
|
3851
|
+
const bold = useColors ? pc.bold : (s) => s;
|
|
3852
|
+
const dim = useColors ? pc.dim : (s) => s;
|
|
3853
|
+
const cyan = useColors ? pc.cyan : (s) => s;
|
|
3854
|
+
const green = useColors ? pc.green : (s) => s;
|
|
3855
|
+
const yellow = useColors ? pc.yellow : (s) => s;
|
|
3856
|
+
const red = useColors ? pc.red : (s) => s;
|
|
3857
|
+
const stateColor = report.form_state === "complete" ? green : report.form_state === "invalid" ? red : yellow;
|
|
3858
|
+
const progressStr = `${report.progress.filled_fields}/${report.progress.total_fields} fields filled, ${report.progress.empty_required_fields} required remaining`;
|
|
3859
|
+
lines.push(`${bold("State:")} ${stateColor(report.form_state)} ${dim(`(${progressStr})`)}`);
|
|
3860
|
+
if (report.is_complete) {
|
|
3861
|
+
lines.push("");
|
|
3862
|
+
lines.push(green(bold("Form is complete!")));
|
|
3863
|
+
return lines.join("\n");
|
|
3864
|
+
}
|
|
3865
|
+
lines.push("");
|
|
3866
|
+
lines.push(bold(`Next fields to fill (${report.issues.length} issues, budget: ${report.step_budget}):`));
|
|
3867
|
+
lines.push("");
|
|
3868
|
+
for (const issue of report.issues) {
|
|
3869
|
+
const prioLabel = `P${issue.priority}`;
|
|
3870
|
+
const prioColor = issue.priority <= 1 ? red : issue.priority <= 2 ? yellow : cyan;
|
|
3871
|
+
const sevLabel = issue.severity === "required" ? "required" : "recommended";
|
|
3872
|
+
const kindStr = issue.field ? ` ${dim(`(${issue.field.kind})`)}` : "";
|
|
3873
|
+
let optionsStr = "";
|
|
3874
|
+
if (issue.field?.options && issue.field.options.length > 0) optionsStr = ` ${dim(`[${issue.field.options.join(", ")}]`)}`;
|
|
3875
|
+
lines.push(` ${prioColor(prioLabel)} ${dim(`[${sevLabel}]`)} ${bold(issue.ref)}${kindStr}${optionsStr}`);
|
|
3876
|
+
lines.push(` ${issue.message}`);
|
|
3877
|
+
lines.push(` ${dim("->")} ${cyan(issue.set_example)}`);
|
|
3878
|
+
if (issue.skip_example) lines.push(` ${dim("->")} ${dim(issue.skip_example)}`);
|
|
3879
|
+
lines.push("");
|
|
3880
|
+
}
|
|
3881
|
+
return lines.join("\n").trimEnd();
|
|
3882
|
+
}
|
|
3883
|
+
const DEFAULT_MAX_ISSUES = 10;
|
|
3884
|
+
/**
|
|
3885
|
+
* Register the next command.
|
|
3886
|
+
*/
|
|
3887
|
+
function registerNextCommand(program) {
|
|
3888
|
+
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) => {
|
|
3889
|
+
const ctx = getCommandContext(cmd);
|
|
3890
|
+
try {
|
|
3891
|
+
let targetRoles;
|
|
3892
|
+
if (options.roles) try {
|
|
3893
|
+
targetRoles = parseRolesFlag(options.roles);
|
|
3894
|
+
if (targetRoles.includes("*")) targetRoles = void 0;
|
|
3895
|
+
} catch (error) {
|
|
3896
|
+
logError(`Invalid --roles: ${error instanceof Error ? error.message : String(error)}`);
|
|
3897
|
+
process.exit(1);
|
|
3898
|
+
}
|
|
3899
|
+
const maxFields = options.maxFields ? parseInt(options.maxFields, 10) : void 0;
|
|
3900
|
+
const maxGroups = options.maxGroups ? parseInt(options.maxGroups, 10) : void 0;
|
|
3901
|
+
const maxIssuesParsed = options.maxIssues ? parseInt(options.maxIssues, 10) : void 0;
|
|
3902
|
+
if (maxFields !== void 0 && isNaN(maxFields) || maxGroups !== void 0 && isNaN(maxGroups) || maxIssuesParsed !== void 0 && isNaN(maxIssuesParsed)) {
|
|
3903
|
+
logError("--max-fields, --max-groups, and --max-issues must be numeric");
|
|
3904
|
+
process.exit(1);
|
|
3905
|
+
}
|
|
3906
|
+
logVerbose(ctx, `Reading file: ${file}`);
|
|
3907
|
+
const content = await readFile$1(file);
|
|
3908
|
+
logVerbose(ctx, "Parsing form...");
|
|
3909
|
+
const form = parseForm(content);
|
|
3910
|
+
const harnessConfig = form.metadata?.harnessConfig;
|
|
3911
|
+
const effectiveMaxIssues = maxIssuesParsed ?? harnessConfig?.maxIssuesPerTurn ?? DEFAULT_MAX_ISSUES;
|
|
3912
|
+
const effectiveMaxFields = maxFields ?? void 0;
|
|
3913
|
+
const effectiveMaxGroups = maxGroups ?? void 0;
|
|
3914
|
+
logVerbose(ctx, "Running inspection...");
|
|
3915
|
+
const result = inspect(form, { targetRoles });
|
|
3916
|
+
const limitedIssues = filterIssuesByScope(filterIssuesByOrder(result.issues, form), form, effectiveMaxFields, effectiveMaxGroups).slice(0, effectiveMaxIssues);
|
|
3917
|
+
const maxPatches = harnessConfig?.maxPatchesPerTurn ?? limitedIssues.length;
|
|
3918
|
+
const stepBudget = Math.min(maxPatches, limitedIssues.length);
|
|
3919
|
+
const enrichedIssues = limitedIssues.map((issue) => enrichIssue(issue, form, file));
|
|
3920
|
+
const counts = result.progressSummary.counts;
|
|
3921
|
+
const output = formatOutput(ctx, {
|
|
3922
|
+
is_complete: result.isComplete,
|
|
3923
|
+
form_state: result.formState,
|
|
3924
|
+
step_budget: stepBudget,
|
|
3925
|
+
progress: {
|
|
3926
|
+
total_fields: counts.totalFields,
|
|
3927
|
+
required_fields: counts.requiredFields,
|
|
3928
|
+
filled_fields: counts.filledFields,
|
|
3929
|
+
empty_required_fields: counts.emptyRequiredFields
|
|
3930
|
+
},
|
|
3931
|
+
issues: enrichedIssues
|
|
3932
|
+
}, (data, useColors) => formatConsoleReport$3(data, useColors));
|
|
3933
|
+
console.log(output);
|
|
3934
|
+
} catch (error) {
|
|
3935
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
3936
|
+
process.exit(1);
|
|
3937
|
+
}
|
|
3938
|
+
});
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3730
3941
|
//#endregion
|
|
3731
3942
|
//#region src/cli/commands/plan.ts
|
|
3732
3943
|
/**
|
|
@@ -3930,13 +4141,18 @@ function openBrowser(url) {
|
|
|
3930
4141
|
/**
|
|
3931
4142
|
* Build tabs for a form file.
|
|
3932
4143
|
* All tabs are always present - content is generated dynamically from the form.
|
|
3933
|
-
* Tab order:
|
|
4144
|
+
* Tab order: Form, Report, Edit, Source, Values, Schema, Fill Record (if sidecar exists)
|
|
3934
4145
|
*/
|
|
3935
4146
|
function buildFormTabs(formPath) {
|
|
3936
4147
|
const tabs = [
|
|
3937
4148
|
{
|
|
3938
4149
|
id: "view",
|
|
3939
|
-
label: "
|
|
4150
|
+
label: "Form",
|
|
4151
|
+
path: null
|
|
4152
|
+
},
|
|
4153
|
+
{
|
|
4154
|
+
id: "report",
|
|
4155
|
+
label: "Report",
|
|
3940
4156
|
path: null
|
|
3941
4157
|
},
|
|
3942
4158
|
{
|
|
@@ -3949,11 +4165,6 @@ function buildFormTabs(formPath) {
|
|
|
3949
4165
|
label: "Source",
|
|
3950
4166
|
path: formPath
|
|
3951
4167
|
},
|
|
3952
|
-
{
|
|
3953
|
-
id: "report",
|
|
3954
|
-
label: "Report",
|
|
3955
|
-
path: null
|
|
3956
|
-
},
|
|
3957
4168
|
{
|
|
3958
4169
|
id: "values",
|
|
3959
4170
|
label: "Values",
|
|
@@ -4326,7 +4537,7 @@ async function handleSave(req, res, form, filePath, ctx, updateForm) {
|
|
|
4326
4537
|
}));
|
|
4327
4538
|
return;
|
|
4328
4539
|
}
|
|
4329
|
-
await writeFile(newPath, content);
|
|
4540
|
+
await writeFile$1(newPath, content);
|
|
4330
4541
|
logInfo(ctx, pc.green(`Saved to: ${newPath}`));
|
|
4331
4542
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4332
4543
|
res.end(JSON.stringify({
|
|
@@ -4882,10 +5093,10 @@ function renderFormHtml(form, tabs) {
|
|
|
4882
5093
|
if (response.ok) {
|
|
4883
5094
|
tabCache[tabId] = await response.text();
|
|
4884
5095
|
} else {
|
|
4885
|
-
tabCache[tabId] = '<div class="error">
|
|
5096
|
+
tabCache[tabId] = '<div class="error">No content available for this tab.</div>';
|
|
4886
5097
|
}
|
|
4887
5098
|
} catch (err) {
|
|
4888
|
-
tabCache[tabId] = '<div class="error">
|
|
5099
|
+
tabCache[tabId] = '<div class="error">No content available for this tab.</div>';
|
|
4889
5100
|
}
|
|
4890
5101
|
}
|
|
4891
5102
|
tabViewContent.innerHTML = tabCache[tabId];
|
|
@@ -4902,10 +5113,10 @@ function renderFormHtml(form, tabs) {
|
|
|
4902
5113
|
if (response.ok) {
|
|
4903
5114
|
tabCache[tabId] = await response.text();
|
|
4904
5115
|
} else {
|
|
4905
|
-
tabCache[tabId] = '<div class="error">
|
|
5116
|
+
tabCache[tabId] = '<div class="error">No content available for this tab.</div>';
|
|
4906
5117
|
}
|
|
4907
5118
|
} catch (err) {
|
|
4908
|
-
tabCache[tabId] = '<div class="error">
|
|
5119
|
+
tabCache[tabId] = '<div class="error">No content available for this tab.</div>';
|
|
4909
5120
|
}
|
|
4910
5121
|
}
|
|
4911
5122
|
tabOtherContent.innerHTML = tabCache[tabId];
|
|
@@ -4913,20 +5124,34 @@ function renderFormHtml(form, tabs) {
|
|
|
4913
5124
|
}
|
|
4914
5125
|
}
|
|
4915
5126
|
|
|
5127
|
+
// Navigate to a tab by id, updating the button state and hash
|
|
5128
|
+
let navigating = false;
|
|
5129
|
+
async function navigateToTab(tabId) {
|
|
5130
|
+
navigating = true;
|
|
5131
|
+
tabButtons.forEach(b => b.classList.remove('active'));
|
|
5132
|
+
const btn = document.querySelector('[data-tab="' + tabId + '"]');
|
|
5133
|
+
if (btn) btn.classList.add('active');
|
|
5134
|
+
location.hash = tabId;
|
|
5135
|
+
await showTab(tabId);
|
|
5136
|
+
navigating = false;
|
|
5137
|
+
}
|
|
5138
|
+
|
|
4916
5139
|
tabButtons.forEach(btn => {
|
|
4917
5140
|
btn.addEventListener('click', async () => {
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
// Update active button
|
|
4921
|
-
tabButtons.forEach(b => b.classList.remove('active'));
|
|
4922
|
-
btn.classList.add('active');
|
|
4923
|
-
|
|
4924
|
-
await showTab(tabId);
|
|
5141
|
+
await navigateToTab(btn.dataset.tab);
|
|
4925
5142
|
});
|
|
4926
5143
|
});
|
|
4927
5144
|
|
|
4928
|
-
//
|
|
4929
|
-
|
|
5145
|
+
// Handle hash-based navigation (back/forward, direct URL)
|
|
5146
|
+
window.addEventListener('hashchange', () => {
|
|
5147
|
+
if (navigating) return;
|
|
5148
|
+
const hash = window.location.hash.slice(1);
|
|
5149
|
+
if (hash) navigateToTab(hash);
|
|
5150
|
+
});
|
|
5151
|
+
|
|
5152
|
+
// Load initial tab from hash or default to Form (view) tab
|
|
5153
|
+
const initialTab = window.location.hash.slice(1) || 'view';
|
|
5154
|
+
navigateToTab(initialTab);
|
|
4930
5155
|
|
|
4931
5156
|
// URL copy tooltip functionality - initialize once
|
|
4932
5157
|
(function initUrlCopyTooltip() {
|
|
@@ -5523,7 +5748,7 @@ function registerRenderCommand(program) {
|
|
|
5523
5748
|
logDryRun(`Would write HTML to: ${outputPath}`);
|
|
5524
5749
|
return;
|
|
5525
5750
|
}
|
|
5526
|
-
await writeFile(outputPath, html);
|
|
5751
|
+
await writeFile$1(outputPath, html);
|
|
5527
5752
|
logSuccess(ctx, pc.green(`✓ Rendered to ${outputPath}`));
|
|
5528
5753
|
} catch (error) {
|
|
5529
5754
|
logError(error instanceof Error ? error.message : String(error));
|
|
@@ -5685,9 +5910,9 @@ function registerResearchCommand(program) {
|
|
|
5685
5910
|
console.log(` ${formPath} ${pc.dim("(filled markform source)")}`);
|
|
5686
5911
|
console.log(` ${schemaPath} ${pc.dim("(JSON Schema)")}`);
|
|
5687
5912
|
if (options.transcript && result.transcript) {
|
|
5688
|
-
const { serializeSession } = await import("./session-
|
|
5913
|
+
const { serializeSession } = await import("./session-DHyTMP67.mjs");
|
|
5689
5914
|
const transcriptPath = outputPath.replace(/\.form\.md$/, ".session.yaml");
|
|
5690
|
-
const { writeFile } = await import("./shared-
|
|
5915
|
+
const { writeFile } = await import("./shared-BLh342F5.mjs");
|
|
5691
5916
|
await writeFile(transcriptPath, serializeSession(result.transcript));
|
|
5692
5917
|
logInfo(ctx, `Transcript: ${transcriptPath}`);
|
|
5693
5918
|
}
|
|
@@ -5734,6 +5959,354 @@ function registerSchemaCommand(program) {
|
|
|
5734
5959
|
});
|
|
5735
5960
|
}
|
|
5736
5961
|
|
|
5962
|
+
//#endregion
|
|
5963
|
+
//#region src/cli/commands/set.ts
|
|
5964
|
+
/**
|
|
5965
|
+
* Parse a CLI value string into a RawFieldValue.
|
|
5966
|
+
*
|
|
5967
|
+
* Only detects JSON arrays/objects ([ or { prefix). Everything else
|
|
5968
|
+
* is passed through as a string — the coercion layer handles
|
|
5969
|
+
* type conversion based on the field's kind.
|
|
5970
|
+
*/
|
|
5971
|
+
function parseCliValue(raw) {
|
|
5972
|
+
if (raw.startsWith("[") || raw.startsWith("{")) try {
|
|
5973
|
+
return JSON.parse(raw);
|
|
5974
|
+
} catch {
|
|
5975
|
+
return raw;
|
|
5976
|
+
}
|
|
5977
|
+
return raw;
|
|
5978
|
+
}
|
|
5979
|
+
/**
|
|
5980
|
+
* Format set report for console output.
|
|
5981
|
+
*/
|
|
5982
|
+
function formatConsoleReport$2(report, useColors) {
|
|
5983
|
+
const lines = [];
|
|
5984
|
+
const bold = useColors ? pc.bold : (s) => s;
|
|
5985
|
+
const dim = useColors ? pc.dim : (s) => s;
|
|
5986
|
+
const cyan = useColors ? pc.cyan : (s) => s;
|
|
5987
|
+
const green = useColors ? pc.green : (s) => s;
|
|
5988
|
+
const red = useColors ? pc.red : (s) => s;
|
|
5989
|
+
lines.push(bold(cyan("Set Result")));
|
|
5990
|
+
lines.push("");
|
|
5991
|
+
const statusColor = report.apply_status === "applied" ? green : red;
|
|
5992
|
+
lines.push(`${bold("Status:")} ${statusColor(report.apply_status)}`);
|
|
5993
|
+
lines.push(`${bold("Form State:")} ${report.form_state}`);
|
|
5994
|
+
lines.push(`${bold("Complete:")} ${report.is_complete ? green("yes") : dim("no")}`);
|
|
5995
|
+
lines.push("");
|
|
5996
|
+
const counts = report.progress.counts;
|
|
5997
|
+
lines.push(bold("Progress:"));
|
|
5998
|
+
lines.push(` Total fields: ${counts.totalFields}`);
|
|
5999
|
+
lines.push(` Filled: ${counts.filledFields}, Empty: ${counts.emptyFields}`);
|
|
6000
|
+
lines.push(` Empty required: ${counts.emptyRequiredFields}`);
|
|
6001
|
+
lines.push("");
|
|
6002
|
+
if (report.issues.length > 0) {
|
|
6003
|
+
lines.push(bold(`Issues (${report.issues.length}):`));
|
|
6004
|
+
for (const issue of report.issues) lines.push(` P${issue.priority} ${dim(issue.ref)}: ${issue.message}`);
|
|
6005
|
+
} else lines.push(dim("No issues."));
|
|
6006
|
+
return lines.join("\n");
|
|
6007
|
+
}
|
|
6008
|
+
/**
|
|
6009
|
+
* Register the set command.
|
|
6010
|
+
*/
|
|
6011
|
+
function registerSetCommand(program) {
|
|
6012
|
+
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) => {
|
|
6013
|
+
const ctx = getCommandContext(cmd);
|
|
6014
|
+
try {
|
|
6015
|
+
logVerbose(ctx, `Reading file: ${file}`);
|
|
6016
|
+
const content = await readFile$1(file);
|
|
6017
|
+
logVerbose(ctx, "Parsing form...");
|
|
6018
|
+
const form = parseForm(content);
|
|
6019
|
+
let patches;
|
|
6020
|
+
if (options.values) {
|
|
6021
|
+
if (fieldId) {
|
|
6022
|
+
logError("Cannot use --values with positional fieldId/value arguments");
|
|
6023
|
+
process.exit(1);
|
|
6024
|
+
}
|
|
6025
|
+
if (options.clear || options.skip || options.abort || options.append !== void 0 || options.delete !== void 0) {
|
|
6026
|
+
logError("Cannot use --values with --clear, --skip, --abort, --append, or --delete");
|
|
6027
|
+
process.exit(1);
|
|
6028
|
+
}
|
|
6029
|
+
let inputContext;
|
|
6030
|
+
try {
|
|
6031
|
+
inputContext = JSON.parse(options.values);
|
|
6032
|
+
} catch {
|
|
6033
|
+
logError("Invalid JSON in --values option");
|
|
6034
|
+
process.exit(1);
|
|
6035
|
+
}
|
|
6036
|
+
if (typeof inputContext !== "object" || Array.isArray(inputContext)) {
|
|
6037
|
+
logError("--values must be a JSON object");
|
|
6038
|
+
process.exit(1);
|
|
6039
|
+
}
|
|
6040
|
+
const result = coerceInputContext(form, inputContext);
|
|
6041
|
+
for (const w of result.warnings) logVerbose(ctx, `Warning: ${w}`);
|
|
6042
|
+
if (result.errors.length > 0) {
|
|
6043
|
+
for (const e of result.errors) logError(e);
|
|
6044
|
+
process.exit(1);
|
|
6045
|
+
}
|
|
6046
|
+
patches = result.patches;
|
|
6047
|
+
} else if (!fieldId) {
|
|
6048
|
+
logError("Either <fieldId> or --values is required");
|
|
6049
|
+
process.exit(1);
|
|
6050
|
+
} else if (options.clear) {
|
|
6051
|
+
if (!findFieldById(form, fieldId)) {
|
|
6052
|
+
logError(`Field "${fieldId}" not found in form`);
|
|
6053
|
+
process.exit(1);
|
|
6054
|
+
}
|
|
6055
|
+
patches = [{
|
|
6056
|
+
op: "clear_field",
|
|
6057
|
+
fieldId
|
|
6058
|
+
}];
|
|
6059
|
+
} else if (options.skip) {
|
|
6060
|
+
if (!findFieldById(form, fieldId)) {
|
|
6061
|
+
logError(`Field "${fieldId}" not found in form`);
|
|
6062
|
+
process.exit(1);
|
|
6063
|
+
}
|
|
6064
|
+
patches = [{
|
|
6065
|
+
op: "skip_field",
|
|
6066
|
+
fieldId,
|
|
6067
|
+
role: options.role,
|
|
6068
|
+
...options.reason && { reason: options.reason }
|
|
6069
|
+
}];
|
|
6070
|
+
} else if (options.abort) {
|
|
6071
|
+
if (!findFieldById(form, fieldId)) {
|
|
6072
|
+
logError(`Field "${fieldId}" not found in form`);
|
|
6073
|
+
process.exit(1);
|
|
6074
|
+
}
|
|
6075
|
+
patches = [{
|
|
6076
|
+
op: "abort_field",
|
|
6077
|
+
fieldId,
|
|
6078
|
+
role: options.role,
|
|
6079
|
+
...options.reason && { reason: options.reason }
|
|
6080
|
+
}];
|
|
6081
|
+
} else if (options.append !== void 0) {
|
|
6082
|
+
const field = findFieldById(form, fieldId);
|
|
6083
|
+
if (!field) {
|
|
6084
|
+
logError(`Field "${fieldId}" not found in form`);
|
|
6085
|
+
process.exit(1);
|
|
6086
|
+
}
|
|
6087
|
+
const rawValue = parseCliValue(options.append);
|
|
6088
|
+
if (field.kind === "table") {
|
|
6089
|
+
const rows = Array.isArray(rawValue) ? rawValue : typeof rawValue === "object" && rawValue !== null ? [rawValue] : null;
|
|
6090
|
+
if (!rows) {
|
|
6091
|
+
logError(`--append for table field "${fieldId}" requires a JSON object or array of row objects`);
|
|
6092
|
+
process.exit(1);
|
|
6093
|
+
}
|
|
6094
|
+
patches = [{
|
|
6095
|
+
op: "append_table",
|
|
6096
|
+
fieldId,
|
|
6097
|
+
value: rows
|
|
6098
|
+
}];
|
|
6099
|
+
} else if (field.kind === "string_list") patches = [{
|
|
6100
|
+
op: "append_string_list",
|
|
6101
|
+
fieldId,
|
|
6102
|
+
value: Array.isArray(rawValue) ? rawValue : [typeof rawValue === "string" ? rawValue : JSON.stringify(rawValue)]
|
|
6103
|
+
}];
|
|
6104
|
+
else if (field.kind === "url_list") patches = [{
|
|
6105
|
+
op: "append_url_list",
|
|
6106
|
+
fieldId,
|
|
6107
|
+
value: Array.isArray(rawValue) ? rawValue : [typeof rawValue === "string" ? rawValue : JSON.stringify(rawValue)]
|
|
6108
|
+
}];
|
|
6109
|
+
else {
|
|
6110
|
+
logError(`--append is not supported for ${field.kind} fields (only table, string_list, url_list)`);
|
|
6111
|
+
process.exit(1);
|
|
6112
|
+
}
|
|
6113
|
+
} else if (options.delete !== void 0) {
|
|
6114
|
+
const field = findFieldById(form, fieldId);
|
|
6115
|
+
if (!field) {
|
|
6116
|
+
logError(`Field "${fieldId}" not found in form`);
|
|
6117
|
+
process.exit(1);
|
|
6118
|
+
}
|
|
6119
|
+
const index = parseInt(options.delete, 10);
|
|
6120
|
+
if (isNaN(index) || index < 0) {
|
|
6121
|
+
logError(`--delete requires a non-negative integer index, got "${options.delete}"`);
|
|
6122
|
+
process.exit(1);
|
|
6123
|
+
}
|
|
6124
|
+
if (field.kind === "table") patches = [{
|
|
6125
|
+
op: "delete_table",
|
|
6126
|
+
fieldId,
|
|
6127
|
+
value: index
|
|
6128
|
+
}];
|
|
6129
|
+
else if (field.kind === "string_list") patches = [{
|
|
6130
|
+
op: "delete_string_list",
|
|
6131
|
+
fieldId,
|
|
6132
|
+
value: index
|
|
6133
|
+
}];
|
|
6134
|
+
else if (field.kind === "url_list") patches = [{
|
|
6135
|
+
op: "delete_url_list",
|
|
6136
|
+
fieldId,
|
|
6137
|
+
value: index
|
|
6138
|
+
}];
|
|
6139
|
+
else {
|
|
6140
|
+
logError(`--delete is not supported for ${field.kind} fields (only table, string_list, url_list)`);
|
|
6141
|
+
process.exit(1);
|
|
6142
|
+
}
|
|
6143
|
+
} else if (value !== void 0) {
|
|
6144
|
+
const result = coerceToFieldPatch(form, fieldId, parseCliValue(value));
|
|
6145
|
+
if (!result.ok) {
|
|
6146
|
+
logError(result.error);
|
|
6147
|
+
process.exit(1);
|
|
6148
|
+
}
|
|
6149
|
+
if ("warning" in result && result.warning) logVerbose(ctx, `Warning: ${result.warning}`);
|
|
6150
|
+
patches = [result.patch];
|
|
6151
|
+
} else {
|
|
6152
|
+
logError("No value provided. Use <value>, --clear, --skip, --abort, --append, or --delete");
|
|
6153
|
+
process.exit(1);
|
|
6154
|
+
}
|
|
6155
|
+
if (ctx.dryRun) {
|
|
6156
|
+
logDryRun(`Would apply ${patches.length} patches to ${file}`, { patches });
|
|
6157
|
+
return;
|
|
6158
|
+
}
|
|
6159
|
+
logVerbose(ctx, `Applying ${patches.length} patches...`);
|
|
6160
|
+
const applyResult = applyPatches(form, patches);
|
|
6161
|
+
if (applyResult.applyStatus === "rejected") {
|
|
6162
|
+
logError("Patches rejected");
|
|
6163
|
+
for (const rp of applyResult.rejectedPatches) logError(` ${rp.message}`);
|
|
6164
|
+
process.exit(1);
|
|
6165
|
+
}
|
|
6166
|
+
if (!(Boolean(options.clear) || Boolean(options.skip) || Boolean(options.abort) || options.delete !== void 0)) {
|
|
6167
|
+
const patchedFieldIds = new Set(patches.map((p) => "fieldId" in p ? p.fieldId : "").filter(Boolean));
|
|
6168
|
+
const relevantIssues = applyResult.issues.filter((i) => i.reason === "validation_error" && patchedFieldIds.has(i.ref));
|
|
6169
|
+
for (const issue of relevantIssues) logWarn(ctx, issue.message);
|
|
6170
|
+
}
|
|
6171
|
+
if (options.report) {
|
|
6172
|
+
const output = formatOutput(ctx, {
|
|
6173
|
+
apply_status: applyResult.applyStatus,
|
|
6174
|
+
form_state: applyResult.formState,
|
|
6175
|
+
is_complete: applyResult.isComplete,
|
|
6176
|
+
structure: applyResult.structureSummary,
|
|
6177
|
+
progress: applyResult.progressSummary,
|
|
6178
|
+
issues: applyResult.issues
|
|
6179
|
+
}, (data, useColors) => formatConsoleReport$2(data, useColors));
|
|
6180
|
+
if (options.output) {
|
|
6181
|
+
await writeFile$1(options.output, output);
|
|
6182
|
+
logSuccess(ctx, `Report written to ${options.output}`);
|
|
6183
|
+
} else console.log(output);
|
|
6184
|
+
} else {
|
|
6185
|
+
const output = serializeForm(form, { preserveContent: !options.normalize });
|
|
6186
|
+
const target = options.output ?? file;
|
|
6187
|
+
await writeFile$1(target, output);
|
|
6188
|
+
logSuccess(ctx, `Form updated: ${target}`);
|
|
6189
|
+
}
|
|
6190
|
+
} catch (error) {
|
|
6191
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
6192
|
+
process.exit(1);
|
|
6193
|
+
}
|
|
6194
|
+
});
|
|
6195
|
+
}
|
|
6196
|
+
|
|
6197
|
+
//#endregion
|
|
6198
|
+
//#region src/cli/commands/setup.ts
|
|
6199
|
+
const DO_NOT_EDIT_MARKER = `<!-- DO NOT EDIT: Generated by markform setup.
|
|
6200
|
+
Run 'markform setup' to update.
|
|
6201
|
+
-->`;
|
|
6202
|
+
/**
|
|
6203
|
+
* Load SKILL.md content and insert the DO NOT EDIT marker after frontmatter.
|
|
6204
|
+
*/
|
|
6205
|
+
function loadSkillWithMarker() {
|
|
6206
|
+
const lines = readFileSync(resolvePackagePath(import.meta.url, "docs/skill/SKILL.md"), "utf-8").split("\n");
|
|
6207
|
+
let endOfFrontmatter = -1;
|
|
6208
|
+
if (lines[0] === "---") {
|
|
6209
|
+
for (let i = 1; i < lines.length; i++) if (lines[i] === "---") {
|
|
6210
|
+
endOfFrontmatter = i;
|
|
6211
|
+
break;
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
if (endOfFrontmatter >= 0) lines.splice(endOfFrontmatter + 1, 0, "", DO_NOT_EDIT_MARKER);
|
|
6215
|
+
return lines.join("\n");
|
|
6216
|
+
}
|
|
6217
|
+
/**
|
|
6218
|
+
* Install the skill file to .claude/skills/markform/SKILL.md.
|
|
6219
|
+
*/
|
|
6220
|
+
async function installSkill(projectDir, ctx) {
|
|
6221
|
+
const skillDir = join(projectDir, ".claude", "skills", "markform");
|
|
6222
|
+
const skillPath = join(skillDir, "SKILL.md");
|
|
6223
|
+
const isUpdate = existsSync(skillPath);
|
|
6224
|
+
const content = loadSkillWithMarker();
|
|
6225
|
+
await mkdir(skillDir, { recursive: true });
|
|
6226
|
+
await writeFile(skillPath, content, "utf-8");
|
|
6227
|
+
logSuccess(ctx, `${isUpdate ? "Updated" : "Installed"} ${pc.bold(".claude/skills/markform/SKILL.md")}`);
|
|
6228
|
+
}
|
|
6229
|
+
/**
|
|
6230
|
+
* Run setup in auto mode (non-interactive, for agents).
|
|
6231
|
+
*/
|
|
6232
|
+
async function setupAuto(ctx) {
|
|
6233
|
+
logInfo(ctx, "Installing Markform skill for Claude Code...");
|
|
6234
|
+
await installSkill(process.cwd(), ctx);
|
|
6235
|
+
logInfo(ctx, `Run ${pc.cyan("markform skill")} to view the installed skill content.`);
|
|
6236
|
+
}
|
|
6237
|
+
/**
|
|
6238
|
+
* Run setup in interactive mode (guided, for humans).
|
|
6239
|
+
*/
|
|
6240
|
+
async function setupInteractive(ctx) {
|
|
6241
|
+
p.intro(pc.bgCyan(pc.black(" markform setup ")));
|
|
6242
|
+
p.log.info([
|
|
6243
|
+
"This will install Markform as a Claude Code skill in your project.",
|
|
6244
|
+
"",
|
|
6245
|
+
` ${pc.dim("Creates:")} .claude/skills/markform/SKILL.md`,
|
|
6246
|
+
"",
|
|
6247
|
+
"This teaches Claude Code how to use markform commands when working",
|
|
6248
|
+
"with .form.md files in this project."
|
|
6249
|
+
].join("\n"));
|
|
6250
|
+
const shouldContinue = await p.confirm({
|
|
6251
|
+
message: "Install the Markform skill?",
|
|
6252
|
+
initialValue: true
|
|
6253
|
+
});
|
|
6254
|
+
if (p.isCancel(shouldContinue) || !shouldContinue) {
|
|
6255
|
+
p.outro("Setup cancelled.");
|
|
6256
|
+
return;
|
|
6257
|
+
}
|
|
6258
|
+
await installSkill(process.cwd(), ctx);
|
|
6259
|
+
p.outro("Markform skill installed! Claude Code will now know how to use markform.");
|
|
6260
|
+
}
|
|
6261
|
+
/**
|
|
6262
|
+
* Register the setup command.
|
|
6263
|
+
*/
|
|
6264
|
+
function registerSetupCommand(program) {
|
|
6265
|
+
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) => {
|
|
6266
|
+
const ctx = getCommandContext(command);
|
|
6267
|
+
try {
|
|
6268
|
+
if (!options.auto && !options.interactive) {
|
|
6269
|
+
cmd.help();
|
|
6270
|
+
return;
|
|
6271
|
+
}
|
|
6272
|
+
if (options.auto) await setupAuto(ctx);
|
|
6273
|
+
else await setupInteractive(ctx);
|
|
6274
|
+
} catch (error) {
|
|
6275
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
6276
|
+
process.exit(1);
|
|
6277
|
+
}
|
|
6278
|
+
});
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
//#endregion
|
|
6282
|
+
//#region src/cli/commands/skill.ts
|
|
6283
|
+
/**
|
|
6284
|
+
* Load the SKILL.md content.
|
|
6285
|
+
*/
|
|
6286
|
+
function loadSkillContent() {
|
|
6287
|
+
const skillPath = resolvePackagePath(import.meta.url, "docs/skill/SKILL.md");
|
|
6288
|
+
try {
|
|
6289
|
+
return readFileSync(skillPath, "utf-8");
|
|
6290
|
+
} catch (error) {
|
|
6291
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6292
|
+
throw new Error(`Failed to load SKILL.md from ${skillPath}: ${message}`);
|
|
6293
|
+
}
|
|
6294
|
+
}
|
|
6295
|
+
/**
|
|
6296
|
+
* Register the skill command.
|
|
6297
|
+
*/
|
|
6298
|
+
function registerSkillCommand(program) {
|
|
6299
|
+
program.command("skill").description("Output SKILL.md content for Claude Code integration").action(() => {
|
|
6300
|
+
try {
|
|
6301
|
+
const content = loadSkillContent();
|
|
6302
|
+
process.stdout.write(content);
|
|
6303
|
+
} catch (error) {
|
|
6304
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
6305
|
+
process.exit(1);
|
|
6306
|
+
}
|
|
6307
|
+
});
|
|
6308
|
+
}
|
|
6309
|
+
|
|
5737
6310
|
//#endregion
|
|
5738
6311
|
//#region src/cli/commands/status.ts
|
|
5739
6312
|
/**
|
|
@@ -6039,7 +6612,7 @@ function registerValidateCommand(program) {
|
|
|
6039
6612
|
/**
|
|
6040
6613
|
* CLI implementation for markform.
|
|
6041
6614
|
*
|
|
6042
|
-
* Provides commands for inspecting,
|
|
6615
|
+
* Provides commands for inspecting, patching, exporting,
|
|
6043
6616
|
* serving, and running harness loops on .form.md files.
|
|
6044
6617
|
*/
|
|
6045
6618
|
/**
|
|
@@ -6047,6 +6620,7 @@ function registerValidateCommand(program) {
|
|
|
6047
6620
|
*/
|
|
6048
6621
|
function withColoredHelp(cmd) {
|
|
6049
6622
|
cmd.configureHelp({
|
|
6623
|
+
helpWidth: 88,
|
|
6050
6624
|
styleTitle: (str) => pc.bold(pc.cyan(str)),
|
|
6051
6625
|
styleCommandText: (str) => pc.green(str),
|
|
6052
6626
|
styleOptionText: (str) => pc.yellow(str),
|
|
@@ -6059,12 +6633,18 @@ function withColoredHelp(cmd) {
|
|
|
6059
6633
|
*/
|
|
6060
6634
|
function createProgram() {
|
|
6061
6635
|
const program = withColoredHelp(new Command());
|
|
6062
|
-
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)")
|
|
6636
|
+
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", `
|
|
6637
|
+
Skill Setup:
|
|
6638
|
+
To use Markform as a Claude Code skill, run: markform setup --auto
|
|
6639
|
+
To view the skill content: markform skill
|
|
6640
|
+
|
|
6641
|
+
Getting Started:
|
|
6642
|
+
npm install -g markform && markform setup --auto`);
|
|
6063
6643
|
registerReadmeCommand(program);
|
|
6064
6644
|
registerDocsCommand(program);
|
|
6065
6645
|
registerSpecCommand(program);
|
|
6066
6646
|
registerApisCommand(program);
|
|
6067
|
-
|
|
6647
|
+
registerPatchCommand(program);
|
|
6068
6648
|
registerBrowseCommand(program);
|
|
6069
6649
|
registerDumpCommand(program);
|
|
6070
6650
|
registerExamplesCommand(program);
|
|
@@ -6072,6 +6652,7 @@ function createProgram() {
|
|
|
6072
6652
|
registerFillCommand(program);
|
|
6073
6653
|
registerInspectCommand(program);
|
|
6074
6654
|
registerModelsCommand(program);
|
|
6655
|
+
registerNextCommand(program);
|
|
6075
6656
|
registerPlanCommand(program);
|
|
6076
6657
|
registerRenderCommand(program);
|
|
6077
6658
|
registerReportCommand(program);
|
|
@@ -6079,6 +6660,9 @@ function createProgram() {
|
|
|
6079
6660
|
registerRunCommand(program);
|
|
6080
6661
|
registerSchemaCommand(program);
|
|
6081
6662
|
registerServeCommand(program);
|
|
6663
|
+
registerSetCommand(program);
|
|
6664
|
+
registerSetupCommand(program);
|
|
6665
|
+
registerSkillCommand(program);
|
|
6082
6666
|
registerStatusCommand(program);
|
|
6083
6667
|
registerValidateCommand(program);
|
|
6084
6668
|
return program;
|
|
@@ -6092,4 +6676,4 @@ async function runCli() {
|
|
|
6092
6676
|
|
|
6093
6677
|
//#endregion
|
|
6094
6678
|
export { runCli as t };
|
|
6095
|
-
//# sourceMappingURL=cli-
|
|
6679
|
+
//# sourceMappingURL=cli-B1T8kMFt.mjs.map
|