claude-code-modes 0.3.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -150,7 +150,7 @@ prompts/
150
150
  modifiers/ Behavioral layers (bold, debug, methodical, director, readonly, context-pacing, speak-plain, tdd, muse, flow, playful)
151
151
  ```
152
152
 
153
- Each base has a `base.json` manifest — a flat JSON array declaring fragment order with `"axes"` and `"modifiers"` as reserved insertion points. The standard base is validated against Claude Code **v2.1.197**.
153
+ Each base has a `base.json` manifest — a flat JSON array declaring fragment order with `"axes"` and `"modifiers"` as reserved insertion points. The standard base is validated against Claude Code **v2.1.217**.
154
154
 
155
155
  The behavioral layer is composed from three independent axes — **agency** (how much initiative), **quality** (what code standard), and **scope** (how far beyond the request). Presets are just named combinations of these three values.
156
156
 
@@ -198,9 +198,12 @@ claude-mode create --append-system-prompt "Use Rust, not TypeScript"
198
198
  Pass flags through to Claude Code:
199
199
 
200
200
  ```bash
201
- claude-mode create -- --verbose --model sonnet
201
+ claude-mode create --model sonnet # model choice also lands in the prompt's environment info
202
+ claude-mode create -- --verbose # anything after -- goes to claude verbatim
202
203
  ```
203
204
 
205
+ The environment section of the assembled prompt reports the model claude will actually run: from `--model` (before or after `--`), else the `ANTHROPIC_MODEL` env var, else Claude settings files (`.claude/settings.local.json`, `.claude/settings.json`, `~/.claude/settings.json`), else the newest known model.
206
+
204
207
  Debug the assembled prompt:
205
208
 
206
209
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-modes",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "description": "Behaviorally-tuned system prompts for Claude Code",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,4 +8,4 @@ Examples of the kind of risky actions that warrant user confirmation:
8
8
  - Actions visible to others or that affect shared state: pushing code, creating/closing/commenting on PRs or issues, sending messages (Slack, email, GitHub), posting to external services, modifying shared infrastructure or permissions
9
9
  - Uploading content to third-party web tools (diagram renderers, pastebins, gists) publishes it - consider whether it could be sensitive before sending, since it may be cached or indexed even if later deleted.
10
10
 
11
- When you encounter an obstacle, do not use destructive actions as a shortcut to simply make it go away. For instance, try to identify root causes and fix underlying issues rather than bypassing safety checks (e.g. --no-verify). If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting, as it may represent the user's in-progress work. For example, typically resolve merge conflicts rather than discarding changes; similarly, if a lock file exists, investigate what process holds it rather than deleting it. In short: only take risky actions carefully, and when in doubt, ask before acting. Follow both the spirit and letter of these instructions - measure twice, cut once.
11
+ When you encounter an obstacle, do not use destructive actions as a shortcut to simply make it go away. For instance, try to identify root causes and fix underlying issues rather than bypassing safety checks (e.g. --no-verify). If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting, as it may represent the user's in-progress work. If you're unsure whether the user would want something kept, prefer a reversible step (move it aside, rename it, or stash it) over deleting; files you created yourself this session (scratch outputs, experiment intermediates) are yours to clean up freely. For example, typically resolve merge conflicts rather than discarding changes; similarly, if a lock file exists, investigate what process holds it rather than deleting it. In a git repository, run `git status` before any command that could discard uncommitted work (git checkout/restore/reset/clean, rm -rf on a repo path, restoring from a snapshot), and stash (with `-u` for untracked) or commit anything you find first. And when staging or committing: review what's included (`git status` after a broad `git add`), and if you see anything suspicious that might reveal secrets — even if the filename looks innocuous — double-check the file's contents before pushing. In short: only take risky actions carefully, and when in doubt, ask before acting. Follow both the spirit and letter of these instructions - measure twice, cut once.
@@ -0,0 +1,16 @@
1
+ # Style: Declaudified
2
+
3
+ How to write everything the user reads — answers, summaries, explanations, commit messages, docs.
4
+
5
+ - Lead with the answer. State the point first, then support it.
6
+ - Cut filler and metadiscourse — language that narrates the communicating instead of communicating: "let me lay out," "it's worth noting," "as mentioned above." Say the thing; don't say you're about to say it.
7
+ - Delete your opening sentence. The real first sentence is usually the second or third — cut the runway that sets up the answer instead of giving it.
8
+ - Do add relevant surrounding context that hasn't already been established in the conversation — don't assume the user has perfect repo or domain knowledge. Adding context they lack is welcome; restating what they just told you is not.
9
+ - Don't signpost structure. A list or table is self-evident; don't preface it with "here's a table of."
10
+ - Plain language: real names for real things, minimal jargon, no padding adjectives. Use real terminology; don't invent labels ("status," not "milestones").
11
+ - Drop dead tech-metaphors and stock phrases ("ship," "load-bearing," "first-class," "surface" as a verb, "seamless," "leverage," "robust"). Use the plain word or cut it; keep "ship" only for releasing software (else deliver / finish / send / hand off).
12
+ - Don't state a cause without evidence — label speculation or leave it out.
13
+
14
+ In short: say the thing; don't say you're about to say it, and don't say you understood the question.
15
+
16
+ References: George Orwell, "Politics and the English Language" (his plain-English rules); Strunk & White, *The Elements of Style* ("omit needless words"); Joseph Williams, *Style: Toward Clarity and Grace* (on cutting metadiscourse).
package/src/args.ts CHANGED
@@ -3,6 +3,7 @@ import { parseArgs } from "node:util";
3
3
  export interface ParsedArgs {
4
4
  base?: string;
5
5
  preset: string | null;
6
+ style?: string;
6
7
  overrides: {
7
8
  agency?: string;
8
9
  quality?: string;
@@ -17,10 +18,22 @@ export interface ParsedArgs {
17
18
  forwarded: {
18
19
  appendSystemPrompt?: string;
19
20
  appendSystemPromptFile?: string;
21
+ model?: string;
20
22
  };
23
+ /** Model claude will run — from --model, or peeked from after `--`; drives env detection */
24
+ modelHint: string | null;
21
25
  passthroughArgs: string[];
22
26
  }
23
27
 
28
+ // After `--` everything is passed verbatim, but the model choice still shapes
29
+ // the environment section — peek without disturbing the passthrough list
30
+ function peekModelAfterDashDash(args: string[]): string | null {
31
+ const flagIdx = args.indexOf("--model");
32
+ if (flagIdx >= 0 && flagIdx + 1 < args.length) return args[flagIdx + 1];
33
+ const inline = args.find((arg) => arg.startsWith("--model="));
34
+ return inline ? inline.slice("--model=".length) : null;
35
+ }
36
+
24
37
  export function parseCliArgs(argv: string[]): ParsedArgs {
25
38
  // Split at -- separator
26
39
  const dashDashIdx = argv.indexOf("--");
@@ -34,12 +47,14 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
34
47
  agency: { type: "string" },
35
48
  quality: { type: "string" },
36
49
  scope: { type: "string" },
50
+ style: { type: "string" },
37
51
  modifier: { type: "string", multiple: true },
38
52
  readonly: { type: "boolean" },
39
53
  print: { type: "boolean" },
40
54
  "context-pacing": { type: "boolean" },
41
55
  "append-system-prompt": { type: "string" },
42
56
  "append-system-prompt-file": { type: "string" },
57
+ model: { type: "string" },
43
58
  "system-prompt": { type: "string" },
44
59
  "system-prompt-file": { type: "string" },
45
60
  help: { type: "boolean" },
@@ -78,8 +93,8 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
78
93
 
79
94
  // Collect unknown flags for passthrough
80
95
  const knownFlags = new Set([
81
- "base", "agency", "quality", "scope", "modifier", "readonly", "print", "context-pacing",
82
- "append-system-prompt", "append-system-prompt-file",
96
+ "base", "agency", "quality", "scope", "style", "modifier", "readonly", "print", "context-pacing",
97
+ "append-system-prompt", "append-system-prompt-file", "model",
83
98
  "system-prompt", "system-prompt-file", "help", "version",
84
99
  ]);
85
100
  const unknownPassthrough: string[] = [];
@@ -102,6 +117,7 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
102
117
  return {
103
118
  base: values.base as string | undefined,
104
119
  preset,
120
+ style: values.style as string | undefined,
105
121
  overrides,
106
122
  modifiers: {
107
123
  readonly: values.readonly === true,
@@ -112,7 +128,9 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
112
128
  forwarded: {
113
129
  appendSystemPrompt: values["append-system-prompt"] as string | undefined,
114
130
  appendSystemPromptFile: values["append-system-prompt-file"] as string | undefined,
131
+ model: values.model as string | undefined,
115
132
  },
133
+ modelHint: (values.model as string | undefined) ?? peekModelAfterDashDash(afterDashDash),
116
134
  passthroughArgs,
117
135
  };
118
136
  }
package/src/assemble.ts CHANGED
@@ -116,6 +116,15 @@ export function getFragmentOrder(mode: ModeConfig, promptsDir: string): string[]
116
116
  }
117
117
  }
118
118
  }
119
+ // Style fragment rides the axes insertion point but applies even in
120
+ // none mode — an explicit --style is honored like explicit modifiers
121
+ if (mode.style) {
122
+ if (isAbsolute(mode.style)) {
123
+ fragments.push(mode.style);
124
+ } else {
125
+ fragments.push(`style/${mode.style}.md`);
126
+ }
127
+ }
119
128
  } else if (entry === "modifiers") {
120
129
  // All modifiers are fragment paths — just add them
121
130
  for (const modPath of mode.modifiers) {
package/src/build-info.ts CHANGED
@@ -12,6 +12,6 @@ export interface BuildInfo {
12
12
  export const BUILD_INFO: BuildInfo = {
13
13
  "repo": "https://github.com/nklisch/claude-code-modes",
14
14
  "branch": null,
15
- "commit": "370493c",
15
+ "commit": "6303b59",
16
16
  "dirty": false
17
17
  };
@@ -98,7 +98,7 @@ function main(): void {
98
98
  }
99
99
 
100
100
  // Detect environment and build template vars
101
- const env = detectEnv();
101
+ const env = detectEnv(parsed.modelHint);
102
102
  const templateVars = buildTemplateVars(env);
103
103
 
104
104
  // Assemble the prompt
@@ -127,6 +127,9 @@ function main(): void {
127
127
  if (parsed.forwarded.appendSystemPromptFile) {
128
128
  claudeArgs.push("--append-system-prompt-file", parsed.forwarded.appendSystemPromptFile);
129
129
  }
130
+ if (parsed.forwarded.model) {
131
+ claudeArgs.push("--model", parsed.forwarded.model);
132
+ }
130
133
 
131
134
  // Add passthrough args
132
135
  claudeArgs.push(...parsed.passthroughArgs);
package/src/cli.ts CHANGED
@@ -119,7 +119,7 @@ async function main(): Promise<void> {
119
119
  }
120
120
 
121
121
  // Detect environment and build template vars
122
- const env = detectEnv();
122
+ const env = detectEnv(parsed.modelHint);
123
123
  const templateVars = buildTemplateVars(env);
124
124
 
125
125
  // Assemble the prompt
@@ -150,6 +150,9 @@ async function main(): Promise<void> {
150
150
  if (parsed.forwarded.appendSystemPromptFile) {
151
151
  claudeArgs.push("--append-system-prompt-file", parsed.forwarded.appendSystemPromptFile);
152
152
  }
153
+ if (parsed.forwarded.model) {
154
+ claudeArgs.push("--model", parsed.forwarded.model);
155
+ }
153
156
 
154
157
  // Add passthrough args
155
158
  claudeArgs.push(...parsed.passthroughArgs);
package/src/config-cli.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  checkModifierNameCollision,
9
9
  checkPresetNameCollision,
10
10
  checkAxisValueCollision,
11
+ checkStyleNameCollision,
11
12
  } from "./config.js";
12
13
  import { AXIS_BUILTINS } from "./types.js";
13
14
  const VALID_AXES = Object.keys(AXIS_BUILTINS) as Array<"agency" | "quality" | "scope">;
@@ -120,6 +121,33 @@ function configRemoveModifier(configPath: string, name: string): void {
120
121
  );
121
122
  }
122
123
 
124
+ function configAddStyle(configPath: string, name: string, mdPath: string): void {
125
+ checkStyleNameCollision(name);
126
+ const config = readConfig(configPath);
127
+ config.styles = { ...config.styles, [name]: mdPath };
128
+ writeConfig(configPath, config);
129
+ process.stdout.write(
130
+ `Registered style "${name}" in ${configFileName(configPath)}\n`
131
+ );
132
+ }
133
+
134
+ function configRemoveStyle(configPath: string, name: string): void {
135
+ const config = readConfig(configPath);
136
+ const styles = config.styles ?? {};
137
+ if (!(name in styles)) {
138
+ throw new Error(
139
+ `Style "${name}" not found in ${configFileName(configPath)}`
140
+ );
141
+ }
142
+ const updated = { ...styles };
143
+ delete updated[name];
144
+ config.styles = updated;
145
+ writeConfig(configPath, config);
146
+ process.stdout.write(
147
+ `Unregistered style "${name}" from ${configFileName(configPath)}\n`
148
+ );
149
+ }
150
+
123
151
  function configAddAxis(configPath: string, axis: string, name: string, mdPath: string): void {
124
152
  if (!VALID_AXES.includes(axis as ValidAxis)) {
125
153
  throw new Error(
@@ -173,6 +201,7 @@ function configAddPreset(configPath: string, name: string, flags: string[]): voi
173
201
  agency: { type: "string" },
174
202
  quality: { type: "string" },
175
203
  scope: { type: "string" },
204
+ style: { type: "string" },
176
205
  modifier: { type: "string", multiple: true },
177
206
  readonly: { type: "boolean" },
178
207
  "context-pacing": { type: "boolean" },
@@ -184,6 +213,7 @@ function configAddPreset(configPath: string, name: string, flags: string[]): voi
184
213
  if (values.agency !== undefined) presetDef.agency = values.agency;
185
214
  if (values.quality !== undefined) presetDef.quality = values.quality;
186
215
  if (values.scope !== undefined) presetDef.scope = values.scope;
216
+ if (values.style !== undefined) presetDef.style = values.style;
187
217
  if (values.modifier !== undefined && values.modifier.length > 0) {
188
218
  presetDef.modifiers = values.modifier as string[];
189
219
  }
@@ -238,6 +268,8 @@ Subcommands:
238
268
  remove-default <name> Remove from defaultModifiers
239
269
  add-modifier <name> <path> Register a named modifier
240
270
  remove-modifier <name> Unregister a named modifier
271
+ add-style <name> <path> Register a named style
272
+ remove-style <name> Unregister a named style
241
273
  add-axis <axis> <name> <path> Register custom axis value
242
274
  remove-axis <axis> <name> Unregister custom axis value
243
275
  add-preset <name> [flags] Create a custom preset
@@ -247,6 +279,7 @@ Flags for add-preset:
247
279
  --agency <value>
248
280
  --quality <value>
249
281
  --scope <value>
282
+ --style <value>
250
283
  --modifier <name> (repeatable)
251
284
  --readonly
252
285
  --context-pacing`;
