@kisev/agentomatic 11.0.2 → 12.0.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.
Files changed (64) hide show
  1. package/README.md +32 -16
  2. package/README.ru.md +33 -16
  3. package/dist/agent-profiles.d.ts +8 -2
  4. package/dist/agent-profiles.js +131 -82
  5. package/dist/assets/agents/architect.md +4 -4
  6. package/dist/assets/agents/critic.md +10 -11
  7. package/dist/assets/agents/manager.md +11 -12
  8. package/dist/assets/agents/mapper.md +4 -4
  9. package/dist/assets/agents/review.md +11 -12
  10. package/dist/assets/agents/worker.md +29 -32
  11. package/dist/assets/commands/asd-ste100.md +10 -0
  12. package/dist/assets/commands/code-simplify.md +12 -0
  13. package/dist/assets/commands/eli5.md +10 -0
  14. package/dist/assets/commands/handoff.md +10 -0
  15. package/dist/assets/commands/rtk-stats.md +2 -2
  16. package/dist/assets/commands/skill-doctor.md +10 -0
  17. package/dist/assets/migration-inventory.json +17 -6
  18. package/dist/assets/plugins/code-simplify.js +3 -0
  19. package/dist/assets/plugins/rtk.js +1 -1
  20. package/dist/assets/plugins/rules-injector.js +1 -1
  21. package/dist/assets/plugins/zed-bell.js +1 -1
  22. package/dist/catalog.d.ts +2 -2
  23. package/dist/catalog.js +9 -3
  24. package/dist/cli-output.js +6 -6
  25. package/dist/cli.d.ts +1 -1
  26. package/dist/cli.js +1 -1257
  27. package/dist/command-cli.d.ts +1 -0
  28. package/dist/command-cli.js +1004 -0
  29. package/dist/config-setup.d.ts +14 -8
  30. package/dist/config-setup.js +197 -64
  31. package/dist/contracts.js +26 -0
  32. package/dist/doctor.d.ts +0 -1
  33. package/dist/doctor.js +42 -55
  34. package/dist/index.d.ts +13 -118
  35. package/dist/index.js +118 -125
  36. package/dist/installer.d.ts +25 -2
  37. package/dist/installer.js +68 -23
  38. package/dist/jsonc.d.ts +18 -2
  39. package/dist/jsonc.js +109 -5
  40. package/dist/opencode-config.d.ts +11 -0
  41. package/dist/opencode-config.js +237 -0
  42. package/dist/plugins/code-simplify.d.ts +25 -0
  43. package/dist/plugins/code-simplify.js +49 -0
  44. package/dist/plugins/events.d.ts +20 -0
  45. package/dist/plugins/events.js +48 -0
  46. package/dist/plugins/rtk.d.ts +9 -12
  47. package/dist/plugins/rtk.js +20 -5
  48. package/dist/plugins/rules-injector.d.ts +21 -11
  49. package/dist/plugins/rules-injector.js +43 -31
  50. package/dist/plugins/zed-bell.d.ts +8 -5
  51. package/dist/plugins/zed-bell.js +10 -2
  52. package/dist/reconcile.d.ts +4 -1
  53. package/dist/reconcile.js +12 -16
  54. package/dist/registry.d.ts +1 -0
  55. package/dist/registry.js +21 -5
  56. package/dist/routing.d.ts +5 -3
  57. package/dist/routing.js +57 -22
  58. package/dist/self-install.d.ts +7 -0
  59. package/dist/self-install.js +52 -1
  60. package/dist/terminal-wizard.d.ts +1 -1
  61. package/dist/terminal-wizard.js +2 -2
  62. package/package.json +11 -6
  63. package/dist/assets/commands/skill-improve.md +0 -10
  64. package/dist/assets/commands/stopit.md +0 -10
