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,371 @@
|
|
|
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.runConvert = runConvert;
|
|
7
|
+
/**
|
|
8
|
+
* `svharness convert`
|
|
9
|
+
*
|
|
10
|
+
* Upload local source documents (pdf / docx / pptx / xlsx / html / txt / ...)
|
|
11
|
+
* to a remotely-deployed `markitdown_serve` instance and persist the returned
|
|
12
|
+
* Markdown under `--output` (standalone) or `<harness>/<type>/md/` (harness mode).
|
|
13
|
+
*
|
|
14
|
+
* Design rules (aligned with the approved plan):
|
|
15
|
+
* - The CLI is a pure HTTP client. It never spawns / installs / manages the
|
|
16
|
+
* Python-side service.
|
|
17
|
+
* - Independent subcommand — does NOT run as part of `build`.
|
|
18
|
+
* - Single-file failures do not abort the whole batch; the summary prints
|
|
19
|
+
* success / skipped / failed counts at the end.
|
|
20
|
+
* - Existing output files are kept by default and receive a `-N` suffix on
|
|
21
|
+
* name collision; pass `--force` to overwrite instead.
|
|
22
|
+
*/
|
|
23
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
24
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
25
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
26
|
+
const logger_1 = require("../utils/logger");
|
|
27
|
+
const validate_args_1 = require("../utils/validate-args");
|
|
28
|
+
const markitdown_client_1 = require("../core/markitdown-client");
|
|
29
|
+
const doc_intake_paths_1 = require("../core/doc-intake-paths");
|
|
30
|
+
/**
|
|
31
|
+
* Extensions recognised by Microsoft MarkItDown (v0.x). Anything outside this
|
|
32
|
+
* whitelist is skipped client-side to avoid a round-trip that is guaranteed to
|
|
33
|
+
* return 415. Keep in sync with the server-side whitelist in
|
|
34
|
+
* `markitdown_serve/app/main.py`.
|
|
35
|
+
*/
|
|
36
|
+
const SUPPORTED_EXTS = new Set([
|
|
37
|
+
'.pdf',
|
|
38
|
+
'.docx',
|
|
39
|
+
'.doc',
|
|
40
|
+
'.pptx',
|
|
41
|
+
'.ppt',
|
|
42
|
+
'.xlsx',
|
|
43
|
+
'.xls',
|
|
44
|
+
'.html',
|
|
45
|
+
'.htm',
|
|
46
|
+
'.epub',
|
|
47
|
+
'.csv',
|
|
48
|
+
'.json',
|
|
49
|
+
'.xml',
|
|
50
|
+
'.txt',
|
|
51
|
+
'.md',
|
|
52
|
+
'.rtf',
|
|
53
|
+
'.msg',
|
|
54
|
+
'.eml',
|
|
55
|
+
'.zip',
|
|
56
|
+
'.jpg',
|
|
57
|
+
'.jpeg',
|
|
58
|
+
'.png',
|
|
59
|
+
'.bmp',
|
|
60
|
+
'.tiff',
|
|
61
|
+
'.mp3',
|
|
62
|
+
'.wav',
|
|
63
|
+
'.m4a',
|
|
64
|
+
]);
|
|
65
|
+
async function runConvert(opts) {
|
|
66
|
+
(0, logger_1.setVerbose)(!!opts.verbose);
|
|
67
|
+
const v = (0, validate_args_1.validateConvertArgs)({
|
|
68
|
+
input: opts.input,
|
|
69
|
+
harness: opts.harness,
|
|
70
|
+
output: opts.output,
|
|
71
|
+
allowMissingHarnessYaml: opts.allowMissingHarnessYaml,
|
|
72
|
+
endpoint: opts.endpoint,
|
|
73
|
+
concurrency: opts.concurrency,
|
|
74
|
+
maxFileMB: opts.maxFileMB,
|
|
75
|
+
timeoutSec: opts.timeoutSec,
|
|
76
|
+
type: opts.type,
|
|
77
|
+
});
|
|
78
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
79
|
+
const outDir = v.outputDir;
|
|
80
|
+
await fs_extra_1.default.ensureDir(outDir);
|
|
81
|
+
const configRows = [
|
|
82
|
+
{ label: 'mode ', value: v.harnessMode ? 'harness' : 'standalone' },
|
|
83
|
+
];
|
|
84
|
+
if (v.harnessMode && v.harnessRoot) {
|
|
85
|
+
configRows.push({ label: 'harness ', value: v.harnessRoot });
|
|
86
|
+
configRows.push({ label: 'type ', value: v.type });
|
|
87
|
+
}
|
|
88
|
+
configRows.push({ label: 'output dir ', value: outDir }, { label: 'endpoint ', value: v.endpoint }, { label: 'concurrency', value: String(v.concurrency) }, { label: 'max file ', value: `${v.maxFileMB} MB` }, { label: 'timeout ', value: `${v.timeoutSec}s` }, { label: 'force ', value: opts.force ? 'yes' : 'no' });
|
|
89
|
+
logger_1.logger.configBox('svharness convert', configRows);
|
|
90
|
+
// 1. Collect candidate files.
|
|
91
|
+
const candidates = await collectFiles(v.input, cwd);
|
|
92
|
+
if (candidates.length === 0) {
|
|
93
|
+
logger_1.logger.warn('no files matched the given --input patterns');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
logger_1.logger.info(`discovered ${candidates.length} candidate file(s)`);
|
|
97
|
+
// 2. Partition into convertible vs skipped (size / ext).
|
|
98
|
+
const limitBytes = v.maxFileMB * 1024 * 1024;
|
|
99
|
+
const plan = await buildPlan(candidates, limitBytes);
|
|
100
|
+
const skipped = plan.filter((p) => p.status === 'skipped');
|
|
101
|
+
const toUpload = plan.filter((p) => p.status !== 'skipped');
|
|
102
|
+
for (const s of skipped) {
|
|
103
|
+
logger_1.logger.debug(`skip ${s.source}: ${s.reason}`);
|
|
104
|
+
}
|
|
105
|
+
if (skipped.length > 0) {
|
|
106
|
+
logger_1.logger.warn(`skipping ${skipped.length} file(s) locally (unsupported ext or oversized)`);
|
|
107
|
+
}
|
|
108
|
+
if (toUpload.length === 0) {
|
|
109
|
+
logger_1.logger.warn('nothing to upload after local filtering');
|
|
110
|
+
printSummary(skipped.map(planToResult));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// 3. Interactive confirmation (skipped with -y).
|
|
114
|
+
if (!opts.yes) {
|
|
115
|
+
const ans = await (0, prompts_1.default)({
|
|
116
|
+
type: 'confirm',
|
|
117
|
+
name: 'go',
|
|
118
|
+
message: `Upload ${toUpload.length} file(s) to ${v.endpoint}?`,
|
|
119
|
+
initial: true,
|
|
120
|
+
});
|
|
121
|
+
if (!ans.go) {
|
|
122
|
+
logger_1.logger.info('aborted by user');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// 4. Probe health endpoint before any upload.
|
|
127
|
+
try {
|
|
128
|
+
const h = await (0, markitdown_client_1.ping)(v.endpoint);
|
|
129
|
+
logger_1.logger.debug(`healthz ok: status=${h.status} markitdown=${h.markitdown_version ?? 'n/a'}`);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
const e = err;
|
|
133
|
+
logger_1.logger.error(`markitdown_serve unreachable at ${v.endpoint}: ${e.message}`);
|
|
134
|
+
logger_1.logger.plain('');
|
|
135
|
+
logger_1.logger.plain(' - confirm the service is deployed and reachable from this machine');
|
|
136
|
+
logger_1.logger.plain(' - pass --endpoint <url> or set env SVHARNESS_MARKITDOWN_ENDPOINT');
|
|
137
|
+
logger_1.logger.plain(' - check the server code under svharnessbuild/markitdown_serve/');
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// 5. Bounded-concurrency upload.
|
|
142
|
+
const results = skipped.map(planToResult);
|
|
143
|
+
const used = new Set();
|
|
144
|
+
const queue = toUpload.slice();
|
|
145
|
+
const workers = [];
|
|
146
|
+
const runOne = async () => {
|
|
147
|
+
while (queue.length > 0) {
|
|
148
|
+
const item = queue.shift();
|
|
149
|
+
if (!item)
|
|
150
|
+
break;
|
|
151
|
+
results.push(await uploadOne(item.source, outDir, used, v, opts));
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
for (let i = 0; i < Math.min(v.concurrency, toUpload.length); i++) {
|
|
155
|
+
workers.push(runOne());
|
|
156
|
+
}
|
|
157
|
+
await Promise.all(workers);
|
|
158
|
+
// 6. Summary.
|
|
159
|
+
printSummary(results);
|
|
160
|
+
if (v.harnessMode && v.harnessRoot) {
|
|
161
|
+
const relocated = await (0, doc_intake_paths_1.relocateMisplacedConvertedMd)(v.harnessRoot, v.type);
|
|
162
|
+
if (relocated > 0) {
|
|
163
|
+
logger_1.logger.warn(`已将 ${relocated} 个误落在 raw/**/converted_md/ 下的 .md 移至 ${v.type}/md/`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
167
|
+
if (failed.length > 0) {
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Walk glob patterns / explicit paths / directories into an absolute file list. */
|
|
172
|
+
async function collectFiles(patterns, cwd) {
|
|
173
|
+
const out = new Set();
|
|
174
|
+
for (const p of patterns) {
|
|
175
|
+
if (isGlob(p)) {
|
|
176
|
+
const matched = await globExpand(p, cwd);
|
|
177
|
+
for (const m of matched)
|
|
178
|
+
out.add(m);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const abs = node_path_1.default.isAbsolute(p) ? p : node_path_1.default.resolve(cwd, p);
|
|
182
|
+
if (!(await fs_extra_1.default.pathExists(abs))) {
|
|
183
|
+
logger_1.logger.warn(`input not found, skipped: ${p}`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const st = await fs_extra_1.default.stat(abs);
|
|
187
|
+
if (st.isDirectory()) {
|
|
188
|
+
for (const f of await walkDir(abs))
|
|
189
|
+
out.add(f);
|
|
190
|
+
}
|
|
191
|
+
else if (st.isFile()) {
|
|
192
|
+
out.add(abs);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return [...out].sort();
|
|
196
|
+
}
|
|
197
|
+
function isGlob(p) {
|
|
198
|
+
return /[*?[\]{}]/.test(p);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Minimal glob expander to avoid pulling in a dependency. Supports the
|
|
202
|
+
* common forms users actually type: `./docs/*.pdf`, `docs/**\/*.docx`,
|
|
203
|
+
* `a/{x,y}.md`. Falls back to treating the pattern as a literal path if no
|
|
204
|
+
* metachars remain after expansion.
|
|
205
|
+
*
|
|
206
|
+
* Implementation: split pattern into base directory (up to first metachar)
|
|
207
|
+
* and the glob tail, then walk the base directory and regex-match each file.
|
|
208
|
+
*/
|
|
209
|
+
async function globExpand(pattern, cwd) {
|
|
210
|
+
// Expand simple brace groups `{a,b,c}` into multiple patterns first.
|
|
211
|
+
const expanded = expandBraces(pattern);
|
|
212
|
+
const results = new Set();
|
|
213
|
+
for (const pat of expanded) {
|
|
214
|
+
for (const f of await globOne(pat, cwd))
|
|
215
|
+
results.add(f);
|
|
216
|
+
}
|
|
217
|
+
return [...results];
|
|
218
|
+
}
|
|
219
|
+
function expandBraces(pattern) {
|
|
220
|
+
const m = /\{([^{}]+)\}/.exec(pattern);
|
|
221
|
+
if (!m)
|
|
222
|
+
return [pattern];
|
|
223
|
+
const [whole, inner] = m;
|
|
224
|
+
const options = inner.split(',');
|
|
225
|
+
const head = pattern.slice(0, m.index);
|
|
226
|
+
const tail = pattern.slice(m.index + whole.length);
|
|
227
|
+
return options.flatMap((opt) => expandBraces(head + opt + tail));
|
|
228
|
+
}
|
|
229
|
+
async function globOne(pattern, cwd) {
|
|
230
|
+
const norm = pattern.replace(/\\/g, '/');
|
|
231
|
+
const firstMeta = norm.search(/[*?[]/);
|
|
232
|
+
if (firstMeta === -1) {
|
|
233
|
+
const abs = node_path_1.default.isAbsolute(norm) ? norm : node_path_1.default.resolve(cwd, norm);
|
|
234
|
+
return (await fs_extra_1.default.pathExists(abs)) ? [abs] : [];
|
|
235
|
+
}
|
|
236
|
+
const lastSlashBeforeMeta = norm.lastIndexOf('/', firstMeta);
|
|
237
|
+
const basePart = lastSlashBeforeMeta === -1 ? '.' : norm.slice(0, lastSlashBeforeMeta) || '/';
|
|
238
|
+
const tail = lastSlashBeforeMeta === -1 ? norm : norm.slice(lastSlashBeforeMeta + 1);
|
|
239
|
+
const baseAbs = node_path_1.default.isAbsolute(basePart)
|
|
240
|
+
? basePart
|
|
241
|
+
: node_path_1.default.resolve(cwd, basePart);
|
|
242
|
+
if (!(await fs_extra_1.default.pathExists(baseAbs)))
|
|
243
|
+
return [];
|
|
244
|
+
const re = globToRegex(tail);
|
|
245
|
+
const all = await walkDir(baseAbs);
|
|
246
|
+
return all.filter((f) => re.test(node_path_1.default.relative(baseAbs, f).replace(/\\/g, '/')));
|
|
247
|
+
}
|
|
248
|
+
function globToRegex(glob) {
|
|
249
|
+
let re = '^';
|
|
250
|
+
for (let i = 0; i < glob.length; i++) {
|
|
251
|
+
const c = glob[i];
|
|
252
|
+
if (c === '*') {
|
|
253
|
+
if (glob[i + 1] === '*') {
|
|
254
|
+
re += '.*';
|
|
255
|
+
i++;
|
|
256
|
+
if (glob[i + 1] === '/')
|
|
257
|
+
i++;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
re += '[^/]*';
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else if (c === '?') {
|
|
264
|
+
re += '[^/]';
|
|
265
|
+
}
|
|
266
|
+
else if ('.+^$()|\\'.includes(c)) {
|
|
267
|
+
re += '\\' + c;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
re += c;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
re += '$';
|
|
274
|
+
return new RegExp(re);
|
|
275
|
+
}
|
|
276
|
+
async function walkDir(dir) {
|
|
277
|
+
const out = [];
|
|
278
|
+
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
279
|
+
for (const e of entries) {
|
|
280
|
+
const abs = node_path_1.default.join(dir, e.name);
|
|
281
|
+
if (e.isDirectory()) {
|
|
282
|
+
// Skip harness artifact dirs and noisy dot-dirs.
|
|
283
|
+
if ((0, doc_intake_paths_1.shouldSkipRawSubdir)(e.name)) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
out.push(...(await walkDir(abs)));
|
|
287
|
+
}
|
|
288
|
+
else if (e.isFile()) {
|
|
289
|
+
out.push(abs);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
function planToResult(p) {
|
|
295
|
+
return p.status === 'skipped'
|
|
296
|
+
? { source: p.source, status: 'skipped', reason: p.reason }
|
|
297
|
+
: { source: p.source, status: 'ok' };
|
|
298
|
+
}
|
|
299
|
+
async function buildPlan(files, limitBytes) {
|
|
300
|
+
const out = [];
|
|
301
|
+
for (const f of files) {
|
|
302
|
+
const ext = node_path_1.default.extname(f).toLowerCase();
|
|
303
|
+
if (!SUPPORTED_EXTS.has(ext)) {
|
|
304
|
+
out.push({ source: f, status: 'skipped', reason: `unsupported ext ${ext || '(none)'}` });
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const st = await fs_extra_1.default.stat(f);
|
|
308
|
+
if (st.size > limitBytes) {
|
|
309
|
+
out.push({
|
|
310
|
+
source: f,
|
|
311
|
+
status: 'skipped',
|
|
312
|
+
reason: `exceeds max-file-mb (${Math.round(st.size / 1024 / 1024)}MB)`,
|
|
313
|
+
});
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
out.push({ source: f, status: 'ready' });
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
async function uploadOne(source, outDir, used, v, opts) {
|
|
321
|
+
const basename = node_path_1.default.basename(source, node_path_1.default.extname(source));
|
|
322
|
+
const outPath = await resolveOutputPath(outDir, basename, used, !!opts.force);
|
|
323
|
+
used.add(outPath);
|
|
324
|
+
logger_1.logger.debug(`upload ${source} -> ${outPath}`);
|
|
325
|
+
try {
|
|
326
|
+
const resp = await (0, markitdown_client_1.postConvertWithRetry)(v.endpoint, source, v.timeoutSec * 1000);
|
|
327
|
+
await fs_extra_1.default.writeFile(outPath, resp.markdown, 'utf8');
|
|
328
|
+
const bytes = Buffer.byteLength(resp.markdown, 'utf8');
|
|
329
|
+
logger_1.logger.success(`${node_path_1.default.basename(source)} -> ${node_path_1.default.basename(outPath)} (${bytes}B)`);
|
|
330
|
+
return { source, output: outPath, status: 'ok', bytes };
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
const e = err;
|
|
334
|
+
const reason = e.message;
|
|
335
|
+
logger_1.logger.error(`failed: ${node_path_1.default.basename(source)} — ${reason}`);
|
|
336
|
+
return { source, status: 'failed', reason };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Decide the final .md path. With `--force` we always write to
|
|
341
|
+
* `<basename>.md`; otherwise we append `-1`, `-2`, ... until a free slot is
|
|
342
|
+
* found (also respecting files reserved by the current run via `used`).
|
|
343
|
+
*/
|
|
344
|
+
async function resolveOutputPath(outDir, basename, used, force) {
|
|
345
|
+
const primary = node_path_1.default.join(outDir, `${basename}.md`);
|
|
346
|
+
if (force)
|
|
347
|
+
return primary;
|
|
348
|
+
if (!used.has(primary) && !(await fs_extra_1.default.pathExists(primary)))
|
|
349
|
+
return primary;
|
|
350
|
+
for (let i = 1; i < 1000; i++) {
|
|
351
|
+
const cand = node_path_1.default.join(outDir, `${basename}-${i}.md`);
|
|
352
|
+
if (!used.has(cand) && !(await fs_extra_1.default.pathExists(cand)))
|
|
353
|
+
return cand;
|
|
354
|
+
}
|
|
355
|
+
throw new Error(`unable to allocate unique output filename for ${basename} under ${outDir}`);
|
|
356
|
+
}
|
|
357
|
+
function printSummary(results) {
|
|
358
|
+
const ok = results.filter((r) => r.status === 'ok').length;
|
|
359
|
+
const failed = results.filter((r) => r.status === 'failed').length;
|
|
360
|
+
const skipped = results.filter((r) => r.status === 'skipped').length;
|
|
361
|
+
logger_1.logger.section('convert summary');
|
|
362
|
+
logger_1.logger.plain(` ok : ${ok}`);
|
|
363
|
+
logger_1.logger.plain(` skipped : ${skipped}`);
|
|
364
|
+
logger_1.logger.plain(` failed : ${failed}`);
|
|
365
|
+
if (failed > 0) {
|
|
366
|
+
logger_1.logger.plain('');
|
|
367
|
+
for (const r of results.filter((x) => x.status === 'failed')) {
|
|
368
|
+
logger_1.logger.plain(` - ${node_path_1.default.basename(r.source)}: ${r.reason}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -16,8 +16,14 @@ const state_1 = require("../core/state");
|
|
|
16
16
|
const agent_injector_1 = require("../core/agent-injector");
|
|
17
17
|
const adapters_1 = require("../adapters");
|
|
18
18
|
const next_steps_1 = require("../core/next-steps");
|
|
19
|
+
const build_project_entry_1 = require("../core/build-project-entry");
|
|
20
|
+
const project_ignore_1 = require("../core/project-ignore");
|
|
21
|
+
const harness_name_1 = require("../utils/harness-name");
|
|
19
22
|
const repomix_pack_1 = require("../core/repomix-pack");
|
|
23
|
+
const extra_assets_intake_1 = require("../core/extra-assets-intake");
|
|
20
24
|
const baseline_copy_1 = require("../utils/baseline-copy");
|
|
25
|
+
const doc_intake_paths_1 = require("../core/doc-intake-paths");
|
|
26
|
+
const convert_1 = require("./convert");
|
|
21
27
|
const wiki_1 = require("../wiki");
|
|
22
28
|
/**
|
|
23
29
|
* Resolve the absolute path to the bundled templates/ directory.
|
|
@@ -26,6 +32,45 @@ const wiki_1 = require("../wiki");
|
|
|
26
32
|
function resolveTemplatesRoot() {
|
|
27
33
|
return node_path_1.default.resolve(__dirname, '..', '..', 'templates');
|
|
28
34
|
}
|
|
35
|
+
async function runBuildAutoConvert(targetRoot, rawDir, type, opts) {
|
|
36
|
+
logger_1.logger.info(`开始自动转换 ${type}:${rawDir} -> ${node_path_1.default.join(targetRoot, type, 'md')}`);
|
|
37
|
+
const prevExitCode = process.exitCode;
|
|
38
|
+
process.exitCode = undefined;
|
|
39
|
+
try {
|
|
40
|
+
await (0, convert_1.runConvert)({
|
|
41
|
+
input: [rawDir],
|
|
42
|
+
// Explicit md dir: harness.yaml may not exist yet (convert runs before render-meta).
|
|
43
|
+
output: node_path_1.default.join(targetRoot, type, 'md'),
|
|
44
|
+
harness: targetRoot,
|
|
45
|
+
type,
|
|
46
|
+
endpoint: opts.convertEndpoint,
|
|
47
|
+
concurrency: opts.convertConcurrency,
|
|
48
|
+
maxFileMB: opts.convertMaxFileMB,
|
|
49
|
+
timeoutSec: opts.convertTimeoutSec,
|
|
50
|
+
force: opts.convertForce,
|
|
51
|
+
yes: true,
|
|
52
|
+
verbose: opts.verbose,
|
|
53
|
+
cwd: opts.cwd,
|
|
54
|
+
allowMissingHarnessYaml: true,
|
|
55
|
+
});
|
|
56
|
+
const relocated = await (0, doc_intake_paths_1.relocateMisplacedConvertedMd)(targetRoot, type);
|
|
57
|
+
if (relocated > 0) {
|
|
58
|
+
logger_1.logger.warn(`已将 ${relocated} 个误落在 raw/**/converted_md/ 下的 .md 移至 ${type}/md/`);
|
|
59
|
+
}
|
|
60
|
+
if ((process.exitCode ?? 0) !== 0) {
|
|
61
|
+
logger_1.logger.warn(`自动转换 ${type} 存在失败项,已继续 build 流程`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
logger_1.logger.success(`自动转换 ${type} 完成`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
logger_1.logger.warn(`自动转换 ${type} 失败,已继续 build:${err.message}`);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
process.exitCode = prevExitCode;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
29
74
|
async function runInit(opts) {
|
|
30
75
|
(0, logger_1.setVerbose)(!!opts.verbose);
|
|
31
76
|
// 0. Mutual-exclusion between wiki modes.
|
|
@@ -46,14 +91,15 @@ async function runInit(opts) {
|
|
|
46
91
|
logger_1.logger.warn('--baseline-branch 在本地基线模式下被忽略');
|
|
47
92
|
}
|
|
48
93
|
const cwd = opts.cwd ?? process.cwd();
|
|
49
|
-
const
|
|
94
|
+
const harnessDirName = (0, harness_name_1.harnessDirNameFromName)(validated.name);
|
|
95
|
+
const targetRoot = node_path_1.default.resolve(cwd, harnessDirName);
|
|
50
96
|
const cliVersion = (0, version_1.getCliVersion)();
|
|
51
97
|
const createdAt = new Date().toISOString();
|
|
52
|
-
// Baseline code lands at <target>/
|
|
98
|
+
// Baseline code lands at <target>/baseline/code/ for both git & local
|
|
53
99
|
// modes. Wiki generation consumes this directory by default so it always
|
|
54
100
|
// reflects the baseline the user asked for (not the caller's cwd).
|
|
55
101
|
const hasBaseline = !!validated.baseline;
|
|
56
|
-
const baselineCodeDir = node_path_1.default.join(targetRoot, '
|
|
102
|
+
const baselineCodeDir = node_path_1.default.join(targetRoot, 'baseline', 'code');
|
|
57
103
|
const wikiSourceRoot = node_path_1.default.resolve(opts.wikiSource ?? (hasBaseline ? baselineCodeDir : cwd));
|
|
58
104
|
// Derive the final wiki mode:
|
|
59
105
|
// - no baseline -> 'off' (wiki flags are warned and ignored)
|
|
@@ -79,6 +125,24 @@ async function runInit(opts) {
|
|
|
79
125
|
{ label: 'Agent', value: validated.agent },
|
|
80
126
|
{ label: '目标路径', value: targetRoot },
|
|
81
127
|
];
|
|
128
|
+
if (opts.requirements) {
|
|
129
|
+
configItems.push({
|
|
130
|
+
label: 'Requirements 路径',
|
|
131
|
+
value: node_path_1.default.resolve(cwd, opts.requirements),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (opts.references) {
|
|
135
|
+
configItems.push({
|
|
136
|
+
label: 'References 路径',
|
|
137
|
+
value: node_path_1.default.resolve(cwd, opts.references),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if ((opts.extraSkills?.length ?? 0) > 0) {
|
|
141
|
+
configItems.push({
|
|
142
|
+
label: 'Extra 资源',
|
|
143
|
+
value: opts.extraSkills.map((item) => node_path_1.default.resolve(cwd, item)).join(', '),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
82
146
|
if (validated.baseline) {
|
|
83
147
|
const modeTag = validated.baselineMode === 'git' ? 'git' : 'local';
|
|
84
148
|
configItems.push({ label: `基线 (${modeTag})`, value: validated.baseline });
|
|
@@ -106,9 +170,9 @@ async function runInit(opts) {
|
|
|
106
170
|
baseUrl: opts.wikiBaseUrl,
|
|
107
171
|
model: opts.wikiModel,
|
|
108
172
|
});
|
|
109
|
-
configItems.push({ label: 'Wiki 模型', value: wikiCfg.model });
|
|
173
|
+
// configItems.push({ label: 'Wiki 模型', value: wikiCfg.model });
|
|
110
174
|
const displayBaseUrl = wikiCfg.baseUrl.replace(/\/v1$/, '');
|
|
111
|
-
configItems.push({ label: 'Wiki API', value: displayBaseUrl });
|
|
175
|
+
// configItems.push({ label: 'Wiki API', value: displayBaseUrl });
|
|
112
176
|
}
|
|
113
177
|
catch (e) {
|
|
114
178
|
// Ignore config resolution errors for display
|
|
@@ -166,6 +230,8 @@ async function runInit(opts) {
|
|
|
166
230
|
arch: validated.arch,
|
|
167
231
|
agent: validated.agent,
|
|
168
232
|
sourcePath: validated.baseline ?? '',
|
|
233
|
+
requirementsPath: opts.requirements ? node_path_1.default.resolve(cwd, opts.requirements) : '',
|
|
234
|
+
referencesPath: opts.references ? node_path_1.default.resolve(cwd, opts.references) : '',
|
|
169
235
|
createdAt,
|
|
170
236
|
version: cliVersion,
|
|
171
237
|
};
|
|
@@ -175,16 +241,35 @@ async function runInit(opts) {
|
|
|
175
241
|
// optional: baseline copy, repomix pack, wiki generation
|
|
176
242
|
// last: state file
|
|
177
243
|
let stepCursor = 4;
|
|
244
|
+
const hasExtraAssets = (opts.extraSkills?.length ?? 0) > 0;
|
|
245
|
+
const stepExtraAssets = hasExtraAssets ? ++stepCursor : 0;
|
|
178
246
|
const stepBaseline = hasBaseline ? ++stepCursor : 0;
|
|
179
247
|
const stepRepomix = hasBaseline ? ++stepCursor : 0;
|
|
180
248
|
const stepWiki = wikiEnabled ? ++stepCursor : 0;
|
|
181
249
|
const stepState = ++stepCursor;
|
|
182
250
|
const totalSteps = stepCursor;
|
|
183
251
|
let baselineMeta;
|
|
252
|
+
let extraAssetsResult;
|
|
184
253
|
try {
|
|
185
254
|
logger_1.logger.section(`步骤 1/${totalSteps} - 生成目录骨架`);
|
|
186
255
|
const copied = await (0, scaffold_1.scaffoldLayered)(skeletonSrcs, targetRoot);
|
|
187
256
|
logger_1.logger.success(`已拷贝骨架文件 ${copied.length} 个`);
|
|
257
|
+
if (opts.requirements) {
|
|
258
|
+
const requirementRawDir = node_path_1.default.join(targetRoot, 'requirements', 'raw');
|
|
259
|
+
const copiedReq = await (0, doc_intake_paths_1.copySeedInputToRaw)(node_path_1.default.resolve(cwd, opts.requirements), requirementRawDir, 'requirements');
|
|
260
|
+
logger_1.logger.success(`已注入 requirements 输入到 raw:${copiedReq} 项`);
|
|
261
|
+
if (copiedReq > 0) {
|
|
262
|
+
await runBuildAutoConvert(targetRoot, requirementRawDir, 'requirements', opts);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (opts.references) {
|
|
266
|
+
const referenceRawDir = node_path_1.default.join(targetRoot, 'references', 'raw');
|
|
267
|
+
const copiedRef = await (0, doc_intake_paths_1.copySeedInputToRaw)(node_path_1.default.resolve(cwd, opts.references), referenceRawDir, 'references');
|
|
268
|
+
logger_1.logger.success(`已注入 references 输入到 raw:${copiedRef} 项`);
|
|
269
|
+
if (copiedRef > 0) {
|
|
270
|
+
await runBuildAutoConvert(targetRoot, referenceRawDir, 'references', opts);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
188
273
|
logger_1.logger.section(`步骤 2/${totalSteps} - 渲染元文件`);
|
|
189
274
|
const meta = await (0, render_meta_1.renderMetaLayered)(metaSrcs, targetRoot, ctx);
|
|
190
275
|
logger_1.logger.success(`已渲染元文件 ${meta.length} 个: ${meta.join(', ')}`);
|
|
@@ -193,16 +278,34 @@ async function runInit(opts) {
|
|
|
193
278
|
// Skills are injected beside the harness folder (cwd), not inside it,
|
|
194
279
|
// so the Agent picks them up from the project root.
|
|
195
280
|
const harnessDirName = node_path_1.default.basename(targetRoot);
|
|
196
|
-
const
|
|
281
|
+
const buildSkillsBinding = {
|
|
282
|
+
harnessDirName,
|
|
283
|
+
harnessName: validated.name,
|
|
284
|
+
arch: validated.arch,
|
|
285
|
+
agent: validated.agent,
|
|
286
|
+
cliVersion,
|
|
287
|
+
generatedAt: createdAt,
|
|
288
|
+
};
|
|
289
|
+
const injected = await (0, agent_injector_1.injectSkillsLayered)(buildSkillsSrcs, targetRoot, adapter, cwd, buildSkillsBinding);
|
|
197
290
|
logger_1.logger.success(`已向 ${node_path_1.default.join(cwd, adapter.skillsDir)}/ 注入 skill ${injected.length} 个`);
|
|
198
291
|
logger_1.logger.section(`步骤 4/${totalSteps} - 注入 harness 构建规则`);
|
|
199
292
|
const rules = await (0, agent_injector_1.injectRulesLayered)(buildRulesSrcs, adapter, cwd);
|
|
200
293
|
if (adapter.rulesDir) {
|
|
201
294
|
logger_1.logger.success(`已向 ${node_path_1.default.join(cwd, adapter.rulesDir)}/ 注入 rule ${rules.length} 个`);
|
|
202
295
|
}
|
|
296
|
+
if (hasExtraAssets && opts.extraSkills) {
|
|
297
|
+
logger_1.logger.section(`步骤 ${stepExtraAssets}/${totalSteps} - 导入额外运行期资源到 _incoming`);
|
|
298
|
+
extraAssetsResult = await (0, extra_assets_intake_1.intakeExtraAssets)({
|
|
299
|
+
inputs: opts.extraSkills,
|
|
300
|
+
cwd,
|
|
301
|
+
harnessRoot: targetRoot,
|
|
302
|
+
});
|
|
303
|
+
logger_1.logger.success(`incoming 清单: ${extraAssetsResult.manifestRelPath}(总计 ${extraAssetsResult.incomingTotal},` +
|
|
304
|
+
`skills ${extraAssetsResult.skillsCount},rules ${extraAssetsResult.rulesCount},unknown ${extraAssetsResult.unknownCount})`);
|
|
305
|
+
}
|
|
203
306
|
if (hasBaseline && validated.baseline && validated.baselineMode) {
|
|
204
307
|
logger_1.logger.section(`步骤 ${stepBaseline}/${totalSteps} - 拷贝基线源码`);
|
|
205
|
-
const destDir = node_path_1.default.join(targetRoot, '
|
|
308
|
+
const destDir = node_path_1.default.join(targetRoot, 'baseline', 'code');
|
|
206
309
|
baselineMeta = await (0, baseline_copy_1.copyBaseline)({
|
|
207
310
|
mode: validated.baselineMode,
|
|
208
311
|
source: validated.baseline,
|
|
@@ -222,6 +325,40 @@ async function runInit(opts) {
|
|
|
222
325
|
await (0, scaffold_1.rollbackTarget)(targetRoot);
|
|
223
326
|
throw err;
|
|
224
327
|
}
|
|
328
|
+
{
|
|
329
|
+
const adapterEntry = (0, adapters_1.getAdapter)(validated.agent);
|
|
330
|
+
const dirName = node_path_1.default.basename(targetRoot);
|
|
331
|
+
await (0, build_project_entry_1.writeBuildProjectEntry)({
|
|
332
|
+
projectRoot: cwd,
|
|
333
|
+
adapter: adapterEntry,
|
|
334
|
+
harnessDirName: dirName,
|
|
335
|
+
harnessName: validated.name,
|
|
336
|
+
arch: validated.arch,
|
|
337
|
+
agent: validated.agent,
|
|
338
|
+
force: !!opts.force,
|
|
339
|
+
});
|
|
340
|
+
const ignoreEntries = [
|
|
341
|
+
`/${dirName}/`,
|
|
342
|
+
'/AGENTS.md',
|
|
343
|
+
'/CLAUDE.md',
|
|
344
|
+
'/.codechat/',
|
|
345
|
+
];
|
|
346
|
+
const ignoreAdded = await (0, project_ignore_1.appendProjectIgnoreEntries)({
|
|
347
|
+
projectRoot: cwd,
|
|
348
|
+
entries: ignoreEntries,
|
|
349
|
+
marker: '# svharness build',
|
|
350
|
+
});
|
|
351
|
+
const totalLines = Object.values(ignoreAdded).reduce((n, a) => n + a.length, 0);
|
|
352
|
+
if (totalLines > 0) {
|
|
353
|
+
const detail = Object.entries(ignoreAdded)
|
|
354
|
+
.map(([f, lines]) => `${f}: ${lines.join(', ')}`)
|
|
355
|
+
.join(';');
|
|
356
|
+
logger_1.logger.success(`已在项目 ignore 文件中追加:${detail}`);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
logger_1.logger.info('项目 ignore 文件已包含 build 注入路径,未追加新行');
|
|
360
|
+
}
|
|
361
|
+
}
|
|
225
362
|
// 6a. Repomix XML pack of baseline code (default when --baseline; non-fatal on failure).
|
|
226
363
|
if (hasBaseline) {
|
|
227
364
|
logger_1.logger.section(`步骤 ${stepRepomix}/${totalSteps} - 生成 baseline Repomix 包`);
|
|
@@ -258,7 +395,7 @@ async function runInit(opts) {
|
|
|
258
395
|
try {
|
|
259
396
|
const repoRootFs = wikiSourceRoot;
|
|
260
397
|
const wikiOutputRootFs = targetRoot;
|
|
261
|
-
const wikiRelPath = node_path_1.default.join('
|
|
398
|
+
const wikiRelPath = node_path_1.default.join('baseline', 'wiki');
|
|
262
399
|
// Guard: baseline wiki must be based on real code. If the user provided
|
|
263
400
|
// --baseline but the copy step produced an empty directory (e.g. every
|
|
264
401
|
// file was filtered out), refuse to silently index cwd or an empty dir.
|
|
@@ -311,12 +448,12 @@ async function runInit(opts) {
|
|
|
311
448
|
logger_1.logger.warn(`wiki 生成失败(不回滚 harness 骨架):${err.message}`);
|
|
312
449
|
}
|
|
313
450
|
}
|
|
314
|
-
// If the full run failed, downgrade
|
|
451
|
+
// If the full run failed, downgrade S10_wiki from DONE to PENDING
|
|
315
452
|
// so the agent can pick it up via harness-build-skill-wiki-writer.
|
|
316
453
|
if (!wikiFullOk) {
|
|
317
454
|
resolvedWikiPhase = 'pending';
|
|
318
455
|
resolvedWikiSource = 'cli-full-degraded';
|
|
319
|
-
logger_1.logger.warn('
|
|
456
|
+
logger_1.logger.warn('S10_wiki 将标记为 PENDING,等待 agent 接力补齐');
|
|
320
457
|
}
|
|
321
458
|
}
|
|
322
459
|
else if (wikiMode === 'tasks') {
|
|
@@ -324,7 +461,7 @@ async function runInit(opts) {
|
|
|
324
461
|
try {
|
|
325
462
|
const repoRootFs = wikiSourceRoot;
|
|
326
463
|
const wikiOutputRootFs = targetRoot;
|
|
327
|
-
const wikiRelPath = node_path_1.default.join('
|
|
464
|
+
const wikiRelPath = node_path_1.default.join('baseline', 'wiki');
|
|
328
465
|
if (hasBaseline) {
|
|
329
466
|
const exists = await fs_extra_1.default.pathExists(repoRootFs);
|
|
330
467
|
const entries = exists ? await fs_extra_1.default.readdir(repoRootFs) : [];
|
|
@@ -400,6 +537,14 @@ async function runInit(opts) {
|
|
|
400
537
|
wikiPhase: resolvedWikiPhase,
|
|
401
538
|
wikiSource: resolvedWikiSource,
|
|
402
539
|
});
|
|
540
|
+
if (extraAssetsResult) {
|
|
541
|
+
const s65 = state.phases.S65_customize_agent_env;
|
|
542
|
+
s65.incoming_manifest = extraAssetsResult.manifestRelPath;
|
|
543
|
+
s65.incoming_total = extraAssetsResult.incomingTotal;
|
|
544
|
+
s65.skills_count = extraAssetsResult.skillsCount;
|
|
545
|
+
s65.rules_count = extraAssetsResult.rulesCount;
|
|
546
|
+
s65.unknown_count = extraAssetsResult.unknownCount;
|
|
547
|
+
}
|
|
403
548
|
const statePath = await (0, state_1.writeStateFile)(targetRoot, state);
|
|
404
549
|
logger_1.logger.success(`状态文件: ${node_path_1.default.relative(targetRoot, statePath)}`);
|
|
405
550
|
}
|