pi-native-output-styles 0.5.0 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Changed
6
+
7
+ - Rewrote the leader brief around behaviour instead of rules. It now says what a style may and may not describe, asks for the smallest set of sentences that actually change output, and decides between edit / rewrite / create instead of following a fixed flow. Review stays report-only, and the final report is three fields: what changed, the file path, whether it was verified.
8
+ - The brief is now written in Chinese. It is still plain Markdown in `extensions/prompts/output-style-leader.md`.
9
+
10
+ ### Fixed
11
+
12
+ - Delegation went dormant under the rewrite. Measured over live runs on the same task, the previous brief delegated 4 of 6 times; the permissive phrasing in the rewrite produced 0 of 4, including a run whose request asked for a two-file comparison. Naming a default — "多角度任务(如 review)默认拆开" — restored it: review then split into two children, with report-only and no-child-writes both still holding.
13
+
14
+ ## 0.6.0
15
+
16
+ ### Added
17
+
18
+ - `/output-style config` for the two settings worth persisting. It runs from the user config file, so both apply to every session and project:
19
+ - `config default <style|off>` — the style new sessions start with.
20
+ - `config indicator <status|widget|off>` — where the active style is shown; `status` is the default.
21
+ - Run `config` with no arguments for a dialog per setting. A bare key reads the value back, an unknown key is an error, and a run without a dialog UI prints the config instead.
22
+ - A `widget` indicator renders the style above the editor. Every refresh writes both surfaces, so switching modes can never leave a stale badge.
23
+
24
+ ### Changed
25
+
26
+ - `config` joins the management words in the command router. `configure this style` still routes to the agent.
27
+ - Clearing a saved default now merges state instead of overwriting the file, so it no longer drops the indicator setting.
28
+
3
29
  ## 0.5.0
4
30
 
5
31
  One command, plus a bundled style and an agent that maintains styles.
package/README.md CHANGED
@@ -41,7 +41,17 @@ Then start a **new** session. Extensions do not hot-reload.
41
41
  /output-style 创建一个适合代码 Review 的 output style
