@codemcp/ade 0.2.6 → 0.3.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/.agentskills/skills/conventional-commits/SKILL.md +36 -0
- package/.beads/issues.jsonl +6 -0
- package/.beads/last-touched +1 -1
- package/.kiro/agents/ade.json +9 -2
- package/.opencode/agents/ade.md +9 -18
- package/.vibe/beads-state-ade-fix-no-git-k396xs.json +34 -0
- package/.vibe/development-plan-fix-no-git.md +76 -0
- package/AGENTS.md +27 -0
- package/config.lock.yaml +33 -9
- package/config.yaml +3 -0
- package/package.json +1 -1
- package/packages/cli/dist/index.js +114 -32
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/commands/conventions.integration.spec.ts +7 -1
- package/packages/cli/src/commands/install.ts +19 -1
- package/packages/cli/src/commands/setup.ts +19 -1
- package/packages/core/package.json +1 -1
- package/packages/harnesses/package.json +2 -1
- package/packages/harnesses/src/util.spec.ts +97 -0
- package/packages/harnesses/src/util.ts +21 -4
- package/packages/harnesses/src/writers/opencode.spec.ts +4 -6
- package/packages/harnesses/src/writers/opencode.ts +23 -27
- package/skills-lock.json +6 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: conventional-commits
|
|
3
|
+
description: Conventional Commits specification for structured commit messages
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Conventional Commits
|
|
7
|
+
|
|
8
|
+
## Format
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
<type>[optional scope]: <description>
|
|
12
|
+
|
|
13
|
+
[optional body]
|
|
14
|
+
|
|
15
|
+
[optional footer(s)]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Types
|
|
19
|
+
|
|
20
|
+
- `feat`: A new feature (correlates with MINOR in SemVer)
|
|
21
|
+
- `fix`: A bug fix (correlates with PATCH in SemVer)
|
|
22
|
+
- `docs`: Documentation only changes
|
|
23
|
+
- `style`: Changes that do not affect the meaning of the code
|
|
24
|
+
- `refactor`: A code change that neither fixes a bug nor adds a feature
|
|
25
|
+
- `perf`: A code change that improves performance
|
|
26
|
+
- `test`: Adding missing tests or correcting existing tests
|
|
27
|
+
- `chore`: Changes to the build process or auxiliary tools
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
|
|
31
|
+
- Subject line must not exceed 72 characters
|
|
32
|
+
- Use imperative mood in the subject line ("add" not "added")
|
|
33
|
+
- Do not end the subject line with a period
|
|
34
|
+
- Separate subject from body with a blank line
|
|
35
|
+
- Use the body to explain what and why, not how
|
|
36
|
+
- `BREAKING CHANGE:` footer or `!` after type/scope for breaking changes
|
package/.beads/issues.jsonl
CHANGED
|
@@ -50,3 +50,9 @@
|
|
|
50
50
|
{"id":"ade-3.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-18T16:29:10.265074+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-18T16:29:10.265074+01:00","dependencies":[{"issue_id":"ade-3.3","depends_on_id":"ade-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"ade-3.3","depends_on_id":"ade-3.2","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
51
51
|
{"id":"ade-3.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-18T16:29:10.449967+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-18T16:29:10.449967+01:00","dependencies":[{"issue_id":"ade-3.4","depends_on_id":"ade-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"ade-3.4","depends_on_id":"ade-3.3","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
52
52
|
{"id":"ade-3.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-18T16:29:10.642935+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-18T16:29:10.642935+01:00","dependencies":[{"issue_id":"ade-3.5","depends_on_id":"ade-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"ade-3.5","depends_on_id":"ade-3.4","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
53
|
+
{"id":"ade-4","title":"ade: bugfix (development-plan-fix-no-git.md)","description":"Responsible vibe engineering session using bugfix workflow for ade","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T10:33:06.662828+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T10:33:06.662828+01:00"}
|
|
54
|
+
{"id":"ade-4.1","title":"Reproduce","description":"Gather specific information to reliably reproduce the reported bug: - What are the exact OS, browser/runtime versions, and hardware specs? - What is the precise sequence of actions that trigger the bug? - What error messages, logs, or stack traces are available? - Does this happen every time or intermittently? - How many users are affected and what is the business impact? Create test cases that demonstrate the problem. Document your findings and create tasks as needed.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T10:33:06.80022+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T10:33:06.80022+01:00","dependencies":[{"issue_id":"ade-4.1","depends_on_id":"ade-4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
55
|
+
{"id":"ade-4.2","title":"Analyze","description":"Examine the code paths involved in the bug, identify the root cause, and understand why the issue occurs. Use debugging tools, add logging, and trace through the problematic code. Document your analysis and create tasks as needed.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-03-19T10:33:06.939399+01:00","created_by":"Oliver Jägle","updated_at":"2026-03-19T10:33:06.939399+01:00","dependencies":[{"issue_id":"ade-4.2","depends_on_id":"ade-4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"ade-4.2","depends_on_id":"ade-4.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
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
|
+
{"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
|
+
{"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"}]}
|
package/.beads/last-touched
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ade-
|
|
1
|
+
ade-4.5
|
package/.kiro/agents/ade.json
CHANGED
|
@@ -14,7 +14,14 @@
|
|
|
14
14
|
"autoApprove": ["*"]
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
-
"tools": ["read", "write", "spec", "@workflows/*", "@agentskills/*"
|
|
18
|
-
"allowedTools": [
|
|
17
|
+
"tools": ["read", "write", "shell", "spec", "@workflows/*", "@agentskills/*"],
|
|
18
|
+
"allowedTools": [
|
|
19
|
+
"read",
|
|
20
|
+
"write",
|
|
21
|
+
"shell",
|
|
22
|
+
"spec",
|
|
23
|
+
"@workflows/*",
|
|
24
|
+
"@agentskills/*"
|
|
25
|
+
],
|
|
19
26
|
"useLegacyMcpJson": true
|
|
20
27
|
}
|
package/.opencode/agents/ade.md
CHANGED
|
@@ -7,25 +7,25 @@ permission:
|
|
|
7
7
|
"*.env": "deny"
|
|
8
8
|
"*.env.*": "deny"
|
|
9
9
|
"*.env.example": "allow"
|
|
10
|
-
|
|
10
|
+
skill: "deny"
|
|
11
|
+
todoread: "deny"
|
|
12
|
+
todowrite: "deny"
|
|
13
|
+
task: "deny"
|
|
14
|
+
lsp: "allow"
|
|
11
15
|
glob: "allow"
|
|
12
16
|
grep: "allow"
|
|
13
17
|
list: "allow"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
todoread: "deny"
|
|
17
|
-
todowrite: "deny"
|
|
18
|
-
skill: "deny"
|
|
18
|
+
external_directory: "ask"
|
|
19
|
+
edit: "allow"
|
|
19
20
|
webfetch: "ask"
|
|
20
21
|
websearch: "ask"
|
|
21
22
|
codesearch: "ask"
|
|
22
23
|
bash:
|
|
23
|
-
"*": "
|
|
24
|
+
"*": "ask"
|
|
24
25
|
"grep *": "allow"
|
|
25
26
|
"rg *": "allow"
|
|
26
27
|
"find *": "allow"
|
|
27
28
|
"fd *": "allow"
|
|
28
|
-
ls: "allow"
|
|
29
29
|
"ls *": "allow"
|
|
30
30
|
"cat *": "allow"
|
|
31
31
|
"head *": "allow"
|
|
@@ -60,14 +60,7 @@ permission:
|
|
|
60
60
|
"yq *": "allow"
|
|
61
61
|
"mkdir *": "allow"
|
|
62
62
|
"touch *": "allow"
|
|
63
|
-
"
|
|
64
|
-
"mv *": "ask"
|
|
65
|
-
"ln *": "ask"
|
|
66
|
-
"npm *": "ask"
|
|
67
|
-
"node *": "ask"
|
|
68
|
-
"pip *": "ask"
|
|
69
|
-
"python *": "ask"
|
|
70
|
-
"python3 *": "ask"
|
|
63
|
+
"kill *": "ask"
|
|
71
64
|
"rm *": "deny"
|
|
72
65
|
"rmdir *": "deny"
|
|
73
66
|
"curl *": "deny"
|
|
@@ -88,7 +81,6 @@ permission:
|
|
|
88
81
|
"mkfs *": "deny"
|
|
89
82
|
"mount *": "deny"
|
|
90
83
|
"umount *": "deny"
|
|
91
|
-
"kill *": "deny"
|
|
92
84
|
"killall *": "deny"
|
|
93
85
|
"pkill *": "deny"
|
|
94
86
|
"nc *": "deny"
|
|
@@ -107,7 +99,6 @@ permission:
|
|
|
107
99
|
"useradd *": "deny"
|
|
108
100
|
"userdel *": "deny"
|
|
109
101
|
"iptables *": "deny"
|
|
110
|
-
external_directory: "deny"
|
|
111
102
|
doom_loop: "deny"
|
|
112
103
|
---
|
|
113
104
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"conversationId": "ade-fix-no-git-k396xs",
|
|
3
|
+
"projectPath": "/Users/oliverjaegle/projects/privat/codemcp/ade",
|
|
4
|
+
"epicId": "ade-4",
|
|
5
|
+
"phaseTasks": [
|
|
6
|
+
{
|
|
7
|
+
"phaseId": "reproduce",
|
|
8
|
+
"phaseName": "Reproduce",
|
|
9
|
+
"taskId": "ade-4.1"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"phaseId": "analyze",
|
|
13
|
+
"phaseName": "Analyze",
|
|
14
|
+
"taskId": "ade-4.2"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"phaseId": "fix",
|
|
18
|
+
"phaseName": "Fix",
|
|
19
|
+
"taskId": "ade-4.3"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"phaseId": "verify",
|
|
23
|
+
"phaseName": "Verify",
|
|
24
|
+
"taskId": "ade-4.4"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"phaseId": "finalize",
|
|
28
|
+
"phaseName": "Finalize",
|
|
29
|
+
"taskId": "ade-4.5"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"createdAt": "2026-03-19T09:33:07.866Z",
|
|
33
|
+
"updatedAt": "2026-03-19T09:33:07.866Z"
|
|
34
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Development Plan: ade (fix-no-git branch)
|
|
2
|
+
|
|
3
|
+
*Generated on 2026-03-19 by Vibe Feature MCP*
|
|
4
|
+
*Workflow: [bugfix](https://mrsimpson.github.io/responsible-vibe-mcp/workflows/bugfix)*
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
Fix `writeGitHooks` crashing with ENOENT when run in a non-git directory. Instead of throwing, it should detect the absence of `.git` and emit a warning, then skip gracefully (Option B).
|
|
8
|
+
|
|
9
|
+
## Reproduce
|
|
10
|
+
<!-- beads-phase-id: ade-4.1 -->
|
|
11
|
+
### Bug Description
|
|
12
|
+
When `ade setup` is run in a non-git directory and pre-commit hooks are configured, `writeGitHooks` tries to open `.git/hooks/pre-commit` for writing. Since `.git` doesn't exist, Node throws `ENOENT` and the whole process crashes.
|
|
13
|
+
|
|
14
|
+
### Error
|
|
15
|
+
```
|
|
16
|
+
Error: ENOENT: no such file or directory, open '/private/tmp/manual-test/.git/hooks/pre-commit'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Root Cause (already identified via code reading)
|
|
20
|
+
`packages/harnesses/src/util.ts` → `writeGitHooks()` writes directly to `.git/hooks/<phase>` without checking if `.git` exists.
|
|
21
|
+
|
|
22
|
+
### Tasks
|
|
23
|
+
|
|
24
|
+
## Analyze
|
|
25
|
+
<!-- beads-phase-id: ade-4.2 -->
|
|
26
|
+
### Phase Entrance Criteria
|
|
27
|
+
- [x] Bug is reproducible and root cause is identified.
|
|
28
|
+
- [x] Affected code location is known (`writeGitHooks` in `packages/harnesses/src/util.ts`).
|
|
29
|
+
|
|
30
|
+
### Analysis
|
|
31
|
+
- `writeGitHooks` is a shared utility called by every harness writer (universal, cursor, copilot, cline, claude-code, roo-code, opencode, kiro, windsurf).
|
|
32
|
+
- The fix belongs in `writeGitHooks` itself — one place, all callers benefit.
|
|
33
|
+
- Chosen approach: **Option B** — check for `.git` existence; if absent, emit a `clack.log.warn(...)` and return early. This keeps the user informed (backpressure) without crashing.
|
|
34
|
+
- `clack` is already used throughout the codebase; it is available in `util.ts`.
|
|
35
|
+
|
|
36
|
+
### Tasks
|
|
37
|
+
|
|
38
|
+
## Fix
|
|
39
|
+
<!-- beads-phase-id: ade-4.3 -->
|
|
40
|
+
### Phase Entrance Criteria
|
|
41
|
+
- [x] Root cause is confirmed to be in `writeGitHooks`.
|
|
42
|
+
- [x] Fix strategy (Option B: warn + skip) is agreed upon.
|
|
43
|
+
|
|
44
|
+
### Tasks
|
|
45
|
+
- [ ] In `writeGitHooks`: use `fs/promises.access` to check for `.git` directory existence before writing hooks.
|
|
46
|
+
- [ ] If `.git` is absent, call `clack.log.warn(...)` with a clear message and return.
|
|
47
|
+
- [ ] Also ensure `.git/hooks` directory is created if `.git` exists but `hooks` subdir is missing (use `mkdir` with `recursive: true`).
|
|
48
|
+
- [ ] Add / update unit tests in `install.spec.ts` or `util.spec.ts` to cover the non-git-repo case.
|
|
49
|
+
|
|
50
|
+
## Verify
|
|
51
|
+
<!-- beads-phase-id: ade-4.4 -->
|
|
52
|
+
### Phase Entrance Criteria
|
|
53
|
+
- [ ] `writeGitHooks` checks for `.git` existence and warns instead of crashing.
|
|
54
|
+
- [ ] `.git/hooks` dir is created when `.git` exists but `hooks` subdir is missing.
|
|
55
|
+
- [ ] Unit tests cover the new behavior.
|
|
56
|
+
|
|
57
|
+
### Tasks
|
|
58
|
+
- [ ] Run existing test suite and confirm no regressions.
|
|
59
|
+
- [ ] Manually verify the warning message is shown when run outside a git repo.
|
|
60
|
+
|
|
61
|
+
## Finalize
|
|
62
|
+
<!-- beads-phase-id: ade-4.5 -->
|
|
63
|
+
### Phase Entrance Criteria
|
|
64
|
+
- [ ] All tests pass.
|
|
65
|
+
- [ ] Warning behavior verified.
|
|
66
|
+
|
|
67
|
+
### Tasks
|
|
68
|
+
- [ ] 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.
|
|
69
|
+
|
|
70
|
+
## Key Decisions
|
|
71
|
+
- **Option B (warn + skip)** chosen over silent skip so users receive feedback that hooks were not installed — this preserves backpressure (user knows something was skipped).
|
|
72
|
+
- Fix is centralised in `writeGitHooks` utility; no changes needed in individual harness writers.
|
|
73
|
+
|
|
74
|
+
## Notes
|
|
75
|
+
- `clack` import must be added to `util.ts` if not already present.
|
|
76
|
+
- All harness writers already import `writeGitHooks` from `../util.js` — no call-site changes needed.
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# AGENTS
|
|
2
|
+
|
|
3
|
+
You are an AI assistant that helps users develop software features using the workflows server.
|
|
4
|
+
IMPORTANT: Call whats_next() after each user message to get phase-specific instructions and maintain the development workflow.
|
|
5
|
+
Each tool call returns a JSON response with an "instructions" field. Follow these instructions immediately after you receive them.
|
|
6
|
+
Use the development plan which you will retrieve via whats_next() to record important insights and decisions as per the structure of the plan.
|
|
7
|
+
Do not use your own task management tools.
|
|
8
|
+
|
|
9
|
+
## Autonomy
|
|
10
|
+
|
|
11
|
+
Universal harness limitation: `AGENTS.md` + `.mcp.json` provide documentation and server registration only; there is no enforceable harness-level permission schema here.
|
|
12
|
+
|
|
13
|
+
Treat this autonomy profile as documentation-only guidance for built-in/basic operations.
|
|
14
|
+
|
|
15
|
+
Profile: `sensible-defaults`
|
|
16
|
+
|
|
17
|
+
Built-in/basic capability guidance:
|
|
18
|
+
|
|
19
|
+
- `read`: allow
|
|
20
|
+
- `edit_write`: allow
|
|
21
|
+
- `search_list`: allow
|
|
22
|
+
- `bash_safe`: allow
|
|
23
|
+
- `bash_unsafe`: ask
|
|
24
|
+
- `web`: ask
|
|
25
|
+
- `task_agent`: allow
|
|
26
|
+
|
|
27
|
+
MCP permissions are not re-modeled by autonomy here; any MCP approvals must come from provisioning-aware consuming harnesses rather than the Universal writer.
|
package/config.lock.yaml
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
version: 1
|
|
2
|
-
generated_at: 2026-03-
|
|
2
|
+
generated_at: 2026-03-19T09:28:46.177Z
|
|
3
3
|
choices:
|
|
4
4
|
process: codemcp-workflows
|
|
5
5
|
practices:
|
|
6
6
|
- adr-nygard
|
|
7
|
+
- conventional-commits
|
|
7
8
|
autonomy: sensible-defaults
|
|
8
9
|
harnesses:
|
|
9
10
|
- universal
|
|
@@ -104,15 +105,38 @@ logical_config:
|
|
|
104
105
|
|
|
105
106
|
- Title should be a short noun phrase (e.g. "Use PostgreSQL for
|
|
106
107
|
persistence")
|
|
108
|
+
- name: conventional-commits
|
|
109
|
+
description: Conventional Commits specification for structured commit messages
|
|
110
|
+
body: |-
|
|
111
|
+
# Conventional Commits
|
|
112
|
+
|
|
113
|
+
## Format
|
|
114
|
+
```
|
|
115
|
+
<type>[optional scope]: <description>
|
|
116
|
+
|
|
117
|
+
[optional body]
|
|
118
|
+
|
|
119
|
+
[optional footer(s)]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Types
|
|
123
|
+
- `feat`: A new feature (correlates with MINOR in SemVer)
|
|
124
|
+
- `fix`: A bug fix (correlates with PATCH in SemVer)
|
|
125
|
+
- `docs`: Documentation only changes
|
|
126
|
+
- `style`: Changes that do not affect the meaning of the code
|
|
127
|
+
- `refactor`: A code change that neither fixes a bug nor adds a feature
|
|
128
|
+
- `perf`: A code change that improves performance
|
|
129
|
+
- `test`: Adding missing tests or correcting existing tests
|
|
130
|
+
- `chore`: Changes to the build process or auxiliary tools
|
|
131
|
+
|
|
132
|
+
## Rules
|
|
133
|
+
- Subject line must not exceed 72 characters
|
|
134
|
+
- Use imperative mood in the subject line ("add" not "added")
|
|
135
|
+
- Do not end the subject line with a period
|
|
136
|
+
- Separate subject from body with a blank line
|
|
137
|
+
- Use the body to explain what and why, not how
|
|
138
|
+
- `BREAKING CHANGE:` footer or `!` after type/scope for breaking changes
|
|
107
139
|
git_hooks: []
|
|
108
140
|
setup_notes: []
|
|
109
141
|
permission_policy:
|
|
110
142
|
profile: sensible-defaults
|
|
111
|
-
capabilities:
|
|
112
|
-
read: allow
|
|
113
|
-
edit_write: allow
|
|
114
|
-
search_list: allow
|
|
115
|
-
bash_safe: allow
|
|
116
|
-
bash_unsafe: ask
|
|
117
|
-
web: ask
|
|
118
|
-
task_agent: allow
|
package/config.yaml
CHANGED
package/package.json
CHANGED
|
@@ -11222,6 +11222,23 @@ var B = class {
|
|
|
11222
11222
|
}
|
|
11223
11223
|
}
|
|
11224
11224
|
};
|
|
11225
|
+
var kt = class extends B {
|
|
11226
|
+
get cursor() {
|
|
11227
|
+
return this.value ? 0 : 1;
|
|
11228
|
+
}
|
|
11229
|
+
get _value() {
|
|
11230
|
+
return this.cursor === 0;
|
|
11231
|
+
}
|
|
11232
|
+
constructor(e) {
|
|
11233
|
+
super(e, false), this.value = !!e.initialValue, this.on("userInput", () => {
|
|
11234
|
+
this.value = this._value;
|
|
11235
|
+
}), this.on("confirm", (s) => {
|
|
11236
|
+
this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s, this.state = "submit", this.close();
|
|
11237
|
+
}), this.on("cursor", () => {
|
|
11238
|
+
this.value = !this.value;
|
|
11239
|
+
});
|
|
11240
|
+
}
|
|
11241
|
+
};
|
|
11225
11242
|
var Lt = class extends B {
|
|
11226
11243
|
options;
|
|
11227
11244
|
cursor = 0;
|
|
@@ -11556,6 +11573,33 @@ var X2 = ({ cursor: e, options: r2, style: s, output: i = process.stdout, maxIte
|
|
|
11556
11573
|
for (const A3 of d3) for (const b of A3) C2.push(b);
|
|
11557
11574
|
return $2 && C2.push(c), C2;
|
|
11558
11575
|
};
|
|
11576
|
+
var Rt = (e) => {
|
|
11577
|
+
const r2 = e.active ?? "Yes", s = e.inactive ?? "No";
|
|
11578
|
+
return new kt({ active: r2, inactive: s, signal: e.signal, input: e.input, output: e.output, initialValue: e.initialValue ?? true, render() {
|
|
11579
|
+
const i = e.withGuide ?? _.withGuide, a = `${i ? `${t("gray", h)}
|
|
11580
|
+
` : ""}${W2(this.state)} ${e.message}
|
|
11581
|
+
`, o2 = this.value ? r2 : s;
|
|
11582
|
+
switch (this.state) {
|
|
11583
|
+
case "submit": {
|
|
11584
|
+
const u2 = i ? `${t("gray", h)} ` : "";
|
|
11585
|
+
return `${a}${u2}${t("dim", o2)}`;
|
|
11586
|
+
}
|
|
11587
|
+
case "cancel": {
|
|
11588
|
+
const u2 = i ? `${t("gray", h)} ` : "";
|
|
11589
|
+
return `${a}${u2}${t(["strikethrough", "dim"], o2)}${i ? `
|
|
11590
|
+
${t("gray", h)}` : ""}`;
|
|
11591
|
+
}
|
|
11592
|
+
default: {
|
|
11593
|
+
const u2 = i ? `${t("cyan", h)} ` : "", l = i ? t("cyan", x2) : "";
|
|
11594
|
+
return `${a}${u2}${this.value ? `${t("green", z2)} ${r2}` : `${t("dim", H2)} ${t("dim", r2)}`}${e.vertical ? i ? `
|
|
11595
|
+
${t("cyan", h)} ` : `
|
|
11596
|
+
` : ` ${t("dim", "/")} `}${this.value ? `${t("dim", H2)} ${t("dim", s)}` : `${t("green", z2)} ${s}`}
|
|
11597
|
+
${l}
|
|
11598
|
+
`;
|
|
11599
|
+
}
|
|
11600
|
+
}
|
|
11601
|
+
} }).prompt();
|
|
11602
|
+
};
|
|
11559
11603
|
var R2 = { message: (e = [], { symbol: r2 = t("gray", h), secondarySymbol: s = t("gray", h), output: i = process.stdout, spacing: a = 1, withGuide: o2 } = {}) => {
|
|
11560
11604
|
const u2 = [], l = o2 ?? _.withGuide, n = l ? s : "", c = l ? `${r2} ` : "", p2 = l ? `${s} ` : "";
|
|
11561
11605
|
for (let g2 = 0; g2 < a; g2++) u2.push(n);
|
|
@@ -21824,7 +21868,7 @@ async function installSkills(skills, projectRoot) {
|
|
|
21824
21868
|
}
|
|
21825
21869
|
|
|
21826
21870
|
// ../harnesses/dist/util.js
|
|
21827
|
-
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
21871
|
+
import { access as access2, mkdir as mkdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
21828
21872
|
import { dirname as dirname5, join as join9 } from "path";
|
|
21829
21873
|
async function readJsonOrEmpty(path2) {
|
|
21830
21874
|
try {
|
|
@@ -21891,10 +21935,19 @@ async function writeAgentMd(config, opts) {
|
|
|
21891
21935
|
await writeFile4(opts.path, content, "utf-8");
|
|
21892
21936
|
}
|
|
21893
21937
|
async function writeGitHooks(hooks, projectRoot) {
|
|
21894
|
-
if (!hooks)
|
|
21938
|
+
if (!hooks || hooks.length === 0)
|
|
21939
|
+
return;
|
|
21940
|
+
const gitDir = join9(projectRoot, ".git");
|
|
21941
|
+
try {
|
|
21942
|
+
await access2(gitDir);
|
|
21943
|
+
} catch {
|
|
21944
|
+
R2.warn("Git hooks were configured but could not be installed: the project is not a git repository.\nRun `git init` and re-run setup to install the hooks.");
|
|
21895
21945
|
return;
|
|
21946
|
+
}
|
|
21947
|
+
const hooksDir = join9(gitDir, "hooks");
|
|
21948
|
+
await mkdir3(hooksDir, { recursive: true });
|
|
21896
21949
|
for (const hook of hooks) {
|
|
21897
|
-
const hookPath = join9(
|
|
21950
|
+
const hookPath = join9(hooksDir, hook.phase);
|
|
21898
21951
|
await writeFile4(hookPath, hook.script, { mode: 493 });
|
|
21899
21952
|
}
|
|
21900
21953
|
}
|
|
@@ -22434,7 +22487,27 @@ function getKiroAgentMcpServers(servers) {
|
|
|
22434
22487
|
|
|
22435
22488
|
// ../harnesses/dist/writers/opencode.js
|
|
22436
22489
|
import { join as join18 } from "path";
|
|
22490
|
+
var APPLICABLE_TO_ALL = {
|
|
22491
|
+
read: {
|
|
22492
|
+
"*": "allow",
|
|
22493
|
+
"*.env": "deny",
|
|
22494
|
+
"*.env.*": "deny",
|
|
22495
|
+
"*.env.example": "allow"
|
|
22496
|
+
},
|
|
22497
|
+
skill: "deny",
|
|
22498
|
+
//we're using an own skills-mcp
|
|
22499
|
+
todoread: "deny",
|
|
22500
|
+
//no agent-proprieatry todo tools
|
|
22501
|
+
todowrite: "deny",
|
|
22502
|
+
task: "deny",
|
|
22503
|
+
lsp: "allow",
|
|
22504
|
+
glob: "allow",
|
|
22505
|
+
grep: "allow",
|
|
22506
|
+
list: "allow",
|
|
22507
|
+
external_directory: "ask"
|
|
22508
|
+
};
|
|
22437
22509
|
var RIGID_RULES = {
|
|
22510
|
+
...APPLICABLE_TO_ALL,
|
|
22438
22511
|
"*": "ask",
|
|
22439
22512
|
webfetch: "ask",
|
|
22440
22513
|
websearch: "ask",
|
|
@@ -22443,31 +22516,17 @@ var RIGID_RULES = {
|
|
|
22443
22516
|
doom_loop: "deny"
|
|
22444
22517
|
};
|
|
22445
22518
|
var SENSIBLE_DEFAULTS_RULES = {
|
|
22446
|
-
|
|
22447
|
-
"*": "allow",
|
|
22448
|
-
"*.env": "deny",
|
|
22449
|
-
"*.env.*": "deny",
|
|
22450
|
-
"*.env.example": "allow"
|
|
22451
|
-
},
|
|
22519
|
+
...APPLICABLE_TO_ALL,
|
|
22452
22520
|
edit: "allow",
|
|
22453
|
-
glob: "allow",
|
|
22454
|
-
grep: "allow",
|
|
22455
|
-
list: "allow",
|
|
22456
|
-
lsp: "allow",
|
|
22457
|
-
task: "allow",
|
|
22458
|
-
todoread: "deny",
|
|
22459
|
-
todowrite: "deny",
|
|
22460
|
-
skill: "deny",
|
|
22461
22521
|
webfetch: "ask",
|
|
22462
22522
|
websearch: "ask",
|
|
22463
22523
|
codesearch: "ask",
|
|
22464
22524
|
bash: {
|
|
22465
|
-
"*": "
|
|
22525
|
+
"*": "ask",
|
|
22466
22526
|
"grep *": "allow",
|
|
22467
22527
|
"rg *": "allow",
|
|
22468
22528
|
"find *": "allow",
|
|
22469
22529
|
"fd *": "allow",
|
|
22470
|
-
ls: "allow",
|
|
22471
22530
|
"ls *": "allow",
|
|
22472
22531
|
"cat *": "allow",
|
|
22473
22532
|
"head *": "allow",
|
|
@@ -22502,14 +22561,7 @@ var SENSIBLE_DEFAULTS_RULES = {
|
|
|
22502
22561
|
"yq *": "allow",
|
|
22503
22562
|
"mkdir *": "allow",
|
|
22504
22563
|
"touch *": "allow",
|
|
22505
|
-
"
|
|
22506
|
-
"mv *": "ask",
|
|
22507
|
-
"ln *": "ask",
|
|
22508
|
-
"npm *": "ask",
|
|
22509
|
-
"node *": "ask",
|
|
22510
|
-
"pip *": "ask",
|
|
22511
|
-
"python *": "ask",
|
|
22512
|
-
"python3 *": "ask",
|
|
22564
|
+
"kill *": "ask",
|
|
22513
22565
|
"rm *": "deny",
|
|
22514
22566
|
"rmdir *": "deny",
|
|
22515
22567
|
"curl *": "deny",
|
|
@@ -22530,7 +22582,6 @@ var SENSIBLE_DEFAULTS_RULES = {
|
|
|
22530
22582
|
"mkfs *": "deny",
|
|
22531
22583
|
"mount *": "deny",
|
|
22532
22584
|
"umount *": "deny",
|
|
22533
|
-
"kill *": "deny",
|
|
22534
22585
|
"killall *": "deny",
|
|
22535
22586
|
"pkill *": "deny",
|
|
22536
22587
|
"nc *": "deny",
|
|
@@ -22550,15 +22601,14 @@ var SENSIBLE_DEFAULTS_RULES = {
|
|
|
22550
22601
|
"userdel *": "deny",
|
|
22551
22602
|
"iptables *": "deny"
|
|
22552
22603
|
},
|
|
22553
|
-
external_directory: "deny",
|
|
22554
22604
|
doom_loop: "deny"
|
|
22555
22605
|
};
|
|
22556
22606
|
var MAX_AUTONOMY_RULES = {
|
|
22607
|
+
...APPLICABLE_TO_ALL,
|
|
22557
22608
|
"*": "allow",
|
|
22558
22609
|
webfetch: "ask",
|
|
22559
22610
|
websearch: "ask",
|
|
22560
22611
|
codesearch: "ask",
|
|
22561
|
-
external_directory: "deny",
|
|
22562
22612
|
doom_loop: "deny"
|
|
22563
22613
|
};
|
|
22564
22614
|
function getPermissionRules(profile) {
|
|
@@ -22746,7 +22796,23 @@ async function runSetup(projectRoot, catalog) {
|
|
|
22746
22796
|
To use the latest defaults, remove .ade/skills/ and re-run setup.`
|
|
22747
22797
|
);
|
|
22748
22798
|
}
|
|
22749
|
-
|
|
22799
|
+
if (logicalConfig.skills.length > 0) {
|
|
22800
|
+
const confirmInstall = await Rt({
|
|
22801
|
+
message: `Install ${logicalConfig.skills.length} skill(s) now?`,
|
|
22802
|
+
initialValue: true
|
|
22803
|
+
});
|
|
22804
|
+
if (typeof confirmInstall === "symbol") {
|
|
22805
|
+
Nt("Setup cancelled.");
|
|
22806
|
+
return;
|
|
22807
|
+
}
|
|
22808
|
+
if (confirmInstall) {
|
|
22809
|
+
await installSkills(logicalConfig.skills, projectRoot);
|
|
22810
|
+
} else {
|
|
22811
|
+
R2.info(
|
|
22812
|
+
"Skills not installed. Run manually when ready:\n npx @codemcp/skills experimental_install"
|
|
22813
|
+
);
|
|
22814
|
+
}
|
|
22815
|
+
}
|
|
22750
22816
|
if (logicalConfig.knowledge_sources.length > 0) {
|
|
22751
22817
|
R2.info(
|
|
22752
22818
|
"Knowledge sources selected. Initialize them separately:\n npx @codemcp/knowledge init"
|
|
@@ -22831,7 +22897,23 @@ async function runInstall(projectRoot, harnessIds) {
|
|
|
22831
22897
|
To use the latest defaults, remove .ade/skills/ and re-run install.`
|
|
22832
22898
|
);
|
|
22833
22899
|
}
|
|
22834
|
-
|
|
22900
|
+
if (logicalConfig.skills.length > 0) {
|
|
22901
|
+
const confirmInstall = await Rt({
|
|
22902
|
+
message: `Install ${logicalConfig.skills.length} skill(s) now?`,
|
|
22903
|
+
initialValue: true
|
|
22904
|
+
});
|
|
22905
|
+
if (typeof confirmInstall === "symbol") {
|
|
22906
|
+
Nt("Install cancelled.");
|
|
22907
|
+
return;
|
|
22908
|
+
}
|
|
22909
|
+
if (confirmInstall) {
|
|
22910
|
+
await installSkills(logicalConfig.skills, projectRoot);
|
|
22911
|
+
} else {
|
|
22912
|
+
R2.info(
|
|
22913
|
+
"Skills not installed. Run manually when ready:\n npx @codemcp/skills experimental_install"
|
|
22914
|
+
);
|
|
22915
|
+
}
|
|
22916
|
+
}
|
|
22835
22917
|
if (logicalConfig.knowledge_sources.length > 0) {
|
|
22836
22918
|
R2.info(
|
|
22837
22919
|
"Knowledge sources configured. Initialize them separately:\n npx @codemcp/knowledge init"
|
|
@@ -8,9 +8,15 @@ vi.mock("@clack/prompts", () => ({
|
|
|
8
8
|
outro: vi.fn(),
|
|
9
9
|
select: vi.fn(),
|
|
10
10
|
multiselect: vi.fn(),
|
|
11
|
-
confirm: vi.fn(),
|
|
11
|
+
confirm: vi.fn().mockResolvedValue(true),
|
|
12
12
|
isCancel: vi.fn().mockReturnValue(false),
|
|
13
13
|
cancel: vi.fn(),
|
|
14
|
+
log: {
|
|
15
|
+
info: vi.fn(),
|
|
16
|
+
warn: vi.fn(),
|
|
17
|
+
error: vi.fn(),
|
|
18
|
+
success: vi.fn()
|
|
19
|
+
},
|
|
14
20
|
spinner: vi.fn().mockReturnValue({ start: vi.fn(), stop: vi.fn() })
|
|
15
21
|
}));
|
|
16
22
|
|
|
@@ -51,7 +51,25 @@ export async function runInstall(
|
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
if (logicalConfig.skills.length > 0) {
|
|
55
|
+
const confirmInstall = await clack.confirm({
|
|
56
|
+
message: `Install ${logicalConfig.skills.length} skill(s) now?`,
|
|
57
|
+
initialValue: true
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (typeof confirmInstall === "symbol") {
|
|
61
|
+
clack.cancel("Install cancelled.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (confirmInstall) {
|
|
66
|
+
await installSkills(logicalConfig.skills, projectRoot);
|
|
67
|
+
} else {
|
|
68
|
+
clack.log.info(
|
|
69
|
+
"Skills not installed. Run manually when ready:\n npx @codemcp/skills experimental_install"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
55
73
|
|
|
56
74
|
if (logicalConfig.knowledge_sources.length > 0) {
|
|
57
75
|
clack.log.info(
|
|
@@ -172,7 +172,25 @@ export async function runSetup(
|
|
|
172
172
|
);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
if (logicalConfig.skills.length > 0) {
|
|
176
|
+
const confirmInstall = await clack.confirm({
|
|
177
|
+
message: `Install ${logicalConfig.skills.length} skill(s) now?`,
|
|
178
|
+
initialValue: true
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (typeof confirmInstall === "symbol") {
|
|
182
|
+
clack.cancel("Setup cancelled.");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (confirmInstall) {
|
|
187
|
+
await installSkills(logicalConfig.skills, projectRoot);
|
|
188
|
+
} else {
|
|
189
|
+
clack.log.info(
|
|
190
|
+
"Skills not installed. Run manually when ready:\n npx @codemcp/skills experimental_install"
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
176
194
|
|
|
177
195
|
if (logicalConfig.knowledge_sources.length > 0) {
|
|
178
196
|
clack.log.info(
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"typecheck": "tsc --noEmit"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@clack/prompts": "^1.1.0",
|
|
31
32
|
"@codemcp/ade-core": "workspace:*",
|
|
32
33
|
"@codemcp/skills": "^2.3.0"
|
|
33
34
|
},
|
|
@@ -39,5 +40,5 @@
|
|
|
39
40
|
"typescript": "catalog:",
|
|
40
41
|
"vitest": "catalog:"
|
|
41
42
|
},
|
|
42
|
-
"version": "0.
|
|
43
|
+
"version": "0.3.0"
|
|
43
44
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { mkdtemp, rm, mkdir, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import * as clack from "@clack/prompts";
|
|
6
|
+
import type { GitHook } from "@codemcp/ade-core";
|
|
7
|
+
import { writeGitHooks } from "./util.js";
|
|
8
|
+
|
|
9
|
+
describe("writeGitHooks", () => {
|
|
10
|
+
let dir: string;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
dir = await mkdtemp(join(tmpdir(), "ade-util-git-hooks-"));
|
|
14
|
+
vi.spyOn(clack.log, "warn").mockImplementation(() => undefined);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await rm(dir, { recursive: true, force: true });
|
|
19
|
+
vi.restoreAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("is a no-op when hooks is undefined", async () => {
|
|
23
|
+
await expect(writeGitHooks(undefined, dir)).resolves.toBeUndefined();
|
|
24
|
+
expect(clack.log.warn).not.toHaveBeenCalled();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("is a no-op when hooks array is empty", async () => {
|
|
28
|
+
await expect(writeGitHooks([], dir)).resolves.toBeUndefined();
|
|
29
|
+
expect(clack.log.warn).not.toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("warns and skips gracefully when .git directory does not exist", async () => {
|
|
33
|
+
const hooks: GitHook[] = [
|
|
34
|
+
{ phase: "pre-commit", script: "#!/bin/sh\nnpx lint-staged" }
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
await expect(writeGitHooks(hooks, dir)).resolves.toBeUndefined();
|
|
38
|
+
|
|
39
|
+
expect(clack.log.warn).toHaveBeenCalledOnce();
|
|
40
|
+
expect(clack.log.warn).toHaveBeenCalledWith(
|
|
41
|
+
expect.stringContaining("not a git repository")
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// No .git/hooks directory should have been created
|
|
45
|
+
await expect(stat(join(dir, ".git"))).rejects.toThrow();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("writes hook files when .git exists", async () => {
|
|
49
|
+
await mkdir(join(dir, ".git"), { recursive: true });
|
|
50
|
+
|
|
51
|
+
const script = "#!/bin/sh\nnpx lint-staged\n";
|
|
52
|
+
const hooks: GitHook[] = [{ phase: "pre-commit", script }];
|
|
53
|
+
|
|
54
|
+
await writeGitHooks(hooks, dir);
|
|
55
|
+
|
|
56
|
+
expect(clack.log.warn).not.toHaveBeenCalled();
|
|
57
|
+
|
|
58
|
+
const written = await readFile(
|
|
59
|
+
join(dir, ".git", "hooks", "pre-commit"),
|
|
60
|
+
"utf-8"
|
|
61
|
+
);
|
|
62
|
+
expect(written).toBe(script);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("creates .git/hooks directory if it does not exist yet", async () => {
|
|
66
|
+
// .git exists but no hooks subdir
|
|
67
|
+
await mkdir(join(dir, ".git"), { recursive: true });
|
|
68
|
+
|
|
69
|
+
const hooks: GitHook[] = [{ phase: "pre-commit", script: "#!/bin/sh\n" }];
|
|
70
|
+
await writeGitHooks(hooks, dir);
|
|
71
|
+
|
|
72
|
+
const hookStat = await stat(join(dir, ".git", "hooks"));
|
|
73
|
+
expect(hookStat.isDirectory()).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("writes multiple hooks", async () => {
|
|
77
|
+
await mkdir(join(dir, ".git"), { recursive: true });
|
|
78
|
+
|
|
79
|
+
const hooks: GitHook[] = [
|
|
80
|
+
{ phase: "pre-commit", script: "#!/bin/sh\necho pre-commit\n" },
|
|
81
|
+
{ phase: "pre-push", script: "#!/bin/sh\necho pre-push\n" }
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
await writeGitHooks(hooks, dir);
|
|
85
|
+
|
|
86
|
+
const preCommit = await readFile(
|
|
87
|
+
join(dir, ".git", "hooks", "pre-commit"),
|
|
88
|
+
"utf-8"
|
|
89
|
+
);
|
|
90
|
+
const prePush = await readFile(
|
|
91
|
+
join(dir, ".git", "hooks", "pre-push"),
|
|
92
|
+
"utf-8"
|
|
93
|
+
);
|
|
94
|
+
expect(preCommit).toBe(hooks[0].script);
|
|
95
|
+
expect(prePush).toBe(hooks[1].script);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
+
import * as clack from "@clack/prompts";
|
|
3
4
|
import type { GitHook, LogicalConfig, McpServerEntry } from "@codemcp/ade-core";
|
|
4
5
|
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
@@ -169,15 +170,31 @@ export async function writeAgentMd(
|
|
|
169
170
|
/**
|
|
170
171
|
* Write git hook scripts to `.git/hooks/<phase>`.
|
|
171
172
|
* Files are created with executable permissions (0o755).
|
|
172
|
-
* No-op when the hooks array is empty.
|
|
173
|
+
* No-op when the hooks array is empty or undefined.
|
|
174
|
+
* Emits a warning and skips gracefully when the project root is not a git repository.
|
|
173
175
|
*/
|
|
174
176
|
export async function writeGitHooks(
|
|
175
177
|
hooks: GitHook[] | undefined,
|
|
176
178
|
projectRoot: string
|
|
177
179
|
): Promise<void> {
|
|
178
|
-
if (!hooks) return;
|
|
180
|
+
if (!hooks || hooks.length === 0) return;
|
|
181
|
+
|
|
182
|
+
const gitDir = join(projectRoot, ".git");
|
|
183
|
+
try {
|
|
184
|
+
await access(gitDir);
|
|
185
|
+
} catch {
|
|
186
|
+
clack.log.warn(
|
|
187
|
+
"Git hooks were configured but could not be installed: the project is not a git repository.\n" +
|
|
188
|
+
"Run `git init` and re-run setup to install the hooks."
|
|
189
|
+
);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const hooksDir = join(gitDir, "hooks");
|
|
194
|
+
await mkdir(hooksDir, { recursive: true });
|
|
195
|
+
|
|
179
196
|
for (const hook of hooks) {
|
|
180
|
-
const hookPath = join(
|
|
197
|
+
const hookPath = join(hooksDir, hook.phase);
|
|
181
198
|
await writeFile(hookPath, hook.script, { mode: 0o755 });
|
|
182
199
|
}
|
|
183
200
|
}
|
|
@@ -102,17 +102,16 @@ describe("opencodeWriter", () => {
|
|
|
102
102
|
expect(defaultsAgent).toContain('grep: "allow"');
|
|
103
103
|
expect(defaultsAgent).toContain('list: "allow"');
|
|
104
104
|
expect(defaultsAgent).toContain('lsp: "allow"');
|
|
105
|
-
expect(defaultsAgent).toContain('task: "
|
|
105
|
+
expect(defaultsAgent).toContain('task: "deny"');
|
|
106
106
|
expect(defaultsAgent).toContain('skill: "deny"');
|
|
107
107
|
expect(defaultsAgent).toContain('todoread: "deny"');
|
|
108
108
|
expect(defaultsAgent).toContain('todowrite: "deny"');
|
|
109
109
|
expect(defaultsAgent).toContain('webfetch: "ask"');
|
|
110
110
|
expect(defaultsAgent).toContain('websearch: "ask"');
|
|
111
111
|
expect(defaultsAgent).toContain('codesearch: "ask"');
|
|
112
|
-
expect(defaultsAgent).toContain('external_directory: "
|
|
112
|
+
expect(defaultsAgent).toContain('external_directory: "ask"');
|
|
113
113
|
expect(defaultsAgent).toContain('doom_loop: "deny"');
|
|
114
114
|
expect(defaultsAgent).toContain('"grep *": "allow"');
|
|
115
|
-
expect(defaultsAgent).toContain('"cp *": "ask"');
|
|
116
115
|
expect(defaultsAgent).toContain('"rm *": "deny"');
|
|
117
116
|
expect(defaultsFrontmatter.permission).toMatchObject({
|
|
118
117
|
edit: "allow",
|
|
@@ -120,21 +119,20 @@ describe("opencodeWriter", () => {
|
|
|
120
119
|
grep: "allow",
|
|
121
120
|
list: "allow",
|
|
122
121
|
lsp: "allow",
|
|
123
|
-
task: "
|
|
122
|
+
task: "deny",
|
|
124
123
|
skill: "deny",
|
|
125
124
|
todoread: "deny",
|
|
126
125
|
todowrite: "deny",
|
|
127
126
|
webfetch: "ask",
|
|
128
127
|
websearch: "ask",
|
|
129
128
|
codesearch: "ask",
|
|
130
|
-
external_directory: "
|
|
129
|
+
external_directory: "ask",
|
|
131
130
|
doom_loop: "deny"
|
|
132
131
|
});
|
|
133
132
|
const defaultsPermission = defaultsFrontmatter.permission as {
|
|
134
133
|
bash: Record<string, string>;
|
|
135
134
|
};
|
|
136
135
|
expect(defaultsPermission.bash["grep *"]).toBe("allow");
|
|
137
|
-
expect(defaultsPermission.bash["cp *"]).toBe("ask");
|
|
138
136
|
expect(defaultsPermission.bash["rm *"]).toBe("deny");
|
|
139
137
|
|
|
140
138
|
expect(maxAgent).toContain('"*": "allow"');
|
|
@@ -12,7 +12,26 @@ import { getAutonomyProfile } from "../permission-policy.js";
|
|
|
12
12
|
type PermissionDecision = "ask" | "allow" | "deny";
|
|
13
13
|
type PermissionRule = PermissionDecision | Record<string, PermissionDecision>;
|
|
14
14
|
|
|
15
|
+
const APPLICABLE_TO_ALL: Record<string, PermissionRule> = {
|
|
16
|
+
read: {
|
|
17
|
+
"*": "allow",
|
|
18
|
+
"*.env": "deny",
|
|
19
|
+
"*.env.*": "deny",
|
|
20
|
+
"*.env.example": "allow"
|
|
21
|
+
},
|
|
22
|
+
skill: "deny", //we're using an own skills-mcp
|
|
23
|
+
todoread: "deny", //no agent-proprieatry todo tools
|
|
24
|
+
todowrite: "deny",
|
|
25
|
+
task: "deny",
|
|
26
|
+
lsp: "allow",
|
|
27
|
+
glob: "allow",
|
|
28
|
+
grep: "allow",
|
|
29
|
+
list: "allow",
|
|
30
|
+
external_directory: "ask"
|
|
31
|
+
};
|
|
32
|
+
|
|
15
33
|
const RIGID_RULES: Record<string, PermissionRule> = {
|
|
34
|
+
...APPLICABLE_TO_ALL,
|
|
16
35
|
"*": "ask",
|
|
17
36
|
webfetch: "ask",
|
|
18
37
|
websearch: "ask",
|
|
@@ -22,31 +41,17 @@ const RIGID_RULES: Record<string, PermissionRule> = {
|
|
|
22
41
|
};
|
|
23
42
|
|
|
24
43
|
const SENSIBLE_DEFAULTS_RULES: Record<string, PermissionRule> = {
|
|
25
|
-
|
|
26
|
-
"*": "allow",
|
|
27
|
-
"*.env": "deny",
|
|
28
|
-
"*.env.*": "deny",
|
|
29
|
-
"*.env.example": "allow"
|
|
30
|
-
},
|
|
44
|
+
...APPLICABLE_TO_ALL,
|
|
31
45
|
edit: "allow",
|
|
32
|
-
glob: "allow",
|
|
33
|
-
grep: "allow",
|
|
34
|
-
list: "allow",
|
|
35
|
-
lsp: "allow",
|
|
36
|
-
task: "allow",
|
|
37
|
-
todoread: "deny",
|
|
38
|
-
todowrite: "deny",
|
|
39
|
-
skill: "deny",
|
|
40
46
|
webfetch: "ask",
|
|
41
47
|
websearch: "ask",
|
|
42
48
|
codesearch: "ask",
|
|
43
49
|
bash: {
|
|
44
|
-
"*": "
|
|
50
|
+
"*": "ask",
|
|
45
51
|
"grep *": "allow",
|
|
46
52
|
"rg *": "allow",
|
|
47
53
|
"find *": "allow",
|
|
48
54
|
"fd *": "allow",
|
|
49
|
-
ls: "allow",
|
|
50
55
|
"ls *": "allow",
|
|
51
56
|
"cat *": "allow",
|
|
52
57
|
"head *": "allow",
|
|
@@ -81,14 +86,7 @@ const SENSIBLE_DEFAULTS_RULES: Record<string, PermissionRule> = {
|
|
|
81
86
|
"yq *": "allow",
|
|
82
87
|
"mkdir *": "allow",
|
|
83
88
|
"touch *": "allow",
|
|
84
|
-
"
|
|
85
|
-
"mv *": "ask",
|
|
86
|
-
"ln *": "ask",
|
|
87
|
-
"npm *": "ask",
|
|
88
|
-
"node *": "ask",
|
|
89
|
-
"pip *": "ask",
|
|
90
|
-
"python *": "ask",
|
|
91
|
-
"python3 *": "ask",
|
|
89
|
+
"kill *": "ask",
|
|
92
90
|
"rm *": "deny",
|
|
93
91
|
"rmdir *": "deny",
|
|
94
92
|
"curl *": "deny",
|
|
@@ -109,7 +107,6 @@ const SENSIBLE_DEFAULTS_RULES: Record<string, PermissionRule> = {
|
|
|
109
107
|
"mkfs *": "deny",
|
|
110
108
|
"mount *": "deny",
|
|
111
109
|
"umount *": "deny",
|
|
112
|
-
"kill *": "deny",
|
|
113
110
|
"killall *": "deny",
|
|
114
111
|
"pkill *": "deny",
|
|
115
112
|
"nc *": "deny",
|
|
@@ -129,16 +126,15 @@ const SENSIBLE_DEFAULTS_RULES: Record<string, PermissionRule> = {
|
|
|
129
126
|
"userdel *": "deny",
|
|
130
127
|
"iptables *": "deny"
|
|
131
128
|
},
|
|
132
|
-
external_directory: "deny",
|
|
133
129
|
doom_loop: "deny"
|
|
134
130
|
};
|
|
135
131
|
|
|
136
132
|
const MAX_AUTONOMY_RULES: Record<string, PermissionRule> = {
|
|
133
|
+
...APPLICABLE_TO_ALL,
|
|
137
134
|
"*": "allow",
|
|
138
135
|
webfetch: "ask",
|
|
139
136
|
websearch: "ask",
|
|
140
137
|
codesearch: "ask",
|
|
141
|
-
external_directory: "deny",
|
|
142
138
|
doom_loop: "deny"
|
|
143
139
|
};
|
|
144
140
|
|
package/skills-lock.json
CHANGED
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
"adr-nygard": {
|
|
5
5
|
"source": "/Users/oliverjaegle/projects/privat/codemcp/ade/.ade/skills/adr-nygard",
|
|
6
6
|
"sourceType": "local",
|
|
7
|
-
"computedHash": "
|
|
7
|
+
"computedHash": "d62ee4c175a38f91d98a0536f863396ceb48c7de4275787520b07870705b4367"
|
|
8
8
|
},
|
|
9
9
|
"commit": {
|
|
10
10
|
"source": "mrsimpson/skills-coding",
|
|
11
11
|
"sourceType": "github",
|
|
12
12
|
"computedHash": "fc628c7d577d2d9cf3cb0a917d3c5e2e35b460fdc62c353595b7472b6f1c6548"
|
|
13
13
|
},
|
|
14
|
+
"conventional-commits": {
|
|
15
|
+
"source": "/Users/oliverjaegle/projects/privat/codemcp/ade/.ade/skills/conventional-commits",
|
|
16
|
+
"sourceType": "local",
|
|
17
|
+
"computedHash": "49dd439dbd856412264fa345eaa9bbf2526095cb16457ffc7fb66a9d2f4d5f9d"
|
|
18
|
+
},
|
|
14
19
|
"tdd": {
|
|
15
20
|
"source": "mrsimpson/skills-coding",
|
|
16
21
|
"sourceType": "github",
|