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
21 changes: 16 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@ archives the feature to `.sdlc/archive/<slug>/`.

| What | Where | Written by |
|---|---|---|
| A product's business rule (정책) — "a submitted answer cannot be edited" | `memory/areas/<area-slug>.md` Business rules, numbered P1… (templates/area.md) | close merge, shipped only |
| What changed in an area, when | the area page's History, one line per feature | close merge, shipped only |
| A product's business rule (정책) — "a submitted answer cannot be edited" | `memory/areas/<menu path>.md` Business rules table, one row per rule — P1…, one plain sentence (templates/area.md) | close merge, shipped only |
| How an on-screen count, rate, score or chart figure is calculated | the same page's Numbers table, one row per figure — N1… (templates/area.md) | close merge, shipped only |
| Where a rule or figure came from, and the code behind the area | the same page's evidence block at the bottom (`<details>`, or the folded callout `> [!info]-` in an Obsidian store (index_style: obsidian)): the Where line, then one evidence row per rule or figure — `\| P1 \| source \| set by \| verified \|` | close merge |
| What changed in an area, when | the area page's History table, one row per feature | close merge, shipped only |
| A fact that holds for one area only | the area page's How it works | close merge |
| A term, or a system fact/constraint that spans areas | `memory/DOMAIN.md` (100 lines max) | close merge |
| A trap and the correct move | `memory/lessons/<date>-<slug>.md` + one `INDEX.md` line (50 lines max) | close merge |
Expand All @@ -159,16 +161,25 @@ archives the feature to `.sdlc/archive/<slug>/`.
named by its menu path (`학습 > 평가 > 제출`); otherwise a module, API,
job, or CLI command — not the `--area` knowledge folder of rule 7.
summary.md's `Area:` line and the page's `Menu:` line use the same words,
which is how `kb.sh` links them. DOMAIN over its limit: move
which is how `kb.sh` links them. The page's file name is that Menu with
each ` > ` written ` - ` and any of `/ \ : * ? " < > |` replaced with `-`
(`교사 > 학생 > 학급 분석` → `memory/areas/교사 - 학생 - 학급 분석.md`).
An area page reads reader first: rules, figures and history as table rows
and how it works as bullets, in plain language, on top; every source, verification, and code identifier
in the evidence block at the bottom — a `<details>` block, or the folded
callout in an Obsidian store (index_style: obsidian). DOMAIN over its limit: move
area-specific facts to their area page. INDEX over its limit: merge
near-duplicates, drop superseded entries, replace promoted ones
(skills/6-maintain).
**memory/ has one writer: the close step** (POLICY.md aside). Mid-loop,
stages and researchers append candidates — one line each — to
`.sdlc/work/<slug>/harvest.md` (templates/harvest.md). At close, merge
every candidate into its row above — creating an area page from the
template when none exists, numbering new rules, adding the feature's
History line to each area it changed — then delete harvest.md. Business
template when none exists (file named by its menu path, as above),
numbering new rules, writing each rule or figure as a table row and its
source as its evidence row, adding the feature's History row to each
area it changed (a line-shaped candidate becomes a row) — then delete
harvest.md. Business
rules and History describe what the product DOES, so they merge only when
the feature closes `shipped`: any other close drops them (summary.md keeps
the story), and a stale merge leaves them in harvest.md for that close;
Expand Down
4 changes: 2 additions & 2 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ agent APPROVED: intent of claims-status (.sdlc/work/claims-status/intent.md)
│ ├── POLICY.md # 사람이 선언한 하드 룰, 에이전트는 전사만
│ ├── INDEX.md # 교훈 포인터, 50줄 이하
│ ├── DOMAIN.md # 여러 제품 영역에 걸친 용어 · 사실 · 제약
│ ├── areas/<area-slug>.md # 제품 영역(웹앱은 메뉴)마다 한 장: 업무 정책 P1… · 동작 · 변경 이력
│ ├── areas/<메뉴 경로>.md # 제품 영역(웹앱은 메뉴)마다 한 장, 예: "학습 - 평가 - 제출.md": 업무 정책 P1… · 통계 산정 N1…(선택) · 동작 · 변경 이력 · 근거(접힘)
│ └── lessons/<date>-<lesson>.md
├── work/<slug>/ # 열린 피처만
│ ├── origin.md # 요청 당시의 티켓 · 기획서 스냅샷 — intent 게이트가 결합
Expand All @@ -169,7 +169,7 @@ agent APPROVED: intent of claims-status (.sdlc/work/claims-status/intent.md)

