@codemcp/ade 0.3.0 → 0.5.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/.beads/issues.jsonl +4 -0
- package/.beads/last-touched +1 -1
- package/.vibe/beads-state-ade-better-in-cli-explanation-b8jcv3.json +24 -0
- package/.vibe/development-plan-better-in-cli-explanation.md +64 -0
- package/package.json +1 -1
- package/packages/cli/dist/index.js +144 -36
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/commands/conventions.integration.spec.ts +1 -0
- package/packages/cli/src/commands/install.integration.spec.ts +1 -0
- package/packages/cli/src/commands/knowledge.integration.spec.ts +1 -0
- package/packages/cli/src/commands/setup.integration.spec.ts +2 -0
- package/packages/cli/src/commands/setup.spec.ts +2 -1
- package/packages/cli/src/commands/setup.ts +43 -6
- package/packages/cli/src/index.ts +8 -1
- package/packages/core/package.json +1 -1
- package/packages/core/src/catalog/catalog.spec.ts +1 -1
- package/packages/core/src/catalog/facets/autonomy.ts +9 -9
- package/packages/core/src/catalog/facets/backpressure.ts +1 -1
- package/packages/core/src/catalog/facets/practices.ts +1 -2
- package/packages/core/src/catalog/facets/process.ts +11 -2
- package/packages/core/src/registry.ts +3 -2
- package/packages/core/src/resolver.spec.ts +29 -0
- package/packages/core/src/writers/mcp-server.spec.ts +62 -0
- package/packages/core/src/writers/mcp-server.ts +25 -0
- package/packages/core/src/writers/workflows.spec.ts +22 -0
- package/packages/core/src/writers/workflows.ts +5 -2
- package/packages/harnesses/package.json +1 -1
- package/packages/harnesses/src/writers/copilot.spec.ts +2 -6
- package/packages/harnesses/src/writers/copilot.ts +2 -9
- package/packages/harnesses/src/writers/kiro.spec.ts +32 -0
- package/packages/harnesses/src/writers/kiro.ts +22 -5
- package/packages/harnesses/src/writers/opencode.spec.ts +66 -0
- package/packages/harnesses/src/writers/opencode.ts +30 -3
package/.beads/issues.jsonl
CHANGED
|
@@ -56,3 +56,7 @@
|
|
|
56
56
|
{"id":"ade-4.3","title":"Fix","description":"Implement the solution based on your analysis: - If exists: Follow the design from it - Otherwise: Elaborate design options and present them to the user Before implementing, assess the approach: - How critical is this system? What is the blast radius if the fix causes issues? - Should this be a minimal fix or a more comprehensive solution? Make targeted changes that address the root cause without introducing new issues. Be careful to maintain existing functionality while fixing the bug.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T10:33:07.076548+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T10:33:07.076548+01:00","dependencies":[{"issue_id":"ade-4.3","depends_on_id":"ade-4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"ade-4.3","depends_on_id":"ade-4.2","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
57
57
|
{"id":"ade-4.4","title":"Verify","description":"Test the fix thoroughly to ensure the original bug is resolved and no new issues were introduced. Run existing tests, create new ones if needed, and verify the solution is robust.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T10:33:07.222264+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T10:33:07.222264+01:00","dependencies":[{"issue_id":"ade-4.4","depends_on_id":"ade-4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"ade-4.4","depends_on_id":"ade-4.3","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
58
58
|
{"id":"ade-4.5","title":"Finalize","description":"Ensure code quality and documentation accuracy through systematic cleanup and review. **STEP 1: Code Cleanup** Systematically clean up development artifacts: - Remove all temporary debug output statements used during bug investigation (console logging, print statements, debug output functions) - Address each TODO/FIXME comment by either implementing the solution or documenting why it's deferred - Remove completed TODOs and convert remaining ones to proper issue tracking if needed - Remove temporary debugging code, test code blocks, and commented-out code - Ensure proper error handling replaces temporary debug logging **STEP 2: Documentation Review** Review and update documentation to reflect the bug fix: - If exists, update it if design details were refined or changed during the fix - Compare documentation against the actual bug fix implementation - Update only the documentation sections that have functional changes - Remove references to investigation iterations, progress notes, and temporary decisions - Ensure documentation describes the final fixed state, not the debugging process - Ask the user to review document updates **STEP 3: Final Validation** - Run existing tests to ensure cleanup didn't break functionality - Verify documentation accuracy with a final review - Ensure bug fix is ready for production - Update task progress and mark completed work as you finalize the bug fix","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T10:33:07.356694+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T10:33:07.356694+01:00","dependencies":[{"issue_id":"ade-4.5","depends_on_id":"ade-4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"ade-4.5","depends_on_id":"ade-4.4","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
59
|
+
{"id":"ade-5","title":"ade: minor (development-plan-better-in-cli-explanation.md)","description":"Responsible vibe engineering session using minor workflow for ade","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T11:33:13.313751+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T11:33:13.313751+01:00"}
|
|
60
|
+
{"id":"ade-5.1","title":"Explore","description":"Understand the problem, analyze existing patterns, and design your approach. Consider the scope and impact of the change. **STEP 1: Analyze Requirements** - If exists: Use it to understand the required changes - Otherwise: Document requirements in your task management system **STEP 2: Review Design Approach** - If exists: Respect the design approach documented in - Otherwise: Design your approach based on the problem analysis **STEP 3: Document Decisions** - Document your analysis and design decisions - Create tasks to guide implementation - Focus on analysis and design only - do not write any code yet","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T11:33:13.459272+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T11:33:13.459272+01:00","dependencies":[{"issue_id":"ade-5.1","depends_on_id":"ade-5","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
61
|
+
{"id":"ade-5.2","title":"Implement","description":"Write clean, focused code for the minor enhancement, test your changes, and prepare for commit. **STEP 1: Review Design and Requirements** - If exists: Follow your design from - Otherwise: Elaborate design options and present them to the user - If exists: Ensure the relevant requirements from are met - Otherwise: Ensure existing requirements are met based on your task context **STEP 2: Implement Changes** - Write clean, focused code for the minor enhancement - Test your changes to ensure they work correctly and don't break existing functionality **STEP 3: Prepare for Finalization** - Update task progress as needed - Prepare documentation and commit when ready","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T11:33:13.597501+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T11:33:13.597501+01:00","dependencies":[{"issue_id":"ade-5.2","depends_on_id":"ade-5","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"ade-5.2","depends_on_id":"ade-5.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
62
|
+
{"id":"ade-5.3","title":"Finalize","description":"Ensure code quality and documentation accuracy through systematic cleanup and review. **STEP 1: Code Cleanup** Systematically clean up development artifacts: - **Remove Debug Output**: Search for and remove all temporary debug output statements used during development. Look for language-specific debug output methods (console logging, print statements, debug output functions). Remove any debugging statements that were added for development purposes. - **Review TODO/FIXME Comments**: - Address each TODO/FIXME comment by either implementing the solution or documenting why it's deferred - Remove completed TODOs - Convert remaining TODOs to proper issue tracking if needed - **Remove Debugging Code Blocks**: - Remove temporary debugging code, test code blocks, and commented-out code - Clean up any experimental code that's no longer needed - Ensure proper error handling replaces temporary debug logging **STEP 2: Documentation Review** Review and update documentation to reflect final implementation: - **Update Long-Term Memory Documents**: Based on what was actually implemented: - If exists: Update if requirements changed during development - If exists: Update if design details were refined or changed - **Compare Against Implementation**: Review documentation against actual implemented functionality - **Update Changed Sections**: Only modify documentation sections that have functional changes - **Remove Development Progress**: Remove references to development iterations, progress notes, and temporary decisions - **Focus on Final State**: Ensure documentation describes the final implemented state, not the development process - **Ask User to Review Document Updates** **STEP 3: Final Validation** - Run existing tests to ensure cleanup didn't break functionality - Verify documentation accuracy with a final review - Ensure minor enhancement is ready for delivery - Update task progress and mark completed work as you finalize the minor enhancement","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T11:33:13.733496+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T11:33:13.733496+01:00","dependencies":[{"issue_id":"ade-5.3","depends_on_id":"ade-5","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"ade-5.3","depends_on_id":"ade-5.2","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
package/.beads/last-touched
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ade-
|
|
1
|
+
ade-5.3
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"conversationId": "ade-better-in-cli-explanation-b8jcv3",
|
|
3
|
+
"projectPath": "/Users/oliverjaegle/projects/privat/codemcp/ade",
|
|
4
|
+
"epicId": "ade-5",
|
|
5
|
+
"phaseTasks": [
|
|
6
|
+
{
|
|
7
|
+
"phaseId": "explore",
|
|
8
|
+
"phaseName": "Explore",
|
|
9
|
+
"taskId": "ade-5.1"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"phaseId": "implement",
|
|
13
|
+
"phaseName": "Implement",
|
|
14
|
+
"taskId": "ade-5.2"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"phaseId": "finalize",
|
|
18
|
+
"phaseName": "Finalize",
|
|
19
|
+
"taskId": "ade-5.3"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"createdAt": "2026-03-19T10:33:13.963Z",
|
|
23
|
+
"updatedAt": "2026-03-19T10:33:13.963Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Development Plan: ade (better-in-cli-explanation branch)
|
|
2
|
+
|
|
3
|
+
*Generated on 2026-03-19 by Vibe Feature MCP*
|
|
4
|
+
*Workflow: [minor](https://mrsimpson.github.io/responsible-vibe-mcp/workflows/minor)*
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
Improve user guidance in the ADE CLI — add contextual introductory text to help users understand what ADE is and what each setup facet (Process, Architecture, Practices, Autonomy, Backpressure, Harnesses, Docsets) means before they make a selection.
|
|
8
|
+
|
|
9
|
+
## Explore
|
|
10
|
+
<!-- beads-phase-id: ade-5.1 -->
|
|
11
|
+
### Tasks
|
|
12
|
+
- [x] Read and understand current CLI structure (index.ts, setup.ts, install.ts)
|
|
13
|
+
- [x] Read all facet definitions (process, architecture, practices, autonomy, backpressure)
|
|
14
|
+
- [x] Identify all prompts where contextual guidance is missing
|
|
15
|
+
|
|
16
|
+
### Findings
|
|
17
|
+
- The setup command starts with `clack.intro("ade setup")` — no explanation of what ADE is or what the wizard will do
|
|
18
|
+
- Facet prompts show only `facet.label` as the message — no educational context about what the facet means
|
|
19
|
+
- The harness prompt says "which coding agents should receive config?" — helpful but no context on what harnesses are
|
|
20
|
+
- The docset prompt says "deselect any you don't need" — no explanation of what docsets are used for
|
|
21
|
+
- The skill install confirm says only "Install N skill(s) now?" — no context on what skills are
|
|
22
|
+
- `index.ts` help text is minimal — no explanation of ADE's purpose
|
|
23
|
+
|
|
24
|
+
### Phase Entrance Criteria for Implement:
|
|
25
|
+
- [x] All touchpoints where user guidance is missing have been identified
|
|
26
|
+
- [x] Content approach is clear (what to say at each step)
|
|
27
|
+
- [x] No ambiguity about scope (we're adding text/hints, not redesigning prompts)
|
|
28
|
+
|
|
29
|
+
## Implement
|
|
30
|
+
<!-- beads-phase-id: ade-5.2 -->
|
|
31
|
+
|
|
32
|
+
### Phase Entrance Criteria:
|
|
33
|
+
- [x] Explore phase findings are complete
|
|
34
|
+
- [x] All identified touchpoints for improvement are listed
|
|
35
|
+
- [x] Scope is clearly defined: text additions only, no structural changes
|
|
36
|
+
|
|
37
|
+
### Tasks
|
|
38
|
+
*Tasks managed via `bd` CLI*
|
|
39
|
+
|
|
40
|
+
## Finalize
|
|
41
|
+
<!-- beads-phase-id: ade-5.3 -->
|
|
42
|
+
|
|
43
|
+
### Phase Entrance Criteria:
|
|
44
|
+
- [x] All identified touchpoints have been addressed with guidance text
|
|
45
|
+
- [x] Code compiles without errors
|
|
46
|
+
- [x] Changes look correct and consistent in tone
|
|
47
|
+
|
|
48
|
+
### Tasks
|
|
49
|
+
- [ ] Squash WIP commits: `git reset --soft <first commit of this branch>`. Then, create a conventional commit. In the message, first summarize the intentions and key decisions from the development plan. Then, add a brief summary of the key changes and their side effects and dependencies
|
|
50
|
+
|
|
51
|
+
*Tasks managed via `bd` CLI*
|
|
52
|
+
|
|
53
|
+
## Key Decisions
|
|
54
|
+
- Used `clack.note()` for the ADE intro (boxed, titled) to make it stand out as orientation content vs. action prompts
|
|
55
|
+
- Used `clack.log.info()` before each facet/section prompt — lightweight, doesn't interrupt the flow, provides context without being intrusive
|
|
56
|
+
- Facet descriptions from the catalog data model are reused directly (they were already well-written) rather than duplicated elsewhere
|
|
57
|
+
- Harness guidance explicitly calls out the 'universal' option since it's the most common default and its purpose isn't obvious from the name alone
|
|
58
|
+
- Skill install context placed immediately before the confirm prompt so it reads naturally in sequence
|
|
59
|
+
|
|
60
|
+
## Notes
|
|
61
|
+
*Additional context and observations*
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
*This plan is maintained by the LLM and uses beads CLI for task management. Tool responses provide guidance on which bd commands to use for task management.*
|
package/package.json
CHANGED
|
@@ -11697,6 +11697,25 @@ ${t("cyan", x2)}
|
|
|
11697
11697
|
}
|
|
11698
11698
|
} }).prompt();
|
|
11699
11699
|
};
|
|
11700
|
+
var jt = (e) => t("dim", e);
|
|
11701
|
+
var kt2 = (e, r2, s) => {
|
|
11702
|
+
const i = { hard: true, trim: false }, a = J2(e, r2, i).split(`
|
|
11703
|
+
`), o2 = a.reduce((n, c) => Math.max(D2(c), n), 0), u2 = a.map(s).reduce((n, c) => Math.max(D2(c), n), 0), l = r2 - (u2 - o2);
|
|
11704
|
+
return J2(e, l, i);
|
|
11705
|
+
};
|
|
11706
|
+
var Vt2 = (e = "", r2 = "", s) => {
|
|
11707
|
+
const i = s?.output ?? N2.stdout, a = s?.withGuide ?? _.withGuide, o2 = s?.format ?? jt, u2 = ["", ...kt2(e, rt(i) - 6, o2).split(`
|
|
11708
|
+
`).map(o2), ""], l = D2(r2), n = Math.max(u2.reduce((g2, E) => {
|
|
11709
|
+
const $2 = D2(E);
|
|
11710
|
+
return $2 > g2 ? $2 : g2;
|
|
11711
|
+
}, 0), l) + 2, c = u2.map((g2) => `${t("gray", h)} ${g2}${" ".repeat(n - D2(g2))}${t("gray", h)}`).join(`
|
|
11712
|
+
`), p2 = a ? `${t("gray", h)}
|
|
11713
|
+
` : "", f = a ? We : ge;
|
|
11714
|
+
i.write(`${p2}${t("green", V)} ${t("reset", r2)} ${t("gray", se.repeat(Math.max(n - l - 1, 1)) + pe)}
|
|
11715
|
+
${c}
|
|
11716
|
+
${t("gray", f + se.repeat(n + 2) + me)}
|
|
11717
|
+
`);
|
|
11718
|
+
};
|
|
11700
11719
|
var ze = { light: I2("\u2500", "-"), heavy: I2("\u2501", "="), block: I2("\u2588", "#") };
|
|
11701
11720
|
var oe = (e, r2) => e.includes(`
|
|
11702
11721
|
`) ? e.split(`
|
|
@@ -11786,14 +11805,34 @@ var instructionWriter = {
|
|
|
11786
11805
|
var workflowsWriter = {
|
|
11787
11806
|
id: "workflows",
|
|
11788
11807
|
async write(config) {
|
|
11789
|
-
const { package: pkg, ref, env: env2 } = config;
|
|
11808
|
+
const { package: pkg, ref, env: env2, allowedTools } = config;
|
|
11790
11809
|
return {
|
|
11791
11810
|
mcp_servers: [
|
|
11792
11811
|
{
|
|
11793
11812
|
ref: ref ?? pkg,
|
|
11794
11813
|
command: "npx",
|
|
11795
11814
|
args: [pkg],
|
|
11796
|
-
env: env2 ?? {}
|
|
11815
|
+
env: env2 ?? {},
|
|
11816
|
+
...allowedTools !== void 0 ? { allowedTools } : {}
|
|
11817
|
+
}
|
|
11818
|
+
]
|
|
11819
|
+
};
|
|
11820
|
+
}
|
|
11821
|
+
};
|
|
11822
|
+
|
|
11823
|
+
// ../core/dist/writers/mcp-server.js
|
|
11824
|
+
var mcpServerWriter = {
|
|
11825
|
+
id: "mcp-server",
|
|
11826
|
+
async write(config) {
|
|
11827
|
+
const { ref, command: command2, args: args2, env: env2, allowedTools } = config;
|
|
11828
|
+
return {
|
|
11829
|
+
mcp_servers: [
|
|
11830
|
+
{
|
|
11831
|
+
ref,
|
|
11832
|
+
command: command2,
|
|
11833
|
+
args: args2,
|
|
11834
|
+
env: env2 ?? {},
|
|
11835
|
+
...allowedTools !== void 0 ? { allowedTools } : {}
|
|
11797
11836
|
}
|
|
11798
11837
|
]
|
|
11799
11838
|
};
|
|
@@ -11863,12 +11902,13 @@ function createDefaultRegistry() {
|
|
|
11863
11902
|
const registry2 = createRegistry();
|
|
11864
11903
|
registerProvisionWriter(registry2, instructionWriter);
|
|
11865
11904
|
registerProvisionWriter(registry2, workflowsWriter);
|
|
11905
|
+
registerProvisionWriter(registry2, mcpServerWriter);
|
|
11866
11906
|
registerProvisionWriter(registry2, skillsWriter);
|
|
11867
11907
|
registerProvisionWriter(registry2, knowledgeWriter);
|
|
11868
11908
|
registerProvisionWriter(registry2, gitHooksWriter);
|
|
11869
11909
|
registerProvisionWriter(registry2, setupNoteWriter);
|
|
11870
11910
|
registerProvisionWriter(registry2, permissionPolicyWriter);
|
|
11871
|
-
for (const id of ["
|
|
11911
|
+
for (const id of ["installable"]) {
|
|
11872
11912
|
registerProvisionWriter(registry2, {
|
|
11873
11913
|
id,
|
|
11874
11914
|
write: async () => ({})
|
|
@@ -11881,7 +11921,7 @@ function createDefaultRegistry() {
|
|
|
11881
11921
|
var processFacet = {
|
|
11882
11922
|
id: "process",
|
|
11883
11923
|
label: "Process",
|
|
11884
|
-
description: "How
|
|
11924
|
+
description: "How will you guide your agent",
|
|
11885
11925
|
required: true,
|
|
11886
11926
|
options: [
|
|
11887
11927
|
{
|
|
@@ -11893,7 +11933,16 @@ var processFacet = {
|
|
|
11893
11933
|
writer: "workflows",
|
|
11894
11934
|
config: {
|
|
11895
11935
|
package: "@codemcp/workflows-server@latest",
|
|
11896
|
-
ref: "workflows"
|
|
11936
|
+
ref: "workflows",
|
|
11937
|
+
env: {
|
|
11938
|
+
VIBE_WORKFLOW_DOMAINS: "skilled"
|
|
11939
|
+
},
|
|
11940
|
+
allowedTools: [
|
|
11941
|
+
"whats_next",
|
|
11942
|
+
"conduct_review",
|
|
11943
|
+
"list_workflows",
|
|
11944
|
+
"get_tool_info"
|
|
11945
|
+
]
|
|
11897
11946
|
}
|
|
11898
11947
|
},
|
|
11899
11948
|
{
|
|
@@ -12356,7 +12405,7 @@ var architectureFacet = {
|
|
|
12356
12405
|
var practicesFacet = {
|
|
12357
12406
|
id: "practices",
|
|
12358
12407
|
label: "Practices",
|
|
12359
|
-
description: "
|
|
12408
|
+
description: "Development practices \u2014 mix and match regardless of stack",
|
|
12360
12409
|
required: false,
|
|
12361
12410
|
multiSelect: true,
|
|
12362
12411
|
options: [
|
|
@@ -12588,7 +12637,7 @@ var JAVA_UNIT_TEST_RECIPE = [
|
|
|
12588
12637
|
var backpressureFacet = {
|
|
12589
12638
|
id: "backpressure",
|
|
12590
12639
|
label: "Backpressure",
|
|
12591
|
-
description: "
|
|
12640
|
+
description: "Which automated quality gates to put in place. Translates to git hooks",
|
|
12592
12641
|
required: false,
|
|
12593
12642
|
multiSelect: true,
|
|
12594
12643
|
dependsOn: ["architecture"],
|
|
@@ -12647,31 +12696,31 @@ var autonomyFacet = {
|
|
|
12647
12696
|
multiSelect: false,
|
|
12648
12697
|
options: [
|
|
12649
12698
|
{
|
|
12650
|
-
id: "
|
|
12651
|
-
label: "
|
|
12652
|
-
description: "
|
|
12699
|
+
id: "sensible-defaults",
|
|
12700
|
+
label: "Sensible defaults",
|
|
12701
|
+
description: "Allow a curated built-in capabilities set while keeping potentially risky actions approval-gated",
|
|
12653
12702
|
recipe: [
|
|
12654
12703
|
{
|
|
12655
12704
|
writer: "permission-policy",
|
|
12656
|
-
config: { profile: "
|
|
12705
|
+
config: { profile: "sensible-defaults" }
|
|
12657
12706
|
}
|
|
12658
12707
|
]
|
|
12659
12708
|
},
|
|
12660
12709
|
{
|
|
12661
|
-
id: "
|
|
12662
|
-
label: "
|
|
12663
|
-
description: "
|
|
12710
|
+
id: "rigid",
|
|
12711
|
+
label: "Rigid",
|
|
12712
|
+
description: "Keep built-in capabilities approval-gated and require confirmation before acting",
|
|
12664
12713
|
recipe: [
|
|
12665
12714
|
{
|
|
12666
12715
|
writer: "permission-policy",
|
|
12667
|
-
config: { profile: "
|
|
12716
|
+
config: { profile: "rigid" }
|
|
12668
12717
|
}
|
|
12669
12718
|
]
|
|
12670
12719
|
},
|
|
12671
12720
|
{
|
|
12672
12721
|
id: "max-autonomy",
|
|
12673
12722
|
label: "Max autonomy",
|
|
12674
|
-
description: "Allow broad local built-in autonomy
|
|
12723
|
+
description: "Allow broad local built-in autonomy. Recommended for sandboxed environments only",
|
|
12675
12724
|
recipe: [
|
|
12676
12725
|
{
|
|
12677
12726
|
writer: "permission-policy",
|
|
@@ -22276,13 +22325,7 @@ function getBuiltInTools(profile) {
|
|
|
22276
22325
|
}
|
|
22277
22326
|
}
|
|
22278
22327
|
function getForwardedMcpTools(servers) {
|
|
22279
|
-
return servers.
|
|
22280
|
-
const allowedTools = server.allowedTools ?? ["*"];
|
|
22281
|
-
if (allowedTools.includes("*")) {
|
|
22282
|
-
return [`${server.ref}/*`];
|
|
22283
|
-
}
|
|
22284
|
-
return allowedTools.map((tool) => `${server.ref}/${tool}`);
|
|
22285
|
-
});
|
|
22328
|
+
return servers.map((server) => `${server.ref}/*`);
|
|
22286
22329
|
}
|
|
22287
22330
|
function renderCopilotAgentMcpServers(servers) {
|
|
22288
22331
|
if (servers.length === 0) {
|
|
@@ -22294,7 +22337,7 @@ function renderCopilotAgentMcpServers(servers) {
|
|
|
22294
22337
|
lines.push(" type: stdio");
|
|
22295
22338
|
lines.push(` command: ${JSON.stringify(server.command)}`);
|
|
22296
22339
|
lines.push(` args: ${JSON.stringify(server.args)}`);
|
|
22297
|
-
lines.push(` tools: ${JSON.stringify(
|
|
22340
|
+
lines.push(` tools: ${JSON.stringify(["*"])}`);
|
|
22298
22341
|
if (Object.keys(server.env).length > 0) {
|
|
22299
22342
|
lines.push(" env:");
|
|
22300
22343
|
for (const [key, value] of Object.entries(server.env)) {
|
|
@@ -22441,20 +22484,21 @@ var kiroWriter = {
|
|
|
22441
22484
|
})
|
|
22442
22485
|
});
|
|
22443
22486
|
const tools = getKiroTools(getAutonomyProfile(config), config.mcp_servers);
|
|
22487
|
+
const allowedTools = getKiroAllowedTools(getAutonomyProfile(config), config.mcp_servers);
|
|
22444
22488
|
await writeJson(join17(projectRoot, ".kiro", "agents", "ade.json"), {
|
|
22445
22489
|
name: "ade",
|
|
22446
22490
|
description: "ADE \u2014 Agentic Development Environment agent with project conventions and tools.",
|
|
22447
22491
|
prompt: config.instructions.join("\n\n") || "ADE \u2014 Agentic Development Environment agent.",
|
|
22448
22492
|
mcpServers: getKiroAgentMcpServers(config.mcp_servers),
|
|
22449
22493
|
tools,
|
|
22450
|
-
allowedTools
|
|
22494
|
+
allowedTools,
|
|
22451
22495
|
useLegacyMcpJson: true
|
|
22452
22496
|
});
|
|
22453
22497
|
await writeGitHooks(config.git_hooks, projectRoot);
|
|
22454
22498
|
}
|
|
22455
22499
|
};
|
|
22456
22500
|
function getKiroTools(profile, servers) {
|
|
22457
|
-
const mcpTools =
|
|
22501
|
+
const mcpTools = servers.map((server) => `@${server.ref}/*`);
|
|
22458
22502
|
switch (profile) {
|
|
22459
22503
|
case "rigid":
|
|
22460
22504
|
return ["read", "shell", "spec", ...mcpTools];
|
|
@@ -22466,14 +22510,24 @@ function getKiroTools(profile, servers) {
|
|
|
22466
22510
|
return ["read", "write", "shell", "spec", ...mcpTools];
|
|
22467
22511
|
}
|
|
22468
22512
|
}
|
|
22469
|
-
function
|
|
22470
|
-
|
|
22513
|
+
function getKiroAllowedTools(profile, servers) {
|
|
22514
|
+
const mcpAllowedTools = servers.flatMap((server) => {
|
|
22471
22515
|
const allowedTools = server.allowedTools ?? ["*"];
|
|
22472
22516
|
if (allowedTools.includes("*")) {
|
|
22473
22517
|
return [`@${server.ref}/*`];
|
|
22474
22518
|
}
|
|
22475
22519
|
return allowedTools.map((tool) => `@${server.ref}/${tool}`);
|
|
22476
22520
|
});
|
|
22521
|
+
switch (profile) {
|
|
22522
|
+
case "rigid":
|
|
22523
|
+
return ["read", "shell", "spec", ...mcpAllowedTools];
|
|
22524
|
+
case "sensible-defaults":
|
|
22525
|
+
return ["read", "write", "shell", "spec", ...mcpAllowedTools];
|
|
22526
|
+
case "max-autonomy":
|
|
22527
|
+
return ["read", "write", "shell(*)", "spec", ...mcpAllowedTools];
|
|
22528
|
+
default:
|
|
22529
|
+
return ["read", "write", "shell", "spec", ...mcpAllowedTools];
|
|
22530
|
+
}
|
|
22477
22531
|
}
|
|
22478
22532
|
function getKiroAgentMcpServers(servers) {
|
|
22479
22533
|
return Object.fromEntries(servers.map((server) => [
|
|
@@ -22611,6 +22665,19 @@ var MAX_AUTONOMY_RULES = {
|
|
|
22611
22665
|
codesearch: "ask",
|
|
22612
22666
|
doom_loop: "deny"
|
|
22613
22667
|
};
|
|
22668
|
+
function getMcpPermissions(servers) {
|
|
22669
|
+
const entries = servers.flatMap((server) => {
|
|
22670
|
+
const allowedTools = server.allowedTools ?? ["*"];
|
|
22671
|
+
if (allowedTools.includes("*")) {
|
|
22672
|
+
return [[`${server.ref}*`, "allow"]];
|
|
22673
|
+
}
|
|
22674
|
+
return [
|
|
22675
|
+
[`${server.ref}*`, "ask"],
|
|
22676
|
+
...allowedTools.map((tool) => [`${server.ref}_${tool}`, "allow"])
|
|
22677
|
+
];
|
|
22678
|
+
});
|
|
22679
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
22680
|
+
}
|
|
22614
22681
|
function getPermissionRules(profile) {
|
|
22615
22682
|
switch (profile) {
|
|
22616
22683
|
case "rigid":
|
|
@@ -22639,9 +22706,11 @@ var opencodeWriter = {
|
|
|
22639
22706
|
defaults: { $schema: "https://opencode.ai/config.json" }
|
|
22640
22707
|
});
|
|
22641
22708
|
const permission = getPermissionRules(getAutonomyProfile(config));
|
|
22709
|
+
const mcpPermissions = getMcpPermissions(config.mcp_servers);
|
|
22710
|
+
const mergedPermission = permission || mcpPermissions ? { ...mcpPermissions ?? {}, ...permission ?? {} } : void 0;
|
|
22642
22711
|
await writeAgentMd(config, {
|
|
22643
22712
|
path: join18(projectRoot, ".opencode", "agents", "ade.md"),
|
|
22644
|
-
extraFrontmatter:
|
|
22713
|
+
extraFrontmatter: mergedPermission ? renderYamlMapping("permission", mergedPermission) : void 0,
|
|
22645
22714
|
fallbackBody: "ADE \u2014 Agentic Development Environment agent with project conventions and tools."
|
|
22646
22715
|
});
|
|
22647
22716
|
await writeGitHooks(config.git_hooks, projectRoot);
|
|
@@ -22681,7 +22750,35 @@ function getHarnessIds() {
|
|
|
22681
22750
|
|
|
22682
22751
|
// src/commands/setup.ts
|
|
22683
22752
|
async function runSetup(projectRoot, catalog) {
|
|
22684
|
-
|
|
22753
|
+
let lineIndex = 0;
|
|
22754
|
+
const LOGO_LINES = [
|
|
22755
|
+
"\n",
|
|
22756
|
+
" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
22757
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
22758
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
22759
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D ",
|
|
22760
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 ",
|
|
22761
|
+
"\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D ",
|
|
22762
|
+
"\n"
|
|
22763
|
+
];
|
|
22764
|
+
for (const line of LOGO_LINES) {
|
|
22765
|
+
lineIndex++;
|
|
22766
|
+
if (lineIndex === 1) {
|
|
22767
|
+
Wt2(line);
|
|
22768
|
+
} else {
|
|
22769
|
+
console.log(`\u2502 ${line}`);
|
|
22770
|
+
}
|
|
22771
|
+
}
|
|
22772
|
+
Vt2(
|
|
22773
|
+
[
|
|
22774
|
+
"You're about to define how your team works with coding agents.",
|
|
22775
|
+
"",
|
|
22776
|
+
"Pick your facets \u2014 architecture, practices, process \u2014 and ADE",
|
|
22777
|
+
"translates them into a shared information hierarchy your agents",
|
|
22778
|
+
"read from the repo. One setup, consistent across the whole team."
|
|
22779
|
+
].join("\n"),
|
|
22780
|
+
"ADE \u2014 Agentic Development Environment"
|
|
22781
|
+
);
|
|
22685
22782
|
const existingConfig = await readUserConfig(projectRoot);
|
|
22686
22783
|
const existingChoices = existingConfig?.choices ?? {};
|
|
22687
22784
|
for (const [facetId, value] of Object.entries(existingChoices)) {
|
|
@@ -22726,7 +22823,7 @@ async function runSetup(projectRoot, catalog) {
|
|
|
22726
22823
|
let excludedDocsets;
|
|
22727
22824
|
if (impliedDocsets.length > 0) {
|
|
22728
22825
|
const selected = await Lt2({
|
|
22729
|
-
message: "Documentation \u2014
|
|
22826
|
+
message: "Documentation sources \u2014 Those will be pulled to your local disk for browsing on demand",
|
|
22730
22827
|
options: impliedDocsets.map((d3) => ({
|
|
22731
22828
|
value: d3.id,
|
|
22732
22829
|
label: d3.label,
|
|
@@ -22755,7 +22852,7 @@ async function runSetup(projectRoot, catalog) {
|
|
|
22755
22852
|
(h3) => allHarnessWriters.some((w2) => w2.id === h3)
|
|
22756
22853
|
);
|
|
22757
22854
|
const selectedHarnesses = await Lt2({
|
|
22758
|
-
message: "
|
|
22855
|
+
message: "Which coding agents should receive config?\nADE generates config files for each agent you select.\n",
|
|
22759
22856
|
options: harnessOptions,
|
|
22760
22857
|
initialValues: validExistingHarnesses && validExistingHarnesses.length > 0 ? validExistingHarnesses : ["universal"],
|
|
22761
22858
|
required: false
|
|
@@ -22797,8 +22894,12 @@ To use the latest defaults, remove .ade/skills/ and re-run setup.`
|
|
|
22797
22894
|
);
|
|
22798
22895
|
}
|
|
22799
22896
|
if (logicalConfig.skills.length > 0) {
|
|
22897
|
+
const skillNames = logicalConfig.skills.map((s) => ` \u2022 ${s.name}`).join("\n");
|
|
22800
22898
|
const confirmInstall = await Rt({
|
|
22801
|
-
message: `Install ${logicalConfig.skills.length} skill(s) now
|
|
22899
|
+
message: `Install ${logicalConfig.skills.length} skill(s) now?
|
|
22900
|
+
` + skillNames + `
|
|
22901
|
+
You can also install them later with:
|
|
22902
|
+
npx @codemcp/skills experimental_install`,
|
|
22802
22903
|
initialValue: true
|
|
22803
22904
|
});
|
|
22804
22905
|
if (typeof confirmInstall === "symbol") {
|
|
@@ -22845,7 +22946,7 @@ function promptSelect(facet, existingChoices) {
|
|
|
22845
22946
|
}
|
|
22846
22947
|
const initialValue = getValidInitialValue(facet, existingChoices);
|
|
22847
22948
|
return Jt({
|
|
22848
|
-
message: facet.label
|
|
22949
|
+
message: `${facet.label} \u2014 ${facet.description}`,
|
|
22849
22950
|
options: options2,
|
|
22850
22951
|
...initialValue !== void 0 && { initialValue }
|
|
22851
22952
|
});
|
|
@@ -22858,7 +22959,7 @@ function promptMultiSelect(facet, existingChoices) {
|
|
|
22858
22959
|
}));
|
|
22859
22960
|
const initialValues = getValidInitialValues(facet, existingChoices);
|
|
22860
22961
|
return Lt2({
|
|
22861
|
-
message: facet.label
|
|
22962
|
+
message: `${facet.label} \u2014 ${facet.description}`,
|
|
22862
22963
|
options: options2,
|
|
22863
22964
|
required: false,
|
|
22864
22965
|
...initialValues !== void 0 && { initialValues }
|
|
@@ -22943,7 +23044,14 @@ if (command === "setup") {
|
|
|
22943
23044
|
console.log(version);
|
|
22944
23045
|
} else {
|
|
22945
23046
|
const allIds = getHarnessIds();
|
|
22946
|
-
console.log(`ade v${version}`);
|
|
23047
|
+
console.log(`ade v${version} \u2014 Agentic Development Environment`);
|
|
23048
|
+
console.log();
|
|
23049
|
+
console.log(
|
|
23050
|
+
"Define how your team works with coding agents \u2014 pick your facets,"
|
|
23051
|
+
);
|
|
23052
|
+
console.log(
|
|
23053
|
+
"ADE translates them into a shared information hierarchy in your repo."
|
|
23054
|
+
);
|
|
22947
23055
|
console.log();
|
|
22948
23056
|
console.log("Usage: ade <command> [options]");
|
|
22949
23057
|
console.log();
|
|
@@ -7,11 +7,13 @@ import { join } from "node:path";
|
|
|
7
7
|
vi.mock("@clack/prompts", () => ({
|
|
8
8
|
intro: vi.fn(),
|
|
9
9
|
outro: vi.fn(),
|
|
10
|
+
note: vi.fn(),
|
|
10
11
|
select: vi.fn(),
|
|
11
12
|
multiselect: vi.fn(),
|
|
12
13
|
confirm: vi.fn(),
|
|
13
14
|
isCancel: vi.fn().mockReturnValue(false),
|
|
14
15
|
cancel: vi.fn(),
|
|
16
|
+
log: { info: vi.fn(), warn: vi.fn() },
|
|
15
17
|
spinner: vi.fn().mockReturnValue({ start: vi.fn(), stop: vi.fn() })
|
|
16
18
|
}));
|
|
17
19
|
|
|
@@ -6,6 +6,7 @@ import type { Catalog, LogicalConfig } from "@codemcp/ade-core";
|
|
|
6
6
|
vi.mock("@clack/prompts", () => ({
|
|
7
7
|
intro: vi.fn(),
|
|
8
8
|
outro: vi.fn(),
|
|
9
|
+
note: vi.fn(),
|
|
9
10
|
select: vi.fn(),
|
|
10
11
|
multiselect: vi.fn(),
|
|
11
12
|
confirm: vi.fn(),
|
|
@@ -298,7 +299,7 @@ describe("runSetup", () => {
|
|
|
298
299
|
expect(clack.multiselect).toHaveBeenCalledTimes(1);
|
|
299
300
|
expect(clack.multiselect).toHaveBeenCalledWith(
|
|
300
301
|
expect.objectContaining({
|
|
301
|
-
message: expect.stringContaining("
|
|
302
|
+
message: expect.stringContaining("coding agents")
|
|
302
303
|
})
|
|
303
304
|
);
|
|
304
305
|
});
|
|
@@ -26,7 +26,35 @@ export async function runSetup(
|
|
|
26
26
|
projectRoot: string,
|
|
27
27
|
catalog: Catalog
|
|
28
28
|
): Promise<void> {
|
|
29
|
-
|
|
29
|
+
let lineIndex = 0;
|
|
30
|
+
const LOGO_LINES = [
|
|
31
|
+
"\n",
|
|
32
|
+
" █████╗ ██████╗ ███████╗ ███████╗███████╗████████╗██╗ ██╗██████╗ ",
|
|
33
|
+
"██╔══██╗██╔══██╗██╔════╝ ██╔════╝██╔════╝╚══██╔══╝██║ ██║██╔══██╗",
|
|
34
|
+
"███████║██║ ██║█████╗ ███████╗█████╗ ██║ ██║ ██║██████╔╝",
|
|
35
|
+
"██╔══██║██║ ██║██╔══╝ ╚════██║██╔══╝ ██║ ██║ ██║██╔═══╝ ",
|
|
36
|
+
"██║ ██║██████╔╝███████╗ ███████║███████╗ ██║ ╚██████╔╝██║ ",
|
|
37
|
+
"╚═╝ ╚═╝╚═════╝ ╚══════╝ ╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ",
|
|
38
|
+
"\n"
|
|
39
|
+
];
|
|
40
|
+
for (const line of LOGO_LINES) {
|
|
41
|
+
lineIndex++;
|
|
42
|
+
if (lineIndex === 1) {
|
|
43
|
+
clack.intro(line);
|
|
44
|
+
} else {
|
|
45
|
+
console.log(`│ ${line}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
clack.note(
|
|
49
|
+
[
|
|
50
|
+
"You're about to define how your team works with coding agents.",
|
|
51
|
+
"",
|
|
52
|
+
"Pick your facets — architecture, practices, process — and ADE",
|
|
53
|
+
"translates them into a shared information hierarchy your agents",
|
|
54
|
+
"read from the repo. One setup, consistent across the whole team."
|
|
55
|
+
].join("\n"),
|
|
56
|
+
"ADE — Agentic Development Environment"
|
|
57
|
+
);
|
|
30
58
|
|
|
31
59
|
const existingConfig = await readUserConfig(projectRoot);
|
|
32
60
|
const existingChoices = existingConfig?.choices ?? {};
|
|
@@ -83,7 +111,8 @@ export async function runSetup(
|
|
|
83
111
|
|
|
84
112
|
if (impliedDocsets.length > 0) {
|
|
85
113
|
const selected = await clack.multiselect({
|
|
86
|
-
message:
|
|
114
|
+
message:
|
|
115
|
+
"Documentation sources — Those will be pulled to your local disk for browsing on demand",
|
|
87
116
|
options: impliedDocsets.map((d) => ({
|
|
88
117
|
value: d.id,
|
|
89
118
|
label: d.label,
|
|
@@ -120,7 +149,9 @@ export async function runSetup(
|
|
|
120
149
|
);
|
|
121
150
|
|
|
122
151
|
const selectedHarnesses = await clack.multiselect({
|
|
123
|
-
message:
|
|
152
|
+
message:
|
|
153
|
+
"Which coding agents should receive config?\n" +
|
|
154
|
+
"ADE generates config files for each agent you select.\n",
|
|
124
155
|
options: harnessOptions,
|
|
125
156
|
initialValues:
|
|
126
157
|
validExistingHarnesses && validExistingHarnesses.length > 0
|
|
@@ -173,8 +204,14 @@ export async function runSetup(
|
|
|
173
204
|
}
|
|
174
205
|
|
|
175
206
|
if (logicalConfig.skills.length > 0) {
|
|
207
|
+
const skillNames = logicalConfig.skills
|
|
208
|
+
.map((s) => ` • ${s.name}`)
|
|
209
|
+
.join("\n");
|
|
176
210
|
const confirmInstall = await clack.confirm({
|
|
177
|
-
message:
|
|
211
|
+
message:
|
|
212
|
+
`Install ${logicalConfig.skills.length} skill(s) now?\n` +
|
|
213
|
+
skillNames +
|
|
214
|
+
`\nYou can also install them later with:\n npx @codemcp/skills experimental_install`,
|
|
178
215
|
initialValue: true
|
|
179
216
|
});
|
|
180
217
|
|
|
@@ -243,7 +280,7 @@ function promptSelect(
|
|
|
243
280
|
const initialValue = getValidInitialValue(facet, existingChoices);
|
|
244
281
|
|
|
245
282
|
return clack.select({
|
|
246
|
-
message: facet.label
|
|
283
|
+
message: `${facet.label} — ${facet.description}`,
|
|
247
284
|
options,
|
|
248
285
|
...(initialValue !== undefined && { initialValue })
|
|
249
286
|
});
|
|
@@ -262,7 +299,7 @@ function promptMultiSelect(
|
|
|
262
299
|
const initialValues = getValidInitialValues(facet, existingChoices);
|
|
263
300
|
|
|
264
301
|
return clack.multiselect({
|
|
265
|
-
message: facet.label
|
|
302
|
+
message: `${facet.label} — ${facet.description}`,
|
|
266
303
|
options,
|
|
267
304
|
required: false,
|
|
268
305
|
...(initialValues !== undefined && { initialValues })
|
|
@@ -31,7 +31,14 @@ if (command === "setup") {
|
|
|
31
31
|
console.log(version);
|
|
32
32
|
} else {
|
|
33
33
|
const allIds = getHarnessIds();
|
|
34
|
-
console.log(`ade v${version}`);
|
|
34
|
+
console.log(`ade v${version} — Agentic Development Environment`);
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(
|
|
37
|
+
"Define how your team works with coding agents — pick your facets,"
|
|
38
|
+
);
|
|
39
|
+
console.log(
|
|
40
|
+
"ADE translates them into a shared information hierarchy in your repo."
|
|
41
|
+
);
|
|
35
42
|
console.log();
|
|
36
43
|
console.log("Usage: ade <command> [options]");
|
|
37
44
|
console.log();
|
|
@@ -470,8 +470,8 @@ describe("catalog", () => {
|
|
|
470
470
|
expect(autonomy!.required).toBe(false);
|
|
471
471
|
expect(autonomy!.multiSelect).toBe(false);
|
|
472
472
|
expect(autonomy!.options.map((option) => option.id)).toEqual([
|
|
473
|
-
"rigid",
|
|
474
473
|
"sensible-defaults",
|
|
474
|
+
"rigid",
|
|
475
475
|
"max-autonomy"
|
|
476
476
|
]);
|
|
477
477
|
});
|
|
@@ -9,26 +9,26 @@ export const autonomyFacet: Facet = {
|
|
|
9
9
|
multiSelect: false,
|
|
10
10
|
options: [
|
|
11
11
|
{
|
|
12
|
-
id: "
|
|
13
|
-
label: "
|
|
12
|
+
id: "sensible-defaults",
|
|
13
|
+
label: "Sensible defaults",
|
|
14
14
|
description:
|
|
15
|
-
"
|
|
15
|
+
"Allow a curated built-in capabilities set while keeping potentially risky actions approval-gated",
|
|
16
16
|
recipe: [
|
|
17
17
|
{
|
|
18
18
|
writer: "permission-policy",
|
|
19
|
-
config: { profile: "
|
|
19
|
+
config: { profile: "sensible-defaults" }
|
|
20
20
|
}
|
|
21
21
|
]
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
|
-
id: "
|
|
25
|
-
label: "
|
|
24
|
+
id: "rigid",
|
|
25
|
+
label: "Rigid",
|
|
26
26
|
description:
|
|
27
|
-
"
|
|
27
|
+
"Keep built-in capabilities approval-gated and require confirmation before acting",
|
|
28
28
|
recipe: [
|
|
29
29
|
{
|
|
30
30
|
writer: "permission-policy",
|
|
31
|
-
config: { profile: "
|
|
31
|
+
config: { profile: "rigid" }
|
|
32
32
|
}
|
|
33
33
|
]
|
|
34
34
|
},
|
|
@@ -36,7 +36,7 @@ export const autonomyFacet: Facet = {
|
|
|
36
36
|
id: "max-autonomy",
|
|
37
37
|
label: "Max autonomy",
|
|
38
38
|
description:
|
|
39
|
-
"Allow broad local built-in autonomy
|
|
39
|
+
"Allow broad local built-in autonomy. Recommended for sandboxed environments only",
|
|
40
40
|
recipe: [
|
|
41
41
|
{
|
|
42
42
|
writer: "permission-policy",
|
|
@@ -89,7 +89,7 @@ export const backpressureFacet: Facet = {
|
|
|
89
89
|
id: "backpressure",
|
|
90
90
|
label: "Backpressure",
|
|
91
91
|
description:
|
|
92
|
-
"
|
|
92
|
+
"Which automated quality gates to put in place. Translates to git hooks",
|
|
93
93
|
required: false,
|
|
94
94
|
multiSelect: true,
|
|
95
95
|
dependsOn: ["architecture"],
|
|
@@ -3,8 +3,7 @@ import type { Facet } from "../../types.js";
|
|
|
3
3
|
export const practicesFacet: Facet = {
|
|
4
4
|
id: "practices",
|
|
5
5
|
label: "Practices",
|
|
6
|
-
description:
|
|
7
|
-
"Composable development practices — mix and match regardless of stack",
|
|
6
|
+
description: "Development practices — mix and match regardless of stack",
|
|
8
7
|
required: false,
|
|
9
8
|
multiSelect: true,
|
|
10
9
|
options: [
|
|
@@ -3,7 +3,7 @@ import type { Facet } from "../../types.js";
|
|
|
3
3
|
export const processFacet: Facet = {
|
|
4
4
|
id: "process",
|
|
5
5
|
label: "Process",
|
|
6
|
-
description: "How
|
|
6
|
+
description: "How will you guide your agent",
|
|
7
7
|
required: true,
|
|
8
8
|
options: [
|
|
9
9
|
{
|
|
@@ -16,7 +16,16 @@ export const processFacet: Facet = {
|
|
|
16
16
|
writer: "workflows",
|
|
17
17
|
config: {
|
|
18
18
|
package: "@codemcp/workflows-server@latest",
|
|
19
|
-
ref: "workflows"
|
|
19
|
+
ref: "workflows",
|
|
20
|
+
env: {
|
|
21
|
+
VIBE_WORKFLOW_DOMAINS: "skilled"
|
|
22
|
+
},
|
|
23
|
+
allowedTools: [
|
|
24
|
+
"whats_next",
|
|
25
|
+
"conduct_review",
|
|
26
|
+
"list_workflows",
|
|
27
|
+
"get_tool_info"
|
|
28
|
+
]
|
|
20
29
|
}
|
|
21
30
|
},
|
|
22
31
|
{
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from "./types.js";
|
|
6
6
|
import { instructionWriter } from "./writers/instruction.js";
|
|
7
7
|
import { workflowsWriter } from "./writers/workflows.js";
|
|
8
|
+
import { mcpServerWriter } from "./writers/mcp-server.js";
|
|
8
9
|
import { skillsWriter } from "./writers/skills.js";
|
|
9
10
|
import { knowledgeWriter } from "./writers/knowledge.js";
|
|
10
11
|
import { gitHooksWriter } from "./writers/git-hooks.js";
|
|
@@ -51,15 +52,15 @@ export function createDefaultRegistry(): WriterRegistry {
|
|
|
51
52
|
|
|
52
53
|
registerProvisionWriter(registry, instructionWriter);
|
|
53
54
|
registerProvisionWriter(registry, workflowsWriter);
|
|
55
|
+
registerProvisionWriter(registry, mcpServerWriter);
|
|
54
56
|
registerProvisionWriter(registry, skillsWriter);
|
|
55
|
-
|
|
56
57
|
registerProvisionWriter(registry, knowledgeWriter);
|
|
57
58
|
registerProvisionWriter(registry, gitHooksWriter);
|
|
58
59
|
registerProvisionWriter(registry, setupNoteWriter);
|
|
59
60
|
registerProvisionWriter(registry, permissionPolicyWriter);
|
|
60
61
|
|
|
61
62
|
// Stub writers for types not yet implemented
|
|
62
|
-
for (const id of ["
|
|
63
|
+
for (const id of ["installable"]) {
|
|
63
64
|
registerProvisionWriter(registry, {
|
|
64
65
|
id,
|
|
65
66
|
write: async () => ({})
|
|
@@ -120,6 +120,35 @@ describe("resolve", () => {
|
|
|
120
120
|
expect(result.mcp_servers.length).toBeGreaterThanOrEqual(1);
|
|
121
121
|
expect(result.instructions).toContain("Extra instruction");
|
|
122
122
|
});
|
|
123
|
+
|
|
124
|
+
it("env set in the catalog option config is forwarded to the resolved mcp_server entry", async () => {
|
|
125
|
+
const userConfig: UserConfig = {
|
|
126
|
+
choices: { process: "codemcp-workflows" }
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Patch the catalog option's provision config to include env
|
|
130
|
+
const processFacet = catalog.facets.find((f) => f.id === "process")!;
|
|
131
|
+
const option = processFacet.options.find(
|
|
132
|
+
(o) => o.id === "codemcp-workflows"
|
|
133
|
+
)!;
|
|
134
|
+
const workflowsProvision = option.recipe.find(
|
|
135
|
+
(p) => p.writer === "workflows"
|
|
136
|
+
)!;
|
|
137
|
+
workflowsProvision.config = {
|
|
138
|
+
...workflowsProvision.config,
|
|
139
|
+
env: { VIBE_WORKFLOWS_DOMAIN: "skilled" }
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = await resolve(userConfig, catalog, registry);
|
|
143
|
+
|
|
144
|
+
const workflowsServer = result.mcp_servers.find(
|
|
145
|
+
(s) => s.ref === "workflows"
|
|
146
|
+
);
|
|
147
|
+
expect(workflowsServer).toBeDefined();
|
|
148
|
+
expect(workflowsServer!.env).toEqual({
|
|
149
|
+
VIBE_WORKFLOWS_DOMAIN: "skilled"
|
|
150
|
+
});
|
|
151
|
+
});
|
|
123
152
|
});
|
|
124
153
|
|
|
125
154
|
describe("unknown facet in choices", () => {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mcpServerWriter } from "./mcp-server.js";
|
|
3
|
+
import type { ResolutionContext } from "../types.js";
|
|
4
|
+
|
|
5
|
+
describe("mcpServerWriter", () => {
|
|
6
|
+
const context: ResolutionContext = { resolved: {} };
|
|
7
|
+
|
|
8
|
+
it("has id 'mcp-server'", () => {
|
|
9
|
+
expect(mcpServerWriter.id).toBe("mcp-server");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns mcp_servers with correct ref, command, args, and env", async () => {
|
|
13
|
+
const result = await mcpServerWriter.write(
|
|
14
|
+
{
|
|
15
|
+
ref: "my-server",
|
|
16
|
+
command: "npx",
|
|
17
|
+
args: ["my-mcp-package"],
|
|
18
|
+
env: { KEY: "value" }
|
|
19
|
+
},
|
|
20
|
+
context
|
|
21
|
+
);
|
|
22
|
+
expect(result).toEqual({
|
|
23
|
+
mcp_servers: [
|
|
24
|
+
{
|
|
25
|
+
ref: "my-server",
|
|
26
|
+
command: "npx",
|
|
27
|
+
args: ["my-mcp-package"],
|
|
28
|
+
env: { KEY: "value" }
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("defaults env to an empty object when not specified", async () => {
|
|
35
|
+
const result = await mcpServerWriter.write(
|
|
36
|
+
{ ref: "my-server", command: "npx", args: ["my-mcp-package"] },
|
|
37
|
+
context
|
|
38
|
+
);
|
|
39
|
+
expect(result.mcp_servers![0].env).toEqual({});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("includes allowedTools when specified", async () => {
|
|
43
|
+
const result = await mcpServerWriter.write(
|
|
44
|
+
{
|
|
45
|
+
ref: "my-server",
|
|
46
|
+
command: "npx",
|
|
47
|
+
args: ["my-mcp-package"],
|
|
48
|
+
allowedTools: ["tool_a", "tool_b"]
|
|
49
|
+
},
|
|
50
|
+
context
|
|
51
|
+
);
|
|
52
|
+
expect(result.mcp_servers![0].allowedTools).toEqual(["tool_a", "tool_b"]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("omits allowedTools from entry when not specified", async () => {
|
|
56
|
+
const result = await mcpServerWriter.write(
|
|
57
|
+
{ ref: "my-server", command: "npx", args: ["my-mcp-package"] },
|
|
58
|
+
context
|
|
59
|
+
);
|
|
60
|
+
expect(result.mcp_servers![0]).not.toHaveProperty("allowedTools");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ProvisionWriterDef } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export const mcpServerWriter: ProvisionWriterDef = {
|
|
4
|
+
id: "mcp-server",
|
|
5
|
+
async write(config) {
|
|
6
|
+
const { ref, command, args, env, allowedTools } = config as {
|
|
7
|
+
ref: string;
|
|
8
|
+
command: string;
|
|
9
|
+
args: string[];
|
|
10
|
+
env?: Record<string, string>;
|
|
11
|
+
allowedTools?: string[];
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
mcp_servers: [
|
|
15
|
+
{
|
|
16
|
+
ref,
|
|
17
|
+
command,
|
|
18
|
+
args,
|
|
19
|
+
env: env ?? {},
|
|
20
|
+
...(allowedTools !== undefined ? { allowedTools } : {})
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -59,6 +59,28 @@ describe("workflowsWriter", () => {
|
|
|
59
59
|
expect(result.mcp_servers![0].env).toEqual({});
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
it("includes allowedTools in the entry when specified", async () => {
|
|
63
|
+
const result = await workflowsWriter.write(
|
|
64
|
+
{
|
|
65
|
+
package: "@codemcp/workflows-server",
|
|
66
|
+
allowedTools: ["whats_next", "conduct_review"]
|
|
67
|
+
},
|
|
68
|
+
context
|
|
69
|
+
);
|
|
70
|
+
expect(result.mcp_servers![0].allowedTools).toEqual([
|
|
71
|
+
"whats_next",
|
|
72
|
+
"conduct_review"
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("omits allowedTools from entry when not specified", async () => {
|
|
77
|
+
const result = await workflowsWriter.write(
|
|
78
|
+
{ package: "@codemcp/workflows-server" },
|
|
79
|
+
context
|
|
80
|
+
);
|
|
81
|
+
expect(result.mcp_servers![0]).not.toHaveProperty("allowedTools");
|
|
82
|
+
});
|
|
83
|
+
|
|
62
84
|
it("only returns mcp_servers, not other LogicalConfig keys", async () => {
|
|
63
85
|
const result = await workflowsWriter.write(
|
|
64
86
|
{ package: "@codemcp/workflows-server" },
|
|
@@ -6,11 +6,13 @@ export const workflowsWriter: ProvisionWriterDef = {
|
|
|
6
6
|
const {
|
|
7
7
|
package: pkg,
|
|
8
8
|
ref,
|
|
9
|
-
env
|
|
9
|
+
env,
|
|
10
|
+
allowedTools
|
|
10
11
|
} = config as {
|
|
11
12
|
package: string;
|
|
12
13
|
ref?: string;
|
|
13
14
|
env?: Record<string, string>;
|
|
15
|
+
allowedTools?: string[];
|
|
14
16
|
};
|
|
15
17
|
return {
|
|
16
18
|
mcp_servers: [
|
|
@@ -18,7 +20,8 @@ export const workflowsWriter: ProvisionWriterDef = {
|
|
|
18
20
|
ref: ref ?? pkg,
|
|
19
21
|
command: "npx",
|
|
20
22
|
args: [pkg],
|
|
21
|
-
env: env ?? {}
|
|
23
|
+
env: env ?? {},
|
|
24
|
+
...(allowedTools !== undefined ? { allowedTools } : {})
|
|
22
25
|
}
|
|
23
26
|
]
|
|
24
27
|
};
|
|
@@ -198,12 +198,8 @@ describe("copilotWriter", () => {
|
|
|
198
198
|
expect(sensibleAgent).not.toContain(" - execute");
|
|
199
199
|
expect(sensibleAgent).not.toContain(" - todo");
|
|
200
200
|
expect(sensibleAgent).not.toContain(" - web");
|
|
201
|
-
expect(sensibleAgent).toContain(" - workflows
|
|
202
|
-
expect(sensibleAgent).toContain(
|
|
203
|
-
expect(sensibleAgent).not.toContain(" - workflows/*");
|
|
204
|
-
expect(sensibleAgent).toContain(
|
|
205
|
-
' tools: ["whats_next","proceed_to_phase"]'
|
|
206
|
-
);
|
|
201
|
+
expect(sensibleAgent).toContain(" - workflows/*");
|
|
202
|
+
expect(sensibleAgent).toContain(' tools: ["*"]');
|
|
207
203
|
|
|
208
204
|
expect(maxAgent).toContain(" - read");
|
|
209
205
|
expect(maxAgent).toContain(" - edit");
|
|
@@ -56,14 +56,7 @@ function getBuiltInTools(profile: AutonomyProfile | undefined): string[] {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function getForwardedMcpTools(servers: McpServerEntry[]): string[] {
|
|
59
|
-
return servers.
|
|
60
|
-
const allowedTools = server.allowedTools ?? ["*"];
|
|
61
|
-
if (allowedTools.includes("*")) {
|
|
62
|
-
return [`${server.ref}/*`];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return allowedTools.map((tool) => `${server.ref}/${tool}`);
|
|
66
|
-
});
|
|
59
|
+
return servers.map((server) => `${server.ref}/*`);
|
|
67
60
|
}
|
|
68
61
|
|
|
69
62
|
function renderCopilotAgentMcpServers(servers: McpServerEntry[]): string[] {
|
|
@@ -78,7 +71,7 @@ function renderCopilotAgentMcpServers(servers: McpServerEntry[]): string[] {
|
|
|
78
71
|
lines.push(" type: stdio");
|
|
79
72
|
lines.push(` command: ${JSON.stringify(server.command)}`);
|
|
80
73
|
lines.push(` args: ${JSON.stringify(server.args)}`);
|
|
81
|
-
lines.push(` tools: ${JSON.stringify(
|
|
74
|
+
lines.push(` tools: ${JSON.stringify(["*"])}`);
|
|
82
75
|
|
|
83
76
|
if (Object.keys(server.env).length > 0) {
|
|
84
77
|
lines.push(" env:");
|
|
@@ -186,4 +186,36 @@ describe("kiroWriter", () => {
|
|
|
186
186
|
expect(rigidMcp.mcpServers.workflows.autoApprove).toEqual(["*"]);
|
|
187
187
|
expect(maxMcp.mcpServers.workflows.autoApprove).toEqual(["*"]);
|
|
188
188
|
});
|
|
189
|
+
|
|
190
|
+
it("uses wildcard in tools but restricted names in allowedTools when allowedTools is set", async () => {
|
|
191
|
+
const config: LogicalConfig = {
|
|
192
|
+
mcp_servers: [
|
|
193
|
+
{
|
|
194
|
+
ref: "workflows",
|
|
195
|
+
command: "npx",
|
|
196
|
+
args: ["-y", "@codemcp/workflows"],
|
|
197
|
+
env: {},
|
|
198
|
+
allowedTools: ["whats_next", "conduct_review"]
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
instructions: [],
|
|
202
|
+
cli_actions: [],
|
|
203
|
+
knowledge_sources: [],
|
|
204
|
+
skills: [],
|
|
205
|
+
git_hooks: [],
|
|
206
|
+
setup_notes: []
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
await kiroWriter.install(config, dir);
|
|
210
|
+
|
|
211
|
+
const agent = JSON.parse(
|
|
212
|
+
await readFile(join(dir, ".kiro", "agents", "ade.json"), "utf-8")
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(agent.tools).toContain("@workflows/*");
|
|
216
|
+
expect(agent.tools).not.toContain("@workflows/whats_next");
|
|
217
|
+
expect(agent.allowedTools).toContain("@workflows/whats_next");
|
|
218
|
+
expect(agent.allowedTools).toContain("@workflows/conduct_review");
|
|
219
|
+
expect(agent.allowedTools).not.toContain("@workflows/*");
|
|
220
|
+
});
|
|
189
221
|
});
|
|
@@ -27,6 +27,10 @@ export const kiroWriter: HarnessWriter = {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
const tools = getKiroTools(getAutonomyProfile(config), config.mcp_servers);
|
|
30
|
+
const allowedTools = getKiroAllowedTools(
|
|
31
|
+
getAutonomyProfile(config),
|
|
32
|
+
config.mcp_servers
|
|
33
|
+
);
|
|
30
34
|
await writeJson(join(projectRoot, ".kiro", "agents", "ade.json"), {
|
|
31
35
|
name: "ade",
|
|
32
36
|
description:
|
|
@@ -36,7 +40,7 @@ export const kiroWriter: HarnessWriter = {
|
|
|
36
40
|
"ADE — Agentic Development Environment agent.",
|
|
37
41
|
mcpServers: getKiroAgentMcpServers(config.mcp_servers),
|
|
38
42
|
tools,
|
|
39
|
-
allowedTools
|
|
43
|
+
allowedTools,
|
|
40
44
|
useLegacyMcpJson: true
|
|
41
45
|
});
|
|
42
46
|
|
|
@@ -48,7 +52,7 @@ function getKiroTools(
|
|
|
48
52
|
profile: AutonomyProfile | undefined,
|
|
49
53
|
servers: McpServerEntry[]
|
|
50
54
|
): string[] {
|
|
51
|
-
const mcpTools =
|
|
55
|
+
const mcpTools = servers.map((server) => `@${server.ref}/*`);
|
|
52
56
|
|
|
53
57
|
switch (profile) {
|
|
54
58
|
case "rigid":
|
|
@@ -62,15 +66,28 @@ function getKiroTools(
|
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
function
|
|
66
|
-
|
|
69
|
+
function getKiroAllowedTools(
|
|
70
|
+
profile: AutonomyProfile | undefined,
|
|
71
|
+
servers: McpServerEntry[]
|
|
72
|
+
): string[] {
|
|
73
|
+
const mcpAllowedTools = servers.flatMap((server) => {
|
|
67
74
|
const allowedTools = server.allowedTools ?? ["*"];
|
|
68
75
|
if (allowedTools.includes("*")) {
|
|
69
76
|
return [`@${server.ref}/*`];
|
|
70
77
|
}
|
|
71
|
-
|
|
72
78
|
return allowedTools.map((tool) => `@${server.ref}/${tool}`);
|
|
73
79
|
});
|
|
80
|
+
|
|
81
|
+
switch (profile) {
|
|
82
|
+
case "rigid":
|
|
83
|
+
return ["read", "shell", "spec", ...mcpAllowedTools];
|
|
84
|
+
case "sensible-defaults":
|
|
85
|
+
return ["read", "write", "shell", "spec", ...mcpAllowedTools];
|
|
86
|
+
case "max-autonomy":
|
|
87
|
+
return ["read", "write", "shell(*)", "spec", ...mcpAllowedTools];
|
|
88
|
+
default:
|
|
89
|
+
return ["read", "write", "shell", "spec", ...mcpAllowedTools];
|
|
90
|
+
}
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
function getKiroAgentMcpServers(
|
|
@@ -148,6 +148,72 @@ describe("opencodeWriter", () => {
|
|
|
148
148
|
expect(rigidAgent).not.toContain("tools:");
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it("writes allowed MCP tools into the permission block of the agent frontmatter", async () => {
|
|
152
|
+
const projectRoot = join(dir, "mcp-tools");
|
|
153
|
+
const config: LogicalConfig = {
|
|
154
|
+
mcp_servers: [
|
|
155
|
+
{
|
|
156
|
+
ref: "workflows",
|
|
157
|
+
command: "npx",
|
|
158
|
+
args: ["@codemcp/workflows-server@latest"],
|
|
159
|
+
env: {},
|
|
160
|
+
allowedTools: ["whats_next", "conduct_review"]
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
instructions: ["Follow project rules."],
|
|
164
|
+
cli_actions: [],
|
|
165
|
+
knowledge_sources: [],
|
|
166
|
+
skills: [],
|
|
167
|
+
git_hooks: [],
|
|
168
|
+
setup_notes: []
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
await opencodeWriter.install(config, projectRoot);
|
|
172
|
+
|
|
173
|
+
const agent = await readFile(
|
|
174
|
+
join(projectRoot, ".opencode", "agents", "ade.md"),
|
|
175
|
+
"utf-8"
|
|
176
|
+
);
|
|
177
|
+
const frontmatter = parseFrontmatter(agent);
|
|
178
|
+
const permission = frontmatter.permission as Record<string, string>;
|
|
179
|
+
|
|
180
|
+
expect(permission["workflows*"]).toBe("ask");
|
|
181
|
+
expect(permission["workflows_whats_next"]).toBe("allow");
|
|
182
|
+
expect(permission["workflows_conduct_review"]).toBe("allow");
|
|
183
|
+
expect(agent).not.toContain("tools:");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("writes wildcard MCP permission when allowedTools is not restricted", async () => {
|
|
187
|
+
const projectRoot = join(dir, "mcp-wildcard");
|
|
188
|
+
const config: LogicalConfig = {
|
|
189
|
+
mcp_servers: [
|
|
190
|
+
{
|
|
191
|
+
ref: "workflows",
|
|
192
|
+
command: "npx",
|
|
193
|
+
args: ["@codemcp/workflows-server@latest"],
|
|
194
|
+
env: {}
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
instructions: ["Follow project rules."],
|
|
198
|
+
cli_actions: [],
|
|
199
|
+
knowledge_sources: [],
|
|
200
|
+
skills: [],
|
|
201
|
+
git_hooks: [],
|
|
202
|
+
setup_notes: []
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
await opencodeWriter.install(config, projectRoot);
|
|
206
|
+
|
|
207
|
+
const agent = await readFile(
|
|
208
|
+
join(projectRoot, ".opencode", "agents", "ade.md"),
|
|
209
|
+
"utf-8"
|
|
210
|
+
);
|
|
211
|
+
const frontmatter = parseFrontmatter(agent);
|
|
212
|
+
const permission = frontmatter.permission as Record<string, string>;
|
|
213
|
+
|
|
214
|
+
expect(permission["workflows*"]).toBe("allow");
|
|
215
|
+
});
|
|
216
|
+
|
|
151
217
|
it("keeps MCP servers in project config and writes documented environment fields", async () => {
|
|
152
218
|
const projectRoot = join(dir, "mcp");
|
|
153
219
|
const config = {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
AutonomyProfile,
|
|
4
|
+
LogicalConfig,
|
|
5
|
+
McpServerEntry
|
|
6
|
+
} from "@codemcp/ade-core";
|
|
3
7
|
import type { HarnessWriter } from "../types.js";
|
|
4
8
|
import {
|
|
5
9
|
writeAgentMd,
|
|
@@ -138,6 +142,24 @@ const MAX_AUTONOMY_RULES: Record<string, PermissionRule> = {
|
|
|
138
142
|
doom_loop: "deny"
|
|
139
143
|
};
|
|
140
144
|
|
|
145
|
+
function getMcpPermissions(
|
|
146
|
+
servers: McpServerEntry[]
|
|
147
|
+
): Record<string, PermissionRule> | undefined {
|
|
148
|
+
const entries: [string, PermissionRule][] = servers.flatMap((server) => {
|
|
149
|
+
const allowedTools = server.allowedTools ?? ["*"];
|
|
150
|
+
if (allowedTools.includes("*")) {
|
|
151
|
+
return [[`${server.ref}*`, "allow"]] as [string, PermissionRule][];
|
|
152
|
+
}
|
|
153
|
+
return [
|
|
154
|
+
[`${server.ref}*`, "ask"] as [string, PermissionRule],
|
|
155
|
+
...allowedTools.map(
|
|
156
|
+
(tool) => [`${server.ref}_${tool}`, "allow"] as [string, PermissionRule]
|
|
157
|
+
)
|
|
158
|
+
];
|
|
159
|
+
});
|
|
160
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
141
163
|
function getPermissionRules(
|
|
142
164
|
profile: AutonomyProfile | undefined
|
|
143
165
|
): Record<string, PermissionRule> | undefined {
|
|
@@ -170,11 +192,16 @@ export const opencodeWriter: HarnessWriter = {
|
|
|
170
192
|
});
|
|
171
193
|
|
|
172
194
|
const permission = getPermissionRules(getAutonomyProfile(config));
|
|
195
|
+
const mcpPermissions = getMcpPermissions(config.mcp_servers);
|
|
196
|
+
const mergedPermission =
|
|
197
|
+
permission || mcpPermissions
|
|
198
|
+
? { ...(mcpPermissions ?? {}), ...(permission ?? {}) }
|
|
199
|
+
: undefined;
|
|
173
200
|
|
|
174
201
|
await writeAgentMd(config, {
|
|
175
202
|
path: join(projectRoot, ".opencode", "agents", "ade.md"),
|
|
176
|
-
extraFrontmatter:
|
|
177
|
-
? renderYamlMapping("permission",
|
|
203
|
+
extraFrontmatter: mergedPermission
|
|
204
|
+
? renderYamlMapping("permission", mergedPermission)
|
|
178
205
|
: undefined,
|
|
179
206
|
fallbackBody:
|
|
180
207
|
"ADE — Agentic Development Environment agent with project conventions and tools."
|