Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "harnish",
"version": "0.0.5",
"version": "0.1.0",
"description": "자율 구현 엔진. PRD 생성(drafti) → 자율 구현(harnish) → 점검(ralphi).",
"author": {
"name": "jazz1x",
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,22 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.14"

- name: Install bats + jq (macos)
- name: Install bats + jq
run: brew install bats-core jq

- name: Run test suite
- name: Install pytest
run: pip install pytest

- name: Run pytest unit tests
run: PYTHONPATH=scripts python3 -m pytest tests/ -v
env:
PYTHONDONTWRITEBYTECODE: 1

- name: Run bats test suite
run: bash tests/run.sh

- name: Run deep suite
- name: Run bats deep suite
run: HARNISH_RUN_DEEP_TESTS=1 bash tests/run.sh
if: success()
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Thumbs.db

# Runtime data (per-project, generated in user's CWD)
.harnish/
.galmuri/

# Python
__pycache__/
*.pyc
.pytest_cache/

# Packaged skills
*.skill
25 changes: 24 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.0] - 2026-04-29

Big-bang migration: all `jq` usage replaced with Python standard library. External interface (CLI flags, stdin/stdout JSON, exit codes, hooks.json, SKILL.md) is 100% preserved.

### Changed
- **All 18 scripts**: bash+jq logic replaced with `scripts/harnish_py/` Python package; `.sh` files are now 2-line wrappers (`PYTHONPATH + exec python3 -m harnish_py <sub> "$@"`)
- `common.sh`: `require_cmd jq` removed; only `resolve_*` helpers and `slugify` remain
- `scripts/skillify.sh`: `skillify_version` bumped `0.0.5` → `0.1.0`
- CI: Python `3.12` → `3.14`; `pytest` unit step added before bats; `jq` install retained for test infrastructure (`test-all.sh` + bats files use jq for assertions; production scripts no longer depend on jq)
- VERSION + 10 SKILL.md frontmatters: `0.0.5` → `0.1.0`
- `.claude-plugin/plugin.json`: `0.0.4` → `0.1.0` (catches up the missed bump from v0.0.5 release; was a pre-existing inconsistency)

### Added
- `scripts/harnish_py/` — 18-module Python package (cli, io, common, asset, record, query, init, compress, promote, detect, skillify, quality, thresholds, purge, migrate, progress, violations)
- `tests/conftest.py` — pytest sys.path injection (honne pattern)
- 11 pytest unit test files (35+ tests): io, cli, asset, record, query, compress, promote, skillify, purge, progress, init
- Python 3.14+ version guard (`sys.version_info < (3, 14)` → exit 4)
- `_Parser` class (argparse exit code 1 instead of 2, honne pattern)

### Removed
- `jq` runtime dependency — `scripts/` no longer calls `jq` anywhere (wrappers are pure delegation)

## [0.0.5] - 2026-04-28

