supipowers 2.1.0 → 2.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Workflow extension for OMP coding agents.",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/bootstrap.ts CHANGED
@@ -43,6 +43,7 @@ import { registerUltraPlanAuthoringTool } from "./ultraplan/authoring-tool.js";
43
43
  import { registerUltraPlanAuthoringPipelineTools } from "./ultraplan/authoring/authoring-tools.js";
44
44
  import { registerActiveToolController } from "./tool-catalog/active-tool-controller.js";
45
45
  import { registerMempalaceHooks } from "./mempalace/hooks.js";
46
+ import { registerRunbookCommand, handleRunbook } from "./commands/runbook.js";
46
47
  import { registerMempalaceTool } from "./mempalace/tool.js";
47
48
 
48
49
  // TUI-only commands — intercepted at the input level to prevent
@@ -65,6 +66,7 @@ const TUI_COMMANDS: Record<string, (platform: Platform, ctx: any, args?: string)
65
66
  "supi:ultraplan": (platform, ctx, args) => handleUltraplan(platform, ctx, args),
66
67
  "supi:harness": (platform, ctx, args) => { void handleHarness(platform, ctx, args); },
67
68
  "supi:memory": (platform, ctx, args) => handleMemory(platform, ctx, args),
69
+ "runbook": (platform, ctx, args) => handleRunbook(platform, ctx, args),
68
70
  };
69
71
 
