dsh-mnemon-source-skills 0.5.8 → 0.5.9

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/client.js CHANGED
@@ -317,7 +317,9 @@ window.__ModuleLoader__.load({
317
317
  "evolution.noAgent": "当前没有可用的任务 Agent",
318
318
  "evolution.drafting": "正在生成草案",
319
319
  "evolution.draftFailed": "草案生成失败:{error}",
320
- "evolution.skillGone": "这个技能已不存在。",
320
+ "evolution.skillGone": "原技能文件已删除、移动或无法读取。请恢复文件,或在新位置打开技能并重新生成草案。",
321
+ "evolution.originUnavailable": "旧记录未可靠保存原工作区。请切换到原工作区,在技能页打开该技能并重新生成草案;现有草案和证据会保留。",
322
+ "evolution.currentSkill": "当前技能文件",
321
323
  "evolution.skillChanged": "技能在建议之后被改过,生成草案前会以当前版本为准。",
322
324
  "evolution.adopted": "已采纳",
323
325
  "evolution.openSkill": "查看技能",
@@ -671,7 +673,9 @@ window.__ModuleLoader__.load({
671
673
  "evolution.noAgent": "No task Agent is available",
672
674
  "evolution.drafting": "Drafting",
673
675
  "evolution.draftFailed": "Drafting failed: {error}",
674
- "evolution.skillGone": "This skill no longer exists.",
676
+ "evolution.skillGone": "The original skill file was deleted, moved, or cannot be read. Restore it or open the skill at its new location and generate a new draft.",
677
+ "evolution.originUnavailable": "The original workspace was not recorded reliably. Select its original workspace, open the skill on the Skills page and generate a new draft; the existing draft and evidence are kept.",
678
+ "evolution.currentSkill": "Current skill file",
675
679
  "evolution.skillChanged": "The skill changed after this suggestion; the draft starts from its current version.",
676
680
  "evolution.adopted": "Adopted",
677
681
  "evolution.openSkill": "Open skill",
@@ -3748,7 +3752,7 @@ window.__ModuleLoader__.load({
3748
3752
  className: css.skKicker,
3749
3753
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionTag, { action: suggestion.action }), suggestion.status !== "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusTag, { status: suggestion.status })]
3750
3754
  }),
3751
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: view.skill === void 0 ? suggestion.skillName : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: view.skill === void 0 || view.skillInCatalog === false ? suggestion.skillName : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3752
3756
  type: "button",
3753
3757
  className: css.skTitleLink,
3754
3758
  onClick: () => context.navigate({
@@ -3811,7 +3815,7 @@ window.__ModuleLoader__.load({
3811
3815
  suggestion.rationale !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Clamp, { text: suggestion.rationale }),
3812
3816
  suggestion.action === "update" && view.skill === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3813
3817
  className: css.skWarn,
3814
- children: t("evolution.skillGone")
3818
+ children: t(view.resolution === "origin-unavailable" ? "evolution.originUnavailable" : "evolution.skillGone")
3815
3819
  }),
3816
3820
  suggestion.action === "update" && view.skill !== void 0 && !view.hashCurrent && suggestion.status === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3817
3821
  className: css.skWarn,
@@ -3832,7 +3836,7 @@ window.__ModuleLoader__.load({
3832
3836
  children: [
3833
3837
  t("evolution.adopted"),
3834
3838
  " ",
3835
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3839
+ view.skillInCatalog !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3836
3840
  type: "button",
3837
3841
  className: css.skLink,
3838
3842
  onClick: () => context.navigate({
@@ -3843,9 +3847,16 @@ window.__ModuleLoader__.load({
3843
3847
  })
3844
3848
  ]
3845
3849
  }),
3846
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3850
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3847
3851
  className: css.skFolds,
3848
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Fold, {
3852
+ children: [view.skill !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Fold, {
3853
+ title: t("evolution.currentSkill"),
3854
+ meta: t(`root.${view.skill.root}`),
3855
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3856
+ className: css.skMuted,
3857
+ children: view.skill.path
3858
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillMarkdown, { body: view.skill.body })]
3859
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Fold, {
3849
3860
  title: t("evolution.evidence"),
3850
3861
  meta: t("evolution.evidenceMeta", {
3851
3862
  conversations: references.length,
@@ -3884,7 +3895,7 @@ window.__ModuleLoader__.load({
3884
3895
  })]
3885
3896
  }, index);
3886
3897
  })
3887
- })
3898
+ })]
3888
3899
  })
3889
3900
  ]
3890
3901
  });
@@ -3986,16 +3997,21 @@ window.__ModuleLoader__.load({
3986
3997
  });
3987
3998
  const started = await context.client.backtest({
3988
3999
  skillName: draft.skillName,
3989
- target: { kind: draft.action === "update" ? "current" : "none" }
4000
+ target: {
4001
+ kind: draft.action === "update" ? "current" : "none",
4002
+ ...draft.action === "update" ? { draftId: draft.id } : {}
4003
+ }
3990
4004
  });
3991
4005
  await context.client.linkRun(draft.id, started.runId);
3992
4006
  });
