uctm 1.3.2 → 1.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.
@@ -28,7 +28,7 @@ elif [ -f "Cargo.toml" ]; then
28
28
  elif [ -f "go.mod" ]; then
29
29
  go build ./... 2>&1
30
30
  elif [ -f "pyproject.toml" ] || [ -f "setup.py" ]; then
31
- python -m py_compile $(find . -name "*.py" -not -path "*/venv/*" | head -20) 2>&1
31
+ python -m py_compile $(find . -maxdepth 3 -name "*.py" -not -path "*/venv/*" 2>/dev/null) 2>&1
32
32
  elif [ -f "Makefile" ]; then
33
33
  make build 2>&1 || make 2>&1
34
34
  fi
@@ -68,22 +68,20 @@ works/{WORK_ID}/
68
68
 
69
69
  ## § 4. File System Discovery Scripts
70
70
 
71
- ```bash
71
+ ```
72
72
  # Find latest WORK with incomplete TASKs
73
- for dir in $(ls -d works/WORK-* 2>/dev/null | sort -V -r); do
74
- WORK_ID=$(basename $dir)
75
- TOTAL=$(ls $dir/TASK-*.md 2>/dev/null | grep -v result | wc -l)
76
- DONE=$(ls $dir/TASK-*_result.md 2>/dev/null | wc -l)
77
- [ "$DONE" -lt "$TOTAL" ] && echo "$WORK_ID" && break
78
- done
73
+ # Use Glob tool: pattern "works/WORK-*/" list all WORK directories (sorted)
74
+ # For each WORK (descending), compare:
75
+ # Glob "works/WORK-NN/TASK-*.md" (exclude *_result.md, *_progress.md) TOTAL
76
+ # Glob "works/WORK-NN/TASK-*_result.md" DONE
77
+ # First WORK where DONE < TOTAL is the active WORK
79
78
 
80
79
  # List all WORKs
81
- ls -d works/WORK-* 2>/dev/null | sort -V
80
+ # Use Glob tool: pattern "works/WORK-*/"
82
81
 
83
82
  # TASK completion status
84
- TOTAL=$(ls works/${WORK_ID}/TASK-*.md 2>/dev/null | grep -v result | wc -l)
85
- DONE=$(ls works/${WORK_ID}/TASK-*_result.md 2>/dev/null | wc -l)
86
- echo "$DONE / $TOTAL"
83
+ # TOTAL = count of Glob "works/${WORK_ID}/TASK-??.md"
84
+ # DONE = count of Glob "works/${WORK_ID}/TASK-*_result.md"
87
85
  ```
88
86
 
89
87
  ---
@@ -164,28 +162,17 @@ Rules:
164
162
 
165
163
  ## § 10. Callback Transmission Template
166
164
 
165
+ → **Bash command rules: see § 13** — each step below is a separate tool call.
166
+
167
167
  Replace `{CallbackType}` with the actual key name (e.g., `ProgressCallback`, `TaskCallback`).
168
168
 
169
+ **Step 1.** Use `Grep` tool to find `{CallbackType}:` line in CLAUDE.md. If not found, skip callback entirely.
170
+
171
+ **Step 2.** Use `Grep` tool to find `CallbackToken:` line in CLAUDE.md (optional).
172
+
173
+ **Step 3.** Send callback with a single `curl` command:
169
174
  ```bash
