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
|
@@ -7,9 +7,12 @@ exports.detectBaselineMode = detectBaselineMode;
|
|
|
7
7
|
exports.validateArgs = validateArgs;
|
|
8
8
|
exports.listSupportedArches = listSupportedArches;
|
|
9
9
|
exports.listSupportedAgents = listSupportedAgents;
|
|
10
|
+
exports.isHarnessRoot = isHarnessRoot;
|
|
11
|
+
exports.resolveConvertPaths = resolveConvertPaths;
|
|
10
12
|
exports.validateConvertArgs = validateConvertArgs;
|
|
11
13
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const harness_name_1 = require("./harness-name");
|
|
13
16
|
const SUPPORTED_ARCHES = [
|
|
14
17
|
'android-compose',
|
|
15
18
|
'android-xml',
|
|
@@ -25,7 +28,6 @@ const SUPPORTED_AGENTS = [
|
|
|
25
28
|
'opencode',
|
|
26
29
|
'generic',
|
|
27
30
|
];
|
|
28
|
-
const NAME_RE = /^[a-z][a-z0-9-]{1,39}$/;
|
|
29
31
|
const DEFAULT_BASELINE_MAX_FILE_KB = 1024;
|
|
30
32
|
/**
|
|
31
33
|
* Decide whether the `--baseline` value looks like a git remote or a local path.
|
|
@@ -57,12 +59,19 @@ function detectBaselineMode(input) {
|
|
|
57
59
|
}
|
|
58
60
|
function validateArgs(input) {
|
|
59
61
|
const errors = [];
|
|
60
|
-
// name
|
|
62
|
+
// name (normalized to leading `.`, e.g. my-app → .my-app)
|
|
63
|
+
let normalizedName;
|
|
61
64
|
if (!input.name) {
|
|
62
|
-
errors.push('--name is required');
|
|
65
|
+
errors.push('--harness-name is required');
|
|
63
66
|
}
|
|
64
|
-
else
|
|
65
|
-
|
|
67
|
+
else {
|
|
68
|
+
const nameCheck = (0, harness_name_1.validateHarnessNameInput)(input.name);
|
|
69
|
+
if (!nameCheck.ok) {
|
|
70
|
+
errors.push(nameCheck.message);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
normalizedName = nameCheck.normalized;
|
|
74
|
+
}
|
|
66
75
|
}
|
|
67
76
|
// arch
|
|
68
77
|
if (!input.arch) {
|
|
@@ -117,7 +126,7 @@ function validateArgs(input) {
|
|
|
117
126
|
throw new Error('Argument validation failed:\n - ' + errors.join('\n - '));
|
|
118
127
|
}
|
|
119
128
|
return {
|
|
120
|
-
name: input.name,
|
|
129
|
+
name: normalizedName ?? (0, harness_name_1.normalizeHarnessName)(input.name),
|
|
121
130
|
arch: input.arch,
|
|
122
131
|
agent: input.agent,
|
|
123
132
|
baseline: baselineValue,
|
|
@@ -138,54 +147,79 @@ const DEFAULT_TIMEOUT_SEC = 120;
|
|
|
138
147
|
const DEFAULT_ENDPOINT = 'http://markitdown.desaysz.site';
|
|
139
148
|
const ENV_ENDPOINT = 'SVHARNESS_MARKITDOWN_ENDPOINT';
|
|
140
149
|
const DEFAULT_CONVERT_TYPE = 'requirements';
|
|
150
|
+
/** True when `dir` exists and contains `harness.yaml`. */
|
|
151
|
+
function isHarnessRoot(dir) {
|
|
152
|
+
return fs_extra_1.default.existsSync(node_path_1.default.join(dir, 'harness.yaml'));
|
|
153
|
+
}
|
|
154
|
+
function harnessMdDir(harnessRoot, type) {
|
|
155
|
+
return node_path_1.default.join(harnessRoot, type, 'md');
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Resolve markdown output directory vs harness layout.
|
|
159
|
+
*
|
|
160
|
+
* - Standalone: `--output` is the final md directory (no `harness.yaml` under it).
|
|
161
|
+
* - Harness: `--output` points at harness root (has `harness.yaml`), or `--harness` is set
|
|
162
|
+
* → writes to `<harness>/<type>/md/` unless both `--harness` and `--output` are given
|
|
163
|
+
* (then `--output` is the final directory).
|
|
164
|
+
*/
|
|
165
|
+
function resolveConvertPaths(input) {
|
|
166
|
+
const outputProvided = input.output !== undefined;
|
|
167
|
+
const outputAbs = node_path_1.default.resolve(input.output ?? '.');
|
|
168
|
+
const harnessExplicit = !!input.harness;
|
|
169
|
+
if (harnessExplicit) {
|
|
170
|
+
const harnessRoot = node_path_1.default.resolve(input.harness);
|
|
171
|
+
if (outputProvided) {
|
|
172
|
+
return {
|
|
173
|
+
outputDir: outputAbs,
|
|
174
|
+
harnessRoot,
|
|
175
|
+
harnessMode: true,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
outputDir: harnessMdDir(harnessRoot, input.type),
|
|
180
|
+
harnessRoot,
|
|
181
|
+
harnessMode: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (isHarnessRoot(outputAbs)) {
|
|
185
|
+
return {
|
|
186
|
+
outputDir: harnessMdDir(outputAbs, input.type),
|
|
187
|
+
harnessRoot: outputAbs,
|
|
188
|
+
harnessMode: true,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
outputDir: outputAbs,
|
|
193
|
+
harnessMode: false,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
141
196
|
/**
|
|
142
197
|
* Validate and normalise options for `svharness convert`.
|
|
143
198
|
*
|
|
144
199
|
* All parameters have sensible defaults:
|
|
145
200
|
* - --input defaults to `.` (current directory)
|
|
146
201
|
* - --endpoint defaults to http://markitdown.desaysz.site
|
|
147
|
-
* - --
|
|
148
|
-
* - --output defaults to `.` (current directory)
|
|
202
|
+
* - --output defaults to `.` (current directory); see `resolveConvertPaths` for layout
|
|
149
203
|
*
|
|
150
204
|
* When --harness is explicitly given, it must exist and contain harness.yaml.
|
|
151
|
-
* When inferred from --output, we skip the harness.yaml requirement so users
|
|
152
|
-
* can run `svharness convert` without ceremony.
|
|
153
205
|
*/
|
|
154
206
|
function validateConvertArgs(input) {
|
|
155
207
|
const errors = [];
|
|
156
|
-
// resolve output base (defaults to cwd)
|
|
157
|
-
const outputRaw = input.output ?? '.';
|
|
158
|
-
const outputAbs = node_path_1.default.resolve(outputRaw);
|
|
159
208
|
// input: default to ['.']
|
|
160
209
|
const patterns = (input.input ?? ['.']).map((s) => s.trim()).filter(Boolean);
|
|
161
|
-
// harness: explicit > output path
|
|
162
|
-
let harnessAbs;
|
|
163
210
|
const harnessExplicit = !!input.harness;
|
|
164
211
|
if (harnessExplicit) {
|
|
165
|
-
harnessAbs = node_path_1.default.resolve(input.harness);
|
|
212
|
+
const harnessAbs = node_path_1.default.resolve(input.harness);
|
|
166
213
|
if (!fs_extra_1.default.existsSync(harnessAbs)) {
|
|
167
214
|
errors.push(`--harness path does not exist: ${harnessAbs}`);
|
|
168
215
|
}
|
|
169
216
|
else if (!fs_extra_1.default.statSync(harnessAbs).isDirectory()) {
|
|
170
217
|
errors.push(`--harness must be a directory: ${harnessAbs}`);
|
|
171
218
|
}
|
|
172
|
-
else if (!
|
|
219
|
+
else if (!input.allowMissingHarnessYaml && !isHarnessRoot(harnessAbs)) {
|
|
173
220
|
errors.push(`--harness "${harnessAbs}" does not look like a harness root (missing harness.yaml)`);
|
|
174
221
|
}
|
|
175
222
|
}
|
|
176
|
-
else {
|
|
177
|
-
// fallback to output path
|
|
178
|
-
harnessAbs = outputAbs;
|
|
179
|
-
// skip harness.yaml validation when inferred — just warn if missing
|
|
180
|
-
if (fs_extra_1.default.existsSync(harnessAbs) && fs_extra_1.default.statSync(harnessAbs).isDirectory()) {
|
|
181
|
-
if (!fs_extra_1.default.existsSync(node_path_1.default.join(harnessAbs, 'harness.yaml'))) {
|
|
182
|
-
// warn-only (harmless: outDir just gets created)
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
// if output path doesn't exist yet, that's fine — we'll create it
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
223
|
// endpoint: flag > env > default
|
|
190
224
|
let endpoint = (input.endpoint && input.endpoint.trim()) ||
|
|
191
225
|
(process.env[ENV_ENDPOINT] && process.env[ENV_ENDPOINT].trim()) ||
|
|
@@ -211,9 +245,16 @@ function validateConvertArgs(input) {
|
|
|
211
245
|
if (errors.length > 0) {
|
|
212
246
|
throw new Error('Argument validation failed:\n - ' + errors.join('\n - '));
|
|
213
247
|
}
|
|
248
|
+
const paths = resolveConvertPaths({
|
|
249
|
+
harness: input.harness,
|
|
250
|
+
output: input.output,
|
|
251
|
+
type: convertType,
|
|
252
|
+
});
|
|
214
253
|
return {
|
|
215
254
|
input: patterns,
|
|
216
|
-
|
|
255
|
+
outputDir: paths.outputDir,
|
|
256
|
+
harnessRoot: paths.harnessRoot,
|
|
257
|
+
harnessMode: paths.harnessMode,
|
|
217
258
|
endpoint,
|
|
218
259
|
concurrency,
|
|
219
260
|
maxFileMB,
|
package/package.json
CHANGED
|
@@ -1,122 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-apply-skills-main
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
然后加载 `<harness_root>/agent-env/rules/` 作为强约束、扫描
|
|
7
|
-
`<harness_root>/agent-env/skills/*/SKILL.md` 作为子能力,并参考
|
|
8
|
-
`<harness_root>/baseline/`、`<harness_root>/specs/` 产出代码。
|
|
9
|
-
当用户说"应用 harness-apply-skills-main 完成 xxx 功能开发"、
|
|
10
|
-
"使用 harness 开发 xxx"、"基于 harness 做 xxx"、"apply harness"、
|
|
11
|
-
"按 harness 规范实现 xxx" 时触发。
|
|
4
|
+
harness 应用入口(薄索引模式)。提示用户当前项目已注入 runtime skills/rules,
|
|
5
|
+
优先直接调用已注入的 `harness-apply-skills-*` 子 skill;如无匹配,再按普通开发流程执行。
|
|
12
6
|
---
|
|
13
7
|
|
|
14
|
-
# harness-apply-skills-main(harness
|
|
8
|
+
# harness-apply-skills-main(harness 应用入口)
|
|
15
9
|
|
|
16
|
-
你是一个 **harness
|
|
17
|
-
完成功能开发时,你不直接写业务代码,而是先把 harness 的约束、能力、基线、规格
|
|
18
|
-
装载到上下文,再按语义把任务路由到 harness 内部的子 skill。
|
|
10
|
+
你是一个 **harness 应用入口提示器**。当前项目已经注入运行期 rules 与 skills:
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
> 位置,也不要去读取外部 `binding.yaml`——它只是冗余副本,不是权威来源。
|
|
12
|
+
- harness 根目录:`__HARNESS_ROOT_REL__`
|
|
13
|
+
- 运行期技能目录:`__HARNESS_ROOT_REL__/agent-env/skills/`
|
|
14
|
+
- 运行期规则目录:`__HARNESS_ROOT_REL__/agent-env/rules/`
|
|
24
15
|
|
|
25
|
-
##
|
|
16
|
+
## 使用原则
|
|
26
17
|
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- `harness_version` —— harness 构建状态版本(来自 `<harness_root>/VERSION`)
|
|
32
|
-
- `target_project` —— 绑定的目标项目根路径
|
|
33
|
-
- `agent` —— 当前 IDE / agent 标识
|
|
34
|
-
- `applied_at` / `applied_by` —— 绑定时间与工具版本
|
|
35
|
-
- `apply_skill_registry` —— 由 S60 登记的**内容引用**清单(`name` / `reference_md` /
|
|
36
|
-
`description`,可选 `entry` / `source_id`);`svharness apply` 将其内联到本 skill,
|
|
37
|
-
运行期**仅通过本清单 + `references/md/` 原文**路由与执行,不向目标项目注入
|
|
38
|
-
`harness-apply-skills-*` 子目录
|
|
39
|
-
- (旁置,不读)`./references/binding.yaml` —— 冗余副本,仅供 `svharness`
|
|
40
|
-
自身工具链(status / detach 等)解析;skill 运行时**不读它**。
|
|
18
|
+
1. 优先枚举当前项目已注入的 `harness-apply-skills-*` 子 skill,并调用最匹配者完成任务。
|
|
19
|
+
2. 每次任务必须遵守已注入的 rules(always_on 优先)。
|
|
20
|
+
3. 若没有匹配 skill,按普通实现流程完成,但仍需参考 `__HARNESS_ROOT_REL__/specs/`、`__HARNESS_ROOT_REL__/baseline/`。
|
|
21
|
+
4. 不修改 `__HARNESS_ROOT_REL__` 下的任何文件;harness 作为只读知识源。
|
|
41
22
|
|
|
42
|
-
##
|
|
23
|
+
## 典型触发
|
|
43
24
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
25
|
+
- 应用 harness-apply-skills-main 完成 xxx 功能开发
|
|
26
|
+
- 使用 harness 开发 xxx
|
|
27
|
+
- 基于 harness 做 xxx
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
`<harness_root>/agent-env/rules/*.md`(`always_on` 必须全部遵守;
|
|
50
|
-
`model_decision` 按本次任务语义挑选)。规则冲突时以 `always_on` 为准。
|
|
29
|
+
## 注意
|
|
51
30
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- 再枚举 `<harness_root>/agent-env/skills/*/SKILL.md`(模板 skill、baseline 合并
|
|
55
|
-
skill;**不含** `harness-apply-skills-*` 前缀——references 已由 registry 承载),
|
|
56
|
-
提取 `name` + `description`;
|
|
57
|
-
- 合并去重后得到本次会话可用的 **子 skill 路由表**。不要整目录朗读内容,按需要再深读。
|
|
58
|
-
|
|
59
|
-
4. **语义路由**:
|
|
60
|
-
- 若任务匹配 `apply_skill_registry` 中某项,按「References 能力 → 执行」处理,
|
|
61
|
-
**直接读** `<harness_root>/<reference_md>`,勿再查找同名子 skill 目录;
|
|
62
|
-
- 否则在路由表中选择最匹配的 1 ~ N 个 agent-env 子 skill;
|
|
63
|
-
- 若无匹配,回到普通实现路径,但仍必须遵守步骤 2 的规则。
|
|
64
|
-
|
|
65
|
-
5. **查阅基线与规格**(按需):
|
|
66
|
-
- `<harness_root>/baseline/wiki/` —— 项目知识摘要,优先用于建立上下文
|
|
67
|
-
- `<harness_root>/baseline/code/` —— 权威参考实现;禁止引用该目录外的路径作为"参考实现"
|
|
68
|
-
- `<harness_root>/specs/signals|ui|behavior|interfaces/` —— 若任务与信号/UI/行为/接口相关,必须先对齐 schema
|
|
69
|
-
- `<harness_root>/tasks/` —— 若存在匹配的任务模板,优先复用
|
|
70
|
-
|
|
71
|
-
6. **执行与交付**:严格在 `target_project` 下写代码,引用 harness 文件时用
|
|
72
|
-
相对路径(`harness_root_rel` + 子路径)。不得把 harness 内部文件复制到
|
|
73
|
-
目标项目;harness 是只读的知识源。
|
|
74
|
-
|
|
75
|
-
7. **记忆沉淀**:若本次产生了可复用经验,按 agent-env 的 memory 规范追加到
|
|
76
|
-
`<harness_root>/agent-env/memory/inbox/`(如果目录存在);不确定时停住询问。
|
|
77
|
-
|
|
78
|
-
## 守则(不得违反)
|
|
79
|
-
|
|
80
|
-
- **不得绕过本 skill 内嵌的 `## 绑定元数据` 小节**去猜测 harness 路径;路径错误必须让用户重绑定。
|
|
81
|
-
- **不得修改 harness 目录下的文件**(rules / skills / specs / baseline / VERSION)。
|
|
82
|
-
harness 是知识源,演进由 `svharness` 和 harness 内部的 maintainer skill 负责。
|
|
83
|
-
- **不得把 harness 的 always_on rules 当成"建议"**;它们是硬约束。
|
|
84
|
-
- **不得引用 `baseline/code/` 之外的源码**作为"参考实现"。
|
|
85
|
-
- **不得把本 skill 当作业务 skill**:它只做上下文装载 + 路由,业务实现由子 skill 或
|
|
86
|
-
agent 本体完成。
|
|
87
|
-
- 所有面向用户的说明使用**中文**;字段名、类名、路径保留英文。
|
|
88
|
-
- 多个子 skill 有冲突结论时,显式告知用户并请其裁决,不得自行二选一。
|
|
89
|
-
|
|
90
|
-
## 典型触发例
|
|
91
|
-
|
|
92
|
-
- "应用 harness-apply-skills-main 完成无线充电开关功能开发"
|
|
93
|
-
- "基于 harness 新增设置项 Fragment"
|
|
94
|
-
- "按 harness 规范实现新信号 CMDID 0xA3"
|
|
95
|
-
- "apply harness 给我做一个埋点上报"
|
|
96
|
-
|
|
97
|
-
以上触发均需先走算法 1~5,再进入业务实现。
|
|
98
|
-
|
|
99
|
-
## References 能力(`apply_skill_registry`)
|
|
100
|
-
|
|
101
|
-
`apply_skill_registry` 是 S60 为**内容引用**登记的清单,由 `svharness apply` 内联到
|
|
102
|
-
本 skill 绑定元数据。运行期以清单 + `<harness_root>/references/md/` 为准,**不依赖**
|
|
103
|
-
目标项目或 harness 内是否存在 `harness-apply-skills-*` 子 skill 目录。
|
|
104
|
-
|
|
105
|
-
### 路由
|
|
106
|
-
|
|
107
|
-
1. 将用户任务与 `apply_skill_registry[].description`、`name` 做语义匹配。
|
|
108
|
-
2. 选出 0 ~ 1 条(必要时可多条)登记项;无匹配时回退到 agent-env 子 skill 扫描。
|
|
109
|
-
|
|
110
|
-
### 执行(匹配到登记项后)
|
|
111
|
-
|
|
112
|
-
1. 从绑定元数据取 `harness_root`、`target_project`。
|
|
113
|
-
2. 阅读 `<harness_root>/<reference_md>` 中与本次任务相关的章节;有 `source_id` 时可
|
|
114
|
-
对照 `<harness_root>/references/yaml/index.yaml`。**禁止**跳过原文臆造流程。
|
|
115
|
-
3. 遵守 `<harness_root>/agent-env/rules/` 中 `always_on` 规则。
|
|
116
|
-
4. 若登记项含 `entry`(如 `/agent analyst`),按 references 原文执行该入口。
|
|
117
|
-
5. 在 `target_project` 下交付;与 `<harness_root>/specs/` 冲突时先请用户裁决。
|
|
118
|
-
6. 不得修改 harness 目录内任何文件;不得把本路径当作构建期 skill(`harness-build-*`)。
|
|
119
|
-
|
|
120
|
-
## 绑定元数据(权威来源,由 svharness apply 生成,请勿手工编辑)
|
|
121
|
-
|
|
122
|
-
{{BINDING_YAML_BLOCK}}
|
|
31
|
+
- 本 skill 不负责二级调度与 binding 解析。
|
|
32
|
+
- 如发现路径不一致,请重新执行 `svharness apply --force`。
|