diff --git a/README.ko.md b/README.ko.md index 0be50f9..f6b33f0 100644 --- a/README.ko.md +++ b/README.ko.md @@ -45,7 +45,7 @@ flowchart LR | 단계 | 하는 일 | 되돌아가는 때 | |---|---|---| -| **1 Intent** | 요청한 것, 에이전트가 이해한 것, 그 이해에 숨은 위험을 적고, 여섯 영역으로 명확도를 매깁니다. 90%에 못 미치면 에이전트가 코드를 살피고, 버그를 재현하고, 스펙을 조사하거나, 티켓·이슈·PR에서 요청자에게 묻습니다. 사람이 확인합니다. | — | +| **1 Intent** | 요청한 것, 에이전트가 이해한 것, 그 이해에 숨은 위험을 적고, 여섯 영역으로 명확도를 매깁니다. 90%에 못 미치면 에이전트가 코드를 살피고, 버그를 재현하고, 스펙을 조사하거나, 티켓·이슈·PR에서 요청자에게 묻습니다. 맨 위 세 줄 요약으로 시작하고 에이전트용 근거는 맨 아래에 둡니다. 사람은 "이게 원하는 것인가?"에 답해 승인합니다. | — | | **2 Plan** | 여러 scout가 나란히 정본·데이터·로직·사용자 흐름을 조사해 `refs/`에 남깁니다. `PLAN.md` 한 장에 AS-IS → TO-BE, 부작용, QA가 돌릴 시나리오를 담습니다. | 누락 점검에서 빈틈이 나올 때 | | **3 Build** | 가장 작은 완전한 변경을 만들어 커밋하고, 별도 리뷰어가 공격적으로 검토합니다. | 리뷰 FAIL: 고치고 다시 리뷰 | | **4 Evidence** | 실제 검사 뒤, 모든 시나리오를 각 페르소나로 실제 인터페이스에서 돌립니다. 점수와 다음 경로는 에이전트가 아니라 `sdlc.sh score`가 정합니다. | 계획이 틀렸거나 같은 실패 반복 → Plan, 코드 실패 → Build | diff --git a/README.md b/README.md index d0a2f7d..1525b0f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ flowchart LR | Phase | What happens | Goes back when | |---|---|---| -| **1 Intent** | What you asked, what the agent understood, and the risks in it, scored for clarity across six areas. Below 90% the agent explores, reproduces the bug, researches the spec or asks the requester on the ticket, issue or pull request. You confirm it. | — | +| **1 Intent** | What you asked, what the agent understood, and the risks in it, scored for clarity across six areas. Below 90% the agent explores, reproduces the bug, researches the spec or asks the requester on the ticket, issue or pull request. It opens with a three-line bottom line and keeps the agent's evidence at the bottom; you approve it by answering "is this what you want?". | — | | **2 Plan** | Parallel scouts map source of truth, data, logic and user flow into `refs/`; one `PLAN.md` holds AS-IS → TO-BE, side effects and the scenarios QA will run. | the gap check finds holes | | **3 Build** | The smallest complete change, committed, then a separate adversarial reviewer. | the review FAILs: fix it, review again | | **4 Evidence** | Real checks, then every scenario as its persona through the real interface. `sdlc.sh score` scores and routes it; the agent does not judge. | the plan was wrong or the same failure came twice → Plan; code failed → Build | diff --git a/VERSION b/VERSION index c8e38b6..10c2c0c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.9.0 +2.10.0 diff --git a/log/changelog-v2.10.0.md b/log/changelog-v2.10.0.md new file mode 100644 index 0000000..0e7f989 --- /dev/null +++ b/log/changelog-v2.10.0.md @@ -0,0 +1,42 @@ +# v2.10.0 — Intent reads top-down for a human + +intent.md now opens with a three-line Bottom line and keeps the agent's +working record at the bottom. An answer to a blocking question no longer +counts as approval of the whole intent. + +## Why + +- **The human could not find the decision.** Long Clarity cells (one AS-IS + cell held ten facts with file:line references) buried what needed the + human. Risks mixed approval-level warnings with evidence limits. +- **A question's answer was recorded as approval.** A requester answered one + blocking question; the agent passed those words to + `approve intent --human` and went on to plan without asking "is this what + you want?". + +## Changes + +- **`## Bottom line`** at the top: Changes (now the outcome; Understood as + drops its Outcome line), Left out and Needs you, which cite the Out of + scope item or risk instead of restating it. +- **One fact, one place inside intent.md:** other sections cite O2, F3, Q1 + or Request instead of restating. +- **Order:** Bottom line, Request, Understood as, Risks, Delivery, Blocking + questions, then `---` and the agent's record: Terms, Clarity, Facts. +- **`## Facts`:** Clarity cells are one sentence citing `F1`-style IDs; the + proof sits one fact per line in Facts. +- **Writing rule** in phase 1: one idea per line, bold lead-in, exact + numbers, warning on the line it guards, the requester's language with + headings, field labels and Clarity words in English. +- **Gate:** show Bottom line, Risks and any assumed Clarity area, ask once, + end the turn. `approve intent --human` refuses a note of 12 bytes or more + found inside a resolved blocking-question line, compared as the parser + reads it (comments dropped, `*` and backticks stripped, case folded). + Limit: a generic phrase that is also part of an answer ("yes, go ahead") + is refused too; record the human's fuller words. + +## Compatibility + +Sections are still read by heading, in any order; the parser only adds +resolved answers for the Gate check. Older +intent.md files still pass; Bottom line and Facts are optional to the script. diff --git a/phases/1-intent.md b/phases/1-intent.md index 4c4fd09..d9a0f80 100644 --- a/phases/1-intent.md +++ b/phases/1-intent.md @@ -8,19 +8,22 @@ plans how to build it. (`abc-123-fix-login`, `261012-fix-login`). 2. Read the request and its source (ticket, issue, pull request, document) with its comments and links, and pages in the `memory` folder with the same words. -3. Fill `intent.md` (`templates/intent.md`): - - **Request** — the human's words, verbatim or a faithful excerpt. - - **Understood as** — the outcome, what is in and out of scope, and +3. Fill `intent.md` (`templates/intent.md`) for a skimming reader: one idea + per line, a bold lead-in so the bold alone carries the gist, exact + numbers, each warning on the line it guards; proof goes to Facts. Each + fact appears once; elsewhere cite it (O2, F3, Q1, Request). Human + sections come first, the agent's record (Terms, Clarity, Facts) last. + Write in the requester's language; headings, field labels and Clarity words stay English. + - **Understood as** — scope (Bottom line Changes is the outcome) and success criteria `O1..On`, each observable so a scenario can check it. - **Terms** — match each term the request turns on to `glossary.md` in - the `memory` folder, else the code. A ❓ term, read two ways, - leaves its Clarity area open and is a blocking question; the question - shows each reading with a real value. - - **Clarity** — one row per area: Outcome, Scope, AS-IS, TO-BE, Success, - Constraints. State `clear` only when How settled cites evidence that + the `memory` folder, else the code. A ❓ term leaves its Clarity area + open and is a blocking question showing each reading with a real value. + - **Clarity** — state `clear` only when How settled cites evidence that leaves one reading: the requester's words, a file:line, a log, a primary source. Otherwise `assumed` (and why) or `open` (and what is missing). + - **Facts** — that evidence, one fact per line (`F1:`) with its source. - **Risks in this intent** — ambiguity, a claim the code or data already contradicts (with the evidence), a policy the request would change and who owns it, work that needs authority (rule 3). Before logs, error @@ -32,11 +35,8 @@ plans how to build it. a time; record each answer in place as `— resolved: ` and cite it in its Clarity area. Other uncertainty is written as `[assumed: why]`. - - **Delivery** — `local`, `pr` or `deploy` alone on the Target line - (`pr` also covers pushing a branch for review with no pull request), - details such as branch or environment on a `Note:` line, and - `Authorized: yes — ""` when they authorized it, else - `no`. + - **Delivery** — `pr` also covers a branch pushed for review; record + `Authorized: yes` only with the human's words. 4. Several independent outcomes → one slug each; list them under Understood as. 5. A bug report also records what was done, what happened against what was @@ -50,7 +50,7 @@ cheapest way that works, in this order: 1. **Explore** — the code path, screen or command the request names, and earlier tickets or commits on it. Look only as far as what is wanted; how - to change it safely (every caller, data states, side effects) is phase 2. + to change it safely is phase 2. A question needing more than a few reads goes to a scout (`roles/scout.md`); phase 2 reuses its `refs/` file. 2. **Reproduce** — drive a reported bug where that is safe: locally or in a @@ -77,10 +77,11 @@ cheapest way that works, in this order: Once clarity is 90% and every blocking question is resolved, dispatch the reviewer in intent mode (`roles/reviewer.md`); skip it only when every area cites the requester's own words. An area it shows unsettled goes back to -Raise clarity. Then — human gate (the default): show the human Understood -as, Clarity and Risks, and ask one question — is this what you want? On -explicit approval run -`sdlc.sh approve intent --human ""`. -Otherwise check intent.md against the request yourself, run +Raise clarity. Then — human gate (the default): show the human Bottom line, +Risks and any assumed Clarity area, ask one question — is this what you +want? — and end the turn. Only a reply to that question approves: +`sdlc.sh approve intent --human ""`. An answer to a +blocking question is not approval; ask again after it. +Without an intent gate, check intent.md against the request yourself, run `sdlc.sh approve intent --agent ""`, and post it as FYI. Then phase 2. diff --git a/sdlc.sh b/sdlc.sh index 6e1de67..25406c9 100755 --- a/sdlc.sh +++ b/sdlc.sh @@ -98,7 +98,7 @@ parse() { awk -v mode="$1" -v objectives="$objectives" -v scenarios="$scenarios" -v scored="$scored" -v withdrawn="$withdrawn" "$UNCOMMENT"' function trim(s){sub(/^[ \t]+/,"",s);sub(/[ \t]+$/,"",s);return s}function problem(s){print "X\034" s} function area(id, k,s){k=tolower(id);gsub(/[^a-z]/,"",k);s=tolower(a[3]);if(k=="area")return;ca++;if(k!~/^(outcome|scope|asis|tobe|success|constraints)$/){print "Y\034Clarity: unknown area " id;cy++;return}if(k in cs){print "Y\034Clarity: duplicate area " id;cy++;return}cs[k]="open";if(s!~/^(clear|assumed|open)$/){print "Y\034Clarity " id ": State must be clear, assumed or open";cy++}else if(tolower(a[4])~/^(<.*>|[-—–?.]*|n\/a|tbd)$/){print "Y\034Clarity " id ": How settled needs its evidence or reason";cy++}else cs[k]=s} - function question(){if(q!=""){l=tolower(trim(q));if(l!="none"&&l!="n/a"&&l!~/^resolved:/&&l!~/— resolved:/&&l!~/-- resolved:/)print "Q\034" q;q=""}} + function question(){if(q!=""){l=tolower(trim(q));if(l!="none"&&l!="n/a"&&l!~/^resolved:/&&l!~/— resolved:/&&l!~/-- resolved:/)print "Q\034" q;else if(l~/resolved:/)print "V\034" l;q=""}} function fields(s, c,j){for(j in a)delete a[j];nfields=1;for(j=1;j<=length(s);j++){c=substr(s,j,1);if(c=="\\"&&substr(s,j+1,1)=="|"){a[nfields]=a[nfields] "|";j++}else if(c=="|")nfields++;else a[nfields]=a[nfields] c}for(j=1;j<=nfields;j++)a[j]=trim(a[j]);cells=nfields-1-(a[nfields]=="");return a[2]} { if(uncomment())next @@ -137,9 +137,9 @@ parse() { }' "$2" } read_intent() { - objectives=; iproblems=; questions=; itarget=; iauth=; cproblems= + objectives=; iproblems=; questions=; itarget=; iauth=; cproblems=; answers= while IFS=$'\034' read -r type a b; do - case "$type" in O) objectives="$objectives $a";; X) iproblems="${iproblems}${a}"$'\n';; Q) questions="${questions}${a}"$'\n';; Y) cproblems="${cproblems}${a}"$'\n';; T) itarget=$a;; A) iauth=$a;; esac + case "$type" in O) objectives="$objectives $a";; X) iproblems="${iproblems}${a}"$'\n';; Q) questions="${questions}${a}"$'\n';; Y) cproblems="${cproblems}${a}"$'\n';; V) answers="${answers}${a}"$'\n';; T) itarget=$a;; A) iauth=$a;; esac done < <(parse intent "$F/intent.md") } read_plan() { @@ -481,7 +481,7 @@ case "$command" in if [ "$which" = plan ] && [ "$who" = agent ] && stopped; then die "stopped for the human: $stopreason; resume with approve plan --human"; fi read_intent; problems "$iproblems" || exit 1; problems "$questions" || exit 1 if [ "$which" = intent ]; then - if [ "$who" = agent ] && human_gate intent; then die 'intent needs --human approval'; fi + if [ "$who" = agent ] && human_gate intent; then die 'intent needs --human approval'; fi; if [ "$who" = human ] && lnote=$(printf '%s' "${note//[\`*]/}" | tr '[:upper:]' '[:lower:]') && [ "$(printf '%s' "$lnote" | LC_ALL=C wc -c)" -ge 12 ] && [[ $answers == *"$lnote"* ]]; then die 'that note answers a blocking question; it does not approve this intent: show the human Bottom line, Risks and any assumed Clarity area, ask "is this what you want?" and record their reply'; fi hash=$(sha < "$F/intent.md"); if [ "$ish" = "$hash" ]; then printf 'intent already approved\n'; exit; fi; problems "$cproblems" || exit 1 event; intent="$hash $who $seq $ts $note" else diff --git a/templates/intent.md b/templates/intent.md index de0ce96..be9817f 100644 --- a/templates/intent.md +++ b/templates/intent.md @@ -4,39 +4,49 @@ - Requested by: - Source: +## Bottom line +- **Changes:** +- **Left out:** +- **Needs you:** + ## Request - +- ## Understood as -- Outcome: - In scope: <…> -- Out of scope: <…> -- O1: +- Out of scope: +- O1: <**short name** — observable success criterion> + +## Risks in this intent + +- + +## Delivery +- Target: +- Note: +- Authorized: + +## Blocking questions +- none + + +--- ## Terms - -- ✅ = , e.g. + +- ✅ **** = , e.g. - Source: · Code: ## Clarity - + | Area | State | How settled | |---|---|---| -| Outcome | | | +| Outcome | | | | Scope | | | | AS-IS | | | | TO-BE | | | | Success | | | | Constraints | | | -## Risks in this intent -- - -## Blocking questions -- none - - -## Delivery -- Target: -- Note: -- Authorized: +## Facts +- F1: **** — diff --git a/tests/selftest.sh b/tests/selftest.sh index 08c60f2..9ca222a 100755 --- a/tests/selftest.sh +++ b/tests/selftest.sh @@ -1207,6 +1207,17 @@ printf 'human_gates: # deliberately empty\n' > .sdlc/config.md feature ungated expect 0 approve intent ungated --agent reviewed ok 'present empty human_gates allows agent approval' +printf 'human_gates: intent\n' > .sdlc/config.md +feature answered +printf '## Blocking questions\n- Q1. scope? — resolved: "TC-25 later, the rest go ahead" (requester, chat)\n' >> "$F/intent.md" +expect 1 approve intent answered --human 'TC-25 later, the rest go ahead'; contains 'answers a blocking question' +expect 0 approve intent answered --human 'yes, this is what I want' +feature answered2 +printf '## Blocking questions\n- Q1. scope? -- Resolved: "**나머진** 진행" (requester)\n\n' >> "$F/intent.md" +expect 1 approve intent answered2 --human '나머진 진행'; contains 'answers a blocking question' +expect 0 approve intent answered2 --human 'the line ends with' +ok 'an answer to a blocking question is not --human approval of the intent' +printf 'human_gates: # deliberately empty\n' > .sdlc/config.md mkdir -p nested/deep cd nested/deep expect 0 status ungated; contains 'ungated'