170
- CALLBACK_URL=$(grep "^{CallbackType}:" CLAUDE.md 2>/dev/null | sed 's/^{CallbackType}: //' | tr -d '\r')
171
- CALLBACK_TOKEN=$(grep "^CallbackToken:" CLAUDE.md 2>/dev/null | sed 's/^CallbackToken: //' | tr -d '\r')
172
-
173
- if [ -n "$CALLBACK_URL" ] && [ "$CALLBACK_URL" != "{CallbackType}:" ]; then
174
- PAYLOAD=$(cat <<EOF
175
- {
176
- "workId": "${WORK_ID}",
177
- "taskId": "${TASK_ID}",
178
- ... agent-specific fields ...
179
- }
180
- EOF
181
- )
182
- AUTH_HEADER=""
183
- [ -n "$CALLBACK_TOKEN" ] && AUTH_HEADER="-H \"X-Runner-Api-Key: ${CALLBACK_TOKEN}\""
184
- curl -s -X POST "$CALLBACK_URL" \
185
- -H "Content-Type: application/json" \
186
- $AUTH_HEADER \
187
- -d "$PAYLOAD" > /dev/null 2>&1
188
- fi
175
+ curl -s -X POST "CALLBACK_URL" -H "Content-Type: application/json" -H "X-Runner-Api-Key: TOKEN" -d '{"workId":"WORK-01","taskId":"TASK-00",...}'
189
176
  ```
190
177
 
191
178
  Agent-specific payload fields:
@@ -201,13 +188,13 @@ Agent-specific payload fields:
201
188
  grep -oP '(?<=Language:\s?)[a-z]{2}' CLAUDE.md 2>/dev/null
202
189
 
203
190
  # 2. Tech stack
204
- cat package.json 2>/dev/null | head -50
205
- cat pyproject.toml 2>/dev/null | head -30
206
- cat Cargo.toml 2>/dev/null | head -20
207
- cat go.mod 2>/dev/null | head -10
191
+ head -50 package.json 2>/dev/null
192
+ head -30 pyproject.toml 2>/dev/null
193
+ head -20 Cargo.toml 2>/dev/null
194
+ head -10 go.mod 2>/dev/null
208
195
 
209
196
  # 3. Structure (when needed)
210
- find . -maxdepth 3 -type f \( -name "*.md" -o -name "*.json" -o -name "*.toml" \) | grep -v node_modules | head -30
197
+ find . -maxdepth 3 -type f \( -name "*.md" -o -name "*.json" -o -name "*.toml" \) -not -path "*/node_modules/*" 2>/dev/null
211
198
  ```
212
199
 
213
200
  ---
@@ -223,7 +210,41 @@ On gate failure → return FAIL task-result immediately. Do not proceed to subse
223
210
 
224
211
  ---
225
212
 
213
+ ## § 13. Bash Command Rules
214
+
215
+ Bash commands MUST follow these rules for permission compatibility.
216
+
217
+ **MANDATORY:**
218
+ - One simple command per Bash call — NO compound commands (`&&`, `||`, `;`, `|`)
219
+ - NO `cd dir && command` — you are already in the project root
220
+ - NO multi-line scripts — split into separate Bash calls
221
+ - NO sub-shell expansions in arguments — e.g., `$(date ...)` inside `printf`
222
+ - Use relative paths from project root (e.g., `works/WORK-01/`) — NO absolute paths
223
+ - Use `git add file`, `git commit -m "msg"` — NO `git -C path` flag
224
+
225
+ **For file operations, prefer dedicated tools over Bash:**
226
+ - Read files → `Read` tool (NOT `cat`)
227
+ - Write/append files → `Write` tool (NOT `echo >>` or `printf >>`)
228
+ - Edit files → `Edit` tool (NOT `sed -i`)
229
+ - Search files → `Grep` tool (NOT `grep`)
230
+ - Find files → `Glob` tool (NOT `find`)
231
+
232
+ **Activity log example:**
233
+ ```
234
+ WRONG: printf '[%s]_%s\n' "$(date ...)" "INIT" >> work.log
235
+ RIGHT: Use Write tool to append a line to the log file
236
+ ```
237
+
238
+ **Git example:**
239
+ ```
240
+ WRONG: cd /path/to/project && git add file && git commit -m "msg"
241
+ RIGHT: git add file (one call)
242
+ git commit -m "msg" (next call)
243
+ ```
244
+
245
+ ---
246
+
226
247
  ## Version
227
248
 
228
249
  - Created: 2026-03-10
229
- - Updated: 2026-03-21
250
+ - Updated: 2026-03-28
@@ -34,12 +34,23 @@ You are the **Specifier** — the agent that transforms user requests into requi
34
34
 
35
35
  **Resolve REFERENCES_DIR**: Check your input for `REFERENCES_DIR=...` line. Use that absolute path. If not provided, default to `.claude/agents`.
