Releases: cskwork/sdlc-kit
Release list
v0.17.0: verification gates ship — baseline, coverage, flaky proof
v0.17.0 — verification that gates ship: baseline, coverage, per-feature recipes
Until now only the host driver enforced the verification receipt. A human
could approve ship and close shipped over a failed, stale or missing
receipt, even under profile: strict; a failure the base already had read
like a regression; nothing proved a new test could fail; a recipe that skipped
a requirement still said VERIFY ok; and two open features collided in the
one project recipe. This release closes those holes. A project without
.sdlc/verify.md behaves as before. The reference is docs/automation.md §4;
the recipe syntax is templates/verify.md.
Changes
- The receipt gates ship.
approve.sh ship(every mode),close.sh shippedandcheck-gate.sh shipread one verdict table
(gates/_auto.sh sdlc_verify_gate), shared withauto.sh,handoff.shand
status.sh.okpasses;unconfiguredpasses with a note;blockedneeds
--accept-gap "<the human's words>"(never--lazy); every other state is
refused with its fix. Accepted words are recorded in the ship approval and,
on ashippedclose, inCLOSED. - No git repository is now
blocked: the receipt binds no source. - Baseline. New
tools/verify.sh baseline <slug> [--base <ref>]runs the
project recipe's build/unit/lint checks at the base commit in a disposable
worktree and writesverify-baseline.md.runlabels a failure the base
shared, with the same exit status,pre-existing. Newbaseline_setup:. - Must fail on base. A check may end in
| must-fail-on-base; with the
newtest_paths:, the baseline runs the new test against the old code.
Passing there, or not running (124/126/127), is the new statevacuous. - Coverage. Every requirement id (spec.md R, compact intent.md O) needs a
check named for it or agap: <id> | <reason>line, else the new state
uncovered. Strict needs a runtime/e2e check and thehappy,boundary
andnegativevariants. Newtools/verify.sh coverage <slug>. - Per-feature recipe. Optional
.sdlc/work/<slug>/verify.md
(templates/verify-feature.md),check:/gap:lines only; the receipt binds
its digest. - Flaky. A failing runtime/e2e check runs once more; passing then is the
new stateflaky, refused likefail. - Lens verdicts. Ship needs a
VERDICT:line under each verifier lens in
evidence.md, or AGENTS.md rule 5's gap line. - Safety.
forbidden_hosts:refuses a recipe that names a listed host.
tools/_run.pyredacts credentials in every log before it is hashed
(best-effort). - Parsing. A UTF-8 BOM no longer hides
profile: strict; value lines take
a trailing# comment; an indented or tabbed directive is refused, not
skipped;doctor_attempt_timeoutis validated. auto.sh --json: new blockersverify.uncovered/verify.vacuous;
flakyisverify.fail; a verification that fails after the ship approval
blocks the delivery stage.verify.shresolves a pyenv/asdf python shim once per run.approve.sh shipwrites the source snapshot before the record.kb.shnever readsverify-baseline.md.
Agent guidance
- roles/verifier.md: baseline, run and coverage with a recipe;
pre-existing vs regression; every changed file traced; the scenario floor
(happy, boundary, negative, + regression, + authz) with expectations first,
per role and platform, source rung stated; writes read back; new tests
must fail on base; red flags. - roles/adversary.md: seven blocking clean-code findings and a per-file
table. - skills/4-build, 5-ship, AGENTS.md rule 6, templates: the checks are
written with the code; ship needsokor the human's--accept-gap;
evidence.md carries variants, the receipt line and each lensVERDICT:.
Compatibility
- No recipe: no change beyond one note line.
- With a recipe: a ship approval or
shippedclose over a non-okreceipt
is now refused. A recipe with no git repository needs--accept-gap. - Old recipes may read
uncovereduntil checks are named for requirements
(R1,R1.happy) or gap lines are added; strict recipes also need the
variants. - evidence.md needs the lens
VERDICT:lines, recipe or not. - Old receipts stay valid; the schema stays
sdlc-kit/verify-receipt@1.
Validation
bash gates/selftest.sh→SELFTEST PASS. New sections cover the
ship gate,--accept-gap, baseline and pre-existing, must-fail and
vacuous, coverage and the strict variants, per-feature recipes, flaky,
forbidden hosts, redaction, the parser rules, worktree cleanup, no-git,
and a receipt going stale when the source changes (a gap before v0.17.0).- Faster: the selftest runs its independent sections in parallel (~31 s →
~9 s), andsdlc_sha256_stdin(preferssha256sum) andsdlc_field
(no awk per field) roughly halve the processesverify.sh run/check
start. bash -non every changed script; no CRLF.- By hand:
auto.sh --jsonblockers,--basehandling, TERM/HUP during a
baseline, and the worktree-removal fallback.
v0.16.0: area pages — figures, menu-path file names, reader-first tables
v0.16.0 — area pages: figures, menu-path file names, reader-first layout
v0.15.0 gave a product area one home for its business rules (P-numbers). A
menu that shows counts, rates, scores or charts had nowhere to say what a
number on screen actually means — "what is counted, out of what, real-time
or batch" lived in someone's head or nowhere. This release adds an optional
Numbers section to the area page, numbered N1… with the same permanence and
filing rules as a business rule. It also names each area page by its menu
path, so the file list reads like the product's menus, and lays the page out
reader first: plain language on top for a non-developer, every source,
verification, and code location folded into an evidence block at the
bottom, with rules, figures, history and evidence as markdown tables. Gates,
approvals, and the --json schema are unchanged; kb.sh's rule count still
counts only live rules (a | P<n> | row, or an older - P<n>: line).
Changes
templates/area.mdgains an optional Numbers (통계 산정) section
between Business rules and How it works: one row per on-screen figure,
named as the screen labels it, recording what it counts, out of what,
whether it is real-time or batch (job, refresh, data-as-of), its source,
and who set it. Delete the section for an area with no figures. N-numbers
follow the P-number rules: permanent, a changed figure is edited in place,
a retired one stays struck through.- The loop treats a figure like a business rule wherever omitting it
would leave it unprotected:templates/spec.md's "Business rules touched" section gains a sibling
line for numbers touched (kept/changed/new), so the Side effects
verifier re-checks a figure it did not set out to change, the same way
it already re-checks a P-number.- Ship's retrospective (
skills/5-ship/SKILL.md) writes a figure a
feature sets, changes, or retires as an area candidate, same as a
business rule. templates/harvest.md's Area candidates format gains theN<n>
line shape the close merge numbers, next to the existingP<n>one.- AGENTS.md rule 4's "what goes where" table gains a row: a figure's
calculation method files to the area page's Numbers, written by the
close merge on a shipped close only — same as a business rule.
- An area page is named by its menu path. The file name is the Menu
line with each>written-, and any character a Windows or macOS
file name may not hold (/ \ : * ? " < > |) replaced with-: Menu
교사 > 학생 > 학급 분석→memory/areas/교사 - 학생 - 학급 분석.md. The
"ASCII kebab-case file name" instruction is gone fromtemplates/area.md,
AGENTS.md rule 4, the READMEs, andinit.sh's DOMAIN.md header; the
close merge (AGENTS.md rule 4,skills/5-ship) is told the rule when it
creates a page. tools/kb.shfinds an area page by its file name, by its Menu line,
or by the file name a Menu gives (kb_area_file); a name holding/or
\, or starting with., is still never looked up as a file. The
contents page's Product areas links percent-encode space%()[
]#<>(kb_area_href) so a link to교사 - 학생 - 학급 분석.md
opens in GitHub and Obsidian; Hangul bytes stay as written.kb_body
prints a<summary>…</summary>line as a section label, soshowprints
the page top first and the evidence block last, under
근거 · 코드 위치 (개발자용):— the part the line bound cuts first. The
Rules column no longer counts an unfilled template row| P1 | <…> |
(or line- P1: <…>), andshowdrops such a line the way it already
dropped- Where: <…>.- Reader-first
templates/area.md. Menu and Aliases on top; Business
rules, Numbers, How it works, and History in plain sentences with no
source and no code; then a<details>block — "근거 · 코드 위치
(개발자용)" — holding the Where line and one evidence row per rule or
figure:| P1 | source | set by | verified |. Evidence rows sit below
the block's opening line, where the rule count stops, so they are never
counted as rules. A retired rule stays struck through on top
(| ~~P3~~ | ~~rule~~ |) and its evidence row's Source cell carries
retired YYYY-MM-DD by <slug>: <why>. The section
headings are unchanged.templates/harvest.md's area candidates split
the plain sentence from its evidence the same way, and ship's
retrospective says which part goes where. - Obsidian form of the evidence block. Obsidian does not render markdown
inside<details>, so a store withindex_style: obsidianwrites it as
a folded callout (> [!info]- 근거 · 코드 위치 (개발자용), every line
prefixed>);kb.shreads both forms (Where found, callout lines
printed last without>, never counted as rules). - Tables for rules, figures, history and evidence. Each is one
markdown table row (| P1 | <rule> |,| N1 | label | counted | out of | timing |,| YYYY-MM-DD | slug | what changed |, and in the evidence
block| P1 | source | set by | verified |under the- Where:line);
Menu, Aliases and How it works stay list lines. A retired rule is
| ~~P3~~ | ~~rule~~ |, its reason in its evidence row's Source cell.
Header words are free (| # | 정책 |works); a literal|in a cell is
\|.kb.shkeys on row shapes only: the Rules count reads| P<n> |
rows above the evidence block (the<details>or> [!…]line), the
last change reads History's first| YYYY-MM-DD |row, andshow
prints tables as written — callout rows without>— dropping only a
template placeholder row (every cell after the first<…>) and the
header of a table left with no rows. The close merge (AGENTS.md rule 4,
ship, harvest.md) writes rows; harvest candidates stay line-shaped. - README.md / README.ko.md: the "Knowledge is filed by product area"
paragraph and the memory-tree comment now mention the optional Numbers
section, the menu-path file name, and the folded evidence block.
Compatibility
Existing ASCII-named area pages keep working. kb.sh show "<menu path>" still finds a page by its Menu line whatever the file is called,
the contents page still lists and links it, and features still file under
it by their Area: line. Only a newly created page takes the menu-path
name. To rename an old page, move it to the name its Menu line gives (a
plain mv when the store is not in git), then regenerate the contents page:
git mv ".sdlc/memory/areas/class-analysis.md" ".sdlc/memory/areas/교사 - 학생 - 학급 분석.md"
tools/kb.sh indexA feature whose Area: line names the old file name
instead of the Menu should be changed to the Menu words. Nothing is renamed
for you.
List-form pages keep working. A page written with - P<n>: rule
lines and - YYYY-MM-DD <slug> — … History lines (v0.15.0 and the first
v0.16.0 commits) is still counted and dated, and a page may mix both forms;
convert it to rows whenever the close merge next edits it.
Existing pages with inline sources keep working. A rule written the old
way — - P1: <rule> — source: … · set by … — still counts and still
prints; the close merge moves its source to the evidence block the next
time it edits that rule, or you can move it by hand.
An area page with no Numbers section is unaffected: the section is
optional and kb.sh's Rules column stays a P-number count only (a store
with only P-numbers renders identically to before). No script parses the
N<n>: line; filing it is an instruction to the agent, exactly like a
P<n>: line.
Validation
bash gates/selftest.sh→SELFTEST PASS. Its "knowledge by product
area" test now also files a feature under a Hangul menu page named
교사 - 학생 - 학급 분석.md, laid out reader first with evidence rows and
a retired rule: the contents-page row links the percent-encoded path with
a Rules count of 2 (evidence and retired rows not counted);showby
Menu and by file name prints the rules before the evidence block; a
lookup holding/is refused; a second page in the Obsidian callout form
is read the same way. Both pages are in table form with Korean header
words: evidence rows, a retired row and a placeholder row are not
counted, the last change comes from the History table, andshow
prints the tables with the evidence last. The list-formrosterpage
still counts. With the oldkb.sh, the test fails.
v0.15.0: knowledge filed by product area, with its business rules
v0.15.0 — knowledge filed by product area, with its business rules
Records were filed by feature and date. Nobody could ask "what are the rules
for the submit screen?" without knowing which slug changed it. In real stores
DOMAIN.md had turned into long evidence sentences with file paths and
hashes, not something a person reads. summary.md had five one-line fields
and no store had used it. This release files knowledge the way people
navigate a product, by area (for a web app, by menu), and gives business
rules (정책) one home. Gates, approvals, the --json schema, and lazymode are
unchanged.
Changes
-
Product area pages (
templates/area.md→memory/areas/<area>.md).
There is one page per area. For a web app an area is one menu, named by its
menu path (학습 > 평가 > 제출); for other software it is a module, API,
job, or CLI command. Each page has:Menu:,Where:, andAliases:lines;- Business rules (정책), numbered P1… and written as testable sentences
a non-developer can read, each with its source and the feature that set
it (P-numbers are never reused, and a retired rule stays struck through); - a short How it works;
- a History line per feature that changed the area.
init.shseedsmemory/areas/. The page replaces DOMAIN.md's old
"split intomemory/domain/<area>.md" overflow rule, so there is one
concept instead of two. -
What goes where, in one table (AGENTS.md rule 4). Business rules go to
the area page, area history to the page's History, cross-area terms and
facts to DOMAIN.md, traps to lessons, agent rules to POLICY.md (only on the
human's word, and never a business rule), and one feature's story to
summary.md. Every row except POLICY.md and summary.md is written only by
the close merge, which creates a missing area page from the template. -
The loop uses the pages:
- Intent names the area and cites the P-numbers the request touches.
- The spec's AS-IS names each rule it keeps, changes, or retires.
- The Side effects verifier re-checks the rules the change did not set out
to change. - Ship writes every set, changed, or retired rule and a history line as area
candidates (a newArea candidatessection intemplates/harvest.md).
-
summary.md reads like a note to a colleague: a Goal sentence as its
title, thenArea,Tags, andStatus(only what delivery.md confirms),
followed by What was wrong, Before → After, How to check, and
Remember. -
tools/kb.sh:indexopens with a Product areas table (live-rule count, last
change, features, and areas a feature names that have no page yet).- The overview table gains an Area column.
show <name>falls back to a product area — the page's file name, its
exact menu path, or an area features name with no page yet — and prints
it with the features that name it. AnArea:line may use the menu path
or the file name.searchalready coveredmemory/, so business rules are found by their
words.- An area name containing
/or starting with.never resolves to a file. - A record heading with nothing under it is no longer printed, so an
unfinished summary shows only what is known. Summaries print up to 20
lines (was 12). - Messages: a
showmiss reads "no feature or product area",showwith
no argument asks for "a feature slug or a product area", an--areamiss
reads "no such knowledge folder (--area)", the usage text lists
show <slug | product area>, the contents page's Search line names
show <slug | area>, and the harvest and close hints name the area
pages.
-
kb.shruns with byte semantics (LC_ALL=C). Under a UTF-8 locale,
macOS awk compares strings by collation, so different Hangul menu paths
compared equal and every feature was filed under every area. Found by macOS
CI (en_US.UTF-8). -
Rules and history describe what shipped. They merge only on a
shippedclose; any other close drops them, and a stale merge leaves
them in harvest.md for that close. The merge
gives a new rule the next number its page never used. A fact about one area
goes to that page's How it works. The spec template gains a Business
rules touched section, and the spec adversary and the verifier read the
area pages. -
E2E lens findings from v0.14.0, fixed:
- The compact route gains a
Baseline:line (build reads it). - Probe 8 covers any outbound call, not only UI.
- evidence.md and the ship authorization name the compact-route source.
- The router states that a broken-and-undiagnosed report goes to stage 6
ahead of the generic change row. - The compact criterion now explains why optional open questions still
disqualify it: the compact route has no spec to answer them.
- The compact route gains a
Compatibility
Older summaries (Problem/Cause/… bullets) still print, now up to 20 lines. A store with
no memory/areas/ and no Area: lines gets no Product areas section. The
overview table's header gains a column; gates/knowledge-test.sh H8 is
updated for that deliberate change.
Validation
bash gates/knowledge-test.sh→KNOWLEDGE-TEST PASS, 152, under the
en_US.UTF-8, ko_KR.UTF-8 and C locales. H38–H45 are new and cover one
contract each: area row, page-less area,showby menu path,showof a
page-less area, path walk, unfilled summary, filled section, and Area by
file name. H38, H39 and H45 fail on the pre-fix kb.sh under en_US.UTF-8.- Three fresh-context verifier lenses (E2E, Side effects, Intent match).
Round 1 found the unfilled-template leak (blocking) and ~20 minor gaps. A
round-2 re-check found every one resolved (PASS), including against the old
kb.sh on seven real stores (only named differences, byte-identical
regeneration, unchanged exit codes). It also found four minor new gaps
(usage line, changelog wording, stale-merge rule loss, researcher
destination), and those were fixed. bash gates/selftest.sh→SELFTEST PASSbash gates/e2e.sh→E2E PASS, 152 ·bash gates/autotest.sh→AUTOTEST PASS, 195
Tests: one smoke test
The kit is mostly instructions, and four suites (about 3,800 lines, several
minutes per run) mostly re-checked wording. gates/e2e.sh,
gates/autotest.sh, gates/knowledge-test.sh, and
gates/win-restricted-run.py are removed. gates/selftest.sh is now one
smoke test (~75 lines, a few seconds) covering: scripts parse and are
LF-only, SKILL.md frontmatter, gates bind content and upstream, lazymode
limits, close proof (lesson, ship approval, confirmed delivery), and
product-area filing under a UTF-8 locale. CI runs only that: on pull
requests (branch protection requires its checks) and by hand, no longer on
every push to main or on tags.
v0.14.0: leaner contract, test-first bug fixes, generic intake
v0.14.0 — a leaner contract, test-first bug fixes, generic intake
Real stores showed where the kit's words went. The agent-facing prose had
grown to about 128 KB. AGENTS.md restated what the scripts already enforce,
each stage skill repeated the heartbeat and memory paragraphs, and bug fixes
could "prove" themselves with reproduction steps that nothing re-runs. The
kit is used for any software (web, API, batch, CLI), but stage 6 intake still
asked only UI questions. This release cuts the duplication and makes the bug
proof durable. Gates, scripts, the --json schema, and lazymode are
unchanged; init.sh gains three optional config lines.
Changes
AGENTS.mdkeeps what an agent must decide (453 → 324 lines, −31%).
Mechanics a script enforces and prints a reason for (digest binding,
ship-snapshot rules, C-quoted paths, handoff refusals, store ownership) are
now one line each. Text thatdocs/automation.mdalready holds (full-auto
intent contract §3, verify receipt §4) is now a pointer. Rule numbers and
section names are unchanged. The stale "rule 3a" citations in
gates/_auto.shandgates/status.shnow resolve: 3a labels the full-auto
intent contract.- Defined once. Heartbeat (rule 9, now listing the stage names and
build's n/m) and memory reading (rule 4) are no longer repeated in six stage
skills; each skill carries a one-line pointer. The harvest/close-writer
rule, thekb.shdigest description, and tripwire caveats are referenced,
not restated.SKILL.mddrops its Coexistence and Invariants summaries,
which repeatedAGENTS.md.roles/researcher.mdpoints atprobes.md
instead of copying three probes. - Bug fixes start with a failing test (AGENTS.md rule 6). By default the
reproduction is an automated test at the lowest level that reaches the
defect. It fails on the pre-fix code for the reported reason, passes after,
and stays in the suite. Manual steps or logs stand in only when no test can
reach the defect, and the evidence says why. Stage 6 drafts the test outside
the source tree, build adds it before the fix, and the verifier runs it
against the pre-fix commit and must see it fail.templates/evidence.md
gains aRegression test:line, and the plan's Proof and the compact
route's Proof line name it. - Generic stage 6 intake. The five questions now fit a UI, an API, a job,
or a CLI: what was done with what input, what happened versus what was
expected, where and when, as whom, and what trace exists (request or trace
id, log line, affected record keys). A symptom class (nothing happened /
wrong result / looks wrong / intermittent) replaces the UI-only
"does not react vs looks disabled" split. Probe 2 is no longer a MyBatis
awkovermapper.xml: it diffs the filters of every query on one entity
(SQL, ORM, API params, cache keys). - Projects name their own helpers. New optional
researcher:,
verifier:,adversary:keys in.sdlc/config.md: the named agent or
skill is dispatched with the kit's role file as its contract ("Running
beside…" rule 3). Empty or absent means the old behavior. Existing configs
are not rewritten.
Agent-facing prose (SKILL.md, AGENTS.md, stage skills, roles): 104,136 →
~89,700 bytes.
Validation
bash gates/knowledge-test.sh→KNOWLEDGE-TEST PASS, 144bash gates/selftest.sh→SELFTEST PASSbash gates/e2e.sh→E2E PASS, 152 ·bash gates/autotest.sh→AUTOTEST PASS, 195- Three fresh-context verifier lenses over the diff: E2E (init.sh in a
disposable repo, plus a backend-bug compact walk and a UI full-route walk),
Side effects (removed instructions and cross-references), and Intent match
(against the approved recommendation). Their minor findings in the changed
text were fixed before release.
v0.12.0: external knowledge areas and retrieval
v0.12.0 — records out of git, an external knowledge area, and retrieval
Records are the project's knowledge, not its source. init.sh now ignores the
whole of .sdlc/ with one anchored rule, an optional external area keeps a
checkout's store outside the checkout entirely, and tools/kb.sh is the way to
read any of it back. Gate authority, tamper detection, source binding, the
--json automation schema and lazymode are unchanged in kind.
Changes
- One ignore rule:
/.sdlc.init.shwrites it, anchored to the project root, so it matches this project's records — a real directory or the symlink an external area installs — and never a.sdlcdeeper in the tree (a nested shipping unit keeps its own rule, written by its own init run). The twenty narrower kit-owned lines earlier versions issued are subsumed and removed by exact match; a CRLF.gitignoreis handled, and every other line, its bytes and its line ending, is written back untouched — read and written by the shell itself, never through an awk that may translate line endings on Windows. The git index is never touched: records already committed by an older kit stay committed until you untrack them yourself, andinit.shprints the exact command. init.sh [dir] --area <folder>— an external knowledge area. The store is<folder>/<unit>-<checkout-id>/,.sdlcis a symlink to it, and<store>/PROJECTrecords the owning checkout, its unit, its id, the creation time and the kit version. One store per checkout, so two worktrees or two same-named clones never share approvals. Refused, writing nothing: an area inside the project, a project inside the area (checked before any folder is created), a store another checkout owns, a directory that is not a store, an unwritable area (refused by the writability test and, if that test cannot see the right — a Windows deny ACL — by the first real write, before anything is linked), a.sdlclink that points elsewhere, or a link the filesystem cannot create.- Ownership is enforced at runtime, not only at init.
check-gate.sh,approve.sh,close.sh,status.sh(prose and--json),tools/auto.sh,tools/verify.sh,tools/handoff.sh,init.sh(including an ordinary re-run) andtools/kb.sh indexall refuse, before any verdict and before any write, when the store they reach names a different checkout, carries no ownership record, or carries an unreadable one. This closes a real accident:cp -R, rsync,tarwithout--dereferenceand most backup restores preserve the.sdlcsymlink, so a copied working copy resolved into the ORIGINAL's store and could read its approvals and archive its features. An ordinary project-local.sdlcdirectory has noPROJECTrecord, needs none, and is unaffected. - Nothing is migrated, re-bound, or moved for you. A refusal prints the store, the recorded owner, this checkout, how to read the records (
tools/kb.sh --store/--area), how to give this checkout a store of its own, and — for a renamed or moved checkout — the one manual line to edit (project:in<store>/PROJECT). A real.sdlcdirectory is never relocated by--area; it keeps working exactly as it is. tools/kb.sh— reading the records back.indexregenerates a store's contents page (init.shandclose.shrun it; aREADME.mdit did not generate is never overwritten),show <slug>prints one feature's goal, track, documents, delivery and lessons,search "<text>"is a bounded literal (grep -F) search across open and closed features and durable memory, andlistnames stores and features.--area <folder>covers every owned store in that folder, read-only and one level deep, following no symlinks — including features whose checkout no longer exists.scratch/,approvals/,progress.md,baseline.txt,checkpoint.mdandverify-receipt.mdare never read. Exit codes:0found,1nothing found,2usage error or refusal.- Readable store names. Only path-hostile characters are replaced in the unit name, so a Korean, Japanese or accented checkout name stays legible in the area listing and in
kb.shoutput (지식-프로젝트-e2117a76), instead of collapsing toproject-<id>. - The source snapshot excludes the bare
.sdlcentry as well as its descendants, in both the working-source list and the committed-tree enumeration, so an external store's symlink is never bound by a ship approval and moving the area is not drift. - Stage guidance and docs.
skills/1-intent,skills/5-ship,skills/6-maintain,templates/evidence.md,AGENTS.mdrule 7,SKILL.md, both READMEs,docs/index.htmlanddocs/automation.mdstate the same storage behaviour: records are ignored by default, a clone does not carry them, the store is yours to back up, and a stage starts with a targetedkb.sh search/showinstead of scanning the archive.
Usage
# default: records stay in the checkout, ignored by git
~/sdlc-kit/init.sh .
# or keep this checkout's records outside it, in a folder you choose
~/sdlc-kit/init.sh . --area ~/knowledge
# → ~/knowledge/<unit>-<checkout-id>/ , with .sdlc linked to it
# read the records back, from the project
~/sdlc-kit/tools/kb.sh index # refresh this store's contents page
~/sdlc-kit/tools/kb.sh show 260920-login-fix # one feature: goal, documents, delivery, lessons
~/sdlc-kit/tools/kb.sh search "rate limit" # bounded literal search
# … or across every store in the area, from anywhere, read-only
~/sdlc-kit/tools/kb.sh list --area ~/knowledge
~/sdlc-kit/tools/kb.sh show 260920-login-fix --area ~/knowledge
~/sdlc-kit/tools/kb.sh search "rate limit" --area ~/knowledge --limit 20Upgrade notes
- Existing projects keep working. Re-run
init.shto get the single/.sdlcrule and the cleanup of the lines it subsumes. Your existing records are not moved, rewritten or deleted, and the git index is left exactly as it is — already-committed records stay in history until you run the untracking commandinit.shprints. - There is no automated migration, by design.
--areanever relocates a real.sdlcdirectory: to use an area for a checkout that already has local records, move them aside yourself and letinit.shcreate an empty store; the old records stay readable withtools/kb.sh --store <path>. - Approval state is per checkout; retrieval is area-wide. Gates, status, verification and handoff answer only for the checkout that owns the store. Reading (
kb.sh list|show|search, with or without--area) is not bound to any checkout, which is what lets knowledge outlive the worktree that produced it. - Backups and versioning of the store are yours, whichever location you choose: git no longer carries the records, and this kit adds no backup mechanism.
- Windows:
--arearequires real symlinks — run Git Bash withMSYS=winsymlinks:nativestrict.init.shfails loudly if the link comes out as a copy rather than creating a forked store, andgates/knowledge-test.shreports the external-area cases as NOT VERIFIED where the filesystem cannot link at all. docs/index.htmlno longer describes the durable record as committable; that policy is the one this release inverts.
Validation
Local, on macOS (darwin 25.6.0, arm64, GNU bash 3.2.57, git 2.54.0, APFS
case-insensitive, UTF-8):
bash gates/selftest.sh→SELFTEST PASSbash gates/knowledge-test.sh→KNOWLEDGE-TEST PASS, 107 assertions, 0 failures (new suite: the ignore rule, area binding and every refusal, the loop through the link, owner isolation against acp -Rcopy, CRLF ignore cleanup, readable Unicode store names, retrieval after the checkout is deleted)bash gates/e2e.sh→E2E PASS, 152 assertions, 0 failuresbash gates/autotest.sh→AUTOTEST PASS, 195 assertions, 0 failuresbash -nover every changed shell script: clean.git diff --check: clean.
The CI matrix runs all four suites on Ubuntu, macOS and Windows Git Bash for
pull requests, main and version tags. The published release notes link the CI
results for the shipped source; earlier runs are not evidence for later edits.
The Windows permission fixture uses a native ACL confined to its temporary
directory and proves an actual write is denied before testing init. Retained
history is searched from a valid directory after the checkout is deleted, with
its evidence bytes and the same query results checked. CRLF preservation is
checked byte by byte, independently of text-mode awk.
Release verification
- PR #5 merged after all required Ubuntu, macOS and Windows Git Bash checks passed in run 35518755595. Each runner executed knowledge, selftest, E2E and automation suites.
- The released commit is
cfb4c2e0ca403b74f6adf621cbc48ee9cb6c5f4a. Its source tree is identical to the tested PR head3bd9525f2e8d01b1159ded1131683e91c7c31132. Remotedev,main, andv0.12.0were verified at that release commit. - Windows permission-denial coverage runs the real write probe and initializer with backup/restore privileges removed only from the test subprocess. No Windows check was waived.
v0.11.0: three-lens verification, bound origin, machine-readable fix loop
v0.11.0 — three-lens verification, bound origin, machine-readable fix loop
The build stage's verification grows from one E2E pass into three lenses that
run in parallel, each in a fresh context, under the one existing role
contract; the request's origin becomes a bound artifact; the fix-loop cap and
data-consistency checks become machine-readable. The gates and the source
binding are unchanged in kind.
Changes
- Three verification lenses.
roles/verifier.mdis now the single definition of what verification checks, dispatched one lens each: E2E (the change through the real interface; the bug-fix proof chain; theverify.shreceipt), Side effects (baseline and untouched items, neighbouring flows, and data consistency — every shape the diff writes or reads followed to its other producers and consumers), and Intent match (the build read back, per intent.md O-item, against the origin of the request, listing what is covered, missing, and beyond; a detail dropped between the origin and an approved spec.md is a finding). The verifier now receives intent.md and its origin on the full route too — before, it saw only plan.md and spec.md, so the human's actual ask was never read back. origin.md— the ticket / 기획서 as requested, bound by the gates (templates/origin.md). Stage 1 snapshots the origin BEFORE the intent gate;approve.sh intentbinds its digest exactly like a spec or plan upstream (upstream_origin:), and every downstream gate,status.sh,check-gate.sh,tools/auto.shandclose.shreport a rewrite in the same words (origin.md changed after … — re-approve intent, then <stage>). An origin written after the approval reads as unbound and closes the gate, exactly like a late spec.md.originis never a gate of its own:approve.sh originis refused. Absent when the request had no origin beyond the chat — nothing is demanded then. It is part of the durable record (AGENTS.md rule 7, init.sh, README trees). One artifact map now serves every script (_common.shsdlc_artifact_of);status.shand_auto.shdropped their private copies.- O-numbered success criteria. intent.md's Success criteria are
O1..Onin the origin's words (else the human's); spec R-items cite them (R1: … (O1)); an O-item with no R is a flagged concern, never a silent drop. The adversary's traceability check and the verifier's Intent match lens count coverage over them instead of rediscovering it. - Data touched in plan.md. The plan names every shape the changed files write or read, with its other producers and consumers and what happens to pre-existing records. The plan adversary checks the list is complete; the Side effects lens executes it and treats a missed shape as a finding.
- Fix loop, any lens, machine-readable cap.
skills/4-buildroutes a finding from ANY lens into the fix loop. A round is one deviations.md line — lens, accepted, declined,re-check: pending | resolved | open: <what>updated in place. The cap stays at three rounds.gates/_auto.shsdlc_auto_fixloop_statereads those lines: round 3 stillopen, or any round past 3, isfixloop.exhausted—tools/auto.sh nextexits 10 (needs-human) at every lazymode, both while build is open and once evidence.md exists over it (the lazy ship gate is refused),status --jsoncarries"fix_loop", andstatus.shprintsFIX LOOP EXHAUSTEDand overrides its next action, exactly like an open material question. A finding that implies new scope is a human decision, not a fix. datacheck kind in.sdlc/verify.md(templates/verify.md, docs/automation.md): a read-only consistency query for the Side effects lens, receipted like every other check and never counted as runtime evidence.templates/evidence.mdcarries the three reports under one Verification section (E2E · Side effects · Intent match · Fix loop) and names the origin and its live re-read; the old End-to-end and Regression sections fold into it.- DRY: the real-E2E prose that was repeated in AGENTS.md rule 6,
skills/4-build,skills/5-shipandroles/verifier.mdnow lives in the role file; rule 6 states the contract in one paragraph and the stage skills reference it.
Upgrade notes
- Nothing is required. Existing approvals keep working: a feature without origin.md binds nothing new, and existing evidence.md files stay valid — no script parses their section names.
- Existing
deviations.mdround lines without are-check:field read asopen(in progress) and never as exhausted; only a round past 3, or a round 3 markedopen, blocks. - Snapshot the ticket or 기획서 as
origin.mdBEFOREapprove.sh intent; written afterwards it closes the gate as unbound until intent is re-approved — that is the binding working. status --jsongainedfix_loop; drivers that ignore unknown fields are unaffected.
Validation
Local, on macOS (Bash 3.2) — darwin 25.6.0, arm64:
bash gates/selftest.sh→SELFTEST PASS(new: origin.md bound by intent and downstream gates, refused as a gate, unbound when written late).bash gates/e2e.sh→E2E PASS, 146 assertions, 0 failures (new: B2b, B10i–B10k, B12b — the full route snapshots, binds, refuses a post-review origin edit, archives origin.md).bash gates/autotest.sh→AUTOTEST PASS, 195 assertions, 0 failures (new: A24a–h fix-loop states throughnext,status.sh,status --jsonand the lazy ship gate; A25a–d thedatakind runs, is receipted, is not runtime evidence, and fails the run when it fails).bash -nover every changed shell script: clean.
v0.10.0: automated SDLC review handoffs
The existing six-stage SDLC can now be driven by an automation host through a verified feature-branch handoff. Human merge and deployment decisions remain separate.
- Machine-readable status and resumable, bounded execution checkpoints.
- Full-auto intent checks that stop on unresolved material questions.
- Source-bound verification recipes, command and log checks, execution timeouts, and owned-process cleanup.
- Feature-branch push and remote SHA confirmation before review-ready handoff.
- Updated English/Korean guides and a Symphony integration example.
Existing manual workflows remain supported. Python 3 is required for the optional bounded verification runner. Hashes detect changes; they do not authenticate reviewers or prove deployment.
Validation: existing gate suite, 141 end-to-end assertions, 183 automation assertions, and local Symphony integration tests. Cross-platform CI results are checked before this release is published.
v0.9.0 — verifiable feature and bug-fix delivery
Feature development and bug fixing
- Ordinary development requests enter SDLC Kit, using one compact route for bounded changes and the full route for complex work.
- Approval records bind the exact artifact, upstream decisions, and reviewed source. Stale approvals, unsupported source paths, and mismatched delivery commits are rejected.
- QA requires scoped real E2E with the command/tool, environment, scenario, and observed result. Unit tests cannot silently substitute for E2E.
- Requirements, final evidence, and delivery summaries remain durable. Repeated approval requests, mandatory delegation at every stage, duplicate test runs, and immediate evidence deletion are reduced.
- Adds a reusable E2E suite and fixes Windows CI fixture setup. PR, main, and version-tag checks remain enabled.
Upgrade notes
- Existing unbound approval records require reapproval.
Track: microremains accepted as the compact route. - Run
init.shand review its changes to kit-owned ignore entries; it does not modify the Git index. - Hashes detect changes; they do not authenticate approvals or prove remote delivery.
- Whole-project source hashing can be slow in large repositories. Git-quoted special path names are rejected; submodule contents are outside the source snapshot.
Validation and release exception
- Local macOS/Bash 3.2: 35 selftests and 133 E2E assertions passed, plus shell syntax and diff checks.
- Independent review: no remaining blockers in the reviewed local implementation.
- PR CI: Ubuntu and macOS passed. Windows selftests passed, but Windows E2E did not pass.
- The repository owner explicitly requested skipping the remaining E2E gate and merging/releasing. Windows' combined required check was temporarily excluded only for PR #1's merge and immediately restored. All three required checks, administrator enforcement, and force-push/deletion protections are active again.
- Windows E2E remains unresolved in this release; this release does not claim that every platform's E2E passed.
Merged through PR #1, commit ef49755886cc2319c2c1acf092d9175a69108a81.
v0.8.0 — evidence and approvals stay on disk
What git keeps is now the decision record
init.sh writes sixteen .gitignore lines instead of four. Per feature, git
keeps intent.md, plan.md, map.md, CLOSED, memory/, and config.md.
Everything else is evidence or working residue and stays in the working copy:
approvals/, spec.md, baseline.txt, deviations.md, evidence.md, and
harvest.md, alongside the existing scratch/ and progress.md. Each is
listed for work/ and archive/ alike, so a close does not resurrect them.
- Gates are unaffected.
check-gate.sh,status.sh, andstats.shread
from disk, so gate state, approval modes, and re-approval counts behave
exactly as before.stats.shmakes no git calls at all — its header claimed
otherwise, and that comment is now correct too. - Durability is what changes. A fresh clone carries no approvals, so
re-cloning mid-feature means approving again. The audit trail is the on-disk
.sdlc/approvals/tree plus the agent rules. AGENTS.md rules 3 and 7, both
READMEs, and the docs site no longer claim git history holds it. - Ship stages less.
skills/5-shipdrops.sdlc/approvals/from the
staged set; staging.sdlc/work/<slug>/now yields only intent, plan, and
map. Anything else showing up there means the project predates this release.
Upgrading an existing project
.gitignore never untracks. Re-run init.sh in the project — it reports
every already-tracked path and prints the removal command:
<kit>/init.sh .
git ls-files -ci --exclude-standard -- .sdlc | tr '\n' '\0' | xargs -0 git rm --cached --Files stay on disk; commit the removal to stop carrying them. status.sh's
kit-drift note points at this, and close.sh stages the approvals deletion
while those paths are still tracked.
gates/selftest.sh gains test 28: the ignore set, the kept decision record,
idempotency, and the already-tracked warning.
v0.7.4 — progress.md heartbeat: one live line per stage
Heartbeat (AGENTS.md rule 9)
.sdlc/work/<slug>/progress.md now holds exactly one overwritten line —
<stage>[ n/m] · <what is happening> · <ISO timestamp> — so a human can see
where the loop is at any moment, in every stage, not just at gates.
- Every stage writes it: on entry, at every sub-task change, before each
dispatch; build counts plan.md's Order of work asbuild n/mand updates
per fix-loop round. - Long sub-tasks refresh it at natural checkpoints (a command finished,
a file edited) even when the text does not change — a stale heartbeat means
a dead loop, not a slow step. status.shshows it as anow →line with its age (BSD/GNU stat);
older than 30 min gets— STALE. Absent file: silent, fully
backward-compatible.- A signal, not a record:
init.shgitignores it underwork/and
archive/; history stays in deviations.md / evidence.md / harvest.md.
Follow it live withwatch -n5 cat .sdlc/work/<slug>/progress.md.
Also retro-tagged v0.7.3 (optional qa: tool in config, intent Refs: line).