3993
4007
  const redraft = (note) => act(() => context.client.generateDraft({
3994
- ...draft.suggestionId === void 0 ? { skill: draft.skillName } : { suggestionId: draft.suggestionId },
4008
+ draftId: draft.id,
4009
+ ...draft.suggestionId === void 0 ? {} : { suggestionId: draft.suggestionId },
3995
4010
  guidance: note,
3996
4011
  ...route
3997
4012
  }), () => setRedrafting(null));
3998
4013
  const ready = draft.status === "ready";
4014
+ const originalAvailable = draft.action !== "update" || view.base !== void 0;
3999
4015
  const progress = (run) => run === void 0 ? 0 : run.progress.done;
4000
4016
  const total = (candidate?.progress.total ?? 0) + (baseline?.progress.total ?? 0);
4001
4017
  const results = /* @__PURE__ */ new Map();
@@ -4029,7 +4045,7 @@ window.__ModuleLoader__.load({
4029
4045
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4030
4046
  variant: "primary",
4031
4047
  size: "sm",
4032
- disabled: !context.writable || busy || editing !== null || running,
4048
+ disabled: !context.writable || busy || editing !== null || running || !originalAvailable,
4033
4049
  onClick: () => regressed ? setConfirmAdopt(true) : void adopt(),
4034
4050
  children: t("draft.adopt")
4035
4051
  }),
@@ -4047,13 +4063,17 @@ window.__ModuleLoader__.load({
4047
4063
  items: [{
4048
4064
  id: "edit",
4049
4065
  label: t("draft.edit")
4050
- }, ...context.client.canAssist("skill-draft") ? [{
4066
+ }, ...originalAvailable && context.client.canAssist("skill-draft") ? [{
4051
4067
  id: "redraft",
4052
4068
  label: t("draft.redraft")
4053
4069
  }] : []]
4054
4070
  })
4055
4071
  ] })]
4056
4072
  }),
4073
+ !originalAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4074
+ className: css.skWarn,
4075
+ children: t(view.resolution === "origin-unavailable" ? "evolution.originUnavailable" : "evolution.skillGone")
4076
+ }),
4057
4077
  draft.notes !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Clamp, { text: draft.notes }),
4058
4078
  draft.changes.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoreList, {
4059
4079
  items: draft.changes,
@@ -4089,7 +4109,7 @@ window.__ModuleLoader__.load({
4089
4109
  }), !running && (cases ?? 0) > 0 && context.client.canAssist("skill-backtest") && ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4090
4110
  variant: "ghost",
4091
4111
  size: "sm",
4092
- disabled: !context.writable || busy,
4112
+ disabled: !context.writable || busy || !originalAvailable,
4093
4113
  onClick: () => void backtest(),
4094
4114
  children: view.runs.length === 0 ? t("draft.runBacktest") : t("draft.rerun")
4095
4115
  })]
@@ -163,6 +163,13 @@ interface EvidenceReference {
163
163
  }
164
164
  type SuggestionStatus = 'pending' | 'drafting' | 'review' | 'adopted' | 'dismissed' | 'expired' | 'invalidated';
165
165
  declare const SUGGESTION_STATUSES: readonly SuggestionStatus[];
