ygc-dsh-sync 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/core.js CHANGED
@@ -134,34 +134,51 @@ const DEFAULT_STATE = {
134
134
  lastResult: null,
135
135
  },
136
136
  skills: {
137
- /** 母版根目录;空表示未配置。 */
138
- masterRoot: '',
139
- /** 自动拉取开关。 */
140
- enabled: false,
141
- /** 拉取间隔,默认 120 分钟。 */
142
- intervalMinutes: 120,
143
- /**
144
- * 记忆插件(dsh-mnemon)技能优化自动落地;默认开。
145
- * 关掉后草稿只在账本里堆着,等人到面板上逐个采纳。
146
- */
147
- mnemonAuto: true,
148
137
  /**
149
- * 记忆插件技能优化闭环(发现 → 判断 → 入队 → 审核 → 通过);默认开。
150
- * 关掉后只做草稿落地,不主动发现和处理新建议。
138
+ * 技能同步总开关(1.3.0 起默认开)。
139
+ *
140
+ * 母版功能已取消:技能不再有「外部母版目录」,来源只有两处——
141
+ * 本机特征文件夹里的备份、别人特征文件夹里的备份。所以这里不再需要
142
+ * masterRoot,也就不再有「母版没设置就整个停摆」这种状态。
151
143
  */
152
- mnemonOptimize: true,
153
- /** 分诊时的置信度门槛,低于它的建议不处理。 */
154
- mnemonMinConfidence: 0.7,
155
- /** 一轮优化闭环最多起草几条:模型调用要花时间,默认 6 条一批。 */
156
- mnemonMaxDrafts: 6,
144
+ enabled: true,
145
+ /** 同步间隔,默认 120 分钟。 */
146
+ intervalMinutes: 120,
157
147
  lastRunAt: 0,
158
148
  lastResult: null,
159
149
  /**
160
- * 技能的单机属性(只存本机,不随母版同步):技能名 → 'sync' | 'local' | 'master'
161
- * sync 同步(双向)
162
- * local 本机独属(母版不该有它)
163
- * master 母版有·本机不拉取
164
- * 未列出的按自动:本机有母版没有 → local,其余 → sync。
150
+ * 技能共享(1.3.0 新增):与插件清单同步同构——一人一格,只写自己那一格。
151
+ *
152
+ * 与插件侧的区别:技能没有 registry,整棵目录就是产物,所以这里存的是
153
+ * 技能本体(含 scripts/references),不是清单。同机不同内容不覆盖,
154
+ * 交给学习评估产出草稿,人批了才落到本机。
155
+ */
156
+ share: {
157
+ /** 技能同步触发时顺带扫别人的格子。默认开。 */
158
+ enabled: true,
159
+ /** 同名不同内容时调模型评估「怎么优化」。默认开。 */
160
+ learn: true,
161
+ /** 一轮最多评估几条差异:模型调用要时间,一次跑太多会拖住同步。 */
162
+ learnPerRound: 4,
163
+ /** 评估结论是否直接落到本机技能根。默认关:先出草稿,人批了再落。 */
164
+ autoApply: false,
165
+ /** 一轮本机优化最多出几份草稿(面板「优化」按钮用)。 */
166
+ optimizePerRound: 3,
167
+ /** 人工指定的技能归属覆盖:技能名 → 'own' | 'third-party'。 */
168
+ scope: {},
169
+ /** 上次运行时间与结果摘要。 */
170
+ lastRunAt: 0,
171
+ lastResult: null,
172
+ lastError: '',
173
+ /** 上次优化的时间与摘要(与共享分开记,面板能分别显示)。 */
174
+ lastOptimizeAt: 0,
175
+ lastOptimize: null,
176
+ /** 上次的归属判定摘要。 */
177
+ lastScope: null,
178
+ },
179
+ /**
180
+ * 老字段:母版时代的「单机属性」。母版取消后不再参与任何判定,
181
+ * 只为兼容老 state 保留,读到时会被忽略(见下方归一化)。
165
182
  */
166
183
  modes: {},
167
184
  },
