@codemcp/ade-core 0.0.2 → 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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @codemcp/ade-core@0.0.2 build /home/runner/work/ade/ade/packages/core
2
+ > @codemcp/ade-core@0.1.0 build /home/runner/work/ade/ade/packages/core
3
3
  > tsc -p tsconfig.build.json
4
4
 
@@ -1,5 +1,5 @@
1
1
 
2
- > @codemcp/ade-core@0.0.2 format /home/runner/work/ade/ade/packages/core
2
+ > @codemcp/ade-core@0.1.0 format /home/runner/work/ade/ade/packages/core
3
3
  > prettier --check .
4
4
 
5
5
  Checking formatting...
@@ -1,4 +1,4 @@
1
1
 
2
- > @codemcp/ade-core@0.0.2 lint /home/runner/work/ade/ade/packages/core
2
+ > @codemcp/ade-core@0.1.0 lint /home/runner/work/ade/ade/packages/core
3
3
  > eslint .
4
4
 
@@ -1,21 +1,21 @@
1
1
 
2
- > @codemcp/ade-core@0.0.2 test /home/runner/work/ade/ade/packages/core
2
+ > @codemcp/ade-core@0.1.0 test /home/runner/work/ade/ade/packages/core
3
3
  > vitest --run
4
4
 
5
5
 
6
6
   RUN  v3.2.4 /home/runner/work/ade/ade/packages/core
7
7
 
8
- ✓ src/registry.spec.ts (8 tests) 51ms
9
- ✓ src/resolver.spec.ts (19 tests) 54ms
10
- ✓ src/catalog/catalog.spec.ts (41 tests) 106ms
11
- ✓ src/writers/skills.spec.ts (6 tests) 36ms
12
- ✓ src/writers/workflows.spec.ts (6 tests) 22ms
13
- ✓ src/config.spec.ts (7 tests) 86ms
14
- ✓ src/writers/instruction.spec.ts (5 tests) 20ms
15
- ✓ src/writers/knowledge.spec.ts (2 tests) 6ms
8
+ ✓ src/registry.spec.ts (8 tests) 36ms
9
+ ✓ src/resolver.spec.ts (21 tests) 67ms
10
+ ✓ src/catalog/catalog.spec.ts (43 tests) 113ms
11
+ ✓ src/writers/workflows.spec.ts (6 tests) 25ms
12
+ ✓ src/writers/skills.spec.ts (6 tests) 32ms
13
+ ✓ src/config.spec.ts (7 tests) 117ms
14
+ ✓ src/writers/instruction.spec.ts (5 tests) 12ms
15
+ ✓ src/writers/knowledge.spec.ts (2 tests) 19ms
16
16
 
17
17
   Test Files  8 passed (8)
18
-  Tests  94 passed (94)
19
-  Start at  06:53:29
20
-  Duration  3.21s (transform 1.20s, setup 0ms, collect 2.14s, tests 382ms, environment 4ms, prepare 2.43s)
18
+  Tests  98 passed (98)
19
+  Start at  12:11:20
20
+  Duration  3.08s (transform 930ms, setup 0ms, collect 1.94s, tests 420ms, environment 8ms, prepare 2.09s)
21
21
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @codemcp/ade-core@0.0.2 typecheck /home/runner/work/ade/ade/packages/core
2
+ > @codemcp/ade-core@0.1.0 typecheck /home/runner/work/ade/ade/packages/core
3
3
  > tsc
4
4
 