42
42
  ```
43
43
 
44
- The rule is one line: *a single word that names a style, `off`, or `none` is management; anything else is a request for the agent.* So `/output-style concise` activates, and `/output-style rewrite concise` asks. To review a style whose name you would otherwise activate, say more than its name.
44
+ **Configure it** `/output-style config`:
45
+
46
+ ```text
47
+ /output-style config interactive: pick the default and the indicator
48
+ /output-style config default caveman set the cross-session default
49
+ /output-style config default off clear it
50
+ /output-style config indicator widget where the active style shows
51
+ /output-style config indicator read one setting back
52
+ ```
53
+
54
+ The rule is one line: *`config` and a single word that names a style, `off`, or `none` are management; anything else is a request for the agent.* So `/output-style concise` activates, and `/output-style rewrite concise` asks. To review a style whose name you would otherwise activate, say more than its name.
45
55
 
46
56
  While composing the command, a hint line under the editor shows both forms.
47
57
 
@@ -61,7 +71,7 @@ Review findings are specific: conflicting instructions, duplicate or unenforceab
61
71
 
62
72
  A review reports; it does not edit. The file is written only when the request asks for a change. In observed runs the agent delegated on its own when a task had several independent angles to check, and worked solo on a small single-file review.
63
73
 
64
- The brief lives in [`extensions/prompts/output-style-leader.md`](extensions/prompts/output-style-leader.md) and is plain Markdown — edit it without touching code.
74
+ The brief lives in [`extensions/prompts/output-style-leader.md`](extensions/prompts/output-style-leader.md) and is plain Markdown — edit it without touching code. It is written in Chinese; translate or replace it freely.
65
75
 
66
76
  ## Bundled styles
67
77
 
@@ -97,12 +107,27 @@ Precedence:
97
107
 
98
108
  ## Config
99
109
 
110
+ `/output-style config` holds the two things that are worth setting once. Both live in `~/.pi/agent/output-styles.json`, so they apply to every session and every project without re-stating anything.
111
+
112
+ | Key | Values | What it does |
113
+ | --- | --- | --- |
114
+ | `default` | a style name, or `off` | The style every new session starts with. Alias: `style`. |
115
+ | `indicator` | `status`, `widget`, `off` | Where the active style shows. Default `status`. |
116
+
117
+ A bare key reads the value back. An unknown key is an error, not a silent no-op. Running `config` with no arguments opens a dialog per setting; in a non-dialog run (print or JSON mode) it prints the current config instead.
118
+
119
+ The indicator is a display preference, so like the default style the personal setting wins over a project one, and a project setting applies when you have none.
120
+
121
+ To set a default without opening dialogs, `/output-style <name> --save` does the same thing.
122
+
123
+ ## File locations
124
+
100
125
  | | Path |
101
126
  | --- | --- |
102
127
  | Project styles | `<repo>/.pi/output-styles/` |
103
128
  | User styles | `~/.pi/agent/output-styles/` |
104
129
  | Project default (`--project`) | `<repo>/.pi/output-styles.json` |
105
- | User default (`--save`) | `~/.pi/agent/output-styles.json` |
130
+ | User default + config (`--save`, `config`) | `~/.pi/agent/output-styles.json` |
106
131
 
107
132
  Environment:
108
133
 
@@ -35,6 +35,7 @@ interface ExtensionUI {
35
35
  setWidget(key: string, lines: string[] | undefined, options?: { placement: "aboveEditor" | "belowEditor" }): void;
36
36
  getEditorText(): string;
37
37
  notify(message: string, type?: NotifyType): void;
38
+ select?(title: string, options: string[]): Promise<string | undefined>;
38
39
  }
39
40
 
40
41
  interface ExtensionContext {
@@ -143,13 +144,25 @@ export function bundledStylesDir(): string {
143
144
 
144
145
  export interface StyleState {
145
146
  active?: string;
147
+ indicator?: IndicatorMode;
148
+ }
149
+
150
+ /** Where the active style is shown. `status` is the default. */
151
+ export const INDICATOR_MODES = ["status", "widget", "off"] as const;
152
+ export type IndicatorMode = (typeof INDICATOR_MODES)[number];
153
+
154
+ export function isIndicatorMode(value: unknown): value is IndicatorMode {
155
+ return typeof value === "string" && (INDICATOR_MODES as readonly string[]).includes(value);
146
156
  }
147
157
 
148
158
  export function readState(file: string): StyleState {
149
159
  try {
150
160
  const parsed: unknown = JSON.parse(readFileSync(file, "utf8"));
151
- if (parsed && typeof parsed === "object" && "active" in parsed && typeof parsed.active === "string") {
152
- return { active: parsed.active };
161
+ if (parsed && typeof parsed === "object") {
162
+ const state: StyleState = {};
163
+ if ("active" in parsed && typeof parsed.active === "string") state.active = parsed.active;
164
+ if ("indicator" in parsed && isIndicatorMode(parsed.indicator)) state.indicator = parsed.indicator;
165
+ return state;
153
166
  }
154
167
  } catch {
155
168
  // missing or malformed → empty
@@ -162,6 +175,13 @@ export function writeState(file: string, state: StyleState): void {
162
175
  writeFileSync(file, JSON.stringify(state, null, 2) + "\n");
163
176
  }
164
177
 
178
+ // Merge, never replace: clearing the default must not drop the indicator
179
+ // setting, and setting the indicator must not drop the default. A key passed
180
+ // as undefined drops out of the JSON, which is how a key is cleared.
181
+ export function updateState(file: string, patch: StyleState): void {
182
+ writeState(file, { ...readState(file), ...patch });
183
+ }
184
+
165
185
  export function userStateFile(): string {
166
186
  return join(configHome(), "output-styles.json");
167
187
  }
@@ -179,6 +199,12 @@ export function resolveActiveName(
179
199
  return sessionActive ?? userState.active ?? projectState.active ?? null;
180
200
  }
181
201
 
202
+ // The indicator is presentation, so it resolves from the same places as the
203
+ // default style but falls back to the status bar.
204
+ export function resolveIndicator(cwd: string): IndicatorMode {
205
+ return readState(userStateFile()).indicator ?? readState(projectStateFile(cwd)).indicator ?? "status";
206
+ }
207
+
182
208
  const MARKER_PREFIX = "<!-- output-styles:";
183
209
 
184
210
  export function styleMarker(style: Style): string {
@@ -224,16 +250,20 @@ export function parseStyleCommandArgs(args: string): StyleCommandArgs {
224
250
  return { name, persist };
225
251
  }
226
252
 
227
- // One command serves both jobs, so the split has to be guessable from the
228
- // words alone. Rule: the request is style management only when it is empty,
229
- // or its single non-flag word is `off`/`none` or a style that exists. Anything
230
- // else is a task for the agent. `/output-style concise` activates; `/output-style
231
- // rewrite concise` asks the agent.
232
- export type StyleCommandRoute = { kind: "manage" } | { kind: "task"; request: string };
253
+ // One command serves every job, so the split has to be guessable from the
254
+ // words alone. Rule, in order: the request is `config` when it starts with the
255
+ // config word; empty, `off`, `none`, or a single word naming an existing style
256
+ // is style management; anything else is a task for the agent. `/output-style
257
+ // concise` activates; `/output-style rewrite concise` asks the agent.
258
+ export type StyleCommandRoute =
259
+ | { kind: "config"; args: string }
260
+ | { kind: "manage" }
261
+ | { kind: "task"; request: string };
233
262
 
