svharness 0.8.0
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 +531 -0
- package/bin/cli.js +3 -0
- package/dist/adapters/_frontmatter.js +24 -0
- package/dist/adapters/claude-code.js +12 -0
- package/dist/adapters/codechat.js +12 -0
- package/dist/adapters/cursor.js +19 -0
- package/dist/adapters/generic.js +19 -0
- package/dist/adapters/index.js +26 -0
- package/dist/adapters/qoder.js +12 -0
- package/dist/commands/apply.js +272 -0
- package/dist/commands/init.js +420 -0
- package/dist/core/agent-injector.js +192 -0
- package/dist/core/next-steps.js +91 -0
- package/dist/core/render-meta.js +81 -0
- package/dist/core/repomix-pack.js +54 -0
- package/dist/core/scaffold.js +93 -0
- package/dist/core/state.js +80 -0
- package/dist/index.js +239 -0
- package/dist/types.js +5 -0
- package/dist/utils/baseline-copy.js +591 -0
- package/dist/utils/baseline-defaults.js +106 -0
- package/dist/utils/logger.js +56 -0
- package/dist/utils/validate-args.js +132 -0
- package/dist/utils/version.js +23 -0
- package/dist/wiki/abort.js +30 -0
- package/dist/wiki/config.js +79 -0
- package/dist/wiki/defaults.js +16 -0
- package/dist/wiki/envLoader.js +78 -0
- package/dist/wiki/index.js +29 -0
- package/dist/wiki/openaiCompat.js +219 -0
- package/dist/wiki/repowikiCanonicalSections.js +67 -0
- package/dist/wiki/repowikiCheckpoint.js +106 -0
- package/dist/wiki/repowikiConfig.js +9 -0
- package/dist/wiki/repowikiGit.js +73 -0
- package/dist/wiki/repowikiIndexer.js +824 -0
- package/dist/wiki/repowikiMarkdownPost.js +123 -0
- package/dist/wiki/repowikiMetadataContent.js +64 -0
- package/dist/wiki/repowikiMetadataJson.js +15 -0
- package/dist/wiki/repowikiScanner.js +156 -0
- package/dist/wiki/repowikiStructureNav.js +286 -0
- package/dist/wiki/repowikiStructureNormalize.js +218 -0
- package/dist/wiki/wikiStructureXml.js +316 -0
- package/dist/wiki/wikiTasksWriter.js +127 -0
- package/package.json +57 -0
- package/templates/_shared/apply-skills/harness-apply-skills-main.md +91 -0
- package/templates/_shared/build-rules/harness-build-rule-agent-agnostic.md +35 -0
- package/templates/_shared/build-rules/harness-build-rule-chinese-only.md +49 -0
- package/templates/_shared/build-rules/harness-build-rule-memory-write.md +31 -0
- package/templates/_shared/build-rules/harness-build-rule-orchestrator-flow.md +25 -0
- package/templates/_shared/build-rules/harness-build-rule-skills-tasks-output.md +35 -0
- package/templates/_shared/build-rules/harness-build-rule-specs-schema.md +32 -0
- package/templates/_shared/build-rules/harness-build-rule-user-interaction.md +63 -0
- package/templates/_shared/build-skills/harness-build-skill-knowledge-builder.md +120 -0
- package/templates/_shared/build-skills/harness-build-skill-orchestrator.md +87 -0
- package/templates/_shared/build-skills/harness-build-skill-spec-builder.md +85 -0
- package/templates/_shared/build-skills/harness-build-skill-wiki-writer.md +77 -0
- package/templates/_shared/meta/AGENTS.md.ejs +53 -0
- package/templates/_shared/meta/CHANGELOG.md.ejs +15 -0
- package/templates/_shared/meta/README.md.ejs +51 -0
- package/templates/_shared/meta/VERSION.ejs +1 -0
- package/templates/_shared/meta/harness.yaml.ejs +52 -0
- package/templates/_shared/skeleton/agent-env/memory/categories/.gitkeep +1 -0
- package/templates/_shared/skeleton/agent-env/memory/inbox/.gitkeep +1 -0
- package/templates/_shared/skeleton/agent-env/skills/.gitkeep +1 -0
- package/templates/_shared/skeleton/agent-env/tools/.gitkeep +1 -0
- package/templates/_shared/skeleton/assets/baseline/code/.gitkeep +1 -0
- package/templates/_shared/skeleton/assets/baseline/repomix/.gitkeep +1 -0
- package/templates/_shared/skeleton/assets/baseline/wiki/.gitkeep +1 -0
- package/templates/_shared/skeleton/assets/raw/.gitkeep +1 -0
- package/templates/_shared/skeleton/assets/requirements/.gitkeep +1 -0
- package/templates/_shared/skeleton/commands/install/.gitkeep +1 -0
- package/templates/_shared/skeleton/commands/update/.gitkeep +1 -0
- package/templates/_shared/skeleton/specs/behavior/schema.json +39 -0
- package/templates/_shared/skeleton/specs/interfaces/schema.json +38 -0
- package/templates/_shared/skeleton/specs/signals/schema.json +37 -0
- package/templates/_shared/skeleton/specs/ui/schema.json +44 -0
- package/templates/_shared/skeleton/tasks/templates/.gitkeep +0 -0
- package/templates/android-compose/skeleton/agent-env/rules/harness-compose-mandatory.mdc +49 -0
- package/templates/android-compose/skeleton/agent-env/rules/harness-coroutines-scope.mdc +52 -0
- package/templates/android-compose/skeleton/agent-env/rules/harness-hilt-injection.mdc +47 -0
- package/templates/android-compose/skeleton/agent-env/rules/harness-mvi-layering.mdc +58 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/SKILL.md +260 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/references/gradle-module-patterns.md +66 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/references/implementation-checklist.md +45 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/references/udf-data-flow.md +80 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-android-cli/SKILL.md +79 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-android-cli/references/interact.md +83 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-android-cli/references/journeys.md +97 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/SKILL.md +162 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/canonical-sources.md +116 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/diagnostics.md +182 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/report-template.md +135 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/scoring.md +277 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/search-playbook.md +303 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/scripts/compose-reports.init.gradle +58 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-state/SKILL.md +196 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/SKILL.md +192 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/references/composable-api-guide.md +123 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/references/performance-recipes.md +97 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/references/state-patterns.md +93 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-kotlin-coroutines/SKILL.md +167 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/SKILL.md +45 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/CONFIGURATION.md +44 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md +83 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/REDUNDANT-RULES.md +222 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/REFLECTION-GUIDE.md +139 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/android/topic/performance/app-optimization/enable-app-optimization.md +176 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/android/training/testing/other-components/ui-automator.md +312 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/SKILL.md +87 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/analysis-of-the-project-and-layout.md +42 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md +168 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md +183 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/identify-optimal-xml-candidate.md +31 -0
- package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/xml-layout-migration.md +86 -0
- package/templates/android-xml/skeleton/agent-env/rules/seed-aidl-thread.md +29 -0
- package/templates/android-xml/skeleton/agent-env/rules/seed-lifecycle-awareness.md +32 -0
- package/templates/android-xml/skeleton/agent-env/rules/seed-mvc-layering.md +32 -0
- package/templates/android-xml/skeleton/agent-env/rules/seed-view-binding.md +33 -0
- package/templates/android-xml/skeleton/agent-env/rules/seed-xml-styling.md +27 -0
- package/templates/cpp/skeleton/agent-env/rules/seed-cmake-explicit-sources.md +31 -0
- package/templates/cpp/skeleton/agent-env/rules/seed-header-guards.md +34 -0
- package/templates/cpp/skeleton/agent-env/rules/seed-include-layering.md +39 -0
- package/templates/cpp/skeleton/agent-env/rules/seed-no-cyclic-deps.md +29 -0
- package/templates/cpp/skeleton/agent-env/rules/seed-raii.md +30 -0
- package/templates/python/skeleton/agent-env/rules/seed-context-managers.md +60 -0
- package/templates/python/skeleton/agent-env/rules/seed-docstrings.md +48 -0
- package/templates/python/skeleton/agent-env/rules/seed-import-order.md +49 -0
- package/templates/python/skeleton/agent-env/rules/seed-pep8-naming.md +45 -0
- package/templates/python/skeleton/agent-env/rules/seed-type-annotations.md +43 -0
- package/templates/web-react/skeleton/agent-env/rules/seed-controlled-component.md +43 -0
- package/templates/web-react/skeleton/agent-env/rules/seed-effect-cleanup.md +43 -0
- package/templates/web-react/skeleton/agent-env/rules/seed-hook-rules.md +42 -0
- package/templates/web-react/skeleton/agent-env/rules/seed-key-stability.md +39 -0
- package/templates/web-react/skeleton/agent-env/rules/seed-no-props-drilling.md +43 -0
|
@@ -0,0 +1,272 @@
|
|
|
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.runApply = runApply;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
10
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
11
|
+
const adapters_1 = require("../adapters");
|
|
12
|
+
const validate_args_1 = require("../utils/validate-args");
|
|
13
|
+
const logger_1 = require("../utils/logger");
|
|
14
|
+
const version_1 = require("../utils/version");
|
|
15
|
+
const DISPATCHER_SKILL_NAME = 'harness-apply-skills-main';
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the absolute path to the bundled templates/ directory.
|
|
18
|
+
* At runtime __dirname is dist/commands, so templates are at ../../templates.
|
|
19
|
+
*/
|
|
20
|
+
function resolveTemplatesRoot() {
|
|
21
|
+
return node_path_1.default.resolve(__dirname, '..', '..', 'templates');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read the agent id from `<harness>/.harness-build-state.yaml`. Returns
|
|
25
|
+
* undefined when the state file is missing or malformed.
|
|
26
|
+
*/
|
|
27
|
+
async function readAgentFromState(harnessRoot) {
|
|
28
|
+
const stateFile = node_path_1.default.join(harnessRoot, '.harness-build-state.yaml');
|
|
29
|
+
if (!(await fs_extra_1.default.pathExists(stateFile)))
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
const raw = await fs_extra_1.default.readFile(stateFile, 'utf8');
|
|
33
|
+
const parsed = js_yaml_1.default.load(raw);
|
|
34
|
+
const agent = parsed?.agent?.trim();
|
|
35
|
+
if (!agent)
|
|
36
|
+
return undefined;
|
|
37
|
+
if (!(0, validate_args_1.listSupportedAgents)().includes(agent))
|
|
38
|
+
return undefined;
|
|
39
|
+
return agent;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
logger_1.logger.debug(`failed to read agent from state: ${err.message}`);
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Read `<harness>/VERSION` if present. Falls back to 'unknown'.
|
|
48
|
+
*/
|
|
49
|
+
async function readHarnessVersion(harnessRoot) {
|
|
50
|
+
const versionFile = node_path_1.default.join(harnessRoot, 'VERSION');
|
|
51
|
+
if (!(await fs_extra_1.default.pathExists(versionFile)))
|
|
52
|
+
return 'unknown';
|
|
53
|
+
try {
|
|
54
|
+
const raw = (await fs_extra_1.default.readFile(versionFile, 'utf8')).trim();
|
|
55
|
+
return raw || 'unknown';
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return 'unknown';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Derive the harness project name from the harness directory.
|
|
63
|
+
* `<name>-harness` → `<name>`; otherwise fall back to basename.
|
|
64
|
+
*/
|
|
65
|
+
function deriveHarnessName(harnessRoot) {
|
|
66
|
+
const base = node_path_1.default.basename(harnessRoot);
|
|
67
|
+
return base.endsWith('-harness') ? base.slice(0, -'-harness'.length) : base;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Entry point for `svharnessbuild apply`.
|
|
71
|
+
*
|
|
72
|
+
* Minimum-invasion binding: copies a single dispatcher skill
|
|
73
|
+
* (`harness-apply-skills-main/SKILL.{md|mdc}`) into the target project's
|
|
74
|
+
* agent-native skills directory. The skill carries the harness binding
|
|
75
|
+
* metadata **inlined into its own body** (`## 绑定元数据` section), so any
|
|
76
|
+
* agent can resolve `harness_root` purely from the skill's loaded content
|
|
77
|
+
* without depending on CWD or "same-directory" heuristics (scheme B).
|
|
78
|
+
*
|
|
79
|
+
* `references/binding.yaml` is still written as a redundant copy for
|
|
80
|
+
* `svharnessbuild`'s own tooling (status / detach / re-apply detection) —
|
|
81
|
+
* it is NOT the runtime source of truth.
|
|
82
|
+
*/
|
|
83
|
+
async function runApply(opts) {
|
|
84
|
+
(0, logger_1.setVerbose)(!!opts.verbose);
|
|
85
|
+
// 1. Resolve and validate harness directory.
|
|
86
|
+
if (!opts.harness) {
|
|
87
|
+
throw new Error('--harness <path> 是必填参数');
|
|
88
|
+
}
|
|
89
|
+
const harnessRoot = node_path_1.default.resolve(opts.harness);
|
|
90
|
+
if (!(await fs_extra_1.default.pathExists(harnessRoot))) {
|
|
91
|
+
throw new Error(`harness 目录不存在:${harnessRoot}`);
|
|
92
|
+
}
|
|
93
|
+
const stat = await fs_extra_1.default.stat(harnessRoot);
|
|
94
|
+
if (!stat.isDirectory()) {
|
|
95
|
+
throw new Error(`--harness 必须指向目录:${harnessRoot}`);
|
|
96
|
+
}
|
|
97
|
+
const agentEnvDir = node_path_1.default.join(harnessRoot, 'agent-env');
|
|
98
|
+
if (!(await fs_extra_1.default.pathExists(agentEnvDir))) {
|
|
99
|
+
throw new Error(`harness 目录下缺少 agent-env/:${agentEnvDir}\n 请确认该目录由 svharness build 生成。`);
|
|
100
|
+
}
|
|
101
|
+
// 2. Resolve target project root.
|
|
102
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
103
|
+
const targetRoot = node_path_1.default.resolve(opts.target ?? cwd);
|
|
104
|
+
if (!(await fs_extra_1.default.pathExists(targetRoot))) {
|
|
105
|
+
throw new Error(`目标项目目录不存在:${targetRoot}`);
|
|
106
|
+
}
|
|
107
|
+
// 2b. Optional clone: copy the entire harness directory into the target
|
|
108
|
+
// project root. After cloning, `effectiveHarnessRoot` points to the
|
|
109
|
+
// cloned copy so binding.yaml resolves to the local path.
|
|
110
|
+
let effectiveHarnessRoot = harnessRoot;
|
|
111
|
+
const cloneMode = !!opts.clone;
|
|
112
|
+
const harnessDirName = node_path_1.default.basename(harnessRoot);
|
|
113
|
+
const cloneDest = node_path_1.default.join(targetRoot, harnessDirName);
|
|
114
|
+
if (cloneMode) {
|
|
115
|
+
// Guard: never copy a directory onto itself (would recurse forever).
|
|
116
|
+
const sameDir = node_path_1.default.resolve(cloneDest).toLowerCase() ===
|
|
117
|
+
node_path_1.default.resolve(harnessRoot).toLowerCase();
|
|
118
|
+
if (sameDir) {
|
|
119
|
+
logger_1.logger.warn(`源 harness 与克隆目标路径相同,跳过拷贝:${cloneDest}`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
effectiveHarnessRoot = cloneDest;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// 3. Resolve agent (explicit > state file).
|
|
126
|
+
let agent = opts.agent;
|
|
127
|
+
if (!agent) {
|
|
128
|
+
agent = await readAgentFromState(harnessRoot);
|
|
129
|
+
if (agent) {
|
|
130
|
+
logger_1.logger.info(`从 harness 状态文件读取 agent:${agent}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!agent) {
|
|
134
|
+
throw new Error('无法推断目标 agent。请显式传入 --agent <codechat|qoder|cursor|claude-code|generic>,' +
|
|
135
|
+
'或确认 harness 下存在 .harness-build-state.yaml。');
|
|
136
|
+
}
|
|
137
|
+
const adapter = (0, adapters_1.getAdapter)(agent);
|
|
138
|
+
// 4. Compute injection target.
|
|
139
|
+
const skillsBaseDir = node_path_1.default.join(targetRoot, adapter.skillsDir);
|
|
140
|
+
const dispatcherDir = node_path_1.default.join(skillsBaseDir, DISPATCHER_SKILL_NAME);
|
|
141
|
+
const skillFile = node_path_1.default.join(dispatcherDir, 'SKILL' + adapter.skillExt);
|
|
142
|
+
const bindingFile = node_path_1.default.join(dispatcherDir, 'references', 'binding.yaml');
|
|
143
|
+
// 5. Load dispatcher skill template.
|
|
144
|
+
const templatePath = node_path_1.default.join(resolveTemplatesRoot(), '_shared', 'apply-skills', `${DISPATCHER_SKILL_NAME}.md`);
|
|
145
|
+
if (!(await fs_extra_1.default.pathExists(templatePath))) {
|
|
146
|
+
throw new Error(`dispatcher skill 模板缺失:${templatePath}`);
|
|
147
|
+
}
|
|
148
|
+
const harnessName = deriveHarnessName(harnessRoot);
|
|
149
|
+
const harnessVersion = await readHarnessVersion(harnessRoot);
|
|
150
|
+
// harness_root / harness_root_rel are always computed against the
|
|
151
|
+
// effective (possibly cloned) location, so agents always resolve to the
|
|
152
|
+
// on-disk harness the user actually has.
|
|
153
|
+
const harnessRootRel = node_path_1.default.relative(targetRoot, effectiveHarnessRoot).replace(/\\/g, '/') || '.';
|
|
154
|
+
const cliVersion = (0, version_1.getCliVersion)();
|
|
155
|
+
const appliedAt = new Date().toISOString();
|
|
156
|
+
// 6. Confirm (unless --yes).
|
|
157
|
+
const configRows = [
|
|
158
|
+
{ label: 'harness 源路径', value: harnessRoot },
|
|
159
|
+
{ label: 'harness 名称', value: harnessName },
|
|
160
|
+
{ label: 'harness 版本', value: harnessVersion },
|
|
161
|
+
{ label: '目标项目', value: targetRoot },
|
|
162
|
+
{ label: 'agent', value: agent },
|
|
163
|
+
{ label: '模式', value: cloneMode ? 'clone(拷贝整个 harness 到目标项目)' : 'bind-only(仅绑定,不拷贝)' },
|
|
164
|
+
];
|
|
165
|
+
if (cloneMode && effectiveHarnessRoot !== harnessRoot) {
|
|
166
|
+
configRows.push({ label: '克隆目标', value: effectiveHarnessRoot });
|
|
167
|
+
}
|
|
168
|
+
configRows.push({ label: '注入 skill', value: node_path_1.default.relative(targetRoot, skillFile) }, { label: '绑定元数据(冗余副本)', value: node_path_1.default.relative(targetRoot, bindingFile) }, { label: '工具版本', value: `svharness@${cliVersion}` });
|
|
169
|
+
logger_1.logger.configBox('apply 配置确认', configRows);
|
|
170
|
+
if (!opts.yes) {
|
|
171
|
+
const { ok } = await (0, prompts_1.default)({
|
|
172
|
+
type: 'confirm',
|
|
173
|
+
name: 'ok',
|
|
174
|
+
message: '确认把以上 harness 绑定到该项目?',
|
|
175
|
+
initial: true,
|
|
176
|
+
});
|
|
177
|
+
if (!ok) {
|
|
178
|
+
logger_1.logger.warn('用户取消');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// 7. Guard against overwrite.
|
|
183
|
+
if (await fs_extra_1.default.pathExists(dispatcherDir)) {
|
|
184
|
+
if (!opts.force) {
|
|
185
|
+
throw new Error(`目标 skill 目录已存在:${dispatcherDir}\n 使用 --force 覆盖,或先执行 svharness detach(暂未实现:可手动删除该目录)。`);
|
|
186
|
+
}
|
|
187
|
+
logger_1.logger.warn(`--force 生效,删除已有 ${dispatcherDir}`);
|
|
188
|
+
await fs_extra_1.default.remove(dispatcherDir);
|
|
189
|
+
}
|
|
190
|
+
// 7b. Clone step — copy the whole harness into the target project first,
|
|
191
|
+
// so subsequent skill injection and binding resolve to the local copy.
|
|
192
|
+
const totalSteps = cloneMode && effectiveHarnessRoot !== harnessRoot ? 3 : 2;
|
|
193
|
+
let stepNo = 1;
|
|
194
|
+
if (cloneMode && effectiveHarnessRoot !== harnessRoot) {
|
|
195
|
+
logger_1.logger.section(`步骤 ${stepNo}/${totalSteps} - 克隆 harness 到目标项目`);
|
|
196
|
+
if (await fs_extra_1.default.pathExists(cloneDest)) {
|
|
197
|
+
if (!opts.force) {
|
|
198
|
+
throw new Error(`克隆目标已存在:${cloneDest}\n 使用 --force 覆盖,或改用 bind-only(去掉 --clone)。`);
|
|
199
|
+
}
|
|
200
|
+
logger_1.logger.warn(`--force 生效,删除已有 ${cloneDest}`);
|
|
201
|
+
await fs_extra_1.default.remove(cloneDest);
|
|
202
|
+
}
|
|
203
|
+
await fs_extra_1.default.copy(harnessRoot, cloneDest, {
|
|
204
|
+
overwrite: true,
|
|
205
|
+
errorOnExist: false,
|
|
206
|
+
});
|
|
207
|
+
logger_1.logger.success(`已克隆 harness:${node_path_1.default.relative(targetRoot, cloneDest)}`);
|
|
208
|
+
stepNo++;
|
|
209
|
+
}
|
|
210
|
+
// 8. Build binding metadata first (needed for both inline rendering into
|
|
211
|
+
// the skill body and the redundant sidecar file).
|
|
212
|
+
const binding = {
|
|
213
|
+
harness_name: harnessName,
|
|
214
|
+
harness_root: effectiveHarnessRoot,
|
|
215
|
+
harness_root_rel: harnessRootRel,
|
|
216
|
+
harness_version: harnessVersion,
|
|
217
|
+
target_project: targetRoot,
|
|
218
|
+
agent,
|
|
219
|
+
mode: cloneMode ? 'clone' : 'bind-only',
|
|
220
|
+
applied_at: appliedAt,
|
|
221
|
+
applied_by: `svharness@${cliVersion}`,
|
|
222
|
+
};
|
|
223
|
+
const bindingYaml = '# Generated by `svharness apply`. DO NOT edit by hand.\n' +
|
|
224
|
+
'# Re-run `svharness apply --force` to refresh.\n' +
|
|
225
|
+
js_yaml_1.default.dump(binding, { lineWidth: 120, noRefs: true });
|
|
226
|
+
// 8b. Write skill content with binding inlined into the template body
|
|
227
|
+
// (agent-specific transform keeps frontmatter rules consistent across
|
|
228
|
+
// IDEs). The inlined `## 绑定元数据` block is the runtime source of
|
|
229
|
+
// truth — agents parse it from the skill content itself, never from
|
|
230
|
+
// the sidecar file.
|
|
231
|
+
logger_1.logger.section(`步骤 ${stepNo}/${totalSteps} - 注入 dispatcher skill`);
|
|
232
|
+
const rawSkill = await fs_extra_1.default.readFile(templatePath, 'utf8');
|
|
233
|
+
const bindingPlaceholder = '{{BINDING_YAML_BLOCK}}';
|
|
234
|
+
if (!rawSkill.includes(bindingPlaceholder)) {
|
|
235
|
+
throw new Error(`dispatcher skill 模板缺少占位符 ${bindingPlaceholder}:${templatePath}\n` +
|
|
236
|
+
' 请升级 svharness 到同时包含内联 binding 机制的版本。');
|
|
237
|
+
}
|
|
238
|
+
const bindingBlock = '```yaml\n' +
|
|
239
|
+
js_yaml_1.default.dump(binding, { lineWidth: 120, noRefs: true }).trimEnd() +
|
|
240
|
+
'\n```';
|
|
241
|
+
const renderedSkill = rawSkill.replace(bindingPlaceholder, bindingBlock);
|
|
242
|
+
const skillContent = adapter.transform
|
|
243
|
+
? adapter.transform(renderedSkill, `${DISPATCHER_SKILL_NAME}.md`)
|
|
244
|
+
: renderedSkill;
|
|
245
|
+
await fs_extra_1.default.ensureDir(dispatcherDir);
|
|
246
|
+
await fs_extra_1.default.outputFile(skillFile, skillContent, 'utf8');
|
|
247
|
+
logger_1.logger.success(`已写入 ${node_path_1.default.relative(targetRoot, skillFile)}`);
|
|
248
|
+
stepNo++;
|
|
249
|
+
// 9. Write binding.yaml as a redundant sidecar copy (scheme B tooling:
|
|
250
|
+
// detach / status / re-apply detection). Runtime dispatcher does NOT
|
|
251
|
+
// read this file — the authoritative source is the inlined block
|
|
252
|
+
// written in step 8b.
|
|
253
|
+
logger_1.logger.section(`步骤 ${stepNo}/${totalSteps} - 写入 harness 绑定元数据(冗余副本)`);
|
|
254
|
+
await fs_extra_1.default.outputFile(bindingFile, bindingYaml, 'utf8');
|
|
255
|
+
logger_1.logger.success(`已写入 ${node_path_1.default.relative(targetRoot, bindingFile)}(冗余副本)`);
|
|
256
|
+
// 10. Next steps hint.
|
|
257
|
+
logger_1.logger.plain('');
|
|
258
|
+
logger_1.logger.plain('✨ harness 已绑定到当前项目');
|
|
259
|
+
logger_1.logger.plain('');
|
|
260
|
+
logger_1.logger.plain(' 下一步:在 agent 中输入');
|
|
261
|
+
logger_1.logger.plain(` 应用 ${DISPATCHER_SKILL_NAME} 完成 <你的功能> 功能开发`);
|
|
262
|
+
logger_1.logger.plain('');
|
|
263
|
+
if (cloneMode && effectiveHarnessRoot !== harnessRoot) {
|
|
264
|
+
logger_1.logger.plain(` harness 内容已克隆至:${node_path_1.default.relative(targetRoot, effectiveHarnessRoot)}/(已纳入目标项目)`);
|
|
265
|
+
logger_1.logger.plain(` 源 harness 仍保留在:${harnessRoot}(可独立演进后再次 apply --clone --force 更新)`);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
logger_1.logger.plain(` harness 内容仍位于:${harnessRoot}(未拷贝;如需完整内聚请加 --clone)`);
|
|
269
|
+
}
|
|
270
|
+
logger_1.logger.plain(` 解绑命令:手动删除 ${node_path_1.default.relative(targetRoot, dispatcherDir)}`);
|
|
271
|
+
logger_1.logger.plain('');
|
|
272
|
+
}
|