@@ -271,33 +288,46 @@ export function loadState() {
271
288
  }
272
289
  // 旧版字段清理
273
290
  if (state.skills.syncedSkills !== undefined) delete state.skills.syncedSkills;
274
- // 单机属性表:只接受三种合法值
275
- if (!state.skills.modes || typeof state.skills.modes !== 'object' || Array.isArray(state.skills.modes)) {
276
- state.skills.modes = {};
277
- } else {
278
- const clean = {};
279
- for (const [k, v] of Object.entries(state.skills.modes)) {
280
- if (typeof k !== 'string' || !k) continue;
281
- if (v === 'sync' || v === 'local' || v === 'master') clean[k] = v;
282
- }
283
- state.skills.modes = clean;
291
+ // 母版功能已取消(1.3.0):这些字段一律删掉,读回来当没看见。
292
+ // 不删的话,老 state 里的 masterRoot 会让下游继续以为「有个母版目录要同步」。
293
+ for (const dead of ['masterRoot', 'mnemonAuto', 'mnemonOptimize', 'mnemonMinConfidence', 'mnemonMaxDrafts', 'modes']) {
294
+ if (state.skills[dead] !== undefined) delete state.skills[dead];
284
295
  }
296
+ // 1.3.0 起技能同步默认开(母版取消后没有「未配置就停摆」的前提了)
297
+ state.skills.enabled = state.skills.enabled !== false;
285
298
  const siv = Number(state.skills.intervalMinutes);
286
299
  state.skills.intervalMinutes =
287
300
  Number.isFinite(siv) && siv >= 1 ? Math.min(Math.floor(siv), 10080) : DEFAULT_STATE.skills.intervalMinutes;
288
- // 老版本没有这个字段:补成默认值(开)
289
- if (typeof state.skills.mnemonAuto !== 'boolean') {
290
- state.skills.mnemonAuto = DEFAULT_STATE.skills.mnemonAuto;
291
- }
292
- if (typeof state.skills.mnemonOptimize !== 'boolean') {
293
- state.skills.mnemonOptimize = DEFAULT_STATE.skills.mnemonOptimize;
301
+ // 技能共享(1.3.0 新增;老 state 没有这一段,按默认补齐)
302
+ if (!state.skills.share || typeof state.skills.share !== 'object' || Array.isArray(state.skills.share)) {
303
+ state.skills.share = { ...DEFAULT_STATE.skills.share };
294
304
  }
295
- if (!Number.isFinite(Number(state.skills.mnemonMinConfidence))) {
296
- state.skills.mnemonMinConfidence = DEFAULT_STATE.skills.mnemonMinConfidence;
305
+ const sh = state.skills.share;
306
+ sh.enabled = sh.enabled !== false;
307
+ sh.learn = sh.learn !== false;
308
+ sh.autoApply = sh.autoApply === true;
309
+ const perRound = Number(sh.learnPerRound);
310
+ sh.learnPerRound =
311
+ Number.isFinite(perRound) && perRound >= 0 ? Math.min(Math.floor(perRound), 24) : DEFAULT_STATE.skills.share.learnPerRound;
312
+ sh.lastRunAt = Number(sh.lastRunAt) || 0;
313
+ sh.lastResult = sh.lastResult && typeof sh.lastResult === 'object' ? sh.lastResult : null;
314
+ sh.lastError = typeof sh.lastError === 'string' ? sh.lastError.slice(0, 800) : '';
315
+ if (!Number.isFinite(Number(sh.optimizePerRound)) || Number(sh.optimizePerRound) < 1) {
316
+ sh.optimizePerRound = DEFAULT_STATE.skills.share.optimizePerRound;
297
317
  }
298
- if (!Number.isFinite(Number(state.skills.mnemonMaxDrafts)) || Number(state.skills.mnemonMaxDrafts) < 0) {
299
- state.skills.mnemonMaxDrafts = DEFAULT_STATE.skills.mnemonMaxDrafts;
318
+ sh.optimizePerRound = Math.min(Math.floor(Number(sh.optimizePerRound)), 12);
319
+ if (!sh.scope || typeof sh.scope !== 'object' || Array.isArray(sh.scope)) sh.scope = {};
320
+ else {
321
+ const clean = {};
322
+ for (const [k, v] of Object.entries(sh.scope)) {
323
+ if (typeof k !== 'string' || !k) continue;
324
+ if (v === 'own' || v === 'third-party') clean[k] = v;
325
+ }
326
+ sh.scope = clean;
300
327
  }
328
+ sh.lastOptimizeAt = Number(sh.lastOptimizeAt) || 0;
329
+ sh.lastOptimize = sh.lastOptimize && typeof sh.lastOptimize === 'object' ? sh.lastOptimize : null;
330
+ sh.lastScope = sh.lastScope && typeof sh.lastScope === 'object' ? sh.lastScope : null;
301
331
 
302
332
  // 插件清单同步配置(1.0.0 新增;老 state 没有这一段)
303
333
  if (!state.plugins || typeof state.plugins !== 'object' || Array.isArray(state.plugins)) {
package/lib/features.js CHANGED
@@ -203,6 +203,7 @@ export function planFeatures({
203
203
  const createFolder = [];
204
204
  const restoreWorkspace = [];
205
205
  const ask = [];
206
+ const declined = [];
206
207
  const drop = [];
207
208
  const seed = [];
208
209
 
@@ -232,8 +233,14 @@ export function planFeatures({
232
233
  if (!underFolders(ws.path)) continue;
233
234
 
234
235
  if (decisionOf(state, ws.path) === 'decline') {
235
- // 人明确说过这个目录不要建 → 只列出来,不自作主张
236
- ask.push({ ...entry, why: 'declined' });
236
+ /**
237
+ * 人明确说过这个目录不要建 → **从待决定清单里移除**,也不自动补文件夹。
238
+ *
239
+ * 之前这里是继续 push 进 ask(why='declined'),结果人每轮巡检都被同一个目录
240
+ * 挡着,「跳过」等于没跳过。真正的跳过 = 不再出现在清单里;
241
+ * 想改主意到「未参与同步」那一栏点「建特征文件夹」(走 /features/adopt,会清掉这条记录)。
242
+ */
243
+ declined.push(entry);
237
244
  continue;
238
245
  }
239
246
 
@@ -250,8 +257,16 @@ export function planFeatures({
250
257
  // 这里必须跳过:同一个目录不能既补文件夹又问人(scanFolders 会把它同时扫进 discover)。
251
258
  if (knownWorkspacePaths.has(keyOf(d.path))) continue;
252
259
  const title = d.title || path.basename(d.path);
260
+ /**
261
+ * 人点过「跳过」的目录:**不再列进 pending**。
262
+ *
263
+ * 之前这里是把 why 改成 'declined' 继续留在列表里 —— 那不是跳过,是把
264
+ * 「要不要建」的问句换成「你说过不建」的标签,人每轮巡检还是被同一个目录挡着。
265
+ * 真正的跳过 = 从清单里消失;想改主意到「未参与同步」那一栏点「建特征文件夹」即可
266
+ *(那条路走 /features/adopt,会把 decline 记录清掉)。
267
+ */
253
268
  if (decisionOf(state, d.path) === 'decline') {
254
- ask.push({ id: null, path: d.path, title, why: 'declined' });
269
+ declined.push({ id: null, path: d.path, title });
255
270
  continue;
256
271
  }
257
272
  if (hasFeature(d.path, machineId)) {
@@ -278,7 +293,7 @@ export function planFeatures({
278
293
  drop.push({ id, path: p, title: path.basename(p) });
279
294
  }
280
295
 
281
- return { active, createFolder, restoreWorkspace, ask, drop, seed, seenNow };
296
+ return { active, createFolder, restoreWorkspace, ask, declined, drop, seed, seenNow };
282
297
  }
283
298
 
284
299
  /**