@lmzhen/dsh-evolution-core 0.3.46 → 0.3.47

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 CHANGED
@@ -858,8 +858,9 @@ function parseCuratorNominations(text) {
858
858
  * view so the two can never disagree: records failing ANY of these gates are
859
859
  * outside the managed scope.
860
860
  */
861
- function lifecycleCandidate(name, record, config, bundled, gates = createGateSet(config)) {
861
+ function lifecycleCandidate(name, record, config, bundled, gates = createGateSet(config), protectedNames) {
862
862
  if (record.pinned) return false;
863
+ if (protectedNames?.has(name) === true) return false;
863
864
  if (gates.isBlocked(name)) return false;
864
865
  if (!(record.created_by === "agent" || config.manageUnmanaged === true) && !(config.pruneBuiltins === true && bundled)) return false;
865
866
  if (record.state === "archived") return false;
@@ -887,7 +888,7 @@ function computeScopeView(usage, config, protectedNames, gates) {
887
888
  const suppressed = gateSet.suppressed.has(name);
888
889
  const isBuiltin = PROTECTED_BUILTIN_SKILLS.has(name);
889
890
  if (record.pinned || bundled || suppressed || protectedNames?.has(name) === true || isBuiltin) protectedSet.add(name);
890
- if (lifecycleCandidate(name, record, config, bundled, gateSet)) {
891
+ if (lifecycleCandidate(name, record, config, bundled, gateSet, protectedNames)) {
891
892
  managed.push(name);
892
893
  if (record.state === "stale" || record.quality_warn === true) watched.push(name);
893
894
  if (record.quality_warn === true) qualityWarned.push(name);
@@ -904,7 +905,7 @@ function computeScopeView(usage, config, protectedNames, gates) {
904
905
  function daysSince(iso, created, now) {
905
906
  return (now - new Date(iso ?? created).getTime()) / 864e5;
906
907
  }
907
- function computeLifecycleTransitions(usage, config, now = /* @__PURE__ */ new Date(), gates) {
908
+ function computeLifecycleTransitions(usage, config, now = /* @__PURE__ */ new Date(), gates, protectedNames) {
908
909
  const result = {
909
910
  transitions: [],
910
911
  archive: [],
@@ -913,7 +914,7 @@ function computeLifecycleTransitions(usage, config, now = /* @__PURE__ */ new Da
913
914
  };
914
915
  const gateSet = gates ?? createGateSet(config);
915
916
  for (const [name, record] of usage) {
916
- if (!lifecycleCandidate(name, record, config, config.bundledNames?.has(name) === true, gateSet)) continue;
917
+ if (!lifecycleCandidate(name, record, config, config.bundledNames?.has(name) === true, gateSet, protectedNames)) continue;
917
918
  const age = daysSince(null, record.created_at, now.getTime());
918
919
  if (record.use_count === 0 && age < config.staleAfterDays) continue;
919
920
  const idle = daysSince(latestActivityAt(record), record.created_at, now.getTime());
@@ -1770,19 +1771,19 @@ const PATTERNS = [
1770
1771
  label: "exfil_curl",
1771
1772
  category: "exfiltration",
1772
1773
  scope: "all",
1773
- regex: /curl\s+[^\n]{0,512}\$\{?\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i
1774
+ regex: /\bcurl\s+[^\n]{0,512}\$\{?\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i
1774
1775
  },
1775
1776
  {
1776
1777
  label: "exfil_wget",
1777
1778
  category: "exfiltration",
1778
1779
  scope: "all",
1779
- regex: /wget\s+[^\n]{0,512}\$\{?\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i
1780
+ regex: /\bwget\s+[^\n]{0,512}\$\{?\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i
1780
1781
  },
1781
1782
  {
1782
1783
  label: "read_secrets",
1783
1784
  category: "exfiltration",
1784
1785
  scope: "all",
1785
- regex: /cat\s+[^\n]{0,512}(?:\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i
1786
+ regex: /\bcat\s+[^\n]{0,512}(?:\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i
1786
1787
  },
1787
1788
  {
1788
1789
  label: "ssh_backdoor",
@@ -2158,7 +2159,7 @@ var MemoryStore = class {
2158
2159
  },
2159
2160
  write: null
2160
2161
  };
2161
- if (hasEntryDelimiter(content)) return {
2162
+ if (hasEntryDelimiter(this.addDatePrefix ? `## ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}\n${content}` : content)) return {
2162
2163
  result: {
2163
2164
  ok: false,
2164
2165
  message: "Fact contains the entry delimiter (§) and would split into multiple entries; rewrite it as separate facts.",
@@ -2273,7 +2274,8 @@ var MemoryStore = class {
2273
2274
  },
2274
2275
  write: null
2275
2276
  };
2276
- if (hasEntryDelimiter(body)) return {
2277
+ const entryBody = this.addDatePrefix ? `## ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}\n${body}` : body;
2278
+ if (hasEntryDelimiter(entryBody)) return {
2277
2279
  result: {
2278
2280
  ok: false,
2279
2281
  message: `Operation ${position} (add): Fact contains the entry delimiter (§) and would split into multiple entries; rewrite it as separate facts.${previewEntries(entries)}`,
@@ -2283,7 +2285,7 @@ var MemoryStore = class {
2283
2285
  },
2284
2286
  write: null
2285
2287
  };
2286
- if (!working.some((entry) => stripDatePrefix(entry) === body)) working.push(this.addDatePrefix ? `## ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}\n${body}` : body);
2288
+ if (!working.some((entry) => stripDatePrefix(entry) === body)) working.push(entryBody);
2287
2289
  continue;
2288
2290
  }
2289
2291
  const rawAction = op.action;
@@ -2695,7 +2697,7 @@ function computePrefixClusters(names) {
2695
2697
  * Migrated from `evolution-review` so both channels share one implementation.
2696
2698
  */
2697
2699
  const SECRET_PATTERNS = [
2698
- ["openai-style key", /sk-[A-Za-z0-9_-]{16,}/g],
2700
+ ["openai-style key", /\bsk-[A-Za-z0-9_-]{16,}/g],
2699
2701
  ["aws access key", /AKIA[0-9A-Z]{16}/g],
2700
2702
  ["github token", /gh[pousr]_[A-Za-z0-9]{20,}/g],
2701
2703
  ["gitlab token", /glpat-[A-Za-z0-9_-]{16,}/g],
@@ -3042,8 +3044,13 @@ const DEFAULT_SKILL_LIMITS = {
3042
3044
  };
3043
3045
  /** Upper bound of moves per restructure proposal (validator and core agree). */
3044
3046
  const MAX_RESTRUCTURE_MOVES = 5;
3045
- /** Restructure targets are plain markdown files under references/ — no subdirectories, no other support kind. */
3046
- const RESTRUCTURE_TARGET_RE = /^references\/[a-z0-9][a-z0-9._-]*\.md$/;
3047
+ /** Restructure targets are plain markdown files under references/ — no
3048
+ * subdirectories, no other support kind. V8-10 (0.3.47): the regex-level
3049
+ * `(?!.*\.\.)` keeps the restructure-created set EXACTLY the set
3050
+ * validateSupportPath can reopen — a `references/my..notes.md` target (double
3051
+ * dots) used to pass here while every later patch/write/remove on it was
3052
+ * refused as traversal (an orphan file the user could not touch). */
3053
+ const RESTRUCTURE_TARGET_RE = /^references\/[a-z0-9](?!.*\.\.)[a-z0-9._-]*\.md$/;
3047
3054
  /** Extra file name carried inside a snapshot's `extras/` directory. */
3048
3055
  const SNAPSHOT_EXTRA_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;
3049
3056
  function skillsRoot(env = process.env) {
@@ -4845,11 +4852,14 @@ var SkillLibrary = class {
4845
4852
  */
4846
4853
  /**
4847
4854
  * DSH home root: `$DSH_HOME` or `~/.dsh`. Single source of the empty-string
4848
- * fallback (`||`, not `??`) — an EMPTY DSH_HOME resolves to the default home,
4849
- * never to a CWD-relative path (0.3.19 W1.3, 0.3.22 F-207).
4855
+ * fallback — an EMPTY or WHITESPACE-ONLY DSH_HOME resolves to the default
4856
+ * home, never to a CWD-relative path (0.3.19 W1.3, 0.3.22 F-207);
4857
+ * V8-06 (0.3.47) extends the guard to whitespace (upstream home-paths:
4858
+ * `trim().length > 0` is the adoption test — `DSH_HOME=" "` must not produce
4859
+ * a sidecar under a relative "." path).
4850
4860
  */
4851
4861
  function evolutionRoot(env = process.env) {
4852
- return env.DSH_HOME || join(homedir(), ".dsh");
4862
+ return env.DSH_HOME?.trim() ? env.DSH_HOME : join(homedir(), ".dsh");
4853
4863
  }
4854
4864
  /** Evolution home path helper: `$DSH_HOME/evolution` for plugin-owned sidecar
4855
4865
  * state (reports, activity store, feedback file, state-domain data). */
@@ -117,7 +117,7 @@ export declare function parseCuratorNominations(text: string): CuratorNomination
117
117
  * view so the two can never disagree: records failing ANY of these gates are
118
118
  * outside the managed scope.
119
119
  */
120
- export declare function lifecycleCandidate(name: string, record: UsageRecord, config: CuratorConfig, bundled: boolean, gates?: EvolutionGateSet): boolean;
120
+ export declare function lifecycleCandidate(name: string, record: UsageRecord, config: CuratorConfig, bundled: boolean, gates?: EvolutionGateSet, protectedNames?: ReadonlyMap<string, string>): boolean;
121
121
  export interface ScopeView {
122
122
  /** Skills inside the lifecycle scope right now (candidate gate + active state). */
123
123
  managed: string[];
@@ -137,5 +137,5 @@ export interface ScopeView {
137
137
  * records lack (bundled / hub-installed / pinned from `SkillLibrary.list()`).
138
138
  */
139
139
  export declare function computeScopeView(usage: UsageMap, config: CuratorConfig, protectedNames?: ReadonlyMap<string, string>, gates?: EvolutionGateSet): ScopeView;
140
- export declare function computeLifecycleTransitions(usage: UsageMap, config: CuratorConfig, now?: Date, gates?: EvolutionGateSet): CuratorResult;
140
+ export declare function computeLifecycleTransitions(usage: UsageMap, config: CuratorConfig, now?: Date, gates?: EvolutionGateSet, protectedNames?: ReadonlyMap<string, string>): CuratorResult;
141
141
  //# sourceMappingURL=curator.d.ts.map
@@ -50,7 +50,12 @@ export interface SkillRestructureMove {
50
50
  }
51
51
  /** Upper bound of moves per restructure proposal (validator and core agree). */
52
52
  export declare const MAX_RESTRUCTURE_MOVES = 5;
53
- /** Restructure targets are plain markdown files under references/ — no subdirectories, no other support kind. */
53
+ /** Restructure targets are plain markdown files under references/ — no
54
+ * subdirectories, no other support kind. V8-10 (0.3.47): the regex-level
55
+ * `(?!.*\.\.)` keeps the restructure-created set EXACTLY the set
56
+ * validateSupportPath can reopen — a `references/my..notes.md` target (double
57
+ * dots) used to pass here while every later patch/write/remove on it was
58
+ * refused as traversal (an orphan file the user could not touch). */
54
59
  export declare const RESTRUCTURE_TARGET_RE: RegExp;
55
60
  /** Extra file name carried inside a snapshot's `extras/` directory. */
56
61
  export declare const SNAPSHOT_EXTRA_NAME_RE: RegExp;
@@ -4,8 +4,11 @@
4
4
  */
5
5
  /**
6
6
  * DSH home root: `$DSH_HOME` or `~/.dsh`. Single source of the empty-string
7
- * fallback (`||`, not `??`) — an EMPTY DSH_HOME resolves to the default home,
8
- * never to a CWD-relative path (0.3.19 W1.3, 0.3.22 F-207).
7
+ * fallback — an EMPTY or WHITESPACE-ONLY DSH_HOME resolves to the default
8
+ * home, never to a CWD-relative path (0.3.19 W1.3, 0.3.22 F-207);
9
+ * V8-06 (0.3.47) extends the guard to whitespace (upstream home-paths:
10
+ * `trim().length > 0` is the adoption test — `DSH_HOME=" "` must not produce
11
+ * a sidecar under a relative "." path).
9
12
  */
10
13
  export declare function evolutionRoot(env?: NodeJS.ProcessEnv): string;
11
14
  /** Evolution home path helper: `$DSH_HOME/evolution` for plugin-owned sidecar
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-core",
3
3
  "description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
4
- "version": "0.3.46",
4
+ "version": "0.3.47",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },