@mengyuly/dsh-ponytail 0.1.2 → 0.1.4
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/CHANGELOG.md +95 -0
- package/README.md +33 -5
- package/lib/index.js +201 -49
- package/lib/invariant.js +3 -3
- package/lib/types/content.d.ts +2 -3
- package/lib/types/index.d.ts +1 -2
- package/lib/types/instructions.d.ts +1 -2
- package/lib/types/invariant.d.ts +2 -3
- package/lib/types/modes.d.ts +42 -6
- package/package.json +9 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@mengyuly/dsh-ponytail` are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## [0.1.4] - 2026-08-24
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- 修复运行时 JS 与 TypeScript declarations 不一致:v0.1.3 只同步了
|
|
11
|
+
`lib/index.js`/`lib/invariant.js`,声明文件停留在旧版本。
|
|
12
|
+
- `compileSubagentMatcher` 声明恢复正确(`{ matcher, invalid }`),与运行时一致。
|
|
13
|
+
- 补齐 `readDefaultModeInfo`、`sessionKey` 及诊断类型
|
|
14
|
+
(`DefaultModeIssueKind` / `DefaultModeIssue` / `DefaultModeResolution`)的声明。
|
|
15
|
+
- `writeDefaultMode` 声明注释补充「写入失败会抛出异常」。
|
|
16
|
+
- 移除失效的 declaration source map 引用:声明生成关闭 `declarationMap`,
|
|
17
|
+
发布的 `.d.ts` 不再携带指向不存在 `.d.ts.map` 的 `sourceMappingURL`。
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- 发布流程改为同步**完整** `lib/`(两个运行时 bundle + 全部 `.d.ts`):
|
|
22
|
+
`npm run sync:dist`(要求 `DSH_CHECKOUT`,在权威 checkout 中重建后整体同步)。
|
|
23
|
+
- CI 增加防漂移检查:src 导出 ↔ 声明导出、运行时导出 ↔ 声明导出、
|
|
24
|
+
`modes.d.ts` 关键签名、无悬空 source map、tarball 内容/版本/安装后 smoke、
|
|
25
|
+
NodeNext + `skipLibCheck:false` 的声明消费测试。
|
|
26
|
+
- tarball 现在包含 `CHANGELOG.md`。
|
|
27
|
+
|
|
28
|
+
### Tests
|
|
29
|
+
|
|
30
|
+
- npm tarball smoke test(仅声明 peer 安装后加载)。
|
|
31
|
+
- TypeScript consumer test(打包安装后编译,非相对路径读 src)。
|
|
32
|
+
- 运行时/声明导出一致性验证。
|
|
33
|
+
- src↔d.ts 漂移检查(CI 与 `verify:dist` 双保险)。
|
|
34
|
+
|
|
35
|
+
## [0.1.3] - 2026-08-23
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- Bare `/ponytail` on an `off` session now re-enables at the effective default
|
|
40
|
+
(or `full` when that default is `off` too), matching the documented "resume
|
|
41
|
+
anytime with `/ponytail`" behavior. It still only reports when already enabled.
|
|
42
|
+
- `/ponytail default <mode>` no longer bypasses `PONYTAIL_DEFAULT_MODE`: the
|
|
43
|
+
effective default is recomputed after the write, so the env var keeps its
|
|
44
|
+
documented highest priority; the result distinguishes saved vs effective.
|
|
45
|
+
- Typo: `Ponyytail default set` → `Ponytail default set`.
|
|
46
|
+
- Deactivation phrases now also strip CJK sentence enders (`。?!`), so
|
|
47
|
+
`normal mode?` and `stop ponytail。` deactivate as expected.
|
|
48
|
+
- Config writes are now atomic (temp file + rename); a failed write returns an
|
|
49
|
+
error instead of a false success.
|
|
50
|
+
- A temporarily invalid config during hot-reload keeps the last known good
|
|
51
|
+
default instead of snapping back to `full`.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- `/ponytail status` added as a pure query (never modifies the session).
|
|
56
|
+
- Command hints and error text updated to the new surface
|
|
57
|
+
(`[status|default <mode>|lite|full|ultra|off]`).
|
|
58
|
+
- Invalid config JSON / `defaultMode` / read failures and invalid
|
|
59
|
+
`PONYTAIL_SUBAGENT_MATCHER` now warn exactly once (fail-open preserved,
|
|
60
|
+
missing config file stays silent).
|
|
61
|
+
- Session identity is centralized in one `sessionKey(agent)` helper; mode
|
|
62
|
+
overrides are released on `agent/disposed`, so long-running hosts do not
|
|
63
|
+
accumulate stale per-session entries.
|
|
64
|
+
|
|
65
|
+
### Tests
|
|
66
|
+
|
|
67
|
+
- 47 unit/integration tests (was 24): command semantics (bare `/ponytail`
|
|
68
|
+
restore paths, `status`, `default` saved-vs-effective, write failure),
|
|
69
|
+
priority (env > config > full, invalid env falls back to config),
|
|
70
|
+
session isolation and disposal cleanup, CJK/ASCII deactivation punctuation,
|
|
71
|
+
config file edge cases (BOM, empty, invalid JSON, array root, unknown-field
|
|
72
|
+
preservation, atomic write), hot-reload keep-last-good, and a headless
|
|
73
|
+
(no command plane) mount.
|
|
74
|
+
|
|
75
|
+
## [0.1.2] - 2026-08-23
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- Self-contained bundle: `dsh-llm` / `dsh-skill` are inlined, so the runtime
|
|
80
|
+
only depends on the `@deepseek-ai/cordis` peer — installable from GitHub,
|
|
81
|
+
tarball, or npm without a dsh source tree.
|
|
82
|
+
- npm scope renamed to `@mengyuly/dsh-ponytail` (matches the npm account).
|
|
83
|
+
|
|
84
|
+
### Tests
|
|
85
|
+
|
|
86
|
+
- CI smoke verifies the bundle leaves no dsh core external and loads with only
|
|
87
|
+
cordis installed.
|
|
88
|
+
|
|
89
|
+
## [0.1.0] - 2026-08-23
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
|
|
93
|
+
- Initial release: always-on lazy-senior-developer ruleset, lite/full/ultra/off
|
|
94
|
+
intensities, six skills and slash commands, deactivation phrases,
|
|
95
|
+
`PONYTAIL_DEFAULT_MODE` / config-file defaults, subagent matcher.
|
package/README.md
CHANGED
|
@@ -16,7 +16,10 @@ dsh plugin --profile web add link:$(pwd)
|
|
|
16
16
|
dsh plugin --profile web add github:MengYuil/dsh-ponytail
|
|
17
17
|
|
|
18
18
|
# 方式三:Release 打包件(先下 tgz)
|
|
19
|
-
dsh plugin --profile web add file:./mengyuly-dsh-ponytail-0.1.
|
|
19
|
+
dsh plugin --profile web add file:./mengyuly-dsh-ponytail-0.1.3.tgz
|
|
20
|
+
|
|
21
|
+
# 方式四:npm
|
|
22
|
+
dsh plugin --profile web add @mengyuly/dsh-ponytail
|
|
20
23
|
```
|
|
21
24
|
|
|
22
25
|
装完重启 profile 生效(`dsh web` / `dsh tui`)。装载完成后,会话技能目录里会出现 6 个 `ponytail*` 技能,发 `/ponytail-help` 立即验证。
|
|
@@ -28,17 +31,21 @@ dsh plugin --profile web add file:./mengyuly-dsh-ponytail-0.1.2.tgz
|
|
|
28
31
|
## 功能
|
|
29
32
|
|
|
30
33
|
- **核心模式** `/ponytail` — 每轮注入「懒惰阶梯」:能不做就不做(YAGNI)→ 代码库已有 → 标准库 → 平台原生 → 已装依赖 → 一行能解决 → 才是最少代码。
|
|
31
|
-
- `full`(默认)/ `lite` / `ultra` / `off`
|
|
32
|
-
- `/ponytail
|
|
34
|
+
- `full`(默认)/ `lite` / `ultra` / `off` 四档,**会话级**(会话 A 的档位不影响会话 B,会话结束自动释放)。
|
|
35
|
+
- 裸 `/ponytail`:已启用时只报告;会话为 `off` 时恢复到有效默认档(有效默认也是 `off` 则回 `full`)。
|
|
36
|
+
- `/ponytail status`:只查询、永不修改。
|
|
37
|
+
- `/ponytail lite|full|ultra|off`:显式切换。
|
|
38
|
+
- `/ponytail default <mode>`:持久化默认值到配置文件(环境变量仍优先)。
|
|
33
39
|
- **一次性技能**(用哪个载哪个,不进常驻 prompt):
|
|
34
40
|
- `/ponytail-review` — 针对最近改动找过度工程,一行一条:位置 + 删什么 + 替代。
|
|
35
41
|
- `/ponytail-audit` — 全仓库过度工程审计,排序清单。
|
|
36
42
|
- `/ponytail-debt` — 收割所有 `ponytail:` 注释成债务账本。
|
|
37
43
|
- `/ponytail-gain` — 收益计分板(更少代码/更省成本/更快)。
|
|
38
44
|
- `/ponytail-help` — 参考卡。
|
|
39
|
-
- **停用**:说 `stop ponytail` 或 `normal mode
|
|
40
|
-
- **默认值**:环境变量 `PONYTAIL_DEFAULT_MODE` > `~/.config/ponytail/config.json
|
|
45
|
+
- **停用**:说 `stop ponytail` 或 `normal mode`(兼容中英文句末标点);随时 `/ponytail` 恢复。
|
|
46
|
+
- **默认值**:环境变量 `PONYTAIL_DEFAULT_MODE` > `~/.config/ponytail/config.json`(Windows:`%APPDATA%\ponytail\config.json`)的 `{"defaultMode": "lite"}` > `full`。`/ponytail default` 写入的是配置文件,**环境变量设置且合法时仍压过保存值**(命令会分别提示 saved 与 effective)。
|
|
41
47
|
- **子代理**:常驻段作用于当前会话自身;DSH 内置 `subagent` 工具跑的是隔离的全新子代理、不继承本 persona。`PONYTAIL_SUBAGENT_MATCHER`(匹配子代理 `agentPreset` 的正则)用于在 harness 会下发给子代理的场景里排除指定子代理;缺省全部注入。
|
|
48
|
+
- **配置错误**:非法 JSON / 非法 `defaultMode` / 读取失败 / 非法正则只告警一次(不刷屏);配置文件不存在属正常、不告警;热更新遇到临时非法内容保留上一个合法默认值。
|
|
42
49
|
|
|
43
50
|
## 效率
|
|
44
51
|
|
|
@@ -53,6 +60,27 @@ dsh plugin --profile web add file:./mengyuly-dsh-ponytail-0.1.2.tgz
|
|
|
53
60
|
- 配置文件的默认档位热更新(fs 轮询 ~1s,作用于无覆盖的会话);环境变量改动仍需重启。
|
|
54
61
|
- 发行 `lib/` 是预编译产物;改源码请回主仓重建后同步。
|
|
55
62
|
|
|
63
|
+
## 测试环境与权威关系
|
|
64
|
+
|
|
65
|
+
- 用 Node.js **v24.16.0** 在本机 deepseek-harness checkout 上测试(`tsc -b` + `tsdown` host 构建通过)。与之精确匹配的已发布 DSH/Cordis 版本**待确认**——checkout 是预发布工作树,非发布 tag。
|
|
66
|
+
- 权威源码在 deepseek-harness monorepo 的 `packages/community/ponytail`(`@deepseek-ai/dsh-ponytail`);本仓库(`@mengyuly/dsh-ponytail`)是**发行镜像**:随包附构建产物,`scripts/build.sh` 只做源码类型检查,不是独立真源。
|
|
67
|
+
|
|
68
|
+
## 发行维护
|
|
69
|
+
|
|
70
|
+
- **权威源码**:deepseek-harness monorepo 的 `packages/community/ponytail`(本仓库是发行镜像,只随包发布构建产物)。
|
|
71
|
+
- **完整重新生成并同步 `lib/`**(JS 与声明必须作为同一产物同步,禁止只复制单个 JS 文件):
|
|
72
|
+
```bash
|
|
73
|
+
DSH_CHECKOUT=/path/to/deepseek-harness npm run sync:dist
|
|
74
|
+
```
|
|
75
|
+
该命令在权威 checkout 中重建(`tsc` 生成声明 + `tsdown` 打包运行时),同步 `lib/index.js`、`lib/invariant.js`、`lib/types/*.d.ts`,并自动执行一致性校验;`lib/` 有变化时会提示提交。
|
|
76
|
+
- **验证命令**:
|
|
77
|
+
```bash
|
|
78
|
+
npm run verify:dist # 声明↔src↔运行时导出一致性、关键签名、无悬空 source map
|
|
79
|
+
npm run verify:pack # tarball 内容、版本一致、仅 peer 安装后 smoke
|
|
80
|
+
npm run test:consumer # NodeNext + skipLibCheck:false 的声明消费测试(对打包产物)
|
|
81
|
+
```
|
|
82
|
+
- CI 对已提交产物执行上述全部校验;src 新增导出而 d.ts 未同步、或产物漂移都会失败。
|
|
83
|
+
|
|
56
84
|
## 许可
|
|
57
85
|
|
|
58
86
|
MIT,© 2026 DietrichGebert(上游)+ MengYuil(移植)。详见 [LICENSE](LICENSE)。
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { Service } from "@deepseek-ai/cordis";
|
|
3
|
-
import { mkdirSync, readFileSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdirSync, readFileSync, renameSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
//#region ../../llm/llm/src/brand.ts
|
|
@@ -1713,7 +1713,7 @@ function errorMessage(error) {
|
|
|
1713
1713
|
* `skill` tool). `ponytail` is the source the system-prompt ruleset is
|
|
1714
1714
|
* filtered from; the other five ship verbatim as runtime skills.
|
|
1715
1715
|
*
|
|
1716
|
-
* @module @
|
|
1716
|
+
* @module @deepseek-ai/dsh-ponytail
|
|
1717
1717
|
*/
|
|
1718
1718
|
/** The always-on lazy-senior-dev ruleset: also registered as a loadable skill. */
|
|
1719
1719
|
const PONYTAIL_SKILL_BODY = `
|
|
@@ -2007,7 +2007,9 @@ You can also load any of these with the \`skill\` tool.
|
|
|
2007
2007
|
|
|
2008
2008
|
## Deactivate
|
|
2009
2009
|
|
|
2010
|
-
Say "stop ponytail" or "normal mode". Resume anytime with \`/ponytail
|
|
2010
|
+
Say "stop ponytail" or "normal mode". Resume anytime with \`/ponytail\` —
|
|
2011
|
+
it re-enables at the effective default (or \`full\` when that is off too).
|
|
2012
|
+
\`/ponytail status\` only shows the current level, never changes it.
|
|
2011
2013
|
\`/ponytail off\` also works. Level is session-scoped; a new session starts
|
|
2012
2014
|
from the configured default.
|
|
2013
2015
|
|
|
@@ -2027,7 +2029,8 @@ export PONYTAIL_DEFAULT_MODE=ultra
|
|
|
2027
2029
|
|
|
2028
2030
|
Set \`"off"\` to disable auto-activation on session start, activate manually
|
|
2029
2031
|
with \`/ponytail\` when wanted. \`/ponytail default <mode>\` persists a new
|
|
2030
|
-
default
|
|
2032
|
+
default to the config file; an exported \`PONYTAIL_DEFAULT_MODE\` still
|
|
2033
|
+
outranks the saved value for new sessions.
|
|
2031
2034
|
|
|
2032
2035
|
Resolution: env var > config file > \`full\`.
|
|
2033
2036
|
|
|
@@ -2111,7 +2114,7 @@ function ponytailSkills() {
|
|
|
2111
2114
|
* level via the `/ponytail` command is session-scoped and lives in an
|
|
2112
2115
|
* in-memory, per-agent {@link ModeStore}.
|
|
2113
2116
|
*
|
|
2114
|
-
* @module @
|
|
2117
|
+
* @module @deepseek-ai/dsh-ponytail
|
|
2115
2118
|
*/
|
|
2116
2119
|
const DEFAULT_MODE = "full";
|
|
2117
2120
|
const RUNTIME_MODES = [
|
|
@@ -2140,7 +2143,7 @@ function normalizeRuntimeMode(mode) {
|
|
|
2140
2143
|
* toggle".
|
|
2141
2144
|
*/
|
|
2142
2145
|
function isDeactivationCommand(text) {
|
|
2143
|
-
const normalized = (typeof text === "string" ? text : "").trim().toLowerCase().replace(/[
|
|
2146
|
+
const normalized = (typeof text === "string" ? text : "").trim().toLowerCase().replace(/[\s.!?。?!]+$/, "");
|
|
2144
2147
|
return normalized === "stop ponytail" || normalized === "normal mode";
|
|
2145
2148
|
}
|
|
2146
2149
|
/** Config directory: `$XDG_CONFIG_HOME/ponytail`, `%APPDATA%\ponytail`, else `~/.config/ponytail`. */
|
|
@@ -2154,38 +2157,70 @@ function configPath(env = process.env) {
|
|
|
2154
2157
|
return join(configDir(env), "config.json");
|
|
2155
2158
|
}
|
|
2156
2159
|
/**
|
|
2157
|
-
*
|
|
2158
|
-
* then
|
|
2160
|
+
* Read the configured default with diagnostics: environment variable first,
|
|
2161
|
+
* then the config file, then `full`. A missing config file is normal and
|
|
2162
|
+
* yields no issue; a broken one yields the fallback mode plus one issue for
|
|
2163
|
+
* the caller to warn about once.
|
|
2159
2164
|
*/
|
|
2160
|
-
function
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2165
|
+
function readDefaultModeInfo(env = process.env) {
|
|
2166
|
+
const path = configPath(env);
|
|
2167
|
+
const envMode = normalizeRuntimeMode(env.PONYTAIL_DEFAULT_MODE);
|
|
2168
|
+
let configText;
|
|
2169
|
+
try {
|
|
2170
|
+
configText = readFileSync(path, "utf8");
|
|
2171
|
+
} catch (error) {
|
|
2172
|
+
if (error.code === "ENOENT") return { mode: envMode ?? "full" };
|
|
2173
|
+
return {
|
|
2174
|
+
mode: envMode ?? "full",
|
|
2175
|
+
issue: {
|
|
2176
|
+
kind: "read",
|
|
2177
|
+
detail: `${path}: ${error.message}`
|
|
2178
|
+
}
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
let configIssue;
|
|
2182
|
+
let fromConfig = null;
|
|
2183
|
+
try {
|
|
2184
|
+
const parsed = JSON.parse(stripBom(configText));
|
|
2185
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) configIssue = {
|
|
2186
|
+
kind: "shape",
|
|
2187
|
+
detail: `${path}: root must be a JSON object`
|
|
2188
|
+
};
|
|
2189
|
+
else if ("defaultMode" in parsed) {
|
|
2190
|
+
fromConfig = normalizeRuntimeMode(parsed.defaultMode);
|
|
2191
|
+
if (!fromConfig) configIssue = {
|
|
2192
|
+
kind: "value",
|
|
2193
|
+
detail: `${path}: defaultMode is not lite|full|ultra|off`
|
|
2194
|
+
};
|
|
2168
2195
|
}
|
|
2169
|
-
} catch {
|
|
2170
|
-
|
|
2196
|
+
} catch (error) {
|
|
2197
|
+
configIssue = {
|
|
2198
|
+
kind: "json",
|
|
2199
|
+
detail: `${path}: ${error.message}`
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
if (envMode) return {
|
|
2203
|
+
mode: envMode,
|
|
2204
|
+
...configIssue ? { issue: configIssue } : {}
|
|
2205
|
+
};
|
|
2206
|
+
if (configIssue) return {
|
|
2207
|
+
mode: DEFAULT_MODE,
|
|
2208
|
+
issue: configIssue
|
|
2209
|
+
};
|
|
2210
|
+
return { mode: fromConfig ?? "full" };
|
|
2171
2211
|
}
|
|
2172
2212
|
/**
|
|
2173
2213
|
* Read the configured default for this host: environment variable first, then
|
|
2174
2214
|
* the config file, then `full`.
|
|
2175
2215
|
*/
|
|
2176
2216
|
function readDefaultMode(env = process.env) {
|
|
2177
|
-
|
|
2178
|
-
let configText;
|
|
2179
|
-
try {
|
|
2180
|
-
configText = readFileSync(path, "utf8");
|
|
2181
|
-
} catch {
|
|
2182
|
-
configText = void 0;
|
|
2183
|
-
}
|
|
2184
|
-
return resolveDefaultMode(env.PONYTAIL_DEFAULT_MODE, configText);
|
|
2217
|
+
return readDefaultModeInfo(env).mode;
|
|
2185
2218
|
}
|
|
2186
2219
|
/**
|
|
2187
2220
|
* Persist a new default level to the config file, preserving other fields.
|
|
2188
2221
|
* Returns the normalized mode, or `null` when the value is not a runtime mode.
|
|
2222
|
+
* Throws when the write itself fails, so callers never report success for a
|
|
2223
|
+
* file that was not written.
|
|
2189
2224
|
*/
|
|
2190
2225
|
function writeDefaultMode(mode, env = process.env) {
|
|
2191
2226
|
const normalized = normalizeRuntimeMode(mode);
|
|
@@ -2197,8 +2232,18 @@ function writeDefaultMode(mode, env = process.env) {
|
|
|
2197
2232
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) config = parsed;
|
|
2198
2233
|
} catch {}
|
|
2199
2234
|
config.defaultMode = normalized;
|
|
2235
|
+
const text = `${JSON.stringify(config, null, 2)}\n`;
|
|
2200
2236
|
mkdirSync(dirname(path), { recursive: true });
|
|
2201
|
-
|
|
2237
|
+
const temp = join(dirname(path), `.config-${process.pid}-${Date.now()}.tmp`);
|
|
2238
|
+
try {
|
|
2239
|
+
writeFileSync(temp, text, "utf8");
|
|
2240
|
+
renameSync(temp, path);
|
|
2241
|
+
} catch (error) {
|
|
2242
|
+
try {
|
|
2243
|
+
unlinkSync(temp);
|
|
2244
|
+
} catch {}
|
|
2245
|
+
throw new Error(`failed to write ${path}: ${error.message}`);
|
|
2246
|
+
}
|
|
2202
2247
|
return normalized;
|
|
2203
2248
|
}
|
|
2204
2249
|
/**
|
|
@@ -2222,18 +2267,37 @@ var ModeStore = class {
|
|
|
2222
2267
|
}
|
|
2223
2268
|
};
|
|
2224
2269
|
/**
|
|
2225
|
-
* Compile `PONYTAIL_SUBAGENT_MATCHER` into a case-insensitive regex
|
|
2226
|
-
*
|
|
2227
|
-
*
|
|
2270
|
+
* Compile `PONYTAIL_SUBAGENT_MATCHER` into a case-insensitive regex. An unset
|
|
2271
|
+
* matcher yields `null`; an invalid pattern stays fail-open (every agent gets
|
|
2272
|
+
* the ruleset) but is reported so the caller can warn exactly once.
|
|
2228
2273
|
*/
|
|
2229
2274
|
function compileSubagentMatcher(raw) {
|
|
2230
|
-
if (!raw) return
|
|
2275
|
+
if (!raw) return {
|
|
2276
|
+
matcher: null,
|
|
2277
|
+
invalid: false
|
|
2278
|
+
};
|
|
2231
2279
|
try {
|
|
2232
|
-
return
|
|
2280
|
+
return {
|
|
2281
|
+
matcher: new RegExp(raw, "i"),
|
|
2282
|
+
invalid: false
|
|
2283
|
+
};
|
|
2233
2284
|
} catch {
|
|
2234
|
-
return
|
|
2285
|
+
return {
|
|
2286
|
+
matcher: null,
|
|
2287
|
+
invalid: true
|
|
2288
|
+
};
|
|
2235
2289
|
}
|
|
2236
2290
|
}
|
|
2291
|
+
/**
|
|
2292
|
+
* The stable per-session identity backing every mode override. DSH's `Agent`
|
|
2293
|
+
* type documents `id` as "the single identity shared with session", so the
|
|
2294
|
+
* agent id IS the SessionId: one entry per live session, never shared between
|
|
2295
|
+
* two sessions, and stable across the session's lifetime. Centralized so the
|
|
2296
|
+
* key choice lives in exactly one place.
|
|
2297
|
+
*/
|
|
2298
|
+
function sessionKey(agent) {
|
|
2299
|
+
return agent.id;
|
|
2300
|
+
}
|
|
2237
2301
|
/** Whether a session is a subagent child (origin, or any delegation depth with no origin). */
|
|
2238
2302
|
function isSubagentSession(header) {
|
|
2239
2303
|
return header.origin === "subagent" || (header.delegationDepth ?? 0) > 0;
|
|
@@ -2245,7 +2309,7 @@ function isSubagentSession(header) {
|
|
|
2245
2309
|
* `hooks/ponytail-instructions.js`, so the injected text is byte-for-byte the
|
|
2246
2310
|
* same ruleset every other host emits, filtered to the active intensity.
|
|
2247
2311
|
*
|
|
2248
|
-
* @module @
|
|
2312
|
+
* @module @deepseek-ai/dsh-ponytail
|
|
2249
2313
|
*/
|
|
2250
2314
|
/**
|
|
2251
2315
|
* Keep a line of the skill body only when it belongs to every mode or to the
|
|
@@ -2309,7 +2373,7 @@ const instructionCache = /* @__PURE__ */ new Map();
|
|
|
2309
2373
|
* from `PONYTAIL_DEFAULT_MODE` then `~/.config/ponytail/config.json` (see
|
|
2310
2374
|
* {@link readDefaultMode}).
|
|
2311
2375
|
*
|
|
2312
|
-
* @module @
|
|
2376
|
+
* @module @deepseek-ai/dsh-ponytail
|
|
2313
2377
|
*/
|
|
2314
2378
|
const name = "ponytail";
|
|
2315
2379
|
const inject = ["systemPrompt", "skills"];
|
|
@@ -2331,7 +2395,7 @@ function containsDeactivation(messages) {
|
|
|
2331
2395
|
}
|
|
2332
2396
|
/** Mode visible to one agent: its session override, else the configured default. */
|
|
2333
2397
|
function modeFor(deps, agent) {
|
|
2334
|
-
return deps.store.modeFor(
|
|
2398
|
+
return deps.store.modeFor(sessionKey(agent), deps.defaultMode());
|
|
2335
2399
|
}
|
|
2336
2400
|
/**
|
|
2337
2401
|
* Queue one skill's full `<skill_content>` rendering as the model's next
|
|
@@ -2369,21 +2433,46 @@ function registerCommands(deps, commandCtx) {
|
|
|
2369
2433
|
commandCtx.commands.register({
|
|
2370
2434
|
name: "ponytail",
|
|
2371
2435
|
description: "Set or show Ponytail lazy senior dev intensity",
|
|
2372
|
-
input: { hint: "[lite|full|ultra|off
|
|
2436
|
+
input: { hint: "[status|default <mode>|lite|full|ultra|off]" },
|
|
2373
2437
|
handler: ({ agent, rawInput }) => {
|
|
2374
2438
|
const input = rawInput.trim().toLowerCase();
|
|
2375
2439
|
const [head, ...rest] = input.split(/\s+/).filter(Boolean);
|
|
2376
2440
|
if ((head ?? "") === "default") {
|
|
2377
|
-
|
|
2441
|
+
let written;
|
|
2442
|
+
try {
|
|
2443
|
+
written = writeDefaultMode(rest[0]);
|
|
2444
|
+
} catch (error) {
|
|
2445
|
+
return {
|
|
2446
|
+
kind: "error",
|
|
2447
|
+
text: `Failed to save default: ${error.message}`
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2378
2450
|
if (!written) return {
|
|
2379
2451
|
kind: "error",
|
|
2380
2452
|
text: "Usage: /ponytail default [lite|full|ultra|off]"
|
|
2381
2453
|
};
|
|
2382
|
-
|
|
2454
|
+
const effective = readDefaultMode();
|
|
2455
|
+
deps.setDefault(effective);
|
|
2456
|
+
if (written === effective) {
|
|
2457
|
+
agent.steer(createUserMessage({
|
|
2458
|
+
content: [{
|
|
2459
|
+
type: "text",
|
|
2460
|
+
text: `PONYTAIL DEFAULT SET — new sessions start in ${written}.`
|
|
2461
|
+
}],
|
|
2462
|
+
source: {
|
|
2463
|
+
kind: "plugin",
|
|
2464
|
+
plugin: name
|
|
2465
|
+
}
|
|
2466
|
+
}));
|
|
2467
|
+
return {
|
|
2468
|
+
kind: "success",
|
|
2469
|
+
text: `Ponytail default set — new sessions start in ${written}.`
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2383
2472
|
agent.steer(createUserMessage({
|
|
2384
2473
|
content: [{
|
|
2385
2474
|
type: "text",
|
|
2386
|
-
text: `PONYTAIL DEFAULT SET —
|
|
2475
|
+
text: `PONYTAIL DEFAULT SET — saved ${written}, effective ${effective} (PONYTAIL_DEFAULT_MODE).`
|
|
2387
2476
|
}],
|
|
2388
2477
|
source: {
|
|
2389
2478
|
kind: "plugin",
|
|
@@ -2392,11 +2481,50 @@ function registerCommands(deps, commandCtx) {
|
|
|
2392
2481
|
}));
|
|
2393
2482
|
return {
|
|
2394
2483
|
kind: "success",
|
|
2395
|
-
text: `
|
|
2484
|
+
text: `Saved default: ${written}. Effective default: ${effective}, overridden by PONYTAIL_DEFAULT_MODE.`
|
|
2396
2485
|
};
|
|
2397
2486
|
}
|
|
2487
|
+
if (input === "status") return {
|
|
2488
|
+
kind: "success",
|
|
2489
|
+
text: `Ponytail mode: ${modeFor(deps, agent)}. Use /ponytail lite|full|ultra|off.`
|
|
2490
|
+
};
|
|
2398
2491
|
if (input === "") {
|
|
2399
2492
|
const current = modeFor(deps, agent);
|
|
2493
|
+
const effectiveDefault = deps.defaultMode();
|
|
2494
|
+
if (current === "off") {
|
|
2495
|
+
if (effectiveDefault === "off") {
|
|
2496
|
+
deps.store.set(sessionKey(agent), "full");
|
|
2497
|
+
agent.steer(createUserMessage({
|
|
2498
|
+
content: [{
|
|
2499
|
+
type: "text",
|
|
2500
|
+
text: "PONYTAIL MODE CHANGED — level: full"
|
|
2501
|
+
}],
|
|
2502
|
+
source: {
|
|
2503
|
+
kind: "plugin",
|
|
2504
|
+
plugin: name
|
|
2505
|
+
}
|
|
2506
|
+
}));
|
|
2507
|
+
return {
|
|
2508
|
+
kind: "success",
|
|
2509
|
+
text: "Ponytail re-enabled at full (the effective default is off)."
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
deps.store.clear(sessionKey(agent));
|
|
2513
|
+
agent.steer(createUserMessage({
|
|
2514
|
+
content: [{
|
|
2515
|
+
type: "text",
|
|
2516
|
+
text: `PONYTAIL MODE ACTIVE — level: ${effectiveDefault}`
|
|
2517
|
+
}],
|
|
2518
|
+
source: {
|
|
2519
|
+
kind: "plugin",
|
|
2520
|
+
plugin: name
|
|
2521
|
+
}
|
|
2522
|
+
}));
|
|
2523
|
+
return {
|
|
2524
|
+
kind: "success",
|
|
2525
|
+
text: `Ponytail re-enabled. Effective default: ${effectiveDefault}.`
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2400
2528
|
agent.steer(createUserMessage({
|
|
2401
2529
|
content: [{
|
|
2402
2530
|
type: "text",
|
|
@@ -2415,9 +2543,9 @@ function registerCommands(deps, commandCtx) {
|
|
|
2415
2543
|
const mode = normalizeRuntimeMode(input);
|
|
2416
2544
|
if (!mode) return {
|
|
2417
2545
|
kind: "error",
|
|
2418
|
-
text: "Usage: /ponytail [lite|full|ultra|off]"
|
|
2546
|
+
text: "Usage: /ponytail [status|default <mode>|lite|full|ultra|off]"
|
|
2419
2547
|
};
|
|
2420
|
-
deps.store.set(
|
|
2548
|
+
deps.store.set(sessionKey(agent), mode);
|
|
2421
2549
|
agent.steer(createUserMessage({
|
|
2422
2550
|
content: [{
|
|
2423
2551
|
type: "text",
|
|
@@ -2464,18 +2592,42 @@ function descriptionFor(skill) {
|
|
|
2464
2592
|
*/
|
|
2465
2593
|
function apply(ctx) {
|
|
2466
2594
|
let defaultMode = null;
|
|
2467
|
-
const
|
|
2595
|
+
const warned = /* @__PURE__ */ new Set();
|
|
2596
|
+
const warnOnce = (key, message) => {
|
|
2597
|
+
if (warned.has(key)) return;
|
|
2598
|
+
warned.add(key);
|
|
2599
|
+
ctx.logger.warn(`[ponytail] ${message}`);
|
|
2600
|
+
};
|
|
2601
|
+
const refreshDefault = () => {
|
|
2602
|
+
const resolution = readDefaultModeInfo();
|
|
2603
|
+
if (resolution.issue) warnOnce(`default:${resolution.issue.kind}`, `${resolution.issue.detail}; using ${resolution.mode}`);
|
|
2604
|
+
defaultMode = resolution.mode;
|
|
2605
|
+
return defaultMode;
|
|
2606
|
+
};
|
|
2607
|
+
const readDefault = () => defaultMode ?? refreshDefault();
|
|
2468
2608
|
const setDefault = (mode) => {
|
|
2469
2609
|
defaultMode = mode;
|
|
2470
2610
|
};
|
|
2471
2611
|
const store = new ModeStore();
|
|
2472
|
-
const
|
|
2612
|
+
const matcherResult = compileSubagentMatcher(process.env.PONYTAIL_SUBAGENT_MATCHER);
|
|
2613
|
+
const matcher = matcherResult.matcher;
|
|
2614
|
+
if (matcherResult.invalid) warnOnce("matcher:invalid", "PONYTAIL_SUBAGENT_MATCHER is not a valid regular expression; ignoring it (fail-open).");
|
|
2473
2615
|
const configFile = configPath();
|
|
2474
2616
|
const onConfigChange = () => {
|
|
2475
|
-
|
|
2617
|
+
const resolution = readDefaultModeInfo();
|
|
2618
|
+
if (resolution.issue) {
|
|
2619
|
+
warnOnce(`config:${resolution.issue.kind}`, `${resolution.issue.detail}; keeping the previous default`);
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
defaultMode = resolution.mode;
|
|
2476
2623
|
};
|
|
2477
2624
|
watchFile(configFile, { interval: 1e3 }, onConfigChange).unref();
|
|
2478
|
-
ctx.effect(() => () =>
|
|
2625
|
+
ctx.effect(() => () => {
|
|
2626
|
+
unwatchFile(configFile, onConfigChange);
|
|
2627
|
+
}, "ponytail: config hot reload");
|
|
2628
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
2629
|
+
store.clear(sessionKey(agent));
|
|
2630
|
+
});
|
|
2479
2631
|
ctx.systemPrompt.section({
|
|
2480
2632
|
name: "ponytail",
|
|
2481
2633
|
order: SECTION_ORDER,
|
|
@@ -2484,7 +2636,7 @@ function apply(ctx) {
|
|
|
2484
2636
|
const preset = agent.session.header.agentPreset;
|
|
2485
2637
|
if (preset && !matcher.test(preset)) return "";
|
|
2486
2638
|
}
|
|
2487
|
-
return getPonytailInstructions(agent ? store.modeFor(
|
|
2639
|
+
return getPonytailInstructions(agent ? store.modeFor(sessionKey(agent), readDefault()) : readDefault());
|
|
2488
2640
|
}
|
|
2489
2641
|
});
|
|
2490
2642
|
for (const skill of ponytailSkills()) ctx.skills.register(skill);
|
|
@@ -2498,7 +2650,7 @@ function apply(ctx) {
|
|
|
2498
2650
|
});
|
|
2499
2651
|
ctx.on("agent/pre-step", async (payload, next) => {
|
|
2500
2652
|
const deactivated = containsDeactivation(payload.messages);
|
|
2501
|
-
if (deactivated) store.set(
|
|
2653
|
+
if (deactivated) store.set(sessionKey(payload.agent), "off");
|
|
2502
2654
|
const decision = await next();
|
|
2503
2655
|
if (deactivated && decision.kind === "enter") return {
|
|
2504
2656
|
kind: "enter",
|
package/lib/invariant.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
//#region lib/types/invariant.js
|
|
2
2
|
/**
|
|
3
|
-
* Package-owned invariant companion for `@
|
|
4
|
-
* @module @
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-ponytail`.
|
|
4
|
+
* @module @deepseek-ai/dsh-ponytail/invariant
|
|
5
5
|
*/
|
|
6
|
-
const PACKAGE_NAME = "@
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-ponytail";
|
|
7
7
|
/** Cordis companion plugin name. */
|
|
8
8
|
const name = "ponytail-invariant";
|
|
9
9
|
/** Service required before the companion can reserve package ownership. */
|
package/lib/types/content.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `skill` tool). `ponytail` is the source the system-prompt ruleset is
|
|
5
5
|
* filtered from; the other five ship verbatim as runtime skills.
|
|
6
6
|
*
|
|
7
|
-
* @module @
|
|
7
|
+
* @module @deepseek-ai/dsh-ponytail
|
|
8
8
|
*/
|
|
9
9
|
import type { SkillRegistration } from '@deepseek-ai/dsh-skill';
|
|
10
10
|
/** The always-on lazy-senior-dev ruleset: also registered as a loadable skill. */
|
|
@@ -18,8 +18,7 @@ export declare const DEBT_SKILL_BODY = "\nEvery deliberate ponytail shortcut is
|
|
|
18
18
|
export declare const DEBT_DESCRIPTION: string;
|
|
19
19
|
export declare const GAIN_SKILL_BODY = "\nDisplay this scoreboard when invoked. One-shot: do NOT change mode, write flag\nfiles, or persist anything.\n\nThe figures are the published benchmark medians (5 everyday tasks: email\nvalidator, debounce, CSV sum, countdown timer, rate limiter; three models:\nHaiku, Sonnet, Opus). They are measured, not computed from the current repo.\nSource: the upstream `benchmarks/` directory and README.\n\n## Scoreboard\n\nRender plain ASCII bars. The bar length shows the measured range; the label\ncarries the exact figure:\n\n```\n ponytail gain benchmark median \u00B7 5 tasks \u00B7 3 models\n\n Lines of code no-skill \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 100%\n ponytail \u2588\u2588\u258C\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7 6\u201320% \u25BC 80\u201394%\n Cost no-skill \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 100%\n ponytail \u2588\u2588\u2588\u2588\u2588\u258C\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7 23\u201353% \u25BC 47\u201377%\n Speed ponytail \u25B8 3\u20136\u00D7 faster\n\n This repo: /ponytail-debt (shortcuts you deferred)\n /ponytail-audit (what's still cuttable)\n```\n\n## Honesty boundary\n\nThese are benchmark medians, not this repo. NEVER print a per-repo savings\nnumber (\"you saved X lines/tokens here\"): the unbuilt version was never\nwritten, so there is no real baseline to subtract from in a live repo. The\nonly real per-repo figures come from `/ponytail-debt` (a counted ledger), and\nthis card points there instead of inventing one.\n\n## Boundaries\n\nOne-shot display. Edits nothing, changes no mode.\n\"stop ponytail\" or \"normal mode\": revert.\n";
|
|
20
20
|
export declare const GAIN_DESCRIPTION: string;
|
|
21
|
-
export declare const HELP_SKILL_BODY = "\nDisplay this reference card when invoked. One-shot, do NOT change mode,\nwrite flag files, or persist anything.\n\n## Levels\n\n| Level | Trigger | What change |\n|-------|---------|-------------|\n| **Lite** | `/ponytail lite` | Build what's asked, name the lazier alternative in one line. |\n| **Full** | `/ponytail` | The ladder enforced: YAGNI \u2192 stdlib \u2192 native \u2192 one line \u2192 minimum. Default. |\n| **Ultra** | `/ponytail ultra` | YAGNI extremist. Deletion before addition. Challenges requirements before building. |\n| **Off** | `/ponytail off` | Ponytail stops injecting its ruleset for this session. |\n\nLevel is session-scoped until changed.\n\n## Skills\n\n| Skill | Trigger | What it does |\n|-------|---------|--------------|\n| **ponytail** | `/ponytail` | Lazy mode itself. Simplest solution that works. |\n| **ponytail-review** | `/ponytail-review` | Over-engineering review: `L42: yagni: factory, one product. Inline.` |\n| **ponytail-audit** | `/ponytail-audit` | Whole-repo over-engineering audit: ranked list of what to delete. |\n| **ponytail-debt** | `/ponytail-debt` | Harvest `ponytail:` shortcut comments into a tracked ledger. |\n| **ponytail-gain** | `/ponytail-gain` | Measured-impact scoreboard: less code, less cost, more speed. |\n| **ponytail-help** | `/ponytail-help` | This card. |\n\nYou can also load any of these with the `skill` tool.\n\n## Deactivate\n\nSay \"stop ponytail\" or \"normal mode\". Resume anytime with `/ponytail
|
|
21
|
+
export declare const HELP_SKILL_BODY = "\nDisplay this reference card when invoked. One-shot, do NOT change mode,\nwrite flag files, or persist anything.\n\n## Levels\n\n| Level | Trigger | What change |\n|-------|---------|-------------|\n| **Lite** | `/ponytail lite` | Build what's asked, name the lazier alternative in one line. |\n| **Full** | `/ponytail` | The ladder enforced: YAGNI \u2192 stdlib \u2192 native \u2192 one line \u2192 minimum. Default. |\n| **Ultra** | `/ponytail ultra` | YAGNI extremist. Deletion before addition. Challenges requirements before building. |\n| **Off** | `/ponytail off` | Ponytail stops injecting its ruleset for this session. |\n\nLevel is session-scoped until changed.\n\n## Skills\n\n| Skill | Trigger | What it does |\n|-------|---------|--------------|\n| **ponytail** | `/ponytail` | Lazy mode itself. Simplest solution that works. |\n| **ponytail-review** | `/ponytail-review` | Over-engineering review: `L42: yagni: factory, one product. Inline.` |\n| **ponytail-audit** | `/ponytail-audit` | Whole-repo over-engineering audit: ranked list of what to delete. |\n| **ponytail-debt** | `/ponytail-debt` | Harvest `ponytail:` shortcut comments into a tracked ledger. |\n| **ponytail-gain** | `/ponytail-gain` | Measured-impact scoreboard: less code, less cost, more speed. |\n| **ponytail-help** | `/ponytail-help` | This card. |\n\nYou can also load any of these with the `skill` tool.\n\n## Deactivate\n\nSay \"stop ponytail\" or \"normal mode\". Resume anytime with `/ponytail` \u2014\nit re-enables at the effective default (or `full` when that is off too).\n`/ponytail status` only shows the current level, never changes it.\n`/ponytail off` also works. Level is session-scoped; a new session starts\nfrom the configured default.\n\n## Configure Default Mode\n\nDefault mode = `full`, auto-active every session. Change it:\n\n**Environment variable** (highest priority):\n```bash\nexport PONYTAIL_DEFAULT_MODE=ultra\n```\n\n**Config file** (`~/.config/ponytail/config.json`, Windows: `%APPDATA%\\ponytail\\config.json`):\n```json\n{ \"defaultMode\": \"lite\" }\n```\n\nSet `\"off\"` to disable auto-activation on session start, activate manually\nwith `/ponytail` when wanted. `/ponytail default <mode>` persists a new\ndefault to the config file; an exported `PONYTAIL_DEFAULT_MODE` still\noutranks the saved value for new sessions.\n\nResolution: env var > config file > `full`.\n\n## More\n\nFull docs + examples: https://github.com/DietrichGebert/ponytail\n";
|
|
22
22
|
export declare const HELP_DESCRIPTION: string;
|
|
23
23
|
/** Ordered set of runtime skills surfaced to the model catalog and `/` menu. */
|
|
24
24
|
export declare function ponytailSkills(): readonly SkillRegistration[];
|
|
25
|
-
//# sourceMappingURL=content.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* from `PONYTAIL_DEFAULT_MODE` then `~/.config/ponytail/config.json` (see
|
|
11
11
|
* {@link readDefaultMode}).
|
|
12
12
|
*
|
|
13
|
-
* @module @
|
|
13
|
+
* @module @deepseek-ai/dsh-ponytail
|
|
14
14
|
*/
|
|
15
15
|
import type { Context } from '@deepseek-ai/cordis';
|
|
16
16
|
export declare const name = "ponytail";
|
|
@@ -34,4 +34,3 @@ export declare function containsDeactivation(messages: readonly {
|
|
|
34
34
|
* commands, and the plain-text deactivation listener.
|
|
35
35
|
*/
|
|
36
36
|
export declare function apply(ctx: Context): void;
|
|
37
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `hooks/ponytail-instructions.js`, so the injected text is byte-for-byte the
|
|
4
4
|
* same ruleset every other host emits, filtered to the active intensity.
|
|
5
5
|
*
|
|
6
|
-
* @module @
|
|
6
|
+
* @module @deepseek-ai/dsh-ponytail
|
|
7
7
|
*/
|
|
8
8
|
import { type PonytailRuntimeMode } from './modes.ts';
|
|
9
9
|
/**
|
|
@@ -21,4 +21,3 @@ export declare function fallbackInstructions(mode: PonytailRuntimeMode): string;
|
|
|
21
21
|
* Returns an empty string for `off` (ponytail contributes nothing).
|
|
22
22
|
*/
|
|
23
23
|
export declare function getPonytailInstructions(mode: PonytailRuntimeMode | null | undefined): string;
|
|
24
|
-
//# sourceMappingURL=instructions.d.ts.map
|
package/lib/types/invariant.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Package-owned invariant companion for `@
|
|
3
|
-
* @module @
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-ponytail`.
|
|
3
|
+
* @module @deepseek-ai/dsh-ponytail/invariant
|
|
4
4
|
*/
|
|
5
5
|
import type { Context } from '@deepseek-ai/cordis';
|
|
6
6
|
/** Cordis companion plugin name. */
|
|
@@ -13,4 +13,3 @@ export declare const inject: string[];
|
|
|
13
13
|
* @returns the installed registration's disposer after setup succeeds.
|
|
14
14
|
*/
|
|
15
15
|
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
-
//# sourceMappingURL=invariant.d.ts.map
|
package/lib/types/modes.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* level via the `/ponytail` command is session-scoped and lives in an
|
|
6
6
|
* in-memory, per-agent {@link ModeStore}.
|
|
7
7
|
*
|
|
8
|
-
* @module @
|
|
8
|
+
* @module @deepseek-ai/dsh-ponytail
|
|
9
9
|
*/
|
|
10
10
|
/** One settable runtime intensity. `review` is command-only and never a default. */
|
|
11
11
|
export type PonytailRuntimeMode = 'off' | 'lite' | 'full' | 'ultra';
|
|
@@ -31,6 +31,28 @@ export declare function configPath(env?: NodeJS.ProcessEnv): string;
|
|
|
31
31
|
* then {@link DEFAULT_MODE}. Pure so callers can supply fixtures.
|
|
32
32
|
*/
|
|
33
33
|
export declare function resolveDefaultMode(envMode: unknown, configText: string | undefined): PonytailRuntimeMode;
|
|
34
|
+
/**
|
|
35
|
+
* One configuration problem worth surfacing exactly once. `read` covers
|
|
36
|
+
* permission/IO failures, `json` malformed documents, `shape` a root that is
|
|
37
|
+
* not an object, and `value` a `defaultMode` that is not a runtime level.
|
|
38
|
+
*/
|
|
39
|
+
export type DefaultModeIssueKind = 'read' | 'json' | 'shape' | 'value';
|
|
40
|
+
export interface DefaultModeIssue {
|
|
41
|
+
readonly kind: DefaultModeIssueKind;
|
|
42
|
+
readonly detail: string;
|
|
43
|
+
}
|
|
44
|
+
/** The resolved default plus the first config problem found, if any. */
|
|
45
|
+
export interface DefaultModeResolution {
|
|
46
|
+
readonly mode: PonytailRuntimeMode;
|
|
47
|
+
readonly issue?: DefaultModeIssue;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read the configured default with diagnostics: environment variable first,
|
|
51
|
+
* then the config file, then `full`. A missing config file is normal and
|
|
52
|
+
* yields no issue; a broken one yields the fallback mode plus one issue for
|
|
53
|
+
* the caller to warn about once.
|
|
54
|
+
*/
|
|
55
|
+
export declare function readDefaultModeInfo(env?: NodeJS.ProcessEnv): DefaultModeResolution;
|
|
34
56
|
/**
|
|
35
57
|
* Read the configured default for this host: environment variable first, then
|
|
36
58
|
* the config file, then `full`.
|
|
@@ -39,6 +61,8 @@ export declare function readDefaultMode(env?: NodeJS.ProcessEnv): PonytailRuntim
|
|
|
39
61
|
/**
|
|
40
62
|
* Persist a new default level to the config file, preserving other fields.
|
|
41
63
|
* Returns the normalized mode, or `null` when the value is not a runtime mode.
|
|
64
|
+
* Throws when the write itself fails, so callers never report success for a
|
|
65
|
+
* file that was not written.
|
|
42
66
|
*/
|
|
43
67
|
export declare function writeDefaultMode(mode: unknown, env?: NodeJS.ProcessEnv): PonytailRuntimeMode | null;
|
|
44
68
|
/**
|
|
@@ -56,14 +80,26 @@ export declare class ModeStore {
|
|
|
56
80
|
clear(agentId: string): void;
|
|
57
81
|
}
|
|
58
82
|
/**
|
|
59
|
-
* Compile `PONYTAIL_SUBAGENT_MATCHER` into a case-insensitive regex
|
|
60
|
-
*
|
|
61
|
-
*
|
|
83
|
+
* Compile `PONYTAIL_SUBAGENT_MATCHER` into a case-insensitive regex. An unset
|
|
84
|
+
* matcher yields `null`; an invalid pattern stays fail-open (every agent gets
|
|
85
|
+
* the ruleset) but is reported so the caller can warn exactly once.
|
|
86
|
+
*/
|
|
87
|
+
export declare function compileSubagentMatcher(raw: string | undefined): {
|
|
88
|
+
matcher: RegExp | null;
|
|
89
|
+
invalid: boolean;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* The stable per-session identity backing every mode override. DSH's `Agent`
|
|
93
|
+
* type documents `id` as "the single identity shared with session", so the
|
|
94
|
+
* agent id IS the SessionId: one entry per live session, never shared between
|
|
95
|
+
* two sessions, and stable across the session's lifetime. Centralized so the
|
|
96
|
+
* key choice lives in exactly one place.
|
|
62
97
|
*/
|
|
63
|
-
export declare function
|
|
98
|
+
export declare function sessionKey(agent: {
|
|
99
|
+
readonly id: string;
|
|
100
|
+
}): string;
|
|
64
101
|
/** Whether a session is a subagent child (origin, or any delegation depth with no origin). */
|
|
65
102
|
export declare function isSubagentSession(header: {
|
|
66
103
|
origin?: 'subagent';
|
|
67
104
|
delegationDepth?: number;
|
|
68
105
|
}): boolean;
|
|
69
|
-
//# sourceMappingURL=modes.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mengyuly/dsh-ponytail",
|
|
3
3
|
"description": "Lazy senior dev mode for DeepSeek Harness: always-on minimal-code ruleset, intensity switching, and short review/audit/debt/gain/help skills",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.4",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
+
"CHANGELOG.md",
|
|
28
29
|
"lib/index.js",
|
|
29
30
|
"lib/invariant.js",
|
|
30
31
|
"lib/types/**/*.d.ts",
|
|
@@ -43,5 +44,11 @@
|
|
|
43
44
|
"@deepseek-ai/dsh-llm": ">=0.0.1-rc <2",
|
|
44
45
|
"@deepseek-ai/dsh-skill": ">=0.0.1-rc <2",
|
|
45
46
|
"@deepseek-ai/dsh-system-prompt": ">=0.0.1-rc <2"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"sync:dist": "node scripts/sync-dist.mjs",
|
|
50
|
+
"verify:dist": "node scripts/verify-dist.mjs",
|
|
51
|
+
"verify:pack": "node scripts/verify-pack.mjs",
|
|
52
|
+
"test:consumer": "node scripts/test-consumer.mjs"
|
|
46
53
|
}
|
|
47
|
-
}
|
|
54
|
+
}
|