36
36
 
37
- | File | Purpose |
38
- |------|---------|
39
- | `{REFERENCES_DIR}/file-content-schema.md` | File format schema (PLAN.md, TASK, Requirement.md formats) |
40
- | `{REFERENCES_DIR}/shared-prompt-sections.md` | Common rules (TASK ID patterns, WORK-LIST rules, log_work function) |
41
- | `{REFERENCES_DIR}/xml-schema.md` | XML communication format (dispatch / task-result structure) |
42
- | `{REFERENCES_DIR}/work-activity-log.md` | Activity Log rules (log_work function, STAGE table) |
37
+ #### Reference Loading (ref-cache)
38
+
39
+ 1. Check if `<ref-cache>` exists in the received dispatch XML
40
+ 2. For each required reference file:
41
+ - If present in ref-cache **SKIP file read**, use cached content
42
+ - If absent from ref-cache → Read from `{REFERENCES_DIR}/{filename}.md` and add to ref-cache
43
+ 3. On task completion, include the merged `<ref-cache>` in the returned task-result XML
44
+ 4. **Backward compatibility**: If dispatch contains no `<ref-cache>`, read all reference files normally (existing behavior)
45
+
46
+ Required reference files for this agent:
47
+
48
+ | File | ref-cache key |
49
+ |------|---------------|
50
+ | `{REFERENCES_DIR}/file-content-schema.md` | `file-content-schema` |
51
+ | `{REFERENCES_DIR}/shared-prompt-sections.md` | `shared-prompt-sections` |
52
+ | `{REFERENCES_DIR}/xml-schema.md` | `xml-schema` |
53
+ | `{REFERENCES_DIR}/work-activity-log.md` | `work-activity-log` |
43
54
 
44
55
  ### 3-2. WORK ID Determination
45
56
 
@@ -116,6 +127,7 @@ Requirement complexity assessment:
116
127
  ```
117
128
 
118
129
  → dispatch XML format: see `xml-schema.md` § 1 (to="builder", task="TASK-00", execution-mode="direct")
130
+ → Include `<ref-cache>` with all reference files loaded (see `xml-schema.md` § 6)
119
131
 
120
132
  ### 3-7. Planner Delegation — Complex Requirements (pipeline/full)
121
133
 
@@ -136,6 +148,7 @@ Requirement complexity assessment:
136
148
  ```
137
149
 
138
150
  → dispatch XML format: see `xml-schema.md` § 1 (to="planner", execution-mode="full")
151
+ → Include `<ref-cache>` with all reference files loaded (see `xml-schema.md` § 6)
139
152
 
140
153
  ### 3-8. Output Language Rule
141
154
 
@@ -35,12 +35,23 @@ Verifies the results of TASKs completed by the Builder, checking build, lint, te
35
35
 
36
36
  **Resolve REFERENCES_DIR**: Check your input for `REFERENCES_DIR=...` line or `<references-dir>` XML element. Use that absolute path. If not provided, default to `.claude/agents`.
37
37
 
38
- | File | Purpose |
39
- |------|---------|
40
- | `{REFERENCES_DIR}/shared-prompt-sections.md` | Common rules |
41
- | `{REFERENCES_DIR}/xml-schema.md` | XML communication format |
42
- | `{REFERENCES_DIR}/context-policy.md` | Sliding Window rules |
43
- | `{REFERENCES_DIR}/work-activity-log.md` | Activity Log rules (log_work function, STAGE table) |
38
+ #### Reference Loading (ref-cache)
39
+
40
+ 1. Check if `<ref-cache>` exists in the received dispatch XML
41
+ 2. For each required reference file:
42
+ - If present in ref-cache **SKIP file read**, use cached content
43
+ - If absent from ref-cache → Read from `{REFERENCES_DIR}/{filename}.md` and add to ref-cache
44
+ 3. On task completion, include the merged `<ref-cache>` in the returned task-result XML
45
+ 4. **Backward compatibility**: If dispatch contains no `<ref-cache>`, read all reference files normally (existing behavior)
46
+
47
+ Required reference files for this agent:
48
+
49
+ | File | ref-cache key |
50
+ |------|---------------|
51
+ | `{REFERENCES_DIR}/shared-prompt-sections.md` | `shared-prompt-sections` |
52
+ | `{REFERENCES_DIR}/xml-schema.md` | `xml-schema` |
53
+ | `{REFERENCES_DIR}/context-policy.md` | `context-policy` |
54
+ | `{REFERENCES_DIR}/work-activity-log.md` | `work-activity-log` |
44
55
 