This release combines the asset-store identity correction with the production pipeline closure originally drafted as 0.0.6. Both ship together as 0.0.5.
Expand Down Expand Up @@ -152,7 +174,8 @@ First public release. 5 skills + shared script suite + asset infrastructure + au
- README 구조 정리 (galmuri 동일 톤): badges, install steps, quickstart, usage, hooks, assets, worktrees, fork & customize, naming, triad
- VERSIONING.md, references/* 가이드

[Unreleased]: https://github.com/jazz1x/harnish/compare/v0.0.5...HEAD
[Unreleased]: https://github.com/jazz1x/harnish/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/jazz1x/harnish/compare/v0.0.5...v0.1.0
[0.0.5]: https://github.com/jazz1x/harnish/compare/v0.0.4...v0.0.5
[0.0.4]: https://github.com/jazz1x/harnish/compare/v0.0.3...v0.0.4
[0.0.3]: https://github.com/jazz1x/harnish/compare/v0.0.2...v0.0.3
Expand Down
17 changes: 12 additions & 5 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

> Claude Code 플러그인 — 자율 구현 엔진

![version](https://img.shields.io/badge/version-0.0.5-blue)
![version](https://img.shields.io/badge/version-0.1.0-blue)
![license](https://img.shields.io/badge/license-MIT-green)
![claude-code](https://img.shields.io/badge/claude--code-plugin-purple)
![tests](https://img.shields.io/badge/tests-80%20passing-brightgreen)
![tests](https://img.shields.io/badge/tests-123%20passing-brightgreen)
![python](https://img.shields.io/badge/python-3.14%2B-blue)

**harnish** (harness + ish) = "대충 하네스 비스무리한 것" — 작업할수록 똑똑해지는 구현 환경. 실패가 가드레일이 되고, 패턴이 축적되며, 세션과 워크트리가 바뀌어도 맥락이 유실되지 않는다.

Expand Down Expand Up @@ -34,6 +35,12 @@ ralphi ──→ 어떤 아티팩트든 점검 (PRD, SKILL.md, 스크립트,
HITL(보고→대기) 또는 자율(즉시 수정)
```

## 요구 사항

- **Python 3.14+** — 모든 `scripts/*.sh` 의 런타임 (1-line wrapper로 `scripts/harnish_py/` 에 위임; `sys.version_info < (3, 14)` 가드가 하위 버전에서 exit 4 로 거부).
- **Claude Code** — 플러그인 호스트.
- **`jq` 의존성 없음** (v0.1.0 부터).

## 설치

### 1. 마켓플레이스 등록
Expand All @@ -59,7 +66,7 @@ Claude Code 세션 안에서 실행:
예상 출력:

```
✓ Installed harnish@0.0.5 — 5 skills registered (forki, drafti-feature, drafti-architect, impl, ralphi)
✓ Installed harnish@0.1.0 — 5 skills registered (forki, drafti-feature, drafti-architect, impl, ralphi)
```

### 3. 확인
Expand Down Expand Up @@ -213,14 +220,14 @@ harnish는 **2단 기억 구조** (two-tier memory)로 동작한다. 각 tier의
| **Tier 1 — Asset Store** (episodic) | `.harnish/harnish-assets.jsonl` | 프로젝트별, 세션 간 누적, TTL purge | 일어난 일을 기록 (failure, pattern, guardrail, snippet, decision) | `query-assets.sh --format inject` 로 컨텍스트 주입 (실제 RAG 경로) |
| **Tier 2 — Skills** (procedural) | `skills/*/SKILL.md` | 영구 (소스 트리 버전 관리) | 안정화된 행동 양식 | Claude Code가 자동 로드, 트리거 시 발동 |

`skillify.sh`가 그 다리 — 압축된 Tier-1 자산을 Tier-2 SKILL.md scaffold로 묶는다. v0.0.5부터 scaffold는 **production-grade**:
`skillify.sh`가 그 다리 — 압축된 Tier-1 자산을 Tier-2 SKILL.md scaffold로 묶는다. scaffold는 **production-grade** (v0.0.5 도입, v0.1.0에서 Python으로 재구현):

- frontmatter `Triggers:` 가 자산 title에서 자동 추출됨
- body는 자산 타입별 섹션 + 메타 (level / confidence / stability / resolved)
- `references/source-assets.jsonl` 로 원본 트레이서빌리티 보존
- §1은 여전히 LLM finalize 필요 (1-3개 가이드라인 도출) — draft generator일 뿐, 자율 graduating 아님

**Trigger → Record → Skillify 파이프라인 (v0.0.5에서 닫힘):**
**Trigger → Record → Skillify 파이프라인** (v0.0.5에서 닫힘, v0.1.0에서 Python 구현 — `.sh` 는 1-line wrapper):

```
PostToolUseFailure → detect-asset.sh (노이즈 필터) → /tmp/harnish-pending-*.jsonl
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

> Claude Code plugin — autonomous implementation engine

