@elinpf/dsh-ops-prompts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ title: "@elinpf/dsh-ops-prompts"
2
+ description:
3
+ zh: "Ops 提示词编排中心 — 方法论 section 与动态 reminder 的统一注册渠道"
4
+ en: "Ops prompt orchestration center — methodology sections and dynamic reminders for the ops preset"
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @elinpf/dsh-ops-prompts
2
+
3
+ Ops prompt orchestration center for DeepSeek Harness — one channel through which other ops plugins register methodology sections (static system-prompt text) and dynamic reminders (pre-step checks that inject ephemeral prompts).
4
+
5
+ ## What it does
6
+
7
+ - **`ctx.get('opsPrompts')` handle** with two registration surfaces, each returning a disposer:
8
+ - `registerMethodology({ name, order, text })` — a static system-prompt section. All entries are rendered by a single `ops:methodology` section (order-sorted) that is re-evaluated at every prompt assembly, so late registrations and disposals take effect without rebuilding the prompt.
9
+ - `registerReminder({ name, check })` — a rule evaluated at each `agent/pre-step`. Non-null results are joined and delivered through `agent.inject`, which goes through the durable inbox splice: the reminder is reconstructable from the session log (model-visible ⟺ logged).
10
+ - **Core ops methodology** (`ops:core`, order 250): root-cause discipline, verify-before-concluding, and investigation structuring — the shared baseline every ops tool builds on.
11
+ - **Bundled skills provider** (`ops-prompts-bundled`, `src/skills.ts`): ships this package's `skills/` directory into dsh's native skill subsystem. Markdown files with native frontmatter (`name`/`description` required, `whenToUse` optional, `disable-model-invocation` honored) become catalog candidates; bodies are pulled on demand through the `skill` tool. The skills registry is optional — resolved get-first with an `ctx.inject` fallback, and tolerated when absent (the package then works as a pure prompt channel).
12
+
13
+ ## Design notes
14
+
15
+ - **One section, many entries.** Methodology text aggregates into a single system-prompt section instead of one section per contributor, so ordering stays explicit (`order`) and prompt assembly reads one closure over the registry map.
16
+ - **Reminders are ephemeral, never durable state.** Rules live in fiber-local maps; only the injected message touches the session log, via the platform's own inbox splice — so this package owns no session event types and no projection.
17
+ - **Everything is fiber-scoped.** The system-prompt section and the skills provider register through `ctx.effect`; the pre-step listener is fiber-scoped via `ctx.on`. Fiber disposal/HMR removes every surface (covered by `tests/hmr-unload.spec.ts`).
18
+ - **A prompt-only skill is a text file, never a package** — the bundled provider exists so repo-managed Markdown skills ride the native catalog instead of a self-built loader.
19
+
20
+ ## Config
21
+
22
+ | Key | Type | Default | Description |
23
+ |---|---|---|---|
24
+ | `reminderEnabled` | `boolean` | `true` | Whether dynamic reminders are evaluated at each agent/pre-step. |
25
+
26
+ Required inject: `systemPrompt`. Optional: `skills` (host-plane registry).
27
+
28
+ ## Testing
29
+
30
+ ```sh
31
+ npm run build # tsc → lib/
32
+ npx vitest run # unit specs + HMR unload spec
33
+ ```
34
+
35
+ `tests/ops-prompts.spec.ts` covers the handle, methodology aggregation, and reminder delivery; `tests/skills.spec.ts` covers frontmatter parsing, the bundled provider, and the optional skills registration; `tests/hmr-unload.spec.ts` runs every collected disposer and asserts each registration surface is gone.
package/README.zh.md ADDED
@@ -0,0 +1,35 @@
1
+ # @elinpf/dsh-ops-prompts
2
+
3
+ DeepSeek Harness 的 ops 提示词编排中心 — 其他 ops 插件通过这一个渠道注册方法论 section(静态系统提示词文本)和动态 reminder(pre-step 检查函数,注入临时提示词)。
4
+
5
+ ## 功能
6
+
7
+ - **`ctx.get('opsPrompts')` 句柄**,两个注册面,都返回 disposer:
8
+ - `registerMethodology({ name, order, text })` — 静态系统提示词 section。所有条目由同一个 `ops:methodology` section 渲染(按 `order` 排序),每次组装提示词时重新求值,所以晚注册和注销都能即时生效。
9
+ - `registerReminder({ name, check })` — 每个 `agent/pre-step` 求值的规则。非空结果拼接后通过 `agent.inject` 投递,走持久化 inbox splice:reminder 可以从会话日志重建(model-visible ⟺ logged)。
10
+ - **核心 ops 方法论**(`ops:core`,order 250):根因纪律、下结论前先验证、调查结构化 — 所有 ops 工具共享的基线。
11
+ - **bundled skills provider**(`ops-prompts-bundled`,`src/skills.ts`):把本包 `skills/` 目录送进 dsh 原生 skill 子系统。带原生 frontmatter 的 Markdown(`name`/`description` 必填,`whenToUse` 可选,支持 `disable-model-invocation`)成为 catalog 候选;正文通过 `skill` 工具按需拉取。skills 注册表是可选依赖 — 先 `ctx.get` 再 `ctx.inject` 兜底,缺失时容忍(包退化为纯提示词渠道)。
12
+
13
+ ## 设计要点
14
+
15
+ - **一个 section,多个条目。** 方法论文本聚合进单个系统提示词 section,而不是每个贡献者一个 section:排序靠 `order` 显式表达,提示词组装只读一个闭包。
16
+ - **reminder 是临时的,永远不是持久状态。** 规则活在 fiber 局部 Map 里;只有注入的消息经由平台自己的 inbox splice 进入会话日志 — 因此本包不拥有任何 session 事件类型,也不需要 projection。
17
+ - **一切注册都是 fiber 作用域。** 系统提示词 section 和 skills provider 走 `ctx.effect`,pre-step 监听器经 `ctx.on` 挂在 fiber 上。fiber 销毁/HMR 会卸掉所有注册面(`tests/hmr-unload.spec.ts` 覆盖)。
18
+ - **纯提示词 skill 就是一个文本文件,永远不是一个包** — bundled provider 的意义在于让仓库维护的 Markdown skill 搭原生 catalog 的车,而不是自建加载器。
19
+
20
+ ## 配置
21
+
22
+ | 键 | 类型 | 默认值 | 说明 |
23
+ |---|---|---|---|
24
+ | `reminderEnabled` | `boolean` | `true` | 是否在每个 agent/pre-step 求值动态 reminder。 |
25
+
26
+ 必需 inject:`systemPrompt`。可选:`skills`(host 平面注册表)。
27
+
28
+ ## 测试
29
+
30
+ ```sh
31
+ npm run build # tsc → lib/
32
+ npx vitest run # 单元测试 + HMR 卸载测试
33
+ ```
34
+
35
+ `tests/ops-prompts.spec.ts` 覆盖句柄、方法论聚合和 reminder 投递;`tests/skills.spec.ts` 覆盖 frontmatter 解析、bundled provider 和可选 skills 注册;`tests/hmr-unload.spec.ts` 执行所有收集到的 disposer 并断言每个注册面都被移除。
@@ -0,0 +1 @@
1
+ []
package/lib/index.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Ops prompt orchestration center.
3
+ *
4
+ * Provides a capability (`ctx.get('opsPrompts')`) that other ops plugins use
5
+ * to register methodology prompts (static system prompt sections) and dynamic
6
+ * reminders (evaluated at each agent/pre-step, injected into the conversation
7
+ * flow).
8
+ *
9
+ * It also registers its own core ops methodology prompt and the agent/pre-step
10
+ * listener that evaluates all registered reminder rules.
11
+ *
12
+ * @module @elinpf/dsh-ops-prompts
13
+ */
14
+ import type { Context } from '@deepseek-ai/cordis';
15
+ import z from '@deepseek-ai/schemastery';
16
+ import type { Config as ConfigShape } from './types.js';
17
+ export * from './skills.js';
18
+ export type { MethodologyEntry, OpsPromptsHandle, ReminderEntry } from './types.js';
19
+ export declare const name = "ops-prompts";
20
+ export declare const inject: string[];
21
+ export type Config = ConfigShape;
22
+ export declare const Config: z<Config>;
23
+ export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Ops prompt orchestration center.
3
+ *
4
+ * Provides a capability (`ctx.get('opsPrompts')`) that other ops plugins use
5
+ * to register methodology prompts (static system prompt sections) and dynamic
6
+ * reminders (evaluated at each agent/pre-step, injected into the conversation
7
+ * flow).
8
+ *
9
+ * It also registers its own core ops methodology prompt and the agent/pre-step
10
+ * listener that evaluates all registered reminder rules.
11
+ *
12
+ * @module @elinpf/dsh-ops-prompts
13
+ */
14
+ import z from '@deepseek-ai/schemastery';
15
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
16
+ import { createBundledSkillsProvider } from './skills.js';
17
+ export * from './skills.js';
18
+ // ── Plugin identity ───────────────────────────────────────────────────────────
19
+ export const name = 'ops-prompts';
20
+ export const inject = ['systemPrompt'];
21
+ export const Config = z.object({
22
+ reminderEnabled: z.boolean().default(true),
23
+ });
24
+ // ── Core ops methodology ─────────────────────────────────────────────────────
25
+ const CORE_METHODOLOGY = [
26
+ '## Ops investigation discipline',
27
+ '',
28
+ '### Root cause analysis',
29
+ '- Every conclusion must answer "why". Do not stop at a symptom or intermediate cause.',
30
+ '- Keep asking "why does this happen?" until you reach a physical or infrastructure-level fact',
31
+ ' (e.g. "disk full", "memory exhausted", "network partition").',
32
+ '- "The CSI operation is stuck" is NOT a root cause. "Ceph storage is 99% full, blocking OMAP writes"',
33
+ ' IS a root cause.',
34
+ '',
35
+ '### Verify before concluding',
36
+ '- Do not mark a step done with an unverified hypothesis.',
37
+ '- If you claim "X is caused by Y", verify Y before completing the step.',
38
+ '- `done` means the investigation of this step is complete, NOT that the conclusion is final.',
39
+ ' Use `reopen` when a completed step\'s conclusion turns out to be wrong or incomplete.',
40
+ '',
41
+ '### Structure your investigation',
42
+ '- When you find something new, add a step — do not accumulate findings in a single node.',
43
+ '- Use `link` to record causal edges: "this symptom is caused by that root cause".',
44
+ '- Use `abandon` for dead ends AND for steps no longer relevant due to changed circumstances.',
45
+ ].join('\n');
46
+ // ── Plugin apply ─────────────────────────────────────────────────────────────
47
+ export function apply(ctx, config) {
48
+ const methodologies = new Map();
49
+ const reminders = new Map();
50
+ // Expose the handle so other plugins can register through ctx.get('opsPrompts')
51
+ const handle = {
52
+ registerMethodology(opts) {
53
+ methodologies.set(opts.name, opts);
54
+ return () => { methodologies.delete(opts.name); };
55
+ },
56
+ registerReminder(opts) {
57
+ reminders.set(opts.name, opts);
58
+ return () => { reminders.delete(opts.name); };
59
+ },
60
+ };
61
+ ctx.provide('opsPrompts', handle);
62
+ // Register core ops methodology as the first entry
63
+ handle.registerMethodology({
64
+ name: 'ops:core',
65
+ order: 250,
66
+ text: CORE_METHODOLOGY,
67
+ });
68
+ // Bundled ops skills: ship this package's skills/ directory into dsh's
69
+ // native skill subsystem (catalog via candidates, bodies pulled on demand
70
+ // through the `skill` tool). The skills registry lives in the host
71
+ // composition — resolve with the usual get-first / inject-fallback race
72
+ // discipline, and tolerate its absence (this package then works as a pure
73
+ // prompt channel).
74
+ const registerBundledSkills = (rctx, registry) => {
75
+ rctx.effect(() => registry.registerProvider(() => createBundledSkillsProvider(undefined, m => rctx.logger('ops-prompts').warn(m))));
76
+ };
77
+ const immediateSkills = ctx.get('skills');
78
+ if (immediateSkills !== undefined) {
79
+ registerBundledSkills(ctx, immediateSkills);
80
+ }
81
+ else {
82
+ ctx.inject(['skills'], (pctx) => {
83
+ registerBundledSkills(pctx, pctx.skills);
84
+ });
85
+ }
86
+ // System prompt section that renders all registered methodology entries.
87
+ // Re-evaluated at each prompt assembly. systemPrompt is a REQUIRED inject:
88
+ // an absent service pends the fiber and apply never runs, so read it as a
89
+ // declared dependency — a ctx.get fallback branch would be dead code.
90
+ // The section disposer MUST go through ctx.effect: the service returns a
91
+ // disposer that fiber disposal/HMR would otherwise never run (leak).
92
+ const systemPrompt = ctx.systemPrompt;
93
+ ctx.effect(() => systemPrompt.section({
94
+ name: 'ops:methodology',
95
+ order: 250,
96
+ text: () => {
97
+ const entries = [...methodologies.values()].sort((a, b) => a.order - b.order);
98
+ return entries.map((e) => e.text).join('\n\n');
99
+ },
100
+ }));
101
+ // agent/pre-step listener: evaluate all registered reminder rules.
102
+ // Non-null results are delivered through agent.inject — the message goes
103
+ // through the durable inbox splice (agent/inbox/spliced), so the reminder
104
+ // is reconstructable from the session log (model-visible ⟺ logged) and is
105
+ // claimed at the next step boundary.
106
+ if (config.reminderEnabled) {
107
+ ;
108
+ ctx.on('agent/pre-step', async (payload, next) => {
109
+ const decision = await next();
110
+ if (decision.kind === 'reject')
111
+ return decision;
112
+ const agent = payload?.agent;
113
+ if (!agent || typeof agent.inject !== 'function')
114
+ return decision;
115
+ const results = [];
116
+ for (const reminder of reminders.values()) {
117
+ const text = reminder.check(agent);
118
+ if (text !== null)
119
+ results.push(text);
120
+ }
121
+ if (results.length === 0)
122
+ return decision;
123
+ agent.inject(createUserMessage({
124
+ content: [{ type: 'text', text: results.join('\n') }],
125
+ source: { kind: 'plugin', plugin: name, form: 'notice', summary: 'ops reminder' },
126
+ }));
127
+ return decision;
128
+ }, { prepend: true });
129
+ }
130
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Invariant companion for @elinpf/dsh-ops-prompts.
3
+ *
4
+ * @module @elinpf/dsh-ops-prompts/invariant
5
+ */
6
+ /** Cordis companion plugin name. */
7
+ declare const name = "ops-prompts-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Context carrying the invariant service.
13
+ * @returns a promise resolving after registration.
14
+ */
15
+ declare const apply: (ctx: any) => Promise<void>;
16
+ export { apply, inject, name };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Invariant companion for @elinpf/dsh-ops-prompts.
3
+ *
4
+ * @module @elinpf/dsh-ops-prompts/invariant
5
+ */
6
+ const PACKAGE_NAME = '@elinpf/dsh-ops-prompts';
7
+ /** Cordis companion plugin name. */
8
+ const name = 'ops-prompts-invariant';
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ['invariants'];
11
+ /**
12
+ * No runtime invariant: this plugin owns no session event types and no
13
+ * durable state. Methodology entries and reminder rules are ephemeral
14
+ * registrations held in fiber-local maps, and reminder delivery goes through
15
+ * agent.inject, which the session inbox splice already logs (model-visible
16
+ * ⟺ logged holds without a package-owned fold).
17
+ */
18
+ const install = () => { };
19
+ /**
20
+ * Register this package's invariant companion.
21
+ * @param ctx - Context carrying the invariant service.
22
+ * @returns a promise resolving after registration.
23
+ */
24
+ const apply = async (ctx) => {
25
+ ctx.invariants.register(PACKAGE_NAME, install);
26
+ };
27
+ export { apply, inject, name };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Bundled ops skills provider.
3
+ *
4
+ * Ships the repo-managed ops skills (Markdown files in this package's
5
+ * `skills/` directory) into dsh's NATIVE skill subsystem: candidates carry
6
+ * name + description into the model-facing catalog, bodies are pulled on
7
+ * demand through the `skill` tool. This replaces the earlier self-built
8
+ * loader — the platform already owns discovery, ranking, and loading.
9
+ *
10
+ * Modeled on @deepseek-ai/dsh-skill-badge: provider candidates are listed
11
+ * from the bundled directory, bodies are read from disk relative to this
12
+ * module (same ../skills layout from src/ in tests and lib/ at runtime).
13
+ *
14
+ * @module @elinpf/dsh-ops-prompts
15
+ */
16
+ import { type SkillCandidate, type SkillProvider } from '@deepseek-ai/dsh-skill';
17
+ export declare const BUNDLED_SKILLS_PROVIDER = "ops-prompts-bundled";
18
+ /** The bundled skills directory (package root/skills, from src/ or lib/). */
19
+ export declare function bundledSkillsDir(): string;
20
+ export interface SkillFrontmatter {
21
+ name: string;
22
+ description: string;
23
+ whenToUse?: string;
24
+ disableModelInvocation: boolean;
25
+ }
26
+ /**
27
+ * Parse a native skill file's frontmatter. `name` and `description` are
28
+ * required — a file missing either is not a skill and returns null (the
29
+ * caller skips it with a warning).
30
+ */
31
+ export declare function parseSkillFrontmatter(text: string): SkillFrontmatter | null;
32
+ export interface BundledListResult {
33
+ candidates: SkillCandidate[];
34
+ /** Files skipped for missing/invalid frontmatter (diagnostics). */
35
+ skipped: string[];
36
+ }
37
+ /** List valid skill candidates from a directory of `.md` files. */
38
+ export declare function listBundledSkills(dir?: string): Promise<BundledListResult>;
39
+ /** Create the bundled provider over a skills directory (injectable for tests). */
40
+ export declare function createBundledSkillsProvider(dir?: string, warn?: (message: string) => void): SkillProvider;
package/lib/skills.js ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Bundled ops skills provider.
3
+ *
4
+ * Ships the repo-managed ops skills (Markdown files in this package's
5
+ * `skills/` directory) into dsh's NATIVE skill subsystem: candidates carry
6
+ * name + description into the model-facing catalog, bodies are pulled on
7
+ * demand through the `skill` tool. This replaces the earlier self-built
8
+ * loader — the platform already owns discovery, ranking, and loading.
9
+ *
10
+ * Modeled on @deepseek-ai/dsh-skill-badge: provider candidates are listed
11
+ * from the bundled directory, bodies are read from disk relative to this
12
+ * module (same ../skills layout from src/ in tests and lib/ at runtime).
13
+ *
14
+ * @module @elinpf/dsh-ops-prompts
15
+ */
16
+ import { readdir, readFile } from 'node:fs/promises';
17
+ import { join } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { BUNDLED_SKILL_RANK, } from '@deepseek-ai/dsh-skill';
20
+ export const BUNDLED_SKILLS_PROVIDER = 'ops-prompts-bundled';
21
+ const SKILLS_DIR_URL = new URL('../skills/', import.meta.url);
22
+ const INVOCATION = { modelInvocable: true, userInvocable: true };
23
+ /** The bundled skills directory (package root/skills, from src/ or lib/). */
24
+ export function bundledSkillsDir() {
25
+ return fileURLToPath(SKILLS_DIR_URL);
26
+ }
27
+ /**
28
+ * Parse a native skill file's frontmatter. `name` and `description` are
29
+ * required — a file missing either is not a skill and returns null (the
30
+ * caller skips it with a warning).
31
+ */
32
+ export function parseSkillFrontmatter(text) {
33
+ const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
34
+ if (!match)
35
+ return null;
36
+ const fields = new Map();
37
+ for (const line of match[1].split('\n')) {
38
+ const kv = line.match(/^([\w-]+):\s*(\S[\s\S]*)?$/);
39
+ if (!kv)
40
+ continue;
41
+ fields.set(kv[1], (kv[2] ?? '').trim());
42
+ }
43
+ const name = fields.get('name');
44
+ const description = fields.get('description');
45
+ if (!name || !description)
46
+ return null;
47
+ const parsed = {
48
+ name,
49
+ description,
50
+ disableModelInvocation: fields.get('disable-model-invocation') === 'true',
51
+ };
52
+ const whenToUse = fields.get('whenToUse');
53
+ if (whenToUse)
54
+ parsed.whenToUse = whenToUse;
55
+ return parsed;
56
+ }
57
+ /** List valid skill candidates from a directory of `.md` files. */
58
+ export async function listBundledSkills(dir = bundledSkillsDir()) {
59
+ const candidates = [];
60
+ const skipped = [];
61
+ let files;
62
+ try {
63
+ files = await readdir(dir);
64
+ }
65
+ catch {
66
+ return { candidates, skipped }; // no bundled dir — nothing to offer
67
+ }
68
+ const resourceBase = { kind: 'directory', path: dir };
69
+ for (const file of files.filter(f => f.endsWith('.md')).sort()) {
70
+ const path = join(dir, file);
71
+ let text;
72
+ try {
73
+ text = await readFile(path, 'utf8');
74
+ }
75
+ catch {
76
+ skipped.push(path);
77
+ continue;
78
+ }
79
+ const fm = parseSkillFrontmatter(text);
80
+ if (fm === null) {
81
+ skipped.push(path);
82
+ continue;
83
+ }
84
+ const candidate = {
85
+ name: fm.name,
86
+ description: fm.description,
87
+ ...fm.whenToUse !== undefined ? { whenToUse: fm.whenToUse } : {},
88
+ invocation: { ...INVOCATION, modelInvocable: !fm.disableModelInvocation },
89
+ provider: BUNDLED_SKILLS_PROVIDER,
90
+ source: 'bundled',
91
+ resourceBase,
92
+ rank: BUNDLED_SKILL_RANK,
93
+ locator: path,
94
+ path,
95
+ };
96
+ candidates.push(candidate);
97
+ }
98
+ return { candidates, skipped };
99
+ }
100
+ /** Create the bundled provider over a skills directory (injectable for tests). */
101
+ export function createBundledSkillsProvider(dir = bundledSkillsDir(), warn = () => { }) {
102
+ return {
103
+ name: BUNDLED_SKILLS_PROVIDER,
104
+ async list() {
105
+ const { candidates, skipped } = await listBundledSkills(dir);
106
+ if (skipped.length > 0) {
107
+ // Skips are authoring mistakes in this repo — surface loudly enough
108
+ // to be fixed, without failing the whole catalog.
109
+ warn(`ops-prompts bundled skills: skipped ${skipped.join(', ')} (frontmatter requires name and description)`);
110
+ }
111
+ return candidates;
112
+ },
113
+ async get(candidate) {
114
+ const path = candidate.locator;
115
+ return {
116
+ name: candidate.name,
117
+ description: candidate.description,
118
+ ...candidate.whenToUse !== undefined ? { whenToUse: candidate.whenToUse } : {},
119
+ invocation: candidate.invocation,
120
+ provider: BUNDLED_SKILLS_PROVIDER,
121
+ source: 'bundled',
122
+ resourceBase: candidate.resourceBase,
123
+ content: await readFile(path, 'utf8'),
124
+ path,
125
+ };
126
+ },
127
+ };
128
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Type definitions for the ops-prompts plugin.
3
+ *
4
+ * @module @elinpf/dsh-ops-prompts
5
+ */
6
+ export interface Config {
7
+ /** Whether dynamic reminders are enabled. */
8
+ reminderEnabled: boolean;
9
+ }
10
+ /** A registered methodology prompt section. */
11
+ export interface MethodologyEntry {
12
+ name: string;
13
+ order: number;
14
+ text: string;
15
+ }
16
+ /** A registered dynamic reminder rule. */
17
+ export interface ReminderEntry {
18
+ name: string;
19
+ check: (agent: any) => string | null;
20
+ }
21
+ /** The ops prompt orchestration handle exposed via ctx.get('opsPrompts'). */
22
+ export interface OpsPromptsHandle {
23
+ /** Register a static methodology prompt section. Returns a disposer. */
24
+ registerMethodology(opts: MethodologyEntry): () => void;
25
+ /** Register a dynamic reminder rule evaluated at each agent/pre-step. Returns a disposer. */
26
+ registerReminder(opts: ReminderEntry): () => void;
27
+ }
package/lib/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Type definitions for the ops-prompts plugin.
3
+ *
4
+ * @module @elinpf/dsh-ops-prompts
5
+ */
6
+ export {};
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@elinpf/dsh-ops-prompts",
3
+ "version": "0.1.0",
4
+ "description": "Ops prompt orchestration center — methodology prompts and dynamic reminders for ops preset.",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./invariant": {
14
+ "types": "./lib/invariant.d.ts",
15
+ "default": "./lib/invariant.js"
16
+ },
17
+ "./types": {
18
+ "types": "./lib/types.d.ts",
19
+ "default": "./lib/types.js"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "lib/index.js",
25
+ "lib/invariant.js",
26
+ "lib/skills.js",
27
+ "lib/types.js",
28
+ "lib/**/*.d.ts",
29
+ "skills/",
30
+ "cordis.patch.yml"
31
+ ],
32
+ "dsh": {
33
+ "bundle": {
34
+ "patch": "./cordis.patch.yml"
35
+ }
36
+ },
37
+ "dependencies": {
38
+ "@deepseek-ai/schemastery": "^3.18.1"
39
+ },
40
+ "peerDependencies": {
41
+ "@deepseek-ai/cordis": "^4.0.1",
42
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
43
+ "@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
44
+ "@deepseek-ai/dsh-skill": "^0.1.0-rc.8",
45
+ "@deepseek-ai/dsh-system-prompt": "^0.0.1-rc.1"
46
+ },
47
+ "devDependencies": {
48
+ "@deepseek-ai/dsh-attachment": "0.0.1-rc.5",
49
+ "@deepseek-ai/dsh-brand": "0.0.1-rc.5",
50
+ "@deepseek-ai/dsh-invariants": "0.0.1-rc.5",
51
+ "@deepseek-ai/dsh-scope": "0.0.1-rc.5",
52
+ "@deepseek-ai/dsh-timeout": "0.0.1-rc.5",
53
+ "@deepseek-ai/cordis": "4.0.1",
54
+ "@deepseek-ai/dsh-llm": "0.0.1-rc.5",
55
+ "@deepseek-ai/dsh-skill": "0.1.0-rc.8",
56
+ "@deepseek-ai/dsh-system-prompt": "0.0.1-rc.5",
57
+ "@types/node": "^22.0.0",
58
+ "typescript": "^5.4.0",
59
+ "vitest": "^4.1.11"
60
+ },
61
+ "license": "MIT",
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "scripts": {
66
+ "build": "tsc",
67
+ "typecheck": "tsc --noEmit",
68
+ "test": "vitest run"
69
+ }
70
+ }
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: change
3
+ description: 变更纪律——调查收口后需要 rw 修复时使用的四阶段流程:出方案/验证/人审/执行;验证与执行必须 spawn 干净上下文的子 agent,方案必须五要素齐全。变更是重操作,只能由人用 /change 显式启动。
4
+ whenToUse: trace 调查树收口、需要 rw 权限做修复变更时(人来判断,不由 agent 自动触发)
5
+ disable-model-invocation: true
6
+ ---
7
+
8
+ # 修复变更 — 四阶段纪律
9
+
10
+ 出方案、验证、审核、执行是四种不同的认知活动。混在同一个上下文里会互相污染:验证者被诊断者的确信感传染(确认偏误),执行者被排查中已证伪的错误路径带偏。所以验证和执行必须 spawn 干净上下文的子 agent,绝不在本上下文做。
11
+
12
+ ## 触发条件
13
+
14
+ - 启动:由人在消息首行输入 `/change` 显式启动——通常是在 trace 调查收口(goal 有定论)且修复需要 rw 权限之后。本 skill 不在模型 catalog 中,agent 不会也无法自动加载它
15
+ - 不启动:只读排查、信息收集、状态确认——这些用 kubectl/environment 工具直接做
16
+
17
+ ## 四阶段 workflow
18
+
19
+ ```
20
+ 出方案(本上下文,基于 trace 树)
21
+ → 验证(spawn 子 agent,干净上下文)
22
+ → 打回? 回环修订(同一子会话追问,3 次上限)
23
+ → 人审(人批准方案)
24
+ → 执行(spawn 子 agent,干净上下文,rw 自己申请)
25
+ ```
26
+
27
+ 1. **出方案**:基于 trace 调查树写修复方案——树是现成的压缩诊断产物。方案必须五要素齐全(见下模板),不齐不送验。
28
+ 2. **验证**:spawn 子 agent 送验。它静态推演 + ro 查证环境现状(它有 kubectl 等工具),按「通过 / 打回+理由」回答。
29
+ 3. **回环**:打回则把验证报告作为发现挂回 trace 树(complete 带 summary),修订后在同一子会话追问再送验。3 次上限,超限停止并升级给人。
30
+ 4. **人审+执行**:方案经人批准后,spawn 执行子 agent,prompt 只给已批准的步骤。rw 权限由执行 agent 自己调 request_access 申请。
31
+
32
+ ## 方案模板(五要素)
33
+
34
+ - **目标**:修完后的可验证状态(不是动作,是状态——"X 恢复调度"而不是"重启了 Y")
35
+ - **步骤**:逐条原子操作,每条写明对象(集群/namespace/对象名)
36
+ - **预期结果**:每步执行后应该观察到什么
37
+ - **回滚**:每步怎么撤;不可逆的步骤必须显式标注
38
+ - **影响面**:谁会受影响(哪些服务/用户/数据),执行窗口要不要挑
39
+
40
+ **坏方案**(要素缺失,直接打回):
41
+
42
+ > ceph 的 CSI 锁卡死了,重启 pod 解决。
43
+
44
+ 没有目标状态、没有预期结果、没有回滚、没有影响面——"重启 pod"只是动作不是方案。
45
+
46
+ **好方案**:
47
+
48
+ > 目标:卡死的 PVC 挂载恢复,工作负载重新 Running。
49
+ > 步骤:1) delete pod `<csi-rbdplugin-xxx>`(kube-system,DaemonSet 会自动重建);2) 观察新 pod Ready;3) 确认卡死的挂载解除、工作负载恢复 Running。
50
+ > 预期结果:新 csi-rbdplugin pod Running;卡死的 VolumeAttachment 解除;目标 pod 从 ContainerCreating 转 Running。
51
+ > 回滚:delete pod 由 DaemonSet 自愈,无需回滚;若新 pod 起不来,describe 看原因并升级给人。
52
+ > 影响面:该节点上经此 CSI 插件的挂载在重建窗口内短暂不可用;选择业务低峰执行。
53
+
54
+ ## 验证清单(验证 agent 必须逐项检查)
55
+
56
+ 1. **副作用推演**:每个步骤除了预期结果还会动到什么
57
+ 2. **回滚可行性**:回滚步骤真的可执行吗;不可逆步骤有没有标注
58
+ 3. **前提条件仍成立**:方案依据的诊断结论,环境里还是这样吗(ro 查证)
59
+ 4. **影响面核对**:方案声称的影响面和实际环境一致吗(ro 查证)
60
+ 5. **环境现状**:涉及的对象现在的状态(用 kubectl ro 查,不要信方案的转述)
61
+
62
+ ## spawn 纪律
63
+
64
+ - prompt 必须自包含——子 agent 看不到本会话:给完整方案 + 涉及的环境事实(集群/namespace/对象名)
65
+ - 只给方案,不给你的确信度——"我很确定是 X" 会污染验证
66
+ - 修订送验在同一子会话追问,不重新 spawn(验证上下文要保留)
67
+ - prompt 里必须附上汇报契约(见下)——不定格式,子 agent 会灌给你一屏日志
68
+
69
+ ## 汇报契约(子 agent 的回复格式)
70
+
71
+ 子 agent 的回复是你唯一的信息来源。回复必须短而全:
72
+
73
+ - **≤25 行**:结论(通过 / 打回 / 成功 / 失败)→ 做了什么 → 关键证据 → 未决问题
74
+ - **不贴原始输出**:长输出只留结论 + 可复查的指针(对象名、命名空间、执行的命令),全量输出你自己用 ro 工具去查证
75
+ - **打回理由必须指向具体要素**:哪条回滚不可执行、哪个前提不成立、哪步影响面漏了——"感觉有风险"不接受
76
+
77
+ 完成信号:子 agent 回复结束 = 该任务完成;没回复就是还在跑——等,不要重复 spawn 同一个任务。
78
+
79
+ ## rw 申请纪律
80
+
81
+ - rw 权限由执行 agent 自己调 request_access 申请,**reason 必须写方案摘要**——人审的是方案,不是裸权限
82
+ - 血缘不用自报:门会自动记录你的父会话,人审能看到你是被谁委派的
83
+ - 申请被拒或超时 = 停止并报告人,不重试不绕行
84
+
85
+ ## 失败模式(真实 session 教训)
86
+
87
+ - **跳过 milestone 直接收口 goal**:假设没验证完就 resolve——goal 收口必须等假设逐条证实/证伪
88
+ - **link 当 complete 用**:caused_by 只记因果关系,不改变节点状态;证实要 complete 带 summary
89
+ - **把"我希望它对"带进验证环节**:给验证 agent 的方案里夹带倾向性措辞,等于没验证