@@ -287,6 +320,18 @@ Flags for add-preset:
287
320
  break;
288
321
  }
289
322
 
323
+ case "add-style": {
324
+ if (rest.length < 2) throw new Error("add-style requires <name> <path>");
325
+ configAddStyle(configPath, rest[0], rest[1]);
326
+ break;
327
+ }
328
+
329
+ case "remove-style": {
330
+ if (rest.length < 1) throw new Error("remove-style requires <name>");
331
+ configRemoveStyle(configPath, rest[0]);
332
+ break;
333
+ }
334
+
290
335
  case "add-axis": {
291
336
  if (rest.length < 3) throw new Error("add-axis requires <axis> <name> <path>");
292
337
  configAddAxis(configPath, rest[0], rest[1], rest[2]);
package/src/config.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, existsSync } from "node:fs";
2
2
  import { join, dirname, resolve, isAbsolute } from "node:path";
3
3
  import { homedir } from "node:os";
4
- import { PRESET_NAMES, BUILTIN_MODIFIER_NAMES, AXIS_BUILTINS, BUILTIN_BASE_NAMES, isBuiltinModifier, isPresetName, isBuiltinBase, isBuiltinAxisValue } from "./types.js";
4
+ import { PRESET_NAMES, BUILTIN_MODIFIER_NAMES, AXIS_BUILTINS, BUILTIN_BASE_NAMES, STYLE_VALUES, isBuiltinModifier, isPresetName, isBuiltinBase, isBuiltinAxisValue, isBuiltinStyle } from "./types.js";
5
5
 
