svharness 0.11.0 → 0.13.2
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 +37 -44
- package/dist/commands/apply.js +447 -107
- package/dist/commands/convert.js +17 -15
- package/dist/commands/init.js +33 -5
- package/dist/commands/wizard.js +5 -4
- package/dist/core/apply-project-entry.js +37 -63
- package/dist/core/project-ignore.js +53 -0
- package/dist/index.js +23 -27
- package/dist/utils/harness-name.js +41 -0
- package/dist/utils/validate-args.js +72 -31
- package/package.json +1 -1
- package/templates/_shared/apply-skills/harness-apply-skills-main.md +19 -109
- package/templates/svharness.config.example.yaml +2 -1
package/dist/commands/convert.js
CHANGED
|
@@ -9,7 +9,7 @@ exports.runConvert = runConvert;
|
|
|
9
9
|
*
|
|
10
10
|
* Upload local source documents (pdf / docx / pptx / xlsx / html / txt / ...)
|
|
11
11
|
* to a remotely-deployed `markitdown_serve` instance and persist the returned
|
|
12
|
-
* Markdown under `<harness>/
|
|
12
|
+
* Markdown under `--output` (standalone) or `<harness>/<type>/md/` (harness mode).
|
|
13
13
|
*
|
|
14
14
|
* Design rules (aligned with the approved plan):
|
|
15
15
|
* - The CLI is a pure HTTP client. It never spawns / installs / manages the
|
|
@@ -68,6 +68,7 @@ async function runConvert(opts) {
|
|
|
68
68
|
input: opts.input,
|
|
69
69
|
harness: opts.harness,
|
|
70
70
|
output: opts.output,
|
|
71
|
+
allowMissingHarnessYaml: opts.allowMissingHarnessYaml,
|
|
71
72
|
endpoint: opts.endpoint,
|
|
72
73
|
concurrency: opts.concurrency,
|
|
73
74
|
maxFileMB: opts.maxFileMB,
|
|
@@ -75,18 +76,17 @@ async function runConvert(opts) {
|
|
|
75
76
|
type: opts.type,
|
|
76
77
|
});
|
|
77
78
|
const cwd = opts.cwd ?? process.cwd();
|
|
78
|
-
const outDir =
|
|
79
|
+
const outDir = v.outputDir;
|
|
79
80
|
await fs_extra_1.default.ensureDir(outDir);
|
|
80
|
-
|
|
81
|
-
{ label: '
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{ label: '
|
|
85
|
-
{ label: '
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
]);
|
|
81
|
+
const configRows = [
|
|
82
|
+
{ label: 'mode ', value: v.harnessMode ? 'harness' : 'standalone' },
|
|
83
|
+
];
|
|
84
|
+
if (v.harnessMode && v.harnessRoot) {
|
|
85
|
+
configRows.push({ label: 'harness ', value: v.harnessRoot });
|
|
86
|
+
configRows.push({ label: 'type ', value: v.type });
|
|
87
|
+
}
|
|
88
|
+
configRows.push({ label: 'output dir ', value: outDir }, { label: 'endpoint ', value: v.endpoint }, { label: 'concurrency', value: String(v.concurrency) }, { label: 'max file ', value: `${v.maxFileMB} MB` }, { label: 'timeout ', value: `${v.timeoutSec}s` }, { label: 'force ', value: opts.force ? 'yes' : 'no' });
|
|
89
|
+
logger_1.logger.configBox('svharness convert', configRows);
|
|
90
90
|
// 1. Collect candidate files.
|
|
91
91
|
const candidates = await collectFiles(v.input, cwd);
|
|
92
92
|
if (candidates.length === 0) {
|
|
@@ -157,9 +157,11 @@ async function runConvert(opts) {
|
|
|
157
157
|
await Promise.all(workers);
|
|
158
158
|
// 6. Summary.
|
|
159
159
|
printSummary(results);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
if (v.harnessMode && v.harnessRoot) {
|
|
161
|
+
const relocated = await (0, doc_intake_paths_1.relocateMisplacedConvertedMd)(v.harnessRoot, v.type);
|
|
162
|
+
if (relocated > 0) {
|
|
163
|
+
logger_1.logger.warn(`已将 ${relocated} 个误落在 raw/**/converted_md/ 下的 .md 移至 ${v.type}/md/`);
|
|
164
|
+
}
|
|
163
165
|
}
|
|
164
166
|
const failed = results.filter((r) => r.status === 'failed');
|
|
165
167
|
if (failed.length > 0) {
|
package/dist/commands/init.js
CHANGED
|
@@ -17,6 +17,8 @@ const agent_injector_1 = require("../core/agent-injector");
|
|
|
17
17
|
const adapters_1 = require("../adapters");
|
|
18
18
|
const next_steps_1 = require("../core/next-steps");
|
|
19
19
|
const build_project_entry_1 = require("../core/build-project-entry");
|
|
20
|
+
const project_ignore_1 = require("../core/project-ignore");
|
|
21
|
+
const harness_name_1 = require("../utils/harness-name");
|
|
20
22
|
const repomix_pack_1 = require("../core/repomix-pack");
|
|
21
23
|
const extra_assets_intake_1 = require("../core/extra-assets-intake");
|
|
22
24
|
const baseline_copy_1 = require("../utils/baseline-copy");
|
|
@@ -37,7 +39,9 @@ async function runBuildAutoConvert(targetRoot, rawDir, type, opts) {
|
|
|
37
39
|
try {
|
|
38
40
|
await (0, convert_1.runConvert)({
|
|
39
41
|
input: [rawDir],
|
|
40
|
-
|
|
42
|
+
// Explicit md dir: harness.yaml may not exist yet (convert runs before render-meta).
|
|
43
|
+
output: node_path_1.default.join(targetRoot, type, 'md'),
|
|
44
|
+
harness: targetRoot,
|
|
41
45
|
type,
|
|
42
46
|
endpoint: opts.convertEndpoint,
|
|
43
47
|
concurrency: opts.convertConcurrency,
|
|
@@ -47,6 +51,7 @@ async function runBuildAutoConvert(targetRoot, rawDir, type, opts) {
|
|
|
47
51
|
yes: true,
|
|
48
52
|
verbose: opts.verbose,
|
|
49
53
|
cwd: opts.cwd,
|
|
54
|
+
allowMissingHarnessYaml: true,
|
|
50
55
|
});
|
|
51
56
|
const relocated = await (0, doc_intake_paths_1.relocateMisplacedConvertedMd)(targetRoot, type);
|
|
52
57
|
if (relocated > 0) {
|
|
@@ -86,7 +91,8 @@ async function runInit(opts) {
|
|
|
86
91
|
logger_1.logger.warn('--baseline-branch 在本地基线模式下被忽略');
|
|
87
92
|
}
|
|
88
93
|
const cwd = opts.cwd ?? process.cwd();
|
|
89
|
-
const
|
|
94
|
+
const harnessDirName = (0, harness_name_1.harnessDirNameFromName)(validated.name);
|
|
95
|
+
const targetRoot = node_path_1.default.resolve(cwd, harnessDirName);
|
|
90
96
|
const cliVersion = (0, version_1.getCliVersion)();
|
|
91
97
|
const createdAt = new Date().toISOString();
|
|
92
98
|
// Baseline code lands at <target>/baseline/code/ for both git & local
|
|
@@ -164,9 +170,9 @@ async function runInit(opts) {
|
|
|
164
170
|
baseUrl: opts.wikiBaseUrl,
|
|
165
171
|
model: opts.wikiModel,
|
|
166
172
|
});
|
|
167
|
-
configItems.push({ label: 'Wiki 模型', value: wikiCfg.model });
|
|
173
|
+
// configItems.push({ label: 'Wiki 模型', value: wikiCfg.model });
|
|
168
174
|
const displayBaseUrl = wikiCfg.baseUrl.replace(/\/v1$/, '');
|
|
169
|
-
configItems.push({ label: 'Wiki API', value: displayBaseUrl });
|
|
175
|
+
// configItems.push({ label: 'Wiki API', value: displayBaseUrl });
|
|
170
176
|
}
|
|
171
177
|
catch (e) {
|
|
172
178
|
// Ignore config resolution errors for display
|
|
@@ -321,15 +327,37 @@ async function runInit(opts) {
|
|
|
321
327
|
}
|
|
322
328
|
{
|
|
323
329
|
const adapterEntry = (0, adapters_1.getAdapter)(validated.agent);
|
|
330
|
+
const dirName = node_path_1.default.basename(targetRoot);
|
|
324
331
|
await (0, build_project_entry_1.writeBuildProjectEntry)({
|
|
325
332
|
projectRoot: cwd,
|
|
326
333
|
adapter: adapterEntry,
|
|
327
|
-
harnessDirName:
|
|
334
|
+
harnessDirName: dirName,
|
|
328
335
|
harnessName: validated.name,
|
|
329
336
|
arch: validated.arch,
|
|
330
337
|
agent: validated.agent,
|
|
331
338
|
force: !!opts.force,
|
|
332
339
|
});
|
|
340
|
+
const ignoreEntries = [
|
|
341
|
+
`/${dirName}/`,
|
|
342
|
+
'/AGENTS.md',
|
|
343
|
+
'/CLAUDE.md',
|
|
344
|
+
'/.codechat/',
|
|
345
|
+
];
|
|
346
|
+
const ignoreAdded = await (0, project_ignore_1.appendProjectIgnoreEntries)({
|
|
347
|
+
projectRoot: cwd,
|
|
348
|
+
entries: ignoreEntries,
|
|
349
|
+
marker: '# svharness build',
|
|
350
|
+
});
|
|
351
|
+
const totalLines = Object.values(ignoreAdded).reduce((n, a) => n + a.length, 0);
|
|
352
|
+
if (totalLines > 0) {
|
|
353
|
+
const detail = Object.entries(ignoreAdded)
|
|
354
|
+
.map(([f, lines]) => `${f}: ${lines.join(', ')}`)
|
|
355
|
+
.join(';');
|
|
356
|
+
logger_1.logger.success(`已在项目 ignore 文件中追加:${detail}`);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
logger_1.logger.info('项目 ignore 文件已包含 build 注入路径,未追加新行');
|
|
360
|
+
}
|
|
333
361
|
}
|
|
334
362
|
// 6a. Repomix XML pack of baseline code (default when --baseline; non-fatal on failure).
|
|
335
363
|
if (hasBaseline) {
|
package/dist/commands/wizard.js
CHANGED
|
@@ -8,6 +8,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const prompts_1 = __importDefault(require("prompts"));
|
|
10
10
|
const validate_args_1 = require("../utils/validate-args");
|
|
11
|
+
const harness_name_1 = require("../utils/harness-name");
|
|
11
12
|
const logger_1 = require("../utils/logger");
|
|
12
13
|
const save_config_1 = require("../config/save-config");
|
|
13
14
|
const init_1 = require("./init");
|
|
@@ -61,8 +62,8 @@ async function runBuildWizard(cwd) {
|
|
|
61
62
|
{
|
|
62
63
|
type: 'text',
|
|
63
64
|
name: 'harnessName',
|
|
64
|
-
message: 'harness
|
|
65
|
-
validate: (v) => /^[a-z][a-z0-9-]{1,39}$/.test(
|
|
65
|
+
message: 'harness 名称(如 my-app,将生成 .my-app-harness/)',
|
|
66
|
+
validate: (v) => /^[a-z][a-z0-9-]{1,39}$/.test((0, harness_name_1.harnessNameCore)(v)) || '名称格式无效',
|
|
66
67
|
},
|
|
67
68
|
{
|
|
68
69
|
type: 'select',
|
|
@@ -188,7 +189,7 @@ async function runBuildWizard(cwd) {
|
|
|
188
189
|
return;
|
|
189
190
|
}
|
|
190
191
|
const buildSection = {
|
|
191
|
-
harnessName: String(answers.harnessName).trim(),
|
|
192
|
+
harnessName: (0, harness_name_1.normalizeHarnessName)(String(answers.harnessName).trim()),
|
|
192
193
|
arch: answers.arch,
|
|
193
194
|
agent: answers.agent,
|
|
194
195
|
force: !!answers.force,
|
|
@@ -345,7 +346,7 @@ async function runConvertWizard(cwd) {
|
|
|
345
346
|
{
|
|
346
347
|
type: 'text',
|
|
347
348
|
name: 'output',
|
|
348
|
-
message: '
|
|
349
|
+
message: '输出目录(独立模式=最终 md 目录;harness 模式=含 harness.yaml 的根目录,默认当前目录)',
|
|
349
350
|
initial: '.',
|
|
350
351
|
},
|
|
351
352
|
{
|
|
@@ -3,72 +3,52 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.renderApplyProjectEntryMarkdown = renderApplyProjectEntryMarkdown;
|
|
7
6
|
exports.writeApplyProjectEntry = writeApplyProjectEntry;
|
|
8
7
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
9
|
const logger_1 = require("../utils/logger");
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
return '# CLAUDE.md';
|
|
14
|
-
}
|
|
15
|
-
return '# AGENTS.md';
|
|
10
|
+
function toPosix(p) {
|
|
11
|
+
return p.replace(/\\/g, '/');
|
|
16
12
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
function toHarnessScopedRef(raw, harnessDirName) {
|
|
14
|
+
const ref = toPosix(raw.trim());
|
|
15
|
+
if (ref.startsWith('http://') ||
|
|
16
|
+
ref.startsWith('https://') ||
|
|
17
|
+
ref.startsWith('mailto:') ||
|
|
18
|
+
ref.startsWith('#')) {
|
|
19
|
+
return raw;
|
|
20
20
|
}
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const normalized = ref.replace(/^\.\//, '');
|
|
22
|
+
const coreMatch = /^(agent-env|baseline|specs|references|tasks)\//.test(normalized) ||
|
|
23
|
+
/^(harness\.yaml|VERSION|\.harness-build-state\.yaml|AGENTS_BUILD\.md|AGENTS_APPLY\.md)$/.test(normalized);
|
|
24
|
+
if (!coreMatch)
|
|
25
|
+
return raw;
|
|
26
|
+
return `./${harnessDirName}/${normalized}`;
|
|
23
27
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'> 若内容与你的习惯冲突,可重新执行 `svharness apply --force` 覆盖本文件。',
|
|
37
|
-
'',
|
|
38
|
-
'## 1. 必读',
|
|
39
|
-
'',
|
|
40
|
-
`1. **Harness(相对目标项目)**:\`${relHarness}\``,
|
|
41
|
-
'2. **应用指南**:[`AGENTS_APPLY.md`](./AGENTS_APPLY.md)(若 apply 已注入;不存在时请先在 harness 侧完成 build)',
|
|
42
|
-
'3. **绑定元数据**:dispatcher skill 正文 `## 绑定元数据` 小节(权威来源;勿读 sidecar `references/binding.yaml`)',
|
|
43
|
-
'',
|
|
44
|
-
'## 2. harness-apply 总清单 skill(二级调度,优先入口)',
|
|
45
|
-
'',
|
|
46
|
-
'在支持 skill 的 Agent IDE 中,**优先**加载并触发:',
|
|
47
|
-
'',
|
|
48
|
-
'- **`harness-apply-skills-main`** — 从 skill 内嵌绑定元数据解析 `harness_root` → 装载 `agent-env/rules/` → 扫描 `agent-env/skills/` 与 `apply_skill_registry` → 在目标项目下交付代码。',
|
|
49
|
-
`- 磁盘路径(相对项目根):\`${dispatcherSkillRel}\``,
|
|
50
|
-
'',
|
|
51
|
-
'## 3. 典型触发',
|
|
52
|
-
'',
|
|
53
|
-
'- 应用 harness-apply-skills-main 完成 \<功能\> 开发',
|
|
54
|
-
'- 基于 harness 做 xxx / 按 harness 规范实现 xxx / apply harness',
|
|
55
|
-
'',
|
|
56
|
-
'## 4. 元数据(供人类核对)',
|
|
57
|
-
'',
|
|
58
|
-
`- harness 名称:\`${input.harnessName}\``,
|
|
59
|
-
`- harness 版本:\`${input.harnessVersion}\``,
|
|
60
|
-
`- 绑定模式:\`${input.mode}\``,
|
|
61
|
-
`- Agent:\`${input.agent}\``,
|
|
62
|
-
'',
|
|
63
|
-
].join('\n');
|
|
28
|
+
function rewriteEntryReferences(content, harnessDirName) {
|
|
29
|
+
const withMarkdownLinks = content.replace(/\]\(([^)]+)\)/g, (_m, linkTarget) => {
|
|
30
|
+
const [pathPart, ...rest] = linkTarget.split('#');
|
|
31
|
+
const rewritten = toHarnessScopedRef(pathPart, harnessDirName);
|
|
32
|
+
if (rest.length > 0) {
|
|
33
|
+
return `](${rewritten}#${rest.join('#')})`;
|
|
34
|
+
}
|
|
35
|
+
return `](${rewritten})`;
|
|
36
|
+
});
|
|
37
|
+
return withMarkdownLinks.replace(/`([^`\n]+)`/g, (_m, token) => {
|
|
38
|
+
return `\`${toHarnessScopedRef(token, harnessDirName)}\``;
|
|
39
|
+
});
|
|
64
40
|
}
|
|
65
41
|
/**
|
|
66
|
-
* Write `AGENTS.md` or `CLAUDE.md` at the target project root
|
|
67
|
-
*
|
|
42
|
+
* Write `AGENTS.md` or `CLAUDE.md` at the target project root by copying
|
|
43
|
+
* `<harness>/AGENTS_APPLY.md` and renaming it to the adapter's entry file.
|
|
68
44
|
*
|
|
69
45
|
* @returns relative path written, or undefined when skipped
|
|
70
46
|
*/
|
|
71
47
|
async function writeApplyProjectEntry(input) {
|
|
48
|
+
const src = node_path_1.default.join(input.harnessRoot, 'AGENTS_APPLY.md');
|
|
49
|
+
if (!(await fs_extra_1.default.pathExists(src))) {
|
|
50
|
+
throw new Error(`harness 中缺少 AGENTS_APPLY.md:${src}\n 请先重新执行 svharness build 生成应用指南。`);
|
|
51
|
+
}
|
|
72
52
|
const fileName = input.adapter.projectEntryFile;
|
|
73
53
|
const dest = node_path_1.default.join(input.projectRoot, fileName);
|
|
74
54
|
const rel = fileName;
|
|
@@ -78,15 +58,9 @@ async function writeApplyProjectEntry(input) {
|
|
|
78
58
|
return undefined;
|
|
79
59
|
}
|
|
80
60
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
harnessVersion: input.harnessVersion,
|
|
86
|
-
agent: input.agent,
|
|
87
|
-
mode: input.mode,
|
|
88
|
-
});
|
|
89
|
-
await fs_extra_1.default.outputFile(dest, body, 'utf8');
|
|
90
|
-
logger_1.logger.success(`已写入项目根 AI 入口:${rel}`);
|
|
61
|
+
const raw = await fs_extra_1.default.readFile(src, 'utf8');
|
|
62
|
+
const rewritten = rewriteEntryReferences(raw, input.harnessDirName);
|
|
63
|
+
await fs_extra_1.default.outputFile(dest, rewritten, 'utf8');
|
|
64
|
+
logger_1.logger.success(`已写入项目根 AI 入口:${rel}(由 AGENTS_APPLY.md 重命名)`);
|
|
91
65
|
return rel;
|
|
92
66
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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.appendProjectIgnoreEntries = appendProjectIgnoreEntries;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const DEFAULT_IGNORE_FILENAMES = ['.gitignore', '.cursorignore'];
|
|
10
|
+
/**
|
|
11
|
+
* Idempotently append ignore entries under `projectRoot`.
|
|
12
|
+
* Always writes `.gitignore` (creates if missing). Updates `.cursorignore` only when it already exists.
|
|
13
|
+
*/
|
|
14
|
+
async function appendProjectIgnoreEntries(input) {
|
|
15
|
+
const marker = input.marker ?? '# svharness';
|
|
16
|
+
const filenames = input.filenames ?? DEFAULT_IGNORE_FILENAMES;
|
|
17
|
+
const normalizedEntries = input.entries.map((e) => e.startsWith('/') ? e : `/${e.replace(/^\/+/, '')}`);
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const filename of filenames) {
|
|
20
|
+
const filePath = node_path_1.default.join(input.projectRoot, filename);
|
|
21
|
+
const exists = await fs_extra_1.default.pathExists(filePath);
|
|
22
|
+
if (!exists && filename !== '.gitignore') {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const added = await appendToIgnoreFile(filePath, normalizedEntries, marker, !exists);
|
|
26
|
+
if (added.length > 0) {
|
|
27
|
+
result[filename] = added;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
async function appendToIgnoreFile(filePath, entries, marker, createNew) {
|
|
33
|
+
const raw = createNew || !(await fs_extra_1.default.pathExists(filePath))
|
|
34
|
+
? ''
|
|
35
|
+
: await fs_extra_1.default.readFile(filePath, 'utf8');
|
|
36
|
+
const existing = new Set(raw
|
|
37
|
+
.split(/\r?\n/)
|
|
38
|
+
.map((line) => line.trim())
|
|
39
|
+
.filter((line) => line.length > 0 && !line.startsWith('#')));
|
|
40
|
+
const added = [];
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (!existing.has(entry)) {
|
|
43
|
+
existing.add(entry);
|
|
44
|
+
added.push(entry);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (added.length === 0)
|
|
48
|
+
return [];
|
|
49
|
+
const appendix = `\n${marker}\n` + added.map((line) => `${line}\n`).join('');
|
|
50
|
+
const next = raw.endsWith('\n') || raw.length === 0 ? raw + appendix : raw + '\n' + appendix;
|
|
51
|
+
await fs_extra_1.default.outputFile(filePath, next, 'utf8');
|
|
52
|
+
return added;
|
|
53
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ const commander_1 = require("commander");
|
|
|
4
4
|
const logger_1 = require("./utils/logger");
|
|
5
5
|
const version_1 = require("./utils/version");
|
|
6
6
|
const validate_args_1 = require("./utils/validate-args");
|
|
7
|
+
const harness_name_1 = require("./utils/harness-name");
|
|
7
8
|
const init_1 = require("./commands/init");
|
|
8
9
|
const apply_1 = require("./commands/apply");
|
|
9
10
|
const convert_1 = require("./commands/convert");
|
|
@@ -18,13 +19,21 @@ function resolveHarnessName(opts) {
|
|
|
18
19
|
if (opts.harnessName && opts.name && opts.harnessName !== opts.name) {
|
|
19
20
|
throw new Error('同时收到 --harness-name 与 --name 但取值不同,请只保留一个(推荐 --harness-name)');
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const raw = opts.harnessName ?? opts.name;
|
|
23
|
+
if (!raw) {
|
|
24
|
+
throw new Error('--harness-name <name> 是必填参数(可在配置文件的 build.harnessName 中提供)');
|
|
25
|
+
}
|
|
26
|
+
if (opts.name && !opts.harnessName) {
|
|
24
27
|
logger_1.logger.warn('⚠️ --name 已重命名为 --harness-name,旧名将在下个 minor 版本移除。');
|
|
25
|
-
return opts.name;
|
|
26
28
|
}
|
|
27
|
-
|
|
29
|
+
const check = (0, harness_name_1.validateHarnessNameInput)(raw);
|
|
30
|
+
if (!check.ok) {
|
|
31
|
+
throw new Error(check.message);
|
|
32
|
+
}
|
|
33
|
+
if (check.normalized !== raw.trim()) {
|
|
34
|
+
logger_1.logger.info(`harness 名称已规范为 ${check.normalized}(目录:${check.normalized}-harness)`);
|
|
35
|
+
}
|
|
36
|
+
return check.normalized;
|
|
28
37
|
}
|
|
29
38
|
function buildSectionFromOpts(opts, harnessName) {
|
|
30
39
|
return {
|
|
@@ -105,7 +114,7 @@ function attachBuildOptions(cmd) {
|
|
|
105
114
|
return cmd
|
|
106
115
|
.option('--config <path>', `从 YAML/JSON 读取参数(默认可选 ${config_1.DEFAULT_CONFIG_FILENAME})`)
|
|
107
116
|
.option('--save-config [path]', `执行成功后把参数写入配置文件(默认 ${config_1.DEFAULT_CONFIG_FILENAME})`)
|
|
108
|
-
.option('--harness-name <name>', '【必填】harness
|
|
117
|
+
.option('--harness-name <name>', '【必填】harness 名称(如 my-app → .my-app-harness/ 目录)')
|
|
109
118
|
.option('--name <name>', '【已废弃】--harness-name 的旧别名,下个 minor 版本移除')
|
|
110
119
|
.option('--arch <arch>', '架构模板:' + (0, validate_args_1.listSupportedArches)().join(' | '), DEFAULT_ARCH)
|
|
111
120
|
.option('--agent <agent>', '目标 Agent:' + (0, validate_args_1.listSupportedAgents)().join(' | '), DEFAULT_AGENT)
|
|
@@ -163,39 +172,26 @@ function main() {
|
|
|
163
172
|
program.addHelpText('afterAll', [
|
|
164
173
|
'',
|
|
165
174
|
'┌──────────────────────────────────────────────────────────────────────────',
|
|
166
|
-
'│
|
|
175
|
+
'│ 快速上手, 帮助:svharness <command> --help 或 svharness help <command> ',
|
|
167
176
|
'├──────────────────────────────────────────────────────────────────────────',
|
|
168
|
-
'│ ',
|
|
169
177
|
'│ # 方式零:交互式向导(推荐新手) ',
|
|
170
178
|
'│ svharness wizard ',
|
|
171
179
|
'│ ',
|
|
172
|
-
'│ # 方式一:配置文件驱动 ',
|
|
173
|
-
'│ svharness build --config svharness.config.yaml --yes ',
|
|
174
|
-
'│ ',
|
|
175
180
|
'│ # 方式二:仅 --harness-name 必填 ',
|
|
176
181
|
'│ svharness build --harness-name my-app ',
|
|
177
182
|
'│ ',
|
|
178
|
-
'│ # 方式三:本地基线 + wiki
|
|
179
|
-
'│ svharness build --harness-name my-app --baseline ./src
|
|
183
|
+
'│ # 方式三:本地基线 + wiki + requirements + references ',
|
|
184
|
+
'│ svharness build --harness-name my-app --baseline ./src \ ',
|
|
185
|
+
'│ --requirements ./requirements.xlsx --references ./references.pdf ',
|
|
180
186
|
'│ ',
|
|
181
187
|
'│ # 方式四:绑定 harness 到目标项目 ',
|
|
182
|
-
'│ svharness apply --harness ./my-app-harness --target .
|
|
188
|
+
'│ svharness apply --harness ./my-app-harness --target . ',
|
|
183
189
|
'│ ',
|
|
184
190
|
'│ # 方式五:文档转 Markdown ',
|
|
185
|
-
'│ svharness convert --input ./docs/*.pdf --
|
|
191
|
+
'│ svharness convert --input ./docs/*.pdf --output ./docs/md --type requirements',
|
|
186
192
|
'│ ',
|
|
187
193
|
'└──────────────────────────────────────────────────────────────────────────',
|
|
188
|
-
'',
|
|
189
|
-
'┌──────────────────────────────────────────────────────────────────────────',
|
|
190
|
-
'│ 📋 配置与向导 ',
|
|
191
|
-
'├──────────────────────────────────────────────────────────────────────────',
|
|
192
|
-
'│ --config <path> 读取 YAML/JSON(默认查找 ./svharness.config.yaml) ',
|
|
193
|
-
'│ --save-config build 成功后写入当前参数到配置文件 ',
|
|
194
|
-
'│ svharness wizard 分步 TUI:路径 + 说明,可保存/执行 ',
|
|
195
|
-
'│ 优先级:CLI 参数 > 配置文件 > 代码默认值 ',
|
|
196
|
-
'└──────────────────────────────────────────────────────────────────────────',
|
|
197
|
-
'',
|
|
198
|
-
' 📚 完整文档:https://yesv-desaysv.feishu.cn/docx/YEIHdD4NGooMdrxQO4McqwfNnjf?pre_pathname=%2Fdrive%2Fhome%2F',
|
|
194
|
+
' 📚 使用说明文档:https://yesv-desaysv.feishu.cn/docx/OEBwdywTfoncPNxPZ0acClNincg',
|
|
199
195
|
'',
|
|
200
196
|
].join('\n'));
|
|
201
197
|
attachBuildOptions(program
|
|
@@ -227,7 +223,7 @@ function main() {
|
|
|
227
223
|
.option('--config <path>', `从配置文件读取 convert 节(默认 ${config_1.DEFAULT_CONFIG_FILENAME})`)
|
|
228
224
|
.option('--input <path...>', '【可选,默认 .】源文件、目录或 glob')
|
|
229
225
|
.option('--harness <path>', '【可选】目标 harness 目录')
|
|
230
|
-
.option('--output <path>', '【可选,默认 .】Markdown
|
|
226
|
+
.option('--output <path>', '【可选,默认 .】Markdown 输出目录;含 harness.yaml 时写入 <output>/<type>/md/')
|
|
231
227
|
.option('--endpoint <url>', 'markitdown_serve 基址')
|
|
232
228
|
.option('--concurrency <n>', '【默认 3】并发上传数', (v) => Number(v))
|
|
233
229
|
.option('--max-file-mb <n>', '【默认 50】单文件大小上限(MB)', (v) => Number(v))
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeHarnessName = normalizeHarnessName;
|
|
4
|
+
exports.harnessNameCore = harnessNameCore;
|
|
5
|
+
exports.validateHarnessNameInput = validateHarnessNameInput;
|
|
6
|
+
exports.harnessDirNameFromName = harnessDirNameFromName;
|
|
7
|
+
/**
|
|
8
|
+
* Normalize and validate harness names for `svharness build`.
|
|
9
|
+
*
|
|
10
|
+
* User-facing input `my-app` becomes `.my-app`; directory is `.my-app-harness`.
|
|
11
|
+
*/
|
|
12
|
+
const HARNESS_NAME_CORE_RE = /^[a-z][a-z0-9-]{1,39}$/;
|
|
13
|
+
/** Ensure harness name starts with `.` (e.g. `my-app` → `.my-app`). */
|
|
14
|
+
function normalizeHarnessName(raw) {
|
|
15
|
+
const trimmed = raw.trim();
|
|
16
|
+
if (!trimmed)
|
|
17
|
+
return trimmed;
|
|
18
|
+
return trimmed.startsWith('.') ? trimmed : `.${trimmed}`;
|
|
19
|
+
}
|
|
20
|
+
/** Core segment without leading dot, for validation messages. */
|
|
21
|
+
function harnessNameCore(raw) {
|
|
22
|
+
const trimmed = raw.trim();
|
|
23
|
+
return trimmed.startsWith('.') ? trimmed.slice(1) : trimmed;
|
|
24
|
+
}
|
|
25
|
+
function validateHarnessNameInput(raw) {
|
|
26
|
+
if (!raw?.trim()) {
|
|
27
|
+
return { ok: false, message: '--harness-name 为必填参数' };
|
|
28
|
+
}
|
|
29
|
+
const core = harnessNameCore(raw);
|
|
30
|
+
if (!HARNESS_NAME_CORE_RE.test(core)) {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
message: `harness 名称 "${raw.trim()}" 无效:允许小写字母、数字、中划线;` +
|
|
34
|
+
'须以字母开头;核心段长度 2–40(不含可选的前导 `.`)。',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return { ok: true, normalized: normalizeHarnessName(raw) };
|
|
38
|
+
}
|
|
39
|
+
function harnessDirNameFromName(harnessName) {
|
|
40
|
+
return `${normalizeHarnessName(harnessName)}-harness`;
|
|
41
|
+
}
|