**기록을 어디에 둘지는 사용자가 정합니다.** 기본값은 프로젝트 작업 사본 안이고, `init.sh . --area ~/knowledge`를 쓰면 사용자가 고른 폴더 아래 `<area>/<단위이름>-<체크아웃 식별자>/`에 저장하고 `.sdlc`를 그곳으로 연결합니다. 체크아웃마다 저장소가 하나씩이므로 워크트리 두 개가 승인 상태를 공유하는 일이 없습니다. 영역이 프로젝트 안에 있거나 프로젝트가 영역 안에 있을 때, 다른 체크아웃이 이미 그 저장소를 소유할 때, 실제 `.sdlc` 디렉터리가 이미 있을 때(자동으로 옮기지 않습니다), 링크를 만들 수 없을 때는 아무것도 쓰지 않고 분명히 실패합니다. 이 소유권은 init 시점뿐 아니라 실행 시점에도 다시 확인합니다. `<store>/PROJECT`에 적힌 체크아웃이 지금 실행 중인 체크아웃과 다르면 `check-gate.sh`, `approve.sh`, `close.sh`, `status.sh`, `tools/auto.sh`, `tools/verify.sh`, `tools/handoff.sh`가 판정을 내리거나 상태를 쓰기 전에 거부하므로, 심볼릭 링크를 그대로 복사한 작업 사본(`cp -R`, rsync, 대부분의 백업 복원)이 다른 체크아웃의 게이트를 열거나 그 피처를 닫을 수 없습니다. 읽기는 이 제약을 받지 않아 `tools/kb.sh show|search|list`는 그대로 쓸 수 있고, 소유권을 자동으로 옮기거나 다시 묶는 일은 없습니다. 어느 쪽을 고르든 **저장소 백업은 사용자의 몫입니다.** git이 더 이상 대신해 주지 않습니다.

**지식은 제품 영역별로 정리됩니다.** 웹앱이면 제품 영역은 메뉴 하나이고 메뉴 경로(`학습 > 평가 > 제출`)로 부릅니다. 다른 소프트웨어는 모듈, API, 배치 작업, CLI 명령입니다. 제품 영역마다 `memory/areas/<area-slug>.md` 한 장이 있고, 여기에 개발자가 아니어도 읽을 수 있는 문장으로 쓴 업무 정책(P1, P2…), 동작 방식, 그 영역을 바꾼 피처별 이력 한 줄이 담깁니다. 피처의 `summary.md`는 `Area:` 줄로 제품 영역을 적고, spec은 어떤 정책을 유지하거나 바꾸는지 밝히고, Side effects 검증자는 건드리지 않아야 할 정책이 그대로인지 다시 확인하며, close 병합이 새로 생기거나 바뀐 정책을 페이지에 올립니다. `tools/kb.sh show "학습 > 평가 > 제출"`(또는 페이지 파일 이름)은 그 페이지와 그 제품 영역을 바꾼 피처를 함께 보여줍니다.
**지식은 제품 영역별로 정리됩니다.** 웹앱이면 제품 영역은 메뉴 하나이고 메뉴 경로(`학습 > 평가 > 제출`)로 부릅니다. 다른 소프트웨어는 모듈, API, 배치 작업, CLI 명령입니다. 제품 영역마다 페이지가 한 장 있고, 파일 이름은 메뉴 경로입니다 — `memory/areas/학습 - 평가 - 제출.md`(` > `는 ` - `로, `/ \ : * ? " < > |`는 `-`로 바꿉니다). 페이지는 읽는 사람 우선입니다. 위에는 개발자가 아니어도 읽을 수 있는 문장으로 쓴 업무 정책(P1, P2…), 동작 방식, 그 영역을 바꾼 피처별 이력 한 행이 오고(정책·수치·이력·근거는 한 줄에 하나씩 쓰는 마크다운 표), 출처·검증·코드 위치는 모두 맨 아래 접힌 근거 블록에 둡니다. 건수·비율·점수·차트를 보여주는 영역은 통계 산정(N1, N2…) 섹션도 가지며, 화면의 수치 하나마다 무엇을 세는지와 출처를 한 행으로 남깁니다 — 업무 정책과 같은 영속성·처리 방식을 따릅니다. 피처의 `summary.md`는 `Area:` 줄로 제품 영역을 적고, spec은 어떤 정책이나 수치를 유지하거나 바꾸는지 밝히고, Side effects 검증자는 건드리지 않아야 할 것이 그대로인지 다시 확인하며, close 병합이 새로 생기거나 바뀐 정책·수치를 페이지에 올립니다. `tools/kb.sh show "학습 > 평가 > 제출"`(또는 페이지 파일 이름)은 그 페이지와 그 제품 영역을 바꾼 피처를 함께 보여줍니다.

