dsh-rules 0.1.0 → 0.1.1

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.
Files changed (2) hide show
  1. package/lib/index.js +33 -5
  2. package/package.json +22 -6
package/lib/index.js CHANGED
@@ -68,6 +68,7 @@ const Config = z.object({
68
68
  */
69
69
  function apply(ctx, config = {}) {
70
70
  const resolved = resolveConfig(config);
71
+ reportIgnoredPathEntries(ctx, config, resolved);
71
72
  const state = new RulesState(ctx, resolved);
72
73
  ctx.on("fs/observed", (target, observation, actor) => {
73
74
  if (observation === void 0 || observation.kind === "absent") return;
@@ -106,19 +107,46 @@ function resolveConfig(config) {
106
107
  return {
107
108
  dshHome: resolveDshHome(config.dshHome),
108
109
  projectRootMarkers: config.projectRootMarkers ?? [...DEFAULT_PROJECT_ROOT_MARKERS],
109
- ruleDirNames: cleanPathSegments(config.ruleDirNames, DEFAULT_RULE_DIR_NAMES),
110
+ ruleDirNames: cleanRelativePaths(config.ruleDirNames, DEFAULT_RULE_DIR_NAMES),
110
111
  includeUserRules: config.includeUserRules ?? true,
111
112
  includeClaudeSections: config.includeClaudeSections ?? false,
112
- instructionFileCandidates: cleanPathSegments(config.instructionFileCandidates, DEFAULT_INSTRUCTION_FILE_CANDIDATES),
113
- localInstructionFileCandidates: cleanPathSegments(config.localInstructionFileCandidates, DEFAULT_LOCAL_INSTRUCTION_FILE_CANDIDATES),
113
+ instructionFileCandidates: cleanRelativePaths(config.instructionFileCandidates, DEFAULT_INSTRUCTION_FILE_CANDIDATES),
114
+ localInstructionFileCandidates: cleanRelativePaths(config.localInstructionFileCandidates, DEFAULT_LOCAL_INSTRUCTION_FILE_CANDIDATES),
114
115
  maxBytes: config.maxBytes ?? DEFAULT_MAX_BYTES,
115
116
  maxSourceBytes: config.maxSourceBytes ?? DEFAULT_MAX_SOURCE_BYTES,
116
117
  maxTouchedPaths: config.maxTouchedPaths ?? DEFAULT_MAX_TOUCHED_PATHS
117
118
  };
118
119
  }
119
120
 
120
- function cleanPathSegments(candidates, fallback) {
121
- return (candidates ?? [...fallback]).filter((candidate) => !RESERVED_PATH_SEGMENTS.has(candidate) && !/[\\/]/.test(candidate));
121
+ /**
122
+ * Whether one configured path entry can be safely joined under a known
123
+ * directory: every separator-delimited segment must be non-empty and none of
124
+ * "", ".", "..". Multi-segment relative paths such as the default
125
+ * ".dsh/rules" are expected; absolute paths and Windows drive prefixes are
126
+ * rejected.
127
+ */
128
+ function isSafeRelativePath(candidate) {
129
+ if (typeof candidate !== "string" || candidate.length === 0) return false;
130
+ if (/^[A-Za-z]:/.test(candidate)) return false;
131
+ return candidate.split(/[\\/]/).every((segment) => !RESERVED_PATH_SEGMENTS.has(segment));
132
+ }
133
+
134
+ function cleanRelativePaths(candidates, fallback) {
135
+ return (candidates ?? [...fallback]).filter(isSafeRelativePath);
136
+ }
137
+
138
+ /** Config fields holding relative path entry lists (see {@link cleanRelativePaths}). */
139
+ const PATH_LIST_FIELDS = ["ruleDirNames", "instructionFileCandidates", "localInstructionFileCandidates"];
140
+
141
+ /** Warn when configured path entries were dropped as unsafe, instead of failing silently. */
142
+ function reportIgnoredPathEntries(ctx, config, resolved) {
143
+ for (const field of PATH_LIST_FIELDS) {
144
+ const configured = config[field];
145
+ if (!Array.isArray(configured)) continue;
146
+ const dropped = configured.length - resolved[field].length;
147
+ if (dropped <= 0) continue;
148
+ ctx.logger.warn(`dsh-rules: config.${field}: ignored ${dropped} unsafe ${dropped === 1 ? "entry" : "entries"} (expected project-relative paths such as ".dsh/rules"; ".", "..", empty, or absolute paths are ignored)`);
149
+ }
122
150
  }
123
151
 
124
152
  /** Per-process rules state: versioned rule caches and per-session touch sets. */
package/package.json CHANGED
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "name": "dsh-rules",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "DSH rules plugin: activate rule prompts / markdown documents by glob-matching the files an agent reads or edits (Claude Code rules.md style).",
5
+ "keywords": [
6
+ "dsh",
7
+ "dsh-plugin",
8
+ "deepseek-harness",
9
+ "rules",
10
+ "claude-code",
11
+ "agents",
12
+ "prompt"
13
+ ],
5
14
  "license": "MIT",
6
15
  "type": "module",
7
16
  "main": "lib/index.js",
@@ -29,12 +38,14 @@
29
38
  }
30
39
  },
31
40
  "dependencies": {
41
+ "picomatch": "^4.0.2",
42
+ "yaml": "^2.9.0"
43
+ },
44
+ "peerDependencies": {
32
45
  "@deepseek-ai/cordis": "^4.0.1",
33
46
  "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6",
34
47
  "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
35
- "@deepseek-ai/schemastery": "^3.18.1",
36
- "picomatch": "^4.0.2",
37
- "yaml": "^2.9.0"
48
+ "@deepseek-ai/schemastery": "^3.18.1"
38
49
  },
39
50
  "devDependencies": {
40
51
  "@deepseek-ai/cordis": "^4.0.1",
@@ -45,9 +56,14 @@
45
56
  "yaml": "^2.9.0"
46
57
  },
47
58
  "scripts": {
48
- "test": "node --test"
59
+ "test": "node --test",
60
+ "prepublishOnly": "pnpm test"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
49
64
  },
50
65
  "engines": {
51
66
  "node": "^22.19.0 || >=24.0.0"
52
- }
67
+ },
68
+ "packageManager": "pnpm@11.7.0"
53
69
  }