oh-my-customcode 0.46.1 → 0.47.1
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/README.md +7 -6
- package/dist/cli/index.js +244 -129
- package/package.json +4 -4
- package/templates/.claude/hooks/hooks.json +9 -1
- package/templates/.claude/skills/dev-refactor/SKILL.md +3 -3
- package/templates/.claude/skills/omcustom-feedback/SKILL.md +135 -26
- package/templates/.claude/skills/omcustom-loop/SKILL.md +45 -0
- package/templates/.claude/skills/omcustom-release-notes/SKILL.md +3 -3
- package/templates/.claude/skills/omcustom-takeover/SKILL.md +3 -3
- package/templates/.claude/statusline.sh +8 -2
- package/templates/CLAUDE.md +9 -8
- package/templates/CLAUDE.md.en +2 -2
- package/templates/CLAUDE.md.ko +2 -2
- package/templates/manifest.json +2 -2
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
**[한국어 문서 (Korean)](./README_ko.md)**
|
|
15
15
|
|
|
16
|
-
45 agents.
|
|
16
|
+
45 agents. 83 skills. 21 rules. One command.
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
npm install -g oh-my-customcode && cd your-project && omcustom init
|
|
@@ -138,7 +138,7 @@ Each agent declares its tools, model, memory scope, and limitations in YAML fron
|
|
|
138
138
|
|
|
139
139
|
---
|
|
140
140
|
|
|
141
|
-
### Skills (
|
|
141
|
+
### Skills (83)
|
|
142
142
|
|
|
143
143
|
| Category | Count | Includes |
|
|
144
144
|
|----------|-------|----------|
|
|
@@ -180,11 +180,11 @@ All commands are invoked inside the Claude Code conversation.
|
|
|
180
180
|
|---------|-------------|
|
|
181
181
|
| `/omcustom:analysis` | Analyze project, auto-configure agents and skills |
|
|
182
182
|
| `/omcustom:create-agent` | Create a new agent |
|
|
183
|
-
| `/omcustom
|
|
183
|
+
| `/omcustom-takeover` | Extract canonical spec from existing agent or skill |
|
|
184
184
|
| `/omcustom:audit-agents` | Audit agent dependencies |
|
|
185
185
|
| `/omcustom:update-docs` | Sync project structure and documentation |
|
|
186
186
|
| `/omcustom:sauron-watch` | Full structural verification (5+3 rounds) |
|
|
187
|
-
| `/omcustom
|
|
187
|
+
| `/omcustom-feedback` | Submit feedback as GitHub issue |
|
|
188
188
|
|
|
189
189
|
### Web UI
|
|
190
190
|
|
|
@@ -199,7 +199,7 @@ All commands are invoked inside the Claude Code conversation.
|
|
|
199
199
|
| `/omcustom:npm-publish` | Publish to npm |
|
|
200
200
|
| `/omcustom:npm-version` | Semantic versioning |
|
|
201
201
|
| `/omcustom:npm-audit` | Dependency security audit |
|
|
202
|
-
| `/omcustom
|
|
202
|
+
| `/omcustom-release-notes` | Generate release notes from git history |
|
|
203
203
|
|
|
204
204
|
### Memory & System
|
|
205
205
|
|
|
@@ -208,6 +208,7 @@ All commands are invoked inside the Claude Code conversation.
|
|
|
208
208
|
| `/memory-save` | Save session context |
|
|
209
209
|
| `/memory-recall` | Search and recall memories |
|
|
210
210
|
| `/omcustom:monitoring-setup` | OTel monitoring toggle |
|
|
211
|
+
| `/omcustom:loop` | Auto-continue background agent workflows (3-continue safety limit) |
|
|
211
212
|
| `/omcustom:lists` | Show all commands |
|
|
212
213
|
| `/omcustom:status` | System health check |
|
|
213
214
|
|
|
@@ -271,7 +272,7 @@ your-project/
|
|
|
271
272
|
├── CLAUDE.md # Entry point
|
|
272
273
|
├── .claude/
|
|
273
274
|
│ ├── agents/ # 45 agent definitions
|
|
274
|
-
│ ├── skills/ #
|
|
275
|
+
│ ├── skills/ # 83 skill modules
|
|
275
276
|
│ ├── rules/ # 21 governance rules (R000-R021)
|
|
276
277
|
│ ├── hooks/ # 15 lifecycle hook scripts
|
|
277
278
|
│ ├── schemas/ # Tool input validation schemas
|
package/dist/cli/index.js
CHANGED
|
@@ -9323,7 +9323,7 @@ var init_package = __esm(() => {
|
|
|
9323
9323
|
package_default = {
|
|
9324
9324
|
name: "oh-my-customcode",
|
|
9325
9325
|
workspaces: ["packages/*"],
|
|
9326
|
-
version: "0.
|
|
9326
|
+
version: "0.47.1",
|
|
9327
9327
|
description: "Batteries-included agent harness for Claude Code",
|
|
9328
9328
|
type: "module",
|
|
9329
9329
|
bin: {
|
|
@@ -9353,9 +9353,9 @@ var init_package = __esm(() => {
|
|
|
9353
9353
|
"test:integration": "bun test tests/integration",
|
|
9354
9354
|
"test:e2e": "bun test tests/e2e",
|
|
9355
9355
|
"test:coverage": "bun test --coverage",
|
|
9356
|
-
lint: "biome check
|
|
9357
|
-
"lint:fix": "biome check --write
|
|
9358
|
-
format: "biome format --write
|
|
9356
|
+
lint: "biome check src/ tests/ scripts/",
|
|
9357
|
+
"lint:fix": "biome check --write src/ tests/ scripts/",
|
|
9358
|
+
format: "biome format --write src/ tests/ scripts/",
|
|
9359
9359
|
typecheck: "tsc --noEmit",
|
|
9360
9360
|
"docs:dev": "vitepress dev docs",
|
|
9361
9361
|
"docs:build": "vitepress build docs",
|
|
@@ -9419,7 +9419,7 @@ __export(exports_projects, {
|
|
|
9419
9419
|
default: () => projects_default
|
|
9420
9420
|
});
|
|
9421
9421
|
import { homedir as homedir2 } from "node:os";
|
|
9422
|
-
import { basename as basename3, join as join9 } from "node:path";
|
|
9422
|
+
import { basename as basename3, dirname as dirname3, join as join9 } from "node:path";
|
|
9423
9423
|
async function readLockFile(projectDir) {
|
|
9424
9424
|
const lockFilePath = join9(projectDir, ".omcustom.lock.json");
|
|
9425
9425
|
try {
|
|
@@ -9518,6 +9518,14 @@ async function findProjects(options = {}) {
|
|
|
9518
9518
|
for (const dir2 of DEFAULT_SEARCH_DIRS) {
|
|
9519
9519
|
searchPaths.push(join9(home, dir2));
|
|
9520
9520
|
}
|
|
9521
|
+
if (!options.paths) {
|
|
9522
|
+
const cwd = process.cwd();
|
|
9523
|
+
if (!searchPaths.includes(cwd))
|
|
9524
|
+
searchPaths.push(cwd);
|
|
9525
|
+
const parent = dirname3(cwd);
|
|
9526
|
+
if (parent !== cwd && !searchPaths.includes(parent))
|
|
9527
|
+
searchPaths.push(parent);
|
|
9528
|
+
}
|
|
9521
9529
|
if (options.paths) {
|
|
9522
9530
|
searchPaths.push(...options.paths);
|
|
9523
9531
|
}
|
|
@@ -9546,7 +9554,7 @@ function formatProjectsTable(projects, currentVersion) {
|
|
|
9546
9554
|
if (projects.length === 0) {
|
|
9547
9555
|
console.log(`
|
|
9548
9556
|
oh-my-customcode가 적용된 프로젝트를 찾을 수 없습니다.`);
|
|
9549
|
-
console.log(` 검색 경로: ~/workspace, ~/projects, ~/dev, ~/src, ~/code
|
|
9557
|
+
console.log(` 검색 경로: ~/workspace, ~/projects, ~/dev, ~/src, ~/code, ~/repos, ~/work, (현재 디렉토리 및 부모)
|
|
9550
9558
|
`);
|
|
9551
9559
|
return;
|
|
9552
9560
|
}
|
|
@@ -24896,6 +24904,37 @@ var en_default = {
|
|
|
24896
24904
|
}
|
|
24897
24905
|
}
|
|
24898
24906
|
},
|
|
24907
|
+
web: {
|
|
24908
|
+
description: "Manage the Web UI server (start, stop, status, open)",
|
|
24909
|
+
start: {
|
|
24910
|
+
description: "Start the Web UI server",
|
|
24911
|
+
portOption: "Port number",
|
|
24912
|
+
openOption: "Open browser automatically after start",
|
|
24913
|
+
foregroundOption: "Run in foreground (not detached)",
|
|
24914
|
+
started: "Web UI started: http://localhost:{{port}}",
|
|
24915
|
+
failed: "Failed to start Web UI server"
|
|
24916
|
+
},
|
|
24917
|
+
stop: {
|
|
24918
|
+
description: "Stop the Web UI server",
|
|
24919
|
+
stopped: "Web UI server stopped",
|
|
24920
|
+
notRunning: "Web UI server is not running"
|
|
24921
|
+
},
|
|
24922
|
+
status: {
|
|
24923
|
+
description: "Show Web UI server status",
|
|
24924
|
+
running: "Web UI is running: http://localhost:{{port}}",
|
|
24925
|
+
notRunning: "Web UI is not running",
|
|
24926
|
+
startHint: " Start with: omcustom web start"
|
|
24927
|
+
},
|
|
24928
|
+
open: {
|
|
24929
|
+
description: "Open the Web UI in the default browser",
|
|
24930
|
+
portOption: "Port number",
|
|
24931
|
+
notRunningWarn: "Web UI does not appear to be running. Start it with: omcustom web start"
|
|
24932
|
+
},
|
|
24933
|
+
deprecated: {
|
|
24934
|
+
serve: "[Deprecated] `omcustom serve` is deprecated. Use `omcustom web start` instead.",
|
|
24935
|
+
serveStop: "[Deprecated] `omcustom serve-stop` is deprecated. Use `omcustom web stop` instead."
|
|
24936
|
+
}
|
|
24937
|
+
},
|
|
24899
24938
|
security: {
|
|
24900
24939
|
description: "Scan for security issues in hooks, configs, and templates",
|
|
24901
24940
|
verboseOption: "Show detailed scan results",
|
|
@@ -25247,6 +25286,37 @@ var ko_default = {
|
|
|
25247
25286
|
}
|
|
25248
25287
|
}
|
|
25249
25288
|
},
|
|
25289
|
+
web: {
|
|
25290
|
+
description: "Web UI 서버 관리 (시작, 중지, 상태, 열기)",
|
|
25291
|
+
start: {
|
|
25292
|
+
description: "Web UI 서버 시작",
|
|
25293
|
+
portOption: "포트 번호",
|
|
25294
|
+
openOption: "시작 후 브라우저 자동 열기",
|
|
25295
|
+
foregroundOption: "포그라운드 실행 (백그라운드 분리 안 함)",
|
|
25296
|
+
started: "Web UI 시작됨: http://localhost:{{port}}",
|
|
25297
|
+
failed: "Web UI 서버 시작 실패"
|
|
25298
|
+
},
|
|
25299
|
+
stop: {
|
|
25300
|
+
description: "Web UI 서버 중지",
|
|
25301
|
+
stopped: "Web UI 서버가 중지되었습니다",
|
|
25302
|
+
notRunning: "Web UI 서버가 실행 중이 아닙니다"
|
|
25303
|
+
},
|
|
25304
|
+
status: {
|
|
25305
|
+
description: "Web UI 서버 상태 표시",
|
|
25306
|
+
running: "Web UI 실행 중: http://localhost:{{port}}",
|
|
25307
|
+
notRunning: "Web UI가 실행 중이 아닙니다",
|
|
25308
|
+
startHint: " 시작하려면: omcustom web start"
|
|
25309
|
+
},
|
|
25310
|
+
open: {
|
|
25311
|
+
description: "기본 브라우저에서 Web UI 열기",
|
|
25312
|
+
portOption: "포트 번호",
|
|
25313
|
+
notRunningWarn: "Web UI가 실행 중이지 않은 것 같습니다. 먼저 시작하세요: omcustom web start"
|
|
25314
|
+
},
|
|
25315
|
+
deprecated: {
|
|
25316
|
+
serve: "[Deprecated] `omcustom serve` is deprecated. Use `omcustom web start` instead.",
|
|
25317
|
+
serveStop: "[Deprecated] `omcustom serve-stop` is deprecated. Use `omcustom web stop` instead."
|
|
25318
|
+
}
|
|
25319
|
+
},
|
|
25250
25320
|
security: {
|
|
25251
25321
|
description: "훅, 설정, 템플릿의 보안 문제 검사",
|
|
25252
25322
|
verboseOption: "상세 검사 결과 표시",
|
|
@@ -26874,7 +26944,7 @@ async function doctorCommand(options = {}) {
|
|
|
26874
26944
|
|
|
26875
26945
|
// src/cli/init.ts
|
|
26876
26946
|
init_package();
|
|
26877
|
-
import { join as
|
|
26947
|
+
import { join as join10 } from "node:path";
|
|
26878
26948
|
|
|
26879
26949
|
// src/core/installer.ts
|
|
26880
26950
|
init_fs();
|
|
@@ -27731,83 +27801,6 @@ async function checkUvAvailable() {
|
|
|
27731
27801
|
init_fs();
|
|
27732
27802
|
init_projects();
|
|
27733
27803
|
|
|
27734
|
-
// src/cli/serve.ts
|
|
27735
|
-
import { spawn } from "node:child_process";
|
|
27736
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
27737
|
-
import { readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
27738
|
-
import { join as join10 } from "node:path";
|
|
27739
|
-
var DEFAULT_PORT = 4321;
|
|
27740
|
-
var PID_FILE = join10(process.env.HOME ?? "~", ".omcustom-serve.pid");
|
|
27741
|
-
function findServeBuildDir(projectRoot) {
|
|
27742
|
-
const localBuild = join10(projectRoot, "packages", "serve", "build");
|
|
27743
|
-
if (existsSync2(join10(localBuild, "index.js")))
|
|
27744
|
-
return localBuild;
|
|
27745
|
-
const npmBuild = join10(import.meta.dirname, "..", "..", "packages", "serve", "build");
|
|
27746
|
-
if (existsSync2(join10(npmBuild, "index.js")))
|
|
27747
|
-
return npmBuild;
|
|
27748
|
-
return null;
|
|
27749
|
-
}
|
|
27750
|
-
async function isServeRunning() {
|
|
27751
|
-
try {
|
|
27752
|
-
const raw = await readFile2(PID_FILE, "utf-8");
|
|
27753
|
-
const pid = Number(raw.trim());
|
|
27754
|
-
if (!Number.isFinite(pid) || pid <= 0) {
|
|
27755
|
-
await cleanupPidFile();
|
|
27756
|
-
return false;
|
|
27757
|
-
}
|
|
27758
|
-
process.kill(pid, 0);
|
|
27759
|
-
return true;
|
|
27760
|
-
} catch {
|
|
27761
|
-
await cleanupPidFile();
|
|
27762
|
-
return false;
|
|
27763
|
-
}
|
|
27764
|
-
}
|
|
27765
|
-
async function startServeBackground(projectRoot, port = DEFAULT_PORT) {
|
|
27766
|
-
if (await isServeRunning()) {
|
|
27767
|
-
return;
|
|
27768
|
-
}
|
|
27769
|
-
const buildDir = findServeBuildDir(projectRoot);
|
|
27770
|
-
if (buildDir === null) {
|
|
27771
|
-
return;
|
|
27772
|
-
}
|
|
27773
|
-
const child = spawn("node", [join10(buildDir, "index.js")], {
|
|
27774
|
-
env: {
|
|
27775
|
-
...process.env,
|
|
27776
|
-
OMCUSTOM_PORT: String(port),
|
|
27777
|
-
OMCUSTOM_HOST: "localhost",
|
|
27778
|
-
OMCUSTOM_ORIGIN: `http://localhost:${port}`,
|
|
27779
|
-
OMX_PROJECT_ROOT: projectRoot
|
|
27780
|
-
},
|
|
27781
|
-
stdio: "ignore",
|
|
27782
|
-
detached: true
|
|
27783
|
-
});
|
|
27784
|
-
child.unref();
|
|
27785
|
-
if (child.pid !== undefined) {
|
|
27786
|
-
await writeFile2(PID_FILE, String(child.pid), "utf-8");
|
|
27787
|
-
}
|
|
27788
|
-
}
|
|
27789
|
-
async function stopServe() {
|
|
27790
|
-
try {
|
|
27791
|
-
const raw = await readFile2(PID_FILE, "utf-8");
|
|
27792
|
-
const pid = Number(raw.trim());
|
|
27793
|
-
if (!Number.isFinite(pid) || pid <= 0) {
|
|
27794
|
-
await cleanupPidFile();
|
|
27795
|
-
return false;
|
|
27796
|
-
}
|
|
27797
|
-
process.kill(pid, "SIGTERM");
|
|
27798
|
-
await cleanupPidFile();
|
|
27799
|
-
return true;
|
|
27800
|
-
} catch {
|
|
27801
|
-
await cleanupPidFile();
|
|
27802
|
-
return false;
|
|
27803
|
-
}
|
|
27804
|
-
}
|
|
27805
|
-
async function cleanupPidFile() {
|
|
27806
|
-
try {
|
|
27807
|
-
await unlink(PID_FILE);
|
|
27808
|
-
} catch {}
|
|
27809
|
-
}
|
|
27810
|
-
|
|
27811
27804
|
// node_modules/.bun/@clack+core@1.1.0/node_modules/@clack/core/dist/index.mjs
|
|
27812
27805
|
import { styleText as D } from "node:util";
|
|
27813
27806
|
import { stdout as R, stdin as q } from "node:process";
|
|
@@ -28785,7 +28778,7 @@ async function runInitWizard(options) {
|
|
|
28785
28778
|
// src/cli/init.ts
|
|
28786
28779
|
async function checkExistingInstallation(targetDir) {
|
|
28787
28780
|
const layout = getProviderLayout();
|
|
28788
|
-
const rootDir =
|
|
28781
|
+
const rootDir = join10(targetDir, layout.rootDir);
|
|
28789
28782
|
return fileExists(rootDir);
|
|
28790
28783
|
}
|
|
28791
28784
|
var PROVIDER_SUBDIR_COMPONENTS = new Set([
|
|
@@ -28799,13 +28792,13 @@ var PROVIDER_SUBDIR_COMPONENTS = new Set([
|
|
|
28799
28792
|
function componentToPath(targetDir, component) {
|
|
28800
28793
|
if (component === "entry-md") {
|
|
28801
28794
|
const layout = getProviderLayout();
|
|
28802
|
-
return
|
|
28795
|
+
return join10(targetDir, layout.entryFile);
|
|
28803
28796
|
}
|
|
28804
28797
|
if (PROVIDER_SUBDIR_COMPONENTS.has(component)) {
|
|
28805
28798
|
const layout = getProviderLayout();
|
|
28806
|
-
return
|
|
28799
|
+
return join10(targetDir, layout.rootDir, component);
|
|
28807
28800
|
}
|
|
28808
|
-
return
|
|
28801
|
+
return join10(targetDir, component);
|
|
28809
28802
|
}
|
|
28810
28803
|
function buildInstalledPaths(targetDir, components) {
|
|
28811
28804
|
return components.map((component) => componentToPath(targetDir, component));
|
|
@@ -28911,8 +28904,6 @@ async function initCommand(options) {
|
|
|
28911
28904
|
console.log(" /plugin install context7");
|
|
28912
28905
|
console.log("");
|
|
28913
28906
|
console.log('See CLAUDE.md "외부 의존성" section for details.');
|
|
28914
|
-
await startServeBackground(targetDir).catch(() => {});
|
|
28915
|
-
console.log(`Web UI: http://127.0.0.1:${DEFAULT_PORT}`);
|
|
28916
28907
|
return {
|
|
28917
28908
|
success: true,
|
|
28918
28909
|
message: i18n.t("cli.init.success"),
|
|
@@ -28926,7 +28917,7 @@ async function initCommand(options) {
|
|
|
28926
28917
|
}
|
|
28927
28918
|
|
|
28928
28919
|
// src/cli/list.ts
|
|
28929
|
-
import { basename as basename4, dirname as
|
|
28920
|
+
import { basename as basename4, dirname as dirname4, join as join11, relative as relative3 } from "node:path";
|
|
28930
28921
|
init_fs();
|
|
28931
28922
|
var ALLOWED_TOP_LEVEL_KEYS = new Set(["name", "type", "description", "version", "category"]);
|
|
28932
28923
|
function parseKeyValue(line) {
|
|
@@ -28991,12 +28982,12 @@ function extractAgentTypeFromFilename(filename) {
|
|
|
28991
28982
|
return prefixMap[prefix] || "unknown";
|
|
28992
28983
|
}
|
|
28993
28984
|
function extractSkillCategoryFromPath(skillPath, baseDir, rootDir) {
|
|
28994
|
-
const relativePath = relative3(
|
|
28985
|
+
const relativePath = relative3(join11(baseDir, rootDir, "skills"), skillPath);
|
|
28995
28986
|
const parts = relativePath.split("/").filter(Boolean);
|
|
28996
28987
|
return parts[0] || "unknown";
|
|
28997
28988
|
}
|
|
28998
28989
|
function extractGuideCategoryFromPath(guidePath, baseDir) {
|
|
28999
|
-
const relativePath = relative3(
|
|
28990
|
+
const relativePath = relative3(join11(baseDir, "guides"), guidePath);
|
|
29000
28991
|
const parts = relativePath.split("/").filter(Boolean);
|
|
29001
28992
|
return parts[0] || "unknown";
|
|
29002
28993
|
}
|
|
@@ -29090,7 +29081,7 @@ async function tryExtractMarkdownDescription(mdPath, options = {}) {
|
|
|
29090
29081
|
}
|
|
29091
29082
|
}
|
|
29092
29083
|
async function getAgents(targetDir, rootDir = ".claude", config) {
|
|
29093
|
-
const agentsDir =
|
|
29084
|
+
const agentsDir = join11(targetDir, rootDir, "agents");
|
|
29094
29085
|
if (!await fileExists(agentsDir))
|
|
29095
29086
|
return [];
|
|
29096
29087
|
try {
|
|
@@ -29118,7 +29109,7 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
|
|
|
29118
29109
|
}
|
|
29119
29110
|
}
|
|
29120
29111
|
async function getSkills(targetDir, rootDir = ".claude", config) {
|
|
29121
|
-
const skillsDir =
|
|
29112
|
+
const skillsDir = join11(targetDir, rootDir, "skills");
|
|
29122
29113
|
if (!await fileExists(skillsDir))
|
|
29123
29114
|
return [];
|
|
29124
29115
|
try {
|
|
@@ -29127,8 +29118,8 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
|
|
|
29127
29118
|
const customSkillPaths = new Set(customComponents.filter((c) => c.type === "skill").map((c) => c.path));
|
|
29128
29119
|
const skillMdFiles = await listFiles(skillsDir, { recursive: true, pattern: "SKILL.md" });
|
|
29129
29120
|
const skills = await Promise.all(skillMdFiles.map(async (skillMdPath) => {
|
|
29130
|
-
const skillDir =
|
|
29131
|
-
const indexYamlPath =
|
|
29121
|
+
const skillDir = dirname4(skillMdPath);
|
|
29122
|
+
const indexYamlPath = join11(skillDir, "index.yaml");
|
|
29132
29123
|
const { description, version } = await tryReadIndexYamlMetadata(indexYamlPath);
|
|
29133
29124
|
const relativePath = relative3(targetDir, skillDir);
|
|
29134
29125
|
return {
|
|
@@ -29147,7 +29138,7 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
|
|
|
29147
29138
|
}
|
|
29148
29139
|
}
|
|
29149
29140
|
async function getGuides(targetDir, config) {
|
|
29150
|
-
const guidesDir =
|
|
29141
|
+
const guidesDir = join11(targetDir, "guides");
|
|
29151
29142
|
if (!await fileExists(guidesDir))
|
|
29152
29143
|
return [];
|
|
29153
29144
|
try {
|
|
@@ -29174,7 +29165,7 @@ async function getGuides(targetDir, config) {
|
|
|
29174
29165
|
}
|
|
29175
29166
|
var RULE_PRIORITY_ORDER = { MUST: 0, SHOULD: 1, MAY: 2 };
|
|
29176
29167
|
async function getRules(targetDir, rootDir = ".claude", config) {
|
|
29177
|
-
const rulesDir =
|
|
29168
|
+
const rulesDir = join11(targetDir, rootDir, "rules");
|
|
29178
29169
|
if (!await fileExists(rulesDir))
|
|
29179
29170
|
return [];
|
|
29180
29171
|
try {
|
|
@@ -29246,7 +29237,7 @@ function formatAsJson(components) {
|
|
|
29246
29237
|
console.log(JSON.stringify(components, null, 2));
|
|
29247
29238
|
}
|
|
29248
29239
|
async function getHooks(targetDir, rootDir = ".claude") {
|
|
29249
|
-
const hooksDir =
|
|
29240
|
+
const hooksDir = join11(targetDir, rootDir, "hooks");
|
|
29250
29241
|
if (!await fileExists(hooksDir))
|
|
29251
29242
|
return [];
|
|
29252
29243
|
try {
|
|
@@ -29264,7 +29255,7 @@ async function getHooks(targetDir, rootDir = ".claude") {
|
|
|
29264
29255
|
}
|
|
29265
29256
|
}
|
|
29266
29257
|
async function getContexts(targetDir, rootDir = ".claude") {
|
|
29267
|
-
const contextsDir =
|
|
29258
|
+
const contextsDir = join11(targetDir, rootDir, "contexts");
|
|
29268
29259
|
if (!await fileExists(contextsDir))
|
|
29269
29260
|
return [];
|
|
29270
29261
|
try {
|
|
@@ -29656,46 +29647,127 @@ async function securityCommand(_options = {}) {
|
|
|
29656
29647
|
}
|
|
29657
29648
|
|
|
29658
29649
|
// src/cli/serve-commands.ts
|
|
29659
|
-
import {
|
|
29650
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
29660
29651
|
import { join as join13 } from "node:path";
|
|
29652
|
+
|
|
29653
|
+
// src/cli/serve.ts
|
|
29654
|
+
import { spawn } from "node:child_process";
|
|
29655
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
29656
|
+
import { readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
29657
|
+
import { join as join12 } from "node:path";
|
|
29658
|
+
var DEFAULT_PORT = 4321;
|
|
29659
|
+
var PID_FILE = join12(process.env.HOME ?? "~", ".omcustom-serve.pid");
|
|
29660
|
+
function findServeBuildDir(projectRoot, options) {
|
|
29661
|
+
const localBuild = join12(projectRoot, "packages", "serve", "build");
|
|
29662
|
+
if (existsSync2(join12(localBuild, "index.js")))
|
|
29663
|
+
return localBuild;
|
|
29664
|
+
if (options?.skipNpmFallback !== true) {
|
|
29665
|
+
const npmBuild = join12(import.meta.dirname, "..", "..", "packages", "serve", "build");
|
|
29666
|
+
if (existsSync2(join12(npmBuild, "index.js")))
|
|
29667
|
+
return npmBuild;
|
|
29668
|
+
}
|
|
29669
|
+
return null;
|
|
29670
|
+
}
|
|
29671
|
+
async function isServeRunning() {
|
|
29672
|
+
try {
|
|
29673
|
+
const raw = await readFile2(PID_FILE, "utf-8");
|
|
29674
|
+
const pid = Number(raw.trim());
|
|
29675
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
29676
|
+
await cleanupPidFile();
|
|
29677
|
+
return false;
|
|
29678
|
+
}
|
|
29679
|
+
process.kill(pid, 0);
|
|
29680
|
+
return true;
|
|
29681
|
+
} catch {
|
|
29682
|
+
await cleanupPidFile();
|
|
29683
|
+
return false;
|
|
29684
|
+
}
|
|
29685
|
+
}
|
|
29686
|
+
async function startServeBackground(projectRoot, port = DEFAULT_PORT, buildDirOpts) {
|
|
29687
|
+
if (await isServeRunning()) {
|
|
29688
|
+
return;
|
|
29689
|
+
}
|
|
29690
|
+
const buildDir = findServeBuildDir(projectRoot, buildDirOpts);
|
|
29691
|
+
if (buildDir === null) {
|
|
29692
|
+
return;
|
|
29693
|
+
}
|
|
29694
|
+
const child = spawn("node", [join12(buildDir, "index.js")], {
|
|
29695
|
+
env: {
|
|
29696
|
+
...process.env,
|
|
29697
|
+
OMCUSTOM_PORT: String(port),
|
|
29698
|
+
OMCUSTOM_HOST: "localhost",
|
|
29699
|
+
OMCUSTOM_ORIGIN: `http://localhost:${port}`,
|
|
29700
|
+
OMX_PROJECT_ROOT: projectRoot
|
|
29701
|
+
},
|
|
29702
|
+
stdio: "ignore",
|
|
29703
|
+
detached: true
|
|
29704
|
+
});
|
|
29705
|
+
child.unref();
|
|
29706
|
+
if (child.pid !== undefined) {
|
|
29707
|
+
await writeFile2(PID_FILE, String(child.pid), "utf-8");
|
|
29708
|
+
}
|
|
29709
|
+
}
|
|
29710
|
+
async function stopServe() {
|
|
29711
|
+
try {
|
|
29712
|
+
const raw = await readFile2(PID_FILE, "utf-8");
|
|
29713
|
+
const pid = Number(raw.trim());
|
|
29714
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
29715
|
+
await cleanupPidFile();
|
|
29716
|
+
return false;
|
|
29717
|
+
}
|
|
29718
|
+
process.kill(pid, "SIGTERM");
|
|
29719
|
+
await cleanupPidFile();
|
|
29720
|
+
return true;
|
|
29721
|
+
} catch {
|
|
29722
|
+
await cleanupPidFile();
|
|
29723
|
+
return false;
|
|
29724
|
+
}
|
|
29725
|
+
}
|
|
29726
|
+
async function cleanupPidFile() {
|
|
29727
|
+
try {
|
|
29728
|
+
await unlink(PID_FILE);
|
|
29729
|
+
} catch {}
|
|
29730
|
+
}
|
|
29731
|
+
|
|
29732
|
+
// src/cli/serve-commands.ts
|
|
29661
29733
|
async function serveCommand(options) {
|
|
29662
29734
|
const port = options.port !== undefined ? Number(options.port) : DEFAULT_PORT;
|
|
29663
29735
|
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
|
29664
29736
|
console.error(`Invalid port: ${options.port}`);
|
|
29665
29737
|
process.exit(1);
|
|
29666
29738
|
}
|
|
29667
|
-
const cwd = process.cwd();
|
|
29739
|
+
const cwd = options._projectRoot ?? process.cwd();
|
|
29740
|
+
const buildDirOpts = {
|
|
29741
|
+
skipNpmFallback: options._projectRoot !== undefined
|
|
29742
|
+
};
|
|
29668
29743
|
if (options.foreground === true) {
|
|
29669
|
-
runForeground(cwd, port);
|
|
29744
|
+
runForeground(cwd, port, buildDirOpts);
|
|
29670
29745
|
return;
|
|
29671
29746
|
}
|
|
29672
|
-
await startServeBackground(cwd, port);
|
|
29747
|
+
await startServeBackground(cwd, port, buildDirOpts);
|
|
29673
29748
|
const running = await isServeRunning();
|
|
29674
29749
|
if (running) {
|
|
29675
|
-
console.log(
|
|
29676
|
-
if (options.open === true) {
|
|
29677
|
-
openBrowser(port);
|
|
29678
|
-
}
|
|
29750
|
+
console.log(i18n.t("cli.web.start.started", { port }));
|
|
29679
29751
|
} else {
|
|
29680
|
-
console.error("
|
|
29752
|
+
console.error(i18n.t("cli.web.start.failed"));
|
|
29681
29753
|
process.exit(1);
|
|
29682
29754
|
}
|
|
29683
29755
|
}
|
|
29684
29756
|
async function serveStopCommand() {
|
|
29685
29757
|
const stopped = await stopServe();
|
|
29686
29758
|
if (stopped) {
|
|
29687
|
-
console.log("
|
|
29759
|
+
console.log(i18n.t("cli.web.stop.stopped"));
|
|
29688
29760
|
} else {
|
|
29689
|
-
console.log("
|
|
29761
|
+
console.log(i18n.t("cli.web.stop.notRunning"));
|
|
29690
29762
|
}
|
|
29691
29763
|
}
|
|
29692
|
-
function runForeground(projectRoot, port) {
|
|
29693
|
-
const buildDir = findServeBuildDir(projectRoot);
|
|
29764
|
+
function runForeground(projectRoot, port, buildDirOpts) {
|
|
29765
|
+
const buildDir = findServeBuildDir(projectRoot, buildDirOpts);
|
|
29694
29766
|
if (buildDir === null) {
|
|
29695
29767
|
console.error("Web UI build not found. Run: cd packages/serve && bun run build");
|
|
29696
29768
|
process.exit(1);
|
|
29697
29769
|
}
|
|
29698
|
-
console.log(`Web UI: http://
|
|
29770
|
+
console.log(`Web UI: http://localhost:${port}`);
|
|
29699
29771
|
spawnSync2("node", [join13(buildDir, "index.js")], {
|
|
29700
29772
|
env: {
|
|
29701
29773
|
...process.env,
|
|
@@ -29707,17 +29779,6 @@ function runForeground(projectRoot, port) {
|
|
|
29707
29779
|
stdio: "inherit"
|
|
29708
29780
|
});
|
|
29709
29781
|
}
|
|
29710
|
-
function openBrowser(port) {
|
|
29711
|
-
const url = `http://127.0.0.1:${port}`;
|
|
29712
|
-
const platform = process.platform;
|
|
29713
|
-
if (platform === "darwin") {
|
|
29714
|
-
execFile("open", [url], () => {});
|
|
29715
|
-
} else if (platform === "win32") {
|
|
29716
|
-
execFile("cmd", ["/c", "start", url], () => {});
|
|
29717
|
-
} else {
|
|
29718
|
-
execFile("xdg-open", [url], () => {});
|
|
29719
|
-
}
|
|
29720
|
-
}
|
|
29721
29782
|
|
|
29722
29783
|
// src/cli/update.ts
|
|
29723
29784
|
init_package();
|
|
@@ -30493,6 +30554,35 @@ function printUpdateResults(result) {
|
|
|
30493
30554
|
}
|
|
30494
30555
|
}
|
|
30495
30556
|
|
|
30557
|
+
// src/cli/web-commands.ts
|
|
30558
|
+
async function webStartCommand(options) {
|
|
30559
|
+
await serveCommand(options);
|
|
30560
|
+
}
|
|
30561
|
+
async function webStopCommand() {
|
|
30562
|
+
await serveStopCommand();
|
|
30563
|
+
}
|
|
30564
|
+
async function webStatusCommand() {
|
|
30565
|
+
const running = await isServeRunning();
|
|
30566
|
+
if (running) {
|
|
30567
|
+
const port = process.env.OMCUSTOM_PORT ?? String(DEFAULT_PORT);
|
|
30568
|
+
console.log(i18n.t("cli.web.status.running", { port }));
|
|
30569
|
+
} else {
|
|
30570
|
+
console.log(i18n.t("cli.web.status.notRunning"));
|
|
30571
|
+
console.log(i18n.t("cli.web.status.startHint"));
|
|
30572
|
+
}
|
|
30573
|
+
}
|
|
30574
|
+
async function webOpenCommand(options) {
|
|
30575
|
+
const port = options.port !== undefined ? Number(options.port) : DEFAULT_PORT;
|
|
30576
|
+
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
|
30577
|
+
console.error(`Invalid port: ${options.port}`);
|
|
30578
|
+
process.exit(1);
|
|
30579
|
+
}
|
|
30580
|
+
const running = await isServeRunning();
|
|
30581
|
+
if (!running) {
|
|
30582
|
+
console.warn(i18n.t("cli.web.open.notRunningWarn"));
|
|
30583
|
+
}
|
|
30584
|
+
}
|
|
30585
|
+
|
|
30496
30586
|
// src/cli/index.ts
|
|
30497
30587
|
var require2 = createRequire2(import.meta.url);
|
|
30498
30588
|
var packageJson = require2("../../package.json");
|
|
@@ -30518,10 +30608,28 @@ function createProgram() {
|
|
|
30518
30608
|
const result = await securityCommand(options);
|
|
30519
30609
|
process.exitCode = result.success ? 0 : 1;
|
|
30520
30610
|
});
|
|
30521
|
-
program2.command("
|
|
30611
|
+
const web = program2.command("web").description(i18n.t("cli.web.description"));
|
|
30612
|
+
web.command("start").description(i18n.t("cli.web.start.description")).option("-p, --port <port>", i18n.t("cli.web.start.portOption"), "4321").option("--foreground", i18n.t("cli.web.start.foregroundOption")).action(async (options) => {
|
|
30613
|
+
await webStartCommand(options);
|
|
30614
|
+
});
|
|
30615
|
+
web.command("stop").description(i18n.t("cli.web.stop.description")).action(async () => {
|
|
30616
|
+
await webStopCommand();
|
|
30617
|
+
});
|
|
30618
|
+
web.command("status").description(i18n.t("cli.web.status.description")).action(async () => {
|
|
30619
|
+
await webStatusCommand();
|
|
30620
|
+
});
|
|
30621
|
+
web.command("open").description(i18n.t("cli.web.open.description")).option("-p, --port <port>", i18n.t("cli.web.open.portOption"), "4321").action(async (options) => {
|
|
30622
|
+
await webOpenCommand(options);
|
|
30623
|
+
});
|
|
30624
|
+
web.action(async () => {
|
|
30625
|
+
await webStatusCommand();
|
|
30626
|
+
});
|
|
30627
|
+
program2.command("serve").description("(Deprecated) Start the Web UI server — use `omcustom web start` instead").option("-p, --port <port>", i18n.t("cli.web.start.portOption"), "4321").option("--foreground", i18n.t("cli.web.start.foregroundOption")).action(async (options) => {
|
|
30628
|
+
console.warn(i18n.t("cli.web.deprecated.serve"));
|
|
30522
30629
|
await serveCommand(options);
|
|
30523
30630
|
});
|
|
30524
|
-
program2.command("serve-stop").description("Stop the
|
|
30631
|
+
program2.command("serve-stop").description("(Deprecated) Stop the Web UI server — use `omcustom web stop` instead").action(async () => {
|
|
30632
|
+
console.warn(i18n.t("cli.web.deprecated.serveStop"));
|
|
30525
30633
|
await serveStopCommand();
|
|
30526
30634
|
});
|
|
30527
30635
|
program2.command("projects").description("List all projects on this machine where oh-my-customcode is installed").option("-f, --format <format>", "Output format: table, json, or simple", "table").option("--path <dir>", "Additional search directory (can be specified multiple times)", (val, prev) => [...prev, val], []).action(async (options) => {
|
|
@@ -30530,7 +30638,14 @@ function createProgram() {
|
|
|
30530
30638
|
program2.hook("preAction", async (thisCommand, actionCommand) => {
|
|
30531
30639
|
const opts = thisCommand.optsWithGlobals();
|
|
30532
30640
|
const skipCheck = opts.skipVersionCheck || false;
|
|
30533
|
-
|
|
30641
|
+
const cmdName = actionCommand.name();
|
|
30642
|
+
const parentName = actionCommand.parent?.name();
|
|
30643
|
+
const isServeCmd = cmdName === "serve" || cmdName === "serve-stop";
|
|
30644
|
+
const isWebCmd = cmdName === "web" || parentName === "web";
|
|
30645
|
+
if (isServeCmd || isWebCmd) {
|
|
30646
|
+
return;
|
|
30647
|
+
}
|
|
30648
|
+
if (cmdName === "init") {
|
|
30534
30649
|
await maybeHandleSelfUpdateForInit({
|
|
30535
30650
|
currentVersion: packageJson.version,
|
|
30536
30651
|
skip: skipCheck
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-customcode",
|
|
3
3
|
"workspaces": ["packages/*"],
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.47.1",
|
|
5
5
|
"description": "Batteries-included agent harness for Claude Code",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"test:integration": "bun test tests/integration",
|
|
32
32
|
"test:e2e": "bun test tests/e2e",
|
|
33
33
|
"test:coverage": "bun test --coverage",
|
|
34
|
-
"lint": "biome check
|
|
35
|
-
"lint:fix": "biome check --write
|
|
36
|
-
"format": "biome format --write
|
|
34
|
+
"lint": "biome check src/ tests/ scripts/",
|
|
35
|
+
"lint:fix": "biome check --write src/ tests/ scripts/",
|
|
36
|
+
"format": "biome format --write src/ tests/ scripts/",
|
|
37
37
|
"typecheck": "tsc --noEmit",
|
|
38
38
|
"docs:dev": "vitepress dev docs",
|
|
39
39
|
"docs:build": "vitepress build docs",
|
|
@@ -136,9 +136,17 @@
|
|
|
136
136
|
{
|
|
137
137
|
"type": "command",
|
|
138
138
|
"command": "bash .claude/hooks/scripts/task-outcome-recorder.sh"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"type": "command",
|
|
142
|
+
"command": "count_file=\"/tmp/.claude-loop-count-$PPID\"; if [ -f \"$count_file\" ]; then last_mod=$(stat -f%m \"$count_file\" 2>/dev/null || echo 0); now=$(date +%s); if [ $((now - last_mod)) -gt 60 ]; then echo 0 > \"$count_file\"; fi; fi; count=$(cat \"$count_file\" 2>/dev/null || echo 0); count=$((count + 1)); echo \"$count\" > \"$count_file\"; if [ \"$count\" -ge 4 ]; then echo '[AutoContinue] SAFETY: auto-continue limit (3) reached. Pausing.' >&2; fi; cat"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"type": "prompt",
|
|
146
|
+
"prompt": "A background subagent just completed. Check if there are pending workflow steps that depend on this result. If the previous subagent FAILED, do NOT auto-continue — report the failure and wait for user input. If the previous step succeeded and there are pending steps, proceed automatically. If no pending steps, report results and wait. Safety: The file /tmp/.claude-loop-count-$PPID tracks auto-continue count. After 3 consecutive auto-continues without user interaction, pause and ask the user before proceeding."
|
|
139
147
|
}
|
|
140
148
|
],
|
|
141
|
-
"description": "Record agent outcomes on subagent completion
|
|
149
|
+
"description": "Record agent outcomes + auto-continue workflow on subagent completion"
|
|
142
150
|
}
|
|
143
151
|
],
|
|
144
152
|
"PostCompact": [
|
|
@@ -205,8 +205,8 @@ When the `--spec` flag is present, refactoring is guided by the target's canonic
|
|
|
205
205
|
|
|
206
206
|
### Workflow
|
|
207
207
|
|
|
208
|
-
1. **Load spec**: Read `.claude/specs/<agent-name>.spec.md` (generated by `/omcustom
|
|
209
|
-
- If spec doesn't exist, run takeover first: `/omcustom
|
|
208
|
+
1. **Load spec**: Read `.claude/specs/<agent-name>.spec.md` (generated by `/omcustom-takeover`)
|
|
209
|
+
- If spec doesn't exist, run takeover first: `/omcustom-takeover <name>`
|
|
210
210
|
2. **Extract invariants**: Parse the spec's `## Invariants` section as pre-flight guard constraints
|
|
211
211
|
3. **Refactor**: Perform normal refactoring (per existing workflow)
|
|
212
212
|
4. **Verify invariants**: After refactoring, check each invariant still holds:
|
|
@@ -216,7 +216,7 @@ When the `--spec` flag is present, refactoring is guided by the target's canonic
|
|
|
216
216
|
├── ✓ Invariant 2: {description} — PASS
|
|
217
217
|
└── ✗ Invariant 3: {description} — FAIL (reason)
|
|
218
218
|
```
|
|
219
|
-
5. **Regenerate spec**: If refactoring changed the contract, run `/omcustom
|
|
219
|
+
5. **Regenerate spec**: If refactoring changed the contract, run `/omcustom-takeover <name>` to update
|
|
220
220
|
|
|
221
221
|
### When to Use
|
|
222
222
|
|
|
@@ -1,53 +1,82 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: omcustom-feedback
|
|
3
|
-
description: Submit feedback about oh-my-customcode
|
|
3
|
+
description: Submit feedback about oh-my-customcode (supports anonymous submission)
|
|
4
4
|
scope: harness
|
|
5
5
|
user-invocable: true
|
|
6
6
|
disable-model-invocation: true
|
|
7
|
-
argument-hint: "[description or leave empty for interactive]"
|
|
7
|
+
argument-hint: "[description or leave empty for interactive] [--anonymous]"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Feedback Submitter
|
|
11
11
|
|
|
12
|
-
Submit feedback about oh-my-customcode (bugs, features, improvements, questions) directly
|
|
12
|
+
Submit feedback about oh-my-customcode (bugs, features, improvements, questions) directly from the CLI session. Supports anonymous submission via Airflow DAG when gh CLI is unavailable or when anonymity is requested.
|
|
13
13
|
|
|
14
14
|
## Purpose
|
|
15
15
|
|
|
16
|
-
Lowers the barrier for submitting feedback by allowing users to create GitHub issues without leaving their terminal session. All feedback is filed to the `baekenough/oh-my-customcode` repository.
|
|
16
|
+
Lowers the barrier for submitting feedback by allowing users to create GitHub issues or submit anonymously — without leaving their terminal session. All feedback is filed to the `baekenough/oh-my-customcode` repository.
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
20
|
```
|
|
21
21
|
# Inline feedback
|
|
22
|
-
/omcustom
|
|
22
|
+
/omcustom-feedback HUD display is missing during parallel agent spawn
|
|
23
|
+
|
|
24
|
+
# Anonymous submission
|
|
25
|
+
/omcustom:feedback --anonymous Something feels off with the routing
|
|
23
26
|
|
|
24
27
|
# Interactive (no arguments)
|
|
25
|
-
/omcustom
|
|
28
|
+
/omcustom-feedback
|
|
26
29
|
```
|
|
27
30
|
|
|
28
31
|
## Workflow
|
|
29
32
|
|
|
30
33
|
### Phase 1: Input Parsing
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
Check for `--anonymous` flag in the arguments:
|
|
36
|
+
- If `--anonymous` is present, set `ANONYMOUS=true` and strip the flag from the content
|
|
37
|
+
- Otherwise, set `ANONYMOUS=false`
|
|
38
|
+
|
|
39
|
+
If remaining arguments are provided:
|
|
33
40
|
1. Analyze the content to auto-detect category (`bug`, `feature`, `improvement`, `question`)
|
|
34
41
|
2. Use the content as the issue title (truncate to 80 chars if needed)
|
|
35
42
|
3. Use the full content as the description body
|
|
36
43
|
|
|
37
|
-
If no arguments:
|
|
44
|
+
If no arguments (or only `--anonymous`):
|
|
38
45
|
1. Ask the user for category using AskUserQuestion: `[bug / feature / improvement / question]`
|
|
39
46
|
2. Ask for title and optional detailed description (combine into a single prompt when possible)
|
|
40
47
|
|
|
41
|
-
### Phase 2:
|
|
48
|
+
### Phase 2: Route Decision
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
# Verify gh CLI is installed
|
|
45
|
-
command -v gh >/dev/null 2>&1 || { echo "Error: gh CLI not installed. Install from https://cli.github.com/"; exit 1; }
|
|
50
|
+
Check environment and user intent:
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
```bash
|
|
53
|
+
# Check gh CLI availability
|
|
54
|
+
command -v gh >/dev/null 2>&1 && GH_AVAILABLE=true || GH_AVAILABLE=false
|
|
55
|
+
|
|
56
|
+
# Check gh authentication (only if gh is available)
|
|
57
|
+
if [ "$GH_AVAILABLE" = "true" ]; then
|
|
58
|
+
gh auth status >/dev/null 2>&1 && GH_AUTHED=true || GH_AUTHED=false
|
|
59
|
+
else
|
|
60
|
+
GH_AUTHED=false
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Check curl availability
|
|
64
|
+
command -v curl >/dev/null 2>&1 && CURL_AVAILABLE=true || CURL_AVAILABLE=false
|
|
49
65
|
```
|
|
50
66
|
|
|
67
|
+
**Route A**: `gh` available + authenticated + NOT anonymous
|
|
68
|
+
- Use GitHub Issue creation (see Phase 4A)
|
|
69
|
+
|
|
70
|
+
**Route B**: anonymous OR not authenticated (gh available)
|
|
71
|
+
- Use `curl` to Airflow REST API (see Phase 4C)
|
|
72
|
+
- Phase 4B (workflow_dispatch) reserved for future use
|
|
73
|
+
|
|
74
|
+
**Route C**: `gh` NOT available (or Route B curl fallback)
|
|
75
|
+
- Use `curl` to Airflow REST API (see Phase 4C)
|
|
76
|
+
|
|
77
|
+
**Fallback**: Neither `gh` nor `curl` available
|
|
78
|
+
- Save feedback locally and inform the user (see Phase 4D)
|
|
79
|
+
|
|
51
80
|
### Phase 3: Environment Collection
|
|
52
81
|
|
|
53
82
|
Collect environment info via Bash:
|
|
@@ -64,9 +93,16 @@ OS_INFO=$(uname -s 2>/dev/null || echo "unknown")
|
|
|
64
93
|
|
|
65
94
|
# Project name
|
|
66
95
|
PROJECT_NAME=$(basename "$(pwd)")
|
|
96
|
+
|
|
97
|
+
# Build project context string
|
|
98
|
+
PROJECT_CONTEXT="omcustom v${OMCUSTOM_VERSION}, Claude Code ${CLAUDE_VERSION}, ${OS_INFO}"
|
|
67
99
|
```
|
|
68
100
|
|
|
69
|
-
|
|
101
|
+
For anonymous submissions, do NOT include the project name. Offer to include project context as opt-in:
|
|
102
|
+
- Ask: "Include environment info (version, OS) in the anonymous report? [Y/n]"
|
|
103
|
+
- If declined, set `PROJECT_CONTEXT=""`
|
|
104
|
+
|
|
105
|
+
### Phase 4A: GitHub Issue Creation (Route A — gh + authenticated + not anonymous)
|
|
70
106
|
|
|
71
107
|
1. Show the user a preview of the issue to be created:
|
|
72
108
|
```
|
|
@@ -80,7 +116,7 @@ PROJECT_NAME=$(basename "$(pwd)")
|
|
|
80
116
|
|
|
81
117
|
3. Ensure labels exist (defensive):
|
|
82
118
|
```bash
|
|
83
|
-
gh label create feedback --description "User feedback via /omcustom
|
|
119
|
+
gh label create feedback --description "User feedback via /omcustom-feedback" --color 0E8A16 --repo baekenough/oh-my-customcode 2>/dev/null || true
|
|
84
120
|
```
|
|
85
121
|
|
|
86
122
|
4. Create the issue using `--body-file` for safe markdown handling:
|
|
@@ -102,7 +138,7 @@ PROJECT_NAME=$(basename "$(pwd)")
|
|
|
102
138
|
- Project: {project_name}
|
|
103
139
|
|
|
104
140
|
---
|
|
105
|
-
*Submitted via `/omcustom
|
|
141
|
+
*Submitted via `/omcustom-feedback`*
|
|
106
142
|
FEEDBACK_EOF
|
|
107
143
|
|
|
108
144
|
# Create issue
|
|
@@ -120,18 +156,91 @@ PROJECT_NAME=$(basename "$(pwd)")
|
|
|
120
156
|
|
|
121
157
|
6. Return the issue URL to the user
|
|
122
158
|
|
|
159
|
+
### Phase 4B: Anonymous via GitHub Actions (Route B — FUTURE)
|
|
160
|
+
|
|
161
|
+
> **Note**: This route is reserved for future implementation. The `feedback-submission.yml` workflow does not yet exist. All anonymous/unauthenticated submissions currently fall through to Route C (Airflow REST API).
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
gh workflow run feedback-submission.yml \
|
|
165
|
+
--repo baekenough/oh-my-customcode \
|
|
166
|
+
-f title="$TITLE" \
|
|
167
|
+
-f body="$BODY" \
|
|
168
|
+
-f feedback_type="$TYPE" \
|
|
169
|
+
-f anonymous=true \
|
|
170
|
+
-f project_context="$PROJECT_CONTEXT"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
On success, inform the user:
|
|
174
|
+
```
|
|
175
|
+
[Done] Anonymous feedback submitted via GitHub Actions workflow.
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
If `gh workflow run` fails (e.g., permissions), fall through to Route C.
|
|
179
|
+
|
|
180
|
+
### Phase 4C: Anonymous via Airflow REST API (Route C — no gh)
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Build JSON payload safely with jq
|
|
184
|
+
PAYLOAD=$(jq -n --arg title "$TITLE" --arg body "$BODY" --arg type "$TYPE" --arg ctx "$PROJECT_CONTEXT" \
|
|
185
|
+
'{"conf":{"title":$title,"body":$body,"feedback_type":$type,"anonymous":true,"submitter":"","project_context":$ctx}}')
|
|
186
|
+
|
|
187
|
+
curl -X POST "https://airflow.baekenough.com/api/v2/dags/omc_feedback_collector/dagRuns" \
|
|
188
|
+
-H "Content-Type: application/json" \
|
|
189
|
+
-d "$PAYLOAD" \
|
|
190
|
+
--silent --show-error \
|
|
191
|
+
--max-time 15
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
On HTTP 200/201, inform the user:
|
|
195
|
+
```
|
|
196
|
+
[Done] Anonymous feedback submitted via Airflow.
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
On failure (non-2xx or network error), fall through to Fallback.
|
|
200
|
+
|
|
201
|
+
### Phase 4D: Local Fallback (no gh, no curl, or all routes failed)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
mkdir -p ~/.omcustom/feedback
|
|
205
|
+
TIMESTAMP=$(date +%Y%m%dT%H%M%S)
|
|
206
|
+
FEEDBACK_FILE=~/.omcustom/feedback/${TIMESTAMP}.json
|
|
207
|
+
|
|
208
|
+
cat > "$FEEDBACK_FILE" << EOF
|
|
209
|
+
{
|
|
210
|
+
"title": "$TITLE",
|
|
211
|
+
"body": "$BODY",
|
|
212
|
+
"feedback_type": "$TYPE",
|
|
213
|
+
"anonymous": $ANONYMOUS,
|
|
214
|
+
"project_context": "$PROJECT_CONTEXT",
|
|
215
|
+
"saved_at": "$TIMESTAMP"
|
|
216
|
+
}
|
|
217
|
+
EOF
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Inform the user:
|
|
221
|
+
```
|
|
222
|
+
[Saved] Feedback saved locally to ~/.omcustom/feedback/{timestamp}.json
|
|
223
|
+
Submit manually when connectivity is available:
|
|
224
|
+
- GitHub Issues: https://github.com/baekenough/oh-my-customcode/issues/new
|
|
225
|
+
- Or run /omcustom:feedback again when gh or curl is available
|
|
226
|
+
```
|
|
227
|
+
|
|
123
228
|
### Category-to-Label Mapping
|
|
124
229
|
|
|
125
|
-
| Category | GitHub Label |
|
|
126
|
-
|
|
127
|
-
| bug | bug |
|
|
128
|
-
| feature | enhancement |
|
|
129
|
-
| improvement | enhancement |
|
|
130
|
-
| question | question |
|
|
230
|
+
| Category | GitHub Label | Airflow feedback_type |
|
|
231
|
+
|----------|--------------|-----------------------|
|
|
232
|
+
| bug | bug | bug |
|
|
233
|
+
| feature | enhancement | feature |
|
|
234
|
+
| improvement | enhancement | improvement |
|
|
235
|
+
| question | question | question |
|
|
236
|
+
| (auto-detect fails) | (none) | general |
|
|
131
237
|
|
|
132
238
|
## Notes
|
|
133
239
|
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
240
|
+
- Route A creates a visible GitHub issue attributed to the user's gh account
|
|
241
|
+
- Routes B and C submit anonymously — submitter identity is not recorded
|
|
242
|
+
- Route B requires `gh` but NOT authentication to the repo (public workflow)
|
|
243
|
+
- Route C requires `curl` (available on macOS/Linux by default)
|
|
244
|
+
- Fallback ensures no feedback is silently lost even in offline environments
|
|
137
245
|
- `disable-model-invocation: true` ensures this skill only runs when explicitly invoked by the user
|
|
246
|
+
- Target repo is hardcoded to `baekenough/oh-my-customcode` — feedback is always about omcustom itself
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: omcustom-loop
|
|
3
|
+
description: Prevent session idle during background agent work via SubagentStop prompt hook auto-continuation
|
|
4
|
+
scope: core
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
user-invocable: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /omcustom:loop — Session Auto-Continuation
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Prevents session idle when background subagents complete by using a `SubagentStop` prompt hook that nudges the orchestrator to check for pending workflow steps.
|
|
14
|
+
|
|
15
|
+
## How It Works
|
|
16
|
+
|
|
17
|
+
1. When a background subagent completes, Claude Code fires the `SubagentStop` event
|
|
18
|
+
2. The prompt hook injects a message asking the orchestrator to check for pending steps
|
|
19
|
+
3. If pending steps exist, the orchestrator proceeds automatically
|
|
20
|
+
4. If no pending steps, it reports results and waits for user input
|
|
21
|
+
|
|
22
|
+
## Safety Limits
|
|
23
|
+
|
|
24
|
+
- **3 consecutive auto-continues max**: After 3 automatic progressions without user interaction, the system pauses and asks the user before proceeding
|
|
25
|
+
- **stuck-detector integration**: If the same action repeats 3+ times, stuck-detector intervenes
|
|
26
|
+
- **cost-cap-advisor**: Cost monitoring continues during auto-continuation
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
The hook is configured in `.claude/hooks/hooks.json` under `SubagentStop`. It works alongside the existing `task-outcome-recorder.sh` command hook.
|
|
31
|
+
|
|
32
|
+
## Limitations
|
|
33
|
+
|
|
34
|
+
- **Platform constraint**: Claude Code's turn-based model means the prompt hook only fires when a subagent completes — it cannot wake the model from true idle state
|
|
35
|
+
- **Foreground agents preferred**: For guaranteed continuation, use foreground parallel agents (R009) instead of background agents
|
|
36
|
+
- **PoC status**: This is an experimental feature. If the prompt hook doesn't reliably trigger in all scenarios, fall back to foreground agent patterns
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
/omcustom:loop # Show current auto-continuation status
|
|
42
|
+
/omcustom:loop status # Same as above
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The feature is active by default via hooks.json. No explicit activation needed.
|
|
@@ -17,8 +17,8 @@ Replaces the CI-based `release-notes.yml` workflow that previously used Claude A
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
/omcustom
|
|
21
|
-
/omcustom
|
|
20
|
+
/omcustom-release-notes 0.36.0
|
|
21
|
+
/omcustom-release-notes 0.36.0 --previous-tag v0.35.3
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## Workflow
|
|
@@ -105,7 +105,7 @@ This skill is designed to be used during the release process:
|
|
|
105
105
|
|
|
106
106
|
```
|
|
107
107
|
/omcustom:npm-version patch|minor|major -> version bump
|
|
108
|
-
/omcustom
|
|
108
|
+
/omcustom-release-notes {version} -> generate notes
|
|
109
109
|
mgr-gitnerd: gh release create -> create release with notes
|
|
110
110
|
```
|
|
111
111
|
|
|
@@ -17,8 +17,8 @@ When an agent or skill has evolved organically without a formal spec, `takeover`
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
/omcustom
|
|
21
|
-
/omcustom
|
|
20
|
+
/omcustom-takeover <agent-name>
|
|
21
|
+
/omcustom-takeover <skill-name>
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## Workflow
|
|
@@ -110,6 +110,6 @@ generated: <ISO-8601 timestamp>
|
|
|
110
110
|
## Notes
|
|
111
111
|
|
|
112
112
|
- Specs are git-untracked (under `.claude/`)
|
|
113
|
-
- Regenerate anytime with `/omcustom
|
|
113
|
+
- Regenerate anytime with `/omcustom-takeover <name>`
|
|
114
114
|
- Used by `/dev-refactor --spec` for invariant-preserving refactoring
|
|
115
115
|
- Advisory output — human review recommended before using as contract
|
|
@@ -59,6 +59,11 @@ if [[ -z "$json" ]]; then
|
|
|
59
59
|
exit 0
|
|
60
60
|
fi
|
|
61
61
|
|
|
62
|
+
# Debug logging for CTX investigation
|
|
63
|
+
if [[ -n "${STATUSLINE_DEBUG}" ]]; then
|
|
64
|
+
printf '%s\n' "$json" >> "/tmp/.claude-statusline-debug-${PPID}.jsonl"
|
|
65
|
+
fi
|
|
66
|
+
|
|
62
67
|
# ---------------------------------------------------------------------------
|
|
63
68
|
# 4. Single jq call — extract all fields as TSV
|
|
64
69
|
# Fields: model_name, project_dir, ctx_pct, ctx_size, cost_usd, rl_5h_pct
|
|
@@ -67,7 +72,7 @@ IFS=$'\t' read -r model_name project_dir ctx_pct ctx_size cost_usd rl_5h_pct <<<
|
|
|
67
72
|
printf '%s' "$json" | jq -r '[
|
|
68
73
|
(.model.display_name // "unknown"),
|
|
69
74
|
(.workspace.current_dir // ""),
|
|
70
|
-
(.context_window.used_percentage
|
|
75
|
+
(if .context_window.used != null and .context_window.total != null and .context_window.total > 0 then (.context_window.used / .context_window.total * 100) elif .context_window.used_percentage != null then .context_window.used_percentage else 0 end),
|
|
71
76
|
(.context_window.context_window_size // 0),
|
|
72
77
|
(.cost.total_cost_usd // 0),
|
|
73
78
|
(.rate_limits.five_hour.used_percentage // -1)
|
|
@@ -78,7 +83,8 @@ IFS=$'\t' read -r model_name project_dir ctx_pct ctx_size cost_usd rl_5h_pct <<<
|
|
|
78
83
|
# 4b. Cost & context data bridge — write to temp file for hooks
|
|
79
84
|
# ---------------------------------------------------------------------------
|
|
80
85
|
COST_BRIDGE_FILE="/tmp/.claude-cost-${PPID}"
|
|
81
|
-
|
|
86
|
+
_tmp="${COST_BRIDGE_FILE}.tmp.$$"
|
|
87
|
+
printf '%s\t%s\t%s\t%s\n' "$cost_usd" "$ctx_pct" "$(date +%s)" "$rl_5h_pct" > "$_tmp" 2>/dev/null && mv -f "$_tmp" "$COST_BRIDGE_FILE" 2>/dev/null || true
|
|
82
88
|
|
|
83
89
|
# ---------------------------------------------------------------------------
|
|
84
90
|
# 5. Model display name + color (bash 3.2 compatible case pattern matching)
|
package/templates/CLAUDE.md
CHANGED
|
@@ -101,7 +101,7 @@ oh-my-customcode로 구동됩니다.
|
|
|
101
101
|
| `/omcustom:update-external` | 외부 소스에서 에이전트 업데이트 |
|
|
102
102
|
| `/omcustom:audit-agents` | 에이전트 의존성 감사 |
|
|
103
103
|
| `/omcustom:fix-refs` | 깨진 참조 수정 |
|
|
104
|
-
| `/omcustom
|
|
104
|
+
| `/omcustom-takeover` | 기존 에이전트/스킬에서 canonical spec 추출 |
|
|
105
105
|
| `/adversarial-review` | 공격자 관점 보안 코드 리뷰 |
|
|
106
106
|
| `/ambiguity-gate` | 요청 모호성 분석 및 명확화 질문 (ouroboros 패턴) |
|
|
107
107
|
| `/dev-review` | 코드 베스트 프랙티스 리뷰 |
|
|
@@ -112,8 +112,8 @@ oh-my-customcode로 구동됩니다.
|
|
|
112
112
|
| `/omcustom:npm-publish` | npm 레지스트리에 패키지 배포 |
|
|
113
113
|
| `/omcustom:npm-version` | 시맨틱 버전 관리 |
|
|
114
114
|
| `/omcustom:npm-audit` | 의존성 감사 |
|
|
115
|
-
| `/omcustom
|
|
116
|
-
| `/omcustom
|
|
115
|
+
| `/omcustom-release-notes` | 릴리즈 노트 생성 (git 히스토리 기반) |
|
|
116
|
+
| `/omcustom-feedback` | 사용자 피드백을 GitHub Issue로 등록 |
|
|
117
117
|
| `/codex-exec` | Codex CLI 프롬프트 실행 |
|
|
118
118
|
| `/optimize-analyze` | 번들 및 성능 분석 |
|
|
119
119
|
| `/optimize-bundle` | 번들 크기 최적화 |
|
|
@@ -122,6 +122,7 @@ oh-my-customcode로 구동됩니다.
|
|
|
122
122
|
| `/deep-plan` | 연구 검증 기반 계획 수립 (research → plan → verify) |
|
|
123
123
|
| `/omcustom:sauron-watch` | 전체 R017 검증 |
|
|
124
124
|
| `/structured-dev-cycle` | 6단계 구조적 개발 사이클 (Plan → Verify → Implement → Verify → Compound → Done) |
|
|
125
|
+
| `/omcustom:loop` | 백그라운드 에이전트 자동 계속 실행 |
|
|
125
126
|
| `/omcustom:lists` | 모든 사용 가능한 커맨드 표시 |
|
|
126
127
|
| `/omcustom:status` | 시스템 상태 표시 |
|
|
127
128
|
| `/omcustom:help` | 도움말 표시 |
|
|
@@ -132,12 +133,12 @@ oh-my-customcode로 구동됩니다.
|
|
|
132
133
|
project/
|
|
133
134
|
+-- CLAUDE.md # 진입점
|
|
134
135
|
+-- .claude/
|
|
135
|
-
| +-- agents/ # 서브에이전트 정의 (
|
|
136
|
-
| +-- skills/ # 스킬 (
|
|
136
|
+
| +-- agents/ # 서브에이전트 정의 (45 파일)
|
|
137
|
+
| +-- skills/ # 스킬 (83 디렉토리)
|
|
137
138
|
| +-- rules/ # 전역 규칙 (R000-R021)
|
|
138
139
|
| +-- hooks/ # 훅 스크립트 (보안, 검증, HUD)
|
|
139
140
|
| +-- contexts/ # 컨텍스트 파일 (ecomode)
|
|
140
|
-
+-- guides/ # 레퍼런스 문서 (
|
|
141
|
+
+-- guides/ # 레퍼런스 문서 (28 토픽)
|
|
141
142
|
```
|
|
142
143
|
|
|
143
144
|
## 오케스트레이션
|
|
@@ -185,14 +186,14 @@ oh-my-customcode는 소프트웨어 컴파일과 동일한 구조를 따릅니
|
|
|
185
186
|
| SW Engineer/Frontend | 4 | fe-vercel-agent, fe-vuejs-agent, fe-svelte-agent, fe-flutter-agent |
|
|
186
187
|
| SW Engineer/Tooling | 3 | tool-npm-expert, tool-optimizer, tool-bun-expert |
|
|
187
188
|
| DE Engineer | 6 | de-airflow-expert, de-dbt-expert, de-spark-expert, de-kafka-expert, de-snowflake-expert, de-pipeline-expert |
|
|
188
|
-
| SW Engineer/Database |
|
|
189
|
+
| SW Engineer/Database | 4 | db-supabase-expert, db-postgres-expert, db-redis-expert, db-alembic-expert |
|
|
189
190
|
| Security | 1 | sec-codeql-expert |
|
|
190
191
|
| SW Architect | 2 | arch-documenter, arch-speckit-agent |
|
|
191
192
|
| Infra Engineer | 2 | infra-docker-expert, infra-aws-expert |
|
|
192
193
|
| QA Team | 3 | qa-planner, qa-writer, qa-engineer |
|
|
193
194
|
| Manager | 6 | mgr-creator, mgr-updater, mgr-supplier, mgr-gitnerd, mgr-sauron, mgr-claude-code-bible |
|
|
194
195
|
| System | 2 | sys-memory-keeper, sys-naggy |
|
|
195
|
-
| **총계** | **
|
|
196
|
+
| **총계** | **45** | |
|
|
196
197
|
|
|
197
198
|
## Agent Teams (MUST when enabled)
|
|
198
199
|
|
package/templates/CLAUDE.md.en
CHANGED
|
@@ -100,7 +100,7 @@ NO EXCEPTIONS. NO EXCUSES.
|
|
|
100
100
|
| `/omcustom:update-external` | Update agents from external sources |
|
|
101
101
|
| `/omcustom:audit-agents` | Audit agent dependencies |
|
|
102
102
|
| `/omcustom:fix-refs` | Fix broken references |
|
|
103
|
-
| `/omcustom
|
|
103
|
+
| `/omcustom-takeover` | Extract canonical spec from existing agent/skill |
|
|
104
104
|
| `/dev-review` | Review code for best practices |
|
|
105
105
|
| `/dev-refactor` | Refactor code |
|
|
106
106
|
| `/memory-save` | Save session context to claude-mem |
|
|
@@ -109,7 +109,7 @@ NO EXCEPTIONS. NO EXCUSES.
|
|
|
109
109
|
| `/omcustom:npm-publish` | Publish package to npm registry |
|
|
110
110
|
| `/omcustom:npm-version` | Manage semantic versions |
|
|
111
111
|
| `/omcustom:npm-audit` | Audit dependencies |
|
|
112
|
-
| `/omcustom
|
|
112
|
+
| `/omcustom-release-notes` | Generate release notes from git history |
|
|
113
113
|
| `/codex-exec` | Execute Codex CLI prompts |
|
|
114
114
|
| `/optimize-analyze` | Analyze bundle and performance |
|
|
115
115
|
| `/optimize-bundle` | Optimize bundle size |
|
package/templates/CLAUDE.md.ko
CHANGED
|
@@ -100,7 +100,7 @@ oh-my-customcode로 구동됩니다.
|
|
|
100
100
|
| `/omcustom:update-external` | 외부 소스에서 에이전트 업데이트 |
|
|
101
101
|
| `/omcustom:audit-agents` | 에이전트 의존성 감사 |
|
|
102
102
|
| `/omcustom:fix-refs` | 깨진 참조 수정 |
|
|
103
|
-
| `/omcustom
|
|
103
|
+
| `/omcustom-takeover` | 기존 에이전트/스킬에서 canonical spec 추출 |
|
|
104
104
|
| `/dev-review` | 코드 베스트 프랙티스 리뷰 |
|
|
105
105
|
| `/dev-refactor` | 코드 리팩토링 |
|
|
106
106
|
| `/memory-save` | 세션 컨텍스트를 claude-mem에 저장 |
|
|
@@ -109,7 +109,7 @@ oh-my-customcode로 구동됩니다.
|
|
|
109
109
|
| `/omcustom:npm-publish` | npm 레지스트리에 패키지 배포 |
|
|
110
110
|
| `/omcustom:npm-version` | 시맨틱 버전 관리 |
|
|
111
111
|
| `/omcustom:npm-audit` | 의존성 감사 |
|
|
112
|
-
| `/omcustom
|
|
112
|
+
| `/omcustom-release-notes` | 릴리즈 노트 생성 (git 히스토리 기반) |
|
|
113
113
|
| `/codex-exec` | Codex CLI 프롬프트 실행 |
|
|
114
114
|
| `/optimize-analyze` | 번들 및 성능 분석 |
|
|
115
115
|
| `/optimize-bundle` | 번들 크기 최적화 |
|
package/templates/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.47.1",
|
|
3
3
|
"lastUpdated": "2026-03-16T00:00:00.000Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"name": "skills",
|
|
19
19
|
"path": ".claude/skills",
|
|
20
20
|
"description": "Reusable skill modules (includes slash commands)",
|
|
21
|
-
"files":
|
|
21
|
+
"files": 83
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"name": "guides",
|