234
263
  export function routeStyleCommand(args: string, styleNames: Iterable<string>): StyleCommandRoute {
235
264
  const request = args.trim();
236
265
  if (request.length === 0) return { kind: "manage" };
266
+ if (/^config(?=\s|$)/i.test(request)) return { kind: "config", args: request.replace(/^config\s*/i, "").trim() };
237
267
  const words = request.split(/\s+/).filter(t => t.length > 0 && !t.startsWith("--"));
238
268
  if (words.length === 0) return { kind: "manage" }; // flags only
239
269
  if (words.length > 1) return { kind: "task", request };
@@ -251,13 +281,14 @@ export function resolveStyleName(name: string, styleNames: Iterable<string>): st
251
281
  }
252
282
 
253
283
  const STATUS_KEY = "output-styles";
284
+ const INDICATOR_KEY = "output-styles-indicator";
254
285
  const HINT_KEY = "output-styles-hint";
255
286
  // Persistent ghost hint shown below the editor while `/output-style` is being
256
287
  // composed. Pi only renders inline usage ghost text for builtin commands, so
257
288
  // this widget carries the same message for extension commands.
258
289
  const STYLE_HINT_LINES = [
259
290
  "/output-style <name|off> [--save] [--project]",
260
- "/output-style <ask the agent to review, rewrite, or create a style>",
291
+ "/output-style config default style, indicator",
261
292
  ];
262
293
 
263
294
  // Pure matcher for the widget: show the hint while the input starts with the
@@ -360,9 +391,19 @@ export function resolveActiveStyle(cwd: string, styles?: Map<string, Style>): St
360
391
  return map.get(name) ?? null;
361
392
  }
362
393
 
363
- function refreshStatus(ctx: ExtensionContext, style: Style | null): void {
364
- if (!ctx.hasUI || typeof ctx.ui.setStatus !== "function") return;
365
- ctx.ui.setStatus(STATUS_KEY, style ? `style: ${style.name}` : undefined);
394
+ // One renderer for every call site, so switching the mode can never leave a
395
+ // stale badge behind: both surfaces are written on every refresh, and only the
396
+ // configured one gets text.
397
+ function renderIndicator(ctx: ExtensionContext, style: Style | null): void {
398
+ if (!ctx.hasUI) return;
399
+ const mode = resolveIndicator(ctx.cwd);
400
+ const label = style ? `style: ${style.name}` : undefined;
401
+ if (typeof ctx.ui.setStatus === "function") {
402
+ ctx.ui.setStatus(STATUS_KEY, mode === "status" ? label : undefined);
403
+ }
404
+ if (typeof ctx.ui.setWidget === "function") {
405
+ ctx.ui.setWidget(INDICATOR_KEY, mode === "widget" && label ? [label] : undefined, { placement: "aboveEditor" });
406
+ }
366
407
  }
367
408
 
368
409
  // The Leader brief is plain Markdown next to the extension, so its wording can
@@ -396,9 +437,104 @@ export function buildStyleTask(brief: string, cwd: string, active: Style | null,
396
437
  ].join("\n");
397
438
  }