**기록을 다시 읽는 도구는 `tools/kb.sh`입니다.** `index`는 목차 페이지를 다시 만듭니다(`init.sh`와 `close.sh`가 자동으로 실행합니다). 페이지는 정책 수·마지막 변경·피처를 담은 제품 영역 표, 상태·날짜·제품 영역·태그를 담은 개요 표(최신순), 아직 close가 병합하지 않은 harvest 목록, 피처별 절 순서입니다. `show <slug>`는 피처 하나를 요약본으로 보여줍니다. 목표, `summary.md`(제품 영역·무엇이 문제였나·Before → After·확인 방법을 담는, 계속 갱신하도록 만든 유일한 기록), 배포 상태, 병합되지 않은 harvest 후보, 교훈 제목이 먼저 나오고 파일 경로는 마지막입니다. `search "<문자열>"`은 열린 피처와 닫힌 피처, 지속 메모리를 대상으로 출력량을 제한한 문자열 검색을 하고, `harvest [--stale <일수>]`는 harvest.md가 아직 memory/에 들어가지 않은 열린 피처를 유휴 기간과 함께 나열합니다(유휴 상태가 오래된 피처는 close 없이 병합할 수 있습니다 — AGENTS.md 규칙 4). `--area <폴더>`를 붙이면 그 폴더 안의 모든 저장소를 대상으로 같은 일을 하며, 원래 체크아웃이 사라진 피처도 읽을 수 있습니다. 저장소 config.md에 `index_style: obsidian`을 적으면 Obsidian 볼트용 frontmatter와 인라인 `#태그`를 덧붙입니다. 생성 시각은 절대 쓰지 않으므로 내용이 같으면 diff도 생기지 않습니다. 종료 코드는 `0` 찾음, `1` 없음, `2` 사용법 오류 또는 거부입니다.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Per feature, inside the **target project**:
│ ├── POLICY.md # human-declared hard rules; agents transcribe only
│ ├── INDEX.md # ≤50 lines of lesson pointers
│ ├── DOMAIN.md # terms · facts and constraints that span areas
│ ├── areas/<area-slug>.md # one per product area (web app: one menu): business rules P1… · how it works · history
│ ├── areas/<menu path>.md # one per product area (web app: one menu), e.g. "학습 - 평가 - 제출.md": business rules P1… · numbers N1… (optional) · how it works · history · evidence (folded)
│ └── lessons/<date>-<lesson>.md
├── work/<slug>/ # OPEN features only
│ ├── origin.md # the ticket / 기획서 as requested — bound by the intent gate
Expand All @@ -169,7 +169,7 @@ Per feature, inside the **target project**:

**Where the records live is your choice.** By default they sit in the project's working copy. `init.sh . --area ~/knowledge` puts them in a folder you choose instead — `<area>/<unit>-<checkout-id>/`, with `.sdlc` linked to it, one store per checkout so two worktrees never share approvals. The area is refused if it sits inside the project (or the project inside it), if another checkout already owns that store, if a real `.sdlc` directory is already there (nothing is ever relocated for you), or if the link cannot be made. That ownership is re-checked at RUNTIME, not only at init: `check-gate.sh`, `approve.sh`, `close.sh`, `status.sh`, `tools/auto.sh`, `tools/verify.sh` and `tools/handoff.sh` refuse before any verdict or write when `<store>/PROJECT` names a different checkout, so a copied working copy (`cp -R`, rsync and most restores keep the symlink) can neither open another checkout's gate nor close its features. Reading is never bound that way: `tools/kb.sh show|search|list` still works, and nothing is ever re-bound or moved for you. Whichever you choose, **the store is yours to back up** — git no longer does it for you.