45
56
  ### 3-2. XML Input Parsing
46
57
 
@@ -96,6 +107,7 @@ Only check conventions specified in CLAUDE.md or project config.
96
107
 
97
108
  → task-result XML base structure: see `xml-schema.md` § 2
98
109
  → context-handoff element: see `xml-schema.md` § 4
110
+ → ref-cache element: see `xml-schema.md` § 6
99
111
 
100
112
  Verifier-specific additional fields:
101
113
 
@@ -116,6 +128,12 @@ Verifier-specific additional fields:
116
128
  <suggested-fix>{suggestion}</suggested-fix>
117
129
  </failure>
118
130
  </failure-details>
131
+ <ref-cache>
132
+ <!-- Include all reference files loaded during this execution (from disk or received ref-cache) -->
133
+ <ref key="shared-prompt-sections">{content}</ref>
134
+ <ref key="xml-schema">{content}</ref>
135
+ <!-- ... other keys loaded ... -->
136
+ </ref-cache>
119
137
  ```
120
138
 
121
139
  ---
@@ -10,19 +10,21 @@ Defines the rules for each agent to record WORK progress in the `works/{WORK_ID}
10
10
  * During work: Work items and work content
11
11
  * On task completion: The prompt message sent to other agents** Content of the prompt message received at agent startup (Required)
12
12
 
13
- ## log_work Function
14
-
15
- ```bash
16
- AGENT_NAME="SPECIFIER" # Set appropriately in each agent file
17
-
18
- log_work() {
19
- local WORK_ID="$1" AGENT="$2" STAGE="$3" DESC="$4"
20
- mkdir -p "works/${WORK_ID}"
21
- printf '[%s]_%s_%s_%s\n' \
22
- "$(date '+%Y-%m-%dT%H:%M:%S')" "$AGENT" "$STAGE" "$DESC" \
23
- >> "works/${WORK_ID}/work_${WORK_ID}.log"
24
- }
13
+ ## log_work Method
14
+
15
+ **Do NOT use Bash** for activity log writes. Use the `Write` tool (or `Edit` tool to append).
16
+
17
+ Format each log entry as:
25
18
  ```
19
+ [YYYY-MM-DDTHH:MM:SS]_AGENT_STAGE_description
20
+ ```
21
+
22
+ Example: to log an INIT stage, use the **Write** tool to append to `works/{WORK_ID}/work_{WORK_ID}.log`:
23
+ ```
24
+ [2026-03-28T14:30:00]_SPECIFIER_INIT_WORK-09 created — Execution-Mode: direct
25
+ ```
26
+
27
+ → **Bash command rules: see `shared-prompt-sections.md` § 13**
26
28
 
27
29
  ---
28
30
 
@@ -8,6 +8,13 @@ XML communication format definition for uc-taskmanager agents.
8
8
 
9
9
  ```xml
10
10
  <dispatch to="{receiver}" work="{WORK_ID}" task="{TASK_ID}" execution-mode="{direct|pipeline|full}">
11
+ <ref-cache> <!-- optional -->
12
+ <ref key="shared-prompt-sections">{file content}</ref>
13
+ <ref key="file-content-schema">{file content}</ref>
14
+ <ref key="xml-schema">{file content}</ref>
15
+ <ref key="context-policy">{file content}</ref>
16
+ <ref key="work-activity-log">{file content}</ref>
17
+ </ref-cache>
11
18
  <references-dir>{absolute path to references directory}</references-dir>
12
19
  <context>
13
20
  <project>{project name}</project>
@@ -47,6 +54,13 @@ XML communication format definition for uc-taskmanager agents.
47
54
  <check name="{type}" status="{PASS|FAIL|N/A}">{output}</check>