@@ -0,0 +1,49 @@
1
+ export const CODE_SIMPLIFY_ROLES = [
2
+ "manager",
3
+ "architect",
4
+ "mapper",
5
+ "worker",
6
+ "review",
7
+ "critic",
8
+ ];
9
+ const LADDER = `Prevention ladder for the code being written or changed, applied in order; stop at the first step that satisfies the agreed need: 1) necessity; 2) existing code; 3) standard library; 4) native platform; 5) installed dependency; 6) one line; 7) minimum. The ladder applies to the current change only; never scan repositories or unrelated files — a targeted caller search by exact name for code you are changing is not scanning. Fix the caller in the same change when that is the smaller diff. A conscious cut records a one-line SIMPLIFY marker (ceiling -> upgrade trigger) on the added or changed code in the same edit. Keep new verification proportionate: one runnable check for logic that is not evidently correct, none for a trivial one-liner. Never cancel a clarification or confirmation gate.`;
10
+ const AUDIT = `Complexity audit only on an explicit request, only in the requested scope, always report-only. Findings are ranked one-liners: exact location, one tag of delete/stdlib/native/reuse/yagni/shrink, and the concrete simpler alternative that preserves behavior. Collect SIMPLIFY debt markers in the scope into a separate registry section, never tagged findings: location, ceiling, trigger; mark a marker no-trigger when its upgrade trigger has not fired.`;
11
+ const GUARDS = `Search every usage, including dynamic references, before any delete finding. Never propose simplification that crosses a trust boundary, risks data loss, weakens security or accessibility, drops error handling that hides failures, or removes explicitly requested behavior.`;
12
+ function rules(level) {
13
+ const parts = level === "lite" ? [LADDER] : level === "full" ? [LADDER, AUDIT] : [LADDER, AUDIT, GUARDS];
14
+ return `[code-simplify level=${level}]\n${parts.join("\n\n")}\n[/code-simplify]`;
15
+ }
16
+ function roleOf(agent) {
17
+ if (agent && CODE_SIMPLIFY_ROLES.includes(agent))
18
+ return agent;
19
+ return agent?.startsWith("critic-") ? "critic" : undefined;
20
+ }
21
+ export function codeSimplify(options = {}) {
22
+ const level = options.level ?? "full";
23
+ if (level === "off")
24
+ return {};
25
+ if (options.scope?.some((role) => !CODE_SIMPLIFY_ROLES.includes(role)))
26
+ throw new Error(`code-simplify scope must list roles from ${CODE_SIMPLIFY_ROLES.join(", ")}`);
27
+ const scope = options.scope ? new Set(options.scope) : undefined;
28
+ const text = rules(level);
29
+ return {
30
+ context: async (input) => {
31
+ if (scope) {
32
+ const role = roleOf(input.agent);
33
+ if (!role || !scope.has(role))
34
+ return;
35
+ }
36
+ if (input.system.some((part) => part.type === "text" && part.text === text))
37
+ return;
38
+ input.system.push({ type: "text", text });
39
+ },
40
+ };
41
+ }
42
+ export default {
43
+ id: "agentomatic.code-simplify",
44
+ async setup(ctx) {
45
+ const hooks = await codeSimplify(ctx.options);
46
+ if (hooks.context)
47
+ await ctx.session.hook("context", hooks.context);
48
+ },
49
+ };
@@ -0,0 +1,20 @@
1
+ import type { Plugin } from "@opencode/plugin";
2
+ import type { Result } from "@opencode/plugin/promise/tool";
3
+ export type ToolAfter = {
4
+ tool: string;
5
+ sessionID: string;
6
+ input: unknown;
7
+ } & ({
8
+ status: "completed";
9
+ result: Result;
10
+ } | {
11
+ status: "error";
12
+ error: unknown;
13
+ });
14
+ export declare function toolInput(value: unknown): Record<string, unknown>;
15
+ export declare function resultText(result: Result): string | undefined;
16
+ export declare function replaceResultText(result: Result, text: string): Result;
17
+ export declare function subscribeEvents(ctx: Plugin.Context, receive: (event: {
18
+ type: string;
19
+ data?: unknown;
20
+ }) => Promise<void>): () => Promise<void>;
@@ -0,0 +1,48 @@
1
+ export function toolInput(value) {
2
+ return value && typeof value === "object" && !Array.isArray(value)
3
+ ? value
4
+ : {};
5
+ }
6
+ export function resultText(result) {
7
+ if (typeof result.content === "string")
8
+ return result.content;
9
+ if (Array.isArray(result.content))
10
+ return result.content
11
+ .filter((part) => part.type === "text")
12
+ .map((part) => part.text)
13
+ .join("\n");
14
+ return undefined;
15
+ }
16
+ export function replaceResultText(result, text) {
17
+ if (!Array.isArray(result.content))
18
+ return { ...result, content: text };
19
+ let replaced = false;
20
+ return {
21
+ ...result,
22
+ content: result.content.flatMap((part) => {
23
+ if (part.type !== "text")
24
+ return [part];
25
+ if (replaced)
26
+ return [];
27
+ replaced = true;
28
+ return [{ type: "text", text }];
29
+ }),
30
+ };
31
+ }
32
+ export function subscribeEvents(ctx, receive) {
33
+ const controller = new AbortController();
34
+ const subscription = (async () => {
35
+ try {
36
+ for await (const event of ctx.event.subscribe({ signal: controller.signal }))
37
+ await receive(event);
38
+ }
39
+ catch (error) {
40
+ if (!controller.signal.aborted)
41
+ console.error("agentomatic event subscription failed", error);
42
+ }
43
+ })();
44
+ return async () => {
45
+ controller.abort();
46
+ await subscription;
47
+ };
48
+ }
@@ -1,12 +1,5 @@
1
- type HookInput = {
2
- tool: string;
3
- args?: {
4
- command?: unknown;
5
- };
6
- };
7
- type HookOutput = {
8
- output?: unknown;
9
- };
1
+ import type { Plugin } from "@opencode/plugin";
2
+ import { type ToolAfter } from "./events.js";
10
3
  export type RtkMethod = "compressed-rtk" | "truncated-head-tail" | "rtk-unavailable" | "ineligible" | "below-threshold";