**Knowledge is filed by product area.** For a web app an area is one menu, named by its menu path (`학습 > 평가 > 제출`); for other software a module, API, job, or CLI command. Each area has one page, `memory/areas/<area-slug>.md`: its business rules numbered P1, P2… in sentences a non-developer can read, how it works, and a history line per feature that changed it. A feature's `summary.md` names its area on an `Area:` line, the spec states which rules it keeps or changes, the Side effects verifier re-checks the rules it should not have touched, and the close merge files new or changed rules on the page. `tools/kb.sh show "학습 > 평가 > 제출"` (or the page's file name) prints the page with the features that changed it.
**Knowledge is filed by product area.** For a web app an area is one menu, named by its menu path (`학습 > 평가 > 제출`); for other software a module, API, job, or CLI command. Each area has one page, named by its menu path — `memory/areas/학습 - 평가 - 제출.md` (each ` > ` written ` - `; any of `/ \ : * ? " < > |` becomes `-`). The page reads reader first: its business rules numbered P1, P2… in sentences a non-developer can read, how it works, and a history row per feature that changed it — rules, figures, history and evidence are markdown tables, one row each; every source, verification, and code location sits in a folded evidence block at the bottom. An area that shows counts, rates, scores or charts also gets a Numbers section, N1, N2…, one row per figure recording what it counts and its source — the same permanence and treatment as a business rule. A feature's `summary.md` names its area on an `Area:` line, the spec states which rules or figures it keeps or changes, the Side effects verifier re-checks the ones it should not have touched, and the close merge files new or changed rules and figures on the page. `tools/kb.sh show "학습 > 평가 > 제출"` (or the page's file name) prints the page with the features that changed it.