166
+ /** A file selected by the Source catalog, independent of the page's storage scope. */
167
+ interface SkillOrigin {
168
+ path: string;
169
+ /** The owning project root; absent for user skills. */
170
+ workspace?: string;
171
+ }
172
+ type SkillResolution = 'available' | 'missing' | 'origin-unavailable';
166
173
  interface SkillSuggestion {
167
174
  id: string;
168
175
  skillName: string;
@@ -177,6 +184,8 @@ interface SkillSuggestion {
177
184
  /** For `update`: the SKILL.md the reviewer read, so a later edit invalidates it. */
178
185
  skillKey?: string;
179
186
  skillFileHash?: string;
187
+ /** Absent on legacy records; never supplied by the model. */
188
+ origin?: SkillOrigin;
180
189
  sourceSessionId?: string;
181
190
  sourceSessionTitle?: string;
182
191
  reviewId?: string;
@@ -196,6 +205,8 @@ interface SkillDraft {
196
205
  /** For `update`, the SKILL.md hash the draft was written against. */
197
206
  baseKey?: string;
198
207
  baseHash?: string;
208
+ /** The file this update replaces; absent on legacy records. */
209
+ origin?: SkillOrigin;
199
210
  /** For `create`, where adopting writes the new bundle. */
200
211
  targetRoot?: SkillRootKind;
201
212
  content: string;
@@ -467,7 +478,10 @@ interface SuggestionView {
467
478
  suggestion: SkillSuggestion;
468
479
  /** The loaded file is still the one the reviewer read. */
469
480
  hashCurrent: boolean;
470
- skill?: SkillSummary;
481
+ skill?: SkillDocument;
482
+ resolution?: SkillResolution;
483
+ /** Whether the selected scope's catalog offers this same file for navigation. */
484
+ skillInCatalog?: boolean;
471
485
  draft?: SkillDraft;
472
486
  trajectories: TrajectorySummary[];
473
487
  }
@@ -475,6 +489,7 @@ interface DraftView {
475
489
  draft: SkillDraft;
476
490
  /** The file the draft replaces, as it is now. */
477
491
  base?: SkillDocument;
492
+ resolution?: SkillResolution;
478
493
  runs: BacktestRun[];
479
494
  }
480
495
  /** A string, or the same text per language. */
@@ -623,4 +638,4 @@ interface MarketInstall {
623
638
  installedAt: string;
624
639
  }
625
640
  //#endregion
626
- export { BacktestRun, CaseResult, CatalogView, DEFAULT_SKILLS_POLICY, type DetectedSource, DraftPreferences, DraftPreferencesView, DraftStatus, DraftView, EVOLUTION_SIGNALS, EvalCase, EvalCheck, EvidencePack, EvidenceReference, EvolutionSignal, LocalizedText, MarketAudit, MarketEntry, MarketInstall, MarketInstallState, MarketItem, MarketPreview, MarketRegistry, MarketRegistryFile, MarketSearchResult, MarketSource, type MarketSourceKind, MarketView, ModelRoute, ReviewLogEntry, ReviewRecordInput, ReviewRecordResult, ReviewScore, RunStatus, RunTarget, RunTargetKind, SKILL_ROOT_KINDS, SKILL_ROOT_RANK, SUGGESTION_STATUSES, SkillCatalog, SkillDocument, SkillDraft, SkillInvocation, SkillProblem, SkillRoot, SkillRootKind, SkillSuggestion, SkillSummary, SkillVersion, SkillVersionReason, SkillsOverview, SkillsPolicy, SuggestionStatus, SuggestionView, Trajectory, TrajectoryAnnotation, TrajectoryStats, TrajectoryStep, TrajectoryStepKind, TrajectorySummary, TrashEntry, TrashRestoreInput, TrashRestorePreview };
641
+ export { BacktestRun, CaseResult, CatalogView, DEFAULT_SKILLS_POLICY, type DetectedSource, DraftPreferences, DraftPreferencesView, DraftStatus, DraftView, EVOLUTION_SIGNALS, EvalCase, EvalCheck, EvidencePack, EvidenceReference, EvolutionSignal, LocalizedText, MarketAudit, MarketEntry, MarketInstall, MarketInstallState, MarketItem, MarketPreview, MarketRegistry, MarketRegistryFile, MarketSearchResult, MarketSource, type MarketSourceKind, MarketView, ModelRoute, ReviewLogEntry, ReviewRecordInput, ReviewRecordResult, ReviewScore, RunStatus, RunTarget, RunTargetKind, SKILL_ROOT_KINDS, SKILL_ROOT_RANK, SUGGESTION_STATUSES, SkillCatalog, SkillDocument, SkillDraft, SkillInvocation, SkillOrigin, SkillProblem, SkillResolution, SkillRoot, SkillRootKind, SkillSuggestion, SkillSummary, SkillVersion, SkillVersionReason, SkillsOverview, SkillsPolicy, SuggestionStatus, SuggestionView, Trajectory, TrajectoryAnnotation, TrajectoryStats, TrajectoryStep, TrajectoryStepKind, TrajectorySummary, TrashEntry, TrashRestoreInput, TrashRestorePreview };
package/lib/index.js CHANGED
@@ -368,6 +368,33 @@ function rootPath(catalog, kind) {
368
368
  if (root === void 0) throw new Error(kind.startsWith("project") ? "project skill roots need a selected workspace" : "unknown skill root: " + kind);
369
369
  return root.path;
370
370
  }
371
+ //#endregion
372
+ //#region src/identity.ts
373
+ function skillOrigin(catalog, skill) {
374
+ return {
375
+ path: skill.path,
376
+ ...skill.root.startsWith("project-") ? { workspace: catalog.projectRoot } : {}
377
+ };
378
+ }
379
+ function validOrigin(key, origin) {
380
+ return origin !== null && typeof origin === "object" && typeof origin.path === "string" && isAbsolute(origin.path) && (key.startsWith("project-") ? typeof origin.workspace === "string" && isAbsolute(origin.workspace) : origin.workspace === void 0);
381
+ }
382
+ /** Old reviews kept their cwd in the cited trajectory, even before origins existed. */
383
+ function legacySuggestionWorkspace(ledger, suggestion) {
384
+ if (!suggestion.skillKey?.startsWith("project-")) return void 0;
385
+ const cited = new Set(suggestion.evidence.map((reference) => reference.trajectoryId));
386
+ const trajectories = ledger.trajectories.filter((trajectory) => cited.has(trajectory.id) && (suggestion.reviewId === void 0 || trajectory.reviewId === suggestion.reviewId));
387
+ if (suggestion.reviewId === void 0 && trajectories.length !== cited.size) return void 0;
388
+ if (trajectories.length === 0 || trajectories.some((trajectory) => typeof trajectory.workspace !== "string" || !isAbsolute(trajectory.workspace))) return void 0;
389
+ const roots = new Set(trajectories.map((trajectory) => projectRoot(trajectory.workspace)));
390
+ return roots.size === 1 ? [...roots][0] : void 0;
391
+ }
392
+ /** A name alone, or identical contents in another project, is not an identity. */
393
+ function sameSuggestionSkill(ledger, suggestion, catalog, skill) {
394
+ if (suggestion.skillKey !== skill.key) return false;
395
+ if (suggestion.origin !== void 0) return validOrigin(skill.key, suggestion.origin) && resolve(suggestion.origin.path) === resolve(skill.path) && (!skill.root.startsWith("project-") || resolve(suggestion.origin.workspace) === catalog.projectRoot);
396
+ return !skill.root.startsWith("project-") || legacySuggestionWorkspace(ledger, suggestion) === catalog.projectRoot;
397
+ }
371
398
  function emptyLedger() {
372
399
  return {
373
400
  version: 1,
@@ -953,7 +980,9 @@ function admitReview(context, input) {
953
980
  });
954
981
  continue;
955
982
  }
956
- if (ledger.suggestions.find((suggestion) => suggestion.skillName === proposal.skillName && suggestion.action === proposal.action && suggestion.status === "dismissed" && suggestion.sourceSessionId === input.review.sessionId && now.getTime() - Date.parse(suggestion.updatedAt) < DISMISSED_COOLDOWN_MS) !== void 0) {
983
+ const skill = activeSkill(catalog, proposal.skillName);
984
+ const sameSkill = (suggestion) => suggestion.skillName === proposal.skillName && suggestion.action === proposal.action && (skill === void 0 || sameSuggestionSkill(ledger, suggestion, catalog, skill));
985
+ if (ledger.suggestions.find((suggestion) => sameSkill(suggestion) && suggestion.status === "dismissed" && suggestion.sourceSessionId === input.review.sessionId && now.getTime() - Date.parse(suggestion.updatedAt) < DISMISSED_COOLDOWN_MS) !== void 0) {
957
986
  skipped.push({
958
987
  skillName: proposal.skillName,
959
988
  action: proposal.action,
@@ -961,8 +990,7 @@ function admitReview(context, input) {
961
990
  });
962
991
  continue;
963
992
  }
964
- const skill = activeSkill(catalog, proposal.skillName);
965
- const open = ledger.suggestions.find((suggestion) => suggestion.skillName === proposal.skillName && suggestion.action === proposal.action && OPEN_STATUSES.has(suggestion.status));
993
+ const open = ledger.suggestions.find((suggestion) => sameSkill(suggestion) && OPEN_STATUSES.has(suggestion.status));
966
994
  if (open !== void 0) {
967
995
  open.evidence = [...open.evidence, evidence].slice(-12);
968
996
  open.signals = signals([...open.signals, ...proposal.signals]);
@@ -974,6 +1002,7 @@ function admitReview(context, input) {
974
1002
  if (skill !== void 0) {
975
1003
  open.skillKey = skill.key;
976
1004
  open.skillFileHash = skill.hash;
1005
+ open.origin = skillOrigin(catalog, skill);
977
1006
  }
978
1007
  updated++;
979
1008
  } else skipped.push({
@@ -997,7 +1026,8 @@ function admitReview(context, input) {
997
1026
  evidence: [evidence],
998
1027
  ...skill === void 0 ? {} : {
999
1028
  skillKey: skill.key,
1000
- skillFileHash: skill.hash
1029
+ skillFileHash: skill.hash,
1030
+ origin: skillOrigin(catalog, skill)
1001
1031
  },
1002
1032
  sourceSessionId: input.review.sessionId,
1003
1033
  ...input.review.sessionTitle === void 0 ? {} : { sourceSessionTitle: input.review.sessionTitle },
@@ -3073,6 +3103,28 @@ var SkillsService = class {
3073
3103
  catalog(workspace) {
3074
3104
  return readCatalog(this.options(workspace));
3075
3105
  }
3106
+ original(key, origin, legacyWorkspace) {
3107
+ if (key === void 0 || origin !== void 0 && !validOrigin(key, origin) || key.startsWith("project-") && origin === void 0 && legacyWorkspace === void 0) return { resolution: "origin-unavailable" };
3108
+ const catalog = this.catalog(origin?.workspace ?? legacyWorkspace);
3109
+ const skill = catalog.skills.find((candidate) => candidate.key === key && (origin === void 0 || resolve(candidate.path) === resolve(origin.path)));
3110
+ return skill === void 0 ? { resolution: "missing" } : {
3111
+ resolution: "available",
3112
+ skill,
3113
+ origin: skillOrigin(catalog, skill)
3114
+ };
3115
+ }
3116
+ suggestionOriginal(ledger, suggestion) {
3117
+ return this.original(suggestion.skillKey, suggestion.origin, suggestion.origin === void 0 ? legacySuggestionWorkspace(ledger, suggestion) : void 0);
3118
+ }
3119
+ draftOriginal(ledger, draft) {
3120
+ if (draft.origin !== void 0) return this.original(draft.baseKey, draft.origin);
3121
+ const suggestion = ledger.suggestions.find((candidate) => candidate.id === draft.suggestionId && candidate.skillKey === draft.baseKey);
3122
+ return suggestion === void 0 ? this.original(draft.baseKey, void 0) : this.suggestionOriginal(ledger, suggestion);
3123
+ }
3124
+ requireOriginal(original) {
3125
+ if (original.skill !== void 0) return original.skill;
3126
+ throw new Error(original.resolution === "origin-unavailable" ? "The original workspace was not recorded reliably. Open the skill in its original workspace and generate a new draft; the existing draft and evidence are kept." : "The original skill file was deleted, moved, or cannot be read. Restore it or open the skill at its new location and generate a new draft.");
3127
+ }
3076
3128
  catalogView(workspace) {
3077
3129
  const catalog = this.catalog(workspace);
3078
3130
  const ledger = this.store.ledger();
@@ -3170,13 +3222,19 @@ var SkillsService = class {
3170
3222
  expireSuggestions(ledger, this.clock());
3171
3223
  const suggestion = ledger.suggestions.find((candidate) => candidate.id === id);
3172
3224
  if (suggestion === void 0) throw new Error("suggestion not found: " + id);
3173
- const skill = activeSkill(this.catalog(workspace), suggestion.skillName);
3225
+ const original = suggestion.action === "update" ? this.suggestionOriginal(ledger, suggestion) : void 0;
3226
+ const catalog = this.catalog(workspace);
3227
+ const skill = original === void 0 ? activeSkill(catalog, suggestion.skillName) : original.skill;
3174
3228
  const draft = suggestion.draftId === void 0 ? void 0 : ledger.drafts.find((candidate) => candidate.id === suggestion.draftId);
3175
3229
  const cited = new Set(suggestion.evidence.map((reference) => reference.trajectoryId));
3176
3230
  return {
3177
3231
  suggestion,
3178
3232
  hashCurrent: suggestion.action === "create" ? skill === void 0 : skill !== void 0 && skill.hash === suggestion.skillFileHash,
3179
- ...skill === void 0 ? {} : { skill },
3233
+ ...original === void 0 ? {} : { resolution: original.resolution },
3234
+ ...skill === void 0 ? {} : {
3235
+ skill: readSkillDocument(skill),
3236
+ skillInCatalog: catalog.skills.some((candidate) => candidate.key === skill.key && candidate.path === skill.path)
3237
+ },
3180
3238
  ...draft === void 0 ? {} : { draft },
3181
3239
  trajectories: ledger.trajectories.filter((entry) => cited.has(entry.id))
3182
3240
  };
@@ -3205,16 +3263,19 @@ var SkillsService = class {
3205
3263
  return staleRun(run, this.clock().getTime());
3206
3264
  }
3207
3265
  drafts(filter = {}) {
3208
- return this.store.ledger().drafts.filter((draft) => (filter.suggestionId === void 0 || draft.suggestionId === filter.suggestionId) && (filter.skill === void 0 || draft.skillName === filter.skill));
3266
+ const ledger = this.store.ledger();
3267
+ return ledger.drafts.filter((draft) => (filter.suggestionId === void 0 || draft.suggestionId === filter.suggestionId) && (filter.skill === void 0 || draft.skillName === filter.skill) && (filter.path === void 0 || this.draftOriginal(ledger, draft).skill?.path === filter.path));
3209
3268
  }
3210
- draft(workspace, id) {
3211
- const draft = this.store.ledger().drafts.find((candidate) => candidate.id === id);
3269
+ draft(_workspace, id) {
3270
+ const ledger = this.store.ledger();
3271
+ const draft = ledger.drafts.find((candidate) => candidate.id === id);
3212
3272
  if (draft === void 0) throw new Error("draft not found: " + id);
3213
- const catalog = this.catalog(workspace);
3214
- const base = draft.baseKey === void 0 ? void 0 : catalog.skills.find((skill) => skill.key === draft.baseKey);
3273
+ const original = draft.action === "update" ? this.draftOriginal(ledger, draft) : void 0;
3274
+ const base = original?.skill;
3215
3275
  const now = this.clock().getTime();
3216
3276
  return {
3217
3277
  draft,
3278
+ ...original === void 0 ? {} : { resolution: original.resolution },
3218
3279
  ...base === void 0 ? {} : { base: readSkillDocument(base) },
3219
3280
  runs: draft.runIds.flatMap((id) => {
3220
3281
  const run = this.store.evals().runs.find((candidate) => candidate.id === id);
@@ -3231,11 +3292,14 @@ var SkillsService = class {
3231
3292
  const ledger = this.store.ledger();
3232
3293
  const evals = this.store.evals();
3233
3294
  const catalog = this.catalog(workspace);
3295
+ const prior = input.draftId === void 0 ? void 0 : ledger.drafts.find((candidate) => candidate.id === input.draftId);
3296
+ if (input.draftId !== void 0 && prior === void 0) throw new Error("draft not found: " + input.draftId);
3297
+ if (prior !== void 0 && input.suggestionId !== void 0 && prior.suggestionId !== input.suggestionId) throw new Error("draft is for another suggestion");
3234
3298
  const suggestion = input.suggestionId === void 0 ? void 0 : ledger.suggestions.find((candidate) => candidate.id === input.suggestionId);
3235
3299
  if (input.suggestionId !== void 0 && suggestion === void 0) throw new Error("suggestion not found: " + input.suggestionId);
3236
- const skillName = suggestion?.skillName ?? input.skill;
3300
+ const skillName = prior?.skillName ?? suggestion?.skillName ?? input.skill;
3237
3301
  if (skillName === void 0 || !isSkillName(skillName)) throw new Error("a skill name or a suggestion is required");
3238
- const loaded = activeSkill(catalog, skillName);
3302
+ const loaded = prior?.action === "update" ? this.requireOriginal(this.draftOriginal(ledger, prior)) : suggestion?.action === "update" ? this.requireOriginal(this.suggestionOriginal(ledger, suggestion)) : activeSkill(catalog, skillName);
3239
3303
  const action = loaded === void 0 ? "create" : "update";
3240
3304
  const references = suggestion?.evidence ?? ledger.trajectories.filter((entry) => entry.stats.skills.includes(skillName) || entry.suggestionIds.some((id) => ledger.suggestions.find((candidate) => candidate.id === id)?.skillName === skillName)).slice(0, 3).map((entry) => ({
3241
3305
  trajectoryId: entry.id,
@@ -3273,7 +3337,7 @@ var SkillsService = class {
3273
3337
  description: clip(skill.description, 200),
3274
3338
  root: skill.root
3275
3339
  })),
3276
- drafts: ledger.drafts.filter((draft) => draft.skillName === skillName).slice(0, 3)
3340
+ drafts: ledger.drafts.filter((draft) => draft.skillName === skillName && (loaded === void 0 ? draft.action === "create" : this.draftOriginal(ledger, draft).skill?.path === loaded.path)).slice(0, 3)
3277
3341
  };
3278
3342
  }
3279
3343
  /** Bump the ledger generation, so the revision moves with every write. */
@@ -3721,11 +3785,18 @@ var SkillsService = class {
3721
3785
  return this.write(() => {
3722
3786
  validateSkillContent(input.content, input.skillName);
3723
3787
  const ledger = this.store.ledger();
3724
- const loaded = activeSkill(this.catalog(workspace), input.skillName);
3788
+ const catalog = this.catalog(workspace);
3725
3789
  const now = this.clock().toISOString();
3790
+ const prior = input.draftId === void 0 ? void 0 : ledger.drafts.find((candidate) => candidate.id === input.draftId);
3791
+ if (input.draftId !== void 0 && prior === void 0) throw new Error("draft not found: " + input.draftId);
3792
+ if (prior !== void 0 && (prior.skillName !== input.skillName || prior.suggestionId !== input.suggestionId)) throw new Error("draft is for another skill or suggestion");
3726
3793
  const suggestion = input.suggestionId === void 0 ? void 0 : ledger.suggestions.find((candidate) => candidate.id === input.suggestionId);
3727
3794
  if (input.suggestionId !== void 0 && suggestion === void 0) throw new Error("suggestion not found: " + input.suggestionId);
3728
- for (const draft of ledger.drafts) if (draft.status === "ready" && draft.skillName === input.skillName && draft.suggestionId === input.suggestionId) {
3795
+ if (suggestion !== void 0 && suggestion.skillName !== input.skillName) throw new Error("suggestion is for another skill");
3796
+ const original = prior?.action === "update" ? this.draftOriginal(ledger, prior) : suggestion?.action === "update" ? this.suggestionOriginal(ledger, suggestion) : void 0;
3797
+ const loaded = original === void 0 ? activeSkill(catalog, input.skillName) : this.requireOriginal(original);
3798
+ const origin = loaded === void 0 ? void 0 : original?.origin ?? skillOrigin(catalog, loaded);
3799
+ for (const draft of ledger.drafts) if (draft.status === "ready" && draft.skillName === input.skillName && draft.suggestionId === input.suggestionId && (loaded === void 0 ? draft.action === "create" : this.draftOriginal(ledger, draft).skill?.path === loaded.path)) {
3729
3800
  draft.status = "discarded";
3730
3801
  draft.updatedAt = now;
3731
3802
  }
@@ -3736,7 +3807,8 @@ var SkillsService = class {
3736
3807
  action: loaded === void 0 ? "create" : "update",
3737
3808
  ...loaded === void 0 ? { targetRoot: input.targetRoot ?? suggestion?.targetRoot ?? DEFAULT_ROOT } : {
3738
3809
  baseKey: loaded.key,
3739
- baseHash: loaded.hash
3810
+ baseHash: loaded.hash,
3811
+ origin
3740
3812
  },
3741
3813
  content: input.content,
3742
3814
  notes: clip(input.notes, 2e3),
@@ -3780,12 +3852,14 @@ var SkillsService = class {
3780
3852
  const catalog = this.catalog(workspace);
3781
3853
  let key;
3782
3854
  let previous;
3855
+ let updated;
3783
3856
  if (draft.action === "update") {
3784
- const base = catalog.skills.find((skill) => skill.key === draft.baseKey);
3785
- if (base === void 0) throw new Error(`${draft.skillName} is no longer in ${draft.baseKey?.split(":")[0]}; draft it again`);
3857
+ const original = this.draftOriginal(ledger, draft);
3858
+ const base = this.requireOriginal(original);
3786
3859
  previous = this.requireHash(base, draft.baseHash);
3787
3860
  writeFileAtomic(base.path, draft.content);
3788
3861
  key = base.key;
3862
+ updated = findSkill(this.catalog(original.origin?.workspace), key);
3789
3863
  } else {
3790
3864
  const root = input.root ?? draft.targetRoot ?? DEFAULT_ROOT;
3791
3865
  if (activeSkill(catalog, draft.skillName) !== void 0) throw new Error(`${draft.skillName} exists now; draft it again as an update`);
@@ -3814,7 +3888,7 @@ var SkillsService = class {
3814
3888
  }
3815
3889
  this.store.saveLedger(ledger);
3816
3890
  return {
3817
- skill: findSkill(this.catalog(workspace), key),
3891
+ skill: updated ?? findSkill(this.catalog(workspace), key),
3818
3892
  version
3819
3893
  };
3820
3894
  });
@@ -3899,7 +3973,9 @@ var SkillsService = class {
3899
3973
  }
3900
3974
  };
3901
3975
  }
3902
- const loaded = activeSkill(this.catalog(workspace), skillName);
3976
+ const draft = target.draftId === void 0 ? void 0 : this.store.ledger().drafts.find((candidate) => candidate.id === target.draftId && candidate.skillName === skillName);
3977
+ if (target.draftId !== void 0 && draft === void 0) throw new Error("draft not found for this skill");
3978
+ const loaded = draft === void 0 ? activeSkill(this.catalog(workspace), skillName) : this.requireOriginal(this.draftOriginal(this.store.ledger(), draft));
3903
3979
  if (loaded === void 0) throw new Error(`${skillName} has no file to test; test a draft or no skill`);
3904
3980
  const document = readSkillDocument(loaded);
3905
3981
  return {
@@ -3907,7 +3983,8 @@ var SkillsService = class {
3907
3983
  target: {
3908
3984
  kind: "current",
3909
3985
  skillKey: loaded.key,
3910
- hash: document.hash
3986
+ hash: document.hash,
3987
+ ...draft === void 0 ? {} : { draftId: draft.id }
3911
3988
  }
3912
3989
  };
3913
3990
  }
@@ -4754,7 +4831,8 @@ function createSkillsMemorySource(config = {}) {
4754
4831
  case "run": return service.run(memoryInputText(fields.id, "id", 100));
4755
4832
  case "drafts": return service.drafts({
4756
4833
  ...optionalText(fields.suggestionId, "suggestionId", 100) === void 0 ? {} : { suggestionId: optionalText(fields.suggestionId, "suggestionId", 100) },
4757
- ...optionalText(fields.skill, "skill", 64) === void 0 ? {} : { skill: optionalText(fields.skill, "skill", 64) }
4834
+ ...optionalText(fields.skill, "skill", 64) === void 0 ? {} : { skill: optionalText(fields.skill, "skill", 64) },
4835
+ ...optionalText(fields.path, "path", 4096) === void 0 ? {} : { path: optionalText(fields.path, "path", 4096) }
4758
4836
  });
4759
4837
  case "draft": return service.draft(root, memoryInputText(fields.id, "id", 100));
4760
4838
  case "draft-preferences": return service.draftPreferences();
@@ -4764,6 +4842,7 @@ function createSkillsMemorySource(config = {}) {
4764
4842
  });
4765
4843
  case "evidence-pack": return service.evidencePack(root, {
4766
4844
  ...optionalText(fields.suggestionId, "suggestionId", 100) === void 0 ? {} : { suggestionId: optionalText(fields.suggestionId, "suggestionId", 100) },
4845
+ ...optionalText(fields.draftId, "draftId", 100) === void 0 ? {} : { draftId: optionalText(fields.draftId, "draftId", 100) },
4767
4846
  ...optionalText(fields.skill, "skill", 64) === void 0 ? {} : { skill: optionalText(fields.skill, "skill", 64) }
4768
4847
  });
4769
4848
  case "target-content": return service.targetContent(root, memoryInputText(fields.skillName, "skillName", 64), runTarget(fields.target));
@@ -4884,6 +4963,7 @@ function createSkillsMemorySource(config = {}) {
4884
4963
  case "draft-record":
4885
4964
  value = await service.recordDraft(root, {
4886
4965
  ...optionalText(fields.suggestionId, "suggestionId", 100) === void 0 ? {} : { suggestionId: optionalText(fields.suggestionId, "suggestionId", 100) },
4966
+ ...optionalText(fields.draftId, "draftId", 100) === void 0 ? {} : { draftId: optionalText(fields.draftId, "draftId", 100) },
4887
4967
  skillName: memoryInputText(fields.skillName, "skillName", 64),
4888
4968
  content: typeof fields.content === "string" ? fields.content : "",
4889
4969
  notes: typeof fields.notes === "string" ? fields.notes : "",
@@ -42,6 +42,7 @@ export interface SkillsPageClient {
42
42
  }): Promise<ReviewLogEntry[]>;
43
43
  evidencePack(input: {
44
44
  suggestionId?: string;
45
+ draftId?: string;
45
46
  skill?: string;
46
47
  }): Promise<EvidencePack>;
47
48
  createSkill(input: {
@@ -135,6 +136,7 @@ export interface SkillsPageClient {
135
136
  canAssist(operation: 'skill-draft' | 'skill-backtest' | 'skill-derive-cases' | 'skill-review-now'): boolean;
136
137
  generateDraft(input: {
137
138
  suggestionId?: string;
139
+ draftId?: string;
138
140
  skill?: string;
139
141
  guidance?: string;
140
142
  targetRoot?: SkillRootKind;
@@ -145,6 +145,13 @@ export interface EvidenceReference {
145
145
  }
146
146
  export type SuggestionStatus = 'pending' | 'drafting' | 'review' | 'adopted' | 'dismissed' | 'expired' | 'invalidated';
147
147
  export declare const SUGGESTION_STATUSES: readonly SuggestionStatus[];
148
+ /** A file selected by the Source catalog, independent of the page's storage scope. */
149
+ export interface SkillOrigin {
150
+ path: string;
151
+ /** The owning project root; absent for user skills. */
152
+ workspace?: string;
153
+ }
154
+ export type SkillResolution = 'available' | 'missing' | 'origin-unavailable';
148
155
  export interface SkillSuggestion {
149
156
  id: string;
150
157
  skillName: string;
@@ -159,6 +166,8 @@ export interface SkillSuggestion {
159
166
  /** For `update`: the SKILL.md the reviewer read, so a later edit invalidates it. */
160
167
  skillKey?: string;
161
168
  skillFileHash?: string;
169
+ /** Absent on legacy records; never supplied by the model. */
170
+ origin?: SkillOrigin;
162
171
  sourceSessionId?: string;
163
172
  sourceSessionTitle?: string;
164
173
  reviewId?: string;
@@ -178,6 +187,8 @@ export interface SkillDraft {
178
187
  /** For `update`, the SKILL.md hash the draft was written against. */
179
188
  baseKey?: string;
180
189
  baseHash?: string;
190
+ /** The file this update replaces; absent on legacy records. */
191
+ origin?: SkillOrigin;
181
192
  /** For `create`, where adopting writes the new bundle. */
182
193
  targetRoot?: SkillRootKind;
183
194
  content: string;
@@ -449,7 +460,10 @@ export interface SuggestionView {
449
460
  suggestion: SkillSuggestion;
450
461
  /** The loaded file is still the one the reviewer read. */
451
462
  hashCurrent: boolean;
452
- skill?: SkillSummary;
463
+ skill?: SkillDocument;
464
+ resolution?: SkillResolution;
465
+ /** Whether the selected scope's catalog offers this same file for navigation. */
466
+ skillInCatalog?: boolean;
453
467
  draft?: SkillDraft;
454
468
  trajectories: TrajectorySummary[];
455
469
  }
@@ -457,6 +471,7 @@ export interface DraftView {
457
471
  draft: SkillDraft;
458
472
  /** The file the draft replaces, as it is now. */
459
473
  base?: SkillDocument;
474
+ resolution?: SkillResolution;
460
475
  runs: BacktestRun[];
461
476
  }
462
477
  export type { DetectedSource, MarketSourceKind } from './source-kind.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mnemon-source-skills",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
4
4
  "description": "Skills Source for Composable View Memory: DSH skill management, evolution suggestions, evidence trajectories and eval sets.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -62,7 +62,7 @@
62
62
  "@types/node": "^22.0.0",
63
63
  "@types/react": "^18.3.0",
64
64
  "@types/react-dom": "^18.3.0",
65
- "dsh-mnemon": "^0.5.41",
65
+ "dsh-mnemon": "^0.5.42",
66
66
  "jsdom": "^26.0.0",
67
67
  "react": "^18.2.0",
68
68
  "react-dom": "^18.2.0",
@@ -135,7 +135,9 @@
135
135
  "evolution.noAgent": "当前没有可用的任务 Agent",
136
136
  "evolution.drafting": "正在生成草案",
137
137
  "evolution.draftFailed": "草案生成失败:{error}",
138
- "evolution.skillGone": "这个技能已不存在。",
138
+ "evolution.skillGone": "原技能文件已删除、移动或无法读取。请恢复文件,或在新位置打开技能并重新生成草案。",
139
+ "evolution.originUnavailable": "旧记录未可靠保存原工作区。请切换到原工作区,在技能页打开该技能并重新生成草案;现有草案和证据会保留。",
140
+ "evolution.currentSkill": "当前技能文件",
139
141
  "evolution.skillChanged": "技能在建议之后被改过,生成草案前会以当前版本为准。",
140
142
  "evolution.adopted": "已采纳",
141
143
  "evolution.openSkill": "查看技能",
@@ -489,7 +491,9 @@
489
491
  "evolution.noAgent": "No task Agent is available",
490
492
  "evolution.drafting": "Drafting",
491
493
  "evolution.draftFailed": "Drafting failed: {error}",
492
- "evolution.skillGone": "This skill no longer exists.",
494
+ "evolution.skillGone": "The original skill file was deleted, moved, or cannot be read. Restore it or open the skill at its new location and generate a new draft.",
495
+ "evolution.originUnavailable": "The original workspace was not recorded reliably. Select its original workspace, open the skill on the Skills page and generate a new draft; the existing draft and evidence are kept.",
496
+ "evolution.currentSkill": "Current skill file",
493
497
  "evolution.skillChanged": "The skill changed after this suggestion; the draft starts from its current version.",
494
498
  "evolution.adopted": "Adopted",
495
499
  "evolution.openSkill": "Open skill",