6
6
  /**
7
7
  * Matches paths that reference potentially sensitive files (SSH keys, credentials, etc.).
@@ -33,6 +33,8 @@ function validateConfigDefinedPath(path: string, context: string, configPath: st
33
33
  export interface UserConfig {
34
34
  defaultBase?: string;
35
35
  bases?: Record<string, string>; // name → directory path (relative to config dir)
36
+ defaultStyle?: string;
37
+ styles?: Record<string, string>; // name → .md path (relative to config dir)
36
38
  defaultModifiers?: string[];
37
39
  modifiers?: Record<string, string>;
38
40
  axes?: {
@@ -48,6 +50,7 @@ export interface CustomPresetDef {
48
50
  agency?: string;
49
51
  quality?: string;
50
52
  scope?: string;
53
+ style?: string;
51
54
  modifiers?: string[];
52
55
  readonly?: boolean;
53
56
  contextPacing?: boolean;
@@ -76,6 +79,15 @@ export function checkPresetNameCollision(name: string): void {
76
79
  }
77
80
  }
78
81
 
82
+ /** Throws if name collides with a built-in style name. */
83
+ export function checkStyleNameCollision(name: string): void {
84
+ if (isBuiltinStyle(name)) {
85
+ throw new Error(
86
+ `"${name}" is a built-in style name (${STYLE_VALUES.join(", ")}); choose a different name`
87
+ );
88
+ }
89
+ }
90
+
79
91
  /** Throws if name collides with a built-in base name. */
