oh-my-customcode 1.1.107 → 2.0.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 +1 -1
- package/dist/cli/index.js +14 -40
- package/dist/index.js +14 -40
- package/package.json +1 -1
- package/templates/.claude/hooks/scripts/claude-md-reinject.sh +1 -7
- package/templates/.claude/hooks/scripts/session-reflection.sh +15 -60
- package/templates/.claude/hooks/scripts/stuck-detector.sh +14 -110
- package/templates/.claude/rules/MAY-optimization.md +1 -1
- package/templates/.claude/rules/SHOULD-memory-integration.md +1 -1
- package/templates/.claude/rules/SHOULD-verification-ladder.md +0 -9
- package/templates/.claude/skills/pipeline/workflows/auto-dev.yaml +1 -3
- package/templates/guides/external-tools/ecc-absorption-decisions.md +1 -1
- package/templates/guides/multi-provider-exec/README.md +1 -1
- package/templates/manifest.json +1 -1
- package/templates/workflows/auto-dev.yaml +1 -3
package/README.md
CHANGED
|
@@ -296,7 +296,7 @@ your-project/
|
|
|
296
296
|
|
|
297
297
|
## External Tool Integrations
|
|
298
298
|
|
|
299
|
-
Starting with
|
|
299
|
+
Starting with v2.0.0, project RTK integration is retired: the public `/rtk-exec` skill, automatic installation, doctor integration, and RTK hooks are removed. Separately installed RTK binaries remain on your machine. Updates automatically remove retired managed files only when their hashes match the previous lockfile. Modified files and files with unknown ownership are preserved and reported as conflicts; retained user skill files may still be discovered automatically. Other tools are optional:
|
|
300
300
|
|
|
301
301
|
| Tool | Purpose | Install | Status |
|
|
302
302
|
|------|---------|---------|--------|
|
package/dist/cli/index.js
CHANGED
|
@@ -335,7 +335,7 @@ var init_package = __esm(() => {
|
|
|
335
335
|
workspaces: [
|
|
336
336
|
"packages/*"
|
|
337
337
|
],
|
|
338
|
-
version: "
|
|
338
|
+
version: "2.0.0",
|
|
339
339
|
description: "Batteries-included agent harness for Claude Code",
|
|
340
340
|
type: "module",
|
|
341
341
|
bin: {
|
|
@@ -15800,7 +15800,7 @@ var MESSAGES = {
|
|
|
15800
15800
|
"update.file_applied": "Applied update to {{path}}",
|
|
15801
15801
|
"update.lockfile_regenerated": "Lockfile regenerated ({{files}} files tracked)",
|
|
15802
15802
|
"update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
|
|
15803
|
-
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}}
|
|
15803
|
+
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} updated: {{hint}}",
|
|
15804
15804
|
"update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
|
|
15805
15805
|
"update.hook_commands_migrated": "Migrated {{count}} hook/statusLine command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
15806
15806
|
"update.hook_commands_migration_failed": "Failed to migrate hook commands in {{path}}",
|
|
@@ -15836,8 +15836,8 @@ var MESSAGES = {
|
|
|
15836
15836
|
"update.entry_doc_force_updated": "Entry document force-updated: {{path}}",
|
|
15837
15837
|
"update.entry_merge_warning": "Entry document merge warning: {{warning}}",
|
|
15838
15838
|
"update.entry_template_not_found": "Entry document template not found: {{template}}",
|
|
15839
|
-
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}}
|
|
15840
|
-
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped:
|
|
15839
|
+
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}} overwritten: {{hint}}",
|
|
15840
|
+
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped: {{hint}}",
|
|
15841
15841
|
"update.settings_local_backfill_failed": "Failed to backfill statusLine refreshInterval in {{path}}",
|
|
15842
15842
|
"general.done": "Done!",
|
|
15843
15843
|
"general.failed": "Failed",
|
|
@@ -15913,7 +15913,7 @@ var MESSAGES = {
|
|
|
15913
15913
|
"update.file_applied": "{{path}} 업데이트 적용",
|
|
15914
15914
|
"update.lockfile_regenerated": "잠금 파일 재생성 완료 ({{files}}개 파일 추적)",
|
|
15915
15915
|
"update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
|
|
15916
|
-
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}})
|
|
15916
|
+
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트됨: {{hint}}",
|
|
15917
15917
|
"update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
|
|
15918
15918
|
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로 훅/상태줄 명령 {{count}}개를 마이그레이션했습니다",
|
|
15919
15919
|
"update.hook_commands_migration_failed": "{{path}}의 훅 명령 마이그레이션에 실패했습니다",
|
|
@@ -15949,8 +15949,8 @@ var MESSAGES = {
|
|
|
15949
15949
|
"update.entry_doc_force_updated": "진입 문서를 강제로 업데이트했습니다: {{path}}",
|
|
15950
15950
|
"update.entry_merge_warning": "진입 문서 병합 경고: {{warning}}",
|
|
15951
15951
|
"update.entry_template_not_found": "진입 문서 템플릿을 찾을 수 없습니다: {{template}}",
|
|
15952
|
-
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}}
|
|
15953
|
-
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다:
|
|
15952
|
+
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}}을(를) 덮어썼습니다: {{hint}}",
|
|
15953
|
+
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다: {{hint}}",
|
|
15954
15954
|
"update.settings_local_backfill_failed": "{{path}}의 statusLine refreshInterval 보충에 실패했습니다",
|
|
15955
15955
|
"general.done": "완료!",
|
|
15956
15956
|
"general.failed": "실패",
|
|
@@ -22135,29 +22135,6 @@ async function runFullUpdatePostProcessing(options, result, config, customizatio
|
|
|
22135
22135
|
});
|
|
22136
22136
|
}
|
|
22137
22137
|
}
|
|
22138
|
-
async function getUpdateVersionDecision(installedVersion, cliVersion) {
|
|
22139
|
-
if (!(installedVersion !== "0.0.0" && compareSemver2(installedVersion, cliVersion) > 0)) {
|
|
22140
|
-
return "standard";
|
|
22141
|
-
}
|
|
22142
|
-
if (installedVersion !== "2.0.0") {
|
|
22143
|
-
return "blocked";
|
|
22144
|
-
}
|
|
22145
|
-
const match = /^1\.1\.(0|[1-9]\d*)$/.exec(cliVersion);
|
|
22146
|
-
if (!match) {
|
|
22147
|
-
return "blocked";
|
|
22148
|
-
}
|
|
22149
|
-
const patch = Number(match[1]);
|
|
22150
|
-
if (!Number.isSafeInteger(patch) || patch < 107) {
|
|
22151
|
-
return "blocked";
|
|
22152
|
-
}
|
|
22153
|
-
const manifestPath = resolveTemplatePath(getProviderLayout().manifestFile);
|
|
22154
|
-
if (!await fileExists(manifestPath)) {
|
|
22155
|
-
return "blocked";
|
|
22156
|
-
}
|
|
22157
|
-
const manifest = await readJsonFile(manifestPath);
|
|
22158
|
-
const correctionAllowed = typeof manifest === "object" && manifest !== null && !Array.isArray(manifest) && "version" in manifest && manifest.version === cliVersion;
|
|
22159
|
-
return correctionAllowed ? "correction" : "blocked";
|
|
22160
|
-
}
|
|
22161
22138
|
function compareSemver2(a, b) {
|
|
22162
22139
|
const pa = a.split(".").map(Number);
|
|
22163
22140
|
const pb = b.split(".").map(Number);
|
|
@@ -22218,8 +22195,7 @@ async function update(options) {
|
|
|
22218
22195
|
const config = await loadConfig(options.targetDir);
|
|
22219
22196
|
result.previousVersion = config.version;
|
|
22220
22197
|
const cliVersion = package_default.version;
|
|
22221
|
-
|
|
22222
|
-
if (versionDecision === "blocked") {
|
|
22198
|
+
if (result.previousVersion !== "0.0.0" && compareSemver2(result.previousVersion, cliVersion) > 0) {
|
|
22223
22199
|
result.success = false;
|
|
22224
22200
|
result.error = `Downgrade prevented: project has v${result.previousVersion} but CLI is v${cliVersion}. Update the CLI first: npm install -g oh-my-customcode@latest`;
|
|
22225
22201
|
return result;
|
|
@@ -22228,11 +22204,6 @@ async function update(options) {
|
|
|
22228
22204
|
return result;
|
|
22229
22205
|
}
|
|
22230
22206
|
const updateCheck = await checkForUpdates(options.targetDir);
|
|
22231
|
-
if (versionDecision === "correction" && updateCheck.latestVersion !== cliVersion) {
|
|
22232
|
-
result.success = false;
|
|
22233
|
-
result.error = `Version correction prevented: CLI is v${cliVersion} but templates are v${updateCheck.latestVersion}. Reinstall the CLI: npm install -g oh-my-customcode@latest`;
|
|
22234
|
-
return result;
|
|
22235
|
-
}
|
|
22236
22207
|
result.newVersion = updateCheck.latestVersion;
|
|
22237
22208
|
if (!updateCheck.hasUpdates && !options.force) {
|
|
22238
22209
|
info("update.no_updates");
|
|
@@ -22406,19 +22377,22 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
22406
22377
|
if (options.forceOverwriteAll) {
|
|
22407
22378
|
warn("update.protected_file_force_overwrite", {
|
|
22408
22379
|
file: protectedPath,
|
|
22409
|
-
component
|
|
22380
|
+
component,
|
|
22381
|
+
hint: "File contains AI behavioral constraints. Overwriting because --force-overwrite-all was set."
|
|
22410
22382
|
});
|
|
22411
22383
|
} else {
|
|
22412
22384
|
warn("update.protected_file_skipped", {
|
|
22413
22385
|
file: protectedPath,
|
|
22414
|
-
component
|
|
22386
|
+
component,
|
|
22387
|
+
hint: "File was modified by user and preserved. Use --force-overwrite-all to override."
|
|
22415
22388
|
});
|
|
22416
22389
|
}
|
|
22417
22390
|
}
|
|
22418
22391
|
for (const updatedPath of protectedUpdatedPaths) {
|
|
22419
22392
|
info("update.protected_file_updated", {
|
|
22420
22393
|
file: updatedPath,
|
|
22421
|
-
component
|
|
22394
|
+
component,
|
|
22395
|
+
hint: "Protected file updated (unmodified by user, matches lockfile hash)."
|
|
22422
22396
|
});
|
|
22423
22397
|
}
|
|
22424
22398
|
skipPaths.push(...protectedSkipPaths);
|
package/dist/index.js
CHANGED
|
@@ -416,7 +416,7 @@ var MESSAGES = {
|
|
|
416
416
|
"update.file_applied": "Applied update to {{path}}",
|
|
417
417
|
"update.lockfile_regenerated": "Lockfile regenerated ({{files}} files tracked)",
|
|
418
418
|
"update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
|
|
419
|
-
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}}
|
|
419
|
+
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} updated: {{hint}}",
|
|
420
420
|
"update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
|
|
421
421
|
"update.hook_commands_migrated": "Migrated {{count}} hook/statusLine command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
422
422
|
"update.hook_commands_migration_failed": "Failed to migrate hook commands in {{path}}",
|
|
@@ -452,8 +452,8 @@ var MESSAGES = {
|
|
|
452
452
|
"update.entry_doc_force_updated": "Entry document force-updated: {{path}}",
|
|
453
453
|
"update.entry_merge_warning": "Entry document merge warning: {{warning}}",
|
|
454
454
|
"update.entry_template_not_found": "Entry document template not found: {{template}}",
|
|
455
|
-
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}}
|
|
456
|
-
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped:
|
|
455
|
+
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}} overwritten: {{hint}}",
|
|
456
|
+
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped: {{hint}}",
|
|
457
457
|
"update.settings_local_backfill_failed": "Failed to backfill statusLine refreshInterval in {{path}}",
|
|
458
458
|
"general.done": "Done!",
|
|
459
459
|
"general.failed": "Failed",
|
|
@@ -529,7 +529,7 @@ var MESSAGES = {
|
|
|
529
529
|
"update.file_applied": "{{path}} 업데이트 적용",
|
|
530
530
|
"update.lockfile_regenerated": "잠금 파일 재생성 완료 ({{files}}개 파일 추적)",
|
|
531
531
|
"update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
|
|
532
|
-
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}})
|
|
532
|
+
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트됨: {{hint}}",
|
|
533
533
|
"update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
|
|
534
534
|
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로 훅/상태줄 명령 {{count}}개를 마이그레이션했습니다",
|
|
535
535
|
"update.hook_commands_migration_failed": "{{path}}의 훅 명령 마이그레이션에 실패했습니다",
|
|
@@ -565,8 +565,8 @@ var MESSAGES = {
|
|
|
565
565
|
"update.entry_doc_force_updated": "진입 문서를 강제로 업데이트했습니다: {{path}}",
|
|
566
566
|
"update.entry_merge_warning": "진입 문서 병합 경고: {{warning}}",
|
|
567
567
|
"update.entry_template_not_found": "진입 문서 템플릿을 찾을 수 없습니다: {{template}}",
|
|
568
|
-
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}}
|
|
569
|
-
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다:
|
|
568
|
+
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}}을(를) 덮어썼습니다: {{hint}}",
|
|
569
|
+
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다: {{hint}}",
|
|
570
570
|
"update.settings_local_backfill_failed": "{{path}}의 statusLine refreshInterval 보충에 실패했습니다",
|
|
571
571
|
"general.done": "완료!",
|
|
572
572
|
"general.failed": "실패",
|
|
@@ -2906,7 +2906,7 @@ var package_default = {
|
|
|
2906
2906
|
workspaces: [
|
|
2907
2907
|
"packages/*"
|
|
2908
2908
|
],
|
|
2909
|
-
version: "
|
|
2909
|
+
version: "2.0.0",
|
|
2910
2910
|
description: "Batteries-included agent harness for Claude Code",
|
|
2911
2911
|
type: "module",
|
|
2912
2912
|
bin: {
|
|
@@ -5901,29 +5901,6 @@ async function runFullUpdatePostProcessing(options, result, config, customizatio
|
|
|
5901
5901
|
});
|
|
5902
5902
|
}
|
|
5903
5903
|
}
|
|
5904
|
-
async function getUpdateVersionDecision(installedVersion, cliVersion) {
|
|
5905
|
-
if (!(installedVersion !== "0.0.0" && compareSemver(installedVersion, cliVersion) > 0)) {
|
|
5906
|
-
return "standard";
|
|
5907
|
-
}
|
|
5908
|
-
if (installedVersion !== "2.0.0") {
|
|
5909
|
-
return "blocked";
|
|
5910
|
-
}
|
|
5911
|
-
const match = /^1\.1\.(0|[1-9]\d*)$/.exec(cliVersion);
|
|
5912
|
-
if (!match) {
|
|
5913
|
-
return "blocked";
|
|
5914
|
-
}
|
|
5915
|
-
const patch = Number(match[1]);
|
|
5916
|
-
if (!Number.isSafeInteger(patch) || patch < 107) {
|
|
5917
|
-
return "blocked";
|
|
5918
|
-
}
|
|
5919
|
-
const manifestPath = resolveTemplatePath(getProviderLayout().manifestFile);
|
|
5920
|
-
if (!await fileExists(manifestPath)) {
|
|
5921
|
-
return "blocked";
|
|
5922
|
-
}
|
|
5923
|
-
const manifest = await readJsonFile(manifestPath);
|
|
5924
|
-
const correctionAllowed = typeof manifest === "object" && manifest !== null && !Array.isArray(manifest) && "version" in manifest && manifest.version === cliVersion;
|
|
5925
|
-
return correctionAllowed ? "correction" : "blocked";
|
|
5926
|
-
}
|
|
5927
5904
|
function compareSemver(a, b) {
|
|
5928
5905
|
const pa = a.split(".").map(Number);
|
|
5929
5906
|
const pb = b.split(".").map(Number);
|
|
@@ -5984,8 +5961,7 @@ async function update(options) {
|
|
|
5984
5961
|
const config = await loadConfig(options.targetDir);
|
|
5985
5962
|
result.previousVersion = config.version;
|
|
5986
5963
|
const cliVersion = package_default.version;
|
|
5987
|
-
|
|
5988
|
-
if (versionDecision === "blocked") {
|
|
5964
|
+
if (result.previousVersion !== "0.0.0" && compareSemver(result.previousVersion, cliVersion) > 0) {
|
|
5989
5965
|
result.success = false;
|
|
5990
5966
|
result.error = `Downgrade prevented: project has v${result.previousVersion} but CLI is v${cliVersion}. Update the CLI first: npm install -g oh-my-customcode@latest`;
|
|
5991
5967
|
return result;
|
|
@@ -5994,11 +5970,6 @@ async function update(options) {
|
|
|
5994
5970
|
return result;
|
|
5995
5971
|
}
|
|
5996
5972
|
const updateCheck = await checkForUpdates(options.targetDir);
|
|
5997
|
-
if (versionDecision === "correction" && updateCheck.latestVersion !== cliVersion) {
|
|
5998
|
-
result.success = false;
|
|
5999
|
-
result.error = `Version correction prevented: CLI is v${cliVersion} but templates are v${updateCheck.latestVersion}. Reinstall the CLI: npm install -g oh-my-customcode@latest`;
|
|
6000
|
-
return result;
|
|
6001
|
-
}
|
|
6002
5973
|
result.newVersion = updateCheck.latestVersion;
|
|
6003
5974
|
if (!updateCheck.hasUpdates && !options.force) {
|
|
6004
5975
|
info("update.no_updates");
|
|
@@ -6193,19 +6164,22 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
6193
6164
|
if (options.forceOverwriteAll) {
|
|
6194
6165
|
warn("update.protected_file_force_overwrite", {
|
|
6195
6166
|
file: protectedPath,
|
|
6196
|
-
component
|
|
6167
|
+
component,
|
|
6168
|
+
hint: "File contains AI behavioral constraints. Overwriting because --force-overwrite-all was set."
|
|
6197
6169
|
});
|
|
6198
6170
|
} else {
|
|
6199
6171
|
warn("update.protected_file_skipped", {
|
|
6200
6172
|
file: protectedPath,
|
|
6201
|
-
component
|
|
6173
|
+
component,
|
|
6174
|
+
hint: "File was modified by user and preserved. Use --force-overwrite-all to override."
|
|
6202
6175
|
});
|
|
6203
6176
|
}
|
|
6204
6177
|
}
|
|
6205
6178
|
for (const updatedPath of protectedUpdatedPaths) {
|
|
6206
6179
|
info("update.protected_file_updated", {
|
|
6207
6180
|
file: updatedPath,
|
|
6208
|
-
component
|
|
6181
|
+
component,
|
|
6182
|
+
hint: "Protected file updated (unmodified by user, matches lockfile hash)."
|
|
6209
6183
|
});
|
|
6210
6184
|
}
|
|
6211
6185
|
skipPaths.push(...protectedSkipPaths);
|
package/package.json
CHANGED
|
@@ -38,13 +38,7 @@ fi
|
|
|
38
38
|
|
|
39
39
|
source_field=$(printf '%s' "$input" | jq -r '.source // "unknown"' 2>/dev/null) || source_field="unknown"
|
|
40
40
|
|
|
41
|
-
# #1770
|
|
42
|
-
# running inside a Git monorepo package, set it to that package to select its
|
|
43
|
-
# CLAUDE.md; unset/empty instead selects the Git toplevel, then pwd if Git fails.
|
|
44
|
-
# The script location is never the fallback project anchor.
|
|
45
|
-
# B-L2: a nonempty invalid anchor is authoritative; there is no Git/cwd retry.
|
|
46
|
-
# If its CLAUDE.md is missing (including a missing directory or a file anchor),
|
|
47
|
-
# the file check below returns silently with exit 0, preserving advisory behavior.
|
|
41
|
+
# #1770: honor CLAUDE_PROJECT_DIR first; fallback git toplevel (or pwd), not script location.
|
|
48
42
|
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
49
43
|
CLAUDE_MD="$PROJECT_ROOT/CLAUDE.md"
|
|
50
44
|
|
|
@@ -29,67 +29,22 @@ if ! command -v jq >/dev/null 2>&1; then
|
|
|
29
29
|
exit 0
|
|
30
30
|
fi
|
|
31
31
|
|
|
32
|
-
# ──
|
|
33
|
-
|
|
34
|
-
# 제거하여 필드 구분자와 충돌하지 않게 하고, 각 필드의 끝 LF는 기존 command
|
|
35
|
-
# substitution처럼 제거한다. JSON 문자열 안의 escaped NUL은 그대로 유지한다.
|
|
36
|
-
# slurp 후에도 입력별 출력 순서와 오류 전 부분 출력 + fallback을 유지한다.
|
|
37
|
-
# process substitution의 jq 실패를 잃지 않도록 별도 종료 상태 필드를 보낸다.
|
|
38
|
-
parent_fields=()
|
|
39
|
-
while IFS= read -r -d '' parent_field; do
|
|
40
|
-
parent_fields[${#parent_fields[@]}]="$parent_field"
|
|
41
|
-
done < <(
|
|
42
|
-
if printf '%s\n' "$input" | jq -sj '
|
|
43
|
-
. as $inputs
|
|
44
|
-
| [try ($inputs[] | .background_tasks // [] | tojson) catch "[]"] as $bg
|
|
45
|
-
| [try ($inputs[] | .session_crons // [] | tojson) catch "[]"] as $crons
|
|
46
|
-
| [try ($bg[] | fromjson | length) catch 0] as $bg_count
|
|
47
|
-
| [try ($crons[] | fromjson | length) catch 0] as $cron_count
|
|
48
|
-
| [try ($bg[] | fromjson
|
|
49
|
-
| [.[] | select(.status? == "running" or .status? == "pending" or .status? == "in_progress")]
|
|
50
|
-
| length) catch 0] as $dangling
|
|
51
|
-
| ($inputs[] | .session_id // empty
|
|
52
|
-
| if type == "string" then gsub("\u0000"; "") else . end
|
|
53
|
-
| (., "\n")),
|
|
54
|
-
"\u0000",
|
|
55
|
-
($bg | join("\n")), "\u0000",
|
|
56
|
-
($crons | join("\n")), "\u0000",
|
|
57
|
-
($bg_count | map(tostring) | join("\n")), "\u0000",
|
|
58
|
-
($cron_count | map(tostring) | join("\n")), "\u0000",
|
|
59
|
-
($dangling | map(tostring) | join("\n")), "\u0000"
|
|
60
|
-
' 2>/dev/null; then
|
|
61
|
-
parent_status=0
|
|
62
|
-
else
|
|
63
|
-
parent_status=$?
|
|
64
|
-
fi
|
|
65
|
-
# An extra separator terminates partial last fields from a failed producer.
|
|
66
|
-
printf '\0%s\0' "$parent_status"
|
|
67
|
-
)
|
|
68
|
-
parent_status="${parent_fields[${#parent_fields[@]}-1]}"
|
|
69
|
-
if [ "$parent_status" -ne 0 ]; then
|
|
70
|
-
exit "$parent_status"
|
|
71
|
-
fi
|
|
72
|
-
if [ "${#parent_fields[@]}" -ne 8 ] || [ -n "${parent_fields[6]}" ]; then
|
|
73
|
-
exit 1
|
|
74
|
-
fi
|
|
75
|
-
for parent_index in 0 1 2 3 4 5; do
|
|
76
|
-
while [[ "${parent_fields[$parent_index]}" == *$'\n' ]]; do
|
|
77
|
-
parent_fields[$parent_index]="${parent_fields[$parent_index]%$'\n'}"
|
|
78
|
-
done
|
|
79
|
-
done
|
|
80
|
-
session_id="${parent_fields[0]}"
|
|
32
|
+
# ── session_id 추출 ──
|
|
33
|
+
session_id=$(printf '%s\n' "$input" | jq -r '.session_id // empty' 2>/dev/null)
|
|
81
34
|
if [ -z "$session_id" ]; then
|
|
82
35
|
printf '%s\n' "$input"
|
|
83
36
|
exit 0
|
|
84
37
|
fi
|
|
85
38
|
|
|
86
|
-
# ── Phase 2 (#1196): background_tasks / session_crons (CC v2.1.145+) ──
|
|
87
|
-
#
|
|
88
|
-
bg_tasks_json
|
|
89
|
-
session_crons_json
|
|
90
|
-
bg_tasks_count
|
|
91
|
-
session_crons_count
|
|
92
|
-
|
|
39
|
+
# ── Phase 2 (#1196): background_tasks / session_crons 추출 (CC v2.1.145+) ──
|
|
40
|
+
# 신규 필드 미존재 시 빈 배열로 처리 (graceful degrade).
|
|
41
|
+
bg_tasks_json=$(printf '%s\n' "$input" | jq -c '.background_tasks // []' 2>/dev/null || echo '[]')
|
|
42
|
+
session_crons_json=$(printf '%s\n' "$input" | jq -c '.session_crons // []' 2>/dev/null || echo '[]')
|
|
43
|
+
bg_tasks_count=$(printf '%s\n' "$bg_tasks_json" | jq 'length' 2>/dev/null || echo 0)
|
|
44
|
+
session_crons_count=$(printf '%s\n' "$session_crons_json" | jq 'length' 2>/dev/null || echo 0)
|
|
45
|
+
|
|
46
|
+
# 미완료 백그라운드 태스크: running/pending/in_progress 상태 카운트
|
|
47
|
+
bg_dangling_count=$(printf '%s\n' "$bg_tasks_json" | jq '[.[] | select(.status? == "running" or .status? == "pending" or .status? == "in_progress")] | length' 2>/dev/null || echo 0)
|
|
93
48
|
|
|
94
49
|
# ── 경로 결정 (환경변수 override 지원) ──
|
|
95
50
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -284,8 +239,7 @@ WORKER_EOF
|
|
|
284
239
|
chmod +x "$WORKER_SCRIPT"
|
|
285
240
|
|
|
286
241
|
# ── 백그라운드로 실행 (Stop hook 시간 예산 <3s 준수) ──
|
|
287
|
-
#
|
|
288
|
-
# 분석은 비동기이며 완료 후 임시 파일을 제거한다. setsid/session 생존 보장은 없다.
|
|
242
|
+
# setsid로 부모 종료 후에도 실행 지속; 완료 후 임시 파일 자정리
|
|
289
243
|
WORKER_ERR_LOG="/tmp/.claude-reflection-err-${PPID}.log"
|
|
290
244
|
|
|
291
245
|
(
|
|
@@ -296,9 +250,10 @@ WORKER_ERR_LOG="/tmp/.claude-reflection-err-${PPID}.log"
|
|
|
296
250
|
"$SCRIPT_DIR" \
|
|
297
251
|
"$bg_tasks_json" \
|
|
298
252
|
"$session_crons_json" \
|
|
299
|
-
"$bg_dangling_count"
|
|
253
|
+
"$bg_dangling_count" \
|
|
254
|
+
2>>"$WORKER_ERR_LOG"
|
|
300
255
|
rm -f "$WORKER_SCRIPT"
|
|
301
|
-
)
|
|
256
|
+
) &
|
|
302
257
|
disown $! 2>/dev/null || true
|
|
303
258
|
|
|
304
259
|
# ── 즉시 stdin pass-through 후 exit 0 (Stop hook 체인 유지) ──
|
|
@@ -536,92 +536,8 @@ input=$(cat)
|
|
|
536
536
|
# Swallow it instead: exit 0 with no stdout, exactly as if the hook had not
|
|
537
537
|
# run. "jq -e" exits non-zero both for a parse error and for a false/null
|
|
538
538
|
# result, so one guard covers every non-object shape.
|
|
539
|
-
|
|
540
|
-
# NUL retain the original extraction path: command substitution has platform-
|
|
541
|
-
# specific NUL handling, and independent jq failures are part of the contract.
|
|
542
|
-
extraction_mode=$(printf '%s' "$input" | jq -er '
|
|
543
|
-
if type != "object" then false
|
|
544
|
-
elif any(.. | strings; contains("\u0000")) then "legacy"
|
|
545
|
-
elif ((.tool_name // "unknown" | type) != "string") then "legacy"
|
|
546
|
-
elif ((.tool_input != null) and ((.tool_input | type) != "object")) then "legacy"
|
|
547
|
-
elif ((.tool_output != null) and ((.tool_output | type) != "object")) then "legacy"
|
|
548
|
-
elif ((.tool_input.file_path // "" | type) != "string")
|
|
549
|
-
or ((.tool_input.command // "" | type) != "string")
|
|
550
|
-
or ((.tool_input.old_string // .tool_input.content // .tool_input.new_string // "" | type) != "string")
|
|
551
|
-
then "legacy" else "batched" end
|
|
552
|
-
' 2>/dev/null) || exit 0
|
|
553
|
-
|
|
554
|
-
# NUL-delimited fields are safe only after the guard above excludes embedded
|
|
555
|
-
# NUL. A final producer-status record makes jq failures observable; process
|
|
556
|
-
# substitution alone would discard its exit status. jq -j preserves -r scalar
|
|
557
|
-
# rendering without an added newline; trim actual trailing LF just as $(...) did.
|
|
558
|
-
_read_stuck_fields() {
|
|
559
|
-
local filter="$1" field status
|
|
560
|
-
STUCK_FIELDS=()
|
|
561
|
-
while IFS= read -r -d '' field; do
|
|
562
|
-
while [[ "$field" == *$'\n' ]]; do field="${field%$'\n'}"; done
|
|
563
|
-
STUCK_FIELDS[${#STUCK_FIELDS[@]}]="$field"
|
|
564
|
-
done < <(
|
|
565
|
-
if printf '%s' "$input" | jq -j "$filter"; then
|
|
566
|
-
printf '%s\0' '__OMCUSTOM_JQ_STATUS__0'
|
|
567
|
-
else
|
|
568
|
-
printf '__OMCUSTOM_JQ_STATUS__%s\0' "$?"
|
|
569
|
-
fi
|
|
570
|
-
)
|
|
571
|
-
status="${STUCK_FIELDS[${#STUCK_FIELDS[@]}-1]:-}"
|
|
572
|
-
case "$status" in
|
|
573
|
-
__OMCUSTOM_JQ_STATUS__*) status="${status#__OMCUSTOM_JQ_STATUS__}" ;;
|
|
574
|
-
*) return 1 ;;
|
|
575
|
-
esac
|
|
576
|
-
unset "STUCK_FIELDS[$((${#STUCK_FIELDS[@]} - 1))]"
|
|
577
|
-
return "$status"
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
TARGET_KEY_CAP=1000
|
|
581
|
-
|
|
582
|
-
truncate_key() {
|
|
583
|
-
local v="$1"
|
|
584
|
-
if [ "${#v}" -gt "$TARGET_KEY_CAP" ]; then
|
|
585
|
-
printf '%s' "${v:0:$TARGET_KEY_CAP}#len=${#v}"
|
|
586
|
-
else
|
|
587
|
-
printf '%s' "$v"
|
|
588
|
-
fi
|
|
589
|
-
}
|
|
539
|
+
printf '%s' "$input" | jq -e 'type == "object"' >/dev/null 2>&1 || exit 0
|
|
590
540
|
|
|
591
|
-
# Batched extraction changes no classifier or detector inputs. Preview retains
|
|
592
|
-
# its original byte-cap pipeline, including broken-pipe tolerance.
|
|
593
|
-
if [ "$extraction_mode" = "batched" ]; then
|
|
594
|
-
_read_stuck_fields '(.tool_name // "unknown"), "\u0000",
|
|
595
|
-
(if (.tool_name // "unknown") == "Bash"
|
|
596
|
-
then (.tool_input.command // .tool_input.file_path // "")
|
|
597
|
-
else (.tool_input.file_path // "") end), "\u0000",
|
|
598
|
-
(.tool_output.is_error // false), "\u0000"'
|
|
599
|
-
[ "${#STUCK_FIELDS[@]}" -eq 3 ] || exit 1
|
|
600
|
-
tool_name="${STUCK_FIELDS[0]}"
|
|
601
|
-
if [ "$tool_name" = "Bash" ]; then
|
|
602
|
-
file_path=""
|
|
603
|
-
target_key=$(truncate_key "${STUCK_FIELDS[1]}")
|
|
604
|
-
else
|
|
605
|
-
file_path_full="${STUCK_FIELDS[1]}"
|
|
606
|
-
file_path=$(printf '%s' "$file_path_full" | head -c "$TARGET_KEY_CAP" || true)
|
|
607
|
-
target_key=$(truncate_key "$file_path_full")
|
|
608
|
-
fi
|
|
609
|
-
is_error="${STUCK_FIELDS[2]}"
|
|
610
|
-
output_preview=$(printf '%s' "$input" \
|
|
611
|
-
| jq -r '(.tool_output.output // "" | tostring)[0:200]' \
|
|
612
|
-
| head -c 200 || true)
|
|
613
|
-
_read_stuck_fields '(.tool_input.command // ""), "\u0000",
|
|
614
|
-
(if (.tool_name == "Edit" or .tool_name == "Write")
|
|
615
|
-
then ((.tool_input.old_string // .tool_input.content // .tool_input.new_string // "")
|
|
616
|
-
| gsub("[[:space:]]+"; " ") | ltrimstr(" ") | rtrimstr(" ")
|
|
617
|
-
| (length as $l | if $l <= 120 then "\($l)#\(.)"
|
|
618
|
-
else ((explode | add // 0) as $sum
|
|
619
|
-
| "\($l)#\($sum)#\(.[0:80])#\(.[-40:])") end))
|
|
620
|
-
else "" end), "\u0000"'
|
|
621
|
-
[ "${#STUCK_FIELDS[@]}" -eq 2 ] || exit 1
|
|
622
|
-
raw_command="${STUCK_FIELDS[0]}"
|
|
623
|
-
edit_hash="${STUCK_FIELDS[1]}"
|
|
624
|
-
else
|
|
625
541
|
# Extract tool info
|
|
626
542
|
tool_name=$(printf '%s' "$input" | jq -r '.tool_name // "unknown"')
|
|
627
543
|
# Bash commands are NOT files. Feeding tool_input.command into file_path made
|
|
@@ -636,6 +552,16 @@ tool_name=$(printf '%s' "$input" | jq -r '.tool_name // "unknown"')
|
|
|
636
552
|
# remove that collision class, so truncate_key() ALSO appends the ORIGINAL
|
|
637
553
|
# length: two values that share the capped prefix but differ in total length
|
|
638
554
|
# no longer produce the same key (M-3).
|
|
555
|
+
TARGET_KEY_CAP=1000
|
|
556
|
+
|
|
557
|
+
truncate_key() {
|
|
558
|
+
local v="$1"
|
|
559
|
+
if [ "${#v}" -gt "$TARGET_KEY_CAP" ]; then
|
|
560
|
+
printf '%s' "${v:0:$TARGET_KEY_CAP}#len=${#v}"
|
|
561
|
+
else
|
|
562
|
+
printf '%s' "$v"
|
|
563
|
+
fi
|
|
564
|
+
}
|
|
639
565
|
|
|
640
566
|
if [ "$tool_name" = "Bash" ]; then
|
|
641
567
|
file_path=""
|
|
@@ -701,35 +627,13 @@ if [ "$tool_name" = "Edit" ] || [ "$tool_name" = "Write" ]; then
|
|
|
701
627
|
end)')
|
|
702
628
|
fi
|
|
703
629
|
|
|
704
|
-
fi
|
|
705
|
-
|
|
706
630
|
# History entries are written by jq, so their "path" values are JSON-ENCODED.
|
|
707
631
|
# Matching them requires the SAME encoding, not the raw text: a raw value
|
|
708
632
|
# containing a quote (common in Bash commands) can never match the encoded
|
|
709
633
|
# form. The previous BRE escaping was doubly broken — in POSIX BRE "( ) + ? { |"
|
|
710
634
|
# are literals, so escaping them turned them INTO operators and silently
|
|
711
635
|
# stopped matching. Encode once here and match with grep -F (fixed string).
|
|
712
|
-
|
|
713
|
-
encoded_matches=()
|
|
714
|
-
while IFS= read -r -d '' encoded_match; do
|
|
715
|
-
encoded_matches[${#encoded_matches[@]}]="$encoded_match"
|
|
716
|
-
done < <(
|
|
717
|
-
if jq -nj --arg path "$target_key" --arg edit "$edit_hash" \
|
|
718
|
-
'($path | tojson), "\u0000", ($edit | tojson), "\u0000"'; then
|
|
719
|
-
printf '%s\0' '__OMCUSTOM_JQ_STATUS__0'
|
|
720
|
-
else
|
|
721
|
-
printf '__OMCUSTOM_JQ_STATUS__%s\0' "$?"
|
|
722
|
-
fi
|
|
723
|
-
)
|
|
724
|
-
encoded_status="${encoded_matches[${#encoded_matches[@]}-1]:-}"
|
|
725
|
-
case "$encoded_status" in
|
|
726
|
-
__OMCUSTOM_JQ_STATUS__0) ;;
|
|
727
|
-
__OMCUSTOM_JQ_STATUS__*) exit "${encoded_status#__OMCUSTOM_JQ_STATUS__}" ;;
|
|
728
|
-
*) exit 1 ;;
|
|
729
|
-
esac
|
|
730
|
-
[ "${#encoded_matches[@]}" -eq 3 ] || exit 1
|
|
731
|
-
target_key_json="${encoded_matches[0]}"
|
|
732
|
-
edit_hash_json="${encoded_matches[1]}"
|
|
636
|
+
target_key_json=$(jq -n --arg v "$target_key" '$v')
|
|
733
637
|
path_match="\"path\":${target_key_json}"
|
|
734
638
|
|
|
735
639
|
# #1649 follow-up: edit_hash now preserves punctuation (see the edit_hash
|
|
@@ -738,7 +642,7 @@ path_match="\"path\":${target_key_json}"
|
|
|
738
642
|
# (--arg ehash "$edit_hash" -> jq serializes it), so Check 1 / Check 3 below
|
|
739
643
|
# must match it with the SAME JSON-encoded literal, not the raw string — same
|
|
740
644
|
# fix as path_match above, applied to edit_hash.
|
|
741
|
-
|
|
645
|
+
edit_hash_json=$(jq -n --arg v "$edit_hash" '$v')
|
|
742
646
|
edit_hash_match="\"edit_hash\":${edit_hash_json}"
|
|
743
647
|
|
|
744
648
|
is_readonly="false"
|
|
@@ -747,7 +651,7 @@ if [ "$tool_name" = "Bash" ]; then
|
|
|
747
651
|
fi
|
|
748
652
|
|
|
749
653
|
# Session-scoped history
|
|
750
|
-
HISTORY_FILE="
|
|
654
|
+
HISTORY_FILE="/tmp/.claude-tool-history-${PPID}"
|
|
751
655
|
|
|
752
656
|
# Create entry
|
|
753
657
|
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
@@ -86,7 +86,7 @@ zsh는 미인용 `$var`를 단어분할하지 않습니다(#1683 #5) — 분할
|
|
|
86
86
|
|
|
87
87
|
도구 이름 ≠ 그 프로그램(#1590): 사용 전 `type <tool>`로 실체를 확인합니다 — 이 저장소 Bash의 `grep`은 `.gitignore`를 존중하는 셸 함수이므로 저장소 전수조사는 `git grep`을 표준으로 합니다.
|
|
88
88
|
|
|
89
|
-
Bash `cd`는 세션 cwd로 남습니다(#1778, #1767): 훅 명령은 `CLAUDE_PROJECT_DIR`로 고정되어 지속 `cd`에 영향받지 않지만, 상대 경로를 내부에서 읽는 스크립트·도구와 이후 명령의 상대 경로는 어긋날 수 있습니다 — 절대 경로나 서브셸 `( cd <dir> && … )`만 사용하고 지속 `cd`는 남기지 않습니다.
|
|
89
|
+
Bash `cd`는 세션 cwd로 남습니다(#1778, #1767): 훅 명령은 `CLAUDE_PROJECT_DIR`로 고정되어 지속 `cd`에 영향받지 않지만, 상대 경로를 내부에서 읽는 스크립트·도구와 이후 명령의 상대 경로는 어긋날 수 있습니다 — 절대 경로나 서브셸 `( cd <dir> && … )`만 사용하고 지속 `cd`는 남기지 않습니다.
|
|
90
90
|
|
|
91
91
|
<!-- DETAIL: tool name vs program
|
|
92
92
|
> **도구 이름 ≠ 그 프로그램 (#1590)**: 도구를 쓰기 전에 `type <tool>`로 실체를 확인한다. Bash 도구의 `grep`은 `~/.claude/shell-snapshots/snapshot-zsh-*.sh`의 **셸 함수**이며 `ugrep --ignore-files`에 위임한다. 그 결과 `.gitignore`의 리터럴 `CLAUDE.md` 패턴을 존중해, **force-tracked 파일을 재귀 탐색에서 조용히 누락**한다(에러 없이 exit 0). 명시 경로를 준 grep은 정상 동작하므로 **traversal만 영향**을 받는다. 실측(2026-08-15): 동일 패턴·동일 대상에 대해 셸 함수 36 / `command grep` 43 / `git grep` 38 히트 — 셸 함수만 `CLAUDE.md`를 0 히트로 놓쳤다. 진단 함정: `git check-ignore`는 **index-aware**라 tracked 파일에 "not ignored"(exit 1)를 반환한다 — 원인을 보려면 `git check-ignore --no-index`를 써야 한다. 처방: 저장소 전수 조사는 `git grep`을 표준으로 한다(R017 Count Sync cross-ref). Origin: #1590.
|
|
@@ -259,7 +259,7 @@ MEMORY.md supports an optional `## User Model` section (DISTINCT from `## Behavi
|
|
|
259
259
|
- Focus: AI agent orchestration, CLI tooling
|
|
260
260
|
|
|
261
261
|
### Override Decisions [confidence: low]
|
|
262
|
-
- Overrode /scout SKIP verdict → INTEGRATE for RTK (#756) (과거 결정 예시입니다. RTK 프로젝트 통합은
|
|
262
|
+
- Overrode /scout SKIP verdict → INTEGRATE for RTK (#756) (과거 결정 예시입니다. RTK 프로젝트 통합은 v2.0.0에서 퇴역하며 현재 실행 추천으로 해석하지 마십시오.)
|
|
263
263
|
|
|
264
264
|
### Categories
|
|
265
265
|
|
|
@@ -101,15 +101,6 @@ Fable 5 실행 에이전트/스킬은 지시(instruction) 장문화가 오히려
|
|
|
101
101
|
- [ ] Tier 1에서 잡을 수 있는 문제를 다루는가? (상위 tier 대신 시프트 권고)
|
|
102
102
|
- [ ] Ladder 순서를 문서화했는가? (어떤 검사를 먼저 실행하는지)
|
|
103
103
|
|
|
104
|
-
### 문구 작성·정정
|
|
105
|
-
|
|
106
|
-
새 주석/JSDoc/문서의 문구 작성·정정 시 확인합니다(#1816 H1):
|
|
107
|
-
|
|
108
|
-
- [ ] 새 문구의 사실 절마다 코드 앵커 또는 실제 관측 출력을 대응표에 기록했는가?
|
|
109
|
-
- [ ] 독립 문구 검토로 사실 절과 근거의 대응을 확인했는가?
|
|
110
|
-
|
|
111
|
-
lint·typecheck·테스트 통과만으로 문구의 사실 정확성을 확정하지 않습니다. pipeline auto-dev의 문구 검토 요구와 함께 확인합니다.
|
|
112
|
-
|
|
113
104
|
## Safety-Signal Rule Authoring — Carve-Out Pre-Check (shift-left)
|
|
114
105
|
|
|
115
106
|
<!-- DETAIL: Origin note
|
|
@@ -1446,11 +1446,9 @@ steps:
|
|
|
1446
1446
|
re-running step d then step e, re-staging, and re-running this assertion.
|
|
1447
1447
|
|
|
1448
1448
|
Version decision (semver) — PATCH-PREFERRED policy (post-1.0.0):
|
|
1449
|
-
- The user's explicit version or bump-level judgment takes precedence. Approval to remove a feature or change hooks is not approval for a major or minor version.
|
|
1450
1449
|
- No existing tags → v0.1.0
|
|
1451
1450
|
- Previous tag exists → DEFAULT to patch. When in doubt, patch — do NOT reflexively bump minor for rule/doc/skill edits.
|
|
1452
1451
|
- patch (DEFAULT): rule/doc/skill/config/wiki/workflow changes, bugfixes, hardening, CC-compat notes — the vast majority of this project's changes
|
|
1453
|
-
- Public skill/agent/integration additions and removals follow this repository's PATCH-PREFERRED lifecycle policy, including RTK integration and /rtk-exec retirement. Names, files, or counts appearing or disappearing alone do not justify a major or minor bump.
|
|
1454
1452
|
- minor: ONLY a new user-facing capability that changes HOW the harness is used — a new
|
|
1455
1453
|
workflow axis, a new command surface users must learn, or a contract other components
|
|
1456
1454
|
depend on. NOT "a file was added under .claude/skills/ or .claude/agents/".
|
|
@@ -1462,7 +1460,7 @@ steps:
|
|
|
1462
1460
|
baseline rate of change, NOT a minor signal. If a version bump would follow
|
|
1463
1461
|
mechanically from "a new file exists", it is patch. Ask instead: does a user's
|
|
1464
1462
|
workflow change because of this?
|
|
1465
|
-
- major:
|
|
1463
|
+
- major: breaking changes to user-facing contracts (CLI flags, public skill/command names, public API), or a deliberate milestone declaration (e.g., v1.0.0)
|
|
1466
1464
|
- Previous tag is ahead of source version (e.g., tag v0.136.1, package.json 0.136.0): use next available skip-version (0.136.2)
|
|
1467
1465
|
|
|
1468
1466
|
2. Release notes via omcustom-release-notes skill
|
|
@@ -183,7 +183,7 @@ v1.1.29(#1484)에서 제거되었습니다.
|
|
|
183
183
|
|
|
184
184
|
DEFER 기간 동안의 cross-tool integration 우회 경로:
|
|
185
185
|
|
|
186
|
-
아래 대안과 use case 범위는 당시 결정 이력으로 보존합니다. RTK 프로젝트 통합은
|
|
186
|
+
아래 대안과 use case 범위는 당시 결정 이력으로 보존합니다. RTK 프로젝트 통합은 v2.0.0에서 퇴역하므로 RTK proxy skill을 현재 실행 경로로 사용하지 마십시오. 이 기록의 효과 범위를 현재 버전에서 새로 실측했다는 뜻은 아닙니다.
|
|
187
187
|
|
|
188
188
|
- **RTK proxy skill**: 기존 infra 활용, Cursor 등에서 oh-my-customcode skill 호출
|
|
189
189
|
- **Codex/Gemini exec skill**: 외부 LLM executor를 skill로 래핑, harness 종속성 없음
|
|
@@ -8,7 +8,7 @@ Inspired by OpenHarness's provider profile switching pattern, adapted for oh-my-
|
|
|
8
8
|
|
|
9
9
|
## Provider Matrix
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
v2.0.0에서는 사용자 결정에 따라 프로젝트 RTK 통합과 공개 `rtk-exec` 스킬을 퇴역합니다. 이 가이드의 RTK 실행 예시·추천을 사용하지 마십시오. 머신에 별도로 설치한 RTK 바이너리는 이 변경으로 제거하지 않습니다.
|
|
12
12
|
|
|
13
13
|
## Availability Detection
|
|
14
14
|
|
package/templates/manifest.json
CHANGED
|
@@ -1446,11 +1446,9 @@ steps:
|
|
|
1446
1446
|
re-running step d then step e, re-staging, and re-running this assertion.
|
|
1447
1447
|
|
|
1448
1448
|
Version decision (semver) — PATCH-PREFERRED policy (post-1.0.0):
|
|
1449
|
-
- The user's explicit version or bump-level judgment takes precedence. Approval to remove a feature or change hooks is not approval for a major or minor version.
|
|
1450
1449
|
- No existing tags → v0.1.0
|
|
1451
1450
|
- Previous tag exists → DEFAULT to patch. When in doubt, patch — do NOT reflexively bump minor for rule/doc/skill edits.
|
|
1452
1451
|
- patch (DEFAULT): rule/doc/skill/config/wiki/workflow changes, bugfixes, hardening, CC-compat notes — the vast majority of this project's changes
|
|
1453
|
-
- Public skill/agent/integration additions and removals follow this repository's PATCH-PREFERRED lifecycle policy, including RTK integration and /rtk-exec retirement. Names, files, or counts appearing or disappearing alone do not justify a major or minor bump.
|
|
1454
1452
|
- minor: ONLY a new user-facing capability that changes HOW the harness is used — a new
|
|
1455
1453
|
workflow axis, a new command surface users must learn, or a contract other components
|
|
1456
1454
|
depend on. NOT "a file was added under .claude/skills/ or .claude/agents/".
|
|
@@ -1462,7 +1460,7 @@ steps:
|
|
|
1462
1460
|
baseline rate of change, NOT a minor signal. If a version bump would follow
|
|
1463
1461
|
mechanically from "a new file exists", it is patch. Ask instead: does a user's
|
|
1464
1462
|
workflow change because of this?
|
|
1465
|
-
- major:
|
|
1463
|
+
- major: breaking changes to user-facing contracts (CLI flags, public skill/command names, public API), or a deliberate milestone declaration (e.g., v1.0.0)
|
|
1466
1464
|
- Previous tag is ahead of source version (e.g., tag v0.136.1, package.json 0.136.0): use next available skip-version (0.136.2)
|
|
1467
1465
|
|
|
1468
1466
|
2. Release notes via omcustom-release-notes skill
|