![version](https://img.shields.io/badge/version-0.0.5-blue)
![version](https://img.shields.io/badge/version-0.1.0-blue)
![license](https://img.shields.io/badge/license-MIT-green)
![claude-code](https://img.shields.io/badge/claude--code-plugin-purple)
![tests](https://img.shields.io/badge/tests-80%20passing-brightgreen)
![tests](https://img.shields.io/badge/tests-123%20passing-brightgreen)
![python](https://img.shields.io/badge/python-3.14%2B-blue)

**harnish** (harness + ish) — an implementation environment that gets smarter as you work. Failures become guardrails, patterns accumulate, and context persists across sessions and worktrees.

Expand Down Expand Up @@ -34,6 +35,12 @@ ralphi ──→ inspects any artifact (PRD, SKILL.md, scripts, code)
HITL (report → wait) or autonomous (fix immediately)
```

## Requirements

- **Python 3.14+** — runtime for all `scripts/*.sh` (they delegate to `scripts/harnish_py/` via 1-line wrappers; the `sys.version_info < (3, 14)` guard exits with code 4 on older interpreters).
- **Claude Code** — plugin host.
- **No `jq` dependency** as of v0.1.0.

## Install

### 1. Register the marketplace
Expand All @@ -59,7 +66,7 @@ Expected output:
Expected output:

```
✓ Installed harnish@0.0.5 — 5 skills registered (forki, drafti-feature, drafti-architect, impl, ralphi)
✓ Installed harnish@0.1.0 — 5 skills registered (forki, drafti-feature, drafti-architect, impl, ralphi)
```

### 3. Verify
Expand Down Expand Up @@ -214,14 +221,14 @@ harnish runs a **two-tier memory** system. Each tier serves a different role; th
| **Tier 1 — Asset Store** (episodic) | `.harnish/harnish-assets.jsonl` | Per-project, accumulates across sessions, TTL-purged | Records what happened (failures, patterns, guardrails, snippets, decisions) | Injected into context on demand via `query-assets.sh --format inject` (this is the actual RAG path) |
| **Tier 2 — Skills** (procedural) | `skills/*/SKILL.md` | Permanent (versioned in source tree) | Codifies stable behavior | Auto-loaded by Claude Code as triggerable skills |

`skillify.sh` is the bridge — it bundles compressed Tier-1 assets into a Tier-2 SKILL.md scaffold. As of v0.0.5 the scaffold is **production-grade**:
`skillify.sh` is the bridge — it bundles compressed Tier-1 assets into a Tier-2 SKILL.md scaffold. The scaffold is **production-grade** (since v0.0.5; reimplemented in Python in v0.1.0):

- Frontmatter `Triggers:` auto-extracted from asset titles
- Body sectioned by asset type, with metadata (level / confidence / stability / resolved)
- `references/source-assets.jsonl` preserves originals for traceability
- §1 still needs LLM finalization of 1-3 actionable guidelines — draft generator, not autonomous graduation

**Trigger → Record → Skillify pipeline (closed in v0.0.5):**
**Trigger → Record → Skillify pipeline** (closed in v0.0.5; pure-Python implementation in v0.1.0, `.sh` files are 1-line wrappers):

```
PostToolUseFailure → detect-asset.sh (noise filter) → /tmp/harnish-pending-*.jsonl
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.5
0.1.0
52 changes: 3 additions & 49 deletions scripts/abstract-asset.sh
Original file line number Diff line number Diff line change
@@ -1,51 +1,5 @@
#!/usr/bin/env bash
# abstract-asset.sh — 프로젝트 특정 자산을 범용(generic)으로 추상화 (JSONL 기반)
#
# 사용법:
# abstract-asset.sh --slug "docker-build-cache" [--base-dir .harnish]

set -euo pipefail

# abstract-asset.sh — Python migration wrapper (v0.1.0+)
# 실제 구현: scripts/harnish_py/asset.py
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"

BASE="$(resolve_base_dir)"
SLUG=""

while [[ $# -gt 0 ]]; do
case $1 in
--slug) SLUG="$2"; shift 2;;
--base-dir) BASE="$2"; shift 2;;
*) shift;;
esac
done

if [[ -z "$SLUG" ]]; then
echo "오류: --slug 필수" >&2
exit 1
fi

ASSET_FILE="$BASE/harnish-assets.jsonl"

if [[ ! -f "$ASSET_FILE" ]]; then
echo "오류: $ASSET_FILE 없음" >&2
exit 1
fi

# 원본 찾기
ORIGINAL=$(jq -c --arg s "$SLUG" 'select(.slug == $s)' "$ASSET_FILE" 2>/dev/null | head -1)

if [[ -z "$ORIGINAL" ]]; then
echo "오류: slug '$SLUG' 없음" >&2
exit 1
fi

# scope를 generic으로 변경한 사본 추가 (atomic write)
ABSTRACTED=$(echo "$ORIGINAL" | jq -c '.scope = "generic" | .slug = .slug + "-generic" | .context = .context + " (추상화)"')
TMPRAG=$(mktemp "${ASSET_FILE}.XXXXXX")
trap 'rm -f "$TMPRAG"' EXIT
cp "$ASSET_FILE" "$TMPRAG"
echo "$ABSTRACTED" >> "$TMPRAG"
mv "$TMPRAG" "$ASSET_FILE"

echo "{\"status\":\"abstracted\",\"slug\":\"${SLUG}-generic\"}"
PYTHONPATH="$SCRIPT_DIR" exec python3 -m harnish_py abstract-asset "$@"
38 changes: 3 additions & 35 deletions scripts/check-thresholds.sh
Original file line number Diff line number Diff line change
@@ -1,37 +1,5 @@
#!/usr/bin/env bash
# check-thresholds.sh — JSONL 자산의 태그별 카운트를 확인하고 임계치 도달 여부를 보고한다.
#
# 사용법:
# check-thresholds.sh [--threshold N] # 기본 5
# check-thresholds.sh --base-dir .harnish

set -euo pipefail

# check-thresholds.sh — Python migration wrapper (v0.1.0+)
# 실제 구현: scripts/harnish_py/thresholds.py
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"

BASE="$(resolve_base_dir)"
THRESHOLD=5

while [[ $# -gt 0 ]]; do
case $1 in
--base-dir) BASE="$2"; shift 2;;
--threshold) THRESHOLD="$2"; shift 2;;
*) shift;;
esac
done

ASSET_FILE="$BASE/harnish-assets.jsonl"

if [[ ! -f "$ASSET_FILE" ]] || [[ ! -s "$ASSET_FILE" ]]; then
echo "자산 없음"
exit 0
fi

jq -c 'select(.compressed != true) | .tags[]' "$ASSET_FILE" 2>/dev/null \
| sort | uniq -c | sort -rn \
| awk -v t="$THRESHOLD" '{
count=$1; tag=$2;
if (count >= t) printf "%s(%d건) ⚠ 압축 권장\n", tag, count;
else printf "%s(%d건)\n", tag, count;
}'
PYTHONPATH="$SCRIPT_DIR" exec python3 -m harnish_py check-thresholds "$@"
34 changes: 3 additions & 31 deletions scripts/check-violations.sh
Original file line number Diff line number Diff line change
@@ -1,33 +1,5 @@
#!/usr/bin/env bash
# check-violations.sh — harnish-current-work.json 위반/에스컬레이션 확인
# 사용법: bash check-violations.sh [harnish-current-work.json 경로]

set -euo pipefail

# check-violations.sh — Python migration wrapper (v0.1.0+)
# 실제 구현: scripts/harnish_py/violations.py
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"

PROGRESS_FILE="${1:-$(resolve_progress_file)}"

if [[ ! -f "$PROGRESS_FILE" ]]; then
echo "ERROR: $PROGRESS_FILE not found" >&2
exit 1
fi

VIOLATIONS=$(jq '.violations | length' "$PROGRESS_FILE" 2>/dev/null || echo "0")
ESCALATIONS=$(jq '.escalations | length' "$PROGRESS_FILE" 2>/dev/null || echo "0")

echo "위반 기록: ${VIOLATIONS}건"
echo "에스컬레이션: ${ESCALATIONS}건"

if [[ "$VIOLATIONS" -gt 0 ]]; then
echo ""
echo "── 위반 내역 ──"
jq -r '.violations[] | " \(.timestamp) | Task \(.task) | \(.violation) | 판단: \(.user_decision // "미결")"' "$PROGRESS_FILE" 2>/dev/null
fi

if [[ "$ESCALATIONS" -gt 0 ]]; then
echo ""
echo "── 에스컬레이션 내역 ──"
jq -r '.escalations[] | " \(.timestamp) | Task \(.task) | \(.blocked_at)"' "$PROGRESS_FILE" 2>/dev/null
fi
PYTHONPATH="$SCRIPT_DIR" exec python3 -m harnish_py check-violations "$@"
19 changes: 3 additions & 16 deletions scripts/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,16 @@
# 역할: 디렉토리·인덱스 관리, 환경 해석, 유틸리티 함수
# 규칙: L2 이상의 스크립트를 호출하지 않는다.
#
# v0.1.0: jq 의존 제거 — 모든 JSON 처리는 Python(harnish_py)으로 이전됨.
# 이 파일은 resolve_* 헬퍼와 slugify만 남긴다.
#
# 사용법 (다른 스크립트에서):
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# source "$SCRIPT_DIR/common.sh"

# ═══════════════════════════════════════
# 의존성 체크
# ═══════════════════════════════════════
require_cmd() {
local cmd="$1" install_hint="${2:-}"
if ! command -v "$cmd" &>/dev/null; then
echo "오류: '$cmd'이(가) 설치되어 있지 않습니다.${install_hint:+ $install_hint}" >&2
exit 1
fi
}

require_cmd jq "brew install jq"

# ═══════════════════════════════════════
# 환경 해석
# ═══════════════════════════════════════
# 이 파일이 source된 스크립트의 SCRIPT_DIR을 기준으로 한다.
# SCRIPT_DIR은 source하기 전에 설정되어야 한다.

# 자산 루트 경로 해석
# 우선순위: ASSET_BASE_DIR > CWD/.harnish
Expand Down Expand Up @@ -87,4 +75,3 @@ slugify() {
echo "$hash"
fi
}

Loading
Loading