80
92
  export function checkBaseNameCollision(name: string): void {
81
93
  if (isBuiltinBase(name)) {
@@ -204,6 +216,22 @@ function validateConfig(raw: unknown, configPath: string): UserConfig {
204
216
  for (const key of Object.keys(bases)) checkBaseNameCollision(key);
205
217
  }
206
218
 
219
+ // Validate defaultStyle
220
+ if (obj.defaultStyle !== undefined && typeof obj.defaultStyle !== "string") {
221
+ throw new Error(
222
+ `Invalid config file ${configPath}: "defaultStyle" must be a string`
223
+ );
224
+ }
225
+
226
+ // Validate styles map
227
+ if (obj.styles !== undefined) {
228
+ const styles = validateStringRecord(obj.styles, "styles", configPath);
229
+ for (const [key, val] of Object.entries(styles)) {
230
+ checkStyleNameCollision(key);
231
+ validateConfigDefinedPath(val, `"styles.${key}"`, configPath);
232
+ }
233
+ }
234
+
207
235
  // Validate defaultModifiers
208
236
  if (obj.defaultModifiers !== undefined) {
209
237
  const defaultModifiers = validateStringArray(obj.defaultModifiers, "defaultModifiers", configPath);
@@ -262,7 +290,7 @@ function validateConfig(raw: unknown, configPath: string): UserConfig {
262
290
  `Invalid config file ${configPath}: preset "${presetName}.base" must be a string`
263
291
  );
264
292
  }
265
- for (const field of ["agency", "quality", "scope"] as const) {
293
+ for (const field of ["agency", "quality", "scope", "style"] as const) {
266
294
  if (def[field] !== undefined && typeof def[field] !== "string") {
267
295
  throw new Error(
268
296
  `Invalid config file ${configPath}: preset "${presetName}.${field}" must be a string`
@@ -45,7 +45,7 @@ Examples of the kind of risky actions that warrant user confirmation:
45
45
  - Actions visible to others or that affect shared state: pushing code, creating/closing/commenting on PRs or issues, sending messages (Slack, email, GitHub), posting to external services, modifying shared infrastructure or permissions
46
46
  - Uploading content to third-party web tools (diagram renderers, pastebins, gists) publishes it - consider whether it could be sensitive before sending, since it may be cached or indexed even if later deleted.
47
47
 
48
- When you encounter an obstacle, do not use destructive actions as a shortcut to simply make it go away. For instance, try to identify root causes and fix underlying issues rather than bypassing safety checks (e.g. --no-verify). If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting, as it may represent the user's in-progress work. For example, typically resolve merge conflicts rather than discarding changes; similarly, if a lock file exists, investigate what process holds it rather than deleting it. In short: only take risky actions carefully, and when in doubt, ask before acting. Follow both the spirit and letter of these instructions - measure twice, cut once.
48
+ When you encounter an obstacle, do not use destructive actions as a shortcut to simply make it go away. For instance, try to identify root causes and fix underlying issues rather than bypassing safety checks (e.g. --no-verify). If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting, as it may represent the user's in-progress work. If you're unsure whether the user would want something kept, prefer a reversible step (move it aside, rename it, or stash it) over deleting; files you created yourself this session (scratch outputs, experiment intermediates) are yours to clean up freely. For example, typically resolve merge conflicts rather than discarding changes; similarly, if a lock file exists, investigate what process holds it rather than deleting it. In a git repository, run \`git status\` before any command that could discard uncommitted work (git checkout/restore/reset/clean, rm -rf on a repo path, restoring from a snapshot), and stash (with \`-u\` for untracked) or commit anything you find first. And when staging or committing: review what's included (\`git status\` after a broad \`git add\`), and if you see anything suspicious that might reveal secrets — even if the filename looks innocuous — double-check the file's contents before pushing. In short: only take risky actions carefully, and when in doubt, ask before acting. Follow both the spirit and letter of these instructions - measure twice, cut once.
49
49
  `,
50
50
  "base/tools.md": `# Using your tools
51
51
  - Use your dedicated tools instead of shell equivalents. Read works better than cat or grep. Editing via sed or awk is error-prone and slow compared to Edit or your global search-and-replace tools. Using pgrep or echo for process monitoring just slows us down without adding control. Bash tools require user approval and may be rejected, especially in a sequence — calling them when a dedicated tool would do is a cost we don't need to pay.
@@ -533,6 +533,23 @@ Stay strictly within the bounds of what was requested.
533
533
  - Do not refactor, rename, or reorganize anything that isn't directly required by the task.
534
534
  - If the request is to change function X, change function X. Do not also update its callers, its tests, or its documentation unless the request explicitly includes those.
535
535
  - If completing the request requires changing more code than expected, pause and confirm the scope with the user before proceeding.
536
+ `,
537
+ "style/declaudified.md": `# Style: Declaudified
538
+
539
+ How to write everything the user reads — answers, summaries, explanations, commit messages, docs.
540
+
541
+ - Lead with the answer. State the point first, then support it.
542
+ - Cut filler and metadiscourse — language that narrates the communicating instead of communicating: "let me lay out," "it's worth noting," "as mentioned above." Say the thing; don't say you're about to say it.
543
+ - Delete your opening sentence. The real first sentence is usually the second or third — cut the runway that sets up the answer instead of giving it.
544
+ - Do add relevant surrounding context that hasn't already been established in the conversation — don't assume the user has perfect repo or domain knowledge. Adding context they lack is welcome; restating what they just told you is not.
545
+ - Don't signpost structure. A list or table is self-evident; don't preface it with "here's a table of."
546
+ - Plain language: real names for real things, minimal jargon, no padding adjectives. Use real terminology; don't invent labels ("status," not "milestones").
547
+ - Drop dead tech-metaphors and stock phrases ("ship," "load-bearing," "first-class," "surface" as a verb, "seamless," "leverage," "robust"). Use the plain word or cut it; keep "ship" only for releasing software (else deliver / finish / send / hand off).
548
+ - Don't state a cause without evidence — label speculation or leave it out.
549
+
550
+ In short: say the thing; don't say you're about to say it, and don't say you understood the question.
551
+
552
+ References: George Orwell, "Politics and the English Language" (his plain-English rules); Strunk & White, *The Elements of Style* ("omit needless words"); Joseph Williams, *Style: Toward Clarity and Grace* (on cutting metadiscourse).
536
553
  `,
537
554
  "modifiers/readonly.md": `# Read-only mode
538
555
 
package/src/env.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { execSync } from "node:child_process";
2
- import { basename } from "node:path";
3
- import type { EnvInfo, TemplateVars } from "./types.js";
2
+ import { readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { basename, join } from "node:path";
5
+ import type { EnvInfo, ModelInfo, TemplateVars } from "./types.js";
4
6
 
5
7
  function exec(command: string): string | null {
6
8
  try {
@@ -18,7 +20,77 @@ function exec(command: string): string | null {
18
20
  }
19
21
  }
20
22
 
21
- export function detectEnv(): EnvInfo {
23
+ // Model metadata table extracted from the Claude Code binary — update when Claude Code updates.
24
+ // Extraction: grep the native binary for `{id:"claude-...,display_name:...,knowledge_cutoff:...`
25
+ const MODEL_TABLE: readonly ModelInfo[] = [
26
+ { id: "claude-fable-5", name: "Fable 5", cutoff: "January 2026" },
27
+ { id: "claude-mythos-5", name: "Mythos 5", cutoff: "January 2026" },
28
+ { id: "claude-opus-4-8", name: "Opus 4.8", cutoff: "January 2026" },
29
+ { id: "claude-opus-4-7", name: "Opus 4.7", cutoff: "January 2026" },
30
+ { id: "claude-opus-4-6", name: "Opus 4.6", cutoff: "May 2025" },
31
+ { id: "claude-opus-4-5", name: "Opus 4.5", cutoff: "May 2025" },
32
+ { id: "claude-opus-4-1", name: "Opus 4.1", cutoff: "January 2025" },
33
+ { id: "claude-opus-4-0", name: "Opus 4", cutoff: "January 2025" },
34
+ { id: "claude-sonnet-5", name: "Sonnet 5", cutoff: "January 2026" },
35
+ { id: "claude-sonnet-4-6", name: "Sonnet 4.6", cutoff: "August 2025" },
36
+ { id: "claude-sonnet-4-5", name: "Sonnet 4.5", cutoff: "January 2025" },
37
+ { id: "claude-sonnet-4-0", name: "Sonnet 4", cutoff: "January 2025" },
38
+ { id: "claude-haiku-4-5", name: "Haiku 4.5", cutoff: "February 2025" },
39
+ ] as const;
40
+
41
+ // Aliases resolve to the newest model of the family; opusplan executes on opus
42
+ const MODEL_ALIASES: Record<string, string> = {
43
+ opus: "claude-opus-4-8",
44
+ opusplan: "claude-opus-4-8",
45
+ sonnet: "claude-sonnet-5",
46
+ haiku: "claude-haiku-4-5",
47
+ fable: "claude-fable-5",
48
+ };
49
+
50
+ const DEFAULT_MODEL: ModelInfo = MODEL_TABLE[0];
51
+
52
+ export function resolveModel(raw: string | null | undefined): ModelInfo {
53
+ if (!raw || raw === "default") return DEFAULT_MODEL;
54
+
55
+ const has1m = raw.endsWith("[1m]");
56
+ const base = has1m ? raw.slice(0, -"[1m]".length) : raw;
57
+ const id = MODEL_ALIASES[base] ?? base;
58
+
59
+ // Exact id first, then dated variants like claude-haiku-4-5-20251001
60
+ const entry =
61
+ MODEL_TABLE.find((m) => m.id === id) ?? MODEL_TABLE.find((m) => id.startsWith(`${m.id}-`));
62
+ if (!entry) {
63
+ // Unknown model — likely newer than the table; surface the id as the name
64
+ // and keep the newest known cutoff rather than inventing one
65
+ return { name: id, id: has1m ? `${id}[1m]` : id, cutoff: DEFAULT_MODEL.cutoff };
66
+ }
67
+
68
+ return {
69
+ name: has1m ? `${entry.name} (1M context)` : entry.name,
70
+ id: has1m ? `${id}[1m]` : id,
71
+ cutoff: entry.cutoff,
72
+ };
73
+ }
74
+
75
+ // Claude Code reads its model setting from these files, most specific first
76
+ function readConfiguredModel(cwd: string): string | null {
77
+ const candidates = [
78
+ join(cwd, ".claude", "settings.local.json"),
79
+ join(cwd, ".claude", "settings.json"),
80
+ join(homedir(), ".claude", "settings.json"),
81
+ ];
82
+ for (const path of candidates) {
83
+ try {
84
+ const settings = JSON.parse(readFileSync(path, "utf8"));
85
+ if (typeof settings.model === "string" && settings.model !== "") return settings.model;
86
+ } catch {
87
+ // Missing or malformed settings file — try the next candidate
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+
93
+ export function detectEnv(modelArg?: string | null): EnvInfo {
22
94
  const cwd = process.cwd();
23
95
  const isGit = exec("git rev-parse --is-inside-work-tree") === "true";
24
96
 
@@ -39,15 +111,11 @@ export function detectEnv(): EnvInfo {
39
111
  const platform = exec("uname -s")?.toLowerCase() ?? "unknown";
40
112
  const shell = basename(process.env.SHELL || "bash");
41
113
  const osVersion = exec("uname -sr") ?? "unknown";
114
+ const model = resolveModel(modelArg ?? process.env.ANTHROPIC_MODEL ?? readConfiguredModel(cwd));
42
115
 
43
- return { cwd, isGit, isWorktree, gitBranch, gitStatus, gitLog, platform, shell, osVersion };
116
+ return { cwd, isGit, isWorktree, gitBranch, gitStatus, gitLog, platform, shell, osVersion, model };
44
117
  }
45
118
 
46
- // Hardcoded model info — update when Claude Code updates
47
- const MODEL_NAME = "Claude Opus 4.8";
48
- const MODEL_ID = "claude-opus-4-8";
49
- const KNOWLEDGE_CUTOFF = "January 2026";
50
-
51
119
  export function buildTemplateVars(env: EnvInfo): TemplateVars {
52
120
  let gitStatusBlock = "";
53
121
  if (env.isGit) {
@@ -63,7 +131,8 @@ export function buildTemplateVars(env: EnvInfo): TemplateVars {
63
131
  }
64
132
 
65
133
  const worktreeNotice = env.isWorktree
66
- ? "\n - This is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT `cd` to the original repository root."
134
+ ? "\n - This is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT `cd` to the original repository root." +
135
+ "\n - The git stash stack is shared with the main checkout and all other worktrees, and other Claude sessions may push or pop it concurrently. Never use bare `git stash` / `git stash pop` — you could pop another session's changes. Prefer a temporary WIP commit to set work aside; if you must stash, use `git stash push -u -m \"<unique-tag>\"`, immediately capture your entry's SHA via `git stash list --format='%H %gs'`, restore with `git stash apply <sha>` (not pop), and afterwards drop the entry, re-finding its current `stash@{n}` by tag first."
67
136
  : "";
68
137
 
69
138
  return {
@@ -72,9 +141,9 @@ export function buildTemplateVars(env: EnvInfo): TemplateVars {
72
141
  PLATFORM: env.platform,
73
142
  SHELL: env.shell,
74
143
  OS_VERSION: env.osVersion,
75
- MODEL_NAME,
76
- MODEL_ID,
77
- KNOWLEDGE_CUTOFF,
144
+ MODEL_NAME: env.model.name,
145
+ MODEL_ID: env.model.id,
146
+ KNOWLEDGE_CUTOFF: env.model.cutoff,
78
147
  GIT_STATUS: gitStatusBlock,
79
148
  WORKTREE_NOTICE: worktreeNotice,
80
149
  };
package/src/inspect.ts CHANGED
@@ -66,6 +66,13 @@ function collectConfigDefinedPaths(loadedConfig: LoadedConfig | null): Set<strin
66
66
  }
67
67
  }
68
68
 
69
+ // Style definitions
70
+ if (config.styles) {
71
+ for (const val of Object.values(config.styles)) {
72
+ paths.add(resolveConfigPath(configDir, val));
73
+ }
74
+ }
75
+
69
76
  // Axis definitions
70
77
  if (config.axes) {
71
78
  for (const axisName of ["agency", "quality", "scope"] as const) {
package/src/presets.ts CHANGED
@@ -5,6 +5,7 @@ export interface PresetDefinition {
5
5
  axes: AxisConfig | null;
6
6
  readonly: boolean;
7
7
  base?: string; // default base for this preset
8
+ style?: string; // default style for this preset
8
9
  modifiers: string[]; // built-in modifier names to apply
9
10
  }
10
11
 
package/src/resolve.ts CHANGED
@@ -7,11 +7,13 @@ import {
7
7
  AGENCY_VALUES,
8
8
  QUALITY_VALUES,
9
9
  SCOPE_VALUES,
10
+ STYLE_VALUES,
10
11
  BUILTIN_MODIFIER_NAMES,
11
12
  BUILTIN_BASE_NAMES,
12
13
  PRESET_NAMES,
13
14
  isBuiltinModifier,
14
15
  isBuiltinBase,
16
+ isBuiltinStyle,
15
17
  } from "./types.js";
16
18
  import { resolve as pathResolve, isAbsolute } from "node:path";
17
19
 
@@ -128,6 +130,51 @@ function applyModifiers(
128
130
  }
129
131
  }
130
132
 
133
+ /**
134
+ * Resolves a style value to either a built-in name or an absolute path.
135
+ * Resolution order: built-in values → config-defined names → file path heuristic.
136
+ * Throws with descriptive error if unresolvable.
137
+ */
138
+ function resolveStyleValue(raw: string, loadedConfig: LoadedConfig | null): string {
139
+ // 1. Built-in value
140
+ if (isBuiltinStyle(raw)) return raw;
141
+
142
+ // 2. Config-defined custom name
143
+ const configStyles = loadedConfig?.config.styles;
144
+ if (configStyles && raw in configStyles) {
145
+ return resolveConfigPath(loadedConfig!.configDir, configStyles[raw]);
146
+ }
147
+
148
+ // 3. File path
149
+ if (looksLikeFilePath(raw)) {
150
+ return isAbsolute(raw) ? raw : pathResolve(raw);
151
+ }
152
+
153
+ // 4. Unknown
154
+ const configHint = loadedConfig
155
+ ? ` Config loaded from: ${loadedConfig.configDir}`
156
+ : " No config file found.";
157
+ throw new Error(
158
+ `Unknown --style value: "${raw}". ` +
159
+ `Must be one of: ${STYLE_VALUES.join(", ")}, ` +
160
+ `a name defined in your config, or a file path.${configHint}`
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Resolves the active style, or null when none applies.
166
+ * Priority: CLI --style > config defaultStyle > preset style > none
167
+ */
168
+ function resolveStyle(
169
+ raw: string | undefined,
170
+ loadedConfig: LoadedConfig | null,
171
+ presetStyle: string | undefined,
172
+ ): string | null {
173
+ const value = raw ?? loadedConfig?.config.defaultStyle ?? presetStyle;
174
+ if (value === undefined) return null;
175
+ return resolveStyleValue(value, loadedConfig);
176
+ }
177
+
131
178
  /**
132
179
  * Resolves a base reference to a built-in name or absolute directory path.
133
180
  * Priority: CLI --base > config defaultBase > preset base > "standard"
@@ -190,12 +237,14 @@ export function resolveConfig(
190
237
  // 3. CLI --modifier flags — appended after defaults
191
238
  applyModifiers(parsed.customModifiers, loadedConfig, modifierPaths, "append");
192
239
 
193
- // Handle "none" preset — resolve base before early return
240
+ // Handle "none" preset — resolve base before early return.
241
+ // Explicit --style (or config defaultStyle) still applies, like modifiers.
194
242
  if (parsed.preset === "none") {
195
243
  const base = resolveBase(parsed.base, loadedConfig, undefined);
196
244
  return {
197
245
  base,
198
246
  axes: null,
247
+ style: resolveStyle(parsed.style, loadedConfig, undefined),
199
248
  modifiers: modifierPaths,
200
249
  };
201
250
  }
@@ -204,6 +253,7 @@ export function resolveConfig(
204
253
  let quality: string;
205
254
  let scope: string;
206
255
  let presetBase: string | undefined;
256
+ let presetStyle: string | undefined;
207
257
 
208
258
  if (parsed.preset) {
209
259
  // Check built-in presets first, then config presets
@@ -220,6 +270,7 @@ export function resolveConfig(
220
270
  ? resolveAxisValue(parsed.overrides.scope, "scope", SCOPE_VALUES, loadedConfig)
221
271
  : preset.axes.scope;
222
272
  presetBase = preset.base;
273
+ presetStyle = preset.style;
223
274
 
224
275
  // Apply preset's readonly flag as a modifier
225
276
  if (preset.readonly) {
@@ -233,6 +284,7 @@ export function resolveConfig(
233
284
  // Config-defined preset
234
285
  const customPreset = config.presets[parsed.preset];
235
286
  presetBase = customPreset.base;
287
+ presetStyle = customPreset.style;
236
288
  agency = parsed.overrides.agency
237
289
  ? resolveAxisValue(parsed.overrides.agency, "agency", AGENCY_VALUES, loadedConfig)
238
290
  : customPreset.agency
@@ -284,10 +336,12 @@ export function resolveConfig(
284
336
  }
285
337
 
286
338
  const base = resolveBase(parsed.base, loadedConfig, presetBase);
339
+ const style = resolveStyle(parsed.style, loadedConfig, presetStyle);
287
340
 
288
341
  return {
289
342
  base,
290
343
  axes: { agency, quality, scope },
344
+ style,
291
345
  modifiers: modifierPaths,
292
346
  };
293
347
  }
package/src/types.ts CHANGED
@@ -7,6 +7,13 @@ export type Quality = (typeof QUALITY_VALUES)[number];
7
7
  export const SCOPE_VALUES = ["unrestricted", "adjacent", "narrow"] as const;
8
8
  export type Scope = (typeof SCOPE_VALUES)[number];
9
9
 
10
+ // Built-in style names — writing styles applied on top of any base/axes
11
+ export const STYLE_VALUES = ["declaudified"] as const;
12
+ export type Style = (typeof STYLE_VALUES)[number];
13
+ export function isBuiltinStyle(value: string): value is Style {
14
+ return (STYLE_VALUES as readonly string[]).includes(value);
15
+ }
16
+
10
17
  export const PRESET_NAMES = [
11
18
  "create",
12
19
  "extend",
@@ -69,9 +76,17 @@ export interface AxisConfig {
69
76
  export interface ModeConfig {
70
77
  base: string; // built-in name ("standard", "chill") or absolute path to base directory
71
78
  axes: AxisConfig | null; // null for "none" mode
79
+ style: string | null; // built-in name or absolute path to a custom fragment; null = no style
72
80
  modifiers: string[]; // ordered list of modifier fragment paths (embedded keys or absolute paths)
73
81
  }
74
82
 
83
+ /** Resolved model metadata for env.md substitution */
84
+ export interface ModelInfo {
85
+ name: string;
86
+ id: string;
87
+ cutoff: string;
88
+ }
89
+
75
90
  export interface EnvInfo {
76
91
  cwd: string;
77
92
  isGit: boolean;
@@ -82,6 +97,7 @@ export interface EnvInfo {
82
97
  platform: string;
83
98
  shell: string;
84
99
  osVersion: string;
100
+ model: ModelInfo;
85
101
  }
86
102
 
87
103
  /** Template variables for env.md substitution */
package/src/update.ts CHANGED
@@ -159,11 +159,13 @@ export function classifyInstall(
159
159
  execPath: string = process.execPath,
160
160
  buildInfo: BuildInfo = BUILD_INFO,
161
161
  ): InstallClassification {
162
- // 1. Runtime interpreter → source mode
163
- if (SOURCE_EXEC_NAMES.has(basename(execPath))) {
162
+ // 1. Runtime interpreter → source mode. The npm distribution of bun names
163
+ // its binary "bun.exe" on every platform, so strip the suffix before matching.
164
+ const execName = basename(execPath).replace(/\.exe$/, "");
165
+ if (SOURCE_EXEC_NAMES.has(execName)) {
164
166
  return {
165
167
  kind: "source",
166
- reason: `Running as ${basename(execPath)} runtime (source mode)`,
168
+ reason: `Running as ${execName} runtime (source mode)`,
167
169
  };
168
170
  }
169
171
 
package/src/usage.ts CHANGED
@@ -42,6 +42,11 @@ Axis overrides:
42
42
  --scope <value> Built-in: unrestricted, adjacent, narrow
43
43
  Axis values can also be config-defined names or file paths (.md files).
44
44
 
45
+ Style:
46
+ --style <value> Writing style for output. Built-in: declaudified
47
+ Style can also be a config-defined name or a file path (.md file).
48
+ No style is applied unless set via --style, config defaultStyle, or a preset.
49
+
45
50
  Modifiers:
46
51
  --readonly Prevent file modifications
47
52
  --context-pacing Include context pacing prompt
@@ -51,6 +56,8 @@ Modifiers:
51
56
  Forwarded to claude:
52
57
  --append-system-prompt <text>
53
58
  --append-system-prompt-file <path>
59
+ --model <alias|id> Also sets the model info in the prompt's environment section.
60
+ Without --model, model info comes from ANTHROPIC_MODEL or Claude settings files.
54
61
 
55
62
  Config: .claude-mode.json (project) or ~/.config/claude-mode/config.json (global)
56
63
 
@@ -60,6 +67,7 @@ Examples:
60
67
  claude-mode create
61
68
  claude-mode create --base chill # use the chill base
62
69
  claude-mode create --quality pragmatic
70
+ claude-mode create --style declaudified # lead-with-the-answer writing, no filler
63
71
  claude-mode create --modifier ./my-rules.md
64
72
  claude-mode --agency autonomous --quality ./team-quality.md
65
73
  claude-mode team-default # custom preset from config