48
55
  </verification>
49
56
  <notes>{notes}</notes>
57
+ <ref-cache> <!-- optional -->
58
+ <ref key="shared-prompt-sections">{file content}</ref>
59
+ <ref key="file-content-schema">{file content}</ref>
60
+ <ref key="xml-schema">{file content}</ref>
61
+ <ref key="context-policy">{file content}</ref>
62
+ <ref key="work-activity-log">{file content}</ref>
63
+ </ref-cache>
50
64
  </task-result>
51
65
  ```
52
66
 
@@ -107,3 +121,39 @@ Invariants (regardless of mode):
107
121
  | `TASK-XX_result.md` | Committer | Committer |
108
122
  | COMMITTER DONE callback | Committer | Committer |
109
123
  | `WORK-LIST.md` IN_PROGRESS | Specifier | Specifier |
124
+
125
+ ---
126
+
127
+ ## 6. ref-cache Element Definition
128
+
129
+ `<ref-cache>` is an optional container element that carries pre-loaded reference file contents within dispatch and task-result XML. When present, receiving agents MUST use these contents instead of reading files from disk.
130
+
131
+ ### Structure
132
+
133
+ ```xml
134
+ <ref-cache>
135
+ <ref key="{filename-without-extension}">{full file content}</ref>
136
+ ...
137
+ </ref-cache>
138
+ ```
139
+
140
+ | Element | Required | Description |
141
+ |---------|----------|-------------|
142
+ | `<ref-cache>` | Optional | Container for cached reference files. Omit entirely if no cache is available. |
143
+ | `<ref key="...">` | — | Individual reference file. `key` is the filename without extension (e.g., `shared-prompt-sections`). |
144
+
145
+ ### Recognized Keys
146
+
147
+ | Key | Corresponding File |
148
+ |-----|--------------------|
149
+ | `shared-prompt-sections` | `{REFERENCES_DIR}/shared-prompt-sections.md` |
150
+ | `file-content-schema` | `{REFERENCES_DIR}/file-content-schema.md` |
151
+ | `xml-schema` | `{REFERENCES_DIR}/xml-schema.md` |
152
+ | `context-policy` | `{REFERENCES_DIR}/context-policy.md` |
153
+ | `work-activity-log` | `{REFERENCES_DIR}/work-activity-log.md` |
154
+
155
+ ### Backward Compatibility
156
+
157
+ - Dispatch or task-result XML without `<ref-cache>` is fully valid — agents fall back to reading files from `REFERENCES_DIR`.
158
+ - Agents that do not yet support ref-cache simply ignore the element and read files normally.
159
+ - Partial ref-cache (only some keys present) is allowed — missing keys are read from disk.
package/bin/cli.mjs CHANGED
@@ -100,7 +100,7 @@ async function main() {
100
100
  lang = await promptLang();
101
101
  }
102
102
  const { init } = await import('../lib/init.mjs');
103
- init(isGlobal, lang);
103
+ await init(isGlobal, lang);
104
104
  return;
105
105
  }
106
106
 
package/lib/constants.mjs CHANGED
@@ -31,34 +31,63 @@ export function getAgentsSrcDir(lang) {
31
31
  return join(__dirname, '..', 'agents', lang);
32
32
  }
33
33
 
34
- export const CLAUDE_MD_SECTION_KO = `
35
- ## Agent 호출 규칙
36
34
 
