oh-my-customcode 0.84.0 → 0.86.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/README.md +3 -16
- package/dist/cli/index.js +53 -44
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/.claude/skills/hada-scout/SKILL.md +92 -0
- package/templates/CLAUDE.md +1 -1
- package/templates/manifest.json +3 -3
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
**[한국어 문서 (Korean)](./README_ko.md)**
|
|
15
15
|
|
|
16
|
-
48 agents.
|
|
16
|
+
48 agents. 105 skills. 22 rules. One command.
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
npm install -g oh-my-customcode && cd your-project && omcustom init
|
|
@@ -21,19 +21,6 @@ npm install -g oh-my-customcode && cd your-project && omcustom init
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
## What's New in v0.74.0
|
|
25
|
-
|
|
26
|
-
| Feature | Description |
|
|
27
|
-
|---------|-------------|
|
|
28
|
-
| **`omcustom sync`** | Drift detection for `.claude/` configuration — compare against lockfile, export team snapshots |
|
|
29
|
-
| **`omcustom init --from-snapshot`** | Team reproducibility — install from pre-configured snapshot directory |
|
|
30
|
-
| **`analysis --interview`** | Interactive AI architecture interview before file-based project detection |
|
|
31
|
-
| **skill-extractor** | 100th skill — analyze task trajectories to propose reusable SKILL.md candidates |
|
|
32
|
-
| **User Model** | Structured tracking of correction patterns, skill preferences, expertise profile |
|
|
33
|
-
| **Release Cleanup** | Auto-close linked issues and delete release branches on PR merge |
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
24
|
## Philosophy
|
|
38
25
|
|
|
39
26
|
oh-my-customcode is built on two ideas:
|
|
@@ -145,7 +132,7 @@ Each agent declares its tools, model, memory scope, and limitations in YAML fron
|
|
|
145
132
|
|
|
146
133
|
---
|
|
147
134
|
|
|
148
|
-
### Skills (
|
|
135
|
+
### Skills (105)
|
|
149
136
|
|
|
150
137
|
| Category | Count | Includes |
|
|
151
138
|
|----------|-------|----------|
|
|
@@ -285,7 +272,7 @@ your-project/
|
|
|
285
272
|
├── CLAUDE.md # Entry point
|
|
286
273
|
├── .claude/
|
|
287
274
|
│ ├── agents/ # 48 agent definitions
|
|
288
|
-
│ ├── skills/ #
|
|
275
|
+
│ ├── skills/ # 105 skill modules
|
|
289
276
|
│ ├── rules/ # 22 governance rules (R000-R021)
|
|
290
277
|
│ ├── hooks/ # 15 lifecycle hook scripts
|
|
291
278
|
│ ├── schemas/ # Tool input validation schemas
|
package/dist/cli/index.js
CHANGED
|
@@ -2301,7 +2301,7 @@ var init_package = __esm(() => {
|
|
|
2301
2301
|
workspaces: [
|
|
2302
2302
|
"packages/*"
|
|
2303
2303
|
],
|
|
2304
|
-
version: "0.
|
|
2304
|
+
version: "0.86.0",
|
|
2305
2305
|
description: "Batteries-included agent harness for Claude Code",
|
|
2306
2306
|
type: "module",
|
|
2307
2307
|
bin: {
|
|
@@ -9608,6 +9608,23 @@ async function getTemplateVersion() {
|
|
|
9608
9608
|
}
|
|
9609
9609
|
return package_default.version;
|
|
9610
9610
|
}
|
|
9611
|
+
function matchesSearchPaths(projectPath, searchPaths) {
|
|
9612
|
+
if (!searchPaths || searchPaths.length === 0)
|
|
9613
|
+
return true;
|
|
9614
|
+
return searchPaths.some((searchPath) => projectPath === searchPath || projectPath.startsWith(searchPath + sep2));
|
|
9615
|
+
}
|
|
9616
|
+
function isUnderHome(projectPath, home) {
|
|
9617
|
+
return projectPath.startsWith(home + sep2) || projectPath === home;
|
|
9618
|
+
}
|
|
9619
|
+
function sortProjects(projects) {
|
|
9620
|
+
return projects.sort((a, b) => {
|
|
9621
|
+
if (a.status === "latest" && b.status !== "latest")
|
|
9622
|
+
return -1;
|
|
9623
|
+
if (a.status !== "latest" && b.status === "latest")
|
|
9624
|
+
return 1;
|
|
9625
|
+
return a.name.localeCompare(b.name);
|
|
9626
|
+
});
|
|
9627
|
+
}
|
|
9611
9628
|
async function findProjects(options = {}) {
|
|
9612
9629
|
const currentVersion = await getTemplateVersion();
|
|
9613
9630
|
const registry = await readRegistry();
|
|
@@ -9619,12 +9636,12 @@ async function findProjects(options = {}) {
|
|
|
9619
9636
|
return fallbackResults;
|
|
9620
9637
|
}
|
|
9621
9638
|
const results = [];
|
|
9639
|
+
const home = process.env.HOME ?? homedir3();
|
|
9622
9640
|
for (const [projectPath, entry] of Object.entries(registry.projects)) {
|
|
9623
|
-
if (
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
}
|
|
9641
|
+
if (!matchesSearchPaths(projectPath, options.paths))
|
|
9642
|
+
continue;
|
|
9643
|
+
if (!isUnderHome(projectPath, home))
|
|
9644
|
+
continue;
|
|
9628
9645
|
results.push({
|
|
9629
9646
|
name: basename4(projectPath),
|
|
9630
9647
|
path: projectPath,
|
|
@@ -9635,31 +9652,21 @@ async function findProjects(options = {}) {
|
|
|
9635
9652
|
detectionMethod: "registry"
|
|
9636
9653
|
});
|
|
9637
9654
|
}
|
|
9638
|
-
return results
|
|
9639
|
-
if (a.status === "latest" && b.status !== "latest")
|
|
9640
|
-
return -1;
|
|
9641
|
-
if (a.status !== "latest" && b.status === "latest")
|
|
9642
|
-
return 1;
|
|
9643
|
-
return a.name.localeCompare(b.name);
|
|
9644
|
-
});
|
|
9655
|
+
return sortProjects(results);
|
|
9645
9656
|
}
|
|
9646
|
-
async function
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
return;
|
|
9660
|
-
}
|
|
9661
|
-
const lockFile = await readLockFile(dir2);
|
|
9662
|
-
if (lockFile) {
|
|
9657
|
+
async function _scanDirForLockfiles(dir2, depth, seen, results, home, currentVersion, fs2) {
|
|
9658
|
+
if (depth > 3 || seen.has(dir2))
|
|
9659
|
+
return;
|
|
9660
|
+
seen.add(dir2);
|
|
9661
|
+
let entries;
|
|
9662
|
+
try {
|
|
9663
|
+
entries = await fs2.readdir(dir2, { withFileTypes: true });
|
|
9664
|
+
} catch {
|
|
9665
|
+
return;
|
|
9666
|
+
}
|
|
9667
|
+
const lockFile = await readLockFile(dir2);
|
|
9668
|
+
if (lockFile) {
|
|
9669
|
+
if (isUnderHome(dir2, home)) {
|
|
9663
9670
|
const version = lockFile.version || lockFile.templateVersion || null;
|
|
9664
9671
|
results.push({
|
|
9665
9672
|
name: basename4(dir2),
|
|
@@ -9670,13 +9677,20 @@ async function _findProjectsFromLockfiles(options, currentVersion) {
|
|
|
9670
9677
|
status: computeStatus(version, currentVersion),
|
|
9671
9678
|
detectionMethod: "lockfile"
|
|
9672
9679
|
});
|
|
9673
|
-
return;
|
|
9674
|
-
}
|
|
9675
|
-
if (depth < 3) {
|
|
9676
|
-
const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist" && e.name !== "build" && e.name !== ".git");
|
|
9677
|
-
await Promise.all(subdirs.map((sub) => scanDir(join10(dir2, sub.name), depth + 1).catch(() => {})));
|
|
9678
9680
|
}
|
|
9681
|
+
return;
|
|
9679
9682
|
}
|
|
9683
|
+
if (depth < 3) {
|
|
9684
|
+
const subdirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SCAN_SKIP_DIRS2.has(e.name));
|
|
9685
|
+
await Promise.all(subdirs.map((sub) => _scanDirForLockfiles(join10(dir2, sub.name), depth + 1, seen, results, home, currentVersion, fs2).catch(() => {})));
|
|
9686
|
+
}
|
|
9687
|
+
}
|
|
9688
|
+
async function _findProjectsFromLockfiles(options, currentVersion) {
|
|
9689
|
+
const { dirname: dirname3 } = await import("node:path");
|
|
9690
|
+
const fs2 = await import("node:fs/promises");
|
|
9691
|
+
const seen = new Set;
|
|
9692
|
+
const results = [];
|
|
9693
|
+
const home = process.env.HOME ?? homedir3();
|
|
9680
9694
|
const searchPaths = options.paths ? [...options.paths] : [];
|
|
9681
9695
|
if (!options.paths) {
|
|
9682
9696
|
const cwd = process.cwd();
|
|
@@ -9686,14 +9700,8 @@ async function _findProjectsFromLockfiles(options, currentVersion) {
|
|
|
9686
9700
|
if (parent !== cwd && !searchPaths.includes(parent))
|
|
9687
9701
|
searchPaths.push(parent);
|
|
9688
9702
|
}
|
|
9689
|
-
await Promise.all(searchPaths.map((p) =>
|
|
9690
|
-
return results
|
|
9691
|
-
if (a.status === "latest" && b.status !== "latest")
|
|
9692
|
-
return -1;
|
|
9693
|
-
if (a.status !== "latest" && b.status === "latest")
|
|
9694
|
-
return 1;
|
|
9695
|
-
return a.name.localeCompare(b.name);
|
|
9696
|
-
});
|
|
9703
|
+
await Promise.all(searchPaths.map((p) => _scanDirForLockfiles(p, 0, seen, results, home, currentVersion, fs2).catch(() => {})));
|
|
9704
|
+
return sortProjects(results);
|
|
9697
9705
|
}
|
|
9698
9706
|
async function writeLockFile(projectDir, version, existing) {
|
|
9699
9707
|
const fs2 = await import("node:fs/promises");
|
|
@@ -9814,11 +9822,12 @@ async function projectsCommand(options = {}) {
|
|
|
9814
9822
|
return { success: false, projects: [], currentVersion, errors: [errorMessage] };
|
|
9815
9823
|
}
|
|
9816
9824
|
}
|
|
9817
|
-
var projects_default;
|
|
9825
|
+
var SCAN_SKIP_DIRS2, projects_default;
|
|
9818
9826
|
var init_projects = __esm(() => {
|
|
9819
9827
|
init_package();
|
|
9820
9828
|
init_registry();
|
|
9821
9829
|
init_fs();
|
|
9830
|
+
SCAN_SKIP_DIRS2 = new Set(["node_modules", "dist", "build", ".git"]);
|
|
9822
9831
|
projects_default = projectsCommand;
|
|
9823
9832
|
});
|
|
9824
9833
|
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hada-scout
|
|
3
|
+
description: hada.io RSS feed monitoring for AI agent/harness articles with automated /scout analysis
|
|
4
|
+
scope: package
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
user-invocable: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# hada-scout
|
|
10
|
+
|
|
11
|
+
Automated pipeline that monitors hada.io (via feedburner RSS) for AI agent, harness, benchmark, and eval-related articles, then runs `/scout` analysis on each match.
|
|
12
|
+
|
|
13
|
+
## Purpose
|
|
14
|
+
|
|
15
|
+
Complement geeknews-scout with harness/eval-focused coverage from hada.io. While geeknews-scout casts a broad net over AI agent news, hada-scout narrows to benchmark/evaluation framework content — the domain most relevant to oh-my-customcode's harness and agent-eval subsystems.
|
|
16
|
+
|
|
17
|
+
## Architecture: 2-Layer Hybrid
|
|
18
|
+
|
|
19
|
+
### Layer 1 — check-feed.sh (feed → issues)
|
|
20
|
+
|
|
21
|
+
1. Fetch hada.io feedburner RSS
|
|
22
|
+
2. Filter entries by keyword regex (case-insensitive)
|
|
23
|
+
3. Dedup against existing `hada-scout` issues
|
|
24
|
+
4. Create GitHub issue per match with labels `hada-scout` + `pending-scout`
|
|
25
|
+
|
|
26
|
+
### Layer 2 — scout-runner.sh (issues → /scout)
|
|
27
|
+
|
|
28
|
+
1. Find open issues with `pending-scout` label
|
|
29
|
+
2. Extract source URL from issue body
|
|
30
|
+
3. Run `claude -p "/scout {url}"` (max 5 executions per run)
|
|
31
|
+
4. Parse verdict from /scout output
|
|
32
|
+
5. Apply verdict label, remove `pending-scout`
|
|
33
|
+
|
|
34
|
+
## Keyword Strategy
|
|
35
|
+
|
|
36
|
+
hada-scout uses harness/benchmark/eval focused keywords, distinct from geeknews-scout's broader AI agent coverage:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
harness|benchmark|eval|evaluation framework|agent framework|코드 리뷰 자동화|하네스|벤치마크|평가
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Geeknews-scout handles: `Claude|Anthropic|MCP|AI agent|에이전트|agentic|multi-agent|...`
|
|
43
|
+
|
|
44
|
+
## Label Scheme
|
|
45
|
+
|
|
46
|
+
| Label | Purpose |
|
|
47
|
+
|-------|---------|
|
|
48
|
+
| `hada-scout` | Source identification — all hada-scout created issues |
|
|
49
|
+
| `pending-scout` | Awaiting /scout analysis (set by Layer 1, cleared by Layer 2) |
|
|
50
|
+
| `scout:internalize` | /scout verdict: adopt into project |
|
|
51
|
+
| `scout:integrate` | /scout verdict: use as external dependency |
|
|
52
|
+
| `scout:skip` | /scout verdict: not relevant |
|
|
53
|
+
|
|
54
|
+
## Cost Controls
|
|
55
|
+
|
|
56
|
+
- Layer 2 runs at most **5 /scout executions per cron invocation**
|
|
57
|
+
- Each /scout call costs ~$0.5–1.5 (sonnet)
|
|
58
|
+
- Remaining `pending-scout` issues are processed in the next scheduled run
|
|
59
|
+
|
|
60
|
+
## Deployment
|
|
61
|
+
|
|
62
|
+
- Pattern: same K8s CronJob structure as `infra/geeknews-scout/`
|
|
63
|
+
- Host: `ubuntu-ext` cluster
|
|
64
|
+
- Infrastructure files: `infra/hada-scout/`
|
|
65
|
+
- `check-feed.sh` — Layer 1 feed poller
|
|
66
|
+
- `scout-runner.sh` — Layer 2 /scout executor
|
|
67
|
+
- `Dockerfile`
|
|
68
|
+
- `cronjob.template.yaml`
|
|
69
|
+
- `deploy.sh`
|
|
70
|
+
- `.env.example`
|
|
71
|
+
|
|
72
|
+
## Environment Variables
|
|
73
|
+
|
|
74
|
+
| Variable | Default | Description |
|
|
75
|
+
|----------|---------|-------------|
|
|
76
|
+
| `GH_TOKEN` | (required) | GitHub PAT for issue creation |
|
|
77
|
+
| `REPO` | `baekenough/oh-my-customcode` | Target repo |
|
|
78
|
+
| `FEED_URL` | `http://feeds.feedburner.com/geeknews-feed` | hada.io RSS feed |
|
|
79
|
+
| `KEYWORDS` | (see above) | Pipe-separated keyword regex |
|
|
80
|
+
| `MAX_SCOUT_PER_RUN` | `5` | Max /scout executions per Layer 2 run |
|
|
81
|
+
|
|
82
|
+
## Integration
|
|
83
|
+
|
|
84
|
+
| Rule | How |
|
|
85
|
+
|------|-----|
|
|
86
|
+
| R009 | Layer 1 and Layer 2 run as independent CronJobs |
|
|
87
|
+
| R010 | Skill defines the architecture; implementation delegated to infra-docker-expert for K8s manifests |
|
|
88
|
+
| scout skill | Layer 2 invokes `/scout` via `claude -p` subprocess |
|
|
89
|
+
|
|
90
|
+
## Tracking Issue
|
|
91
|
+
|
|
92
|
+
GitHub Issue #841
|
package/templates/CLAUDE.md
CHANGED
|
@@ -157,7 +157,7 @@ project/
|
|
|
157
157
|
+-- CLAUDE.md # 진입점
|
|
158
158
|
+-- .claude/
|
|
159
159
|
| +-- agents/ # 서브에이전트 정의 (48 파일)
|
|
160
|
-
| +-- skills/ # 스킬 (
|
|
160
|
+
| +-- skills/ # 스킬 (105 디렉토리)
|
|
161
161
|
| +-- rules/ # 전역 규칙 (R000-R022)
|
|
162
162
|
| +-- hooks/ # 훅 스크립트 (보안, 검증, HUD)
|
|
163
163
|
| +-- contexts/ # 컨텍스트 파일 (ecomode)
|
package/templates/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"lastUpdated": "2026-04-
|
|
2
|
+
"version": "0.86.0",
|
|
3
|
+
"lastUpdated": "2026-04-13T00:00:00.000Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"name": "rules",
|
|
@@ -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": 105
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"name": "guides",
|