@@ -0,0 +1,2 @@
1
+ import type { Facet } from "../../types.js";
2
+ export declare const autonomyFacet: Facet;
@@ -0,0 +1,85 @@
1
+ const ALL_CAPABILITIES = [
2
+ "read",
3
+ "edit_write",
4
+ "search_list",
5
+ "bash_safe",
6
+ "bash_unsafe",
7
+ "web",
8
+ "task_agent"
9
+ ];
10
+ function capabilityMap(defaultDecision, overrides = {}) {
11
+ return Object.fromEntries(ALL_CAPABILITIES.map((capability) => [
12
+ capability,
13
+ overrides[capability] ?? defaultDecision
14
+ ]));
15
+ }
16
+ function autonomyPolicy(profile) {
17
+ switch (profile) {
18
+ case "rigid":
19
+ return {
20
+ profile,
21
+ capabilities: capabilityMap("ask")
22
+ };
23
+ case "sensible-defaults":
24
+ return {
25
+ profile,
26
+ capabilities: capabilityMap("ask", {
27
+ read: "allow",
28
+ edit_write: "allow",
29
+ search_list: "allow",
30
+ bash_safe: "allow",
31
+ task_agent: "allow",
32
+ web: "ask"
33
+ })
34
+ };
35
+ case "max-autonomy":
36
+ return {
37
+ profile,
38
+ capabilities: capabilityMap("allow", {
39
+ web: "ask"
40
+ })
41
+ };
42
+ }
43
+ }
44
+ export const autonomyFacet = {
45
+ id: "autonomy",
46
+ label: "Autonomy",
47
+ description: "How much initiative and execution freedom the agent should have",
48
+ required: false,
49
+ multiSelect: false,
50
+ options: [
51
+ {
52
+ id: "rigid",
53
+ label: "Rigid",
54
+ description: "Keep built-in capabilities approval-gated and require confirmation before acting",
55
+ recipe: [
56
+ {
57
+ writer: "permission-policy",
58
+ config: autonomyPolicy("rigid")
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ id: "sensible-defaults",
64
+ label: "Sensible defaults",
65
+ description: "Allow a curated low-risk built-in capability set while keeping web access approval-gated",
66
+ recipe: [
67
+ {
68
+ writer: "permission-policy",
69
+ config: autonomyPolicy("sensible-defaults")
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ id: "max-autonomy",
75
+ label: "Max autonomy",
76
+ description: "Allow broad local built-in autonomy while keeping web access approval-gated",
77
+ recipe: [
78
+ {
79
+ writer: "permission-policy",
80
+ config: autonomyPolicy("max-autonomy")
81
+ }
82
+ ]
83
+ }
84
+ ]
85
+ };
@@ -2,9 +2,16 @@ import { processFacet } from "./facets/process.js";
2
2
  import { architectureFacet } from "./facets/architecture.js";
3
3
  import { practicesFacet } from "./facets/practices.js";
4
4
  import { backpressureFacet } from "./facets/backpressure.js";
5
+ import { autonomyFacet } from "./facets/autonomy.js";
5
6
  export function getDefaultCatalog() {
6
7
  return {
7
- facets: [processFacet, architectureFacet, practicesFacet, backpressureFacet]
8
+ facets: [
9
+ processFacet,
10
+ architectureFacet,
11
+ practicesFacet,
12
+ backpressureFacet,
13
+ autonomyFacet
14
+ ]
8
15
  };
9
16
  }
10
17
  export function getFacet(catalog, id) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { type Catalog, type Facet, type Option, type Provision, type DocsetDef } from "./types.js";
2
- export { type LogicalConfig, type McpServerEntry, type CliAction, type KnowledgeSource, type SkillDefinition, type InlineSkill, type ExternalSkill, type GitHook } from "./types.js";
2
+ export { type LogicalConfig, type McpServerEntry, type CliAction, type KnowledgeSource, type SkillDefinition, type InlineSkill, type ExternalSkill, type GitHook, type PermissionPolicy, type AutonomyProfile, type AutonomyCapability, type PermissionDecision, type PermissionRule } from "./types.js";
3
3
  export { type ResolutionContext, type ResolvedFacet } from "./types.js";
4
4
  export { type UserConfig, type LockFile } from "./types.js";
5
5
  export { type ProvisionWriter } from "./types.js";
@@ -10,3 +10,4 @@ export { resolve, collectDocsets } from "./resolver.js";
10
10
  export { getDefaultCatalog, getFacet, getOption, sortFacets, getVisibleOptions } from "./catalog/index.js";
11
11
  export { skillsWriter } from "./writers/skills.js";
12
12
  export { knowledgeWriter } from "./writers/knowledge.js";
13
+ export { permissionPolicyWriter } from "./writers/permission-policy.js";
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export { resolve, collectDocsets } from "./resolver.js";
4
4
  export { getDefaultCatalog, getFacet, getOption, sortFacets, getVisibleOptions } from "./catalog/index.js";
5
5
  export { skillsWriter } from "./writers/skills.js";
6
6
  export { knowledgeWriter } from "./writers/knowledge.js";
7
+ export { permissionPolicyWriter } from "./writers/permission-policy.js";
package/dist/registry.js CHANGED
@@ -4,6 +4,7 @@ import { skillsWriter } from "./writers/skills.js";
4
4
  import { knowledgeWriter } from "./writers/knowledge.js";
5
5
  import { gitHooksWriter } from "./writers/git-hooks.js";
6
6
  import { setupNoteWriter } from "./writers/setup-note.js";
7
+ import { permissionPolicyWriter } from "./writers/permission-policy.js";
7
8
  export function createRegistry() {
8
9
  return {
9
10
  provisions: new Map(),
@@ -30,6 +31,7 @@ export function createDefaultRegistry() {
30
31
  registerProvisionWriter(registry, knowledgeWriter);
31
32
  registerProvisionWriter(registry, gitHooksWriter);
32
33
  registerProvisionWriter(registry, setupNoteWriter);
34
+ registerProvisionWriter(registry, permissionPolicyWriter);
33
35
  // Stub writers for types not yet implemented
34
36
  for (const id of ["mcp-server", "installable"]) {
35
37
  registerProvisionWriter(registry, {
package/dist/resolver.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { getFacet, getOption } from "./catalog/index.js";
2
2
  import { getProvisionWriter } from "./registry.js";
3
+ import { permissionPolicyWriter } from "./writers/permission-policy.js";
3
4
  export async function resolve(userConfig, catalog, registry) {
4
5
  const result = {
5
6
  mcp_servers: [],
@@ -24,32 +25,13 @@ export async function resolve(userConfig, catalog, registry) {
24
25
  }
25
26
  context.resolved[facetId] = { optionId: selectedId, option };
26
27
  for (const provision of option.recipe) {
27
- const writer = getProvisionWriter(registry, provision.writer);
28
+ const writer = getProvisionWriter(registry, provision.writer) ??
29
+ getBuiltInProvisionWriter(provision);
28
30
  if (!writer) {
29
31
  continue;
30
32
  }
31
33
  const partial = await writer.write(provision.config, context);
32
- if (partial.mcp_servers) {
33
- result.mcp_servers.push(...partial.mcp_servers);
34
- }
35
- if (partial.instructions) {
36
- result.instructions.push(...partial.instructions);
37
- }
38
- if (partial.cli_actions) {
39
- result.cli_actions.push(...partial.cli_actions);
40
- }
41
- if (partial.knowledge_sources) {
42
- result.knowledge_sources.push(...partial.knowledge_sources);
43
- }
44
- if (partial.skills) {
45
- result.skills.push(...partial.skills);
46
- }
47
- if (partial.git_hooks) {
48
- result.git_hooks.push(...partial.git_hooks);
49
- }
50
- if (partial.setup_notes) {
51
- result.setup_notes.push(...partial.setup_notes);
52
- }
34
+ mergeLogicalConfig(result, partial);
53
35
  }
54
36
  }
55
37
  }
@@ -116,6 +98,51 @@ export async function resolve(userConfig, catalog, registry) {
116
98
  result.mcp_servers = Array.from(serversByRef.values());
117
99
  return result;
118
100
  }
101
+ function getBuiltInProvisionWriter(provision) {
102
+ if (provision.writer === "permission-policy") {
103
+ return permissionPolicyWriter;
104
+ }
105
+ return undefined;
106
+ }
107
+ function mergeLogicalConfig(result, partial) {
108
+ if (partial.mcp_servers) {
109
+ result.mcp_servers.push(...partial.mcp_servers);
110
+ }
111
+ if (partial.instructions) {
112
+ result.instructions.push(...partial.instructions);
113
+ }
114
+ if (partial.cli_actions) {
115
+ result.cli_actions.push(...partial.cli_actions);
116
+ }
117
+ if (partial.knowledge_sources) {
118
+ result.knowledge_sources.push(...partial.knowledge_sources);
119
+ }
120
+ if (partial.skills) {
121
+ result.skills.push(...partial.skills);
122
+ }
123
+ if (partial.git_hooks) {
124
+ result.git_hooks.push(...partial.git_hooks);
125
+ }
126
+ if (partial.setup_notes) {
127
+ result.setup_notes.push(...partial.setup_notes);
128
+ }
129
+ if (partial.permission_policy) {
130
+ result.permission_policy = mergePermissionPolicy(result.permission_policy, partial.permission_policy);
131
+ }
132
+ }
133
+ function mergePermissionPolicy(existing, incoming) {
134
+ if (!existing) {
135
+ return incoming;
136
+ }
137
+ return {
138
+ ...existing,
139
+ ...incoming,
140
+ capabilities: {
141
+ ...existing.capabilities,
142
+ ...incoming.capabilities
143
+ }
144
+ };
145
+ }
119
146
  /**
120
147
  * Collect all unique docsets implied by the given choices.
121
148
  * Used by the TUI to present docsets for confirmation before resolution.
package/dist/types.d.ts CHANGED
@@ -28,7 +28,7 @@ export interface Provision {
28
28
  writer: ProvisionWriter;
29
29
  config: Record<string, unknown>;
30
30
  }
31
- export type ProvisionWriter = "workflows" | "skills" | "knowledge" | "mcp-server" | "instruction" | "installable" | "git-hooks" | "setup-note";
31
+ export type ProvisionWriter = "workflows" | "skills" | "knowledge" | "mcp-server" | "instruction" | "installable" | "git-hooks" | "setup-note" | "permission-policy";
32
32
  export interface InlineSkill {
33
33
  name: string;
34
34
  description: string;
@@ -43,7 +43,19 @@ export interface GitHook {
43
43
  phase: "pre-commit" | "pre-push";
44
44
  script: string;
45
45
  }
46
- export interface LogicalConfig {
46
+ export type AutonomyProfile = "rigid" | "sensible-defaults" | "max-autonomy";
47
+ export type PermissionDecision = "ask" | "allow" | "deny";
48
+ export type AutonomyCapability = "read" | "edit_write" | "search_list" | "bash_safe" | "bash_unsafe" | "web" | "task_agent";
49
+ /**
50
+ * @deprecated Harness-specific tool-level rules are no longer produced by core.
51
+ * Kept temporarily as a compatibility type for downstream packages.
52
+ */
53
+ export type PermissionRule = PermissionDecision | Record<string, PermissionDecision>;
54
+ export interface PermissionPolicy extends Record<string, unknown> {
55
+ profile: AutonomyProfile;
56
+ capabilities: Record<AutonomyCapability, PermissionDecision>;
57
+ }
58
+ export interface LogicalConfig extends Record<string, unknown> {
47
59
  mcp_servers: McpServerEntry[];
48
60
  instructions: string[];
49
61
  cli_actions: CliAction[];
@@ -51,6 +63,7 @@ export interface LogicalConfig {
51
63
  skills: SkillDefinition[];
52
64
  git_hooks: GitHook[];
53
65
  setup_notes: string[];
66
+ permission_policy?: PermissionPolicy;
54
67
  }
55
68
  export interface McpServerEntry {
56
69
  ref: string;
@@ -0,0 +1,2 @@
1
+ import type { ProvisionWriterDef } from "../types.js";
2
+ export declare const permissionPolicyWriter: ProvisionWriterDef;
@@ -0,0 +1,6 @@
1
+ export const permissionPolicyWriter = {
2
+ id: "permission-policy",
3
+ async write(config) {
4
+ return { permission_policy: config };
5
+ }
6
+ };
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "yaml": "^2.8.2"
20
20
  },
21
- "version": "0.0.2",
21
+ "version": "0.1.0",
22
22
  "scripts": {
23
23
  "build": "tsc -p tsconfig.build.json",
24
24
  "clean:build": "rimraf ./dist",
@@ -461,6 +461,45 @@ describe("catalog", () => {
461
461
  });
462
462
  });
463
463
 
464
+ describe("autonomy facet", () => {
465
+ it("exists in the default catalog with the supported autonomy profiles", () => {
466
+ const catalog = getDefaultCatalog();
467
+ const autonomy = getFacet(catalog, "autonomy");
468
+
469
+ expect(autonomy).toBeDefined();
470
+ expect(autonomy!.required).toBe(false);
471
+ expect(autonomy!.multiSelect).toBe(false);
472
+ expect(autonomy!.options.map((option) => option.id)).toEqual([
473
+ "rigid",
474
+ "sensible-defaults",
475
+ "max-autonomy"
476
+ ]);
477
+ });
478
+
479
+ it("uses the abstract capability model for autonomy recipes", () => {
480
+ const catalog = getDefaultCatalog();
481
+ const autonomy = getFacet(catalog, "autonomy")!;
482
+ const sensibleDefaults = getOption(autonomy, "sensible-defaults")!;
483
+ const provision = sensibleDefaults.recipe.find(
484
+ (entry) => entry.writer === "permission-policy"
485
+ );
486
+
487
+ expect(provision).toBeDefined();
488
+ expect(provision!.config).toEqual({
489
+ profile: "sensible-defaults",
490
+ capabilities: {
491
+ read: "allow",
492
+ edit_write: "allow",
493
+ search_list: "allow",
494
+ bash_safe: "allow",
495
+ bash_unsafe: "ask",
496
+ web: "ask",
497
+ task_agent: "allow"
498
+ }
499
+ });
500
+ });
501
+ });
502
+
464
503
  describe("sortFacets", () => {
465
504
  it("returns all facets", () => {
466
505
  const catalog = getDefaultCatalog();
@@ -0,0 +1,106 @@
1
+ import type {
2
+ AutonomyCapability,
3
+ Facet,
4
+ PermissionDecision,
5
+ PermissionPolicy
6
+ } from "../../types.js";
7
+
8
+ const ALL_CAPABILITIES: AutonomyCapability[] = [
9
+ "read",
10
+ "edit_write",
11
+ "search_list",
12
+ "bash_safe",
13
+ "bash_unsafe",
14
+ "web",
15
+ "task_agent"
16
+ ];
17
+
18
+ function capabilityMap(
19
+ defaultDecision: PermissionDecision,
20
+ overrides: Partial<Record<AutonomyCapability, PermissionDecision>> = {}
21
+ ): Record<AutonomyCapability, PermissionDecision> {
22
+ return Object.fromEntries(
23
+ ALL_CAPABILITIES.map((capability) => [
24
+ capability,
25
+ overrides[capability] ?? defaultDecision
26
+ ])
27
+ ) as Record<AutonomyCapability, PermissionDecision>;
28
+ }
29
+
30
+ function autonomyPolicy(
31
+ profile: PermissionPolicy["profile"]
32
+ ): PermissionPolicy {
33
+ switch (profile) {
34
+ case "rigid":
35
+ return {
36
+ profile,
37
+ capabilities: capabilityMap("ask")
38
+ };
39
+ case "sensible-defaults":
40
+ return {
41
+ profile,
42
+ capabilities: capabilityMap("ask", {
43
+ read: "allow",
44
+ edit_write: "allow",
45
+ search_list: "allow",
46
+ bash_safe: "allow",
47
+ task_agent: "allow",
48
+ web: "ask"
49
+ })
50
+ };
51
+ case "max-autonomy":
52
+ return {
53
+ profile,
54
+ capabilities: capabilityMap("allow", {
55
+ web: "ask"
56
+ })
57
+ };
58
+ }
59
+ }
60
+
61
+ export const autonomyFacet: Facet = {
62
+ id: "autonomy",
63
+ label: "Autonomy",
64
+ description:
65
+ "How much initiative and execution freedom the agent should have",
66
+ required: false,
67
+ multiSelect: false,
68
+ options: [
69
+ {
70
+ id: "rigid",
71
+ label: "Rigid",
72
+ description:
73
+ "Keep built-in capabilities approval-gated and require confirmation before acting",
74
+ recipe: [
75
+ {
76
+ writer: "permission-policy",
77
+ config: autonomyPolicy("rigid")
78
+ }
79
+ ]
80
+ },
81
+ {
82
+ id: "sensible-defaults",
83
+ label: "Sensible defaults",
84
+ description:
85
+ "Allow a curated low-risk built-in capability set while keeping web access approval-gated",
86
+ recipe: [
87
+ {
88
+ writer: "permission-policy",
89
+ config: autonomyPolicy("sensible-defaults")
90
+ }
91
+ ]
92
+ },
93
+ {
94
+ id: "max-autonomy",
95
+ label: "Max autonomy",
96
+ description:
97
+ "Allow broad local built-in autonomy while keeping web access approval-gated",
98
+ recipe: [
99
+ {
100
+ writer: "permission-policy",
101
+ config: autonomyPolicy("max-autonomy")
102
+ }
103
+ ]
104
+ }
105
+ ]
106
+ };
@@ -3,10 +3,17 @@ import { processFacet } from "./facets/process.js";
3
3
  import { architectureFacet } from "./facets/architecture.js";
4
4
  import { practicesFacet } from "./facets/practices.js";
5
5
  import { backpressureFacet } from "./facets/backpressure.js";
6
+ import { autonomyFacet } from "./facets/autonomy.js";
6
7
 
7
8
  export function getDefaultCatalog(): Catalog {
8
9
  return {
9
- facets: [processFacet, architectureFacet, practicesFacet, backpressureFacet]
10
+ facets: [
11
+ processFacet,
12
+ architectureFacet,
13
+ practicesFacet,
14
+ backpressureFacet,
15
+ autonomyFacet
16
+ ]
10
17
  };
11
18
  }
12
19
 
package/src/index.ts CHANGED
@@ -13,7 +13,12 @@ export {
13
13
  type SkillDefinition,
14
14
  type InlineSkill,
15
15
  type ExternalSkill,
16
- type GitHook
16
+ type GitHook,
17
+ type PermissionPolicy,
18
+ type AutonomyProfile,
19
+ type AutonomyCapability,
20
+ type PermissionDecision,
21
+ type PermissionRule
17
22
  } from "./types.js";
18
23
  export { type ResolutionContext, type ResolvedFacet } from "./types.js";
19
24
  export { type UserConfig, type LockFile } from "./types.js";
@@ -47,3 +52,4 @@ export {
47
52
  } from "./catalog/index.js";
48
53
  export { skillsWriter } from "./writers/skills.js";
49
54
  export { knowledgeWriter } from "./writers/knowledge.js";
55
+ export { permissionPolicyWriter } from "./writers/permission-policy.js";
@@ -115,7 +115,7 @@ describe("registry", () => {
115
115
  });
116
116
 
117
117
  describe("createDefaultRegistry", () => {
118
- it("has all 8 built-in provision writer IDs registered", () => {
118
+ it("has all built-in provision writer IDs registered", () => {
119
119
  const registry = createDefaultRegistry();
120
120
  const expectedIds = [
121
121
  "workflows",
@@ -125,7 +125,8 @@ describe("registry", () => {
125
125
  "instruction",
126
126
  "installable",
127
127
  "git-hooks",
128
- "setup-note"
128
+ "setup-note",
129
+ "permission-policy"
129
130
  ];
130
131
  for (const id of expectedIds) {
131
132
  expect(
@@ -133,7 +134,7 @@ describe("registry", () => {
133
134
  `expected provision writer "${id}" to be registered`
134
135
  ).toBeDefined();
135
136
  }
136
- expect(registry.provisions.size).toBe(8);
137
+ expect(registry.provisions.size).toBe(expectedIds.length);
137
138
  });
138
139
 
139
140
  it("has no agent writers by default (moved to @ade/harnesses)", () => {
package/src/registry.ts CHANGED
@@ -9,6 +9,7 @@ import { skillsWriter } from "./writers/skills.js";
9
9
  import { knowledgeWriter } from "./writers/knowledge.js";
10
10
  import { gitHooksWriter } from "./writers/git-hooks.js";
11
11
  import { setupNoteWriter } from "./writers/setup-note.js";
12
+ import { permissionPolicyWriter } from "./writers/permission-policy.js";
12
13
 
13
14
  export function createRegistry(): WriterRegistry {
14
15
  return {
@@ -55,6 +56,7 @@ export function createDefaultRegistry(): WriterRegistry {
55
56
  registerProvisionWriter(registry, knowledgeWriter);
56
57
  registerProvisionWriter(registry, gitHooksWriter);
57
58
  registerProvisionWriter(registry, setupNoteWriter);
59
+ registerProvisionWriter(registry, permissionPolicyWriter);
58
60
 
59
61
  // Stub writers for types not yet implemented
60
62
  for (const id of ["mcp-server", "installable"]) {
@@ -578,4 +578,49 @@ describe("resolve", () => {
578
578
  expect(duplicates[0].env).toEqual({ CUSTOM: "true" });
579
579
  });
580
580
  });
581
+
582
+ describe("autonomy permission policy", () => {
583
+ it("adds a capability-based permission policy to LogicalConfig and keeps web access on ask", async () => {
584
+ const userConfig: UserConfig = {
585
+ choices: { autonomy: "rigid" }
586
+ };
587
+
588
+ const result = await resolve(userConfig, catalog, registry);
589
+
590
+ expect(result).toHaveProperty("permission_policy");
591
+ expect((result as Record<string, unknown>).permission_policy).toEqual({
592
+ profile: "rigid",
593
+ capabilities: {
594
+ read: "ask",
595
+ edit_write: "ask",
596
+ search_list: "ask",
597
+ bash_safe: "ask",
598
+ bash_unsafe: "ask",
599
+ web: "ask",
600
+ task_agent: "ask"
601
+ }
602
+ });
603
+ });
604
+
605
+ it("uses curated built-in defaults for the sensible-defaults autonomy profile", async () => {
606
+ const userConfig: UserConfig = {
607
+ choices: { autonomy: "sensible-defaults" }
608
+ };
609
+
610
+ const result = await resolve(userConfig, catalog, registry);
611
+
612
+ expect(result.permission_policy).toEqual({
613
+ profile: "sensible-defaults",
614
+ capabilities: {
615
+ read: "allow",
616
+ edit_write: "allow",
617
+ search_list: "allow",
618
+ bash_safe: "allow",
619
+ bash_unsafe: "ask",
620
+ web: "ask",
621
+ task_agent: "allow"
622
+ }
623
+ });
624
+ });
625
+ });
581
626
  });