70
72
  function getInstalledVersion(platform: Platform): string | null {
@@ -101,6 +103,7 @@ export function bootstrap(platform: Platform): void {
101
103
  registerUltraplanCommand(platform);
102
104
  registerHarnessCommand(platform);
103
105
  registerMemoryCommand(platform);
106
+ registerRunbookCommand(platform);
104
107
 
105
108
 
106
109
  registerUltraPlanRuntimeTools(platform);
@@ -18,9 +18,11 @@ import {
18
18
  import type {
19
19
  ManualOptimizationAction,
20
20
  OptimizationPlan,
21
+ WriteCommandAction,
21
22
  WriteRuleAction,
23
+ WriteExtensionAction,
22
24
  } from "../context/startup-optimizer.js";
23
- import { parseManagedRule, renderManagedRule } from "../context/rule-renderer.js";
25
+ import { parseManagedCommand, parseManagedExtension, parseManagedRule, renderManagedCommand, renderManagedExtension, renderManagedRule } from "../context/rule-renderer.js";
24
26
  import { DEFAULT_TOKENIGNORE_ENTRIES, mergeManagedTokenignore } from "../context/tokenignore.js";
25
27
  import {
26
28
  parseStartupOptimizerManifest,
@@ -142,6 +144,20 @@ async function runCheck(
142
144
  }
143
145
  }
144
146
 
147
+ const commandFiles: Record<string, string | null> = {};
148
+ if (manifest) {
149
+ for (const command of manifest.commands) {
150
+ commandFiles[command.path] = readOptionalFile(path.join(ctx.cwd, command.path));
151
+ }
152
+ }
153
+
154
+ const extensionFiles: Record<string, string | null> = {};
155
+ if (manifest) {
156
+ for (const extension of manifest.extensions) {
157
+ extensionFiles[extension.path] = readOptionalFile(path.join(ctx.cwd, extension.path));
158
+ }
159
+ }
160
+
145
161
  const currentSkills: ParsedSkill[] = currentPrompt ? parseIndividualSkills(currentPrompt) : [];
146
162
  const currentSections: PromptSection[] = currentPrompt ? parseSystemPrompt(currentPrompt) : [];
147
163
 
@@ -149,6 +165,8 @@ async function runCheck(
149
165
  manifestPath,
150
166
  manifestText,
151
167
  ruleFiles,
168
+ commandFiles,
169
+ extensionFiles,
152
170
  tokenignorePath,
153
171
  tokenignoreText: readOptionalFile(tokenignorePath),
154
172
  currentPrompt,
@@ -232,17 +250,23 @@ async function showDryRun(ctx: PlatformContext, plan: OptimizationPlan): Promise
232
250
 
233
251
  function buildPlanPreview(plan: OptimizationPlan): string[] {
234
252
  const writeRuleCount = plan.actions.filter((action) => action.kind === "write-rule").length;
235
- const manualCount = plan.actions.filter((action) => action.kind !== "write-rule").length;
253
+ const writeCommandCount = plan.actions.filter((action) => action.kind === "write-command").length;
254
+ const writeExtensionCount = plan.actions.filter((action) => action.kind === "write-extension").length;
255
+ const manualCount = plan.actions.filter((action) => action.kind !== "write-rule" && action.kind !== "write-command" && action.kind !== "write-extension").length;
236
256
  const lines = [
237
257
  `Source set: ${plan.sourceSetHash.slice(0, 12)}`,
238
258
  `Current: ~${formatTokens(Math.ceil(plan.beforeBytes / 4))} tokens | Estimated after planned removals: ~${formatTokens(Math.ceil(plan.estimatedAfterBytes / 4))} tokens`,
239
- `Actions: ${writeRuleCount} write-rule, ${manualCount} manual`,
259
+ `Actions: ${writeRuleCount} write-rule, ${writeCommandCount} write-command, ${writeExtensionCount} write-extension, ${manualCount} manual`,
240
260
  "",
241
261
  ];
242
262
 
243
263
  for (const action of plan.actions) {
244
264
  if (action.kind === "write-rule") {
245
265
  lines.push(`write-rule ${action.mode}: ${action.targetPath}`);
266
+ } else if (action.kind === "write-command") {
267
+ lines.push(`write-command: /${action.commandName} -> ${action.targetPath}`);
268
+ } else if (action.kind === "write-extension") {
269
+ lines.push(`write-extension: ${action.extensionName} -> ${action.targetPath}`);
246
270
  } else if (action.kind === "manual-disable") {
247
271
  lines.push(`manual-disable ${action.sourceName}: ${action.reason}`);
248
272
  } else {
@@ -268,9 +292,14 @@ async function applyOptimizationPlan(
268
292
  }
269
293
 
270
294
  const writeRules = plan.actions.filter((action): action is WriteRuleAction => action.kind === "write-rule");
271
- const preflight = preflightRuleWrites(ctx.cwd, writeRules);
272
- if (preflight.conflicts.length > 0) {
273
- ctx.ui.notify(`Apply blocked: ${preflight.conflicts.join("; ")}`, "error");
295
+ const writeCommands = plan.actions.filter((action): action is WriteCommandAction => action.kind === "write-command");
296
+ const writeExtensions = plan.actions.filter((action): action is WriteExtensionAction => action.kind === "write-extension");
297
+ const rulePreflight = preflightRuleWrites(ctx.cwd, writeRules);
298
+ const commandPreflight = preflightCommandWrites(ctx.cwd, writeCommands);
299
+ const extensionPreflight = preflightExtensionWrites(ctx.cwd, writeExtensions);
300
+ const preflightConflicts = [...rulePreflight.conflicts, ...commandPreflight.conflicts, ...extensionPreflight.conflicts];
301
+ if (preflightConflicts.length > 0) {
302
+ ctx.ui.notify(`Apply blocked: ${preflightConflicts.join("; ")}`, "error");
274
303
  return;
275
304
  }
276
305
 
@@ -279,7 +308,7 @@ async function applyOptimizationPlan(
279
308
  return;
280
309
  }
281
310
 
282
- const summary = buildApplySummary(plan, preflight.updates);
311
+ const summary = buildApplySummary(plan, [...rulePreflight.updates, ...commandPreflight.updates, ...extensionPreflight.updates]);
283
312
  const accepted = await confirmApply(ctx, summary);
284
313
  if (!accepted) {
285
314
  ctx.ui.notify("Apply cancelled.", "info");
@@ -299,6 +328,18 @@ async function applyOptimizationPlan(
299
328
  fs.writeFileSync(target, renderManagedRule(action));
300
329
  }
301
330
 
331
+ for (const action of writeCommands) {
332
+ const target = absoluteRulePath(ctx.cwd, action.targetPath);
333
+ fs.mkdirSync(path.dirname(target), { recursive: true });
334
+ fs.writeFileSync(target, renderManagedCommand(action));
335
+ }
336
+
337
+ for (const action of writeExtensions) {
338
+ const target = absoluteRulePath(ctx.cwd, action.targetPath);
339
+ fs.mkdirSync(path.dirname(target), { recursive: true });
340
+ fs.writeFileSync(target, renderManagedExtension(action));
341
+ }
342
+
302
343
  fs.mkdirSync(path.dirname(tokenignorePath), { recursive: true });
303
344
  fs.writeFileSync(tokenignorePath, tokenignore.content);
304
345
 
@@ -315,7 +356,7 @@ async function applyOptimizationPlan(
315
356
  // process can't see the just-written .omp/rules without a full restart.
316
357
  // Be honest about this rather than silently calling reload() and lying.
317
358
  ctx.ui.notify(
318
- "Applied deterministic context migration. Restart OMP so the new managed rules in .omp/rules are picked up by rule discovery, then disable the original sources and run /supi:optimize-context --check to validate runtime savings.",
359
+ "Applied deterministic context migration. Restart OMP so new managed rules in .omp/rules, commands in .omp/commands, and the runbook extension in .omp/extensions are picked up by discovery, then disable the original sources and run /supi:optimize-context --check to validate runtime savings.",
319
360
  "info",
320
361
  );
321
362
  }
@@ -358,11 +399,73 @@ function preflightRuleWrites(
358
399
  conflicts.push(`${action.targetPath} is malformed: ${parsed.error}`);
359
400
  continue;
360
401
  }
361
- if (
362
- parsed.metadata.sourceHash !== action.sourceHash ||
363
- parsed.metadata.sourceId !== action.sourceId ||
364
- parsed.metadata.mode !== action.mode
365
- ) {
402
+ if (fs.readFileSync(target, "utf-8") !== renderManagedRule(action)) {
403
+ updates.push(action.targetPath);
404
+ }
405
+ }
406
+
407
+ return { conflicts, updates };
408
+ }
409
+
410
+ function preflightCommandWrites(
411
+ cwd: string,
412
+ actions: WriteCommandAction[],
413
+ ): { conflicts: string[]; updates: string[] } {
414
+ const conflicts: string[] = [];
415
+ const updates: string[] = [];
416
+
417
+ for (const action of actions) {
418
+ const target = absoluteRulePath(cwd, action.targetPath);
419
+ if (!fs.existsSync(target)) continue;
420
+ const stat = fs.statSync(target);
421
+ if (!stat.isFile()) {
422
+ conflicts.push(`${action.targetPath} exists and is not a file`);
423
+ continue;
424
+ }
425
+
426
+ const parsed = parseManagedCommand(fs.readFileSync(target, "utf-8"));
427
+ if (parsed.status === "unmanaged") {
428
+ conflicts.push(`${action.targetPath} is unmanaged`);
429
+ continue;
430
+ }
431
+ if (parsed.status === "malformed") {
432
+ conflicts.push(`${action.targetPath} is malformed: ${parsed.error}`);
433
+ continue;
434
+ }
435
+ if (fs.readFileSync(target, "utf-8") !== renderManagedCommand(action)) {
436
+ updates.push(action.targetPath);
437
+ }
438
+ }
439
+
440
+ return { conflicts, updates };
441
+ }
442
+
443
+ function preflightExtensionWrites(
444
+ cwd: string,
445
+ actions: WriteExtensionAction[],
446
+ ): { conflicts: string[]; updates: string[] } {
447
+ const conflicts: string[] = [];
448
+ const updates: string[] = [];
449
+
450
+ for (const action of actions) {
451
+ const target = absoluteRulePath(cwd, action.targetPath);
452
+ if (!fs.existsSync(target)) continue;
453
+ const stat = fs.statSync(target);
454
+ if (!stat.isFile()) {
455
+ conflicts.push(`${action.targetPath} exists and is not a file`);
456
+ continue;
457
+ }
458
+
459
+ const parsed = parseManagedExtension(fs.readFileSync(target, "utf-8"));
460
+ if (parsed.status === "unmanaged") {
461
+ conflicts.push(`${action.targetPath} is unmanaged`);
462
+ continue;
463
+ }
464
+ if (parsed.status === "malformed") {
465
+ conflicts.push(`${action.targetPath} is malformed: ${parsed.error}`);
466
+ continue;
467
+ }
468
+ if (fs.readFileSync(target, "utf-8") !== renderManagedExtension(action)) {
366
469
  updates.push(action.targetPath);
367
470
  }
368
471
  }
@@ -370,11 +473,16 @@ function preflightRuleWrites(
370
473
  return { conflicts, updates };
371
474
  }
372
475
 
476
+
373
477
  function buildApplySummary(plan: OptimizationPlan, updates: string[]): string {
374
478
  const writeRuleCount = plan.actions.filter((action) => action.kind === "write-rule").length;
375
- const manualCount = plan.actions.filter((action) => action.kind !== "write-rule").length;
479
+ const writeCommandCount = plan.actions.filter((action) => action.kind === "write-command").length;
480
+ const writeExtensionCount = plan.actions.filter((action) => action.kind === "write-extension").length;
481
+ const manualCount = plan.actions.filter((action) => action.kind !== "write-rule" && action.kind !== "write-command" && action.kind !== "write-extension").length;
376
482
  const lines = [
377
483
  `Write ${writeRuleCount} managed rule file${writeRuleCount === 1 ? "" : "s"}.`,
484
+ `Write ${writeCommandCount} managed command file${writeCommandCount === 1 ? "" : "s"}.`,
485
+ `Write ${writeExtensionCount} managed extension file${writeExtensionCount === 1 ? "" : "s"}.`,
378
486
  `Merge ${DEFAULT_TOKENIGNORE_ENTRIES.length} managed .tokenignore entries.`,
379
487
  `Record ${manualCount} manual follow-up action${manualCount === 1 ? "" : "s"}.`,
380
488
  `Estimated savings after planned removals: ~${formatTokens(Math.ceil(plan.estimatedSavedBytes / 4))} tokens.`,
@@ -382,7 +490,7 @@ function buildApplySummary(plan: OptimizationPlan, updates: string[]): string {
382
490
  if (updates.length > 0) {
383
491
  lines.push(`Managed update candidate${updates.length === 1 ? "" : "s"}: ${updates.join(", ")}`);
384
492
  }
385
- lines.push("Manifest is written last after managed rule and tokenignore writes succeed.");
493
+ lines.push("Manifest is written last after managed rule, command, extension, and tokenignore writes succeed.");
386
494
  return lines.join("\n");
387
495
  }
388
496
 
@@ -406,8 +514,35 @@ function buildManifest(plan: OptimizationPlan): StartupOptimizerManifest {
406
514
  slug: action.slug,
407
515
  sourceBytes: action.sourceBytes,
408
516
  ...(action.condition ? { condition: action.condition } : {}),
517
+ ...(action.triggers ? { triggers: action.triggers } : {}),
518
+ ...(action.scope ? { scope: action.scope } : {}),
409
519
  ...(action.description ? { description: action.description } : {}),
410
520
  }));
521
+ const commands = plan.actions
522
+ .filter((action): action is WriteCommandAction => action.kind === "write-command")
523
+ .map((action) => ({
524
+ path: action.targetPath,
525
+ sourceId: action.sourceId,
526
+ sourceName: action.sourceName,
527
+ sourceHash: action.sourceHash,
528
+ slug: action.slug,
529
+ commandName: action.commandName,
530
+ sourceBytes: action.sourceBytes,
531
+ ...(action.description ? { description: action.description } : {}),
532
+ }));
533
+ const extensions = plan.actions
534
+ .filter((action): action is WriteExtensionAction => action.kind === "write-extension")
535
+ .map((action) => ({
536
+ path: action.targetPath,
537
+ sourceId: action.sourceId,
538
+ sourceName: action.sourceName,
539
+ sourceHash: action.sourceHash,
540
+ slug: action.slug,
541
+ extensionName: action.extensionName,
542
+ sourceBytes: action.sourceBytes,
543
+ }));
544
+
545
+
411
546
 
412
547
  return {
413
548
  version: 1,
@@ -417,12 +552,14 @@ function buildManifest(plan: OptimizationPlan): StartupOptimizerManifest {
417
552
  estimatedAfterBytes: plan.estimatedAfterBytes,
418
553
  estimatedSavedBytes: plan.estimatedSavedBytes,
419
554
  rules,
555
+ commands,
556
+ extensions,
420
557
  tokenignore: {
421
558
  path: ".omp/supipowers/.tokenignore",
422
559
  entries: tokenignore.entries,
423
560
  hash: tokenignore.hash,
424
561
  },
425
- manualActions: plan.actions.filter((action): action is ManualOptimizationAction => action.kind !== "write-rule"),
562
+ manualActions: plan.actions.filter((action): action is ManualOptimizationAction => action.kind !== "write-rule" && action.kind !== "write-command" && action.kind !== "write-extension"),
426
563
  };
427
564
  }
428
565