@kisev/skills-opencode 1.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 (103) hide show
  1. package/README.md +111 -0
  2. package/assets/agents/architect.md +33 -0
  3. package/assets/agents/critic.md +26 -0
  4. package/assets/agents/manager.md +53 -0
  5. package/assets/agents/mapper.md +24 -0
  6. package/assets/agents/review.md +22 -0
  7. package/assets/agents/worker.md +65 -0
  8. package/assets/commands/askme.md +10 -0
  9. package/assets/commands/ast-grep-rewrite.md +10 -0
  10. package/assets/commands/ast-grep-search.md +10 -0
  11. package/assets/commands/attempt-cancel.md +10 -0
  12. package/assets/commands/attempt-list.md +10 -0
  13. package/assets/commands/attempt-result.md +10 -0
  14. package/assets/commands/attempt-show.md +10 -0
  15. package/assets/commands/capabilities.md +9 -0
  16. package/assets/commands/code-review.md +10 -0
  17. package/assets/commands/commit-msg.md +10 -0
  18. package/assets/commands/docs-prepare.md +10 -0
  19. package/assets/commands/docs-review.md +10 -0
  20. package/assets/commands/doctor.md +9 -0
  21. package/assets/commands/doit.md +10 -0
  22. package/assets/commands/goal-list.md +10 -0
  23. package/assets/commands/goal-pause.md +10 -0
  24. package/assets/commands/goal-prepare.md +10 -0
  25. package/assets/commands/goal-remove.md +10 -0
  26. package/assets/commands/goal-show.md +10 -0
  27. package/assets/commands/goal-start.md +10 -0
  28. package/assets/commands/lsp-report.md +10 -0
  29. package/assets/commands/mattermost.md +10 -0
  30. package/assets/commands/mr-prepare.md +10 -0
  31. package/assets/commands/mr-review.md +10 -0
  32. package/assets/commands/multi-run-cancel.md +10 -0
  33. package/assets/commands/multi-run-compare.md +10 -0
  34. package/assets/commands/multi-run-fusion.md +10 -0
  35. package/assets/commands/multi-run-start.md +10 -0
  36. package/assets/commands/multi-run-status.md +10 -0
  37. package/assets/commands/overview.md +10 -0
  38. package/assets/commands/release-prepare.md +10 -0
  39. package/assets/commands/release-review.md +10 -0
  40. package/assets/commands/route.md +9 -0
  41. package/assets/commands/schedule-add.md +10 -0
  42. package/assets/commands/schedule-disable.md +10 -0
  43. package/assets/commands/schedule-enable.md +10 -0
  44. package/assets/commands/schedule-list.md +10 -0
  45. package/assets/commands/schedule-remove.md +10 -0
  46. package/assets/commands/schedule-status.md +10 -0
  47. package/assets/commands/skill-improver.md +10 -0
  48. package/assets/commands/spec-audit.md +10 -0
  49. package/assets/commands/spec-init.md +10 -0
  50. package/assets/commands/spec-onboard.md +10 -0
  51. package/assets/commands/spec-update.md +10 -0
  52. package/assets/commands/stopit.md +10 -0
  53. package/assets/commands/summary.md +10 -0
  54. package/assets/commands/task-prepare-batch.md +10 -0
  55. package/assets/commands/task-prepare.md +10 -0
  56. package/assets/commands/task-review.md +10 -0
  57. package/assets/commands/task-triage.md +10 -0
  58. package/assets/commands/team-planning.md +10 -0
  59. package/assets/commands/team-retro.md +10 -0
  60. package/assets/commands/team-roadmap.md +10 -0
  61. package/assets/commands/team-slides-prompts.md +10 -0
  62. package/assets/commands/team-sprint-close.md +10 -0
  63. package/assets/commands/team-sprint-status.md +10 -0
  64. package/assets/commands/walkthrough.md +10 -0
  65. package/assets/plugins/autonomy-policy.js +3 -0
  66. package/assets/plugins/background-attempts.js +3 -0
  67. package/assets/plugins/goal-loop.js +3 -0
  68. package/assets/plugins/rtk.js +3 -0
  69. package/assets/plugins/rules-injector.js +3 -0
  70. package/assets/plugins/schedule.js +3 -0
  71. package/assets/plugins/zed-bell.js +3 -0
  72. package/assets/plugins/zed-clickable-paths.js +3 -0
  73. package/dist/cli.d.ts +2 -0
  74. package/dist/cli.js +57 -0
  75. package/dist/generate-assets.d.ts +1 -0
  76. package/dist/generate-assets.js +10 -0
  77. package/dist/index.d.ts +160 -0
  78. package/dist/index.js +69 -0
  79. package/dist/installer.d.ts +25 -0
  80. package/dist/installer.js +332 -0
  81. package/dist/plugins/autonomy-policy.d.ts +21 -0
  82. package/dist/plugins/autonomy-policy.js +71 -0
  83. package/dist/plugins/background-attempts.d.ts +76 -0
  84. package/dist/plugins/background-attempts.js +113 -0
  85. package/dist/plugins/goal-loop.d.ts +51 -0
  86. package/dist/plugins/goal-loop.js +87 -0
  87. package/dist/plugins/rtk.d.ts +21 -0
  88. package/dist/plugins/rtk.js +48 -0
  89. package/dist/plugins/rules-injector.d.ts +22 -0
  90. package/dist/plugins/rules-injector.js +96 -0
  91. package/dist/plugins/schedule.d.ts +36 -0
  92. package/dist/plugins/schedule.js +100 -0
  93. package/dist/plugins/zed-bell.d.ts +13 -0
  94. package/dist/plugins/zed-bell.js +12 -0
  95. package/dist/plugins/zed-clickable-paths.d.ts +11 -0
  96. package/dist/plugins/zed-clickable-paths.js +10 -0
  97. package/dist/registry.d.ts +9 -0
  98. package/dist/registry.js +89 -0
  99. package/dist/routing.d.ts +39 -0
  100. package/dist/routing.js +90 -0
  101. package/dist/runtime/state.d.ts +7 -0
  102. package/dist/runtime/state.js +126 -0
  103. package/package.json +80 -0
