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
|
@@ -0,0 +1,47 @@
|
|
|
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.runReferencesApplySkills = runReferencesApplySkills;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
9
|
+
const logger_1 = require("../utils/logger");
|
|
10
|
+
const reference_apply_skills_1 = require("../core/reference-apply-skills");
|
|
11
|
+
async function runReferencesApplySkills(opts) {
|
|
12
|
+
(0, logger_1.setVerbose)(!!opts.verbose);
|
|
13
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
14
|
+
const harnessRoot = node_path_1.default.resolve(cwd, opts.harness);
|
|
15
|
+
logger_1.logger.configBox('references → apply skills', [
|
|
16
|
+
{ label: 'harness', value: harnessRoot },
|
|
17
|
+
{ label: 'force', value: opts.force ? 'yes' : 'no' },
|
|
18
|
+
]);
|
|
19
|
+
if (!opts.yes) {
|
|
20
|
+
const { ok } = await (0, prompts_1.default)({
|
|
21
|
+
type: 'confirm',
|
|
22
|
+
name: 'ok',
|
|
23
|
+
message: '根据 references/yaml/index.yaml 生成 harness-apply-skills-* 并写入 registry?',
|
|
24
|
+
initial: true,
|
|
25
|
+
});
|
|
26
|
+
if (!ok) {
|
|
27
|
+
logger_1.logger.warn('用户取消');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const result = await (0, reference_apply_skills_1.materializeReferenceApplySkills)(harnessRoot, {
|
|
32
|
+
force: opts.force,
|
|
33
|
+
});
|
|
34
|
+
logger_1.logger.success(`registry: ${node_path_1.default.relative(harnessRoot, result.registryPath)}`);
|
|
35
|
+
if (result.created.length > 0) {
|
|
36
|
+
logger_1.logger.success(`新建 skill: ${result.created.join(', ')}`);
|
|
37
|
+
}
|
|
38
|
+
if (result.updated.length > 0) {
|
|
39
|
+
logger_1.logger.success(`更新 skill: ${result.updated.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
if (result.skipped.length > 0) {
|
|
42
|
+
logger_1.logger.warn(`跳过(已存在,未使用 --force): ${result.skipped.join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
logger_1.logger.plain('');
|
|
45
|
+
logger_1.logger.plain('下一步:svharness apply --harness <path> --target <project> --yes');
|
|
46
|
+
logger_1.logger.plain(' apply 会把 registry 中的 skill 注入目标 Agent,并注册到 harness-apply-skills-main');
|
|
47
|
+
}
|
|
@@ -0,0 +1,442 @@
|
|
|
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.runWizard = runWizard;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
10
|
+
const validate_args_1 = require("../utils/validate-args");
|
|
11
|
+
const harness_name_1 = require("../utils/harness-name");
|
|
12
|
+
const logger_1 = require("../utils/logger");
|
|
13
|
+
const save_config_1 = require("../config/save-config");
|
|
14
|
+
const init_1 = require("./init");
|
|
15
|
+
const apply_1 = require("./apply");
|
|
16
|
+
const convert_1 = require("./convert");
|
|
17
|
+
const DEFAULT_ARCH = 'android-compose';
|
|
18
|
+
const DEFAULT_AGENT = 'codechat';
|
|
19
|
+
function validateExistingPath(cwd, raw) {
|
|
20
|
+
const value = raw.trim();
|
|
21
|
+
if (!value)
|
|
22
|
+
return '路径不能为空';
|
|
23
|
+
const abs = node_path_1.default.resolve(cwd, value);
|
|
24
|
+
if (!node_fs_1.default.existsSync(abs))
|
|
25
|
+
return `路径不存在: ${abs}`;
|
|
26
|
+
const stat = node_fs_1.default.statSync(abs);
|
|
27
|
+
if (!stat.isFile() && !stat.isDirectory()) {
|
|
28
|
+
return `路径必须是文件或目录: ${abs}`;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
async function askTarget() {
|
|
33
|
+
const { target } = await (0, prompts_1.default)({
|
|
34
|
+
type: 'select',
|
|
35
|
+
name: 'target',
|
|
36
|
+
message: '选择要配置/执行的命令',
|
|
37
|
+
choices: [
|
|
38
|
+
{ title: 'build — 创建 harness 骨架', value: 'build' },
|
|
39
|
+
{ title: 'apply — 绑定 harness 到目标项目', value: 'apply' },
|
|
40
|
+
{ title: 'convert — 文档转 Markdown', value: 'convert' },
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
return target ?? null;
|
|
44
|
+
}
|
|
45
|
+
async function askFinishAction() {
|
|
46
|
+
const { action } = await (0, prompts_1.default)({
|
|
47
|
+
type: 'select',
|
|
48
|
+
name: 'action',
|
|
49
|
+
message: '下一步',
|
|
50
|
+
choices: [
|
|
51
|
+
{ title: '立即执行', value: 'run' },
|
|
52
|
+
{ title: '仅保存到 svharness.config.yaml', value: 'save' },
|
|
53
|
+
{ title: '保存并执行', value: 'both' },
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
return action ?? null;
|
|
57
|
+
}
|
|
58
|
+
async function runBuildWizard(cwd) {
|
|
59
|
+
const arches = [...(0, validate_args_1.listSupportedArches)()];
|
|
60
|
+
const agents = [...(0, validate_args_1.listSupportedAgents)()];
|
|
61
|
+
const answers = await (0, prompts_1.default)([
|
|
62
|
+
{
|
|
63
|
+
type: 'text',
|
|
64
|
+
name: 'harnessName',
|
|
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)) || '名称格式无效',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'select',
|
|
70
|
+
name: 'arch',
|
|
71
|
+
message: '架构模板',
|
|
72
|
+
choices: arches.map((a) => ({ title: a, value: a })),
|
|
73
|
+
initial: arches.indexOf(DEFAULT_ARCH),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'select',
|
|
77
|
+
name: 'agent',
|
|
78
|
+
message: '目标 Agent',
|
|
79
|
+
choices: agents.map((a) => ({ title: a, value: a })),
|
|
80
|
+
initial: agents.indexOf(DEFAULT_AGENT),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: 'confirm',
|
|
84
|
+
name: 'useBaseline',
|
|
85
|
+
message: '是否使用 baseline 源码?',
|
|
86
|
+
initial: false,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: (_prev, values) => values.useBaseline ? 'text' : null,
|
|
90
|
+
name: 'baseline',
|
|
91
|
+
message: 'baseline 路径或 Git URL',
|
|
92
|
+
validate: (v) => {
|
|
93
|
+
const value = v.trim();
|
|
94
|
+
if (!value)
|
|
95
|
+
return '请输入 baseline 路径或 Git URL';
|
|
96
|
+
if ((0, validate_args_1.detectBaselineMode)(value) === 'local') {
|
|
97
|
+
return validateExistingPath(cwd, value);
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
type: (_prev, values) => values.useBaseline &&
|
|
104
|
+
typeof values.baseline === 'string' &&
|
|
105
|
+
values.baseline.trim() &&
|
|
106
|
+
(0, validate_args_1.detectBaselineMode)(values.baseline) === 'git'
|
|
107
|
+
? 'text'
|
|
108
|
+
: null,
|
|
109
|
+
name: 'baselineBranch',
|
|
110
|
+
message: 'Git 分支(可选,默认 main)',
|
|
111
|
+
initial: 'main',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'confirm',
|
|
115
|
+
name: 'useRequirements',
|
|
116
|
+
message: '是否导入需求文档?',
|
|
117
|
+
initial: false,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: (_prev, values) => values.useRequirements ? 'text' : null,
|
|
121
|
+
name: 'requirements',
|
|
122
|
+
message: '需求文档路径(文件/目录)',
|
|
123
|
+
validate: (v) => validateExistingPath(cwd, v),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: (_prev, values) => values.useRequirements ? 'text' : null,
|
|
127
|
+
name: 'requirementsNote',
|
|
128
|
+
message: '需求说明(可选,写入配置文件供团队阅读)',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: 'confirm',
|
|
132
|
+
name: 'useReferences',
|
|
133
|
+
message: '是否导入参考资料?',
|
|
134
|
+
initial: false,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
type: (_prev, values) => values.useReferences ? 'text' : null,
|
|
138
|
+
name: 'references',
|
|
139
|
+
message: '参考资料路径(文件/目录)',
|
|
140
|
+
validate: (v) => validateExistingPath(cwd, v),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: (_prev, values) => values.useReferences ? 'text' : null,
|
|
144
|
+
name: 'referencesNote',
|
|
145
|
+
message: '参考资料说明(可选)',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: 'confirm',
|
|
149
|
+
name: 'useExtraSkills',
|
|
150
|
+
message: '是否导入额外 skills/rules?',
|
|
151
|
+
initial: false,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: (_prev, values) => values.useExtraSkills ? 'text' : null,
|
|
155
|
+
name: 'extraSkillsRaw',
|
|
156
|
+
message: '额外资源路径(多个用逗号分隔)',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: (_prev, values) => values.useExtraSkills ? 'text' : null,
|
|
160
|
+
name: 'extraSkillsNote',
|
|
161
|
+
message: '额外资源说明(可选)',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
type: (_prev, values) => values.useBaseline ? 'select' : null,
|
|
165
|
+
name: 'wikiMode',
|
|
166
|
+
message: 'Wiki 生成模式',
|
|
167
|
+
choices: [
|
|
168
|
+
{ title: '禁用', value: 'off' },
|
|
169
|
+
{ title: '仅任务清单(默认,有 baseline 时)', value: 'tasks' },
|
|
170
|
+
{ title: '完整生成', value: 'full' },
|
|
171
|
+
],
|
|
172
|
+
initial: 1,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
type: 'confirm',
|
|
176
|
+
name: 'force',
|
|
177
|
+
message: '覆盖已存在的 harness 目录?',
|
|
178
|
+
initial: false,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
type: 'confirm',
|
|
182
|
+
name: 'yes',
|
|
183
|
+
message: '跳过最终确认提示?',
|
|
184
|
+
initial: true,
|
|
185
|
+
},
|
|
186
|
+
]);
|
|
187
|
+
if (!answers.harnessName) {
|
|
188
|
+
logger_1.logger.warn('用户取消');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const buildSection = {
|
|
192
|
+
harnessName: (0, harness_name_1.normalizeHarnessName)(String(answers.harnessName).trim()),
|
|
193
|
+
arch: answers.arch,
|
|
194
|
+
agent: answers.agent,
|
|
195
|
+
force: !!answers.force,
|
|
196
|
+
yes: !!answers.yes,
|
|
197
|
+
};
|
|
198
|
+
if (answers.useBaseline && answers.baseline) {
|
|
199
|
+
buildSection.baseline = String(answers.baseline).trim();
|
|
200
|
+
if (answers.baselineBranch) {
|
|
201
|
+
buildSection.baselineBranch = String(answers.baselineBranch).trim();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (answers.useRequirements && answers.requirements) {
|
|
205
|
+
buildSection.requirements = String(answers.requirements).trim();
|
|
206
|
+
if (answers.requirementsNote) {
|
|
207
|
+
buildSection.requirementsNote = String(answers.requirementsNote).trim();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (answers.useReferences && answers.references) {
|
|
211
|
+
buildSection.references = String(answers.references).trim();
|
|
212
|
+
if (answers.referencesNote) {
|
|
213
|
+
buildSection.referencesNote = String(answers.referencesNote).trim();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (answers.useExtraSkills && answers.extraSkillsRaw) {
|
|
217
|
+
buildSection.extraSkills = String(answers.extraSkillsRaw)
|
|
218
|
+
.split(',')
|
|
219
|
+
.map((s) => s.trim())
|
|
220
|
+
.filter(Boolean);
|
|
221
|
+
if (answers.extraSkillsNote) {
|
|
222
|
+
buildSection.extraSkillsNote = String(answers.extraSkillsNote).trim();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (answers.wikiMode === 'full') {
|
|
226
|
+
buildSection.generateWiki = true;
|
|
227
|
+
}
|
|
228
|
+
else if (answers.wikiMode === 'tasks') {
|
|
229
|
+
buildSection.wikiTasksOnly = true;
|
|
230
|
+
}
|
|
231
|
+
const finish = await askFinishAction();
|
|
232
|
+
if (!finish) {
|
|
233
|
+
logger_1.logger.warn('用户取消');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (finish === 'save' || finish === 'both') {
|
|
237
|
+
const out = await (0, save_config_1.saveConfigSection)({ cwd, build: buildSection });
|
|
238
|
+
logger_1.logger.success(`配置已保存:${out}`);
|
|
239
|
+
}
|
|
240
|
+
if (finish === 'run' || finish === 'both') {
|
|
241
|
+
await (0, init_1.runInit)({
|
|
242
|
+
name: buildSection.harnessName,
|
|
243
|
+
arch: (buildSection.arch ?? DEFAULT_ARCH),
|
|
244
|
+
agent: (buildSection.agent ?? DEFAULT_AGENT),
|
|
245
|
+
baseline: buildSection.baseline,
|
|
246
|
+
requirements: buildSection.requirements,
|
|
247
|
+
references: buildSection.references,
|
|
248
|
+
extraSkills: buildSection.extraSkills,
|
|
249
|
+
baselineBranch: buildSection.baselineBranch,
|
|
250
|
+
force: buildSection.force,
|
|
251
|
+
yes: buildSection.yes,
|
|
252
|
+
generateWiki: buildSection.generateWiki,
|
|
253
|
+
wikiTasksOnly: buildSection.wikiTasksOnly,
|
|
254
|
+
cwd,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function runApplyWizard(cwd) {
|
|
259
|
+
const agents = [...(0, validate_args_1.listSupportedAgents)()];
|
|
260
|
+
const answers = await (0, prompts_1.default)([
|
|
261
|
+
{
|
|
262
|
+
type: 'text',
|
|
263
|
+
name: 'harness',
|
|
264
|
+
message: '已构建的 harness 目录路径',
|
|
265
|
+
initial: './my-app-harness',
|
|
266
|
+
validate: (v) => (v.trim() ? true : '路径不能为空'),
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
type: 'text',
|
|
270
|
+
name: 'target',
|
|
271
|
+
message: '目标项目根目录',
|
|
272
|
+
initial: '.',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
type: 'select',
|
|
276
|
+
name: 'agent',
|
|
277
|
+
message: '目标 Agent(可选,回车使用 harness 状态文件)',
|
|
278
|
+
choices: [
|
|
279
|
+
{ title: '(从 .harness-build-state.yaml 读取)', value: '' },
|
|
280
|
+
...agents.map((a) => ({ title: a, value: a })),
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
type: 'confirm',
|
|
285
|
+
name: 'clone',
|
|
286
|
+
message: '克隆整个 harness 到目标项目?',
|
|
287
|
+
initial: false,
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
type: 'confirm',
|
|
291
|
+
name: 'force',
|
|
292
|
+
message: '覆盖已存在的 dispatcher skill?',
|
|
293
|
+
initial: false,
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
type: 'confirm',
|
|
297
|
+
name: 'yes',
|
|
298
|
+
message: '跳过最终确认?',
|
|
299
|
+
initial: true,
|
|
300
|
+
},
|
|
301
|
+
]);
|
|
302
|
+
if (!answers.harness) {
|
|
303
|
+
logger_1.logger.warn('用户取消');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const applySection = {
|
|
307
|
+
harness: node_path_1.default.resolve(cwd, String(answers.harness).trim()),
|
|
308
|
+
target: node_path_1.default.resolve(cwd, String(answers.target || '.').trim()),
|
|
309
|
+
clone: !!answers.clone,
|
|
310
|
+
force: !!answers.force,
|
|
311
|
+
yes: !!answers.yes,
|
|
312
|
+
};
|
|
313
|
+
const agentVal = String(answers.agent ?? '').trim();
|
|
314
|
+
if (agentVal && (0, validate_args_1.listSupportedAgents)().includes(agentVal)) {
|
|
315
|
+
applySection.agent = agentVal;
|
|
316
|
+
}
|
|
317
|
+
const finish = await askFinishAction();
|
|
318
|
+
if (!finish) {
|
|
319
|
+
logger_1.logger.warn('用户取消');
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (finish === 'save' || finish === 'both') {
|
|
323
|
+
const out = await (0, save_config_1.saveConfigSection)({ cwd, apply: applySection });
|
|
324
|
+
logger_1.logger.success(`配置已保存:${out}`);
|
|
325
|
+
}
|
|
326
|
+
if (finish === 'run' || finish === 'both') {
|
|
327
|
+
await (0, apply_1.runApply)({
|
|
328
|
+
harness: applySection.harness,
|
|
329
|
+
target: applySection.target,
|
|
330
|
+
agent: applySection.agent,
|
|
331
|
+
clone: applySection.clone,
|
|
332
|
+
force: applySection.force,
|
|
333
|
+
yes: applySection.yes,
|
|
334
|
+
cwd,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function runConvertWizard(cwd) {
|
|
339
|
+
const answers = await (0, prompts_1.default)([
|
|
340
|
+
{
|
|
341
|
+
type: 'text',
|
|
342
|
+
name: 'inputRaw',
|
|
343
|
+
message: '源文件/目录/glob(多个用逗号分隔)',
|
|
344
|
+
initial: '.',
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
type: 'text',
|
|
348
|
+
name: 'output',
|
|
349
|
+
message: '输出目录(独立模式=最终 md 目录;harness 模式=含 harness.yaml 的根目录,默认当前目录)',
|
|
350
|
+
initial: '.',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
type: 'select',
|
|
354
|
+
name: 'type',
|
|
355
|
+
message: '转换目标类型',
|
|
356
|
+
choices: [
|
|
357
|
+
{ title: 'requirements', value: 'requirements' },
|
|
358
|
+
{ title: 'references', value: 'references' },
|
|
359
|
+
],
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
type: 'text',
|
|
363
|
+
name: 'endpoint',
|
|
364
|
+
message: 'markitdown 服务地址(可选)',
|
|
365
|
+
initial: '',
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
type: 'confirm',
|
|
369
|
+
name: 'force',
|
|
370
|
+
message: '覆盖已存在同名 .md?',
|
|
371
|
+
initial: false,
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
type: 'confirm',
|
|
375
|
+
name: 'yes',
|
|
376
|
+
message: '跳过上传前确认?',
|
|
377
|
+
initial: true,
|
|
378
|
+
},
|
|
379
|
+
]);
|
|
380
|
+
if (answers.inputRaw === undefined) {
|
|
381
|
+
logger_1.logger.warn('用户取消');
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const convertSection = {
|
|
385
|
+
input: String(answers.inputRaw)
|
|
386
|
+
.split(',')
|
|
387
|
+
.map((s) => s.trim())
|
|
388
|
+
.filter(Boolean),
|
|
389
|
+
output: node_path_1.default.resolve(cwd, String(answers.output || '.').trim()),
|
|
390
|
+
type: answers.type ?? 'requirements',
|
|
391
|
+
force: !!answers.force,
|
|
392
|
+
yes: !!answers.yes,
|
|
393
|
+
};
|
|
394
|
+
if (answers.endpoint) {
|
|
395
|
+
convertSection.endpoint = String(answers.endpoint).trim();
|
|
396
|
+
}
|
|
397
|
+
const finish = await askFinishAction();
|
|
398
|
+
if (!finish) {
|
|
399
|
+
logger_1.logger.warn('用户取消');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (finish === 'save' || finish === 'both') {
|
|
403
|
+
const out = await (0, save_config_1.saveConfigSection)({ cwd, convert: convertSection });
|
|
404
|
+
logger_1.logger.success(`配置已保存:${out}`);
|
|
405
|
+
}
|
|
406
|
+
if (finish === 'run' || finish === 'both') {
|
|
407
|
+
await (0, convert_1.runConvert)({
|
|
408
|
+
input: convertSection.input ?? [],
|
|
409
|
+
output: convertSection.output,
|
|
410
|
+
endpoint: convertSection.endpoint,
|
|
411
|
+
type: convertSection.type,
|
|
412
|
+
force: convertSection.force,
|
|
413
|
+
yes: convertSection.yes,
|
|
414
|
+
cwd,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Interactive TUI wizard for build / apply / convert.
|
|
420
|
+
*/
|
|
421
|
+
async function runWizard(opts = {}) {
|
|
422
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
423
|
+
logger_1.logger.plain('');
|
|
424
|
+
logger_1.logger.plain('svharness wizard — 分步配置(路径 + 说明均可填写)');
|
|
425
|
+
logger_1.logger.plain('');
|
|
426
|
+
const target = await askTarget();
|
|
427
|
+
if (!target) {
|
|
428
|
+
logger_1.logger.warn('用户取消');
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
switch (target) {
|
|
432
|
+
case 'build':
|
|
433
|
+
await runBuildWizard(cwd);
|
|
434
|
+
break;
|
|
435
|
+
case 'apply':
|
|
436
|
+
await runApplyWizard(cwd);
|
|
437
|
+
break;
|
|
438
|
+
case 'convert':
|
|
439
|
+
await runConvertWizard(cwd);
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONFIG_SCHEMA_VERSION = exports.DEFAULT_CONFIG_FILENAME = void 0;
|
|
4
|
+
/** Default config filename searched in cwd when --config is omitted. */
|
|
5
|
+
exports.DEFAULT_CONFIG_FILENAME = 'svharness.config.yaml';
|
|
6
|
+
/** Supported config file schema version. */
|
|
7
|
+
exports.CONFIG_SCHEMA_VERSION = 1;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.saveConfigSection = exports.mergeConvertOptions = exports.mergeApplyOptions = exports.mergeBuildOptions = exports.resolveConfigHarnessName = exports.normalizeConfig = exports.resolveConfigPath = exports.loadConfig = exports.CONFIG_SCHEMA_VERSION = exports.DEFAULT_CONFIG_FILENAME = void 0;
|
|
4
|
+
var constants_1 = require("./constants");
|
|
5
|
+
Object.defineProperty(exports, "DEFAULT_CONFIG_FILENAME", { enumerable: true, get: function () { return constants_1.DEFAULT_CONFIG_FILENAME; } });
|
|
6
|
+
Object.defineProperty(exports, "CONFIG_SCHEMA_VERSION", { enumerable: true, get: function () { return constants_1.CONFIG_SCHEMA_VERSION; } });
|
|
7
|
+
var load_config_1 = require("./load-config");
|
|
8
|
+
Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return load_config_1.loadConfig; } });
|
|
9
|
+
Object.defineProperty(exports, "resolveConfigPath", { enumerable: true, get: function () { return load_config_1.resolveConfigPath; } });
|
|
10
|
+
var normalize_1 = require("./normalize");
|
|
11
|
+
Object.defineProperty(exports, "normalizeConfig", { enumerable: true, get: function () { return normalize_1.normalizeConfig; } });
|
|
12
|
+
Object.defineProperty(exports, "resolveConfigHarnessName", { enumerable: true, get: function () { return normalize_1.resolveConfigHarnessName; } });
|
|
13
|
+
var merge_options_1 = require("./merge-options");
|
|
14
|
+
Object.defineProperty(exports, "mergeBuildOptions", { enumerable: true, get: function () { return merge_options_1.mergeBuildOptions; } });
|
|
15
|
+
Object.defineProperty(exports, "mergeApplyOptions", { enumerable: true, get: function () { return merge_options_1.mergeApplyOptions; } });
|
|
16
|
+
Object.defineProperty(exports, "mergeConvertOptions", { enumerable: true, get: function () { return merge_options_1.mergeConvertOptions; } });
|
|
17
|
+
var save_config_1 = require("./save-config");
|
|
18
|
+
Object.defineProperty(exports, "saveConfigSection", { enumerable: true, get: function () { return save_config_1.saveConfigSection; } });
|
|
@@ -0,0 +1,54 @@
|
|
|
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.resolveConfigPath = resolveConfigPath;
|
|
7
|
+
exports.loadConfig = loadConfig;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
|
+
const constants_1 = require("./constants");
|
|
12
|
+
const normalize_1 = require("./normalize");
|
|
13
|
+
function parseConfigFile(absPath) {
|
|
14
|
+
const rawText = fs_extra_1.default.readFileSync(absPath, 'utf8');
|
|
15
|
+
const ext = node_path_1.default.extname(absPath).toLowerCase();
|
|
16
|
+
let parsed;
|
|
17
|
+
if (ext === '.json') {
|
|
18
|
+
parsed = JSON.parse(rawText);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
parsed = js_yaml_1.default.load(rawText);
|
|
22
|
+
}
|
|
23
|
+
return (0, normalize_1.normalizeConfig)(parsed, absPath);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve config file path: explicit --config, else cwd/svharness.config.yaml if present.
|
|
27
|
+
*/
|
|
28
|
+
function resolveConfigPath(explicit, cwd = process.cwd()) {
|
|
29
|
+
if (explicit) {
|
|
30
|
+
const abs = node_path_1.default.isAbsolute(explicit) ? explicit : node_path_1.default.resolve(cwd, explicit);
|
|
31
|
+
if (!fs_extra_1.default.existsSync(abs)) {
|
|
32
|
+
throw new Error(`配置文件不存在: ${abs}`);
|
|
33
|
+
}
|
|
34
|
+
return abs;
|
|
35
|
+
}
|
|
36
|
+
const candidate = node_path_1.default.join(cwd, constants_1.DEFAULT_CONFIG_FILENAME);
|
|
37
|
+
if (fs_extra_1.default.existsSync(candidate))
|
|
38
|
+
return candidate;
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Load config from disk. Returns undefined when no file is found (and explicit not required).
|
|
43
|
+
*/
|
|
44
|
+
function loadConfig(options) {
|
|
45
|
+
const cwd = options.cwd ?? process.cwd();
|
|
46
|
+
const resolved = resolveConfigPath(options.configPath, cwd);
|
|
47
|
+
if (!resolved) {
|
|
48
|
+
if (options.required) {
|
|
49
|
+
throw new Error(`未找到配置文件。请传入 --config <path> 或在当前目录创建 ${constants_1.DEFAULT_CONFIG_FILENAME}`);
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return { config: parseConfigFile(resolved), path: resolved };
|
|
54
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeBuildOptions = mergeBuildOptions;
|
|
4
|
+
exports.mergeApplyOptions = mergeApplyOptions;
|
|
5
|
+
exports.mergeConvertOptions = mergeConvertOptions;
|
|
6
|
+
const normalize_1 = require("./normalize");
|
|
7
|
+
function getSource(cmd, key) {
|
|
8
|
+
if (!cmd)
|
|
9
|
+
return undefined;
|
|
10
|
+
try {
|
|
11
|
+
return cmd.getOptionValueSource(key);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function isCliSource(cmd, key) {
|
|
18
|
+
return getSource(cmd, key) === 'cli';
|
|
19
|
+
}
|
|
20
|
+
function pickString(key, cliValue, configValue, cmd) {
|
|
21
|
+
if (isCliSource(cmd, key) && cliValue !== undefined)
|
|
22
|
+
return cliValue;
|
|
23
|
+
if (configValue !== undefined)
|
|
24
|
+
return configValue;
|
|
25
|
+
return cliValue;
|
|
26
|
+
}
|
|
27
|
+
function pickNumber(key, cliValue, configValue, cmd) {
|
|
28
|
+
if (isCliSource(cmd, key) && cliValue !== undefined)
|
|
29
|
+
return cliValue;
|
|
30
|
+
if (configValue !== undefined)
|
|
31
|
+
return configValue;
|
|
32
|
+
return cliValue;
|
|
33
|
+
}
|
|
34
|
+
function pickStringArray(key, cliValue, configValue, cmd) {
|
|
35
|
+
if (isCliSource(cmd, key) && cliValue !== undefined)
|
|
36
|
+
return cliValue;
|
|
37
|
+
if (configValue !== undefined)
|
|
38
|
+
return configValue;
|
|
39
|
+
return cliValue;
|
|
40
|
+
}
|
|
41
|
+
function pickBool(key, cliValue, configValue, defaultValue, cmd) {
|
|
42
|
+
if (isCliSource(cmd, key))
|
|
43
|
+
return !!cliValue;
|
|
44
|
+
if (configValue !== undefined)
|
|
45
|
+
return configValue;
|
|
46
|
+
if (defaultValue !== undefined)
|
|
47
|
+
return defaultValue;
|
|
48
|
+
return cliValue;
|
|
49
|
+
}
|
|
50
|
+
function mergeSection(section, defaults) {
|
|
51
|
+
if (!defaults)
|
|
52
|
+
return (section ?? {});
|
|
53
|
+
return { ...defaults, ...section };
|
|
54
|
+
}
|
|
55
|
+
function mergeBuildOptions(cli, configSection, defaults, cmd) {
|
|
56
|
+
const cfg = mergeSection(configSection, defaults);
|
|
57
|
+
const harnessFromConfig = (0, normalize_1.resolveConfigHarnessName)(cfg);
|
|
58
|
+
const harnessNameCli = pickString('harnessName', cli.harnessName, undefined, cmd);
|
|
59
|
+
const nameCli = pickString('name', cli.name, undefined, cmd);
|
|
60
|
+
return {
|
|
61
|
+
harnessName: harnessNameCli ?? harnessFromConfig,
|
|
62
|
+
name: nameCli ?? cfg.name,
|
|
63
|
+
arch: pickString('arch', cli.arch, cfg.arch, cmd),
|
|
64
|
+
agent: pickString('agent', cli.agent, cfg.agent, cmd),
|
|
65
|
+
baseline: pickString('baseline', cli.baseline, cfg.baseline, cmd),
|
|
66
|
+
requirements: pickString('requirements', cli.requirements, cfg.requirements, cmd),
|
|
67
|
+
references: pickString('references', cli.references, cfg.references, cmd),
|
|
68
|
+
extraSkills: pickStringArray('extraSkills', cli.extraSkills, cfg.extraSkills, cmd),
|
|
69
|
+
baselineBranch: pickString('baselineBranch', cli.baselineBranch, cfg.baselineBranch, cmd),
|
|
70
|
+
baselineMaxFileKb: pickNumber('baselineMaxFileKb', cli.baselineMaxFileKb, cfg.baselineMaxFileKb, cmd),
|
|
71
|
+
convertEndpoint: pickString('convertEndpoint', cli.convertEndpoint, cfg.convertEndpoint, cmd),
|
|
72
|
+
convertConcurrency: pickNumber('convertConcurrency', cli.convertConcurrency, cfg.convertConcurrency, cmd),
|
|
73
|
+
convertMaxFileMb: pickNumber('convertMaxFileMb', cli.convertMaxFileMb, cfg.convertMaxFileMb, cmd),
|
|
74
|
+
convertTimeoutSec: pickNumber('convertTimeoutSec', cli.convertTimeoutSec, cfg.convertTimeoutSec, cmd),
|
|
75
|
+
convertForce: pickBool('convertForce', cli.convertForce, cfg.convertForce, undefined, cmd),
|
|
76
|
+
force: pickBool('force', cli.force, cfg.force, defaults?.force, cmd),
|
|
77
|
+
yes: pickBool('yes', cli.yes, cfg.yes, defaults?.yes, cmd),
|
|
78
|
+
verbose: pickBool('verbose', cli.verbose, cfg.verbose, defaults?.verbose, cmd),
|
|
79
|
+
generateWiki: pickBool('generateWiki', cli.generateWiki, cfg.generateWiki, undefined, cmd),
|
|
80
|
+
wikiTasksOnly: pickBool('wikiTasksOnly', cli.wikiTasksOnly, cfg.wikiTasksOnly, undefined, cmd),
|
|
81
|
+
wikiLang: pickString('wikiLang', cli.wikiLang, cfg.wikiLang, cmd),
|
|
82
|
+
wikiModel: pickString('wikiModel', cli.wikiModel, cfg.wikiModel, cmd),
|
|
83
|
+
wikiBaseUrl: pickString('wikiBaseUrl', cli.wikiBaseUrl, cfg.wikiBaseUrl, cmd),
|
|
84
|
+
wikiApiKey: pickString('wikiApiKey', cli.wikiApiKey, cfg.wikiApiKey, cmd),
|
|
85
|
+
wikiSource: pickString('wikiSource', cli.wikiSource, cfg.wikiSource, cmd),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function mergeApplyOptions(cli, configSection, defaults, cmd) {
|
|
89
|
+
const cfg = mergeSection(configSection, defaults);
|
|
90
|
+
return {
|
|
91
|
+
harness: pickString('harness', cli.harness, cfg.harness, cmd),
|
|
92
|
+
target: pickString('target', cli.target, cfg.target, cmd),
|
|
93
|
+
agent: pickString('agent', cli.agent, cfg.agent, cmd),
|
|
94
|
+
force: pickBool('force', cli.force, cfg.force, defaults?.force, cmd),
|
|
95
|
+
clone: pickBool('clone', cli.clone, cfg.clone, undefined, cmd),
|
|
96
|
+
yes: pickBool('yes', cli.yes, cfg.yes, defaults?.yes, cmd),
|
|
97
|
+
verbose: pickBool('verbose', cli.verbose, cfg.verbose, defaults?.verbose, cmd),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function mergeConvertOptions(cli, configSection, defaults, cmd) {
|
|
101
|
+
const cfg = mergeSection(configSection, defaults);
|
|
102
|
+
return {
|
|
103
|
+
input: pickStringArray('input', cli.input, cfg.input, cmd),
|
|
104
|
+
harness: pickString('harness', cli.harness, cfg.harness, cmd),
|
|
105
|
+
output: pickString('output', cli.output, cfg.output, cmd),
|
|
106
|
+
endpoint: pickString('endpoint', cli.endpoint, cfg.endpoint, cmd),
|
|
107
|
+
concurrency: pickNumber('concurrency', cli.concurrency, cfg.concurrency, cmd),
|
|
108
|
+
maxFileMb: pickNumber('maxFileMb', cli.maxFileMb, cfg.maxFileMb, cmd),
|
|
109
|
+
timeoutSec: pickNumber('timeoutSec', cli.timeoutSec, cfg.timeoutSec, cmd),
|
|
110
|
+
type: pickString('type', cli.type, cfg.type, cmd),
|
|
111
|
+
force: pickBool('force', cli.force, cfg.force, defaults?.force, cmd),
|
|
112
|
+
yes: pickBool('yes', cli.yes, cfg.yes, defaults?.yes, cmd),
|
|
113
|
+
verbose: pickBool('verbose', cli.verbose, cfg.verbose, defaults?.verbose, cmd),
|
|
114
|
+
};
|
|
115
|
+
}
|