@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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-format.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +12 -12
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/catalog/facets/autonomy.d.ts +2 -0
- package/dist/catalog/facets/autonomy.js +85 -0
- package/dist/catalog/index.js +8 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/registry.js +2 -0
- package/dist/resolver.js +49 -22
- package/dist/types.d.ts +15 -2
- package/dist/writers/permission-policy.d.ts +2 -0
- package/dist/writers/permission-policy.js +6 -0
- package/package.json +1 -1
- package/src/catalog/catalog.spec.ts +39 -0
- package/src/catalog/facets/autonomy.ts +106 -0
- package/src/catalog/index.ts +8 -1
- package/src/index.ts +7 -1
- package/src/registry.spec.ts +4 -3
- package/src/registry.ts +2 -0
- package/src/resolver.spec.ts +45 -0
- package/src/resolver.ts +67 -23
- package/src/types.ts +30 -2
- package/src/writers/permission-policy.ts +8 -0
- package/tsconfig.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-format.log
CHANGED
package/.turbo/turbo-lint.log
CHANGED
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
|
|
2
|
-
> @codemcp/ade-core@0.0
|
|
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
|
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/home/runner/work/ade/ade/packages/core[39m
|
|
7
7
|
|
|
8
|
-
[32m✓[39m src/registry.spec.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
9
|
-
[32m✓[39m src/resolver.spec.ts [2m([22m[
|
|
10
|
-
[32m✓[39m src/catalog/catalog.spec.ts [2m([22m[
|
|
11
|
-
[32m✓[39m src/writers/
|
|
12
|
-
[32m✓[39m src/writers/
|
|
13
|
-
[32m✓[39m src/config.spec.ts [2m([22m[2m7 tests[22m[2m)[22m[32m
|
|
14
|
-
[32m✓[39m src/writers/instruction.spec.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
15
|
-
[32m✓[39m src/writers/knowledge.spec.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
8
|
+
[32m✓[39m src/registry.spec.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 36[2mms[22m[39m
|
|
9
|
+
[32m✓[39m src/resolver.spec.ts [2m([22m[2m21 tests[22m[2m)[22m[32m 67[2mms[22m[39m
|
|
10
|
+
[32m✓[39m src/catalog/catalog.spec.ts [2m([22m[2m43 tests[22m[2m)[22m[32m 113[2mms[22m[39m
|
|
11
|
+
[32m✓[39m src/writers/workflows.spec.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 25[2mms[22m[39m
|
|
12
|
+
[32m✓[39m src/writers/skills.spec.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 32[2mms[22m[39m
|
|
13
|
+
[32m✓[39m src/config.spec.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 117[2mms[22m[39m
|
|
14
|
+
[32m✓[39m src/writers/instruction.spec.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 12[2mms[22m[39m
|
|
15
|
+
[32m✓[39m src/writers/knowledge.spec.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 19[2mms[22m[39m
|
|
16
16
|
|
|
17
17
|
[2m Test Files [22m [1m[32m8 passed[39m[22m[90m (8)[39m
|
|
18
|
-
[2m Tests [22m [1m[
|
|
19
|
-
[2m Start at [22m
|
|
20
|
-
[2m Duration [22m 3.
|
|
18
|
+
[2m Tests [22m [1m[32m98 passed[39m[22m[90m (98)[39m
|
|
19
|
+
[2m Start at [22m 12:11:20
|
|
20
|
+
[2m Duration [22m 3.08s[2m (transform 930ms, setup 0ms, collect 1.94s, tests 420ms, environment 8ms, prepare 2.09s)[22m
|
|
21
21
|
|
|
@@ -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
|
+
};
|
package/dist/catalog/index.js
CHANGED
|
@@ -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: [
|
|
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
|
-
|
|
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
|
|
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;
|
package/package.json
CHANGED
|
@@ -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
|
+
};
|
package/src/catalog/index.ts
CHANGED
|
@@ -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: [
|
|
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";
|
package/src/registry.spec.ts
CHANGED
|
@@ -115,7 +115,7 @@ describe("registry", () => {
|
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
describe("createDefaultRegistry", () => {
|
|
118
|
-
it("has all
|
|
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(
|
|
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"]) {
|
package/src/resolver.spec.ts
CHANGED
|
@@ -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
|
});
|