@@ -0,0 +1,160 @@
1
+ import backgroundAttempts, { type BackgroundAttemptsOptions } from "./plugins/background-attempts.js";
2
+ import goalLoop, { type GoalLoopOptions } from "./plugins/goal-loop.js";
3
+ import scheduler, { type SchedulerOptions } from "./plugins/schedule.js";
4
+ import autonomyPolicy, { type AutonomyPolicyOptions } from "./plugins/autonomy-policy.js";
5
+ import rulesInjector, { type RulesInjectorOptions } from "./plugins/rules-injector.js";
6
+ import rtk, { type RtkOptions } from "./plugins/rtk.js";
7
+ import zedBell, { type ZedBellOptions } from "./plugins/zed-bell.js";
8
+ import zedClickablePaths, { type ZedClickablePathsOptions } from "./plugins/zed-clickable-paths.js";
9
+ export { COMMAND_REGISTRY, renderCommand } from "./registry.js";
10
+ export { CATEGORIES, resolveRouting, RoutingGate } from "./routing.js";
11
+ export { backgroundAttempts, goalLoop, scheduler, autonomyPolicy, rulesInjector, rtk, zedBell, zedClickablePaths };
12
+ export type OpenCodeOptions = {
13
+ backgroundAttempts?: BackgroundAttemptsOptions;
14
+ goalLoop?: GoalLoopOptions;
15
+ scheduler?: SchedulerOptions;
16
+ autonomyPolicy?: AutonomyPolicyOptions;
17
+ rulesInjector?: RulesInjectorOptions;
18
+ rtk?: RtkOptions;
19
+ zedBell?: ZedBellOptions;
20
+ zedClickablePaths?: ZedClickablePathsOptions;
21
+ };
22
+ declare const plugin: () => Promise<{
23
+ tool: {
24
+ route: {
25
+ description: string;
26
+ args: {
27
+ action: import("zod").ZodEnum<{
28
+ preview: "preview";
29
+ dispatch: "dispatch";
30
+ }>;
31
+ category: import("zod").ZodEnum<{
32
+ exploration: "exploration";
33
+ architecture: "architecture";
34
+ implementation: "implementation";
35
+ review: "review";
36
+ documentation: "documentation";
37
+ quick: "quick";
38
+ }>;
39
+ task: import("zod").ZodString;
40
+ requirements: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodString>>;
41
+ agents: import("zod").ZodArray<import("zod").ZodObject<{
42
+ agent: import("zod").ZodString;
43
+ available: import("zod").ZodOptional<import("zod").ZodBoolean>;
44
+ capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
45
+ tools: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
46
+ }, import("zod/v4/core").$strip>>;
47
+ override: import("zod").ZodOptional<import("zod").ZodString>;
48
+ budget: import("zod").ZodOptional<import("zod").ZodObject<{
49
+ cost_class: import("zod").ZodOptional<import("zod").ZodString>;
50
+ latency_class: import("zod").ZodOptional<import("zod").ZodString>;
51
+ }, import("zod/v4/core").$strip>>;
52
+ decision: import("zod").ZodOptional<import("zod").ZodAny>;
53
+ };
54
+ execute(args: {
55
+ action: "preview" | "dispatch";
56
+ category: "exploration" | "architecture" | "implementation" | "review" | "documentation" | "quick";
57
+ task: string;
58
+ requirements: string[];
59
+ agents: {
60
+ agent: string;
61
+ available?: boolean | undefined;
62
+ capabilities?: string[] | undefined;
63
+ tools?: string[] | undefined;
64
+ }[];
65
+ override?: string | undefined;
66
+ budget?: {
67
+ cost_class?: string | undefined;
68
+ latency_class?: string | undefined;
69
+ } | undefined;
70
+ decision?: any;
71
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
72
+ };
73
+ capabilities: {
74
+ description: string;
75
+ args: {};
76
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
77
+ };
78
+ doctor: {
79
+ description: string;
80
+ args: {};
81
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
82
+ };
83
+ };
84
+ "tool.execute.before": (input: {
85
+ tool: string;
86
+ sessionID: string;
87
+ }, output: {
88
+ args: unknown;
89
+ }) => Promise<void>;
90
+ }>;
91
+ export declare const server: () => Promise<{
92
+ tool: {
93
+ route: {
94
+ description: string;
95
+ args: {
96
+ action: import("zod").ZodEnum<{
97
+ preview: "preview";
98
+ dispatch: "dispatch";
99
+ }>;
100
+ category: import("zod").ZodEnum<{
101
+ exploration: "exploration";
102
+ architecture: "architecture";
103
+ implementation: "implementation";
104
+ review: "review";
105
+ documentation: "documentation";
106
+ quick: "quick";
107
+ }>;
108
+ task: import("zod").ZodString;
109
+ requirements: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodString>>;
110
+ agents: import("zod").ZodArray<import("zod").ZodObject<{
111
+ agent: import("zod").ZodString;
112
+ available: import("zod").ZodOptional<import("zod").ZodBoolean>;
113
+ capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
114
+ tools: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
115
+ }, import("zod/v4/core").$strip>>;
116
+ override: import("zod").ZodOptional<import("zod").ZodString>;
117
+ budget: import("zod").ZodOptional<import("zod").ZodObject<{
118
+ cost_class: import("zod").ZodOptional<import("zod").ZodString>;
119
+ latency_class: import("zod").ZodOptional<import("zod").ZodString>;
120
+ }, import("zod/v4/core").$strip>>;
121
+ decision: import("zod").ZodOptional<import("zod").ZodAny>;
122
+ };
123
+ execute(args: {
124
+ action: "preview" | "dispatch";
125
+ category: "exploration" | "architecture" | "implementation" | "review" | "documentation" | "quick";
126
+ task: string;
127
+ requirements: string[];
128
+ agents: {
129
+ agent: string;
130
+ available?: boolean | undefined;
131
+ capabilities?: string[] | undefined;
132
+ tools?: string[] | undefined;
133
+ }[];
134
+ override?: string | undefined;
135
+ budget?: {
136
+ cost_class?: string | undefined;
137
+ latency_class?: string | undefined;
138
+ } | undefined;
139
+ decision?: any;
140
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
141
+ };
142
+ capabilities: {
143
+ description: string;
144
+ args: {};
145
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
146
+ };
147
+ doctor: {
148
+ description: string;
149
+ args: {};
150
+ execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
151
+ };
152
+ };
153
+ "tool.execute.before": (input: {
154
+ tool: string;
155
+ sessionID: string;
156
+ }, output: {
157
+ args: unknown;
158
+ }) => Promise<void>;
159
+ }>;
160
+ export default plugin;
package/dist/index.js ADDED
@@ -0,0 +1,69 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { CATEGORIES, RoutingGate } from "./routing.js";
3
+ import backgroundAttempts from "./plugins/background-attempts.js";
4
+ import goalLoop from "./plugins/goal-loop.js";
5
+ import scheduler from "./plugins/schedule.js";
6
+ import autonomyPolicy from "./plugins/autonomy-policy.js";
7
+ import rulesInjector from "./plugins/rules-injector.js";
8
+ import rtk from "./plugins/rtk.js";
9
+ import zedBell from "./plugins/zed-bell.js";
10
+ import zedClickablePaths from "./plugins/zed-clickable-paths.js";
11
+ export { COMMAND_REGISTRY, renderCommand } from "./registry.js";
12
+ export { CATEGORIES, resolveRouting, RoutingGate } from "./routing.js";
13
+ export { backgroundAttempts, goalLoop, scheduler, autonomyPolicy, rulesInjector, rtk, zedBell, zedClickablePaths };
14
+ const CATALOG = {
15
+ skills: ["attempt", "goal", "schedule", "multi-run", "usage", "overview", "lsp-report"],
16
+ plugins: ["background-attempts", "goal-loop", "schedule", "autonomy-policy", "rules-injector", "rtk", "zed-bell", "zed-clickable-paths"],
17
+ replacements: ["capabilities", "route", "doctor"],
18
+ version: "1.0.0",
19
+ };
20
+ const plugin = (async () => {
21
+ const gate = new RoutingGate();
22
+ const route = tool({
23
+ description: "Resolve a capability category and dispatch one eligible agent through a one-use Task receipt gate.",
24
+ args: {
25
+ action: tool.schema.enum(["preview", "dispatch"]),
26
+ category: tool.schema.enum(CATEGORIES),
27
+ task: tool.schema.string(),
28
+ requirements: tool.schema.array(tool.schema.string()).default([]),
29
+ agents: tool.schema.array(tool.schema.object({ agent: tool.schema.string(), available: tool.schema.boolean().optional(), capabilities: tool.schema.array(tool.schema.string()).optional(), tools: tool.schema.array(tool.schema.string()).optional() })),
30
+ override: tool.schema.string().optional(),
31
+ budget: tool.schema.object({ cost_class: tool.schema.string().optional(), latency_class: tool.schema.string().optional() }).optional(),
32
+ decision: tool.schema.any().optional()
33
+ },
34
+ async execute(args, context) {
35
+ const input = { category: args.category, requirements: args.requirements, agents: args.agents, override: args.override, budget: args.budget };
36
+ if (args.action === "preview")
37
+ return JSON.stringify(gate.preview(input));
38
+ const decision = gate.dispatch(input, args.decision);
39
+ gate.grant(context.sessionID, decision);
40
+ return JSON.stringify({ decision, status: "routed" });
41
+ }
42
+ });
43
+ const capabilities = tool({
44
+ description: "Show the bundled OpenCode capability catalog without installing or changing anything.",
45
+ args: {},
46
+ async execute() { return JSON.stringify({ schema_version: 1, status: "ok", ...CATALOG }); },
47
+ });
48
+ const doctor = tool({
49
+ description: "Read package health and opt-in defaults without installing or repairing anything.",
50
+ args: {},
51
+ async execute() {
52
+ return JSON.stringify({ schema_version: 1, status: "ok", package: "@kisev/skills-opencode", opencode: ">=1.18.29", state: "not-inspected", mutations: false, defaults: { backgroundAttempts: false, goalLoop: false, scheduler: false, autonomyPolicy: false, zedBell: false, zedClickablePaths: false } });
53
+ },
54
+ });
55
+ return {
56
+ tool: { route, capabilities, doctor },
57
+ "tool.execute.before": async (input, output) => {
58
+ if (input.tool !== "task")
59
+ return;
60
+ const args = output.args && typeof output.args === "object" ? output.args : {};
61
+ const agent = typeof args.agent === "string" ? args.agent : typeof args.subagent_type === "string" ? args.subagent_type : undefined;
62
+ if (!agent)
63
+ throw new Error("Native Task requires an explicit agent and an active routing receipt");
64
+ gate.consume(input.sessionID, agent);
65
+ }
66
+ };
67
+ });
68
+ export const server = plugin;
69
+ export default plugin;
@@ -0,0 +1,25 @@
1
+ export type Scope = "global" | "project";
2
+ export type Action = "install" | "uninstall";
3
+ export type Operation = "create" | "update" | "remove" | "unchanged" | "missing" | "conflict";
4
+ export type PlanItem = {
5
+ path: string;
6
+ operation: Operation;
7
+ reason?: string;
8
+ sha256?: string;
9
+ };
10
+ export type Plan = {
11
+ schema_version: 1;
12
+ action: Action;
13
+ scope: Scope;
14
+ root: string;
15
+ operations: PlanItem[];
16
+ manifest_sha256?: string;
17
+ digest: string;
18
+ };
19
+ export declare class InstallerError extends Error {
20
+ readonly code: string;
21
+ constructor(code: string, message: string);
22
+ }
23
+ export declare function preview(action: Action, scope: Scope, cwd?: string, home?: string): Promise<Plan>;
24
+ export declare function apply(action: Action, scope: Scope, digest: string, cwd?: string, home?: string): Promise<Plan>;
25
+ export declare function result(plan: Plan, applied: boolean): string;
@@ -0,0 +1,332 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { lstat, mkdir, readdir, readFile, rename, rm, unlink, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { basename, dirname, isAbsolute, join, normalize, parse, relative, resolve, sep } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ const PACKAGE_NAME = "@kisev/skills-opencode";
8
+ const MANIFEST_NAME = ".skills-opencode-manifest.json";
9
+ const MANIFEST_SCHEMA_VERSION = 1;
10
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
11
+ const assetsRoot = resolve(packageRoot, "assets");
12
+ export class InstallerError extends Error {
13
+ code;
14
+ constructor(code, message) {
15
+ super(message);
16
+ this.code = code;
17
+ }
18
+ }
19
+ function stable(value) {
20
+ if (value === null || typeof value !== "object")
21
+ return JSON.stringify(value);
22
+ if (Array.isArray(value))
23
+ return `[${value.map(stable).join(",")}]`;
24
+ const object = value;
25
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stable(object[key])}`).join(",")}}`;
26
+ }
27
+ function sha256(value) {
28
+ return createHash("sha256").update(value).digest("hex");
29
+ }
30
+ function planDigest(plan) {
31
+ return sha256(stable(plan));
32
+ }
33
+ function packageVersion() {
34
+ const metadata = JSON.parse(requireReadFile(resolve(packageRoot, "package.json")));
35
+ if (typeof metadata.version !== "string" || !metadata.version)
36
+ throw new InstallerError("invalid_package", "Package version is unavailable");
37
+ return metadata.version;
38
+ }
39
+ function requireReadFile(path) {
40
+ try {
41
+ return readFileSync(path, "utf8");
42
+ }
43
+ catch (error) {
44
+ throw new InstallerError("asset_error", `Cannot read ${path}: ${String(error)}`);
45
+ }
46
+ }
47
+ function isInside(root, target) {
48
+ const difference = relative(root, target);
49
+ return difference === "" || (!difference.startsWith(`..${sep}`) && difference !== ".." && !isAbsolute(difference));
50
+ }
51
+ function assertSafeRelative(value) {
52
+ if (!value || isAbsolute(value) || normalize(value) !== value || value.split(/[\\/]/).some((part) => !part || part === "." || part === "..")) {
53
+ throw new InstallerError("unsafe_path", `Unsafe relative path: ${value}`);
54
+ }
55
+ }
56
+ async function lstatSafe(path) {
57
+ try {
58
+ return await lstat(path);
59
+ }
60
+ catch (error) {
61
+ if (error.code === "ENOENT")
62
+ return undefined;
63
+ throw error;
64
+ }
65
+ }
66
+ async function assertSafeAncestors(path) {
67
+ const parsed = parse(resolve(path));
68
+ let current = parsed.root;
69
+ const pieces = resolve(path).slice(parsed.root.length).split(sep).filter(Boolean);
70
+ for (const piece of pieces) {
71
+ current = join(current, piece);
72
+ const stat = await lstatSafe(current);
73
+ if (!stat)
74
+ return;
75
+ if (stat.isSymbolicLink())
76
+ throw new InstallerError("unsafe_path", `Symlink is not allowed: ${current}`);
77
+ if (current !== resolve(path) && !stat.isDirectory())
78
+ throw new InstallerError("unsafe_path", `Path parent is not a directory: ${current}`);
79
+ }
80
+ }
81
+ async function readRegular(path) {
82
+ await assertSafeAncestors(path);
83
+ const stat = await lstatSafe(path);
84
+ if (!stat)
85
+ return undefined;
86
+ if (!stat.isFile())
87
+ throw new InstallerError("unsafe_path", `Target is not a regular file: ${path}`);
88
+ return readFile(path);
89
+ }
90
+ async function ensureSafeDirectory(path) {
91
+ await assertSafeAncestors(path);
92
+ const stat = await lstatSafe(path);
93
+ if (stat && !stat.isDirectory())
94
+ throw new InstallerError("unsafe_path", `Destination root is not a directory: ${path}`);
95
+ await mkdir(path, { recursive: true });
96
+ await assertSafeAncestors(path);
97
+ }
98
+ function rootFor(scope, cwd = process.cwd(), home = homedir()) {
99
+ return scope === "global" ? resolve(home, ".config", "opencode") : resolve(cwd, ".opencode");
100
+ }
101
+ function destination(root, relativePath) {
102
+ assertSafeRelative(relativePath);
103
+ const target = resolve(root, relativePath);
104
+ if (!isInside(root, target))
105
+ throw new InstallerError("unsafe_path", `Path escapes destination root: ${relativePath}`);
106
+ return target;
107
+ }
108
+ async function assets() {
109
+ const result = [];
110
+ for (const category of ["agents", "commands", "plugins"]) {
111
+ const directory = resolve(assetsRoot, category);
112
+ await assertSafeAncestors(directory);
113
+ const entries = await readdir(directory, { withFileTypes: true });
114
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
115
+ const extension = category === "plugins" ? ".js" : ".md";
116
+ if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith(extension))
117
+ throw new InstallerError("asset_error", `Asset is not a regular ${extension} asset: ${entry.name}`);
118
+ const relativePath = `${category}/${entry.name}`;
119
+ assertSafeRelative(relativePath);
120
+ const source = destination(assetsRoot, relativePath);
121
+ const content = await readRegular(source);
122
+ if (!content)
123
+ throw new InstallerError("asset_error", `Asset is missing: ${relativePath}`);
124
+ result.push({ relativePath, content, sha256: sha256(content) });
125
+ }
126
+ }
127
+ return result.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
128
+ }
129
+ function manifestPath(root) {
130
+ return destination(root, MANIFEST_NAME);
131
+ }
132
+ function parseManifest(value, path) {
133
+ let parsed;
134
+ try {
135
+ parsed = JSON.parse(value.toString("utf8"));
136
+ }
137
+ catch {
138
+ throw new InstallerError("invalid_manifest", `Ownership manifest is not valid JSON: ${path}`);
139
+ }
140
+ if (!parsed || typeof parsed !== "object")
141
+ throw new InstallerError("invalid_manifest", `Ownership manifest is not an object: ${path}`);
142
+ const manifest = parsed;
143
+ if (manifest.schema_version !== MANIFEST_SCHEMA_VERSION || manifest.package !== PACKAGE_NAME || typeof manifest.version !== "string" || (manifest.state !== undefined && manifest.state !== "applying") || !manifest.files || typeof manifest.files !== "object" || Array.isArray(manifest.files))
144
+ throw new InstallerError("invalid_manifest", `Ownership manifest has an unexpected format: ${path}`);
145
+ for (const [relativePath, record] of Object.entries(manifest.files)) {
146
+ assertSafeRelative(relativePath);
147
+ if (!record || typeof record !== "object" || typeof record.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(record.sha256) || (record.previous_sha256 !== undefined && (typeof record.previous_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(record.previous_sha256))))
148
+ throw new InstallerError("invalid_manifest", `Ownership manifest has an invalid record: ${relativePath}`);
149
+ }
150
+ return manifest;
151
+ }
152
+ async function manifestFor(root) {
153
+ const path = manifestPath(root);
154
+ const content = await readRegular(path);
155
+ return content ? { manifest: parseManifest(content, path), sha256: sha256(content) } : {};
156
+ }
157
+ function makePlan(action, scope, root, operations, manifestHash) {
158
+ const base = { schema_version: 1, action, scope, root, operations, ...(manifestHash ? { manifest_sha256: manifestHash } : {}) };
159
+ return { ...base, digest: planDigest(base) };
160
+ }
161
+ async function installPlan(scope, cwd, home) {
162
+ const root = rootFor(scope, cwd, home);
163
+ await assertSafeAncestors(root);
164
+ const [owned, currentAssets] = await Promise.all([manifestFor(root), assets()]);
165
+ const manifest = owned.manifest;
166
+ const operations = [];
167
+ for (const asset of currentAssets) {
168
+ const target = destination(root, asset.relativePath);
169
+ const content = await readRegular(target);
170
+ if (!content) {
171
+ operations.push({ path: asset.relativePath, operation: "create", sha256: asset.sha256 });
172
+ continue;
173
+ }
174
+ const currentHash = sha256(content);
175
+ const record = manifest?.files[asset.relativePath];
176
+ if (!record)
177
+ operations.push({ path: asset.relativePath, operation: "conflict", reason: "unmanaged_file", sha256: currentHash });
178
+ else if (record.sha256 !== currentHash && !(manifest?.state === "applying" && record.previous_sha256 === currentHash))
179
+ operations.push({ path: asset.relativePath, operation: "conflict", reason: "managed_file_changed", sha256: currentHash });
180
+ else if (currentHash === asset.sha256)
181
+ operations.push({ path: asset.relativePath, operation: "unchanged", sha256: currentHash });
182
+ else
183
+ operations.push({ path: asset.relativePath, operation: "update", sha256: asset.sha256 });
184
+ }
185
+ const active = new Set(currentAssets.map((asset) => asset.relativePath));
186
+ for (const [relativePath, record] of Object.entries(manifest?.files ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
187
+ if (active.has(relativePath))
188
+ continue;
189
+ const content = await readRegular(destination(root, relativePath));
190
+ if (!content)
191
+ operations.push({ path: relativePath, operation: "missing" });
192
+ else if (sha256(content) === record.sha256)
193
+ operations.push({ path: relativePath, operation: "remove", sha256: record.sha256 });
194
+ else
195
+ operations.push({ path: relativePath, operation: "conflict", reason: "managed_file_changed", sha256: sha256(content) });
196
+ }
197
+ return { plan: makePlan("install", scope, root, operations, owned.sha256), assets: currentAssets, manifest };
198
+ }
199
+ async function uninstallPlan(scope, cwd, home) {
200
+ const root = rootFor(scope, cwd, home);
201
+ await assertSafeAncestors(root);
202
+ const owned = await manifestFor(root);
203
+ if (!owned.manifest)
204
+ return { plan: makePlan("uninstall", scope, root, []) };
205
+ const operations = [];
206
+ for (const [relativePath, record] of Object.entries(owned.manifest.files).sort(([left], [right]) => left.localeCompare(right))) {
207
+ const content = await readRegular(destination(root, relativePath));
208
+ if (!content)
209
+ operations.push({ path: relativePath, operation: "missing" });
210
+ else if (sha256(content) === record.sha256)
211
+ operations.push({ path: relativePath, operation: "remove", sha256: record.sha256 });
212
+ else
213
+ operations.push({ path: relativePath, operation: "conflict", reason: "managed_file_changed", sha256: sha256(content) });
214
+ }
215
+ return { plan: makePlan("uninstall", scope, root, operations, owned.sha256), manifest: owned.manifest };
216
+ }
217
+ export async function preview(action, scope, cwd, home) {
218
+ return action === "install" ? (await installPlan(scope, cwd, home)).plan : (await uninstallPlan(scope, cwd, home)).plan;
219
+ }
220
+ async function writeAtomically(path, content) {
221
+ await ensureSafeDirectory(dirname(path));
222
+ await assertSafeAncestors(path);
223
+ const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
224
+ try {
225
+ await writeFile(temporary, content, { flag: "wx", mode: 0o644 });
226
+ await rename(temporary, path);
227
+ }
228
+ finally {
229
+ await rm(temporary, { force: true });
230
+ }
231
+ }
232
+ async function writeManifest(root, manifest, existingHash, expectAbsent = false) {
233
+ const target = manifestPath(root);
234
+ const current = await readRegular(target);
235
+ if (existingHash && (!current || sha256(current) !== existingHash))
236
+ throw new InstallerError("stale_plan", "Ownership manifest changed after preview");
237
+ if (expectAbsent && current)
238
+ throw new InstallerError("stale_plan", "Ownership manifest appeared after preview");
239
+ const content = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`);
240
+ if (!current || !current.equals(content))
241
+ await writeAtomically(target, content);
242
+ return sha256(content);
243
+ }
244
+ async function applyInstall(scope, expectedDigest, cwd, home) {
245
+ const initial = await installPlan(scope, cwd, home);
246
+ if (initial.plan.digest !== expectedDigest)
247
+ throw new InstallerError("stale_plan", "Install plan changed; request a new dry-run");
248
+ if (initial.plan.operations.some((item) => item.operation === "conflict"))
249
+ throw new InstallerError("conflict", "Install plan contains conflicts");
250
+ const verified = await installPlan(scope, cwd, home);
251
+ if (verified.plan.digest !== expectedDigest)
252
+ throw new InstallerError("stale_plan", "Install plan changed before apply");
253
+ await ensureSafeDirectory(verified.plan.root);
254
+ const staged = {
255
+ schema_version: MANIFEST_SCHEMA_VERSION,
256
+ package: PACKAGE_NAME,
257
+ version: packageVersion(),
258
+ state: "applying",
259
+ files: Object.fromEntries(verified.assets.map((asset) => {
260
+ const operation = verified.plan.operations.find((item) => item.path === asset.relativePath)?.operation;
261
+ const previous = operation === "update" ? verified.manifest?.files[asset.relativePath]?.previous_sha256 ?? verified.manifest?.files[asset.relativePath]?.sha256 : undefined;
262
+ return [asset.relativePath, { sha256: asset.sha256, ...(previous ? { previous_sha256: previous } : {}) }];
263
+ }))
264
+ };
265
+ const stagedHash = await writeManifest(verified.plan.root, staged, verified.plan.manifest_sha256, !verified.plan.manifest_sha256);
266
+ for (const asset of verified.assets) {
267
+ const item = verified.plan.operations.find((candidate) => candidate.path === asset.relativePath);
268
+ if (item?.operation === "create" || item?.operation === "update") {
269
+ const target = destination(verified.plan.root, asset.relativePath);
270
+ const current = await readRegular(target);
271
+ const expected = item.operation === "create" ? undefined : verified.manifest?.files[asset.relativePath]?.previous_sha256 ?? verified.manifest?.files[asset.relativePath]?.sha256;
272
+ if ((current && sha256(current)) !== expected)
273
+ throw new InstallerError("stale_plan", `Destination changed: ${asset.relativePath}`);
274
+ await writeAtomically(target, asset.content);
275
+ }
276
+ }
277
+ for (const item of verified.plan.operations.filter((candidate) => candidate.operation === "remove")) {
278
+ const target = destination(verified.plan.root, item.path);
279
+ const current = await readRegular(target);
280
+ if (!current || sha256(current) !== item.sha256)
281
+ throw new InstallerError("stale_plan", `Destination changed: ${item.path}`);
282
+ await unlink(target);
283
+ }
284
+ const completed = {
285
+ schema_version: MANIFEST_SCHEMA_VERSION,
286
+ package: PACKAGE_NAME,
287
+ version: packageVersion(),
288
+ files: Object.fromEntries(verified.assets.map((asset) => [asset.relativePath, { sha256: asset.sha256 }]))
289
+ };
290
+ await writeManifest(verified.plan.root, completed, stagedHash);
291
+ return verified.plan;
292
+ }
293
+ async function applyUninstall(scope, expectedDigest, cwd, home) {
294
+ const initial = await uninstallPlan(scope, cwd, home);
295
+ if (initial.plan.digest !== expectedDigest)
296
+ throw new InstallerError("stale_plan", "Uninstall plan changed; request a new dry-run");
297
+ if (!initial.manifest)
298
+ return initial.plan;
299
+ const verified = await uninstallPlan(scope, cwd, home);
300
+ if (verified.plan.digest !== expectedDigest || !verified.manifest)
301
+ throw new InstallerError("stale_plan", "Uninstall plan changed before apply");
302
+ const remaining = {};
303
+ for (const item of verified.plan.operations) {
304
+ if (item.operation === "remove") {
305
+ const target = destination(verified.plan.root, item.path);
306
+ const current = await readRegular(target);
307
+ if (!current || sha256(current) !== item.sha256)
308
+ throw new InstallerError("stale_plan", `Destination changed: ${item.path}`);
309
+ await unlink(target);
310
+ }
311
+ else if (item.operation === "conflict") {
312
+ remaining[item.path] = verified.manifest.files[item.path];
313
+ }
314
+ }
315
+ const manifestTarget = manifestPath(verified.plan.root);
316
+ const manifestContent = await readRegular(manifestTarget);
317
+ if (!manifestContent || sha256(manifestContent) !== verified.plan.manifest_sha256)
318
+ throw new InstallerError("stale_plan", "Ownership manifest changed after preview");
319
+ if (Object.keys(remaining).length)
320
+ await writeManifest(verified.plan.root, { schema_version: MANIFEST_SCHEMA_VERSION, package: PACKAGE_NAME, version: packageVersion(), files: remaining }, verified.plan.manifest_sha256);
321
+ else
322
+ await unlink(manifestTarget);
323
+ return verified.plan;
324
+ }
325
+ export async function apply(action, scope, digest, cwd, home) {
326
+ if (!/^[a-f0-9]{64}$/.test(digest))
327
+ throw new InstallerError("invalid_digest", "Confirmation digest must be a SHA-256 hex value");
328
+ return action === "install" ? applyInstall(scope, digest, cwd, home) : applyUninstall(scope, digest, cwd, home);
329
+ }
330
+ export function result(plan, applied) {
331
+ return JSON.stringify({ status: "ok", applied, plan }, null, 2);
332
+ }
@@ -0,0 +1,21 @@
1
+ export type AutonomyPolicyOptions = {
2
+ enabled?: boolean;
3
+ now?: () => number;
4
+ };
5
+ export declare function autonomyPolicy({ directory, cwd }?: {
6
+ directory?: string;
7
+ cwd?: string;
8
+ }, options?: AutonomyPolicyOptions): Promise<{
9
+ "permission.ask"?: undefined;
10
+ } | {
11
+ "permission.ask": (input: {
12
+ permission: string;
13
+ sessionID: string;
14
+ patterns?: string[];
15
+ metadata?: Record<string, unknown>;
16
+ tool?: string;
17
+ }, output: {
18
+ status: "ask" | "deny" | "allow";
19
+ }) => Promise<void>;
20
+ }>;
21
+ export default autonomyPolicy;