svharness 0.15.14 → 0.15.15
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 +6 -0
- package/dist/commands/convert.js +31 -28
- package/dist/config/merge-options.js +8 -0
- package/dist/config/normalize.js +5 -1
- package/dist/core/doc-intake-paths.js +3 -1
- package/dist/core/markdown-heading-split.js +109 -0
- package/dist/core/markdown-post-split.js +153 -0
- package/dist/core/markdown-sheet-split.js +4 -46
- package/dist/core/markdown-split-common.js +84 -0
- package/dist/core/markdown-table-split.js +108 -0
- package/dist/index.js +18 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -553,6 +553,12 @@ svharness convert --verbose --yes
|
|
|
553
553
|
| `--force` | flag,覆盖已存在同名 `.md`(默认自动追加 `-1`、`-2` 后缀) | `false` |
|
|
554
554
|
| `--split-sheets-suffix <suffix>` | xlsx/xls 按 sheet 拆分的子目录后缀 | `_split` |
|
|
555
555
|
| `--no-split-sheets` | flag,不将 xlsx/xls 合并 md 按 `##` 拆分为多文件 | `false`(默认拆分) |
|
|
556
|
+
| `--no-split-headings` | flag,不对超大合并 md 按标题(默认 `##`)拆分 | `false`(默认拆分) |
|
|
557
|
+
| `--split-heading-level <n>` | 标题切分锚点级别(1–6) | `2` |
|
|
558
|
+
| `--split-min-lines <n>` | 触发标题切分的最小行数 | `800` |
|
|
559
|
+
| `--split-min-bytes <n>` | 触发标题切分的最小字节数 | `100000` |
|
|
560
|
+
| `--split-tables` | flag,将超大 GFM 表格按行切段(每段重复表头) | `false` |
|
|
561
|
+
| `--split-table-max-rows <n>` | 表格切段时每段最大数据行数 | `80` |
|
|
556
562
|
| `-y, --yes` | flag,跳过交互确认 | `false` |
|
|
557
563
|
| `--verbose` | flag,显示详细日志(含每文件 `converter` / `fallback_used` / endpoint) | `false` |
|
|
558
564
|
|
package/dist/commands/convert.js
CHANGED
|
@@ -29,7 +29,7 @@ const markitdown_client_1 = require("../core/markitdown-client");
|
|
|
29
29
|
const doc_intake_paths_1 = require("../core/doc-intake-paths");
|
|
30
30
|
const structure_scan_1 = require("../lib/requirements/structure-scan");
|
|
31
31
|
const markdown_table_cleanup_1 = require("../core/markdown-table-cleanup");
|
|
32
|
-
const
|
|
32
|
+
const markdown_post_split_1 = require("../core/markdown-post-split");
|
|
33
33
|
/**
|
|
34
34
|
* Extensions recognised by Microsoft MarkItDown (v0.x). Anything outside this
|
|
35
35
|
* whitelist is skipped client-side to avoid a round-trip that is guaranteed to
|
|
@@ -98,6 +98,14 @@ async function runConvert(opts) {
|
|
|
98
98
|
value: opts.splitSheets === false
|
|
99
99
|
? 'no'
|
|
100
100
|
: `yes (${opts.splitSheetsSuffix ?? '_split'})`,
|
|
101
|
+
}, {
|
|
102
|
+
label: 'split headings',
|
|
103
|
+
value: opts.splitHeadings === false
|
|
104
|
+
? 'no'
|
|
105
|
+
: `yes (##, min ${opts.splitMinLines ?? 800} lines / ${opts.splitMinBytes ?? 100_000}B)`,
|
|
106
|
+
}, {
|
|
107
|
+
label: 'split tables',
|
|
108
|
+
value: opts.splitTables ? `yes (max ${opts.splitTableMaxRows ?? 80} rows)` : 'no',
|
|
101
109
|
});
|
|
102
110
|
logger_1.logger.configBox('svharness convert', configRows);
|
|
103
111
|
// 1. Collect candidate files.
|
|
@@ -203,7 +211,7 @@ async function runConvert(opts) {
|
|
|
203
211
|
const item = queue.shift();
|
|
204
212
|
if (!item)
|
|
205
213
|
break;
|
|
206
|
-
results.push(await uploadOne(item.source, outDir, used, v, opts, activeEndpoints));
|
|
214
|
+
results.push(await uploadOne(item.source, outDir, used, v, opts, activeEndpoints, v.harnessRoot));
|
|
207
215
|
}
|
|
208
216
|
};
|
|
209
217
|
for (let i = 0; i < Math.min(v.concurrency, toUpload.length); i++) {
|
|
@@ -384,12 +392,10 @@ async function buildPlan(files, limitBytes) {
|
|
|
384
392
|
}
|
|
385
393
|
return out;
|
|
386
394
|
}
|
|
387
|
-
async function uploadOne(source, outDir, used, v, opts, activeEndpoints) {
|
|
395
|
+
async function uploadOne(source, outDir, used, v, opts, activeEndpoints, harnessRoot) {
|
|
388
396
|
const basename = node_path_1.default.basename(source, node_path_1.default.extname(source));
|
|
389
397
|
const outPath = await resolveOutputPath(outDir, basename, used, !!opts.force);
|
|
390
398
|
used.add(outPath);
|
|
391
|
-
const splitSheets = opts.splitSheets !== false;
|
|
392
|
-
const splitSuffix = opts.splitSheetsSuffix ?? '_split';
|
|
393
399
|
logger_1.logger.debug(`upload ${source} -> ${outPath}`);
|
|
394
400
|
try {
|
|
395
401
|
const resp = await (0, markitdown_client_1.postConvertWithEndpointFailover)(activeEndpoints, source, v.timeoutSec * 1000);
|
|
@@ -413,33 +419,30 @@ async function uploadOne(source, outDir, used, v, opts, activeEndpoints) {
|
|
|
413
419
|
}
|
|
414
420
|
await fs_extra_1.default.writeFile(outPath, markdown, 'utf8');
|
|
415
421
|
const bytes = Buffer.byteLength(markdown, 'utf8');
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
source,
|
|
427
|
-
output: outPath,
|
|
428
|
-
outputs,
|
|
429
|
-
status: 'ok',
|
|
430
|
-
bytes,
|
|
431
|
-
converter: resp.converter,
|
|
432
|
-
fallbackUsed: resp.fallback_used,
|
|
433
|
-
endpoint: resp.endpointUsed,
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
}
|
|
422
|
+
const splitResult = await (0, markdown_post_split_1.runPostConvertSplits)({
|
|
423
|
+
source,
|
|
424
|
+
outDir,
|
|
425
|
+
basename,
|
|
426
|
+
mergedMdPath: outPath,
|
|
427
|
+
markdown,
|
|
428
|
+
ext,
|
|
429
|
+
opts,
|
|
430
|
+
harnessRoot,
|
|
431
|
+
});
|
|
437
432
|
const convNote = resp.fallback_used && resp.converter ? ` [${resp.converter}]` : '';
|
|
438
|
-
|
|
433
|
+
const splitNote = splitResult.sheetSplitDone
|
|
434
|
+
? ` + sheet split`
|
|
435
|
+
: splitResult.headingSplitSections > 0
|
|
436
|
+
? ` + ${splitResult.headingSplitSections} heading sections`
|
|
437
|
+
: '';
|
|
438
|
+
const tableNote = splitResult.tableSplitParts > 0
|
|
439
|
+
? ` + ${splitResult.tableSplitParts} table parts`
|
|
440
|
+
: '';
|
|
441
|
+
logger_1.logger.success(`${node_path_1.default.basename(source)} -> ${node_path_1.default.basename(outPath)} (${bytes}B)${convNote}${splitNote}${tableNote}`);
|
|
439
442
|
return {
|
|
440
443
|
source,
|
|
441
444
|
output: outPath,
|
|
442
|
-
outputs,
|
|
445
|
+
outputs: splitResult.outputs,
|
|
443
446
|
status: 'ok',
|
|
444
447
|
bytes,
|
|
445
448
|
converter: resp.converter,
|
|
@@ -157,6 +157,14 @@ function mergeConvertOptions(cli, configSection, defaults, cmd) {
|
|
|
157
157
|
? false
|
|
158
158
|
: pickBool('splitSheets', cli.splitSheets, cfg.splitSheets, undefined, cmd),
|
|
159
159
|
splitSheetsSuffix: pickString('splitSheetsSuffix', cli.splitSheetsSuffix, cfg.splitSheetsSuffix, cmd),
|
|
160
|
+
splitHeadings: cli.noSplitHeadings
|
|
161
|
+
? false
|
|
162
|
+
: pickBool('splitHeadings', cli.splitHeadings, cfg.splitHeadings, undefined, cmd),
|
|
163
|
+
splitHeadingLevel: pickNumber('splitHeadingLevel', cli.splitHeadingLevel, cfg.splitHeadingLevel, cmd),
|
|
164
|
+
splitMinLines: pickNumber('splitMinLines', cli.splitMinLines, cfg.splitMinLines, cmd),
|
|
165
|
+
splitMinBytes: pickNumber('splitMinBytes', cli.splitMinBytes, cfg.splitMinBytes, cmd),
|
|
166
|
+
splitTables: pickBool('splitTables', cli.splitTables, cfg.splitTables, undefined, cmd),
|
|
167
|
+
splitTableMaxRows: pickNumber('splitTableMaxRows', cli.splitTableMaxRows, cfg.splitTableMaxRows, cmd),
|
|
160
168
|
noStructureScan: pickBool('noStructureScan', cli.noStructureScan, cfg.noStructureScan, undefined, cmd),
|
|
161
169
|
yes: pickBool('yes', cli.yes, cfg.yes, defaults?.yes, cmd),
|
|
162
170
|
verbose: pickBool('verbose', cli.verbose, cfg.verbose, defaults?.verbose, cmd),
|
package/dist/config/normalize.js
CHANGED
|
@@ -155,13 +155,17 @@ function pickConvertSection(raw) {
|
|
|
155
155
|
if (raw[k] !== undefined)
|
|
156
156
|
s[k] = Number(raw[k]);
|
|
157
157
|
}
|
|
158
|
-
for (const k of ['force', 'yes', 'verbose', 'splitSheets']) {
|
|
158
|
+
for (const k of ['force', 'yes', 'verbose', 'splitSheets', 'splitHeadings', 'splitTables']) {
|
|
159
159
|
if (raw[k] !== undefined)
|
|
160
160
|
s[k] = Boolean(raw[k]);
|
|
161
161
|
}
|
|
162
162
|
if (raw.splitSheetsSuffix !== undefined && raw.splitSheetsSuffix !== null) {
|
|
163
163
|
s.splitSheetsSuffix = String(raw.splitSheetsSuffix).trim();
|
|
164
164
|
}
|
|
165
|
+
for (const k of ['splitHeadingLevel', 'splitMinLines', 'splitMinBytes', 'splitTableMaxRows']) {
|
|
166
|
+
if (raw[k] !== undefined)
|
|
167
|
+
s[k] = Number(raw[k]);
|
|
168
|
+
}
|
|
165
169
|
return s;
|
|
166
170
|
}
|
|
167
171
|
function pickDefaults(raw) {
|
|
@@ -22,7 +22,9 @@ exports.RAW_SKIP_DIR_NAMES = new Set([
|
|
|
22
22
|
function shouldSkipRawSubdir(name) {
|
|
23
23
|
if (exports.RAW_SKIP_DIR_NAMES.has(name))
|
|
24
24
|
return true;
|
|
25
|
-
// auto_pack_req
|
|
25
|
+
// auto_pack_req / md-pack:勿把 harness 工作目录当作源资料。
|
|
26
|
+
if (name.startsWith('.requirements-'))
|
|
27
|
+
return true;
|
|
26
28
|
if (name.endsWith('-harness'))
|
|
27
29
|
return true;
|
|
28
30
|
return false;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitMarkdownByHeadingLevel = splitMarkdownByHeadingLevel;
|
|
4
|
+
/**
|
|
5
|
+
* Split large merged Markdown files by ATX heading level (default `##`).
|
|
6
|
+
*/
|
|
7
|
+
const markdown_split_common_1 = require("./markdown-split-common");
|
|
8
|
+
const HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
9
|
+
function parseHeading(line) {
|
|
10
|
+
const m = HEADING_RE.exec(line);
|
|
11
|
+
if (!m)
|
|
12
|
+
return null;
|
|
13
|
+
return { level: m[1].length, title: m[2].trim() };
|
|
14
|
+
}
|
|
15
|
+
function headingPathAtLine(headings, line, splitLevel) {
|
|
16
|
+
const stack = [];
|
|
17
|
+
for (const h of headings) {
|
|
18
|
+
if (h.line >= line)
|
|
19
|
+
break;
|
|
20
|
+
while (stack.length > 0 && stack[stack.length - 1].level >= h.level) {
|
|
21
|
+
stack.pop();
|
|
22
|
+
}
|
|
23
|
+
stack.push({ level: h.level, title: h.title });
|
|
24
|
+
}
|
|
25
|
+
return stack.filter((h) => h.level < splitLevel).map((h) => h.title);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Split markdown at headings of the given level.
|
|
29
|
+
* Requires at least two sections to return a non-empty result.
|
|
30
|
+
*/
|
|
31
|
+
function splitMarkdownByHeadingLevel(markdown, opts) {
|
|
32
|
+
const splitLevel = opts.level ?? 2;
|
|
33
|
+
const minLines = opts.minLines ?? 800;
|
|
34
|
+
const minBytes = opts.minBytes ?? 100_000;
|
|
35
|
+
const charsBefore = Buffer.byteLength(markdown, 'utf8');
|
|
36
|
+
if (!(0, markdown_split_common_1.markdownExceedsSplitThreshold)(markdown, minLines, minBytes)) {
|
|
37
|
+
return {
|
|
38
|
+
sections: [],
|
|
39
|
+
stats: {
|
|
40
|
+
sectionsFound: 0,
|
|
41
|
+
charsBefore,
|
|
42
|
+
charsAfter: charsBefore,
|
|
43
|
+
skippedReason: 'below threshold',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const lines = markdown.split(/\r?\n/);
|
|
48
|
+
const allHeadings = [];
|
|
49
|
+
for (let i = 0; i < lines.length; i++) {
|
|
50
|
+
const h = parseHeading(lines[i]);
|
|
51
|
+
if (h)
|
|
52
|
+
allHeadings.push({ ...h, line: i });
|
|
53
|
+
}
|
|
54
|
+
const anchors = allHeadings.filter((h) => h.level === splitLevel);
|
|
55
|
+
if (anchors.length < 2) {
|
|
56
|
+
return {
|
|
57
|
+
sections: [],
|
|
58
|
+
stats: {
|
|
59
|
+
sectionsFound: 0,
|
|
60
|
+
charsBefore,
|
|
61
|
+
charsAfter: charsBefore,
|
|
62
|
+
skippedReason: `no level-${splitLevel} headings`,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const sections = [];
|
|
67
|
+
for (let i = 0; i < anchors.length; i++) {
|
|
68
|
+
const startLine = anchors[i].line;
|
|
69
|
+
let endLine = lines.length;
|
|
70
|
+
for (let j = startLine + 1; j < lines.length; j++) {
|
|
71
|
+
const h = parseHeading(lines[j]);
|
|
72
|
+
if (h && h.level <= splitLevel) {
|
|
73
|
+
endLine = j;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const title = anchors[i].title;
|
|
78
|
+
const headingPath = [...headingPathAtLine(allHeadings, startLine, splitLevel), title];
|
|
79
|
+
const bodyLines = lines.slice(startLine, endLine);
|
|
80
|
+
const meta = {
|
|
81
|
+
mergedBasename: opts.mergedBasename,
|
|
82
|
+
rawSource: opts.rawSource,
|
|
83
|
+
heading: title,
|
|
84
|
+
headingPath,
|
|
85
|
+
originStartLine: startLine,
|
|
86
|
+
originEndLine: endLine,
|
|
87
|
+
};
|
|
88
|
+
const body = (0, markdown_split_common_1.buildSplitMetaComment)(meta) +
|
|
89
|
+
bodyLines.join('\n').trimEnd() +
|
|
90
|
+
'\n';
|
|
91
|
+
sections.push({
|
|
92
|
+
title,
|
|
93
|
+
body,
|
|
94
|
+
filename: (0, markdown_split_common_1.slugifySplitTitle)(title, i + 1),
|
|
95
|
+
originStartLine: startLine,
|
|
96
|
+
originEndLine: endLine,
|
|
97
|
+
headingPath,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const charsAfter = sections.reduce((sum, s) => sum + Buffer.byteLength(s.body, 'utf8'), 0);
|
|
101
|
+
return {
|
|
102
|
+
sections,
|
|
103
|
+
stats: {
|
|
104
|
+
sectionsFound: sections.length,
|
|
105
|
+
charsBefore,
|
|
106
|
+
charsAfter,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runPostConvertSplits = runPostConvertSplits;
|
|
7
|
+
/**
|
|
8
|
+
* Post-convert markdown splitting: heading split + optional table split.
|
|
9
|
+
*/
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const logger_1 = require("../utils/logger");
|
|
13
|
+
const markdown_heading_split_1 = require("./markdown-heading-split");
|
|
14
|
+
const markdown_table_split_1 = require("./markdown-table-split");
|
|
15
|
+
const markdown_split_common_1 = require("./markdown-split-common");
|
|
16
|
+
const markdown_sheet_split_1 = require("./markdown-sheet-split");
|
|
17
|
+
function relRawPath(source, harnessRoot) {
|
|
18
|
+
if (!harnessRoot)
|
|
19
|
+
return undefined;
|
|
20
|
+
const rel = node_path_1.default.relative(harnessRoot, source).replace(/\\/g, '/');
|
|
21
|
+
if (rel.startsWith('..'))
|
|
22
|
+
return node_path_1.default.basename(source);
|
|
23
|
+
return rel;
|
|
24
|
+
}
|
|
25
|
+
async function applyTableSplitToFile(filePath, opts, force) {
|
|
26
|
+
if (!opts.splitTables) {
|
|
27
|
+
return { outputs: [], parts: 0 };
|
|
28
|
+
}
|
|
29
|
+
const content = await fs_extra_1.default.readFile(filePath, 'utf8');
|
|
30
|
+
const dir = node_path_1.default.dirname(filePath);
|
|
31
|
+
const base = node_path_1.default.basename(filePath, '.md');
|
|
32
|
+
const tablesDir = node_path_1.default.join(dir, 'tables');
|
|
33
|
+
const result = (0, markdown_table_split_1.splitLargeTablesInMarkdown)(content, {
|
|
34
|
+
maxRows: opts.splitTableMaxRows ?? 80,
|
|
35
|
+
fileBasename: base,
|
|
36
|
+
tablesDir,
|
|
37
|
+
});
|
|
38
|
+
if (result.stats.partsWritten === 0) {
|
|
39
|
+
return { outputs: [], parts: 0 };
|
|
40
|
+
}
|
|
41
|
+
await fs_extra_1.default.writeFile(filePath, result.markdown, 'utf8');
|
|
42
|
+
const written = [filePath];
|
|
43
|
+
await fs_extra_1.default.ensureDir(tablesDir);
|
|
44
|
+
for (const part of result.parts) {
|
|
45
|
+
const abs = node_path_1.default.join(dir, part.relativePath);
|
|
46
|
+
if (!force && (await fs_extra_1.default.pathExists(abs))) {
|
|
47
|
+
throw new Error(`table split output exists (pass --force): ${abs}`);
|
|
48
|
+
}
|
|
49
|
+
await fs_extra_1.default.writeFile(abs, part.content, 'utf8');
|
|
50
|
+
written.push(abs);
|
|
51
|
+
}
|
|
52
|
+
return { outputs: written, parts: result.stats.partsWritten };
|
|
53
|
+
}
|
|
54
|
+
async function writeSectionsWithTableSplit(sections, outputDir, sourceName, opts) {
|
|
55
|
+
const force = !!opts.force;
|
|
56
|
+
const processed = [];
|
|
57
|
+
const extraOutputs = [];
|
|
58
|
+
for (const section of sections) {
|
|
59
|
+
if (opts.splitTables) {
|
|
60
|
+
const tablesDir = node_path_1.default.join(outputDir, 'tables');
|
|
61
|
+
const partBase = node_path_1.default.basename(section.filename, '.md');
|
|
62
|
+
const split = (0, markdown_table_split_1.splitLargeTablesInMarkdown)(section.body, {
|
|
63
|
+
maxRows: opts.splitTableMaxRows ?? 80,
|
|
64
|
+
fileBasename: partBase,
|
|
65
|
+
tablesDir,
|
|
66
|
+
});
|
|
67
|
+
processed.push({ ...section, body: split.markdown });
|
|
68
|
+
if (split.parts.length > 0) {
|
|
69
|
+
await fs_extra_1.default.ensureDir(tablesDir);
|
|
70
|
+
for (const part of split.parts) {
|
|
71
|
+
const abs = node_path_1.default.join(outputDir, part.relativePath);
|
|
72
|
+
if (!force && (await fs_extra_1.default.pathExists(abs))) {
|
|
73
|
+
throw new Error(`table split output exists (pass --force): ${abs}`);
|
|
74
|
+
}
|
|
75
|
+
await fs_extra_1.default.writeFile(abs, part.content, 'utf8');
|
|
76
|
+
extraOutputs.push(abs);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
processed.push(section);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const written = await (0, markdown_split_common_1.writeMarkdownSplitOutput)(processed, outputDir, sourceName, { writeIndex: true, force });
|
|
85
|
+
return [...written, ...extraOutputs];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Run sheet / heading / table splits after merged markdown is written.
|
|
89
|
+
*/
|
|
90
|
+
async function runPostConvertSplits(ctx) {
|
|
91
|
+
const { source, outDir, basename, mergedMdPath, markdown, ext, opts } = ctx;
|
|
92
|
+
const splitSheets = opts.splitSheets !== false;
|
|
93
|
+
const splitHeadings = opts.splitHeadings !== false;
|
|
94
|
+
const splitSuffix = opts.splitSheetsSuffix ?? '_split';
|
|
95
|
+
const outputs = [mergedMdPath];
|
|
96
|
+
let sheetSplitDone = false;
|
|
97
|
+
let headingSplitSections = 0;
|
|
98
|
+
let tableSplitParts = 0;
|
|
99
|
+
let splitSkippedReason;
|
|
100
|
+
if (splitSheets && markdown_sheet_split_1.XLSX_SPLIT_EXTS.has(ext)) {
|
|
101
|
+
const split = (0, markdown_sheet_split_1.splitSpreadsheetMarkdownBySheet)(markdown);
|
|
102
|
+
if (split.sections.length >= 2) {
|
|
103
|
+
const splitDir = node_path_1.default.join(outDir, `${basename}${splitSuffix}`);
|
|
104
|
+
const splitWritten = await writeSectionsWithTableSplit(split.sections, splitDir, node_path_1.default.basename(source), opts);
|
|
105
|
+
outputs.push(...splitWritten);
|
|
106
|
+
sheetSplitDone = true;
|
|
107
|
+
logger_1.logger.debug(`sheet split ${node_path_1.default.basename(source)}: ${split.sections.length} sections -> ${splitDir}`);
|
|
108
|
+
const tableCount = splitWritten.length - split.sections.length - 1;
|
|
109
|
+
if (tableCount > 0)
|
|
110
|
+
tableSplitParts += tableCount;
|
|
111
|
+
return {
|
|
112
|
+
outputs,
|
|
113
|
+
sheetSplitDone,
|
|
114
|
+
headingSplitSections: 0,
|
|
115
|
+
tableSplitParts,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (splitHeadings && !sheetSplitDone) {
|
|
120
|
+
const heading = (0, markdown_heading_split_1.splitMarkdownByHeadingLevel)(markdown, {
|
|
121
|
+
level: opts.splitHeadingLevel ?? 2,
|
|
122
|
+
minLines: opts.splitMinLines ?? 800,
|
|
123
|
+
minBytes: opts.splitMinBytes ?? 100_000,
|
|
124
|
+
mergedBasename: basename,
|
|
125
|
+
rawSource: relRawPath(source, ctx.harnessRoot),
|
|
126
|
+
});
|
|
127
|
+
if (heading.stats.skippedReason) {
|
|
128
|
+
splitSkippedReason = heading.stats.skippedReason;
|
|
129
|
+
if (heading.stats.skippedReason.startsWith('no level-')) {
|
|
130
|
+
logger_1.logger.warn(`heading-split skipped for ${node_path_1.default.basename(source)}: ${heading.stats.skippedReason}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (heading.sections.length >= 2) {
|
|
134
|
+
const splitDir = node_path_1.default.join(outDir, `${basename}${splitSuffix}`);
|
|
135
|
+
const splitWritten = await writeSectionsWithTableSplit(heading.sections, splitDir, `${basename}.md`, opts);
|
|
136
|
+
outputs.push(...splitWritten);
|
|
137
|
+
headingSplitSections = heading.sections.length;
|
|
138
|
+
logger_1.logger.debug(`heading split ${node_path_1.default.basename(source)}: ${heading.sections.length} sections -> ${splitDir}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (opts.splitTables) {
|
|
142
|
+
const tableOnMerged = await applyTableSplitToFile(mergedMdPath, opts, !!opts.force);
|
|
143
|
+
outputs.push(...tableOnMerged.outputs.filter((p) => !outputs.includes(p)));
|
|
144
|
+
tableSplitParts += tableOnMerged.parts;
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
outputs,
|
|
148
|
+
sheetSplitDone,
|
|
149
|
+
headingSplitSections,
|
|
150
|
+
tableSplitParts,
|
|
151
|
+
splitSkippedReason,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.XLSX_SPLIT_EXTS = void 0;
|
|
7
4
|
exports.slugifySheetTitle = slugifySheetTitle;
|
|
@@ -13,23 +10,14 @@ exports.writeSheetSplitOutput = writeSheetSplitOutput;
|
|
|
13
10
|
* MarkItDown emits one `## SheetName` block per Excel worksheet. This pass
|
|
14
11
|
* writes each block to its own `.md` file under `<basename><suffix>/`.
|
|
15
12
|
*/
|
|
16
|
-
const
|
|
17
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const markdown_split_common_1 = require("./markdown-split-common");
|
|
18
14
|
/** Source extensions that trigger sheet split during `svharness convert`. */
|
|
19
15
|
exports.XLSX_SPLIT_EXTS = new Set(['.xlsx', '.xls']);
|
|
20
|
-
const INVALID_FILENAME_CHARS = /[<>:"/\\|?*\x00-\x1f]/g;
|
|
21
16
|
/**
|
|
22
17
|
* Turn a sheet title into a safe filename: `{index}_{slug}.md`.
|
|
23
18
|
*/
|
|
24
19
|
function slugifySheetTitle(title, index) {
|
|
25
|
-
|
|
26
|
-
name = name.replace(INVALID_FILENAME_CHARS, '_');
|
|
27
|
-
name = name.replace(/\s+/g, '_');
|
|
28
|
-
name = name.replace(/^[._]+|[._]+$/g, '');
|
|
29
|
-
if (!name) {
|
|
30
|
-
name = 'section';
|
|
31
|
-
}
|
|
32
|
-
return `${String(index).padStart(2, '0')}_${name}.md`;
|
|
20
|
+
return (0, markdown_split_common_1.slugifySplitTitle)(title, index);
|
|
33
21
|
}
|
|
34
22
|
/**
|
|
35
23
|
* Split markdown at `##` headings (one section per sheet).
|
|
@@ -54,7 +42,7 @@ function splitSpreadsheetMarkdownBySheet(markdown) {
|
|
|
54
42
|
for (let i = 0; i < anchors.length; i++) {
|
|
55
43
|
const endLine = i + 1 < anchors.length ? anchors[i + 1].startLine : lines.length;
|
|
56
44
|
const body = lines.slice(anchors[i].startLine, endLine).join('\n').trimEnd() + '\n';
|
|
57
|
-
const filename =
|
|
45
|
+
const filename = (0, markdown_split_common_1.slugifySplitTitle)(anchors[i].title, i + 1);
|
|
58
46
|
sections.push({
|
|
59
47
|
title: anchors[i].title,
|
|
60
48
|
body,
|
|
@@ -71,39 +59,9 @@ function splitSpreadsheetMarkdownBySheet(markdown) {
|
|
|
71
59
|
},
|
|
72
60
|
};
|
|
73
61
|
}
|
|
74
|
-
function buildSplitIndex(sourceName, sections) {
|
|
75
|
-
const lines = [
|
|
76
|
-
`# Split index: ${sourceName}\n`,
|
|
77
|
-
`\nTotal sections: ${sections.length}\n\n`,
|
|
78
|
-
];
|
|
79
|
-
for (const section of sections) {
|
|
80
|
-
lines.push(`- [${section.title}](${section.filename})\n`);
|
|
81
|
-
}
|
|
82
|
-
return lines.join('');
|
|
83
|
-
}
|
|
84
62
|
/**
|
|
85
63
|
* Persist split sections to `outputDir`. Returns absolute paths written.
|
|
86
64
|
*/
|
|
87
65
|
async function writeSheetSplitOutput(sections, outputDir, sourceName, opts) {
|
|
88
|
-
|
|
89
|
-
const force = !!opts?.force;
|
|
90
|
-
await fs_extra_1.default.ensureDir(outputDir);
|
|
91
|
-
const written = [];
|
|
92
|
-
for (const section of sections) {
|
|
93
|
-
const filePath = node_path_1.default.join(outputDir, section.filename);
|
|
94
|
-
if (!force && (await fs_extra_1.default.pathExists(filePath))) {
|
|
95
|
-
throw new Error(`sheet split output exists (pass --force): ${filePath}`);
|
|
96
|
-
}
|
|
97
|
-
await fs_extra_1.default.writeFile(filePath, section.body, 'utf8');
|
|
98
|
-
written.push(filePath);
|
|
99
|
-
}
|
|
100
|
-
if (writeIndex) {
|
|
101
|
-
const indexPath = node_path_1.default.join(outputDir, 'README.md');
|
|
102
|
-
if (!force && (await fs_extra_1.default.pathExists(indexPath))) {
|
|
103
|
-
throw new Error(`sheet split index exists (pass --force): ${indexPath}`);
|
|
104
|
-
}
|
|
105
|
-
await fs_extra_1.default.writeFile(indexPath, buildSplitIndex(sourceName, sections), 'utf8');
|
|
106
|
-
written.push(indexPath);
|
|
107
|
-
}
|
|
108
|
-
return written;
|
|
66
|
+
return (0, markdown_split_common_1.writeMarkdownSplitOutput)(sections, outputDir, sourceName, opts);
|
|
109
67
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.slugifySplitTitle = slugifySplitTitle;
|
|
7
|
+
exports.buildSplitMetaComment = buildSplitMetaComment;
|
|
8
|
+
exports.buildSplitIndex = buildSplitIndex;
|
|
9
|
+
exports.writeMarkdownSplitOutput = writeMarkdownSplitOutput;
|
|
10
|
+
exports.markdownExceedsSplitThreshold = markdownExceedsSplitThreshold;
|
|
11
|
+
/**
|
|
12
|
+
* Shared utilities for markdown split passes (sheet / heading / table).
|
|
13
|
+
*/
|
|
14
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
15
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
16
|
+
const INVALID_FILENAME_CHARS = /[<>:"/\\|?*\x00-\x1f]/g;
|
|
17
|
+
/**
|
|
18
|
+
* Turn a section title into a safe filename: `{index}_{slug}.md`.
|
|
19
|
+
*/
|
|
20
|
+
function slugifySplitTitle(title, index) {
|
|
21
|
+
let name = title.trim();
|
|
22
|
+
name = name.replace(INVALID_FILENAME_CHARS, '_');
|
|
23
|
+
name = name.replace(/\s+/g, '_');
|
|
24
|
+
name = name.replace(/^[._]+|[._]+$/g, '');
|
|
25
|
+
if (!name) {
|
|
26
|
+
name = 'section';
|
|
27
|
+
}
|
|
28
|
+
return `${String(index).padStart(2, '0')}_${name}.md`;
|
|
29
|
+
}
|
|
30
|
+
function buildSplitMetaComment(meta) {
|
|
31
|
+
const lines = [
|
|
32
|
+
'<!-- split-meta',
|
|
33
|
+
`source: ${meta.mergedBasename}.md`,
|
|
34
|
+
];
|
|
35
|
+
if (meta.rawSource) {
|
|
36
|
+
lines.push(`raw: ${meta.rawSource}`);
|
|
37
|
+
}
|
|
38
|
+
lines.push(`heading: ${meta.heading}`);
|
|
39
|
+
lines.push(`heading_path: ${JSON.stringify(meta.headingPath)}`);
|
|
40
|
+
lines.push(`origin_lines: ${meta.originStartLine + 1}-${meta.originEndLine}`);
|
|
41
|
+
lines.push('-->', '');
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
function buildSplitIndex(sourceName, sections) {
|
|
45
|
+
const lines = [
|
|
46
|
+
`# Split index: ${sourceName}\n`,
|
|
47
|
+
`\nTotal sections: ${sections.length}\n\n`,
|
|
48
|
+
];
|
|
49
|
+
for (const section of sections) {
|
|
50
|
+
lines.push(`- [${section.title}](${section.filename})\n`);
|
|
51
|
+
}
|
|
52
|
+
return lines.join('');
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Persist split sections to `outputDir`. Returns absolute paths written.
|
|
56
|
+
*/
|
|
57
|
+
async function writeMarkdownSplitOutput(sections, outputDir, sourceName, opts) {
|
|
58
|
+
const writeIndex = opts?.writeIndex !== false;
|
|
59
|
+
const force = !!opts?.force;
|
|
60
|
+
await fs_extra_1.default.ensureDir(outputDir);
|
|
61
|
+
const written = [];
|
|
62
|
+
for (const section of sections) {
|
|
63
|
+
const filePath = node_path_1.default.join(outputDir, section.filename);
|
|
64
|
+
if (!force && (await fs_extra_1.default.pathExists(filePath))) {
|
|
65
|
+
throw new Error(`split output exists (pass --force): ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
await fs_extra_1.default.writeFile(filePath, section.body, 'utf8');
|
|
68
|
+
written.push(filePath);
|
|
69
|
+
}
|
|
70
|
+
if (writeIndex) {
|
|
71
|
+
const indexPath = node_path_1.default.join(outputDir, 'README.md');
|
|
72
|
+
if (!force && (await fs_extra_1.default.pathExists(indexPath))) {
|
|
73
|
+
throw new Error(`split index exists (pass --force): ${indexPath}`);
|
|
74
|
+
}
|
|
75
|
+
await fs_extra_1.default.writeFile(indexPath, buildSplitIndex(sourceName, sections), 'utf8');
|
|
76
|
+
written.push(indexPath);
|
|
77
|
+
}
|
|
78
|
+
return written;
|
|
79
|
+
}
|
|
80
|
+
function markdownExceedsSplitThreshold(markdown, minLines, minBytes) {
|
|
81
|
+
const lines = markdown.split(/\r?\n/).length;
|
|
82
|
+
const bytes = Buffer.byteLength(markdown, 'utf8');
|
|
83
|
+
return lines >= minLines || bytes >= minBytes;
|
|
84
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.splitLargeTablesInMarkdown = splitLargeTablesInMarkdown;
|
|
7
|
+
/**
|
|
8
|
+
* Split oversized GFM tables inside Markdown into parts with repeated headers.
|
|
9
|
+
*/
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
function isTableRow(line) {
|
|
12
|
+
return /^\|.+\|$/.test(line.trim());
|
|
13
|
+
}
|
|
14
|
+
function isTableSeparator(line) {
|
|
15
|
+
return /^\|[\s:|-]+\|$/.test(line.trim());
|
|
16
|
+
}
|
|
17
|
+
function parseTables(lines) {
|
|
18
|
+
const tables = [];
|
|
19
|
+
let i = 0;
|
|
20
|
+
while (i < lines.length) {
|
|
21
|
+
if (!isTableRow(lines[i])) {
|
|
22
|
+
i++;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const start = i;
|
|
26
|
+
const headerLine = lines[i];
|
|
27
|
+
i++;
|
|
28
|
+
let separatorLine = null;
|
|
29
|
+
if (i < lines.length && isTableSeparator(lines[i])) {
|
|
30
|
+
separatorLine = lines[i];
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
const dataLines = [];
|
|
34
|
+
while (i < lines.length && isTableRow(lines[i])) {
|
|
35
|
+
if (isTableSeparator(lines[i])) {
|
|
36
|
+
i++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
dataLines.push(lines[i]);
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
tables.push({ start, end: i, headerLine, separatorLine, dataLines });
|
|
43
|
+
}
|
|
44
|
+
return tables;
|
|
45
|
+
}
|
|
46
|
+
function buildTablePartContent(headerLine, separatorLine, dataLines) {
|
|
47
|
+
const parts = [headerLine];
|
|
48
|
+
if (separatorLine)
|
|
49
|
+
parts.push(separatorLine);
|
|
50
|
+
parts.push(...dataLines);
|
|
51
|
+
return parts.join('\n') + '\n';
|
|
52
|
+
}
|
|
53
|
+
function stubLinks(parts) {
|
|
54
|
+
const links = parts
|
|
55
|
+
.map((p, idx) => `[p${String(idx + 1).padStart(2, '0')}](${p.relativePath.replace(/\\/g, '/')})`)
|
|
56
|
+
.join('/');
|
|
57
|
+
return `> [split-table] 原表过大,已拆为 ${parts.length} 段:见 ${links}\n`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Split tables exceeding maxRows; replace each oversized table with a stub link.
|
|
61
|
+
*/
|
|
62
|
+
function splitLargeTablesInMarkdown(markdown, opts) {
|
|
63
|
+
const maxRows = opts.maxRows ?? 80;
|
|
64
|
+
const lines = markdown.split(/\r?\n/);
|
|
65
|
+
const tables = parseTables(lines);
|
|
66
|
+
const parts = [];
|
|
67
|
+
let tablesSplit = 0;
|
|
68
|
+
if (tables.every((t) => t.dataLines.length <= maxRows)) {
|
|
69
|
+
return {
|
|
70
|
+
markdown,
|
|
71
|
+
parts: [],
|
|
72
|
+
stats: { tablesSplit: 0, partsWritten: 0 },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const outLines = [...lines];
|
|
76
|
+
let lineOffset = 0;
|
|
77
|
+
for (let ti = 0; ti < tables.length; ti++) {
|
|
78
|
+
const table = tables[ti];
|
|
79
|
+
if (table.dataLines.length <= maxRows)
|
|
80
|
+
continue;
|
|
81
|
+
tablesSplit++;
|
|
82
|
+
const chunkCount = Math.ceil(table.dataLines.length / maxRows);
|
|
83
|
+
const tableParts = [];
|
|
84
|
+
for (let c = 0; c < chunkCount; c++) {
|
|
85
|
+
const slice = table.dataLines.slice(c * maxRows, (c + 1) * maxRows);
|
|
86
|
+
const partName = `${opts.fileBasename}_table_p${String(c + 1).padStart(2, '0')}_of_${String(chunkCount).padStart(2, '0')}.md`;
|
|
87
|
+
const relativePath = node_path_1.default.join('tables', partName).replace(/\\/g, '/');
|
|
88
|
+
const content = `<!-- split-table-part ${c + 1}/${chunkCount} -->\n\n` +
|
|
89
|
+
buildTablePartContent(table.headerLine, table.separatorLine, slice);
|
|
90
|
+
tableParts.push({ relativePath, content });
|
|
91
|
+
parts.push({ relativePath, content });
|
|
92
|
+
}
|
|
93
|
+
const stub = stubLinks(tableParts);
|
|
94
|
+
const adjStart = table.start + lineOffset;
|
|
95
|
+
const adjEnd = table.end + lineOffset;
|
|
96
|
+
const stubLines = stub.split('\n');
|
|
97
|
+
outLines.splice(adjStart, adjEnd - adjStart, ...stubLines);
|
|
98
|
+
lineOffset += stubLines.length - (adjEnd - adjStart);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
markdown: outLines.join('\n'),
|
|
102
|
+
parts,
|
|
103
|
+
stats: {
|
|
104
|
+
tablesSplit,
|
|
105
|
+
partsWritten: parts.length,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -376,6 +376,12 @@ function main() {
|
|
|
376
376
|
.option('--force', '覆盖已存在同名 .md')
|
|
377
377
|
.option('--split-sheets-suffix <suffix>', 'xlsx/xls 按 sheet(##)拆分时的子目录后缀', '_split')
|
|
378
378
|
.option('--no-split-sheets', '不将 xlsx/xls 合并 md 按 ## 拆分为多文件')
|
|
379
|
+
.option('--no-split-headings', '不对超大合并 md 按标题(默认 ##)拆分为多文件')
|
|
380
|
+
.option('--split-heading-level <n>', '标题切分锚点级别(1–6,默认 2)', (v) => Number(v))
|
|
381
|
+
.option('--split-min-lines <n>', '触发标题切分的最小行数(默认 800)', (v) => Number(v))
|
|
382
|
+
.option('--split-min-bytes <n>', '触发标题切分的最小字节数(默认 100000)', (v) => Number(v))
|
|
383
|
+
.option('--split-tables', '将超大 GFM 表格按行切段(默认关闭)')
|
|
384
|
+
.option('--split-table-max-rows <n>', '表格切段时每段最大数据行数(默认 80)', (v) => Number(v))
|
|
379
385
|
.option('--no-structure-scan', 'convert 完成后不自动 structure-scan(仅 requirements)')
|
|
380
386
|
.option('-y, --yes', '跳过交互确认')
|
|
381
387
|
.option('--verbose', '显示详细日志')
|
|
@@ -394,6 +400,12 @@ function main() {
|
|
|
394
400
|
force: opts.force,
|
|
395
401
|
splitSheetsSuffix: opts.splitSheetsSuffix,
|
|
396
402
|
noSplitSheets: opts.noSplitSheets,
|
|
403
|
+
noSplitHeadings: opts.noSplitHeadings,
|
|
404
|
+
splitHeadingLevel: opts.splitHeadingLevel,
|
|
405
|
+
splitMinLines: opts.splitMinLines,
|
|
406
|
+
splitMinBytes: opts.splitMinBytes,
|
|
407
|
+
splitTables: opts.splitTables,
|
|
408
|
+
splitTableMaxRows: opts.splitTableMaxRows,
|
|
397
409
|
noStructureScan: opts.noStructureScan,
|
|
398
410
|
yes: opts.yes,
|
|
399
411
|
verbose: opts.verbose,
|
|
@@ -410,6 +422,12 @@ function main() {
|
|
|
410
422
|
force: !!merged.force,
|
|
411
423
|
splitSheets: merged.noSplitSheets ? false : merged.splitSheets,
|
|
412
424
|
splitSheetsSuffix: merged.splitSheetsSuffix,
|
|
425
|
+
splitHeadings: merged.noSplitHeadings ? false : merged.splitHeadings,
|
|
426
|
+
splitHeadingLevel: merged.splitHeadingLevel,
|
|
427
|
+
splitMinLines: merged.splitMinLines,
|
|
428
|
+
splitMinBytes: merged.splitMinBytes,
|
|
429
|
+
splitTables: merged.splitTables,
|
|
430
|
+
splitTableMaxRows: merged.splitTableMaxRows,
|
|
413
431
|
yes: !!merged.yes,
|
|
414
432
|
verbose: !!merged.verbose,
|
|
415
433
|
noStructureScan: !!merged.noStructureScan,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svharness",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.15",
|
|
4
4
|
"description": "CLI scaffolder for SDD-Driven Agent-Agnostic Coding Framework (harness)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"svharness": "./bin/cli.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"build": "tsc && node -e \"require('fs').cpSync('src/dashboard/static','dist/dashboard/static',{recursive:true,force:true})\"",
|
|
27
27
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
28
28
|
"rebuild": "npm run clean && npm run build",
|
|
29
|
-
"test:requirements": "npm run build && node scripts/test-parse-blocks.js",
|
|
29
|
+
"test:requirements": "npm run build && node scripts/test-parse-blocks.js && node scripts/test-markdown-split.js",
|
|
30
30
|
"test:auto": "npm run build && node scripts/test-harness-name.js && node scripts/test-doc-intake-paths.js && node scripts/test-auto-score.js && node scripts/test-auto-prompt.js && node scripts/test-acp-agent-cli-resolver.js && node scripts/test-auto-pack-req-context.js",
|
|
31
31
|
"test:cli-declarations": "npm run build && node scripts/test-cli-input-declarations.js",
|
|
32
32
|
"test:agent-launcher": "npm run build && node scripts/test-agent-launcher.js",
|