37
- \`[]\` 태그로 시작하는 요청 → \`.claude/agents/agent-flow.md\` 를 읽고 파이프라인을 실행한다.
35
+ /**
36
+ * Bash permissions required by uc-taskmanager agents.
37
+ * Merged into .claude/settings.local.json during init.
38
+ *
39
+ * Categories:
40
+ * - File discovery: ls, cat, basename, find, wc, sort, tail, head
41
+ * - Pattern matching: grep, sed, cut, tr
42
+ * - Formatting: printf, echo
43
+ * - Build/Lint: node, npm, bun, yarn, cargo, go, python, ruff, make
44
+ * - Git: git add, git commit, git log, git rev-parse
45
+ * - Network: curl (callback)
46
+ */
47
+ export const REQUIRED_PERMISSIONS = [
48
+ // File read/write tools (project-root scoped)
49
+ 'Read(/**)',
50
+ 'Edit(/**)',
51
+ 'Write(/**)',
52
+ 'Read(**)',
53
+ 'Edit(**)',
54
+ 'Write(**)',
38
55
 
39
- - **Main Claude가 오케스트레이터**다. 모든 에이전트 호출은 Main Claude가 직접 수행한다.
40
- - \`[]\` 태그 감지 시 → specifier 호출 (첫 번째 에이전트)
41
- - 각 에이전트는 작업 완료 후 결과(dispatch XML 또는 task-result XML)만 반환한다.
42
- - Main Claude가 반환값을 받아 다음 에이전트를 순서대로 호출한다.
43
- - 파이프라인 흐름은 \`.claude/agents/agent-flow.md\` 기준을 따른다.
56
+ // File discovery & text utilities
57
+ 'Bash(ls:*)',
58
+ 'Bash(cat:*)',
59
+ 'Bash(mkdir:*)',
60
+ 'Bash(basename:*)',
61
+ 'Bash(find:*)',
62
+ 'Bash(wc:*)',
63
+ 'Bash(sort:*)',
64
+ 'Bash(tail:*)',
65
+ 'Bash(head:*)',
66
+ 'Bash(echo:*)',
67
+ 'Bash(printf:*)',
44
68
 
45
- 예: \`[추가기능]\`, \`[버그수정]\`, \`[리팩토링]\`, \`[WORK 시작]\` 등
46
- `.trimStart();
69
+ // Pattern matching & text processing
70
+ 'Bash(grep:*)',
71
+ 'Bash(sed:*)',
72
+ 'Bash(cut:*)',
73
+ 'Bash(tr:*)',
47
74
 
48
- export const CLAUDE_MD_SECTION_EN = `
49
- ## Agent Invocation Rules
75
+ // Build & Lint (auto-detect per project type)
76
+ 'Bash(node:*)',
77
+ 'Bash(npm run:*)',
78
+ 'Bash(npm test:*)',
79
+ 'Bash(bun run:*)',
80
+ 'Bash(yarn:*)',
81
+ 'Bash(cargo:*)',
82
+ 'Bash(go build:*)',
83
+ 'Bash(go test:*)',
84
+ 'Bash(python:*)',
85
+ 'Bash(ruff:*)',
86
+ 'Bash(make:*)',
50
87
 
51
- Requests starting with a \`[]\` tag → read \`.claude/agents/agent-flow.md\` and execute the pipeline.
88
+ // Git operations (committer)
89
+ 'Bash(git:*)',
52
90
 
53
- - **Main Claude is the orchestrator.** All agent invocations are performed directly by Main Claude.
54
- - On \`[]\` tag detection → invoke specifier (first agent)
55
- - Each agent only returns results (dispatch XML or task-result XML) after completing its work.
56
- - Main Claude receives return values and invokes the next agent in sequence.
57
- - Pipeline flow follows \`.claude/agents/agent-flow.md\`.
58
-
59
- Examples: \`[new-feature]\`, \`[bugfix]\`, \`[enhancement]\`, \`[new-work]\`, etc.
60
- `.trimStart();
61
-
62
- export function getClaudeMdSection(lang) {
63
- return lang === 'ko' ? CLAUDE_MD_SECTION_KO : CLAUDE_MD_SECTION_EN;
64
- }
91
+ // Network (callback transmission)
92
+ 'Bash(curl:*)',
93
+ ];
package/lib/init.mjs CHANGED
@@ -1,13 +1,16 @@
1
- import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { homedir } from 'node:os';
5
- import { AGENT_FILES, getAgentsSrcDir, getClaudeMdSection } from './constants.mjs';
5
+ import { AGENT_FILES, getAgentsSrcDir, REQUIRED_PERMISSIONS } from './constants.mjs';
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
 
