@tryinget/pi-modes 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,409 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import {
4
+ CONFIG_DIR_NAME,
5
+ type ExtensionAPI,
6
+ type ExtensionCommandContext,
7
+ type ExtensionContext,
8
+ getAgentDir,
9
+ } from "@earendil-works/pi-coding-agent";
10
+ import { Box, Text } from "@earendil-works/pi-tui";
11
+ import {
12
+ ancestorModeDirectories,
13
+ BUILTIN_MODES,
14
+ composeModePrompt,
15
+ deleteMode,
16
+ loadModes,
17
+ MODE_SCHEMA_VERSION,
18
+ MODE_STATE_TYPE,
19
+ type ModeDefinition,
20
+ type ModeScope,
21
+ modePath,
22
+ parseModeDefinition,
23
+ resolveInitialModeSelection,
24
+ saveMode,
25
+ selectedModeFromEntries,
26
+ } from "../src/modes.ts";
27
+
28
+ const MODE_STATUS_ENTRY_TYPE = "pi-mode-status.v1";
29
+
30
+ interface ModeStatusEntryData {
31
+ summary: string;
32
+ available: string[];
33
+ details: string[];
34
+ diagnostics: string[];
35
+ }
36
+
37
+ function directories(ctx: ExtensionContext | ExtensionCommandContext) {
38
+ return {
39
+ globalDir: join(getAgentDir(), "modes"),
40
+ projectDir: join(ctx.cwd, CONFIG_DIR_NAME, "modes"),
41
+ projectDirs: ancestorModeDirectories(ctx.cwd, CONFIG_DIR_NAME),
42
+ };
43
+ }
44
+
45
+ function parseScopedArguments(args: string): {
46
+ scope: Exclude<ModeScope, "builtin">;
47
+ rest: string;
48
+ } {
49
+ const values = args.trim().split(/\s+/).filter(Boolean);
50
+ const project = values[0] === "--project";
51
+ if (project) values.shift();
52
+ return { scope: project ? "project" : "global", rest: values.join(" ") };
53
+ }
54
+
55
+ function modeTemplate(key: string): ModeDefinition {
56
+ return {
57
+ schemaVersion: MODE_SCHEMA_VERSION,
58
+ key,
59
+ label: key
60
+ .split(/[-_]/)
61
+ .filter(Boolean)
62
+ .map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
63
+ .join(" "),
64
+ description: "Describe when this mode should be used.",
65
+ promptStrategy: "replace_base",
66
+ systemPrompt: "Define the complete static base system prompt for this mode.",
67
+ };
68
+ }
69
+
70
+ export default function modeExtension(pi: ExtensionAPI) {
71
+ let activeKey: string | null = null;
72
+ let cachedModeKeys = BUILTIN_MODES.map((mode) => mode.key);
73
+ const warnedUnavailableKeys = new Set<string>();
74
+
75
+ pi.registerEntryRenderer<ModeStatusEntryData>(
76
+ MODE_STATUS_ENTRY_TYPE,
77
+ (entry, { expanded }, theme) => {
78
+ const data = entry.data ?? {
79
+ summary: "status unavailable",
80
+ available: [],
81
+ details: [],
82
+ diagnostics: [],
83
+ };
84
+ const lines = [
85
+ `${theme.fg("accent", "[mode]")} ${data.summary}`,
86
+ theme.fg(
87
+ "dim",
88
+ `${data.available.length} available · ${data.diagnostics.length} diagnostic(s)`,
89
+ ),
90
+ ];
91
+ if (expanded) {
92
+ lines.push(...data.details.map((line) => theme.fg("muted", line)));
93
+ if (data.available.length > 0) {
94
+ lines.push(theme.fg("dim", `available: ${data.available.join(", ")}`));
95
+ }
96
+ lines.push(...data.diagnostics.map((line) => theme.fg("warning", `warning: ${line}`)));
97
+ }
98
+ const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
99
+ box.addChild(new Text(lines.join("\n"), 0, 0));
100
+ return box;
101
+ },
102
+ );
103
+
104
+ function currentModes(ctx: ExtensionContext | ExtensionCommandContext) {
105
+ const dirs = directories(ctx);
106
+ const loaded = loadModes({
107
+ ...dirs,
108
+ projectTrusted: ctx.isProjectTrusted(),
109
+ });
110
+ cachedModeKeys = loaded.modes.map((mode) => mode.key);
111
+ return loaded;
112
+ }
113
+
114
+ function updateStatus(ctx: ExtensionContext | ExtensionCommandContext) {
115
+ if (!ctx.hasUI) return;
116
+ if (!activeKey) {
117
+ ctx.ui.setStatus("pi-modes", undefined);
118
+ return;
119
+ }
120
+ const mode = currentModes(ctx).modes.find((candidate) => candidate.key === activeKey);
121
+ const label = mode?.label ?? `${activeKey} (unavailable)`;
122
+ ctx.ui.setStatus("pi-modes", ctx.ui.theme.fg(mode ? "accent" : "warning", `mode:${label}`));
123
+ }
124
+
125
+ function activate(key: string | null, ctx: ExtensionCommandContext): void {
126
+ activeKey = key;
127
+ pi.appendEntry(MODE_STATE_TYPE, { key });
128
+ updateStatus(ctx);
129
+ if (ctx.hasUI) {
130
+ ctx.ui.notify(
131
+ key ? `Prompt mode activated: ${key}` : "Prompt mode cleared; using host prompt",
132
+ "info",
133
+ );
134
+ }
135
+ }
136
+
137
+ pi.registerCommand("mode", {
138
+ description: "Select a prompt mode, or use /mode off to restore the host prompt",
139
+ getArgumentCompletions: (prefix) => {
140
+ const normalized = prefix.trim().toLowerCase();
141
+ return ["off", ...currentModeKeys()]
142
+ .filter((key) => key.startsWith(normalized))
143
+ .map((key) => ({ value: key, label: key }));
144
+ },
145
+ handler: async (args, ctx) => {
146
+ const loaded = currentModes(ctx);
147
+ let key = args.trim().toLowerCase();
148
+ if (!key) {
149
+ if (!ctx.hasUI) return;
150
+ const options = [
151
+ "off — Host default",
152
+ ...loaded.modes.map(
153
+ (mode) => `${mode.key} — ${mode.label} [${mode.promptStrategy}/${mode.scope}]`,
154
+ ),
155
+ ];
156
+ const selected = await ctx.ui.select("Select prompt mode", options);
157
+ if (!selected) return;
158
+ key = selected.split(" — ", 1)[0] ?? "";
159
+ }
160
+ if (key === "off" || key === "default" || key === "none") {
161
+ activate(null, ctx);
162
+ return;
163
+ }
164
+ if (!loaded.modes.some((mode) => mode.key === key)) {
165
+ if (ctx.hasUI) ctx.ui.notify(`Unknown prompt mode: ${key}`, "error");
166
+ return;
167
+ }
168
+ activate(key, ctx);
169
+ },
170
+ });
171
+
172
+ function currentModeKeys(): string[] {
173
+ return cachedModeKeys;
174
+ }
175
+
176
+ pi.registerCommand("mode-status", {
177
+ description: "Show a durable active-mode status card and discovery diagnostics",
178
+ handler: async (_args, ctx) => {
179
+ if (!ctx.hasUI) return;
180
+ const loaded = currentModes(ctx);
181
+ const mode = loaded.modes.find((candidate) => candidate.key === activeKey);
182
+ const summary = mode
183
+ ? `${mode.key} — ${mode.label} (${mode.promptStrategy}, ${mode.scope})`
184
+ : activeKey
185
+ ? `${activeKey} unavailable — native SYSTEM.md / host base active`
186
+ : "native SYSTEM.md / host base (no named mode)";
187
+ const details = mode
188
+ ? [
189
+ `strategy: ${mode.promptStrategy}`,
190
+ `scope: ${mode.scope}`,
191
+ `source: ${mode.path ?? "built-in"}`,
192
+ ...(mode.description ? [`description: ${mode.description}`] : []),
193
+ ]
194
+ : [
195
+ "Pi resolves project .pi/SYSTEM.md, global ~/.pi/agent/SYSTEM.md, or its built-in base.",
196
+ ];
197
+ pi.appendEntry<ModeStatusEntryData>(MODE_STATUS_ENTRY_TYPE, {
198
+ summary,
199
+ available: loaded.modes.map((candidate) => candidate.key),
200
+ details,
201
+ diagnostics: loaded.diagnostics.map(
202
+ (diagnostic) => `${diagnostic.path}: ${diagnostic.message}`,
203
+ ),
204
+ });
205
+ },
206
+ });
207
+
208
+ pi.registerCommand("mode-preview", {
209
+ description: "Preview the final prompt for a mode without activating it",
210
+ getArgumentCompletions: (prefix) => {
211
+ const normalized = prefix.trim().toLowerCase();
212
+ return currentModeKeys()
213
+ .filter((key) => key.startsWith(normalized))
214
+ .map((key) => ({ value: key, label: key }));
215
+ },
216
+ handler: async (args, ctx) => {
217
+ const loaded = currentModes(ctx);
218
+ let key = args.trim().toLowerCase();
219
+ if (!key) {
220
+ if (!ctx.hasUI) return;
221
+ const options = loaded.modes.map(
222
+ (mode) => `${mode.key} — ${mode.label} [${mode.promptStrategy}/${mode.scope}]`,
223
+ );
224
+ const selected = await ctx.ui.select("Select mode to preview", options);
225
+ if (!selected) return;
226
+ key = selected.split(" — ", 1)[0] ?? "";
227
+ }
228
+ const mode = loaded.modes.find((candidate) => candidate.key === key);
229
+ if (!mode) {
230
+ if (ctx.hasUI) ctx.ui.notify("Select or name a valid mode to preview", "error");
231
+ return;
232
+ }
233
+ const preview = composeModePrompt(mode, ctx.getSystemPromptOptions(), ctx.getSystemPrompt());
234
+ if (ctx.hasUI)
235
+ await ctx.ui.editor(`Preview: ${mode.label} (${mode.promptStrategy})`, preview);
236
+ },
237
+ });
238
+
239
+ pi.registerCommand("mode-new", {
240
+ description: "Create a global mode, or use /mode-new --project <key>",
241
+ handler: async (args, ctx) => {
242
+ if (!ctx.hasUI) return;
243
+ const parsed = parseScopedArguments(args);
244
+ const key = parsed.rest.trim().toLowerCase();
245
+ if (!key) {
246
+ ctx.ui.notify("Usage: /mode-new [--project] <key>", "error");
247
+ return;
248
+ }
249
+ if (parsed.scope === "project" && !ctx.isProjectTrusted()) {
250
+ ctx.ui.notify("Project modes require a trusted project", "error");
251
+ return;
252
+ }
253
+ const dirs = directories(ctx);
254
+ const dir = parsed.scope === "project" ? dirs.projectDir : dirs.globalDir;
255
+ let initial: ModeDefinition;
256
+ try {
257
+ initial = modeTemplate(key);
258
+ modePath(dir, key);
259
+ } catch (error) {
260
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
261
+ return;
262
+ }
263
+ const edited = await ctx.ui.editor(
264
+ `Create ${parsed.scope} prompt mode`,
265
+ JSON.stringify(initial, null, 2),
266
+ );
267
+ if (!edited) return;
268
+ try {
269
+ const mode = parseModeDefinition(JSON.parse(edited));
270
+ const path = modePath(dir, mode.key);
271
+ if (existsSync(path) && !(await ctx.ui.confirm("Overwrite mode?", path))) return;
272
+ saveMode(dir, mode);
273
+ activate(mode.key, ctx);
274
+ } catch (error) {
275
+ ctx.ui.notify(
276
+ `Mode not saved: ${error instanceof Error ? error.message : String(error)}`,
277
+ "error",
278
+ );
279
+ }
280
+ },
281
+ });
282
+
283
+ pi.registerCommand("mode-edit", {
284
+ description: "Edit a custom prompt mode safely",
285
+ handler: async (args, ctx) => {
286
+ if (!ctx.hasUI) return;
287
+ const key = args.trim().toLowerCase();
288
+ const loaded = currentModes(ctx);
289
+ const mode = loaded.modes.find((candidate) => candidate.key === key);
290
+ if (!mode?.path || mode.scope === "builtin") {
291
+ ctx.ui.notify("Name a global or project custom mode to edit", "error");
292
+ return;
293
+ }
294
+ const dirs = directories(ctx);
295
+ if (mode.scope === "project" && resolve(dirname(mode.path)) !== resolve(dirs.projectDir)) {
296
+ const ownerDir = dirname(dirname(dirname(mode.path)));
297
+ ctx.ui.notify(`Inherited mode is read-only here; cd to ${ownerDir} to edit it`, "warning");
298
+ return;
299
+ }
300
+ const edited = await ctx.ui.editor(
301
+ `Edit ${mode.scope} mode: ${mode.key}`,
302
+ readFileSync(mode.path, "utf8"),
303
+ );
304
+ if (!edited) return;
305
+ try {
306
+ const next = parseModeDefinition(JSON.parse(edited));
307
+ if (next.key !== mode.key)
308
+ throw new Error(
309
+ "renaming a mode during edit is not supported; create a new mode instead",
310
+ );
311
+ saveMode(mode.scope === "project" ? dirs.projectDir : dirs.globalDir, next);
312
+ ctx.ui.notify(`Saved prompt mode: ${next.key}`, "info");
313
+ } catch (error) {
314
+ ctx.ui.notify(
315
+ `Mode not saved: ${error instanceof Error ? error.message : String(error)}`,
316
+ "error",
317
+ );
318
+ }
319
+ },
320
+ });
321
+
322
+ pi.registerCommand("mode-delete", {
323
+ description: "Delete a custom prompt mode safely",
324
+ handler: async (args, ctx) => {
325
+ if (!ctx.hasUI) return;
326
+ const key = args.trim().toLowerCase();
327
+ const loaded = currentModes(ctx);
328
+ const mode = loaded.modes.find((candidate) => candidate.key === key);
329
+ if (!mode?.path || mode.scope === "builtin") {
330
+ ctx.ui.notify("Name a global or project custom mode to delete", "error");
331
+ return;
332
+ }
333
+ const dirs = directories(ctx);
334
+ if (mode.scope === "project" && resolve(dirname(mode.path)) !== resolve(dirs.projectDir)) {
335
+ const ownerDir = dirname(dirname(dirname(mode.path)));
336
+ ctx.ui.notify(
337
+ `Inherited mode is read-only here; cd to ${ownerDir} to delete it`,
338
+ "warning",
339
+ );
340
+ return;
341
+ }
342
+ if (!(await ctx.ui.confirm(`Delete ${mode.scope} mode?`, mode.path))) return;
343
+ try {
344
+ const dir = mode.scope === "project" ? dirs.projectDir : dirs.globalDir;
345
+ deleteMode(mode.path, dir);
346
+ if (activeKey === mode.key) activate(null, ctx);
347
+ ctx.ui.notify(`Deleted prompt mode: ${mode.key}`, "info");
348
+ } catch (error) {
349
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
350
+ }
351
+ },
352
+ });
353
+
354
+ pi.on("session_start", async (event, ctx) => {
355
+ const sessionKey = selectedModeFromEntries(ctx.sessionManager.getBranch()).key;
356
+ activeKey = sessionKey;
357
+ const loaded = currentModes(ctx);
358
+ const initial = resolveInitialModeSelection({
359
+ applyEnvironment: event.reason === "startup",
360
+ environmentValue: process.env.PI_MODE,
361
+ sessionKey,
362
+ availableKeys: loaded.modes.map((mode) => mode.key),
363
+ });
364
+
365
+ if (initial.source === "environment") {
366
+ activeKey = initial.key;
367
+ if (sessionKey !== initial.key) pi.appendEntry(MODE_STATE_TYPE, { key: initial.key });
368
+ if (ctx.hasUI) {
369
+ if (initial.error) {
370
+ ctx.ui.notify(`${initial.error}; using the native SYSTEM.md/host base`, "warning");
371
+ } else {
372
+ ctx.ui.notify(
373
+ initial.key
374
+ ? `Startup prompt mode from PI_MODE: ${initial.key}`
375
+ : "PI_MODE=off; using the native SYSTEM.md/host base",
376
+ "info",
377
+ );
378
+ }
379
+ }
380
+ }
381
+
382
+ updateStatus(ctx);
383
+ if (ctx.hasUI && loaded.diagnostics.length > 0) {
384
+ ctx.ui.notify(
385
+ `pi-modes skipped ${loaded.diagnostics.length} invalid mode file(s); run /mode-status`,
386
+ "warning",
387
+ );
388
+ }
389
+ });
390
+
391
+ pi.on("before_agent_start", async (event, ctx) => {
392
+ activeKey = selectedModeFromEntries(ctx.sessionManager.getBranch()).key;
393
+ updateStatus(ctx);
394
+ if (!activeKey) return;
395
+ const mode = currentModes(ctx).modes.find((candidate) => candidate.key === activeKey);
396
+ if (!mode) {
397
+ if (ctx.hasUI && !warnedUnavailableKeys.has(activeKey)) {
398
+ warnedUnavailableKeys.add(activeKey);
399
+ ctx.ui.notify(
400
+ `Selected prompt mode is unavailable: ${activeKey}; using host prompt`,
401
+ "warning",
402
+ );
403
+ }
404
+ return;
405
+ }
406
+ warnedUnavailableKeys.delete(activeKey);
407
+ return { systemPrompt: composeModePrompt(mode, event.systemPromptOptions, event.systemPrompt) };
408
+ });
409
+ }
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@tryinget/pi-modes",
3
+ "version": "0.1.0",
4
+ "description": "Prompt mode switching for Pi with explicit append, replace-base, and replace-final semantics",
5
+ "type": "module",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/tryingET/pi-extensions.git",
10
+ "directory": "packages/pi-modes"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/tryingET/pi-extensions/issues"
14
+ },
15
+ "homepage": "https://github.com/tryingET/pi-extensions/tree/main/packages/pi-modes",
16
+ "keywords": [
17
+ "pi-package",
18
+ "pi-extension",
19
+ "mode",
20
+ "system-prompt",
21
+ "prompt-profiles",
22
+ "monorepo"
23
+ ],
24
+ "publishConfig": {
25
+ "registry": "https://registry.npmjs.org/",
26
+ "access": "public"
27
+ },
28
+ "engines": {
29
+ "node": ">=22.19.0"
30
+ },
31
+ "scripts": {
32
+ "fix": "bash ./scripts/quality-gate.sh fix",
33
+ "lint": "bash ./scripts/quality-gate.sh lint",
34
+ "typecheck": "bash ./scripts/quality-gate.sh typecheck",
35
+ "quality:pre-commit": "bash ./scripts/quality-gate.sh pre-commit",
36
+ "quality:pre-push": "bash ./scripts/quality-gate.sh pre-push",
37
+ "quality:ci": "bash ./scripts/quality-gate.sh ci",
38
+ "check": "npm run quality:ci",
39
+ "test": "npm run quality:ci",
40
+ "docs:list": "bash ./scripts/docs-list.sh",
41
+ "docs:list:workspace": "bash ./scripts/docs-list.sh --workspace --discover",
42
+ "docs:list:json": "bash ./scripts/docs-list.sh --json",
43
+ "release:check": "bash ./scripts/release-check.sh",
44
+ "release:check:quick": "SKIP_PI_SMOKE=1 bash ./scripts/release-check.sh"
45
+ },
46
+ "files": [
47
+ "extensions/mode.ts",
48
+ "src",
49
+ "tests",
50
+ "docs/project/2026-07-11-prompt-mode-architecture.md",
51
+ "docs/project/2026-07-11-implementation-plan.md",
52
+ "prompts",
53
+ "examples",
54
+ "policy/security-policy.json",
55
+ "policy/engineering-lane.json"
56
+ ],
57
+ "pi": {
58
+ "extensions": [
59
+ "./extensions/mode.ts"
60
+ ],
61
+ "prompts": [
62
+ "./prompts"
63
+ ]
64
+ },
65
+ "x-pi-template": {
66
+ "scaffoldMode": "simple-package",
67
+ "workspacePath": "packages/pi-modes",
68
+ "releaseComponent": "pi-modes",
69
+ "releaseConfigMode": "component"
70
+ },
71
+ "devDependencies": {
72
+ "@biomejs/biome": "2.3.14",
73
+ "@earendil-works/pi-ai": "0.80.6",
74
+ "@earendil-works/pi-coding-agent": "0.80.6",
75
+ "@earendil-works/pi-tui": "0.80.6",
76
+ "@types/node": "22.14.0",
77
+ "tsx": "4.20.6",
78
+ "typescript": "5.9.2"
79
+ },
80
+ "overrides": {
81
+ "fast-xml-parser": "5.3.6"
82
+ },
83
+ "peerDependencies": {
84
+ "@earendil-works/pi-ai": "*",
85
+ "@earendil-works/pi-coding-agent": "*",
86
+ "@earendil-works/pi-tui": "*"
87
+ }
88
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "lane": "ts",
3
+ "engineering_core": {
4
+ "tool": "engineering-core",
5
+ "lane": "pi-ts",
6
+ "repository": "https://github.com/tryingET/core_engineering-core",
7
+ "ref": "workspace-local-unpinned",
8
+ "command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core show pi-ts",
9
+ "catalog_command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core catalog --pretty",
10
+ "list_disciplines_command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core list-disciplines",
11
+ "list_templates_command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core list-templates",
12
+ "disciplines": [
13
+ "validation",
14
+ "testing",
15
+ "security-privacy",
16
+ "documentation",
17
+ "dependency-governance",
18
+ "specification-and-dsls",
19
+ "engineering-reasoning"
20
+ ],
21
+ "loop_validation": {
22
+ "version": "repo-loop-validation-v1",
23
+ "contract_doc": "docs/engineering.local.md#repo-loop-validation",
24
+ "commands": {
25
+ "loop-doctor": "n/a: template source surface only; generated package owns concrete loop-doctor command after rendering",
26
+ "loop-verify-fast": "n/a: template source surface only; generated package owns concrete loop-verify-fast command after rendering",
27
+ "loop-impact-plan": "n/a: template source surface only; generated package owns concrete loop-impact-plan command after rendering",
28
+ "loop-impact-run": "n/a: template source surface only; generated package owns concrete loop-impact-run command after rendering",
29
+ "loop-impact-wide": "n/a: template source surface only; generated package owns concrete loop-impact-wide command after rendering",
30
+ "loop-landing-check": "n/a: template source surface only; generated package owns concrete loop-landing-check command after rendering"
31
+ }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "default": {
3
+ "maxRiskScore": 70,
4
+ "requireIntegrity": true,
5
+ "requireSignatures": false,
6
+ "allowNpmDiffFallback": false,
7
+ "minReleaseAgeHours": 0
8
+ },
9
+ "packages": {}
10
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Author a versioned pi-modes prompt profile with an explicit composition strategy
3
+ summary: "Author a versioned pi-modes prompt profile with an explicit composition strategy."
4
+ read_when:
5
+ - "Invoking the packaged mode-authoring prompt template."
6
+ system4d:
7
+ container: "Prompt-profile authoring helper."
8
+ compass: "Make composition strategy and non-authorizations explicit."
9
+ engine: "Interpret objective -> select strategy -> emit schemaVersion 1 JSON."
10
+ fog: "Mode text can accidentally imply runtime authority or hidden continuation."
11
+ ---
12
+
13
+ Design a `pi-modes` JSON profile for this objective:
14
+
15
+ {{args}}
16
+
17
+ Choose and explain exactly one strategy:
18
+
19
+ - `append`: preserve the assembled host prompt and add instructions;
20
+ - `replace_base`: replace the static base while retaining append/context/skills/date/cwd;
21
+ - `replace_final`: use the supplied prompt as the exact final prompt.
22
+
23
+ Return valid schemaVersion 1 JSON with `key`, `label`, `description`, `promptStrategy`, and `systemPrompt`. Do not add autonomous continuation, peer launch, campaign start, mutation permission, or promotion authority to the mode contract.