svharness 0.8.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 +290 -61
- package/dist/adapters/claude-code.js +1 -0
- package/dist/adapters/codechat.js +1 -0
- package/dist/adapters/cursor.js +1 -0
- package/dist/adapters/generic.js +1 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/opencode.js +17 -0
- package/dist/adapters/qoder.js +1 -0
- package/dist/commands/apply.js +456 -71
- package/dist/commands/convert.js +371 -0
- package/dist/commands/init.js +156 -11
- package/dist/commands/references-apply-skills.js +47 -0
- package/dist/commands/wizard.js +442 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/index.js +18 -0
- package/dist/config/load-config.js +54 -0
- package/dist/config/merge-options.js +115 -0
- package/dist/config/normalize.js +165 -0
- package/dist/config/save-config.js +40 -0
- package/dist/config/types.js +2 -0
- package/dist/core/agent-injector.js +58 -9
- package/dist/core/apply-project-entry.js +66 -0
- package/dist/core/build-project-entry.js +98 -0
- package/dist/core/doc-intake-paths.js +155 -0
- package/dist/core/extra-assets-intake.js +254 -0
- package/dist/core/markitdown-client.js +156 -0
- package/dist/core/next-steps.js +33 -22
- package/dist/core/project-ignore.js +53 -0
- package/dist/core/reference-apply-skills.js +35 -0
- package/dist/core/render-meta.js +2 -1
- package/dist/core/repomix-pack.js +3 -3
- package/dist/core/state.js +44 -24
- package/dist/index.js +211 -140
- package/dist/utils/harness-name.js +41 -0
- package/dist/utils/validate-args.js +147 -6
- package/dist/wiki/wikiTasksWriter.js +5 -6
- package/package.json +2 -1
- package/templates/_shared/apply-skills/harness-apply-skills-main.md +19 -78
- package/templates/_shared/build-rules/harness-build-rule-agent-agnostic.md +5 -5
- package/templates/_shared/build-rules/harness-build-rule-chinese-only.md +5 -5
- package/templates/_shared/build-rules/harness-build-rule-convert-check.md +46 -0
- package/templates/_shared/build-rules/harness-build-rule-memory-write.md +1 -1
- package/templates/_shared/build-rules/harness-build-rule-orchestrator-flow.md +36 -10
- package/templates/_shared/build-rules/harness-build-rule-skills-tasks-output.md +3 -2
- package/templates/_shared/build-rules/harness-build-rule-specs-schema.md +3 -3
- package/templates/_shared/build-rules/harness-build-rule-user-interaction.md +36 -16
- package/templates/_shared/build-skills/harness-build-skill-agent-env-merge.md +75 -0
- package/templates/_shared/build-skills/harness-build-skill-knowledge-builder.md +49 -85
- package/templates/_shared/build-skills/harness-build-skill-orchestrator.md +35 -18
- package/templates/_shared/build-skills/harness-build-skill-references-intake.md +91 -0
- package/templates/_shared/build-skills/harness-build-skill-spec-builder.md +19 -9
- package/templates/_shared/build-skills/harness-build-skill-wiki-writer.md +24 -24
- package/templates/_shared/build-skills/harness-build-skills-main.md +83 -0
- package/templates/_shared/meta/AGENTS_APPLY.md.ejs +139 -0
- package/templates/_shared/meta/{AGENTS.md.ejs → AGENTS_BUILD.md.ejs} +7 -5
- package/templates/_shared/meta/CHANGELOG.md.ejs +3 -3
- package/templates/_shared/meta/README.md.ejs +11 -9
- package/templates/_shared/meta/harness.yaml.ejs +28 -7
- package/templates/_shared/skeleton/baseline/code/.gitkeep +1 -0
- package/templates/_shared/skeleton/baseline/wiki/.gitkeep +1 -0
- package/templates/_shared/skeleton/references/apply-skills-registry.example.yaml +11 -0
- package/templates/_shared/skeleton/references/md/.gitkeep +1 -0
- package/templates/_shared/skeleton/references/raw/.gitkeep +1 -0
- package/templates/_shared/skeleton/references/yaml/.gitkeep +1 -0
- package/templates/_shared/skeleton/requirements/md/.gitkeep +1 -0
- package/templates/_shared/skeleton/requirements/raw/.gitkeep +1 -0
- package/templates/_shared/skeleton/requirements/yaml/.gitkeep +1 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-android-cli/SKILL.md +88 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-android-service-patterns/SKILL.md +205 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-android-xml-architecture/SKILL.md +138 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-lifecycle-management/SKILL.md +158 -0
- package/templates/android-xml/skeleton/agent-env/skills/harness-xml-ui/SKILL.md +112 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-cmake-build/SKILL.md +163 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-cpp-architecture/SKILL.md +157 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-cpp-concurrency/SKILL.md +180 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-memory-safety/SKILL.md +163 -0
- package/templates/cpp/skeleton/agent-env/skills/harness-modern-cpp/SKILL.md +149 -0
- package/templates/python/skeleton/agent-env/skills/harness-async-patterns/SKILL.md +162 -0
- package/templates/python/skeleton/agent-env/skills/harness-python-architecture/SKILL.md +160 -0
- package/templates/python/skeleton/agent-env/skills/harness-python-package-structure/SKILL.md +210 -0
- package/templates/python/skeleton/agent-env/skills/harness-python-performance/SKILL.md +207 -0
- package/templates/python/skeleton/agent-env/skills/harness-python-testing/SKILL.md +198 -0
- package/templates/svharness.config.example.yaml +40 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-react-architecture/SKILL.md +177 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-react-performance/SKILL.md +177 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-react-testing/SKILL.md +193 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-react-ui-patterns/SKILL.md +257 -0
- package/templates/web-react/skeleton/agent-env/skills/harness-state-management/SKILL.md +189 -0
- package/templates/_shared/skeleton/assets/baseline/code/.gitkeep +0 -1
- package/templates/_shared/skeleton/assets/baseline/wiki/.gitkeep +0 -1
- package/templates/_shared/skeleton/assets/raw/.gitkeep +0 -1
- package/templates/_shared/skeleton/assets/requirements/.gitkeep +0 -1
- /package/templates/_shared/skeleton/{assets/baseline/repomix → agent-env/_incoming/skills}/.gitkeep +0 -0
package/dist/core/state.js
CHANGED
|
@@ -10,49 +10,69 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
10
10
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
11
|
const logger_1 = require("../utils/logger");
|
|
12
12
|
/**
|
|
13
|
-
* Build the initial state object.
|
|
14
|
-
* everything else starts PENDING. `
|
|
13
|
+
* Build the initial state object. S00 is DONE (skeleton just created);
|
|
14
|
+
* everything else starts PENDING. `S10_wiki` is conditional on
|
|
15
15
|
* `wikiPhase` (only present when project has baseline).
|
|
16
16
|
*
|
|
17
|
-
* Phase order:
|
|
18
|
-
* →
|
|
17
|
+
* Phase order: S00_bootstrap → [S10_wiki] → S20_collect_inputs → S30_convert_docs → S40_extract_requirements
|
|
18
|
+
* → S50_generate_specs → S60_process_references → S61_confirm_baseline_extraction
|
|
19
|
+
* → S65_customize_agent_env → S70_runtime_assets → S80_seed_memory → S90_finalize
|
|
19
20
|
*
|
|
20
|
-
* Design:
|
|
21
|
+
* Design: S10_wiki comes before S20_collect_inputs because baseline wiki is the project
|
|
21
22
|
* experience foundation — it provides architectural knowledge that helps the
|
|
22
|
-
* user write better requirement documents. When no baseline,
|
|
23
|
-
* absent and the flow starts from
|
|
23
|
+
* user write better requirement documents. When no baseline, S10_wiki is
|
|
24
|
+
* absent and the flow starts from S20_collect_inputs.
|
|
24
25
|
*/
|
|
25
26
|
function buildInitialState(input) {
|
|
26
27
|
const phases = {
|
|
27
|
-
|
|
28
|
+
S00_bootstrap: { status: 'DONE' },
|
|
28
29
|
};
|
|
29
30
|
const wp = input.wikiPhase ?? 'none';
|
|
30
|
-
//
|
|
31
|
+
// S10_wiki: baseline wiki (project experience foundation) — only when baseline exists
|
|
31
32
|
if (wp === 'done') {
|
|
32
|
-
phases.
|
|
33
|
+
phases.S10_wiki = { status: 'DONE', requires_agent: false, mode: 'full', source: 'cli-full' };
|
|
33
34
|
}
|
|
34
35
|
else if (wp === 'pending') {
|
|
35
|
-
phases.
|
|
36
|
+
phases.S10_wiki = {
|
|
36
37
|
status: 'PENDING',
|
|
37
38
|
requires_agent: true,
|
|
38
39
|
mode: 'outline-only',
|
|
39
40
|
source: input.wikiSource ?? 'cli-tasks',
|
|
40
|
-
tasks_file: '
|
|
41
|
+
tasks_file: 'baseline/wiki/TASKS.md',
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
|
-
//
|
|
44
|
-
phases.
|
|
45
|
-
|
|
46
|
-
phases.
|
|
47
|
-
phases.
|
|
48
|
-
phases.
|
|
49
|
-
|
|
50
|
-
phases
|
|
51
|
-
//
|
|
52
|
-
|
|
44
|
+
// S20_collect_inputs: collect related inputs (requirements/references/optional extra skills & rules)
|
|
45
|
+
phases.S20_collect_inputs = { status: 'PENDING', manual_required: true };
|
|
46
|
+
// S30_convert_docs: convert non-md raw docs to markdown for downstream entry-ization
|
|
47
|
+
phases.S30_convert_docs = { status: 'PENDING', requires_agent: true };
|
|
48
|
+
phases.S40_extract_requirements = { status: 'PENDING', requires_agent: true };
|
|
49
|
+
phases.S50_generate_specs = { status: 'PENDING', requires_agent: true };
|
|
50
|
+
// S60: references convert + structured indexing
|
|
51
|
+
phases.S60_process_references = { status: 'PENDING', requires_agent: true };
|
|
52
|
+
// S61: explicitly confirm whether baseline auto-extraction (skills/rules) is enabled
|
|
53
|
+
phases.S61_confirm_baseline_extraction = {
|
|
54
|
+
status: 'PENDING',
|
|
55
|
+
manual_required: true,
|
|
56
|
+
baseline_auto_extract: 'UNSET',
|
|
57
|
+
};
|
|
58
|
+
// S65: merge extra-skills / extra-rules with user-confirmed conflict handling
|
|
59
|
+
phases.S65_customize_agent_env = {
|
|
60
|
+
status: 'PENDING',
|
|
61
|
+
manual_required: true,
|
|
62
|
+
incoming_total: 0,
|
|
63
|
+
skills_count: 0,
|
|
64
|
+
rules_count: 0,
|
|
65
|
+
unknown_count: 0,
|
|
66
|
+
conflicts_resolved: 0,
|
|
67
|
+
};
|
|
68
|
+
phases.S70_runtime_assets = { status: 'PENDING', requires_agent: true };
|
|
69
|
+
phases.S80_seed_memory = { status: 'PENDING', requires_agent: true };
|
|
70
|
+
phases.S90_finalize = { status: 'PENDING' };
|
|
71
|
+
// current_phase starts at S10_wiki (if present) or S20_collect_inputs (if no baseline)
|
|
72
|
+
const startPhase = wp !== 'none' ? 'S10_wiki' : 'S20_collect_inputs';
|
|
53
73
|
const startAction = wp !== 'none'
|
|
54
|
-
? '请先构建 baseline wiki
|
|
55
|
-
: '
|
|
74
|
+
? '请先构建 baseline wiki(项目经验底座),完成后进入 S20 输入文件收集。'
|
|
75
|
+
: '请先完成 S20 输入文件收集:requirements/raw、references/raw,并确认是否有额外 skills/rules。';
|
|
56
76
|
const state = {
|
|
57
77
|
project: input.name,
|
|
58
78
|
arch: input.arch,
|
package/dist/index.js
CHANGED
|
@@ -4,70 +4,124 @@ 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");
|
|
10
|
+
const convert_1 = require("./commands/convert");
|
|
11
|
+
const wizard_1 = require("./commands/wizard");
|
|
12
|
+
const config_1 = require("./config");
|
|
9
13
|
/**
|
|
10
14
|
* Default values surfaced to users (also reflected in README "全部参数" tables).
|
|
11
|
-
*
|
|
12
|
-
* Keep this list in one place so README, banner help text and command options
|
|
13
|
-
* stay in sync. Updating a default here is a *single* source of truth change.
|
|
14
15
|
*/
|
|
15
16
|
const DEFAULT_ARCH = 'android-compose';
|
|
16
17
|
const DEFAULT_AGENT = 'codechat';
|
|
17
|
-
/**
|
|
18
|
-
* Resolve `--harness-name` / `--name` (alias) into a single value and emit a
|
|
19
|
-
* deprecation warning when only the legacy `--name` form is used.
|
|
20
|
-
*/
|
|
21
18
|
function resolveHarnessName(opts) {
|
|
22
19
|
if (opts.harnessName && opts.name && opts.harnessName !== opts.name) {
|
|
23
20
|
throw new Error('同时收到 --harness-name 与 --name 但取值不同,请只保留一个(推荐 --harness-name)');
|
|
24
21
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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) {
|
|
28
27
|
logger_1.logger.warn('⚠️ --name 已重命名为 --harness-name,旧名将在下个 minor 版本移除。');
|
|
29
|
-
return opts.name;
|
|
30
28
|
}
|
|
31
|
-
|
|
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;
|
|
32
37
|
}
|
|
33
|
-
|
|
38
|
+
function buildSectionFromOpts(opts, harnessName) {
|
|
39
|
+
return {
|
|
40
|
+
harnessName,
|
|
41
|
+
arch: opts.arch,
|
|
42
|
+
agent: opts.agent,
|
|
43
|
+
baseline: opts.baseline,
|
|
44
|
+
requirements: opts.requirements,
|
|
45
|
+
references: opts.references,
|
|
46
|
+
extraSkills: opts.extraSkills,
|
|
47
|
+
baselineBranch: opts.baselineBranch,
|
|
48
|
+
baselineMaxFileKb: opts.baselineMaxFileKb,
|
|
49
|
+
convertEndpoint: opts.convertEndpoint,
|
|
50
|
+
convertConcurrency: opts.convertConcurrency,
|
|
51
|
+
convertMaxFileMb: opts.convertMaxFileMb,
|
|
52
|
+
convertTimeoutSec: opts.convertTimeoutSec,
|
|
53
|
+
convertForce: opts.convertForce,
|
|
54
|
+
force: opts.force,
|
|
55
|
+
yes: opts.yes,
|
|
56
|
+
verbose: opts.verbose,
|
|
57
|
+
generateWiki: opts.generateWiki,
|
|
58
|
+
wikiTasksOnly: opts.wikiTasksOnly,
|
|
59
|
+
wikiLang: opts.wikiLang === 'en' ? 'en' : opts.wikiLang === 'zh' ? 'zh' : undefined,
|
|
60
|
+
wikiModel: opts.wikiModel,
|
|
61
|
+
wikiBaseUrl: opts.wikiBaseUrl,
|
|
62
|
+
wikiApiKey: opts.wikiApiKey,
|
|
63
|
+
wikiSource: opts.wikiSource,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function runBuildAction(opts, cmd) {
|
|
34
67
|
try {
|
|
35
|
-
const
|
|
68
|
+
const loaded = (0, config_1.loadConfig)({ configPath: opts.config });
|
|
69
|
+
const merged = (0, config_1.mergeBuildOptions)(opts, loaded?.config.build, loaded?.config.defaults, cmd);
|
|
70
|
+
const harnessName = resolveHarnessName(merged);
|
|
36
71
|
await (0, init_1.runInit)({
|
|
37
72
|
name: harnessName,
|
|
38
|
-
arch:
|
|
39
|
-
agent:
|
|
40
|
-
baseline:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
arch: (merged.arch ?? DEFAULT_ARCH),
|
|
74
|
+
agent: (merged.agent ?? DEFAULT_AGENT),
|
|
75
|
+
baseline: merged.baseline,
|
|
76
|
+
requirements: merged.requirements,
|
|
77
|
+
references: merged.references,
|
|
78
|
+
extraSkills: merged.extraSkills,
|
|
79
|
+
baselineBranch: merged.baselineBranch,
|
|
80
|
+
baselineMaxFileKB: merged.baselineMaxFileKb,
|
|
81
|
+
convertEndpoint: merged.convertEndpoint,
|
|
82
|
+
convertConcurrency: merged.convertConcurrency,
|
|
83
|
+
convertMaxFileMB: merged.convertMaxFileMb,
|
|
84
|
+
convertTimeoutSec: merged.convertTimeoutSec,
|
|
85
|
+
convertForce: merged.convertForce,
|
|
86
|
+
force: !!merged.force,
|
|
87
|
+
yes: !!merged.yes,
|
|
88
|
+
verbose: !!merged.verbose,
|
|
89
|
+
generateWiki: !!merged.generateWiki,
|
|
90
|
+
wikiTasksOnly: !!merged.wikiTasksOnly,
|
|
91
|
+
wikiLang: merged.wikiLang === 'en' ? 'en' : merged.wikiLang === 'zh' ? 'zh' : undefined,
|
|
92
|
+
wikiModel: merged.wikiModel,
|
|
93
|
+
wikiBaseUrl: merged.wikiBaseUrl,
|
|
94
|
+
wikiApiKey: merged.wikiApiKey,
|
|
95
|
+
wikiSource: merged.wikiSource,
|
|
53
96
|
});
|
|
97
|
+
if (opts.saveConfig !== undefined && opts.saveConfig !== false) {
|
|
98
|
+
const outPath = typeof opts.saveConfig === 'string' && opts.saveConfig.trim()
|
|
99
|
+
? opts.saveConfig
|
|
100
|
+
: undefined;
|
|
101
|
+
const saved = await (0, config_1.saveConfigSection)({
|
|
102
|
+
outPath,
|
|
103
|
+
build: buildSectionFromOpts(merged, harnessName),
|
|
104
|
+
});
|
|
105
|
+
logger_1.logger.success(`当前参数已写入配置:${saved}`);
|
|
106
|
+
}
|
|
54
107
|
}
|
|
55
108
|
catch (err) {
|
|
56
109
|
logger_1.logger.error(err.message);
|
|
57
110
|
process.exitCode = 1;
|
|
58
111
|
}
|
|
59
112
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Attach all options shared between `build` and the legacy `init` alias.
|
|
62
|
-
* Centralised so the two stay byte-for-byte identical.
|
|
63
|
-
*/
|
|
64
113
|
function attachBuildOptions(cmd) {
|
|
65
114
|
return cmd
|
|
66
|
-
.option('--
|
|
115
|
+
.option('--config <path>', `从 YAML/JSON 读取参数(默认可选 ${config_1.DEFAULT_CONFIG_FILENAME})`)
|
|
116
|
+
.option('--save-config [path]', `执行成功后把参数写入配置文件(默认 ${config_1.DEFAULT_CONFIG_FILENAME})`)
|
|
117
|
+
.option('--harness-name <name>', '【必填】harness 名称(如 my-app → .my-app-harness/ 目录)')
|
|
67
118
|
.option('--name <name>', '【已废弃】--harness-name 的旧别名,下个 minor 版本移除')
|
|
68
119
|
.option('--arch <arch>', '架构模板:' + (0, validate_args_1.listSupportedArches)().join(' | '), DEFAULT_ARCH)
|
|
69
120
|
.option('--agent <agent>', '目标 Agent:' + (0, validate_args_1.listSupportedAgents)().join(' | '), DEFAULT_AGENT)
|
|
70
121
|
.option('--baseline <path|url>', '【可选】基线源码路径或 Git 仓库地址')
|
|
122
|
+
.option('--requirements <path>', '【可选】需求文档输入路径(文件或目录)')
|
|
123
|
+
.option('--references <path>', '【可选】参考资料输入路径(文件或目录)')
|
|
124
|
+
.option('--extra-skills <path...>', '【可选】额外运行期资源(skills/rules 混放,先入 _incoming)')
|
|
71
125
|
.option('--baseline-branch <name>', '【Git 模式】指定分支名(默认:main)')
|
|
72
126
|
.option('--baseline-max-file-kb <kb>', '【过滤】单文件大小上限 KB(默认:1024)', (v) => {
|
|
73
127
|
const n = Number(v);
|
|
@@ -76,16 +130,21 @@ function attachBuildOptions(cmd) {
|
|
|
76
130
|
}
|
|
77
131
|
return n;
|
|
78
132
|
})
|
|
133
|
+
.option('--convert-endpoint <url>', '【build 自动 convert】markitdown_serve 基址(默认 http://markitdown.desaysz.site)')
|
|
134
|
+
.option('--convert-concurrency <n>', '【build 自动 convert】并发上传数(默认沿用 convert)', (v) => Number(v))
|
|
135
|
+
.option('--convert-max-file-mb <n>', '【build 自动 convert】单文件大小上限 MB(默认沿用 convert)', (v) => Number(v))
|
|
136
|
+
.option('--convert-timeout-sec <n>', '【build 自动 convert】超时秒数(默认沿用 convert)', (v) => Number(v))
|
|
137
|
+
.option('--convert-force', '【build 自动 convert】覆盖已存在同名 .md')
|
|
79
138
|
.option('--force', '强制覆盖已存在的目标目录')
|
|
80
139
|
.option('-y, --yes', '跳过交互确认,使用默认值')
|
|
81
140
|
.option('--verbose', '显示详细日志')
|
|
82
141
|
.option('--generate-wiki', '【Wiki】自动生成完整 Wiki 文档(需 --baseline)')
|
|
83
|
-
.option('--wiki-tasks-only', '【Wiki】仅生成 Wiki
|
|
142
|
+
.option('--wiki-tasks-only', '【Wiki】仅生成 Wiki 大纲和任务列表(有 --baseline 时为隐式默认)')
|
|
84
143
|
.option('--wiki-lang <lang>', '【Wiki】输出语言:zh(默认)| en')
|
|
85
|
-
.option('--wiki-model <model>', '【Wiki】AI
|
|
144
|
+
.option('--wiki-model <model>', '【Wiki】AI 模型名称')
|
|
86
145
|
.option('--wiki-base-url <url>', '【Wiki】OpenAI 兼容 API 地址')
|
|
87
146
|
.option('--wiki-api-key <key>', '【Wiki】API 密钥')
|
|
88
|
-
.option('--wiki-source <path>', '【Wiki
|
|
147
|
+
.option('--wiki-source <path>', '【Wiki】扫描的源码根路径');
|
|
89
148
|
}
|
|
90
149
|
function main() {
|
|
91
150
|
const program = new commander_1.Command();
|
|
@@ -97,132 +156,145 @@ function main() {
|
|
|
97
156
|
.helpOption('-h, --help', '显示帮助信息');
|
|
98
157
|
const banner = [
|
|
99
158
|
'',
|
|
100
|
-
'
|
|
101
|
-
'║ svharness v' + version.padEnd(33) + '
|
|
102
|
-
'
|
|
103
|
-
'║ 规格驱动开发(SDD)AI 协作框架脚手架
|
|
104
|
-
'║ 为任意 Agent IDE 生成项目本地知识层骨架(harness)
|
|
105
|
-
'
|
|
106
|
-
'║ 支持架构:android-compose | android-xml | cpp | web-react | python
|
|
107
|
-
'║ 支持 Agent:codechat | qoder | cursor | claude-code | generic
|
|
108
|
-
'║ 运行要求:Node.js ≥ 18
|
|
109
|
-
'
|
|
159
|
+
'╔══════════════════════════════════════════════════════════════════════════',
|
|
160
|
+
'║ svharness v' + version.padEnd(33) + '',
|
|
161
|
+
'╠══════════════════════════════════════════════════════════════════════════',
|
|
162
|
+
'║ 规格驱动开发(SDD)AI 协作框架脚手架 ',
|
|
163
|
+
'║ 为任意 Agent IDE 生成项目本地知识层骨架(harness) ',
|
|
164
|
+
'╠══════════════════════════════════════════════════════════════════════════',
|
|
165
|
+
'║ 支持架构:android-compose | android-xml | cpp | web-react | python ',
|
|
166
|
+
'║ 支持 Agent:codechat | qoder | cursor | claude-code | opencode | generic ',
|
|
167
|
+
'║ 运行要求:Node.js ≥ 18 ',
|
|
168
|
+
'╚══════════════════════════════════════════════════════════════════════════',
|
|
110
169
|
'',
|
|
111
170
|
].join('\n');
|
|
112
171
|
program.addHelpText('beforeAll', banner);
|
|
113
172
|
program.addHelpText('afterAll', [
|
|
114
173
|
'',
|
|
115
|
-
'
|
|
116
|
-
'│
|
|
117
|
-
'
|
|
118
|
-
'│
|
|
119
|
-
'│
|
|
120
|
-
'│
|
|
121
|
-
'│
|
|
122
|
-
'│
|
|
123
|
-
'│
|
|
124
|
-
'│
|
|
125
|
-
'│
|
|
126
|
-
'│
|
|
127
|
-
'│
|
|
128
|
-
'│
|
|
129
|
-
'│
|
|
130
|
-
'│
|
|
131
|
-
'│
|
|
132
|
-
'│
|
|
133
|
-
'│
|
|
134
|
-
'
|
|
135
|
-
'
|
|
136
|
-
'│ │',
|
|
137
|
-
'│ # 方式四:把已构建好的 harness 绑定到目标项目 │',
|
|
138
|
-
'│ svharness apply \\ │',
|
|
139
|
-
'│ --harness ./my-app-harness \\ │',
|
|
140
|
-
'│ --target . \\ │',
|
|
141
|
-
'│ --yes │',
|
|
142
|
-
'│ │',
|
|
143
|
-
'└──────────────────────────────────────────────────────────────────────────┘',
|
|
144
|
-
'',
|
|
145
|
-
'┌──────────────────────────────────────────────────────────────────────────┐',
|
|
146
|
-
'│ 📋 build 参数说明(含默认值) │',
|
|
147
|
-
'├──────────────────────────────────────────────────────────────────────────┤',
|
|
148
|
-
'│ │',
|
|
149
|
-
'│ 必填参数: │',
|
|
150
|
-
'│ --harness-name <name> harness 名称(小写字母、数字、中连线) │',
|
|
151
|
-
'│ │',
|
|
152
|
-
'│ 含默认值参数: │',
|
|
153
|
-
'│ --arch <架构> 默认:android-compose │',
|
|
154
|
-
'│ --agent <Agent> 默认:codechat │',
|
|
155
|
-
'│ --wiki-lang <语言> 默认:zh │',
|
|
156
|
-
'│ --baseline-branch 默认:main(仅 git 模式) │',
|
|
157
|
-
'│ --baseline-max-file-kb 默认:1024 │',
|
|
158
|
-
'│ │',
|
|
159
|
-
'│ 可选开关: │',
|
|
160
|
-
'│ --baseline <路径|URL> 本地目录或 Git 仓库地址 │',
|
|
161
|
-
'│ --generate-wiki 自动生成完整 Wiki │',
|
|
162
|
-
'│ --wiki-tasks-only 仅生成 Wiki 大纲(有 --baseline 时为隐式默认) │',
|
|
163
|
-
'│ --force 覆盖已存在的目标目录 │',
|
|
164
|
-
'│ -y, --yes 跳过交互确认 │',
|
|
165
|
-
'│ --verbose 显示详细日志 │',
|
|
166
|
-
'│ │',
|
|
167
|
-
'└──────────────────────────────────────────────────────────────────────────┘',
|
|
168
|
-
'',
|
|
169
|
-
'┌──────────────────────────────────────────────────────────────────────────┐',
|
|
170
|
-
'│ 📋 apply 参数说明(含默认值) │',
|
|
171
|
-
'├──────────────────────────────────────────────────────────────────────────┤',
|
|
172
|
-
'│ │',
|
|
173
|
-
'│ 必填参数: │',
|
|
174
|
-
'│ --harness <path> 已构建好的 harness 目录路径 │',
|
|
175
|
-
'│ │',
|
|
176
|
-
'│ 含默认值参数: │',
|
|
177
|
-
'│ --target <path> 默认:当前工作目录(cwd) │',
|
|
178
|
-
'│ --agent <agent> 默认:从 <harness>/.harness-build-state.yaml 读取│',
|
|
179
|
-
'│ │',
|
|
180
|
-
'│ 可选开关: │',
|
|
181
|
-
'│ --clone 完整拷贝 harness 到目标项目(默认 bind-only) │',
|
|
182
|
-
'│ --force 覆盖已存在的 dispatcher skill 目录 │',
|
|
183
|
-
'│ -y, --yes 跳过交互确认 │',
|
|
184
|
-
'│ --verbose 显示详细日志 │',
|
|
185
|
-
'│ │',
|
|
186
|
-
'└──────────────────────────────────────────────────────────────────────────┘',
|
|
187
|
-
'',
|
|
188
|
-
' 📚 完整文档:https://yesv-desaysv.feishu.cn/docx/YEIHdD4NGooMdrxQO4McqwfNnjf?pre_pathname=%2Fdrive%2Fhome%2F',
|
|
174
|
+
'┌──────────────────────────────────────────────────────────────────────────',
|
|
175
|
+
'│ 快速上手, 帮助:svharness <command> --help 或 svharness help <command> ',
|
|
176
|
+
'├──────────────────────────────────────────────────────────────────────────',
|
|
177
|
+
'│ # 方式零:交互式向导(推荐新手) ',
|
|
178
|
+
'│ svharness wizard ',
|
|
179
|
+
'│ ',
|
|
180
|
+
'│ # 方式二:仅 --harness-name 必填 ',
|
|
181
|
+
'│ svharness build --harness-name my-app ',
|
|
182
|
+
'│ ',
|
|
183
|
+
'│ # 方式三:本地基线 + wiki + requirements + references ',
|
|
184
|
+
'│ svharness build --harness-name my-app --baseline ./src \ ',
|
|
185
|
+
'│ --requirements ./requirements.xlsx --references ./references.pdf ',
|
|
186
|
+
'│ ',
|
|
187
|
+
'│ # 方式四:绑定 harness 到目标项目 ',
|
|
188
|
+
'│ svharness apply --harness ./my-app-harness --target . ',
|
|
189
|
+
'│ ',
|
|
190
|
+
'│ # 方式五:文档转 Markdown ',
|
|
191
|
+
'│ svharness convert --input ./docs/*.pdf --output ./docs/md --type requirements',
|
|
192
|
+
'│ ',
|
|
193
|
+
'└──────────────────────────────────────────────────────────────────────────',
|
|
194
|
+
' 📚 使用说明文档:https://yesv-desaysv.feishu.cn/docx/OEBwdywTfoncPNxPZ0acClNincg',
|
|
189
195
|
'',
|
|
190
196
|
].join('\n'));
|
|
191
|
-
// ── build:主命令(v0.8+) ───────────────────────────────────────────────
|
|
192
197
|
attachBuildOptions(program
|
|
193
198
|
.command('build')
|
|
194
|
-
.description('构建 harness
|
|
195
|
-
await runBuildAction(opts);
|
|
199
|
+
.description('构建 harness:生成目录骨架、渲染元文件、注入 harness-build skill 与构建规则。')).action(async (opts, cmd) => {
|
|
200
|
+
await runBuildAction(opts, cmd);
|
|
196
201
|
});
|
|
197
|
-
// ── init:隐藏别名(兼容老脚本,下个 minor 版本移除) ─────────────────────
|
|
198
202
|
attachBuildOptions(program
|
|
199
203
|
.command('init', { hidden: true })
|
|
200
|
-
.description('【已废弃别名】等同于
|
|
204
|
+
.description('【已废弃别名】等同于 build')).action(async (opts, cmd) => {
|
|
201
205
|
logger_1.logger.warn('⚠️ `svharness init` 已重命名为 `svharness build`,旧名将在下个 minor 版本移除。');
|
|
202
|
-
await runBuildAction(opts);
|
|
206
|
+
await runBuildAction(opts, cmd);
|
|
207
|
+
});
|
|
208
|
+
program
|
|
209
|
+
.command('wizard')
|
|
210
|
+
.description('交互式向导:分步填写路径与说明,可保存到 svharness.config.yaml 并/或立即执行 build/apply/convert。')
|
|
211
|
+
.action(async () => {
|
|
212
|
+
try {
|
|
213
|
+
await (0, wizard_1.runWizard)();
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
logger_1.logger.error(err.message);
|
|
217
|
+
process.exitCode = 1;
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
program
|
|
221
|
+
.command('convert')
|
|
222
|
+
.description('把本地原始文档通过 markitdown_serve 转为 Markdown,产物落到 <harness>/<type>/md/。')
|
|
223
|
+
.option('--config <path>', `从配置文件读取 convert 节(默认 ${config_1.DEFAULT_CONFIG_FILENAME})`)
|
|
224
|
+
.option('--input <path...>', '【可选,默认 .】源文件、目录或 glob')
|
|
225
|
+
.option('--harness <path>', '【可选】目标 harness 目录')
|
|
226
|
+
.option('--output <path>', '【可选,默认 .】Markdown 输出目录;含 harness.yaml 时写入 <output>/<type>/md/')
|
|
227
|
+
.option('--endpoint <url>', 'markitdown_serve 基址')
|
|
228
|
+
.option('--concurrency <n>', '【默认 3】并发上传数', (v) => Number(v))
|
|
229
|
+
.option('--max-file-mb <n>', '【默认 50】单文件大小上限(MB)', (v) => Number(v))
|
|
230
|
+
.option('--timeout-sec <n>', '【默认 120】超时秒数', (v) => Number(v))
|
|
231
|
+
.option('--type <type>', 'requirements | references')
|
|
232
|
+
.option('--force', '覆盖已存在同名 .md')
|
|
233
|
+
.option('-y, --yes', '跳过交互确认')
|
|
234
|
+
.option('--verbose', '显示详细日志')
|
|
235
|
+
.action(async (opts, cmd) => {
|
|
236
|
+
try {
|
|
237
|
+
const loaded = (0, config_1.loadConfig)({ configPath: opts.config });
|
|
238
|
+
const merged = (0, config_1.mergeConvertOptions)({
|
|
239
|
+
input: opts.input,
|
|
240
|
+
harness: opts.harness,
|
|
241
|
+
output: opts.output,
|
|
242
|
+
endpoint: opts.endpoint,
|
|
243
|
+
concurrency: opts.concurrency,
|
|
244
|
+
maxFileMb: opts.maxFileMb,
|
|
245
|
+
timeoutSec: opts.timeoutSec,
|
|
246
|
+
type: opts.type,
|
|
247
|
+
force: opts.force,
|
|
248
|
+
yes: opts.yes,
|
|
249
|
+
verbose: opts.verbose,
|
|
250
|
+
}, loaded?.config.convert, loaded?.config.defaults, cmd);
|
|
251
|
+
await (0, convert_1.runConvert)({
|
|
252
|
+
input: merged.input ?? [],
|
|
253
|
+
harness: merged.harness,
|
|
254
|
+
output: merged.output,
|
|
255
|
+
endpoint: merged.endpoint,
|
|
256
|
+
concurrency: merged.concurrency,
|
|
257
|
+
maxFileMB: merged.maxFileMb,
|
|
258
|
+
timeoutSec: merged.timeoutSec,
|
|
259
|
+
type: merged.type,
|
|
260
|
+
force: !!merged.force,
|
|
261
|
+
yes: !!merged.yes,
|
|
262
|
+
verbose: !!merged.verbose,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
logger_1.logger.error(err.message);
|
|
267
|
+
process.exitCode = 1;
|
|
268
|
+
}
|
|
203
269
|
});
|
|
204
270
|
program
|
|
205
271
|
.command('apply')
|
|
206
|
-
.description('把已构建好的 harness
|
|
207
|
-
.
|
|
272
|
+
.description('把已构建好的 harness 绑定到目标项目(注入 harness-apply-skills-main,内联 apply_skill_registry)。')
|
|
273
|
+
.option('--config <path>', `从配置文件读取 apply 节(默认 ${config_1.DEFAULT_CONFIG_FILENAME})`)
|
|
274
|
+
.option('--harness <path>', '已构建好的 harness 目录路径(可在配置文件的 apply.harness 中提供)')
|
|
208
275
|
.option('--target <path>', '目标项目根目录(默认:当前工作目录)')
|
|
209
276
|
.option('--agent <agent>', '目标 Agent:' +
|
|
210
277
|
(0, validate_args_1.listSupportedAgents)().join(' | ') +
|
|
211
|
-
'(省略时从 harness
|
|
278
|
+
'(省略时从 harness 状态文件读取)')
|
|
212
279
|
.option('--force', '覆盖已存在的 dispatcher skill 目录')
|
|
213
|
-
.option('--clone', '
|
|
280
|
+
.option('--clone', '拷贝整个 harness 到目标项目(默认 bind-only)')
|
|
214
281
|
.option('-y, --yes', '跳过交互确认')
|
|
215
282
|
.option('--verbose', '显示详细日志')
|
|
216
|
-
.action(async (opts) => {
|
|
283
|
+
.action(async (opts, cmd) => {
|
|
217
284
|
try {
|
|
285
|
+
const loaded = (0, config_1.loadConfig)({ configPath: opts.config });
|
|
286
|
+
const merged = (0, config_1.mergeApplyOptions)(opts, loaded?.config.apply, loaded?.config.defaults, cmd);
|
|
287
|
+
if (!merged.harness) {
|
|
288
|
+
throw new Error('--harness <path> 是必填参数(可在配置文件的 apply.harness 中提供)');
|
|
289
|
+
}
|
|
218
290
|
await (0, apply_1.runApply)({
|
|
219
|
-
harness:
|
|
220
|
-
target:
|
|
221
|
-
agent:
|
|
222
|
-
force: !!
|
|
223
|
-
clone: !!
|
|
224
|
-
yes: !!
|
|
225
|
-
verbose: !!
|
|
291
|
+
harness: merged.harness,
|
|
292
|
+
target: merged.target,
|
|
293
|
+
agent: merged.agent,
|
|
294
|
+
force: !!merged.force,
|
|
295
|
+
clone: !!merged.clone,
|
|
296
|
+
yes: !!merged.yes,
|
|
297
|
+
verbose: !!merged.verbose,
|
|
226
298
|
});
|
|
227
299
|
}
|
|
228
300
|
catch (err) {
|
|
@@ -230,7 +302,6 @@ function main() {
|
|
|
230
302
|
process.exitCode = 1;
|
|
231
303
|
}
|
|
232
304
|
});
|
|
233
|
-
// Parse argv. Use process.argv directly so --help and --version work as-is.
|
|
234
305
|
program.parseAsync(process.argv).catch((err) => {
|
|
235
306
|
logger_1.logger.error(err.message);
|
|
236
307
|
process.exitCode = 1;
|
|
@@ -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
|
+
}
|