9
+ import { createInterface } from 'node:readline';
10
+
9
11
  const green = (s) => `\x1b[32m${s}\x1b[0m`;
10
12
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
13
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
11
14
 
12
15
  function copyAgents(destDir, lang) {
13
16
  const srcDir = getAgentsSrcDir(lang);
@@ -22,6 +25,41 @@ function copyAgents(destDir, lang) {
22
25
  return count;
23
26
  }
24
27
 
28
+ function copyDirRecursive(src, dest) {
29
+ mkdirSync(dest, { recursive: true });
30
+ let count = 0;
31
+ for (const entry of readdirSync(src)) {
32
+ const srcPath = join(src, entry);
33
+ const destPath = join(dest, entry);
34
+ if (statSync(srcPath).isDirectory()) {
35
+ count += copyDirRecursive(srcPath, destPath);
36
+ } else {
37
+ copyFileSync(srcPath, destPath);
38
+ count++;
39
+ }
40
+ }
41
+ return count;
42
+ }
43
+
44
+ function copyPluginResources(destBaseDir) {
45
+ const pkgRoot = join(__dirname, '..');
46
+ let count = 0;
47
+
48
+ // .claude-plugin/
49
+ const pluginSrc = join(pkgRoot, '.claude-plugin');
50
+ if (existsSync(pluginSrc)) {
51
+ count += copyDirRecursive(pluginSrc, join(destBaseDir, '.claude-plugin'));
52
+ }
53
+
54
+ // skills/
55
+ const skillsSrc = join(pkgRoot, 'skills');
56
+ if (existsSync(skillsSrc)) {
57
+ count += copyDirRecursive(skillsSrc, join(destBaseDir, 'skills'));
58
+ }
59
+
60
+ return count;
61
+ }
62
+
25
63
  function ensureRouterConfig(projectDir) {
26
64
  const configDir = join(projectDir, '.agent');
27
65
  const configPath = join(configDir, 'router_rule_config.json');
@@ -41,31 +79,68 @@ function ensureWorksDir(projectDir) {
41
79
  return true;
42
80
  }
43
81
 
44
- function updateClaudeMd(projectDir, lang) {
45
- const section = getClaudeMdSection(lang);
46
- const claudeMdPath = join(projectDir, 'CLAUDE.md');
47
- if (existsSync(claudeMdPath)) {
48
- const content = readFileSync(claudeMdPath, 'utf8');
49
- if (content.includes('Agent 호출 규칙') || content.includes('Agent Invocation Rules') || content.includes('agent-flow.md')) {
50
- return false;
82
+ function mergePermissions(projectDir) {
83
+ const settingsPath = join(projectDir, '.claude', 'settings.local.json');
84
+ let settings = {};
85
+
86
+ if (existsSync(settingsPath)) {
87
+ try {
88
+ settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
89
+ } catch {
90
+ settings = {};
51
91
  }
52
- writeFileSync(claudeMdPath, content.trimEnd() + '\n\n' + section);
53
- } else {
54
- writeFileSync(claudeMdPath, '# CLAUDE.md\n\n' + section);
55
92
  }
56
- return true;
93
+
94
+ if (!settings.permissions) settings.permissions = {};
95
+ if (!Array.isArray(settings.permissions.allow)) settings.permissions.allow = [];
96
+
97
+ const existing = new Set(settings.permissions.allow);
98
+ let added = 0;
99
+
100
+ for (const perm of REQUIRED_PERMISSIONS) {
101
+ if (!existing.has(perm)) {
102
+ settings.permissions.allow.push(perm);
103
+ added++;
104
+ }
105
+ }
106
+
107
+ if (added > 0) {
108
+ const dir = dirname(settingsPath);
109
+ mkdirSync(dir, { recursive: true });
110
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
111
+ }
112
+
113
+ return { added, total: settings.permissions.allow.length };
57
114
  }
58
115
 
59
- export function init(isGlobal, lang) {
116
+ async function promptPermissions() {
117
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
118
+ return new Promise((resolve) => {
119
+ console.log(`\n ${yellow('?')} Auto-configure Bash permissions for agents? (recommended)`);
120
+ console.log(` ${dim('Adds required permissions to .claude/settings.local.json')}`);
121
+ rl.question(' [Y/n] ', (answer) => {
122
+ rl.close();
123
+ const choice = answer.trim().toLowerCase();
124
+ resolve(choice === '' || choice === 'y' || choice === 'yes');
125
+ });
126
+ });
127
+ }
128
+
129
+ export async function init(isGlobal, lang) {
60
130
  const exampleTag = lang === 'ko'
61
131
  ? `[추가기능] Add a hello world feature`
62
132
  : `[new-feature] Add a hello world feature`;
63
133
 
64
134
  if (isGlobal) {
65
- const globalDir = join(homedir(), '.claude', 'agents');
135
+ const globalClaudeDir = join(homedir(), '.claude');
136
+ const globalDir = join(globalClaudeDir, 'agents');
66
137
  const count = copyAgents(globalDir, lang);
67
138
  console.log(`\n Installing to ${dim('~/.claude/agents/')} (${lang}) ...`);
68
139
  console.log(` ${green('✓')} ${count} agent files copied`);
140
+ const globalResCount = copyPluginResources(globalClaudeDir);
141
+ if (globalResCount > 0) {
142
+ console.log(` ${green('✓')} ${globalResCount} plugin resource files copied`);
143
+ }
69
144
  console.log(`\n ${dim('Next steps:')}`);
70
145
  console.log(` 1. Open any project and run ${dim("'claude'")}`);
71
146
  console.log(` 2. Type: ${dim(exampleTag)}\n`);
@@ -80,24 +155,36 @@ export function init(isGlobal, lang) {
80
155
  const count = copyAgents(destDir, lang);
81
156
  console.log(` ${green('✓')} ${count} agent files copied`);
82
157
 
158
+ const claudeDir = join(projectDir, '.claude');
159
+ const resCount = copyPluginResources(claudeDir);
160
+ if (resCount > 0) {
161
+ console.log(` ${green('✓')} ${resCount} plugin resource files copied (.claude-plugin, skills)`);
162
+ }
163
+
83
164
  if (ensureRouterConfig(projectDir)) {
84
165
  console.log(` ${green('✓')} .agent/router_rule_config.json created`);
85
166
  } else {
86
167
  console.log(` ${dim('-')} .agent/router_rule_config.json already exists`);
87
168
  }
88
169
 
89
- if (updateClaudeMd(projectDir, lang)) {
90
- console.log(` ${green('✓')} CLAUDE.md updated`);
91
- } else {
92
- console.log(` ${dim('-')} CLAUDE.md already has agent rules`);
93
- }
94
-
95
170
  if (ensureWorksDir(projectDir)) {
96
171
  console.log(` ${green('✓')} works/ directory created`);
97
172
  } else {
98
173
  console.log(` ${dim('-')} works/ directory already exists`);
99
174
  }
100
175
 
176
+ const wantPermissions = await promptPermissions();
177
+ if (wantPermissions) {
178
+ const { added, total } = mergePermissions(projectDir);
179
+ if (added > 0) {
180
+ console.log(` ${green('✓')} ${added} permissions added to .claude/settings.local.json (total: ${total})`);
181
+ } else {
182
+ console.log(` ${dim('-')} All permissions already configured (${total})`);
183
+ }
184
+ } else {
185
+ console.log(` ${dim('-')} Skipped permission setup`);
186
+ }
187
+
101
188
  console.log(`\n ${dim('Next steps:')}`);
102
189
  console.log(` 1. Run ${dim("'claude'")} to start Claude Code`);
103
190
  console.log(` 2. Type: ${dim(exampleTag)}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uctm",
3
- "version": "1.3.2",
3
+ "version": "1.5.0",
4
4
  "description": "Universal Claude Task Manager — SDD-based task pipeline subagent system for Claude Code CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,9 @@
10
10
  "bin/",
11
11
  "lib/",
12
12
  "agents/",
13
- ".agent/"
13
+ ".agent/",
14
+ ".claude-plugin/",
15
+ "skills/"
14
16
  ],
15
17
  "engines": {
16
18
  "node": ">=18"