11
4
  export type RtkStats = {
12
5
  schema_version: 1;
@@ -34,8 +27,12 @@ export declare function rtkCharsSaved(stats: RtkStats): number;
34
27
  export declare function rtkTokensSavedEstimate(stats: RtkStats): number;
35
28
  export declare function readRtkStats(path?: string): Promise<RtkStats | undefined>;
36
29
  export declare function rtk(options?: RtkOptions): Promise<{
37
- "tool.execute.after"?: undefined;
30
+ "execute.after"?: undefined;
38
31
  } | {
39
- "tool.execute.after": (input: HookInput, output: HookOutput) => Promise<void>;
32
+ "execute.after": (input: ToolAfter) => Promise<void>;
40
33
  }>;
41
- export default rtk;
34
+ declare const _default: {
35
+ id: string;
36
+ setup(ctx: Plugin.Context): Promise<void>;
37
+ };
38
+ export default _default;
@@ -1,3 +1,4 @@
1
+ import { resultText, replaceResultText, toolInput } from "./events.js";
1
2
  import { spawn } from "node:child_process";
2
3
  import { readFile } from "node:fs/promises";
3
4
  import { homedir } from "node:os";
@@ -137,26 +138,33 @@ export async function rtk(options = {}) {
137
138
  ((selected, input) => external(options.bin ?? "rtk", selected, input));
138
139
  const statsPath = options.statsPath === null ? undefined : (options.statsPath ?? rtkStatsPath());
139
140
  return {
140
- "tool.execute.after": async (input, output) => {
141
+ "execute.after": async (input) => {
142
+ if (input.status !== "completed")
143
+ return;
144
+ const output = { output: resultText(input.result) };
141
145
  try {
142
146
  if (input.tool === "edit" &&
143
147
  typeof output.output === "string" &&
144
148
  /oldString (not found|found multiple times|and newString must be different)/i.test(output.output))
145
149
  output.output = `${output.output}\nSTOP. Read the file before retrying Edit.`;
146
- if (input.tool !== "bash" ||
150
+ if (input.tool !== "shell" ||
147
151
  typeof output.output !== "string" ||
148
152
  output.output.length < (options.threshold ?? THRESHOLD)) {
149
- if (input.tool === "bash" && typeof output.output === "string" && statsPath)
153
+ if (input.tool === "shell" && typeof output.output === "string" && statsPath)
150
154
  await recordStats(statsPath, "below-threshold", output.output.length, output.output.length);
155
+ if (output.output !== undefined)
156
+ input.result = replaceResultText(input.result, output.output);
151
157
  return;
152
158
  }
153
- const selected = typeof input.args?.command === "string" ? filter(input.args.command) : undefined;
159
+ const command = toolInput(input.input).command;
160
+ const selected = typeof command === "string" ? filter(command) : undefined;
154
161
  const compressed = selected ? await run(selected, output.output) : undefined;
155
162
  const result = compressed && compressed.length < output.output.length
156
163
  ? compressed
157
164
  : truncate(output.output);
158
165
  const originalSize = output.output.length;
159
166
  output.output = `${result}\n[rtk: compressed method=${result === compressed ? `rtk/${selected}` : "head+tail"}; sizes=${originalSize}->${result.length}; evidence_complete=false; loss=possible]`;
167
+ input.result = replaceResultText(input.result, output.output);
160
168
  if (statsPath) {
161
169
  const method = result === compressed
162
170
  ? "compressed-rtk"
@@ -174,4 +182,11 @@ export async function rtk(options = {}) {
174
182
  },
175
183
  };
176
184
  }
177
- export default rtk;
185
+ export default {
186
+ id: "agentomatic.rtk",
187
+ async setup(ctx) {
188
+ const hooks = await rtk(ctx.options);
189
+ if (hooks["execute.after"])
190
+ await ctx.tool.hook("execute.after", hooks["execute.after"]);
191
+ },
192
+ };
@@ -1,22 +1,32 @@
1
+ import type { Plugin } from "@opencode/plugin";
2
+ import { type ToolAfter } from "./events.js";
1
3
  export type RulesInjectorOptions = {
2
4
  budget?: number;
3
5
  cwd?: string;
4
6
  enabled?: boolean;
5
7
  };
6
8
  export declare function rulesInjector(options?: RulesInjectorOptions): Promise<{
7
- "tool.execute.after"?: undefined;
9
+ "execute.after"?: undefined;
8
10
  event?: undefined;
9
- "experimental.chat.system.transform"?: undefined;
11
+ context?: undefined;
10
12
  } | {
11
- "tool.execute.after": (input: unknown, result: unknown) => Promise<void>;
12
- event: ({ event, }: {
13
- event?: {
14
- type?: string;
15
- properties?: Record<string, unknown>;
16
- };
13
+ "execute.after": (input: ToolAfter & {
14
+ directory?: string;
17
15
  }) => Promise<void>;
18
- "experimental.chat.system.transform": (input: unknown, result: {
19
- system?: unknown[];
16
+ event: (event: {
17
+ type: string;
18
+ data?: unknown;
19
+ }) => Promise<void>;
20
+ context: (input: {
21
+ sessionID: string;
22
+ system: Array<{
23
+ type: "text";
24
+ text: string;
25
+ }>;
20
26
  }) => Promise<void>;
21
27
  }>;
22
- export default rulesInjector;
28
+ declare const _default: {
29
+ id: string;
30
+ setup(ctx: Plugin.Context): Promise<(() => Promise<void>) | undefined>;
31
+ };
32
+ export default _default;
@@ -1,3 +1,4 @@
1
+ import { resultText, replaceResultText, subscribeEvents, toolInput, } from "./events.js";
1
2
  import { lstat, readFile, realpath } from "node:fs/promises";
2
3
  import { homedir } from "node:os";
3
4
  import { dirname, resolve } from "node:path";
@@ -9,20 +10,6 @@ function sessionID(value) {
9
10
  return item[key];
10
11
  return undefined;
11
12
  }
12
- function pathOf(input, output) {
13
- for (const value of [input, output]) {
14
- const args = value?.args;
15
- for (const key of ["filePath", "file_path", "path"])
16
- if (typeof args?.[key] === "string")
17
- return args[key];
18
- }
19
- return undefined;
20
- }
21
- function output(value, text) {
22
- const item = value;
23
- if (typeof item?.output === "string")
24
- item.output = `${item.output}\n${text}`;
25
- }
26
13
  function marker(rule) {
27
14
  return `[rules-injector source=${rule.path} revision=${rule.revision}]`;
28
15
  }
@@ -44,16 +31,19 @@ export async function rulesInjector(options = {}) {
44
31
  }
45
32
  return item;
46
33
  };
47
- const inject = async (input, result) => {
48
- const call = input;
49
- if (call?.tool !== "read" && call?.tool !== "edit")
34
+ const inject = async (input) => {
35
+ if (input.status !== "completed" || (input.tool !== "read" && input.tool !== "edit"))
50
36
  return;
51
- const identifier = sessionID(input);
52
- const target = pathOf(input, result);
53
- if (!identifier || !target)
37
+ const identifier = input.sessionID;
38
+ const target = toolInput(input.input).path;
39
+ const original = resultText(input.result);
40
+ if (!identifier || typeof target !== "string" || original === undefined)
54
41
  return;
42
+ const output = (text) => {
43
+ input.result = replaceResultText(input.result, `${original}\n${text}`);
44
+ };
55
45
  try {
56
- const native = typeof call?.directory === "string" ? call.directory : cwd;
46
+ const native = input.directory ?? cwd;
57
47
  const paths = [];
58
48
  let current = dirname(resolve(native, target));
59
49
  while (true) {
@@ -103,29 +93,51 @@ export async function rulesInjector(options = {}) {
103
93
  }
104
94
  }
105
95
  if (additions.length)
106
- output(result, additions.join("\n\n"));
96
+ output(additions.join("\n\n"));
107
97
  }
108
98
  catch (error) {
109
- output(result, `[rules-injector warning] cannot read AGENTS.md: ${String(error)}`);
99
+ output(`[rules-injector warning] cannot read AGENTS.md: ${String(error)}`);
110
100
  }
111
101
  };
112
102
  return {
113
- "tool.execute.after": inject,
114
- event: async ({ event, }) => {
115
- if (event?.type?.includes("compaction")) {
116
- const identifier = sessionID(event.properties);
103
+ "execute.after": inject,
104
+ event: async (event) => {
105
+ if (event.type === "session.compacted" || event.type.includes("compaction")) {
106
+ const identifier = sessionID(event.data);
117
107
  if (identifier)
118
108
  state(identifier).replay = true;
119
109
  }
120
110
  },
121
- "experimental.chat.system.transform": async (input, result) => {
111
+ context: async (input) => {
122
112
  const identifier = sessionID(input);
123
- if (!identifier || !state(identifier).replay || !Array.isArray(result.system))
113
+ if (!identifier || !state(identifier).replay)
124
114
  return;
125
115
  for (const rule of state(identifier).loaded.values())
126
- result.system.push(block(rule));
116
+ input.system.push({ type: "text", text: block(rule) });
127
117
  state(identifier).replay = false;
128
118
  },
129
119
  };
130
120
  }
131
- export default rulesInjector;
121
+ export default {
122
+ id: "agentomatic.rules-injector",
123
+ async setup(ctx) {
124
+ const hooks = await rulesInjector({ ...ctx.options, cwd: ctx.location.directory });
125
+ if (hooks["execute.after"])
126
+ await ctx.tool.hook("execute.after", async (event) => {
127
+ if (event.status !== "completed" || (event.tool !== "read" && event.tool !== "edit"))
128
+ return;
129
+ const session = await ctx.session
130
+ .get({ sessionID: event.sessionID })
131
+ .catch(() => undefined);
132
+ if (session) {
133
+ const scoped = { ...event, directory: session.location.directory };
134
+ await hooks["execute.after"](scoped);
135
+ event.result = scoped.result;
136
+ }
137
+ });
138
+ if (hooks.context)
139
+ await ctx.session.hook("context", hooks.context);
140
+ if (hooks.event)
141
+ return subscribeEvents(ctx, hooks.event);
142
+ },
143
+ };
@@ -1,13 +1,16 @@
1
+ import type { Plugin } from "@opencode/plugin";
1
2
  export type ZedBellOptions = {
2
3
  enabled?: boolean;
3
4
  };
4
5
  export declare function zedBell(options?: ZedBellOptions): Promise<{
5
6
  event?: undefined;
6
7
  } | {
7
- event: ({ event }: {
8
- event: {
9
- type?: string;
10
- };
8
+ event: (event: {
9
+ type: string;
11
10
  }) => Promise<void>;
12
11
  }>;
13
- export default zedBell;
12
+ declare const _default: {
13
+ id: string;
14
+ setup(ctx: Plugin.Context): Promise<(() => Promise<void>) | undefined>;
15
+ };
16
+ export default _default;
@@ -1,8 +1,9 @@
1
+ import { subscribeEvents } from "./events.js";
1
2
  export async function zedBell(options = {}) {
2
3
  if (!options.enabled)
3
4
  return {};
4
5
  return {
5
- event: async ({ event }) => {
6
+ event: async (event) => {
6
7
  if (event.type !== "session.idle" && event.type !== "permission.asked")
7
8
  return;
8
9
  const acp = process.env.OPENCODE_CLIENT === "acp" || process.argv.includes("acp");
@@ -11,4 +12,11 @@ export async function zedBell(options = {}) {
11
12
  },
12
13
  };
13
14
  }
14
- export default zedBell;
15
+ export default {
16
+ id: "agentomatic.zed-bell",
17
+ async setup(ctx) {
18
+ const hooks = await zedBell(ctx.options);
19
+ if (hooks.event)
20
+ return subscribeEvents(ctx, hooks.event);
21
+ },
22
+ };
@@ -27,13 +27,16 @@ export type ReconcilePlan = {
27
27
  sha256?: string;
28
28
  }>;
29
29
  confirmable: boolean;
30
+ digest: string;
30
31
  };
31
32
  export type ReconcileResult = {
32
33
  status: "ok";
33
34
  applied: true;
34
35
  plan: ReconcilePlan;
35
36
  };
36
- export type ReconcileOptions = TransactionOptions;
37
+ export type ReconcileOptions = TransactionOptions & {
38
+ expectedDigest?: string;
39
+ };
37
40
  export declare class ReconcileError extends LifecycleError {
38
41
  }
39
42
  export declare function previewReconcile(scope: Scope, cwd?: string, home?: string): Promise<ReconcilePlan>;
package/dist/reconcile.js CHANGED
@@ -3,7 +3,7 @@ import { lstat, readdir, readFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { applyTransaction, archiveRoot, assertSafePath, destination, LifecycleError, lifecycleRoot, recoverTransaction, sha256, stable, withLifecycleLock, } from "./lifecycle.js";
6
+ import { applyTransaction, assertSafePath, destination, LifecycleError, lifecycleRoot, readRegular, sha256, stable, withLifecycleLock, } from "./lifecycle.js";
7
7
  import { archiveMutations } from "./installer.js";
8
8
  const PACKAGE_NAME = "@kisev/agentomatic";
9
9
  const GENERIC_MANIFEST = ".agentomatic-manifest.json";
@@ -35,9 +35,6 @@ function loadInventory() {
35
35
  function scopeRoot(scope, cwd, home) {
36
36
  return scope === "global" ? resolve(home) : resolve(cwd);
37
37
  }
38
- function reconcileAllowedRoots(scope, cwd, home) {
39
- return [archiveRoot(scope, cwd, home)];
40
- }
41
38
  function relativePath(root, target) {
42
39
  const value = relative(resolve(root), resolve(target));
43
40
  if (!value || value.startsWith(`..${sep}`) || value === "..")
@@ -398,22 +395,19 @@ async function build(scope, cwd = process.cwd(), home = homedir()) {
398
395
  groups["modified-managed"].length === 0 &&
399
396
  groups.conflict.length === 0;
400
397
  return {
401
- plan: { ...base, confirmable },
398
+ plan: { ...base, confirmable, digest: sha256(stable({ base, mutations })) },
402
399
  mutations,
403
400
  };
404
401
  }
405
402
  export async function previewReconcile(scope, cwd = process.cwd(), home = homedir()) {
406
403
  const stateRoot = lifecycleRoot(scope, cwd, home);
407
- const root = scopeRoot(scope, cwd, home);
408
404
  try {
409
- return await withLifecycleLock(stateRoot, async () => {
410
- if (await recoverTransaction(root, stateRoot, reconcileAllowedRoots(scope, cwd, home)))
411
- throw new ReconcileError("recovered_transaction", "Recovered an interrupted transaction; request a fresh plan");
412
- const built = await build(scope, cwd, home);
413
- const blocked = built.plan.modified_managed.length > 0 || built.plan.conflicts.length > 0;
414
- const actionable = built.plan.operations.length > 0;
415
- return { ...built.plan, confirmable: !blocked && actionable };
416
- });
405
+ if (await readRegular(join(stateRoot, "transaction-journal.json")))
406
+ throw new ReconcileError("recovery_required", "Run maintenance recover before a fresh preview");
407
+ const built = await build(scope, cwd, home);
408
+ const blocked = built.plan.modified_managed.length > 0 || built.plan.conflicts.length > 0;
409
+ const actionable = built.plan.operations.length > 0;
410
+ return { ...built.plan, confirmable: !blocked && actionable };
417
411
  }
418
412
  catch (error) {
419
413
  if (error instanceof ReconcileError)
@@ -432,9 +426,11 @@ export async function applyReconcile(scope, cwd = process.cwd(), home = homedir(
432
426
  const root = scopeRoot(scope, cwd, home);
433
427
  try {
434
428
  return await withLifecycleLock(stateRoot, async () => {
435
- if (await recoverTransaction(root, stateRoot, reconcileAllowedRoots(scope, cwd, home)))
436
- throw new ReconcileError("recovered_transaction", "Recovered an interrupted transaction; request a fresh plan");
429
+ if (await readRegular(join(stateRoot, "transaction-journal.json")))
430
+ throw new ReconcileError("recovery_required", "Run maintenance recover before a fresh preview");
437
431
  const built = await build(scope, cwd, home);
432
+ if (options.expectedDigest && options.expectedDigest !== built.plan.digest)
433
+ throw new ReconcileError("stale_plan", "Cleanup sources changed; request a fresh preview");
438
434
  if (built.plan.conflicts.length || built.plan.modified_managed.length)
439
435
  throw new ReconcileError("conflict", "Reconcile contains unsafe ownership conflicts");
440
436
  await applyTransaction(root, stateRoot, built.mutations, {
@@ -9,6 +9,7 @@ export type PackageCommandRegistration = {
9
9
  body: readonly string[];
10
10
  };
11
11
  export type CommandRegistration = SkillCommandRegistration | PackageCommandRegistration;
12
+ export declare const COMMANDLESS_SKILLS: readonly ["debugging", "tdd", "verification"];
12
13
  export declare const COMMAND_REGISTRY: ({
13
14
  name: string;
14
15
  description: string;
package/dist/registry.js CHANGED
@@ -4,13 +4,16 @@ function description(english, russianTrigger) {
4
4
  }
5
5
  const SKILL_NAMES = [
6
6
  "agents-md",
7
+ "asd-ste100",
7
8
  "askme",
8
9
  "ast-grep",
9
10
  "code-explain",
10
11
  "code-review",
12
+ "code-simplify",
11
13
  "commit-msg",
12
14
  "docs-prepare",
13
15
  "docs-review",
16
+ "eli5",
14
17
  "goal",
15
18
  "humanize",
16
19
  "mattermost",
@@ -19,10 +22,10 @@ const SKILL_NAMES = [
19
22
  "release-prepare",
20
23
  "release-review",
21
24
  "rtk",
22
- "skill-improve",
25
+ "skill-doctor",
23
26
  "slides-prompts-prepare",
24
27
  "spec-manage",
25
- "stopit",
28
+ "handoff",
26
29
  "briefing",
27
30
  "task-prepare",
28
31
  "task-review",
@@ -42,13 +45,21 @@ const SKILL_NAMES = [
42
45
  "team-sprint-close",
43
46
  "team-sprint-start",
44
47
  ];
48
+ // Skills that opt out of command ownership through their frontmatter
49
+ // `metadata.command: false` label (the inspired-by era). They are installed
50
+ // like every other skill and load through the native Skill tool; only the
51
+ // thin slash-command adapter is absent. Keep this list in sync with the
52
+ // frontmatter labels; tests pin the two-way mapping.
53
+ export const COMMANDLESS_SKILLS = ["debugging", "tdd", "verification"];
45
54
  const COMMANDS = [
46
55
  ...SKILL_NAMES.map((name) => ({
47
56
  name,
48
57
  skill: name,
49
58
  description: name === "spec-manage"
50
59
  ? description("Create greenfield specs, onboard an existing project, update target state, or review specs", "спецификация проекта")
51
- : description(`Run the ${name} Agent Skill`, name),
60
+ : name === "code-simplify"
61
+ ? description("Apply the passive prevention ladder while coding and audit a requested scope for unnecessary complexity on an explicit request", "упростить код")
62
+ : description(`Run the ${name} Agent Skill`, name),
52
63
  })),
53
64
  {
54
65
  name: "rtk-stats",
@@ -57,7 +68,7 @@ const COMMANDS = [
57
68
  "Show the RTK output-compression observability summary for this host.",
58
69
  `Run \`npx --yes @kisev/agentomatic@${requirePackageVersion()} doctor --json\` and render the \`rtk.observability\` check as a short human summary: wrapper status, rtk binary availability, event counters, characters saved, and the token estimate.`,
59
70
  "When the doctor command is unavailable, read the stats file directly: `$XDG_STATE_HOME/opencode/skills/rtk/stats.json`, or `~/.local/state/opencode/skills/rtk/stats.json` when that variable is unset.",
60
- "Zero counters with an active wrapper mean no verbose bash output has been compressed yet.",
71
+ "Zero counters with an active wrapper mean no verbose shell output has been compressed yet.",
61
72
  "The `/rtk` command still loads the portable rtk skill and is unaffected by this summary.",
62
73
  ],
63
74
  },
@@ -83,7 +94,12 @@ export function renderCommand(command) {
83
94
  "Natural requests are supported. Explicit mode and scope arguments are passed unchanged; the skill verifies safety preconditions and asks before writing if intent remains ambiguous.",
84
95
  "Examples: `Create canonical specs for this empty project`; `Document this existing service`; `Change the canonical timeout`; `Audit specs without changes`.",
85
96
  ]
86
- : [];
97
+ : command.name === "code-simplify"
98
+ ? [
99
+ "Prevention applies passively while coding: the ladder runs on the current change without scanning anything; a targeted caller search by exact name for the code being changed is not scanning, and a conscious cut records a SIMPLIFY debt marker in the same edit.",
100
+ "Audit is explicit and report-only: name the scope, receive ranked one-line tagged findings plus a separate debt-marker registry, and change nothing.",
101
+ ]
102
+ : [];
87
103
  return [
88
104
  "---",
89
105
  `description: ${command.description}`,
package/dist/routing.d.ts CHANGED
@@ -123,11 +123,13 @@ export declare class ExecutionCardLifecycle {
123
123
  }
124
124
  export declare class RoutingGate {
125
125
  #private;
126
+ requiresReceipt(sessionID: string, agent: string | undefined): boolean;
127
+ hasActive(sessionID: string, agent: string, callID?: string): boolean;
126
128
  preview(input: RoutingInput): RoutingDecision;
127
129
  dispatch(input: RoutingInput, provided: unknown): RoutingDecision;
128
130
  grant(sessionID: string, decision: RoutingDecision, context?: ReceiptContext): RoutingReceipt;
129
- cancel(sessionID: string): void;
130
- consume(sessionID: string, agent: string, context?: ReceiptContext): void;
131
- complete(sessionID: string, agent: string, output: unknown): void;
131
+ cancel(sessionID: string, callID?: string): void;
132
+ consume(sessionID: string, agent: string, context?: ReceiptContext, callID?: string): void;
133
+ complete(sessionID: string, agent: string, output: unknown, callID?: string): void;
132
134
  }
133
135
  export {};