**Reading the records back** is `tools/kb.sh`: `index` regenerates the contents page (`init.sh` and `close.sh` do it for you) — the product areas with their rule count, last change and features, an overview table by state, date, area and tags, newest first, the harvests no close has merged yet, then one section per feature; `show <slug>` prints one feature as a digest — goal, its `summary.md` (area, what was wrong, before → after, how to check, the one record meant to be kept current), delivery, unmerged harvest candidates, lesson titles, then the paths; `search "<text>"` does a bounded literal search over open and closed features plus durable memory; `harvest [--stale <days>]` lists open features whose harvest.md is not in memory yet, with idle time (a stale one may be merged without closing — AGENTS.md rule 4); and `--area <folder>` does any of these across every store in that folder — including features whose checkout no longer exists. `index_style: obsidian` in the store's config.md adds frontmatter and inline `#tags` for a vault; no timestamp is ever written, so an unchanged page produces no diff. Exit codes: `0` found, `1` nothing found, `2` usage error or refusal.

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.15.0
v0.16.0
30 changes: 30 additions & 0 deletions gates/selftest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,36 @@ LC_ALL=en_US.UTF-8 bash "$kit/tools/kb.sh" index >/dev/null
grep -qF '| [명단 > 내보내기](memory/areas/roster.md) | 1 | — | f1 |' .sdlc/README.md || fail "area table wrong: $(grep '명단' .sdlc/README.md)"
out=$(bash "$kit/tools/kb.sh" show "명단 > 내보내기")
case "$out" in (*"P1: 현재 학기만"*) ;; (*) fail "show <menu path> did not print the rule: $out";; esac
# a page named by its menu path (" > " → " - "), reader first, in tables:
# rules counted from rows above the evidence block (evidence and retired rows
# are not), last change from the History table, header words free
mkdir -p .sdlc/work/f3; printf -- '- Area: 교사 > 학생 > 학급 분석\n' > .sdlc/work/f3/summary.md
printf -- '%s\n' '# Area: 교사 > 학생 > 학급 분석' '- Menu: 교사 > 학생 > 학급 분석' '## Business rules (정책)' '| # | 정책 |' '|---|---|' \
'| P1 | 자기 학급만 본다 |' '| P2 | 전학생은 빠진다 |' '| ~~P3~~ | ~~지난 학기도 보인다~~ |' '| P4 | <rule> |' \
'## History' '| 날짜 | 작업 | 바뀐 점 |' '|---|---|---|' '| 2026-09-01 | f3 | 참여율 추가 |' '| 2026-08-01 | f1 | 처음 |' \
'<details>' '<summary>근거 · 코드 위치 (개발자용)</summary>' '' '- Where: ClassAnalysis#get' '' '| # | 출처 | 작업 | 검증 |' '|---|---|---|---|' \
'| P1 | 기획서 | f3 | code — 2026-09-01 |' '| P3 | retired 2026-09-01 by f3: 정책 변경 | f3 | human |' '' '</details>' > ".sdlc/memory/areas/교사 - 학생 - 학급 분석.md"
# the Obsidian form of the evidence block: a folded callout, lines prefixed "> "
printf -- '%s\n' '- Menu: 학생 > 과제' '## Business rules (정책)' '| # | Rule |' '|---|---|' '| P1 | 마감 후 제출 불가 |' \
'## History' '| Date | Feature | What changed |' '|---|---|---|' '| 2026-09-02 | f4 | 마감 표시 |' \
'> [!info]- 근거 · 코드 위치 (개발자용)' '> - Where: HomeworkApi#submit' '>' '> | # | Source | Set by | Verified |' '> |---|---|---|---|' \
'> | P1 | 기획서 | f4 | test — 2026-09-02 |' '> | P2 | 증거 행은 규칙이 아니다 | f4 | — |' > ".sdlc/memory/areas/학생 - 과제.md"
LC_ALL=en_US.UTF-8 bash "$kit/tools/kb.sh" index >/dev/null
grep -qF '| [교사 > 학생 > 학급 분석](memory/areas/교사%20-%20학생%20-%20학급%20분석.md) | 2 | 2026-09-01 | f3 |' .sdlc/README.md \
|| fail "menu-named table page row wrong: $(grep '교사' .sdlc/README.md)"
grep -qF '| [학생 > 과제](memory/areas/학생%20-%20과제.md) | 1 | 2026-09-02 | — |' .sdlc/README.md || fail "callout page row wrong: $(grep '과제' .sdlc/README.md)"
for q in "교사 > 학생 > 학급 분석" "교사 - 학생 - 학급 분석"; do
out=$(bash "$kit/tools/kb.sh" show "$q") || fail "show '$q' found no page"
case "$out" in (*"| P1 | 자기 학급만"*"| 2026-09-01 | f3 |"*"근거 · 코드 위치 (개발자용):"*"Where: ClassAnalysis#get"*"| P3 | retired"*) ;; (*) fail "show '$q' not reader first: $out";; esac
case "$out" in (*"<rule>"*) fail "show printed a placeholder row: $out";; esac
done
out=$(bash "$kit/tools/kb.sh" show "학생 > 과제") || fail "show found no callout page"
case "$out" in (*"| P1 | 마감 후"*"History:"*"근거 · 코드 위치 (개발자용):"*" - Where: HomeworkApi#submit"*" | P1 | 기획서 | f4 |"*) ;; (*) fail "callout evidence not read last: $out";; esac
printf '%s\n' "$out" | grep -q '^ *>' && fail "callout prefix printed: $out"
fns=$(sed -n -e '/^kb_field() {/,/^}/p' -e '/^kb_get() {/,/^}/p' "$kit/tools/kb.sh")
where=$(eval "$fns"; kb_get ".sdlc/memory/areas/학생 - 과제.md" Where)
[ "$where" = "HomeworkApi#submit" ] || fail "kb_get did not read Where inside the callout: '$where'"
bash "$kit/tools/kb.sh" show "../areas/roster" >/dev/null 2>&1 && fail "area lookup followed a path"
echo "ok: knowledge by product area"

echo "SELFTEST PASS"
2 changes: 1 addition & 1 deletion init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ EOF

[ -f .sdlc/memory/DOMAIN.md ] || cat > .sdlc/memory/DOMAIN.md <<'EOF'
# Domain knowledge — how THIS system works: terms and facts that span areas
# (≤100 lines; over → move area-specific facts to memory/areas/<area-slug>.md)
# (≤100 lines; over → move area-specific facts to memory/areas/<menu path>.md)
# ONE writer: the close step. Mid-loop candidates stage in the feature's
# work/<slug>/harvest.md and merge here at close (AGENTS.md rule 4).
# Facts carry [verified: how — YYYY-MM-DD]. Recency wins: a merge candidate
Expand Down
Loading
Loading