398
439
 
440
+ export type ConfigKey = "default" | "indicator";
441
+
442
+ export interface ConfigArgs {
443
+ /** null means "no key given" — open the interactive flow. */
444
+ key: ConfigKey | null;
445
+ value: string;
446
+ }
447
+
448
+ const CONFIG_KEY_ALIASES: Record<string, ConfigKey> = {
449
+ default: "default",
450
+ style: "default",
451
+ indicator: "indicator",
452
+ };
453
+
454
+ // `null` means an unrecognised key; `{key: null}` means no key at all.
455
+ export function parseConfigArgs(args: string): ConfigArgs | null {
456
+ const tokens = args.trim().split(/\s+/).filter(t => t.length > 0);
457
+ if (tokens.length === 0) return { key: null, value: "" };
458
+ const key = CONFIG_KEY_ALIASES[tokens[0].toLowerCase()];
459
+ if (!key) return null;
460
+ return { key, value: tokens.slice(1).join(" ") };
461
+ }
462
+
463
+ function describeConfig(cwd: string): string {
464
+ return `Default style (new sessions): ${readState(userStateFile()).active ?? "(none)"}\nStyle indicator: ${resolveIndicator(cwd)}`;
465
+ }
466
+
467
+ function applyConfigValue(key: ConfigKey, value: string, ctx: ExtensionContext, styles: Map<string, Style>): void {
468
+ const raw = value.trim();
469
+
470
+ if (key === "default") {
471
+ if (raw.length === 0) {
472
+ ctx.ui.notify(`Default style (new sessions): ${readState(userStateFile()).active ?? "(none)"}`, "info");
473
+ return;
474
+ }
475
+ if (OFF_WORDS[raw.toLowerCase()]) {
476
+ updateState(userStateFile(), { active: undefined });
477
+ ctx.ui.notify("Default style cleared. New sessions start with no style.", "info");
478
+ renderIndicator(ctx, resolveActiveStyle(ctx.cwd, styles));
479
+ return;
480
+ }
481
+ const name = resolveStyleName(raw, styles.keys());
482
+ if (!name) {
483
+ ctx.ui.notify(`Unknown style "${raw}". Available: ${[...styles.keys()].sort().join(", ") || "(none)"}`, "error");
484
+ return;
485
+ }
486
+ updateState(userStateFile(), { active: name });
487
+ ctx.ui.notify(`Default style → "${name}" for every new session and project.`, "info");
488
+ renderIndicator(ctx, resolveActiveStyle(ctx.cwd, styles));
489
+ return;
490
+ }
491
+
492
+ if (raw.length === 0) {
493
+ ctx.ui.notify(`Style indicator: ${resolveIndicator(ctx.cwd)}`, "info");
494
+ return;
495
+ }
496
+ if (!isIndicatorMode(raw)) {
497
+ ctx.ui.notify(`Indicator must be one of: ${INDICATOR_MODES.join(", ")}`, "error");
498
+ return;
499
+ }
500
+ updateState(userStateFile(), { indicator: raw });
501
+ ctx.ui.notify(`Style indicator → ${raw}`, "info");
502
+ renderIndicator(ctx, resolveActiveStyle(ctx.cwd, styles));
503
+ }
504
+
505
+ // Interactive path: two dialogs, then a summary. Falls back to printing when
506
+ // the run has no dialog-capable UI (print/json modes).
507
+ async function openConfigDialogs(ctx: ExtensionContext, styles: Map<string, Style>): Promise<void> {
508
+ if (typeof ctx.ui.select !== "function") {
509
+ ctx.ui.notify(describeConfig(ctx.cwd), "info");
510
+ return;
511
+ }
512
+
513
+ const current = readState(userStateFile()).active ?? "(none)";
514
+ const chosen = await ctx.ui.select(`Default output style for new sessions (now: ${current})`, [
515
+ "(keep current)",
516
+ "(none)",
517
+ ...[...styles.keys()].sort(),
518
+ ]);
519
+ if (chosen !== undefined && chosen !== "(keep current)") {
520
+ updateState(userStateFile(), { active: chosen === "(none)" ? undefined : chosen });
521
+ }
522
+
523
+ const mode = await ctx.ui.select(`Style indicator (now: ${resolveIndicator(ctx.cwd)})`, [
524
+ "(keep current)",
525
+ ...INDICATOR_MODES,
526
+ ]);
527
+ if (mode !== undefined && mode !== "(keep current)" && isIndicatorMode(mode)) {
528
+ updateState(userStateFile(), { indicator: mode });
529
+ }
530
+
531
+ renderIndicator(ctx, resolveActiveStyle(ctx.cwd, styles));
532
+ ctx.ui.notify(`Saved.\n${describeConfig(ctx.cwd)}`, "info");
533
+ }
534
+
399
535
  export default function outputStyles(pi: ExtensionAPI): void {
400
536
  pi.on("session_start", (_event, ctx) => {
401
- refreshStatus(ctx, resolveActiveStyle(ctx.cwd));
537
+ renderIndicator(ctx, resolveActiveStyle(ctx.cwd));
402
538
  if (started || !ctx.hasUI) return;
403
539
  started = true;
404
540
  startHintPoller(ctx);
@@ -412,14 +548,14 @@ export default function outputStyles(pi: ExtensionAPI): void {
412
548
  try {
413
549
  const style = resolveActiveStyle(ctx.cwd);
414
550
  if (!style) {
415
- refreshStatus(ctx, null);
551
+ renderIndicator(ctx, null);
416
552
  return;
417
553
  }
418
554
  // Apply first; only reflect the style in the status line once the prompt
419
555
  // was actually augmented, so a swallowed throw never advertises a style
420
556
  // the turn did not apply.
421
557
  const systemPrompt = applyStyle(event.systemPrompt ?? "", style);
422
- refreshStatus(ctx, style);
558
+ renderIndicator(ctx, style);
423
559
  return { systemPrompt };
424
560
  } catch {
425
561
  return; // never fail a turn over a styling concern
@@ -430,10 +566,21 @@ export default function outputStyles(pi: ExtensionAPI): void {
430
566
  description:
431
567
  "Select an output style, or ask the agent to review, rewrite, or create one. Usage: /output-style <name|off|what you want> [--save] [--project]",
432
568
  getArgumentCompletions: argumentPrefix => styleCompletions(argumentPrefix, process.cwd()),
433
- handler: (args, ctx) => {
569
+ handler: async (args, ctx) => {
434
570
  const styles = discoverStyles(styleDirs(ctx.cwd));
435
571
  const route = routeStyleCommand(args, styles.keys());
436
572
 
573
+ if (route.kind === "config") {
574
+ const parsed = parseConfigArgs(route.args);
575
+ if (parsed === null) {
576
+ ctx.ui.notify("Config keys: default <style|off>, indicator <status|widget|off>", "error");
577
+ return;
578
+ }
579
+ if (parsed.key === null) await openConfigDialogs(ctx, styles);
580
+ else applyConfigValue(parsed.key, parsed.value, ctx, styles);
581
+ return;
582
+ }
583
+
437
584
  if (route.kind === "task") {
438
585
  let task: string;
439
586
  try {
@@ -478,16 +625,16 @@ export default function outputStyles(pi: ExtensionAPI): void {
478
625
  let offScope = "this session";
479
626
  try {
480
627
  if (persist === "user") {
481
- writeState(userStateFile(), {});
628
+ updateState(userStateFile(), { active: undefined });
482
629
  offScope = "cleared · user default";
483
630
  } else if (persist === "project") {
484
- writeState(projectStateFile(ctx.cwd), {});
631
+ updateState(projectStateFile(ctx.cwd), { active: undefined });
485
632
  offScope = "cleared · project default";
486
633
  }
487
634
  } catch (err) {
488
635
  ctx.ui.notify(`Cleared for this session, but updating the saved default failed: ${String(err)}`, "warning");
489
636
  }
490
- refreshStatus(ctx, null);
637
+ renderIndicator(ctx, null);
491
638
  ctx.ui.notify(`Output style off (${offScope}).`, "info");
492
639
  return;
493
640
  }
@@ -500,16 +647,16 @@ export default function outputStyles(pi: ExtensionAPI): void {
500
647
  let scope = "this session";
501
648
  try {
502
649
  if (persist === "user") {
503
- writeState(userStateFile(), { active: name });
650
+ updateState(userStateFile(), { active: name });
504
651
  scope = "saved · user default";
505
652
  } else if (persist === "project") {
506
- writeState(projectStateFile(ctx.cwd), { active: name });
653
+ updateState(projectStateFile(ctx.cwd), { active: name });
507
654
  scope = "saved · project default";
508
655
  }
509
656
  } catch (err) {
510
657
  ctx.ui.notify(`Applied for this session, but saving failed: ${String(err)}`, "warning");
511
658
  }
512
- refreshStatus(ctx, styles.get(name) ?? null);
659
+ renderIndicator(ctx, styles.get(name) ?? null);
513
660
  ctx.ui.notify(`Output style → "${name}" (${scope}).`, "info");
514
661
  },
515
662
  });
@@ -1,71 +1,103 @@
1
1
  # Output style task
2
2
 
3
- You own this end to end: read the request, decide what work it needs, do it,
4
- then verify what you produced. Finish with a short report of what changed and
5
- where.
3
+ 你负责完成 output style 的修改、新建或重写。
6
4
 
7
- ## What an output style is
5
+ ## 目标
8
6
 
9
- A style defines **how** you behave: voice, structure, level of detail,
10
- interaction style. It is not a home for project architecture, coding rules,
11
- domain knowledge, tooling conventions, or repo facts. Anything like that is a
12
- responsibility-boundary bug: drop it, or say where it belongs instead.
7
+ 先理解用户真正想改变的**输出行为**,再决定怎么改。
13
8
 
14
- ## Delegation
9
+ Output style 只描述:
15
10
 
16
- Decide for yourself whether the task needs other agents. Delegate only when
17
- independent angles genuinely improve the result. A review is the usual case:
18
- prompt quality, responsibility boundaries, and conflicts/redundancy are worth
19
- splitting. Use the `subagent` tool when it is available, and run the children
20
- in one workflow call. If it is not available, do the analysis yourself.
21
- Children report findings back to you and never write files. You do the final
22
- write and the final summary — never hand the file off.
11
+ * 怎么说
12
+ * 怎么组织答案
13
+ * 信息详细到什么程度
14
+ * 如何与用户互动
23
15
 
24
- ## Reviewing
16
+ 不要把项目架构、编码规范、领域知识、工具使用规则、仓库事实等塞进 style。
25
17
 
26
- Review means report, not edit. Do not change the file unless the request also
27
- asks for the change; offer the fix instead.
18
+ ## 工作方式
28
19
 
29
- Name each finding specifically: what is wrong, what it costs, and the fix.
30
- Look for:
20
+ 先检查:
31
21
 
32
- - instructions that conflict with each other
33
- - duplicate, dead, or unenforceable rules
34
- - vague wording that cannot be acted on
35
- - over-constraining the model
36
- - content that belongs to another concern
37
- - AI-slop voice: inflated claims, filler, ceremony
38
- - rules that are hard to follow while actually working
22
+ * 用户当前使用的 style
23
+ * 相关已有 styles
24
+ * 用户描述的实际问题
25
+ * 必要时查看相关文档、实现或进行 research
39
26
 
40
- No praise padding, and do not restate the style back at the user.
27
+ 不要为了“完整”而 research。只有现有信息不足以判断正确做法时才 research。
41
28
 
42
- ## Rewriting
29
+ 然后决定是:
43
30
 
44
- Keep what works, delete what does not, add only what is missing. Fewer sharp
45
- rules beat more rules. Do not add text to look thorough, and do not quietly
46
- widen the scope.
31
+ * **修改**:保留有效部分,删除无效或造成问题的部分
32
+ * **重写**:当现有 style 的方向已经不对时重新设计
33
+ * **新建**:从目标出发写一个最小可用的 style
47
34
 
48
- ## Creating
35
+ 每次先判断需不需要多方视角:需要就并行分给子 agent(有 `subagent` 工具就用),review 这类要多角度查的任务默认拆开;不需要就自己做。子 agent 只报告不写文件,最终文件由你自己写。
49
36
 
50
- Write the smallest style that achieves the goal. `name` is lowercase
51
- kebab-case; `description` is one line and shows up in `/style`.
37
+ 核心原则:
52
38
 
53
- ## File format
39
+ > 少写规则,直接描述期望的行为。
54
40
 
55
- ---
56
- name: <kebab-case>
57
- description: <one line>
58
- ---
59
- <style body>
41
+ 避免:
60
42
 
61
- ## Where to write
43
+ * 提示词腔
44
+ * AI 味
45
+ * 解释规则为什么存在
46
+ * 重复表达
47
+ * 过度约束
48
+ * 模糊但听起来正确的话
49
+ * 为了显得专业而增加细节
62
50
 
63
- Default to the project directory. Write to the user directory only when asked
64
- for personal or global. Never edit a style under a package install path — those
65
- are read-only bundled styles; copy one out instead.
51
+ 如果一句话不能明显改变模型的输出,就不要写。
66
52
 
67
- ## Before you finish
53
+ ## Review
68
54
 
69
- - Re-read what you wrote: frontmatter plus body, nothing else.
70
- - Confirm the file parses and the `name` matches what `/style <name>` expects.
71
- - State the path you wrote and what changed.
55
+ 如果用户要求审核,只报告问题,不直接修改。
56
+
57
+ 重点找:
58
+
59
+ * 会导致输出变差的规则
60
+ * 相互冲突的规则
61
+ * 重复或无效规则
62
+ * 难以执行的规则
63
+ * 不属于 output style 的内容
64
+ * 让回答变得啰嗦、机械、难读的规则
65
+
66
+ 每个问题说明:
67
+
68
+ **问题 → 影响 → 建议**
69
+
70
+ 不要写赞美,也不要重新解释整个 style。
71
+
72
+ ## Style 格式
73
+
74
+ ```md
75
+ ---
76
+ name: <lowercase-kebab-case>
77
+ description: <one line>
78
+ ---
79
+ <style body>
80
+ ```
81
+
82
+ description 用一句话说明这个 style 会带来什么输出变化。
83
+
84
+ style body 应尽可能短。
85
+
86
+ ## 完成前
87
+
88
+ 重新阅读最终文件,从实际使用角度判断:
89
+
90
+ > 如果模型只看到这个 style,它真的会因此产生不同的、更符合目标的输出吗?
91
+
92
+ 确认:
93
+
94
+ * frontmatter 正确
95
+ * name 与文件名一致
96
+ * style 可以正常加载
97
+ * 内容没有明显重复、冲突或越界
98
+
99
+ 最后只报告:
100
+
101
+ * 做了什么
102
+ * 文件路径
103
+ * 是否完成验证
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-native-output-styles",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Named, swappable system-prompt styles for Pi — live /style switcher with native .pi/ directory support.",
5
5
  "keywords": [
6
6
  "pi-package",