@lmzhen/dsh-evolution-core 0.30.31 → 0.30.32
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/lib/index.js +1087 -239
- package/lib/types/external-support.d.ts +19 -0
- package/lib/types/external-support.js +96 -0
- package/lib/types/extract-plan.d.ts +127 -0
- package/lib/types/extract-plan.js +232 -0
- package/lib/types/extract-prompt.d.ts +8 -0
- package/lib/types/extract-prompt.js +16 -0
- package/lib/types/index.d.ts +8 -0
- package/lib/types/index.js +8 -0
- package/lib/types/owner-resolver.d.ts +40 -0
- package/lib/types/owner-resolver.js +49 -0
- package/lib/types/proposal-report.d.ts +23 -0
- package/lib/types/proposal-report.js +61 -0
- package/lib/types/proposal-view.d.ts +42 -0
- package/lib/types/proposal-view.js +24 -0
- package/lib/types/section-selection.d.ts +38 -0
- package/lib/types/section-selection.js +78 -0
- package/lib/types/skill-history.d.ts +1 -1
- package/lib/types/skill-history.js +2 -1
- package/lib/types/skill-reads.js +1 -1
- package/lib/types/skill-store.d.ts +10 -12
- package/lib/types/skill-store.js +197 -173
- package/lib/types/support-refs.d.ts +17 -0
- package/lib/types/support-refs.js +35 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import { w as allowRowCollisions } from "./composition-DSesmTvf.js";
|
|
|
4
4
|
import { n as errorText, t as EVOLUTION_ERRORS } from "./errors-Br2-uUSK.js";
|
|
5
5
|
import { createSubagentChannel, probeChannelState, subagentChannelState } from "./subagent/index.js";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
8
|
import { copyFileSync, existsSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { cp, lstat, mkdir, open, readFile, readdir, rename, rm, stat } from "node:fs/promises";
|
|
9
|
+
import { cp, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat } from "node:fs/promises";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { parse } from "yaml";
|
|
12
12
|
import { homedir } from "node:os";
|
|
@@ -2251,6 +2251,40 @@ function describeReferenceRewrite(plan) {
|
|
|
2251
2251
|
].filter((part) => part.length > 0).join("; ")}`;
|
|
2252
2252
|
}
|
|
2253
2253
|
//#endregion
|
|
2254
|
+
//#region lib/types/support-refs.js
|
|
2255
|
+
/**
|
|
2256
|
+
* Support-directory references in a markdown body (009 kernel): references/…,
|
|
2257
|
+
* templates/…, scripts/…, assets/… relative links — any extension and nested
|
|
2258
|
+
* paths. Pure: who checks them and what the verdict is belongs to the caller.
|
|
2259
|
+
* `..` traversal is a different defect class (path validation owns it) and is
|
|
2260
|
+
* not a support link.
|
|
2261
|
+
*/
|
|
2262
|
+
function supportRefs(content) {
|
|
2263
|
+
const refs = [];
|
|
2264
|
+
for (const match of content.matchAll(/(?<![A-Za-z0-9._/-])(?:references|templates|scripts|assets)\/[A-Za-z0-9._/-]+/g)) {
|
|
2265
|
+
const ref = match[0];
|
|
2266
|
+
if (ref.includes("..")) continue;
|
|
2267
|
+
refs.push(ref);
|
|
2268
|
+
}
|
|
2269
|
+
return refs;
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* References that leave the skill root: parent traversals or absolute support paths.
|
|
2273
|
+
* Extract must not silently treat these as relative support files; the caller reports
|
|
2274
|
+
* them (bounded external copy is a separate, explicit policy).
|
|
2275
|
+
* @param content - markdown body.
|
|
2276
|
+
* @returns external-looking support references, de-duplicated in first-seen order.
|
|
2277
|
+
*/
|
|
2278
|
+
function externalRefs(content) {
|
|
2279
|
+
const refs = [];
|
|
2280
|
+
for (const match of content.matchAll(/(?:^|[\s("'`])((?:\.\.\/)+[A-Za-z0-9._/-]+|\/[A-Za-z0-9._/-]+)/g)) {
|
|
2281
|
+
const ref = match[1]?.replace(/[.,;:]+$/, "");
|
|
2282
|
+
if (ref === void 0 || ref.length === 0 || refs.includes(ref)) continue;
|
|
2283
|
+
refs.push(ref);
|
|
2284
|
+
}
|
|
2285
|
+
return refs;
|
|
2286
|
+
}
|
|
2287
|
+
//#endregion
|
|
2254
2288
|
//#region lib/types/constants.js
|
|
2255
2289
|
/**
|
|
2256
2290
|
* Shared constants for the dsh-evolution plugin family.
|
|
@@ -3047,6 +3081,786 @@ function authoringFeedback(frontmatter) {
|
|
|
3047
3081
|
* v28 G2.1 (EVO-IO-05): this is a delegation to the seam's own
|
|
3048
3082
|
* `isCommittedWarning` — the marker predicate has exactly one definition. */
|
|
3049
3083
|
//#endregion
|
|
3084
|
+
//#region lib/types/document-body.js
|
|
3085
|
+
/**
|
|
3086
|
+
* What the panel shows when it renders one version as a document.
|
|
3087
|
+
*
|
|
3088
|
+
* Two decisions, both made here so the preview and the rendered diff cannot drift apart:
|
|
3089
|
+
*
|
|
3090
|
+
* - FRONTMATTER IS NOT DOCUMENT TEXT. A SKILL.md opens with a YAML block; the renderer read its
|
|
3091
|
+
* closing `---` as a setext underline, so the metadata line became the document's biggest heading
|
|
3092
|
+
* and the real title sat below it (measured on the installed panel 2026-09-28). The block is
|
|
3093
|
+
* stripped for DISPLAY only: the raw view and the body route still carry every byte, and the
|
|
3094
|
+
* panel already shows the name and description on the row and in the left column.
|
|
3095
|
+
* - STRIPPING FAILS OPEN. Only a block that the family's own reader accepts is removed. A document
|
|
3096
|
+
* whose text merely starts with a thematic break, a block with no closing line, or a frontmatter
|
|
3097
|
+
* whose body is empty is returned UNCHANGED — showing a document's first line is always better
|
|
3098
|
+
* than showing nothing.
|
|
3099
|
+
*
|
|
3100
|
+
* `readingStats` exists for the other half of the reader's honesty: a preview that stops early has
|
|
3101
|
+
* to be able to say HOW much it is not showing.
|
|
3102
|
+
* @module @lmzhen/dsh-evolution-core/document-body
|
|
3103
|
+
*/
|
|
3104
|
+
/**
|
|
3105
|
+
* The document a reader sees: the body without its leading frontmatter block.
|
|
3106
|
+
* @param content - the stored bytes of one version.
|
|
3107
|
+
* @returns the text to render; unchanged when there is no frontmatter the reader accepts.
|
|
3108
|
+
*/
|
|
3109
|
+
function displayBodyOf(content) {
|
|
3110
|
+
const block = frontmatterBlock(content);
|
|
3111
|
+
if (block === null) return content;
|
|
3112
|
+
if (parseFrontmatter(content) === null) return content;
|
|
3113
|
+
return block.lines.slice(block.end + 1).join("\n").replace(/^\s*\n/, "");
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
3116
|
+
* How much of a version the panel is showing.
|
|
3117
|
+
* @param full - the whole stored text.
|
|
3118
|
+
* @param shown - the part the route handed over.
|
|
3119
|
+
* @returns the counts, so the panel can say what it is not showing.
|
|
3120
|
+
*/
|
|
3121
|
+
function readingStats(full, shown) {
|
|
3122
|
+
return {
|
|
3123
|
+
totalChars: full.length,
|
|
3124
|
+
shownChars: shown.length,
|
|
3125
|
+
truncated: shown.length < full.length
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
/**
|
|
3129
|
+
* The H2 sections of a body, in document order — the ONE scan (G3).
|
|
3130
|
+
*
|
|
3131
|
+
* Both the restructure candidate list and the store anchor validation call this function, so a
|
|
3132
|
+
* candidate can only name a heading that exists EXACTLY ONCE and has content: the two rejections a
|
|
3133
|
+
* hand-written anchor used to hit ("no such heading", "ambiguous anchor") stop being reachable.
|
|
3134
|
+
* H2 is the section boundary (a section spans to the next H2 or EOF), matching the store.
|
|
3135
|
+
* @param body - the markdown after the frontmatter block.
|
|
3136
|
+
* @returns one row per H2 heading, with `count` filled in a second pass.
|
|
3137
|
+
*/
|
|
3138
|
+
function h2Sections(body) {
|
|
3139
|
+
const lines = body.split(String.fromCharCode(10));
|
|
3140
|
+
const rows = [];
|
|
3141
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
3142
|
+
const match = /^#{2}\s+(.+?)\s*$/.exec(lines[i] ?? "");
|
|
3143
|
+
if (match === null) continue;
|
|
3144
|
+
const title = (match[1] ?? "").trim();
|
|
3145
|
+
let end = lines.length;
|
|
3146
|
+
for (let j = i + 1; j < lines.length; j += 1) if (/^#{2}\s/.test(lines[j] ?? "")) {
|
|
3147
|
+
end = j;
|
|
3148
|
+
break;
|
|
3149
|
+
}
|
|
3150
|
+
rows.push({
|
|
3151
|
+
title,
|
|
3152
|
+
start: i,
|
|
3153
|
+
end,
|
|
3154
|
+
count: 0
|
|
3155
|
+
});
|
|
3156
|
+
}
|
|
3157
|
+
for (const row of rows) row.count = rows.filter((other) => other.title === row.title).length;
|
|
3158
|
+
return rows;
|
|
3159
|
+
}
|
|
3160
|
+
/**
|
|
3161
|
+
* The sections a restructure proposal may name: heading unique, section non-empty. Derived from
|
|
3162
|
+
* `h2Sections`, i.e. from the SAME scan the store validates against.
|
|
3163
|
+
* @param body - the markdown after the frontmatter block.
|
|
3164
|
+
* @returns move-ready pairs in document order; empty when nothing qualifies.
|
|
3165
|
+
*/
|
|
3166
|
+
function restructureCandidates(body) {
|
|
3167
|
+
return h2Sections(body).filter((section) => section.count === 1 && section.end > section.start + 1).map((section) => ({
|
|
3168
|
+
heading: section.title,
|
|
3169
|
+
toFile: "references/" + section.title.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-").replace(/^-+|-+$/g, "") + ".md"
|
|
3170
|
+
}));
|
|
3171
|
+
}
|
|
3172
|
+
//#endregion
|
|
3173
|
+
//#region lib/types/section-selection.js
|
|
3174
|
+
const LF$1 = String.fromCharCode(10);
|
|
3175
|
+
/**
|
|
3176
|
+
* Select whole H2 sections or exact content sub-blocks from one body.
|
|
3177
|
+
*
|
|
3178
|
+
* The H2 scan is document-body's h2Sections, so restructure and extract share
|
|
3179
|
+
* one boundary rule. A start/end pair selects an inclusive, contiguous run of
|
|
3180
|
+
* exact lines inside that heading; it cannot select the heading itself, overlap
|
|
3181
|
+
* another request, or leave a selected range empty.
|
|
3182
|
+
* @param body - markdown after frontmatter.
|
|
3183
|
+
* @param requests - exact heading plus optional start/end line pairs.
|
|
3184
|
+
* @returns selected ranges in request order, or the first refusal.
|
|
3185
|
+
*/
|
|
3186
|
+
function selectSections(body, requests) {
|
|
3187
|
+
const lines = body.split(LF$1);
|
|
3188
|
+
const sections = h2Sections(body);
|
|
3189
|
+
const selected = [];
|
|
3190
|
+
for (let requestIndex = 0; requestIndex < requests.length; requestIndex += 1) {
|
|
3191
|
+
const request = requests[requestIndex];
|
|
3192
|
+
if (request === void 0) continue;
|
|
3193
|
+
const wanted = request.heading.trim();
|
|
3194
|
+
const matching = sections.filter((section) => section.title === wanted);
|
|
3195
|
+
if (matching.length === 0) return {
|
|
3196
|
+
ok: false,
|
|
3197
|
+
code: "heading-missing",
|
|
3198
|
+
message: "no \"## " + wanted + "\" heading in the body"
|
|
3199
|
+
};
|
|
3200
|
+
if (matching.length > 1) return {
|
|
3201
|
+
ok: false,
|
|
3202
|
+
code: "heading-ambiguous",
|
|
3203
|
+
message: "heading \"## " + wanted + "\" appears " + matching.length + " times (ambiguous anchor)"
|
|
3204
|
+
};
|
|
3205
|
+
const section = matching[0];
|
|
3206
|
+
if (section === void 0) return {
|
|
3207
|
+
ok: false,
|
|
3208
|
+
code: "heading-missing",
|
|
3209
|
+
message: "no \"## " + wanted + "\" heading in the body"
|
|
3210
|
+
};
|
|
3211
|
+
if (selected.some((entry) => entry.start === section.start)) return {
|
|
3212
|
+
ok: false,
|
|
3213
|
+
code: "heading-duplicate",
|
|
3214
|
+
message: "heading \"## " + wanted + "\" is moved twice"
|
|
3215
|
+
};
|
|
3216
|
+
if (!(request.start !== void 0 || request.end !== void 0)) {
|
|
3217
|
+
if (section.end === section.start + 1) return {
|
|
3218
|
+
ok: false,
|
|
3219
|
+
code: "heading-empty",
|
|
3220
|
+
message: "heading \"## " + wanted + "\" has an empty section"
|
|
3221
|
+
};
|
|
3222
|
+
selected.push({
|
|
3223
|
+
heading: wanted,
|
|
3224
|
+
start: section.start,
|
|
3225
|
+
end: section.end,
|
|
3226
|
+
text: lines.slice(section.start, section.end).join(LF$1),
|
|
3227
|
+
requestIndex
|
|
3228
|
+
});
|
|
3229
|
+
continue;
|
|
3230
|
+
}
|
|
3231
|
+
if (request.start === void 0 || request.end === void 0) return {
|
|
3232
|
+
ok: false,
|
|
3233
|
+
code: "part-anchor-incomplete",
|
|
3234
|
+
message: "heading \"## " + wanted + "\" needs both start and end anchors"
|
|
3235
|
+
};
|
|
3236
|
+
const startMatches = [];
|
|
3237
|
+
const endMatches = [];
|
|
3238
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
3239
|
+
if (index <= section.start || index >= section.end) continue;
|
|
3240
|
+
if (lines[index] === request.start) startMatches.push(index);
|
|
3241
|
+
if (lines[index] === request.end) endMatches.push(index);
|
|
3242
|
+
}
|
|
3243
|
+
if (startMatches.length !== 1) return {
|
|
3244
|
+
ok: false,
|
|
3245
|
+
code: "part-anchor-not-found",
|
|
3246
|
+
message: "heading \"## " + wanted + "\" start anchor must match exactly one content line"
|
|
3247
|
+
};
|
|
3248
|
+
if (endMatches.length !== 1) return {
|
|
3249
|
+
ok: false,
|
|
3250
|
+
code: "part-anchor-not-found",
|
|
3251
|
+
message: "heading \"## " + wanted + "\" end anchor must match exactly one content line"
|
|
3252
|
+
};
|
|
3253
|
+
const start = startMatches[0];
|
|
3254
|
+
const end = endMatches[0];
|
|
3255
|
+
if (start === void 0 || end === void 0 || start > end) return {
|
|
3256
|
+
ok: false,
|
|
3257
|
+
code: "part-anchor-order",
|
|
3258
|
+
message: "heading \"## " + wanted + "\" start anchor is after end anchor"
|
|
3259
|
+
};
|
|
3260
|
+
if (selected.some((entry) => start < entry.end && end + 1 > entry.start)) return {
|
|
3261
|
+
ok: false,
|
|
3262
|
+
code: "part-anchor-overlaps",
|
|
3263
|
+
message: "heading \"## " + wanted + "\" selection overlaps another selected block"
|
|
3264
|
+
};
|
|
3265
|
+
selected.push({
|
|
3266
|
+
heading: wanted,
|
|
3267
|
+
start,
|
|
3268
|
+
end: end + 1,
|
|
3269
|
+
text: lines.slice(start, end + 1).join(LF$1),
|
|
3270
|
+
requestIndex,
|
|
3271
|
+
anchorStart: request.start,
|
|
3272
|
+
anchorEnd: request.end
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3275
|
+
return {
|
|
3276
|
+
ok: true,
|
|
3277
|
+
sections: selected
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
//#endregion
|
|
3281
|
+
//#region lib/types/extract-plan.js
|
|
3282
|
+
const LF = String.fromCharCode(10);
|
|
3283
|
+
function sha256$1(text) {
|
|
3284
|
+
return createHash("sha256").update(text).digest("hex");
|
|
3285
|
+
}
|
|
3286
|
+
function unique(values) {
|
|
3287
|
+
return [...new Set(values)];
|
|
3288
|
+
}
|
|
3289
|
+
function computeSupport(parts, residual, sourceName, sourceFiles, targetFiles, externalFiles) {
|
|
3290
|
+
const selectedText = parts.map((part) => part.sourceText).join("\n\n");
|
|
3291
|
+
const selectedRefs = unique(supportRefs(selectedText));
|
|
3292
|
+
const residualRefs = new Set(supportRefs(residual));
|
|
3293
|
+
const unresolved = selectedRefs.filter((ref) => !sourceFiles.includes(ref));
|
|
3294
|
+
if (unresolved.length > 0) return { error: "support-unresolved: " + unresolved.join(", ") };
|
|
3295
|
+
const candidates = selectedRefs.filter((ref) => sourceFiles.includes(ref));
|
|
3296
|
+
const moves = planRehoming(candidates, targetFiles, sourceName).moves;
|
|
3297
|
+
const byFrom = new Map(moves.map((move) => [move.from, move.to]));
|
|
3298
|
+
const exclusive = [];
|
|
3299
|
+
const shared = [];
|
|
3300
|
+
for (const from of candidates) {
|
|
3301
|
+
const to = byFrom.get(from);
|
|
3302
|
+
if (to === void 0) continue;
|
|
3303
|
+
if (residualRefs.has(from)) shared.push({
|
|
3304
|
+
from,
|
|
3305
|
+
to,
|
|
3306
|
+
provenance: sourceName + ":" + from
|
|
3307
|
+
});
|
|
3308
|
+
else exclusive.push({
|
|
3309
|
+
from,
|
|
3310
|
+
to
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
const targetFilesWithMoves = unique([...targetFiles, ...moves.map((move) => move.to)]);
|
|
3314
|
+
const external = unique(externalRefs(selectedText));
|
|
3315
|
+
const suppliedRefs = new Set(externalFiles.map((file) => file.ref));
|
|
3316
|
+
const missingExternal = external.filter((ref) => !suppliedRefs.has(ref));
|
|
3317
|
+
const warnings = [];
|
|
3318
|
+
if (missingExternal.length > 0) warnings.push("external-support-not-copied: " + missingExternal.join(", "));
|
|
3319
|
+
const externalClosure = [];
|
|
3320
|
+
for (const file of externalFiles) {
|
|
3321
|
+
if (!external.includes(file.ref)) return { error: "external-ref-not-selected: " + file.ref };
|
|
3322
|
+
if (file.targetPath.startsWith("/") || file.targetPath.includes("..") || !/^(?:references|templates|scripts|assets)\//.test(file.targetPath)) return { error: "external-target-unsafe: " + file.targetPath };
|
|
3323
|
+
if (targetFilesWithMoves.includes(file.targetPath)) return { error: "external-collision: " + file.targetPath };
|
|
3324
|
+
externalClosure.push({
|
|
3325
|
+
from: file.ref,
|
|
3326
|
+
to: file.targetPath,
|
|
3327
|
+
provenance: file.provenance,
|
|
3328
|
+
originalPath: file.originalPath,
|
|
3329
|
+
content: file.content,
|
|
3330
|
+
sha256: file.sha256
|
|
3331
|
+
});
|
|
3332
|
+
}
|
|
3333
|
+
const rewritten = /* @__PURE__ */ new Map();
|
|
3334
|
+
for (const part of parts) {
|
|
3335
|
+
const partRefs = supportRefs(part.sourceText);
|
|
3336
|
+
const rewrite = planReferenceRewrite({
|
|
3337
|
+
content: part.sourceText,
|
|
3338
|
+
files: sourceFiles,
|
|
3339
|
+
moves: moves.filter((move) => partRefs.includes(move.from)),
|
|
3340
|
+
targetFiles: targetFilesWithMoves
|
|
3341
|
+
});
|
|
3342
|
+
rewritten.set(part.heading, applyReferenceRewrite(part.sourceText, rewrite.edits));
|
|
3343
|
+
warnings.push(...rewrite.unresolved.map((ref) => "support-unresolved: " + ref));
|
|
3344
|
+
warnings.push(...rewrite.residualDangling.map((ref) => "support-dangling: " + ref));
|
|
3345
|
+
}
|
|
3346
|
+
for (const part of parts) {
|
|
3347
|
+
let text = rewritten.get(part.heading) ?? part.sourceText;
|
|
3348
|
+
for (const file of externalClosure) text = text.split(file.from).join(file.to);
|
|
3349
|
+
part.movedText = text;
|
|
3350
|
+
}
|
|
3351
|
+
const exclusiveSet = new Set(exclusive.map((entry) => entry.from));
|
|
3352
|
+
const parentExclusive = supportRefs(residual).filter((ref) => exclusiveSet.has(ref));
|
|
3353
|
+
if (parentExclusive.length > 0) return { error: "support-exclusive-referenced-by-parent: " + parentExclusive.join(", ") };
|
|
3354
|
+
const dangling = unique(warnings.filter((item) => item.startsWith("support-dangling: ")).map((item) => item.slice(18)));
|
|
3355
|
+
return {
|
|
3356
|
+
support: {
|
|
3357
|
+
exclusive,
|
|
3358
|
+
shared,
|
|
3359
|
+
external: externalClosure,
|
|
3360
|
+
unresolved: warnings.filter((item) => item.startsWith("support-unresolved: ")),
|
|
3361
|
+
dangling
|
|
3362
|
+
},
|
|
3363
|
+
warnings
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Build a pure extract plan. No IO, no writes: selection, residual, support
|
|
3368
|
+
* closure and the moved text are computed from the supplied bodies/file lists.
|
|
3369
|
+
* @param input - source body, target identity, owner decision and support lists.
|
|
3370
|
+
* @returns a plan, or a refusal with a stable code.
|
|
3371
|
+
*/
|
|
3372
|
+
function planExtract(input) {
|
|
3373
|
+
if (input.outcome === "owner-gap") {
|
|
3374
|
+
if (input.owner.mode !== "owner-gap") return {
|
|
3375
|
+
ok: false,
|
|
3376
|
+
code: "owner-gap-mismatch",
|
|
3377
|
+
message: "outcome owner-gap requires an owner-gap decision"
|
|
3378
|
+
};
|
|
3379
|
+
if (input.owner.neededOwner === void 0 || input.owner.neededOwner.trim() === "") return {
|
|
3380
|
+
ok: false,
|
|
3381
|
+
code: "owner-gap-needed-owner",
|
|
3382
|
+
message: "owner-gap requires neededOwner"
|
|
3383
|
+
};
|
|
3384
|
+
} else {
|
|
3385
|
+
if (input.owner.mode !== "covered" || !input.owner.triggerCovers || !input.owner.outcomeCovers) return {
|
|
3386
|
+
ok: false,
|
|
3387
|
+
code: "owner-not-covered",
|
|
3388
|
+
message: "extract/mixed requires a covering owner"
|
|
3389
|
+
};
|
|
3390
|
+
if (!input.targetExists) return {
|
|
3391
|
+
ok: false,
|
|
3392
|
+
code: "target-missing",
|
|
3393
|
+
message: "covered owner target does not exist"
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
const selection = selectSections(input.body, input.requests);
|
|
3397
|
+
if (!selection.ok) return {
|
|
3398
|
+
ok: false,
|
|
3399
|
+
code: selection.code,
|
|
3400
|
+
message: selection.message
|
|
3401
|
+
};
|
|
3402
|
+
if (selection.sections.length === 0) return {
|
|
3403
|
+
ok: false,
|
|
3404
|
+
code: "part-empty",
|
|
3405
|
+
message: "at least one part is required"
|
|
3406
|
+
};
|
|
3407
|
+
const parts = selection.sections.map((section) => ({
|
|
3408
|
+
heading: section.heading,
|
|
3409
|
+
anchor: {
|
|
3410
|
+
heading: section.heading,
|
|
3411
|
+
...section.anchorStart === void 0 ? {} : { start: section.anchorStart },
|
|
3412
|
+
...section.anchorEnd === void 0 ? {} : { end: section.anchorEnd }
|
|
3413
|
+
},
|
|
3414
|
+
sourceText: section.text,
|
|
3415
|
+
movedText: section.text,
|
|
3416
|
+
bytes: Buffer.byteLength(section.text, "utf8"),
|
|
3417
|
+
sha256: sha256$1(section.text)
|
|
3418
|
+
}));
|
|
3419
|
+
const pointer = "This capability now lives in " + (input.owner.mode === "covered" ? input.owner.name ?? input.targetName : input.owner.neededOwner ?? input.targetName) + "; project-specific facts stay here.";
|
|
3420
|
+
const ordered = [...selection.sections].sort((a, b) => a.start - b.start);
|
|
3421
|
+
const first = ordered[0];
|
|
3422
|
+
if (first === void 0) return {
|
|
3423
|
+
ok: false,
|
|
3424
|
+
code: "part-empty",
|
|
3425
|
+
message: "at least one part is required"
|
|
3426
|
+
};
|
|
3427
|
+
const byStart = new Map(ordered.map((section) => [section.start, section]));
|
|
3428
|
+
const lines = input.body.split(LF);
|
|
3429
|
+
const rebuilt = [];
|
|
3430
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
3431
|
+
const section = byStart.get(index);
|
|
3432
|
+
if (section !== void 0) {
|
|
3433
|
+
if (index === first.start) rebuilt.push(pointer);
|
|
3434
|
+
index = section.end - 1;
|
|
3435
|
+
} else rebuilt.push(lines[index] ?? "");
|
|
3436
|
+
}
|
|
3437
|
+
const residual = rebuilt.join(LF);
|
|
3438
|
+
if (residual.trim().length === 0) return {
|
|
3439
|
+
ok: false,
|
|
3440
|
+
code: "parent-empty",
|
|
3441
|
+
message: "parent residual is empty"
|
|
3442
|
+
};
|
|
3443
|
+
if (residual.split(LF).filter((line) => line.trim() !== "" && line !== pointer && !line.trimStart().startsWith("#")).length === 0) return {
|
|
3444
|
+
ok: false,
|
|
3445
|
+
code: "parent-hollow-heading",
|
|
3446
|
+
message: "parent residual has no substantive content"
|
|
3447
|
+
};
|
|
3448
|
+
if (input.outcome === "owner-gap") return {
|
|
3449
|
+
ok: true,
|
|
3450
|
+
plan: {
|
|
3451
|
+
source: input.sourceName,
|
|
3452
|
+
target: {
|
|
3453
|
+
name: input.targetName,
|
|
3454
|
+
exists: input.targetExists
|
|
3455
|
+
},
|
|
3456
|
+
outcome: input.outcome,
|
|
3457
|
+
parts,
|
|
3458
|
+
residual,
|
|
3459
|
+
pointer,
|
|
3460
|
+
owner: input.owner,
|
|
3461
|
+
support: {
|
|
3462
|
+
exclusive: [],
|
|
3463
|
+
shared: [],
|
|
3464
|
+
external: [],
|
|
3465
|
+
unresolved: [],
|
|
3466
|
+
dangling: []
|
|
3467
|
+
},
|
|
3468
|
+
warnings: [],
|
|
3469
|
+
...input.anchors === void 0 ? {} : { anchors: input.anchors }
|
|
3470
|
+
}
|
|
3471
|
+
};
|
|
3472
|
+
const supportResult = computeSupport(parts, residual, input.sourceName, input.sourceFiles, input.targetFiles, input.external ?? []);
|
|
3473
|
+
if ("error" in supportResult) return {
|
|
3474
|
+
ok: false,
|
|
3475
|
+
code: supportResult.error.split(":")[0] ?? "support-invalid",
|
|
3476
|
+
message: supportResult.error
|
|
3477
|
+
};
|
|
3478
|
+
if (supportResult.support.dangling.length > 0) return {
|
|
3479
|
+
ok: false,
|
|
3480
|
+
code: "support-dangling",
|
|
3481
|
+
message: "support-dangling: " + supportResult.support.dangling.join(", ")
|
|
3482
|
+
};
|
|
3483
|
+
return {
|
|
3484
|
+
ok: true,
|
|
3485
|
+
plan: {
|
|
3486
|
+
source: input.sourceName,
|
|
3487
|
+
target: {
|
|
3488
|
+
name: input.targetName,
|
|
3489
|
+
exists: input.targetExists
|
|
3490
|
+
},
|
|
3491
|
+
outcome: input.outcome,
|
|
3492
|
+
parts,
|
|
3493
|
+
residual,
|
|
3494
|
+
pointer,
|
|
3495
|
+
owner: input.owner,
|
|
3496
|
+
support: supportResult.support,
|
|
3497
|
+
warnings: unique(supportResult.warnings),
|
|
3498
|
+
...input.anchors === void 0 ? {} : { anchors: input.anchors }
|
|
3499
|
+
}
|
|
3500
|
+
};
|
|
3501
|
+
}
|
|
3502
|
+
/**
|
|
3503
|
+
* Project one plan onto the current source/target SKILL.md bytes.
|
|
3504
|
+
*
|
|
3505
|
+
* This is the one place that composes frontmatter + residual + target additions;
|
|
3506
|
+
* preview and the write adapter both call it so they cannot advertise different
|
|
3507
|
+
* bytes. Pure: no IO.
|
|
3508
|
+
* @param plan - plan from planExtract.
|
|
3509
|
+
* @param sourceMd - current source SKILL.md.
|
|
3510
|
+
* @param targetMd - current target SKILL.md.
|
|
3511
|
+
* @returns both next documents, or the refusal that makes projection impossible.
|
|
3512
|
+
*/
|
|
3513
|
+
function projectExtractBodies(plan, sourceMd, targetMd) {
|
|
3514
|
+
const sourceBlock = frontmatterBlock(sourceMd);
|
|
3515
|
+
const targetBlock = frontmatterBlock(targetMd);
|
|
3516
|
+
if (!sourceBlock) return {
|
|
3517
|
+
ok: false,
|
|
3518
|
+
message: "source 没有有效 frontmatter"
|
|
3519
|
+
};
|
|
3520
|
+
if (!targetBlock) return {
|
|
3521
|
+
ok: false,
|
|
3522
|
+
message: "target 没有有效 frontmatter"
|
|
3523
|
+
};
|
|
3524
|
+
const sourceHeader = sourceBlock.lines.slice(0, sourceBlock.end + 1).join(LF);
|
|
3525
|
+
const targetNormalized = targetMd.replace(/\r\n/g, LF);
|
|
3526
|
+
const targetLines = targetNormalized.split(LF);
|
|
3527
|
+
for (const part of plan.parts) if (targetLines.some((line) => line.trim() === "## " + part.heading)) return {
|
|
3528
|
+
ok: false,
|
|
3529
|
+
message: "target 已有同名 heading「" + part.heading + "」。"
|
|
3530
|
+
};
|
|
3531
|
+
const additions = plan.parts.map((part) => part.movedText).join("\n\n");
|
|
3532
|
+
const targetRaw = targetNormalized.trimEnd() + "\n\n" + additions + LF;
|
|
3533
|
+
const targetAfter = targetBlock.nl === "\r\n" ? targetRaw.replace(/\n/g, "\r\n") : targetRaw;
|
|
3534
|
+
const sourceRaw = (sourceHeader + plan.residual).replace(/\r\n/g, LF);
|
|
3535
|
+
return {
|
|
3536
|
+
ok: true,
|
|
3537
|
+
projection: {
|
|
3538
|
+
sourceAfter: sourceBlock.nl === "\r\n" ? sourceRaw.replace(/\n/g, "\r\n") : sourceRaw,
|
|
3539
|
+
targetAfter
|
|
3540
|
+
}
|
|
3541
|
+
};
|
|
3542
|
+
}
|
|
3543
|
+
//#endregion
|
|
3544
|
+
//#region lib/types/owner-resolver.js
|
|
3545
|
+
/**
|
|
3546
|
+
* Project the current library listing into owner candidates.
|
|
3547
|
+
*
|
|
3548
|
+
* The source is excluded; a skill with an empty description is named in
|
|
3549
|
+
* excluded rather than silently dropped. Protected candidates remain visible
|
|
3550
|
+
* (so a caller can explain why it refused), but are marked for the writer's
|
|
3551
|
+
* protection gate. Headings reuse h2Sections; no second heading scanner.
|
|
3552
|
+
* @param source - skill being extracted from.
|
|
3553
|
+
* @param summaries - SkillLibrary.list({ withContent: true, withFrontmatter: true }).
|
|
3554
|
+
* @returns stable-name-ordered candidates plus explicit exclusions.
|
|
3555
|
+
*/
|
|
3556
|
+
function ownerCandidatesFromSummaries(source, summaries) {
|
|
3557
|
+
const normalized = source.trim();
|
|
3558
|
+
const candidates = [];
|
|
3559
|
+
const excluded = [];
|
|
3560
|
+
for (const summary of summaries) {
|
|
3561
|
+
if (summary.name === normalized) {
|
|
3562
|
+
excluded.push({
|
|
3563
|
+
name: summary.name,
|
|
3564
|
+
reason: "source"
|
|
3565
|
+
});
|
|
3566
|
+
continue;
|
|
3567
|
+
}
|
|
3568
|
+
if (summary.description.trim() === "") {
|
|
3569
|
+
excluded.push({
|
|
3570
|
+
name: summary.name,
|
|
3571
|
+
reason: "unreadable"
|
|
3572
|
+
});
|
|
3573
|
+
continue;
|
|
3574
|
+
}
|
|
3575
|
+
candidates.push({
|
|
3576
|
+
name: summary.name,
|
|
3577
|
+
description: summary.description,
|
|
3578
|
+
...summary.whenToUse === void 0 ? {} : { whenToUse: summary.whenToUse },
|
|
3579
|
+
headings: h2Sections(summary.content ?? "").map((section) => section.title),
|
|
3580
|
+
protectedBy: summary.protectedBy,
|
|
3581
|
+
protectionUnknown: summary.protectionUnknown,
|
|
3582
|
+
managed: summary.managed
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
candidates.sort((left, right) => left.name.localeCompare(right.name));
|
|
3586
|
+
excluded.sort((left, right) => left.name.localeCompare(right.name));
|
|
3587
|
+
return {
|
|
3588
|
+
source: normalized,
|
|
3589
|
+
candidates,
|
|
3590
|
+
excluded
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
/**
|
|
3594
|
+
* Find one named candidate in a lookup.
|
|
3595
|
+
* @param lookup - projection from ownerCandidatesFromSummaries.
|
|
3596
|
+
* @param name - exact skill name.
|
|
3597
|
+
* @returns the candidate, or undefined when absent.
|
|
3598
|
+
*/
|
|
3599
|
+
function findOwnerCandidate(lookup, name) {
|
|
3600
|
+
return lookup.candidates.find((candidate) => candidate.name === name);
|
|
3601
|
+
}
|
|
3602
|
+
//#endregion
|
|
3603
|
+
//#region lib/types/proposal-view.js
|
|
3604
|
+
/**
|
|
3605
|
+
* Project a pure ExtractPlan into the proposal record every read-only surface consumes.
|
|
3606
|
+
*
|
|
3607
|
+
* This is a projection, not a second evaluator: it keeps the plan's owner decision,
|
|
3608
|
+
* support closure and warnings. It never writes and never invents coverage.
|
|
3609
|
+
* @param plan - plan produced by planExtract.
|
|
3610
|
+
* @param provenance - run id and timestamp for the proposing pass.
|
|
3611
|
+
* @returns a proposal with status proposed.
|
|
3612
|
+
*/
|
|
3613
|
+
function proposalFromPlan(plan, provenance) {
|
|
3614
|
+
return {
|
|
3615
|
+
version: 1,
|
|
3616
|
+
id: "extract-" + provenance.runId + "-" + plan.source,
|
|
3617
|
+
source: plan.source,
|
|
3618
|
+
outcome: plan.outcome,
|
|
3619
|
+
parts: plan.parts.map((part) => ({
|
|
3620
|
+
heading: part.heading,
|
|
3621
|
+
anchor: part.anchor,
|
|
3622
|
+
reason: part.sourceText.slice(0, 160)
|
|
3623
|
+
})),
|
|
3624
|
+
owner: plan.owner,
|
|
3625
|
+
support: plan.support,
|
|
3626
|
+
warnings: plan.warnings,
|
|
3627
|
+
provenance: {
|
|
3628
|
+
promptVersion: "v1",
|
|
3629
|
+
runId: provenance.runId,
|
|
3630
|
+
at: provenance.at ?? Date.now()
|
|
3631
|
+
},
|
|
3632
|
+
status: "proposed"
|
|
3633
|
+
};
|
|
3634
|
+
}
|
|
3635
|
+
//#endregion
|
|
3636
|
+
//#region lib/types/state-store.js
|
|
3637
|
+
/**
|
|
3638
|
+
* Evolution home path helpers: the DSH root and `$DSH_HOME/evolution` for
|
|
3639
|
+
* plugin-owned sidecar state (reports, activity store, feedback file,
|
|
3640
|
+
* state-domain data).
|
|
3641
|
+
*
|
|
3642
|
+
* C-10: PATH HELPERS ONLY — despite the file name there is no store
|
|
3643
|
+
* here. Durable evolution state lives in the state stack (evolution-state over
|
|
3644
|
+
* evolution-state-json / -domain); skills and memories live in skill-store.ts
|
|
3645
|
+
* / memory-store.ts. The file name is kept deliberately: renaming it would
|
|
3646
|
+
* touch every family import for zero behavior change, and the audit records
|
|
3647
|
+
* the mismatch as known naming debt.
|
|
3648
|
+
*/
|
|
3649
|
+
/**
|
|
3650
|
+
* DSH home root: `$DSH_HOME` or `~/.dsh`. Single source of the empty-string
|
|
3651
|
+
* fallback — an EMPTY or WHITESPACE-ONLY DSH_HOME resolves to the default
|
|
3652
|
+
* home, never to a CWD-relative path (0.3.19 W1.3, 0.3.22 F-207);
|
|
3653
|
+
* V8-06 (0.3.47) extends the guard to whitespace.
|
|
3654
|
+
* C-11 correction (S2.2, PLAN 2026-09-16): upstream `resolveDshHome`
|
|
3655
|
+
* (util/home-paths) uses `trim()` ONLY as the ADOPTION test and then uses the
|
|
3656
|
+
* RAW env value — the earlier "same trimmed source" form returned the trimmed
|
|
3657
|
+
* text, so `DSH_HOME=" /x "` produced `/x` where upstream produced the literal
|
|
3658
|
+
* padded path. The value semantics now match upstream line for line:
|
|
3659
|
+
* `const selected = configured ?? (fromEnv !== undefined &&
|
|
3660
|
+
* fromEnv.trim().length > 0 ? fromEnv : defaultDshHome())`.
|
|
3661
|
+
* OPT-27 (2026-09, plan D5 — accepted): the v10-era "no `~` expansion, no
|
|
3662
|
+
* resolve" divergence from upstream `resolveDshHome` is RETIRED. It became
|
|
3663
|
+
* load-bearing when the skill-catalog shadow made "same tree as the upstream
|
|
3664
|
+
* `USER_DSH_RANK` provider" a hard contract: upstream watches the EXPANDED
|
|
3665
|
+
* absolute `<home>/skills` while this value fed a literal `~/x` (a directory
|
|
3666
|
+
* named `~` under the host CWD) or a CWD-relative path — split-brain skill
|
|
3667
|
+
* trees, preset installs the platform never reads, doctor probes of a
|
|
3668
|
+
* directory nothing serves.
|
|
3669
|
+
* S2.2 (PLAN 2026-09-16), final correction: the result is ALWAYS
|
|
3670
|
+
* `resolve(expandHomePath(selected))` — the earlier form returned an
|
|
3671
|
+
* already-absolute value VERBATIM, so a trailing slash or `..` segment
|
|
3672
|
+
* landed unnormalized while upstream normalizes every value. Relative
|
|
3673
|
+
* `DSH_HOME` values change landing spot; a CLEAN absolute home stays
|
|
3674
|
+
* byte-identical (resolve is a no-op on it).
|
|
3675
|
+
*/
|
|
3676
|
+
function evolutionRoot(env = process.env) {
|
|
3677
|
+
const fromEnv = env.DSH_HOME;
|
|
3678
|
+
const selected = fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(homedir(), ".dsh");
|
|
3679
|
+
return resolve(selected === "~" ? homedir() : selected.startsWith("~/") || selected.startsWith("~\\") ? join(homedir(), selected.slice(2)) : selected);
|
|
3680
|
+
}
|
|
3681
|
+
/** Evolution home path helper: `$DSH_HOME/evolution` for plugin-owned sidecar
|
|
3682
|
+
* state (reports, activity store, feedback file, state-domain data). */
|
|
3683
|
+
function evolutionHome(env = process.env) {
|
|
3684
|
+
return join(evolutionRoot(env), "evolution");
|
|
3685
|
+
}
|
|
3686
|
+
//#endregion
|
|
3687
|
+
//#region lib/types/proposal-report.js
|
|
3688
|
+
/** Directory for the one proposal lane; reports are retention-swept with the other run reports. */
|
|
3689
|
+
function proposalReportDir() {
|
|
3690
|
+
return join(evolutionHome(), "reports");
|
|
3691
|
+
}
|
|
3692
|
+
/** Stable report filename for one proposal; the id may carry path separators. */
|
|
3693
|
+
function proposalReportName(proposal) {
|
|
3694
|
+
return "extract-proposal-" + encodeURIComponent(proposal.id) + ".json";
|
|
3695
|
+
}
|
|
3696
|
+
/**
|
|
3697
|
+
* Persist one proposal as a report. Best-effort: a failed write never blocks the extract.
|
|
3698
|
+
* @param io - evolution IO seam.
|
|
3699
|
+
* @param proposal - proposal to persist.
|
|
3700
|
+
* @param dir - reports directory (tests pass a temp dir).
|
|
3701
|
+
* @returns the written path.
|
|
3702
|
+
*/
|
|
3703
|
+
async function writeProposalReport(io, proposal, dir = proposalReportDir()) {
|
|
3704
|
+
const path = join(dir, proposalReportName(proposal));
|
|
3705
|
+
await io.writeText(path, JSON.stringify(proposal));
|
|
3706
|
+
return path;
|
|
3707
|
+
}
|
|
3708
|
+
function isProposal(value) {
|
|
3709
|
+
if (value === null || typeof value !== "object") return false;
|
|
3710
|
+
const candidate = value;
|
|
3711
|
+
return candidate.version === 1 && typeof candidate.id === "string" && typeof candidate.source === "string" && (candidate.outcome === "keep" || candidate.outcome === "extract" || candidate.outcome === "mixed" || candidate.outcome === "owner-gap");
|
|
3712
|
+
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Read the newest proposals. Missing/unreadable reports are skipped, never guessed.
|
|
3715
|
+
* @param io - evolution IO seam.
|
|
3716
|
+
* @param dir - reports directory.
|
|
3717
|
+
* @param limit - newest rows to return.
|
|
3718
|
+
* @returns valid proposals, newest filename first.
|
|
3719
|
+
*/
|
|
3720
|
+
async function readProposalReports(io, dir = proposalReportDir(), limit = 10) {
|
|
3721
|
+
const listed = await probeList(io, dir);
|
|
3722
|
+
if (isUnknown(listed) || !isPresent(listed)) return [];
|
|
3723
|
+
const names = listed.value.filter((name) => name.startsWith("extract-proposal-") && name.endsWith(".json"));
|
|
3724
|
+
names.sort((a, b) => b.localeCompare(a));
|
|
3725
|
+
const proposals = [];
|
|
3726
|
+
for (const name of names.slice(0, limit)) {
|
|
3727
|
+
const raw = await probeText(io, join(dir, name));
|
|
3728
|
+
if (!isPresent(raw)) continue;
|
|
3729
|
+
let parsed;
|
|
3730
|
+
try {
|
|
3731
|
+
parsed = JSON.parse(raw.value);
|
|
3732
|
+
} catch {
|
|
3733
|
+
continue;
|
|
3734
|
+
}
|
|
3735
|
+
if (isProposal(parsed)) proposals.push(parsed);
|
|
3736
|
+
}
|
|
3737
|
+
return proposals;
|
|
3738
|
+
}
|
|
3739
|
+
//#endregion
|
|
3740
|
+
//#region lib/types/external-support.js
|
|
3741
|
+
const MAX_FILE_BYTES = 1024 * 1024;
|
|
3742
|
+
const MAX_TOTAL_BYTES = 4 * 1024 * 1024;
|
|
3743
|
+
function targetPathFor(ref) {
|
|
3744
|
+
let value = ref.replace(/^\/+/, "");
|
|
3745
|
+
while (value.startsWith("../")) value = value.slice(3);
|
|
3746
|
+
const support = value.match(/(?:^|\/)(references|templates|scripts|assets)\/(.+)$/);
|
|
3747
|
+
if (support !== null) return support[1] + "/" + support[2];
|
|
3748
|
+
return "references/" + basename(value);
|
|
3749
|
+
}
|
|
3750
|
+
function annotate(content, originalPath) {
|
|
3751
|
+
const note = "external source: " + originalPath;
|
|
3752
|
+
const extension = originalPath.slice(originalPath.lastIndexOf(".")).toLowerCase();
|
|
3753
|
+
if (extension === ".md") return "<!-- " + note + " -->\n\n" + content;
|
|
3754
|
+
const prefix = extension === ".py" || extension === ".sh" || extension === ".rb" ? "# " : "// ";
|
|
3755
|
+
if (extension === ".js" || extension === ".mjs" || extension === ".cjs" || extension === ".ts" || extension === ".tsx" || extension === ".py" || extension === ".sh" || extension === ".rb") {
|
|
3756
|
+
if (content.startsWith("#!")) {
|
|
3757
|
+
const end = content.indexOf(String.fromCharCode(10));
|
|
3758
|
+
if (end >= 0) return content.slice(0, end + 1) + prefix + note + String.fromCharCode(10) + content.slice(end + 1);
|
|
3759
|
+
}
|
|
3760
|
+
return prefix + note + String.fromCharCode(10) + content;
|
|
3761
|
+
}
|
|
3762
|
+
return content;
|
|
3763
|
+
}
|
|
3764
|
+
function inside(root, candidate) {
|
|
3765
|
+
const rel = relative(root, candidate);
|
|
3766
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
3767
|
+
}
|
|
3768
|
+
/**
|
|
3769
|
+
* Read and annotate external support files under one explicit root. The caller owns
|
|
3770
|
+
* the root choice; this module only enforces containment, target-path safety, size
|
|
3771
|
+
* limits and determinism.
|
|
3772
|
+
* @param body - selected source markdown body.
|
|
3773
|
+
* @param sourceDir - source skill directory.
|
|
3774
|
+
* @param allowedRoot - canonical root external files must stay under.
|
|
3775
|
+
* @param targetFiles - existing target support paths.
|
|
3776
|
+
* @returns resolved external entries, or a refusal.
|
|
3777
|
+
*/
|
|
3778
|
+
async function readExternalSupport(body, sourceDir, allowedRoot, targetFiles) {
|
|
3779
|
+
const refs = externalRefs(body);
|
|
3780
|
+
if (refs.length === 0) return {
|
|
3781
|
+
ok: true,
|
|
3782
|
+
files: []
|
|
3783
|
+
};
|
|
3784
|
+
let root;
|
|
3785
|
+
try {
|
|
3786
|
+
root = await realpath(allowedRoot);
|
|
3787
|
+
} catch {
|
|
3788
|
+
return {
|
|
3789
|
+
ok: false,
|
|
3790
|
+
message: "external support 根目录不可读:" + allowedRoot
|
|
3791
|
+
};
|
|
3792
|
+
}
|
|
3793
|
+
const files = [];
|
|
3794
|
+
const targets = new Set(targetFiles);
|
|
3795
|
+
let total = 0;
|
|
3796
|
+
for (const ref of refs) {
|
|
3797
|
+
const candidate = isAbsolute(ref) ? resolve(ref) : resolve(sourceDir, ref);
|
|
3798
|
+
let canonical;
|
|
3799
|
+
try {
|
|
3800
|
+
canonical = await realpath(candidate);
|
|
3801
|
+
} catch {
|
|
3802
|
+
return {
|
|
3803
|
+
ok: false,
|
|
3804
|
+
message: "external support 文件不存在:" + ref
|
|
3805
|
+
};
|
|
3806
|
+
}
|
|
3807
|
+
if (!inside(root, canonical)) return {
|
|
3808
|
+
ok: false,
|
|
3809
|
+
message: "external support 越界:" + ref
|
|
3810
|
+
};
|
|
3811
|
+
const info = await stat(canonical);
|
|
3812
|
+
if (!info.isFile()) return {
|
|
3813
|
+
ok: false,
|
|
3814
|
+
message: "external support 不是普通文件:" + ref
|
|
3815
|
+
};
|
|
3816
|
+
if (info.size > MAX_FILE_BYTES) return {
|
|
3817
|
+
ok: false,
|
|
3818
|
+
message: "external support 超过单文件上限:" + ref
|
|
3819
|
+
};
|
|
3820
|
+
total += info.size;
|
|
3821
|
+
if (total > MAX_TOTAL_BYTES) return {
|
|
3822
|
+
ok: false,
|
|
3823
|
+
message: "external support 超过总上限:" + ref
|
|
3824
|
+
};
|
|
3825
|
+
const targetPath = targetPathFor(ref);
|
|
3826
|
+
if (targets.has(targetPath)) return {
|
|
3827
|
+
ok: false,
|
|
3828
|
+
message: "external support 目标冲突:" + targetPath
|
|
3829
|
+
};
|
|
3830
|
+
targets.add(targetPath);
|
|
3831
|
+
const annotated = annotate(await readFile(canonical, "utf8"), canonical);
|
|
3832
|
+
files.push({
|
|
3833
|
+
ref,
|
|
3834
|
+
originalPath: canonical,
|
|
3835
|
+
targetPath,
|
|
3836
|
+
content: annotated,
|
|
3837
|
+
sha256: createHash("sha256").update(annotated).digest("hex"),
|
|
3838
|
+
provenance: "external:" + canonical
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
return {
|
|
3842
|
+
ok: true,
|
|
3843
|
+
files
|
|
3844
|
+
};
|
|
3845
|
+
}
|
|
3846
|
+
//#endregion
|
|
3847
|
+
//#region lib/types/extract-prompt.js
|
|
3848
|
+
/**
|
|
3849
|
+
* The frozen semantic prompt for skill extraction. One constant, consumed by
|
|
3850
|
+
* explicit extract agents and tests; it is deliberately not part of the review
|
|
3851
|
+
* prompt bundle, and no review path injects it.
|
|
3852
|
+
*/
|
|
3853
|
+
const EXTRACT_PROMPT_VERSION = "v1";
|
|
3854
|
+
const EXTRACT_PROMPT_V1 = `Inspect the source skill, its linked support files, and the candidate owners
|
|
3855
|
+
in the library. A part of the skill (one or more sections) is a separate
|
|
3856
|
+
capability when it has its own trigger and outcome, can be used without the
|
|
3857
|
+
parent context, and leaves the parent coherent. If it is separate and a
|
|
3858
|
+
class-level owner covers it, extract that part. If no owner covers it, report
|
|
3859
|
+
the owner gap and name the class-level owner that would be needed. If it mixes
|
|
3860
|
+
a reusable capability with parent-specific facts, return mixed and name only
|
|
3861
|
+
the reusable part. Otherwise keep it. Keep, extract, mixed, and owner-gap are
|
|
3862
|
+
all valid outcomes; no split is required.`;
|
|
3863
|
+
//#endregion
|
|
3050
3864
|
//#region lib/types/cost.js
|
|
3051
3865
|
/**
|
|
3052
3866
|
* Context-cost accounting for skill bodies (design §5.2).
|
|
@@ -4492,57 +5306,6 @@ function buildLearnPrompt(userRequest) {
|
|
|
4492
5306
|
].join("\n");
|
|
4493
5307
|
}
|
|
4494
5308
|
//#endregion
|
|
4495
|
-
//#region lib/types/state-store.js
|
|
4496
|
-
/**
|
|
4497
|
-
* Evolution home path helpers: the DSH root and `$DSH_HOME/evolution` for
|
|
4498
|
-
* plugin-owned sidecar state (reports, activity store, feedback file,
|
|
4499
|
-
* state-domain data).
|
|
4500
|
-
*
|
|
4501
|
-
* C-10: PATH HELPERS ONLY — despite the file name there is no store
|
|
4502
|
-
* here. Durable evolution state lives in the state stack (evolution-state over
|
|
4503
|
-
* evolution-state-json / -domain); skills and memories live in skill-store.ts
|
|
4504
|
-
* / memory-store.ts. The file name is kept deliberately: renaming it would
|
|
4505
|
-
* touch every family import for zero behavior change, and the audit records
|
|
4506
|
-
* the mismatch as known naming debt.
|
|
4507
|
-
*/
|
|
4508
|
-
/**
|
|
4509
|
-
* DSH home root: `$DSH_HOME` or `~/.dsh`. Single source of the empty-string
|
|
4510
|
-
* fallback — an EMPTY or WHITESPACE-ONLY DSH_HOME resolves to the default
|
|
4511
|
-
* home, never to a CWD-relative path (0.3.19 W1.3, 0.3.22 F-207);
|
|
4512
|
-
* V8-06 (0.3.47) extends the guard to whitespace.
|
|
4513
|
-
* C-11 correction (S2.2, PLAN 2026-09-16): upstream `resolveDshHome`
|
|
4514
|
-
* (util/home-paths) uses `trim()` ONLY as the ADOPTION test and then uses the
|
|
4515
|
-
* RAW env value — the earlier "same trimmed source" form returned the trimmed
|
|
4516
|
-
* text, so `DSH_HOME=" /x "` produced `/x` where upstream produced the literal
|
|
4517
|
-
* padded path. The value semantics now match upstream line for line:
|
|
4518
|
-
* `const selected = configured ?? (fromEnv !== undefined &&
|
|
4519
|
-
* fromEnv.trim().length > 0 ? fromEnv : defaultDshHome())`.
|
|
4520
|
-
* OPT-27 (2026-09, plan D5 — accepted): the v10-era "no `~` expansion, no
|
|
4521
|
-
* resolve" divergence from upstream `resolveDshHome` is RETIRED. It became
|
|
4522
|
-
* load-bearing when the skill-catalog shadow made "same tree as the upstream
|
|
4523
|
-
* `USER_DSH_RANK` provider" a hard contract: upstream watches the EXPANDED
|
|
4524
|
-
* absolute `<home>/skills` while this value fed a literal `~/x` (a directory
|
|
4525
|
-
* named `~` under the host CWD) or a CWD-relative path — split-brain skill
|
|
4526
|
-
* trees, preset installs the platform never reads, doctor probes of a
|
|
4527
|
-
* directory nothing serves.
|
|
4528
|
-
* S2.2 (PLAN 2026-09-16), final correction: the result is ALWAYS
|
|
4529
|
-
* `resolve(expandHomePath(selected))` — the earlier form returned an
|
|
4530
|
-
* already-absolute value VERBATIM, so a trailing slash or `..` segment
|
|
4531
|
-
* landed unnormalized while upstream normalizes every value. Relative
|
|
4532
|
-
* `DSH_HOME` values change landing spot; a CLEAN absolute home stays
|
|
4533
|
-
* byte-identical (resolve is a no-op on it).
|
|
4534
|
-
*/
|
|
4535
|
-
function evolutionRoot(env = process.env) {
|
|
4536
|
-
const fromEnv = env.DSH_HOME;
|
|
4537
|
-
const selected = fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(homedir(), ".dsh");
|
|
4538
|
-
return resolve(selected === "~" ? homedir() : selected.startsWith("~/") || selected.startsWith("~\\") ? join(homedir(), selected.slice(2)) : selected);
|
|
4539
|
-
}
|
|
4540
|
-
/** Evolution home path helper: `$DSH_HOME/evolution` for plugin-owned sidecar
|
|
4541
|
-
* state (reports, activity store, feedback file, state-domain data). */
|
|
4542
|
-
function evolutionHome(env = process.env) {
|
|
4543
|
-
return join(evolutionRoot(env), "evolution");
|
|
4544
|
-
}
|
|
4545
|
-
//#endregion
|
|
4546
5309
|
//#region lib/types/serial.js
|
|
4547
5310
|
/**
|
|
4548
5311
|
* A process-local serial task queue: each task starts only after the previous
|
|
@@ -7053,6 +7816,7 @@ const CONTENT_ACTIONS = [
|
|
|
7053
7816
|
"patch",
|
|
7054
7817
|
"consolidate",
|
|
7055
7818
|
"restructure",
|
|
7819
|
+
"extract",
|
|
7056
7820
|
"restore",
|
|
7057
7821
|
"merge"
|
|
7058
7822
|
];
|
|
@@ -7460,6 +8224,7 @@ function versionActionKind(action) {
|
|
|
7460
8224
|
case "archive": return "archive";
|
|
7461
8225
|
case "consolidate": return "consolidate";
|
|
7462
8226
|
case "restructure": return "restructure";
|
|
8227
|
+
case "extract": return "extract";
|
|
7463
8228
|
case "merge": return "merge";
|
|
7464
8229
|
case "write_file": return "support-write";
|
|
7465
8230
|
case "remove_file": return "support-remove";
|
|
@@ -9524,7 +10289,8 @@ const READ_REQUIRED_ACTIONS = Object.freeze([
|
|
|
9524
10289
|
"delete",
|
|
9525
10290
|
"write_file",
|
|
9526
10291
|
"remove_file",
|
|
9527
|
-
"restructure"
|
|
10292
|
+
"restructure",
|
|
10293
|
+
"extract"
|
|
9528
10294
|
]);
|
|
9529
10295
|
/**
|
|
9530
10296
|
* Does this one op write a skill the caller never read?
|
|
@@ -9735,95 +10501,6 @@ function skillRowCells(skill) {
|
|
|
9735
10501
|
};
|
|
9736
10502
|
}
|
|
9737
10503
|
//#endregion
|
|
9738
|
-
//#region lib/types/document-body.js
|
|
9739
|
-
/**
|
|
9740
|
-
* What the panel shows when it renders one version as a document.
|
|
9741
|
-
*
|
|
9742
|
-
* Two decisions, both made here so the preview and the rendered diff cannot drift apart:
|
|
9743
|
-
*
|
|
9744
|
-
* - FRONTMATTER IS NOT DOCUMENT TEXT. A SKILL.md opens with a YAML block; the renderer read its
|
|
9745
|
-
* closing `---` as a setext underline, so the metadata line became the document's biggest heading
|
|
9746
|
-
* and the real title sat below it (measured on the installed panel 2026-09-28). The block is
|
|
9747
|
-
* stripped for DISPLAY only: the raw view and the body route still carry every byte, and the
|
|
9748
|
-
* panel already shows the name and description on the row and in the left column.
|
|
9749
|
-
* - STRIPPING FAILS OPEN. Only a block that the family's own reader accepts is removed. A document
|
|
9750
|
-
* whose text merely starts with a thematic break, a block with no closing line, or a frontmatter
|
|
9751
|
-
* whose body is empty is returned UNCHANGED — showing a document's first line is always better
|
|
9752
|
-
* than showing nothing.
|
|
9753
|
-
*
|
|
9754
|
-
* `readingStats` exists for the other half of the reader's honesty: a preview that stops early has
|
|
9755
|
-
* to be able to say HOW much it is not showing.
|
|
9756
|
-
* @module @lmzhen/dsh-evolution-core/document-body
|
|
9757
|
-
*/
|
|
9758
|
-
/**
|
|
9759
|
-
* The document a reader sees: the body without its leading frontmatter block.
|
|
9760
|
-
* @param content - the stored bytes of one version.
|
|
9761
|
-
* @returns the text to render; unchanged when there is no frontmatter the reader accepts.
|
|
9762
|
-
*/
|
|
9763
|
-
function displayBodyOf(content) {
|
|
9764
|
-
const block = frontmatterBlock(content);
|
|
9765
|
-
if (block === null) return content;
|
|
9766
|
-
if (parseFrontmatter(content) === null) return content;
|
|
9767
|
-
return block.lines.slice(block.end + 1).join("\n").replace(/^\s*\n/, "");
|
|
9768
|
-
}
|
|
9769
|
-
/**
|
|
9770
|
-
* How much of a version the panel is showing.
|
|
9771
|
-
* @param full - the whole stored text.
|
|
9772
|
-
* @param shown - the part the route handed over.
|
|
9773
|
-
* @returns the counts, so the panel can say what it is not showing.
|
|
9774
|
-
*/
|
|
9775
|
-
function readingStats(full, shown) {
|
|
9776
|
-
return {
|
|
9777
|
-
totalChars: full.length,
|
|
9778
|
-
shownChars: shown.length,
|
|
9779
|
-
truncated: shown.length < full.length
|
|
9780
|
-
};
|
|
9781
|
-
}
|
|
9782
|
-
/**
|
|
9783
|
-
* The H2 sections of a body, in document order — the ONE scan (G3).
|
|
9784
|
-
*
|
|
9785
|
-
* Both the restructure candidate list and the store anchor validation call this function, so a
|
|
9786
|
-
* candidate can only name a heading that exists EXACTLY ONCE and has content: the two rejections a
|
|
9787
|
-
* hand-written anchor used to hit ("no such heading", "ambiguous anchor") stop being reachable.
|
|
9788
|
-
* H2 is the section boundary (a section spans to the next H2 or EOF), matching the store.
|
|
9789
|
-
* @param body - the markdown after the frontmatter block.
|
|
9790
|
-
* @returns one row per H2 heading, with `count` filled in a second pass.
|
|
9791
|
-
*/
|
|
9792
|
-
function h2Sections(body) {
|
|
9793
|
-
const lines = body.split(String.fromCharCode(10));
|
|
9794
|
-
const rows = [];
|
|
9795
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
9796
|
-
const match = /^#{2}\s+(.+?)\s*$/.exec(lines[i] ?? "");
|
|
9797
|
-
if (match === null) continue;
|
|
9798
|
-
const title = (match[1] ?? "").trim();
|
|
9799
|
-
let end = lines.length;
|
|
9800
|
-
for (let j = i + 1; j < lines.length; j += 1) if (/^#{2}\s/.test(lines[j] ?? "")) {
|
|
9801
|
-
end = j;
|
|
9802
|
-
break;
|
|
9803
|
-
}
|
|
9804
|
-
rows.push({
|
|
9805
|
-
title,
|
|
9806
|
-
start: i,
|
|
9807
|
-
end,
|
|
9808
|
-
count: 0
|
|
9809
|
-
});
|
|
9810
|
-
}
|
|
9811
|
-
for (const row of rows) row.count = rows.filter((other) => other.title === row.title).length;
|
|
9812
|
-
return rows;
|
|
9813
|
-
}
|
|
9814
|
-
/**
|
|
9815
|
-
* The sections a restructure proposal may name: heading unique, section non-empty. Derived from
|
|
9816
|
-
* `h2Sections`, i.e. from the SAME scan the store validates against.
|
|
9817
|
-
* @param body - the markdown after the frontmatter block.
|
|
9818
|
-
* @returns move-ready pairs in document order; empty when nothing qualifies.
|
|
9819
|
-
*/
|
|
9820
|
-
function restructureCandidates(body) {
|
|
9821
|
-
return h2Sections(body).filter((section) => section.count === 1 && section.end > section.start + 1).map((section) => ({
|
|
9822
|
-
heading: section.title,
|
|
9823
|
-
toFile: "references/" + section.title.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-").replace(/^-+|-+$/g, "") + ".md"
|
|
9824
|
-
}));
|
|
9825
|
-
}
|
|
9826
|
-
//#endregion
|
|
9827
10504
|
//#region lib/types/drift-signals.js
|
|
9828
10505
|
/**
|
|
9829
10506
|
* Library-level drift signals for the maintenance subagent (design 011).
|
|
@@ -10626,48 +11303,20 @@ function validateSupportPath(filePath) {
|
|
|
10626
11303
|
}
|
|
10627
11304
|
return null;
|
|
10628
11305
|
}
|
|
10629
|
-
/**
|
|
10630
|
-
* Support-directory references in a markdown body (009 kernel): `references/…`,
|
|
10631
|
-
* `templates/…`, `scripts/…`, `assets/…` relative links — any extension and
|
|
10632
|
-
* nested paths (v7 audit P3-1: `.md`-only matching missed `scripts/run.sh` and
|
|
10633
|
-
* `references/sub/x.md`, both of which dangle just like a `.md` link once the
|
|
10634
|
-
* source package is archived). Pure — who checks them and what the verdict is
|
|
10635
|
-
* belongs to the caller's context (a moved/appended body whose references
|
|
10636
|
-
* travel with an archived source is a dangling link; a restructure pointer is
|
|
10637
|
-
* a fresh link to a file written in the same plan). `..` traversal is a
|
|
10638
|
-
* different defect class (path validation owns it) and is not a support link.
|
|
10639
|
-
*/
|
|
10640
|
-
function supportRefs(content) {
|
|
10641
|
-
const refs = [];
|
|
10642
|
-
for (const match of content.matchAll(/\b(?:references|templates|scripts|assets)\/[A-Za-z0-9._/-]+/g)) {
|
|
10643
|
-
const ref = match[0];
|
|
10644
|
-
if (ref.includes("..")) continue;
|
|
10645
|
-
refs.push(ref);
|
|
10646
|
-
}
|
|
10647
|
-
return refs;
|
|
10648
|
-
}
|
|
10649
11306
|
function planRestructureSections(body, moves) {
|
|
11307
|
+
const selection = selectSections(body, moves.map((move) => ({ heading: move.heading })));
|
|
11308
|
+
if (!selection.ok) return { error: selection.message };
|
|
10650
11309
|
const lines = body.split("\n");
|
|
10651
|
-
const
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
if (end === start + 1) return { error: `heading "## ${wanted}" has an empty section` };
|
|
10662
|
-
if (spans.some((span) => start === span.start)) return { error: `heading "## ${wanted}" is moved twice` };
|
|
10663
|
-
spans.push({
|
|
10664
|
-
start,
|
|
10665
|
-
end,
|
|
10666
|
-
rel: move.toFile,
|
|
10667
|
-
heading: wanted,
|
|
10668
|
-
text: lines.slice(start, end).join("\n")
|
|
10669
|
-
});
|
|
10670
|
-
}
|
|
11310
|
+
const spans = selection.sections.map((section) => {
|
|
11311
|
+
const move = moves[section.requestIndex];
|
|
11312
|
+
return {
|
|
11313
|
+
start: section.start,
|
|
11314
|
+
end: section.end,
|
|
11315
|
+
rel: move?.toFile ?? "",
|
|
11316
|
+
heading: section.heading,
|
|
11317
|
+
text: section.text
|
|
11318
|
+
};
|
|
11319
|
+
});
|
|
10671
11320
|
const byStart = new Map(spans.map((span) => [span.start, span]));
|
|
10672
11321
|
const rebuilt = [];
|
|
10673
11322
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -12762,48 +13411,245 @@ var SkillLibrary = class {
|
|
|
12762
13411
|
* restructure (and future reference-mode consolidations) never implement
|
|
12763
13412
|
* two-phase commit themselves.
|
|
12764
13413
|
*/
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
13414
|
+
/**
|
|
13415
|
+
* Apply one G1 ExtractPlan as one cross-skill unit. This is a thin adapter:
|
|
13416
|
+
* selection, residual, pointer and support closure were already decided by
|
|
13417
|
+
* planExtract; this method only projects them into ordered kernel operations.
|
|
13418
|
+
* @param plan - pure plan from extract-plan.ts.
|
|
13419
|
+
* @param origin - write face recorded in the audit ledger.
|
|
13420
|
+
* @returns the kernel result; a refusal writes nothing.
|
|
13421
|
+
*/
|
|
13422
|
+
async extract(plan, origin = this.authority.origin) {
|
|
13423
|
+
if (plan.outcome === "owner-gap" || plan.owner.mode !== "covered") return {
|
|
12769
13424
|
ok: false,
|
|
12770
|
-
message:
|
|
13425
|
+
message: "提取被拒绝:owner-gap 不是写操作。"
|
|
12771
13426
|
};
|
|
12772
|
-
|
|
12773
|
-
if (!await this.io.readText(join(dir, "SKILL.md"))) return {
|
|
13427
|
+
if (plan.support.unresolved.length > 0 || plan.support.dangling.length > 0) return {
|
|
12774
13428
|
ok: false,
|
|
12775
|
-
message:
|
|
13429
|
+
message: "提取被拒绝:support closure 存在 unresolved/dangling。"
|
|
12776
13430
|
};
|
|
12777
|
-
|
|
12778
|
-
if (protection) return {
|
|
13431
|
+
if (plan.parts.length === 0) return {
|
|
12779
13432
|
ok: false,
|
|
12780
|
-
message:
|
|
13433
|
+
message: "提取被拒绝:没有 part。"
|
|
13434
|
+
};
|
|
13435
|
+
const source = plan.source.trim();
|
|
13436
|
+
const target = plan.target.name.trim();
|
|
13437
|
+
if (source === target) return {
|
|
13438
|
+
ok: false,
|
|
13439
|
+
message: "提取被拒绝:source 与 target 相同。"
|
|
13440
|
+
};
|
|
13441
|
+
if (plan.target.exists !== true) return {
|
|
13442
|
+
ok: false,
|
|
13443
|
+
message: "提取被拒绝:target 不存在;owner-gap 不自动 create。"
|
|
13444
|
+
};
|
|
13445
|
+
const sourceDir = this.dirOf(source);
|
|
13446
|
+
const targetDir = this.dirOf(target);
|
|
13447
|
+
const sourceMd = await this.io.readText(join(sourceDir, "SKILL.md"));
|
|
13448
|
+
const targetMd = await this.io.readText(join(targetDir, "SKILL.md"));
|
|
13449
|
+
if (sourceMd === null) return {
|
|
13450
|
+
ok: false,
|
|
13451
|
+
message: "技能「" + source + "」不存在。"
|
|
13452
|
+
};
|
|
13453
|
+
if (targetMd === null) return {
|
|
13454
|
+
ok: false,
|
|
13455
|
+
message: "技能「" + target + "」不存在。"
|
|
13456
|
+
};
|
|
13457
|
+
if (plan.anchors !== void 0) {
|
|
13458
|
+
const sourceHash = contentHash(sourceMd);
|
|
13459
|
+
const targetHash = contentHash(targetMd);
|
|
13460
|
+
if (sourceHash !== plan.anchors.sourceMdSha256 || targetHash !== plan.anchors.targetMdSha256) return {
|
|
13461
|
+
ok: false,
|
|
13462
|
+
message: "提取被拒绝:source/target 在计划之后发生了变化;请重新计划。"
|
|
13463
|
+
};
|
|
13464
|
+
}
|
|
13465
|
+
const projected = projectExtractBodies(plan, sourceMd, targetMd);
|
|
13466
|
+
if (!projected.ok) return {
|
|
13467
|
+
ok: false,
|
|
13468
|
+
message: "提取被拒绝:" + projected.message
|
|
13469
|
+
};
|
|
13470
|
+
const sourceNew = projected.projection.sourceAfter;
|
|
13471
|
+
const targetNew = projected.projection.targetAfter;
|
|
13472
|
+
const ops = [];
|
|
13473
|
+
for (const move of [...plan.support.shared, ...plan.support.exclusive]) {
|
|
13474
|
+
const fromPath = join(sourceDir, ...move.from.split("/"));
|
|
13475
|
+
const toPath = join(targetDir, ...move.to.split("/"));
|
|
13476
|
+
const content = await this.io.readText(fromPath);
|
|
13477
|
+
if (content === null) return {
|
|
13478
|
+
ok: false,
|
|
13479
|
+
message: "提取被拒绝:support 文件不存在 " + move.from
|
|
13480
|
+
};
|
|
13481
|
+
const previous = await this.readForCas(toPath);
|
|
13482
|
+
ops.push({
|
|
13483
|
+
kind: "write",
|
|
13484
|
+
target: toPath,
|
|
13485
|
+
content,
|
|
13486
|
+
expected: previous,
|
|
13487
|
+
skill: target
|
|
13488
|
+
});
|
|
13489
|
+
}
|
|
13490
|
+
for (const file of plan.support.external) {
|
|
13491
|
+
if (file.content === void 0) return {
|
|
13492
|
+
ok: false,
|
|
13493
|
+
message: "提取被拒绝:external support 缺少已验证的内容 " + file.from
|
|
13494
|
+
};
|
|
13495
|
+
if (file.to.startsWith("/") || file.to.includes("..") || !/^(?:references|templates|scripts|assets)\//.test(file.to)) return {
|
|
13496
|
+
ok: false,
|
|
13497
|
+
message: "提取被拒绝:external target 不安全 " + file.to
|
|
13498
|
+
};
|
|
13499
|
+
const toPath = join(targetDir, ...file.to.split("/"));
|
|
13500
|
+
const previous = await this.readForCas(toPath);
|
|
13501
|
+
ops.push({
|
|
13502
|
+
kind: "write",
|
|
13503
|
+
target: toPath,
|
|
13504
|
+
content: file.content,
|
|
13505
|
+
expected: previous,
|
|
13506
|
+
skill: target
|
|
13507
|
+
});
|
|
13508
|
+
}
|
|
13509
|
+
ops.push({
|
|
13510
|
+
kind: "write",
|
|
13511
|
+
target: join(targetDir, "SKILL.md"),
|
|
13512
|
+
content: targetNew,
|
|
13513
|
+
expected: targetMd,
|
|
13514
|
+
skill: target
|
|
13515
|
+
});
|
|
13516
|
+
for (const move of plan.support.exclusive) {
|
|
13517
|
+
const sourcePath = join(sourceDir, ...move.from.split("/"));
|
|
13518
|
+
const previous = await this.readForCas(sourcePath);
|
|
13519
|
+
if (previous === null) return {
|
|
13520
|
+
ok: false,
|
|
13521
|
+
message: "提取被拒绝:待删除 support 不存在 " + move.from
|
|
13522
|
+
};
|
|
13523
|
+
ops.push({
|
|
13524
|
+
kind: "delete",
|
|
13525
|
+
target: sourcePath,
|
|
13526
|
+
expected: previous,
|
|
13527
|
+
skill: source
|
|
13528
|
+
});
|
|
13529
|
+
}
|
|
13530
|
+
ops.push({
|
|
13531
|
+
kind: "write",
|
|
13532
|
+
target: join(sourceDir, "SKILL.md"),
|
|
13533
|
+
content: sourceNew,
|
|
13534
|
+
expected: sourceMd,
|
|
13535
|
+
skill: source
|
|
13536
|
+
});
|
|
13537
|
+
return this.applyTreeChange({
|
|
13538
|
+
name: source,
|
|
13539
|
+
origin,
|
|
13540
|
+
protection: "write",
|
|
13541
|
+
roots: [{
|
|
13542
|
+
name: source,
|
|
13543
|
+
protection: "write"
|
|
13544
|
+
}, {
|
|
13545
|
+
name: target,
|
|
13546
|
+
protection: "write"
|
|
13547
|
+
}],
|
|
13548
|
+
writes: [],
|
|
13549
|
+
ops,
|
|
13550
|
+
auditAction: "extract",
|
|
13551
|
+
auditSummary: "extract " + plan.parts.map((part) => part.heading).join(", ") + " from " + source + " to " + target,
|
|
13552
|
+
eventAction: "extract"
|
|
13553
|
+
});
|
|
13554
|
+
}
|
|
13555
|
+
async applyTreeChange(plan) {
|
|
13556
|
+
const primaryName = plan.name.trim();
|
|
13557
|
+
const declared = plan.roots ?? [{
|
|
13558
|
+
name: primaryName,
|
|
13559
|
+
protection: plan.protection
|
|
13560
|
+
}];
|
|
13561
|
+
const roots = [];
|
|
13562
|
+
for (const root of declared) {
|
|
13563
|
+
const name = root.name.trim();
|
|
13564
|
+
if (roots.some((entry) => entry.name === name)) return {
|
|
13565
|
+
ok: false,
|
|
13566
|
+
message: "目录树变更声明了重复的根「" + name + "」。"
|
|
13567
|
+
};
|
|
13568
|
+
const badName = this.badName(name, { allowReserved: true });
|
|
13569
|
+
if (badName) return {
|
|
13570
|
+
ok: false,
|
|
13571
|
+
message: badName
|
|
13572
|
+
};
|
|
13573
|
+
const dir = this.dirOf(name);
|
|
13574
|
+
if (!await this.io.readText(join(dir, "SKILL.md"))) return {
|
|
13575
|
+
ok: false,
|
|
13576
|
+
message: "技能「" + name + "」不存在。"
|
|
13577
|
+
};
|
|
13578
|
+
const protection = root.protection === "write" ? await this.writeProtection(name, plan.origin) : root.protection === "delete" ? await this.deleteProtection(name) : null;
|
|
13579
|
+
if (protection) return {
|
|
13580
|
+
ok: false,
|
|
13581
|
+
message: "技能「" + name + "」受保护(" + protection + ")。"
|
|
13582
|
+
};
|
|
13583
|
+
roots.push({
|
|
13584
|
+
name,
|
|
13585
|
+
protection: root.protection,
|
|
13586
|
+
dir
|
|
13587
|
+
});
|
|
13588
|
+
}
|
|
13589
|
+
if (!roots.some((root) => root.name === primaryName)) return {
|
|
13590
|
+
ok: false,
|
|
13591
|
+
message: "目录树变更的 primary skill「" + primaryName + "」不在 roots 中。"
|
|
13592
|
+
};
|
|
13593
|
+
const provided = plan.ops ?? [...plan.writes.map((write) => ({
|
|
13594
|
+
kind: "write",
|
|
13595
|
+
...write
|
|
13596
|
+
})), ...(plan.deletes ?? []).map((del) => ({
|
|
13597
|
+
kind: "delete",
|
|
13598
|
+
...del
|
|
13599
|
+
}))];
|
|
13600
|
+
if (provided.length === 0) return {
|
|
13601
|
+
ok: false,
|
|
13602
|
+
message: "目录树变更为空。"
|
|
12781
13603
|
};
|
|
12782
13604
|
const landing = [];
|
|
12783
|
-
for (const
|
|
13605
|
+
for (const op of provided) {
|
|
13606
|
+
const owner = roots.find((root) => {
|
|
13607
|
+
const rel = relative(root.dir, op.target);
|
|
13608
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
13609
|
+
});
|
|
13610
|
+
if (owner === void 0) return {
|
|
13611
|
+
ok: false,
|
|
13612
|
+
message: "目标不属于任何声明的技能根:" + op.target
|
|
13613
|
+
};
|
|
13614
|
+
const skill = op.skill ?? owner.name;
|
|
13615
|
+
if (!roots.some((root) => root.name === skill)) return {
|
|
13616
|
+
ok: false,
|
|
13617
|
+
message: "目标声明的技能「" + skill + "」不在 roots 中:" + op.target
|
|
13618
|
+
};
|
|
12784
13619
|
let previous;
|
|
12785
13620
|
try {
|
|
12786
|
-
previous = await this.io.readText(
|
|
13621
|
+
previous = await this.io.readText(op.target);
|
|
12787
13622
|
} catch (error) {
|
|
12788
13623
|
return {
|
|
12789
13624
|
ok: false,
|
|
12790
|
-
message:
|
|
13625
|
+
message: "拒绝目录树变更:无法安全预读 " + op.target + "(" + (error instanceof Error ? error.message : String(error)) + ");未执行任何写入"
|
|
12791
13626
|
};
|
|
12792
13627
|
}
|
|
12793
|
-
if (
|
|
12794
|
-
ok: false,
|
|
12795
|
-
message: `写入超过 ${this.limits.maxSkillFileBytes} 字节:${write.target}`
|
|
12796
|
-
};
|
|
12797
|
-
const threat = this.contentThreatBlock(write.content);
|
|
12798
|
-
if (threat) return {
|
|
13628
|
+
if (op.kind === "delete" && previous === null) return {
|
|
12799
13629
|
ok: false,
|
|
12800
|
-
message:
|
|
13630
|
+
message: "拒绝目录树变更:删除目标不存在 " + op.target
|
|
12801
13631
|
};
|
|
13632
|
+
if (op.kind === "write") {
|
|
13633
|
+
if (Buffer.byteLength(op.content, "utf8") > this.limits.maxSkillFileBytes) return {
|
|
13634
|
+
ok: false,
|
|
13635
|
+
message: "写入超过 " + this.limits.maxSkillFileBytes + " 字节:" + op.target
|
|
13636
|
+
};
|
|
13637
|
+
const threat = this.contentThreatBlock(op.content);
|
|
13638
|
+
if (threat) return {
|
|
13639
|
+
ok: false,
|
|
13640
|
+
message: threat
|
|
13641
|
+
};
|
|
13642
|
+
}
|
|
13643
|
+
const rel = relative(owner.dir, op.target).split(String.fromCharCode(92)).join("/");
|
|
12802
13644
|
landing.push({
|
|
12803
|
-
|
|
12804
|
-
|
|
13645
|
+
kind: op.kind,
|
|
13646
|
+
target: op.target,
|
|
13647
|
+
content: op.kind === "write" ? op.content : null,
|
|
12805
13648
|
previous,
|
|
12806
|
-
expected:
|
|
13649
|
+
expected: op.expected,
|
|
13650
|
+
skill,
|
|
13651
|
+
dir: owner.dir,
|
|
13652
|
+
rel
|
|
12807
13653
|
});
|
|
12808
13654
|
}
|
|
12809
13655
|
const written = [];
|
|
@@ -12822,11 +13668,12 @@ var SkillLibrary = class {
|
|
|
12822
13668
|
drift.seen = true;
|
|
12823
13669
|
return current;
|
|
12824
13670
|
}
|
|
12825
|
-
return entry.content;
|
|
13671
|
+
return entry.kind === "delete" ? null : entry.content;
|
|
12826
13672
|
});
|
|
12827
|
-
if (!ran.done) throw new Error(
|
|
12828
|
-
if (drift.seen) throw new Error(
|
|
12829
|
-
} else await this.io.
|
|
13673
|
+
if (!ran.done) throw new Error("internal error: the write transaction for " + entry.target + " did not invoke the task; nothing was written");
|
|
13674
|
+
if (drift.seen) throw new Error("concurrent modification detected: " + entry.target + " changed after the plan was computed (a concurrent writer won the race); no further writes were performed");
|
|
13675
|
+
} else if (entry.kind === "delete") await this.io.remove(entry.target);
|
|
13676
|
+
else await this.io.writeText(entry.target, entry.content ?? "");
|
|
12830
13677
|
} catch (error) {
|
|
12831
13678
|
if (!isCommittedOnly(error)) throw error;
|
|
12832
13679
|
durabilityWarning = error instanceof Error ? error.message : String(error);
|
|
@@ -12847,42 +13694,43 @@ var SkillLibrary = class {
|
|
|
12847
13694
|
const reason = error instanceof Error ? error.message : String(error);
|
|
12848
13695
|
if (stuck.length === 0) return {
|
|
12849
13696
|
ok: false,
|
|
12850
|
-
message:
|
|
13697
|
+
message: "目录树变更失败,已回滚:" + reason
|
|
12851
13698
|
};
|
|
12852
13699
|
return {
|
|
12853
13700
|
ok: false,
|
|
12854
|
-
message:
|
|
13701
|
+
message: "目录树变更失败:" + reason + "。回滚无法恢复 " + stuck.join(", ") + ":可恢复性由当前快照决定:" + await this.snapshotRecoveryAvailability() + " —— 若上面不是「最新快照」,请重新应用这次变更。"
|
|
12855
13702
|
};
|
|
12856
13703
|
}
|
|
12857
13704
|
const unitId = plan.unitId ?? "u-" + Date.now().toString(36) + "-" + (nextUnitSeq += 1).toString(36);
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
const auditedBefore = land.previous;
|
|
12874
|
-
const auditedAfter = land.content;
|
|
12875
|
-
await this.audit(name, plan.auditAction, auditedBefore, auditedAfter, plan.auditSummary, rel, unitId, unitHead, plan.origin);
|
|
13705
|
+
const defaultHead = {
|
|
13706
|
+
moves: landing.filter((entry) => basename(entry.target) !== "SKILL.md").map((entry) => ({
|
|
13707
|
+
path: entry.rel,
|
|
13708
|
+
kind: entry.kind === "delete" ? "removed" : entry.previous === null ? "created" : "moved",
|
|
13709
|
+
...entry.previous === null ? {} : { beforeRef: contentHash(entry.previous) },
|
|
13710
|
+
...entry.kind === "delete" || entry.content === null ? {} : { afterRef: contentHash(entry.content) }
|
|
13711
|
+
})),
|
|
13712
|
+
sources: roots.map((root) => ({ skill: root.name }))
|
|
13713
|
+
};
|
|
13714
|
+
const affected = /* @__PURE__ */ new Set();
|
|
13715
|
+
for (let index = 0; index < landing.length; index += 1) {
|
|
13716
|
+
const entry = landing[index];
|
|
13717
|
+
if (entry === void 0) continue;
|
|
13718
|
+
await this.audit(entry.skill, plan.auditAction, entry.previous, entry.kind === "delete" ? null : entry.content, plan.auditSummary, entry.rel === "SKILL.md" ? void 0 : entry.rel, unitId, index === 0 ? plan.unitHead ?? defaultHead : void 0, plan.origin);
|
|
13719
|
+
affected.add(entry.skill);
|
|
12876
13720
|
}
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
13721
|
+
for (const skill of affected) {
|
|
13722
|
+
const dir = roots.find((root) => root.name === skill)?.dir;
|
|
13723
|
+
if (dir !== void 0) this.notifyMutation({
|
|
13724
|
+
action: plan.eventAction,
|
|
13725
|
+
name: skill,
|
|
13726
|
+
skillDir: dir
|
|
13727
|
+
});
|
|
13728
|
+
}
|
|
13729
|
+
const primaryDir = roots.find((root) => root.name === primaryName)?.dir ?? roots[0]?.dir ?? "";
|
|
12882
13730
|
return {
|
|
12883
13731
|
ok: true,
|
|
12884
|
-
message: durabilityWarning === "" ?
|
|
12885
|
-
path:
|
|
13732
|
+
message: durabilityWarning === "" ? plan.eventAction + " \"" + primaryName + "\" succeeded." : plan.eventAction + " \"" + primaryName + "\" succeeded (警告:写入已落盘,但目录 fsync 失败 —— 持久性未确认:" + durabilityWarning + ")",
|
|
13733
|
+
path: primaryDir
|
|
12886
13734
|
};
|
|
12887
13735
|
}
|
|
12888
13736
|
/**
|
|
@@ -15933,4 +16781,4 @@ function isIgnorableEvent(event) {
|
|
|
15933
16781
|
return classifySessionEvent(event) !== "model";
|
|
15934
16782
|
}
|
|
15935
16783
|
//#endregion
|
|
15936
|
-
export { AUTHORING_DESCRIPTION_BAR, AUTHORING_SPLIT_LINE_CHARS, BASELINE_ACTION, CITATION_SUPPORT_DIRS, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CONTENT_SPLIT_HINT, COST_ASCII_TOKEN_HIGH, COST_ASCII_TOKEN_LOW, COST_CJK_TOKEN_HIGH, COST_CJK_TOKEN_LOW, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEDUP_MAX_PAIR_COMPARISONS, DEDUP_SIMILARITY_THRESHOLD, DEFAULT_ADMISSION, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_CITATION_REF_BUDGET, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_RUN_RECORDS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DEFAULT_SUPPORT_READ_TOOL_NAMES, DEFAULT_THREAT_MAX_SCAN_CHARS, DEFAULT_USER_CHAR_LIMIT, DEFAULT_WRITE_CONFIRM_MODE, DEFAULT_WRITE_CONFIRM_TIMEOUT_SECONDS, DIAGNOSTICS_FACT_KEYS, DIAGNOSTICS_RUNTIME_KEYS, DIAGNOSTICS_VIEW_SCHEMA_VERSION, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_ERRORS, EVOLUTION_PRESET_IDS, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, FUZZY_MAX_PATTERN_CHARS, FUZZY_MAX_WORK, HEALTH_STAMP_RE, HISTORY_DIR, HISTORY_INDEX_VERSION, HOOK_LINE_RE, HOST_ROUTES, INSTANCE_KEYS, KEEP_LINE_RE, LONG_RUNS, LOW_QUALITY_THRESHOLD, MAINTAINER_PRESET_ID, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_REQUEST_BODY_BYTES, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_SUPPORT_READ_PATHS, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MODEL_SESSION_EVENT_TYPES as MODEL_SESSION_EVENTS, MODEL_SESSION_EVENT_TYPES, MUTATIONS_FILE_VERSION, MemoryStore, NAMESPACE_MIGRATIONS, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PARAM_ALIASES, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PARAM_NAMESPACES, PATTERN_OVERLAP, PLATFORM_CATALOG_DESCRIPTION_DEFAULT, PLATFORM_ESTIMATE_CHARS_PER_TOKEN, PLATFORM_LINE, PLATFORM_SERVICE_PROBES, POLICY_STAGE_DEFAULTS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, READ_REQUIRED_ACTIONS, RESTRICTED_PRESET_DISPLAY, RESTRICTED_TOOLS_ROW_ID, RESTRICTOR_MODULE, RESTRUCTURE_TARGET_RE, RETENTION_FEEDBACK_KEEP_RATIO, RETENTION_FEEDBACK_MIN_CHARS, REVIEWER_PRESET_ID, RULES, RUNS_SCHEMA_VERSION, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, STORE_LEDGER, SUMMARY_DUPLICATE_HINT_THRESHOLD, SUPPORT_DIRS, SUPPORT_SPRAWL_FILES, SUPPRESSED_FILE_VERSION, SkillLibrary, TEXT_DIFF_MAX_CHARS, TEXT_DIFF_MAX_LINES, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UNIT_JOURNAL_PHASES, UNIT_JOURNAL_SCHEMA_VERSION, UNIT_STORAGE_SCHEMA_VERSION, UNTRUSTED_LINE_BREAK, UNTRUSTED_LINE_MARK, UPSTREAM_LIMIT_CHARS_PER_TOKEN, UnitTransactions, VERSION_SUMMARY_MAX_CHARS, absentHostServices, acquireTreeWriteLock, admissionVerdict, advanceReview, affinity, ageCopyKey, allowRowCollisions, anchorVerdict, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyNamespaceMigration, applyReferenceRewrite, asRecord, assertDispatchPayload, assertSkillsRootAliasRetired, assertUnitJournalTransition, assessStructureHealth, atomicWriteFiles, authoringFeedback, authorityFrom, backupsReachability, blobPath, bodyCost, buildCuratorRunReport, buildHealthBaseline, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, candidateSkills, canonicalWriteId, claimInstance, clampOnce, clampedNumber, classifyNamespaceMigration, classifySessionEvent, clearReviewChannel, collectReadSkillNames, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, contentRetentionFeedback, countDispatches, countReferenceFiles, createGateSet, createSubagentChannel, daysSinceIso, declaredReportStamp, describeReferenceRewrite, diffFindings, dispatchKeyOf, dispatchStepOf, displayBodyOf, duplicateHeadings, elapsedSince, emptyRecord, ensureSkillFrontmatter, entryTarget, errorText, evaluateThreat, eventsFile, evidenceKindIndex, evolutionActivityProjection, evolutionEventPayloadIssue, evolutionEvidenceProjection, evolutionHome, evolutionIoAdapter, evolutionReadsProjection, evolutionRoot, evolutionSignalsProjection, executeUnitUndo, filterUnreadSkillOps, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurnEvent, foldTurnEvents, foldUntrustedLines, frontmatterBlock, frontmatterCatalogInvalid, fuzzyPatch, getRecord, h2Sections, historyIndexFile, historyReachability, historyRoot, hostSurface, identity, idleDays, initialTurnFold, installEvolutionProjections, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isBookkeepingFrame, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isIgnorableEvent, isLandmark, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isRecord, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, isUnreadWrite, json, landmarkVerdict, latestActivityAt, latestVersionAt, lifecycleCandidate, lintBody, lintSkill, listEventArchives, liveBool, liveEnum, liveNumber, liveString, loadMutations, loadSuppressedNames, loadUsage, loadVersionContent, loadVersions, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, methodNotAllowed, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nearDuplicateSummaries, newRunRegistry, newSkillLibrary, nextHistoryIndex, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, normalizeVersionSummary, observeEvent, orderVersions, overlongLines, paramNamespace, paramRowId, paramSettingsId, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseHistoryIndex, partitionVersions, payloadText, persistedWriteSite, persistedWriteSites, pickWithPolicy, planNamespaceMigration, planReferenceRewrite, planRehoming, planUnitUndoAcross, policyStageLimits, probeAbsent, probeChannelState, probeList, probeMtime, probePresent, probeReason, probeText, probeUnknown, projectionText, protectedSkillNamesOf, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readHistoryIndex, readJsonObject, readNumberParam, readParam, readTreeLock, readUnitEvidence, readUnitIndex, readUnitJournal, readUnitReceipt, readUnitSnapshot, readingStats, recordMutation, recordVersions, redactSecrets, referencedBlobs, registerHostRoutes, relatedSkillNames, releaseInstance, renameWithRetry, renderCuratorReportMarkdown, renderLongRunTable, reportTime, reportsReachability, reportsSweepLockTarget, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, resolveUnitMembers, resolveUnitMembersFromIndexes, restructureCandidates, retainEventArchives, retirementReport, reviewPrompt, revisionAnchor, revisionAnchorOf, revisionFingerprint, revisionSupportDigest, runWriteGates, runsFile, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopeOfNearestManifest, scopedPackageName, scopedProbeReport, sessionAudited, sessionEvidenceIndex, sessionLastEventTime, sessionReadNames, sessionReadSkillNames, sessionSeesFamilyTools, sessionTurnSignals, skillLibraryUnitBackend, skillReadNameOf, skillRowCells, skillStateKey, skillWriteGateContext, skillsRoot, staleRouteStubs, subagentChannelState, supportFileReadOf, supportRefs, suppressedFile, sweepReports, sweepReviewChannelSessions, textDiffFacts, tokenLineFor, transactIo, transactTaskGuard, treeLockFile, trimPatternBoundaries, unhookedSupportPointers, unitEvidenceDigest, unitEvidencePath, unitIndexPath, unitJournalPath, unitJournalPhaseRank, unitMembersAreComplete, unitReceiptPath, unitSnapshotPath, unitSnapshotsDir, unitStorageKey, unwrapLive, updateSuppressedNames, upsertUnitIndex, usageFile, usageObserved, userSetKeys, validateFrontmatter, validateRestructureTarget, validateSupportPath, valueOr, verifyPromptBundle, versionActionKind, versionRowCells, versionSummaryPrompt, versionTarget, vocabulary, withTreeWrite, withTreeWriteLock, writeDurableTmp, writePathSymbols, writeUnitEvidence, writeUnitJournal, writeUnitReceipt, writeUnitSnapshot, yamlPlainScalarNeedsQuotes };
|
|
16784
|
+
export { AUTHORING_DESCRIPTION_BAR, AUTHORING_SPLIT_LINE_CHARS, BASELINE_ACTION, CITATION_SUPPORT_DIRS, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CONTENT_SPLIT_HINT, COST_ASCII_TOKEN_HIGH, COST_ASCII_TOKEN_LOW, COST_CJK_TOKEN_HIGH, COST_CJK_TOKEN_LOW, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEDUP_MAX_PAIR_COMPARISONS, DEDUP_SIMILARITY_THRESHOLD, DEFAULT_ADMISSION, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_CITATION_REF_BUDGET, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_RUN_RECORDS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DEFAULT_SUPPORT_READ_TOOL_NAMES, DEFAULT_THREAT_MAX_SCAN_CHARS, DEFAULT_USER_CHAR_LIMIT, DEFAULT_WRITE_CONFIRM_MODE, DEFAULT_WRITE_CONFIRM_TIMEOUT_SECONDS, DIAGNOSTICS_FACT_KEYS, DIAGNOSTICS_RUNTIME_KEYS, DIAGNOSTICS_VIEW_SCHEMA_VERSION, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_ERRORS, EVOLUTION_PRESET_IDS, EVOLUTION_WRITE_TOOLS, EXTRACT_PROMPT_V1, EXTRACT_PROMPT_VERSION, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, FUZZY_MAX_PATTERN_CHARS, FUZZY_MAX_WORK, HEALTH_STAMP_RE, HISTORY_DIR, HISTORY_INDEX_VERSION, HOOK_LINE_RE, HOST_ROUTES, INSTANCE_KEYS, KEEP_LINE_RE, LONG_RUNS, LOW_QUALITY_THRESHOLD, MAINTAINER_PRESET_ID, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_REQUEST_BODY_BYTES, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_SUPPORT_READ_PATHS, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MODEL_SESSION_EVENT_TYPES as MODEL_SESSION_EVENTS, MODEL_SESSION_EVENT_TYPES, MUTATIONS_FILE_VERSION, MemoryStore, NAMESPACE_MIGRATIONS, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PARAM_ALIASES, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PARAM_NAMESPACES, PATTERN_OVERLAP, PLATFORM_CATALOG_DESCRIPTION_DEFAULT, PLATFORM_ESTIMATE_CHARS_PER_TOKEN, PLATFORM_LINE, PLATFORM_SERVICE_PROBES, POLICY_STAGE_DEFAULTS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, READ_REQUIRED_ACTIONS, RESTRICTED_PRESET_DISPLAY, RESTRICTED_TOOLS_ROW_ID, RESTRICTOR_MODULE, RESTRUCTURE_TARGET_RE, RETENTION_FEEDBACK_KEEP_RATIO, RETENTION_FEEDBACK_MIN_CHARS, REVIEWER_PRESET_ID, RULES, RUNS_SCHEMA_VERSION, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, STORE_LEDGER, SUMMARY_DUPLICATE_HINT_THRESHOLD, SUPPORT_DIRS, SUPPORT_SPRAWL_FILES, SUPPRESSED_FILE_VERSION, SkillLibrary, TEXT_DIFF_MAX_CHARS, TEXT_DIFF_MAX_LINES, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UNIT_JOURNAL_PHASES, UNIT_JOURNAL_SCHEMA_VERSION, UNIT_STORAGE_SCHEMA_VERSION, UNTRUSTED_LINE_BREAK, UNTRUSTED_LINE_MARK, UPSTREAM_LIMIT_CHARS_PER_TOKEN, UnitTransactions, VERSION_SUMMARY_MAX_CHARS, absentHostServices, acquireTreeWriteLock, admissionVerdict, advanceReview, affinity, ageCopyKey, allowRowCollisions, anchorVerdict, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyNamespaceMigration, applyReferenceRewrite, asRecord, assertDispatchPayload, assertSkillsRootAliasRetired, assertUnitJournalTransition, assessStructureHealth, atomicWriteFiles, authoringFeedback, authorityFrom, backupsReachability, blobPath, bodyCost, buildCuratorRunReport, buildHealthBaseline, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, candidateSkills, canonicalWriteId, claimInstance, clampOnce, clampedNumber, classifyNamespaceMigration, classifySessionEvent, clearReviewChannel, collectReadSkillNames, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, contentRetentionFeedback, countDispatches, countReferenceFiles, createGateSet, createSubagentChannel, daysSinceIso, declaredReportStamp, describeReferenceRewrite, diffFindings, dispatchKeyOf, dispatchStepOf, displayBodyOf, duplicateHeadings, elapsedSince, emptyRecord, ensureSkillFrontmatter, entryTarget, errorText, evaluateThreat, eventsFile, evidenceKindIndex, evolutionActivityProjection, evolutionEventPayloadIssue, evolutionEvidenceProjection, evolutionHome, evolutionIoAdapter, evolutionReadsProjection, evolutionRoot, evolutionSignalsProjection, executeUnitUndo, externalRefs, filterUnreadSkillOps, findDriftSignal, findOwnerCandidate, foldCuratorFields, foldToolDispatches, foldTurnEvent, foldTurnEvents, foldUntrustedLines, frontmatterBlock, frontmatterCatalogInvalid, fuzzyPatch, getRecord, h2Sections, historyIndexFile, historyReachability, historyRoot, hostSurface, identity, idleDays, initialTurnFold, installEvolutionProjections, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isBookkeepingFrame, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isIgnorableEvent, isLandmark, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isRecord, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, isUnreadWrite, json, landmarkVerdict, latestActivityAt, latestVersionAt, lifecycleCandidate, lintBody, lintSkill, listEventArchives, liveBool, liveEnum, liveNumber, liveString, loadMutations, loadSuppressedNames, loadUsage, loadVersionContent, loadVersions, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, methodNotAllowed, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nearDuplicateSummaries, newRunRegistry, newSkillLibrary, nextHistoryIndex, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, normalizeVersionSummary, observeEvent, orderVersions, overlongLines, ownerCandidatesFromSummaries, paramNamespace, paramRowId, paramSettingsId, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseHistoryIndex, partitionVersions, payloadText, persistedWriteSite, persistedWriteSites, pickWithPolicy, planExtract, planNamespaceMigration, planReferenceRewrite, planRehoming, planUnitUndoAcross, policyStageLimits, probeAbsent, probeChannelState, probeList, probeMtime, probePresent, probeReason, probeText, probeUnknown, projectExtractBodies, projectionText, proposalFromPlan, proposalReportDir, proposalReportName, protectedSkillNamesOf, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readExternalSupport, readHistoryIndex, readJsonObject, readNumberParam, readParam, readProposalReports, readTreeLock, readUnitEvidence, readUnitIndex, readUnitJournal, readUnitReceipt, readUnitSnapshot, readingStats, recordMutation, recordVersions, redactSecrets, referencedBlobs, registerHostRoutes, relatedSkillNames, releaseInstance, renameWithRetry, renderCuratorReportMarkdown, renderLongRunTable, reportTime, reportsReachability, reportsSweepLockTarget, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, resolveUnitMembers, resolveUnitMembersFromIndexes, restructureCandidates, retainEventArchives, retirementReport, reviewPrompt, revisionAnchor, revisionAnchorOf, revisionFingerprint, revisionSupportDigest, runWriteGates, runsFile, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopeOfNearestManifest, scopedPackageName, scopedProbeReport, selectSections, sessionAudited, sessionEvidenceIndex, sessionLastEventTime, sessionReadNames, sessionReadSkillNames, sessionSeesFamilyTools, sessionTurnSignals, skillLibraryUnitBackend, skillReadNameOf, skillRowCells, skillStateKey, skillWriteGateContext, skillsRoot, staleRouteStubs, subagentChannelState, supportFileReadOf, supportRefs, suppressedFile, sweepReports, sweepReviewChannelSessions, textDiffFacts, tokenLineFor, transactIo, transactTaskGuard, treeLockFile, trimPatternBoundaries, unhookedSupportPointers, unitEvidenceDigest, unitEvidencePath, unitIndexPath, unitJournalPath, unitJournalPhaseRank, unitMembersAreComplete, unitReceiptPath, unitSnapshotPath, unitSnapshotsDir, unitStorageKey, unwrapLive, updateSuppressedNames, upsertUnitIndex, usageFile, usageObserved, userSetKeys, validateFrontmatter, validateRestructureTarget, validateSupportPath, valueOr, verifyPromptBundle, versionActionKind, versionRowCells, versionSummaryPrompt, versionTarget, vocabulary, withTreeWrite, withTreeWriteLock, writeDurableTmp, writePathSymbols, writeProposalReport, writeUnitEvidence, writeUnitJournal, writeUnitReceipt, writeUnitSnapshot, yamlPlainScalarNeedsQuotes };
|