forked from bchanot/claude
feat(skills): wire verify+secure into ship-feature/init-project; onboard no-loop + LRN-093 backstop (verify-loops lot 5)
ship-feature: STEP 0e CONTRACT (request verbatim) → ENRICHED at the STEP 3 validation gate (design criteria appended [gated <date>], the human micro-gate) → STEP 5 VERIFY+SECURE judges the branch against the ENRICHED contract via the shared include. Distinct axis from STEP 6 code review, both run (LRN-095). init-project: contract seeded from the PROJECT BRIEF (V1 features → criteria) → ENRICHED at VALIDATION GATE #1 → STEP 9 VERIFY+SECURE. Adds the security gate init-project previously lacked (was deferred to a later /onboard). onboard: explicit NO verify-loop — it produces an audit report, not a change to verify against a request; contract is scope-only, security-auditor runs MODE audit (report-only), never a gate. Documented to prevent a misplaced symmetry loop (BDR-050: dev pipeline != audit). lib/tests/no-vacuous-locks.test.sh: deterministic backstop for LRN-093 (2nd recurrence in this chantier → the advisory alone did not hold). Refuses a literal \n in any grep/tf/tr_/tn pattern across lib/tests/*.test.sh; flip-tested against a synthetic offender so the guard proves it bites. lib/tests/loops-heavy.test.sh: 18 structure locks green. Behavioral dogfood (both vigilance points, real): (1) enrichment — a fresh verifier reads and checks a [gated] design criterion (ECARTS naming it precisely); (2) escalation — 3 consecutive ECARTS on the same criterion → orchestrator STOPs at the max-3 bound + presents the CONTRACT-vs-REALIZED table, no 4th loop, no commit. First real exercise of the infinite-loop guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
65edf8c1ba
commit
1c69de2f40
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================
|
||||||
|
# Structure locks — heavy-flow wiring (verify-loops lot 5)
|
||||||
|
# ship-feature + init-project get contract + enrich-at-gate +
|
||||||
|
# verify-secure-loop; onboard is the explicit NO-LOOP audit case.
|
||||||
|
# ============================================================
|
||||||
|
set -u
|
||||||
|
|
||||||
|
REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
SHF="$REPO/skills/ship-feature/SKILL.md"
|
||||||
|
INI="$REPO/skills/init-project/SKILL.md"
|
||||||
|
ONB="$REPO/skills/onboard/SKILL.md"
|
||||||
|
PASS=0; FAIL=0
|
||||||
|
|
||||||
|
tf() { # tf <label> <file> <fixed-string> (single-line patterns only, LRN-093)
|
||||||
|
if grep -qF -- "$3" "$2" 2>/dev/null; then
|
||||||
|
echo " PASS $1"; PASS=$((PASS+1))
|
||||||
|
else
|
||||||
|
echo " FAIL $1 — missing: $3"; FAIL=$((FAIL+1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "-- ship-feature (enrich-at-gate) --"
|
||||||
|
tf "shf contract step" "$SHF" "STEP 0e — CONTRACT"
|
||||||
|
tf "shf contract-interview" "$SHF" "lib/contract-interview.md"
|
||||||
|
tf "shf enrich at gate" "$SHF" "ENRICH the STEP 0e contract"
|
||||||
|
tf "shf gated marker" "$SHF" "[gated <date>]"
|
||||||
|
tf "shf verify+secure step" "$SHF" "STEP 5 — VERIFY + SECURE"
|
||||||
|
tf "shf uses shared include" "$SHF" "lib/verify-secure-loop.md"
|
||||||
|
tf "shf judges enriched" "$SHF" "ENRICHED contract"
|
||||||
|
tf "shf orthogonal to review" "$SHF" "DISTINCT axis from STEP 6 code review"
|
||||||
|
|
||||||
|
echo "-- init-project (contract from BRIEF + adds security) --"
|
||||||
|
tf "ini contract from brief" "$INI" "contract-interview.md"
|
||||||
|
tf "ini criteria from V1" "$INI" "V1 FEATURES (each testable)"
|
||||||
|
tf "ini enrich at gate1" "$INI" "ENRICH the STEP 1 contract"
|
||||||
|
tf "ini verify+secure step" "$INI" "STEP 9 — VERIFY + SECURE"
|
||||||
|
tf "ini uses shared include" "$INI" "lib/verify-secure-loop.md"
|
||||||
|
tf "ini adds security gate" "$INI" "adds the security gate init-project previously lacked"
|
||||||
|
|
||||||
|
echo "-- onboard (explicit NO-LOOP audit) --"
|
||||||
|
tf "onb no-loop stated" "$ONB" "n'a PAS de boucle verify"
|
||||||
|
tf "onb audit not gate" "$ONB" "MODE: audit"
|
||||||
|
tf "onb scope contract" "$ONB" "contract de SCOPE"
|
||||||
|
tf "onb no symmetry loop" "$ONB" "Ne PAS ajouter la boucle des flux dev"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "loops-heavy structure locks: $PASS pass, $FAIL fail"
|
||||||
|
[ "$FAIL" -eq 0 ]
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================
|
||||||
|
# Deterministic backstop for LRN-093 (vacuous grep locks).
|
||||||
|
# grep NEVER interprets a literal \n as a newline: in a -F
|
||||||
|
# fixed string it splits the pattern into a per-line OR (matches
|
||||||
|
# anything); in -E it is a literal "n". Either way a structure
|
||||||
|
# lock carrying \n proves nothing. The LRN advisory alone did not
|
||||||
|
# hold (2 recurrences same chantier) -> this mechanical guard.
|
||||||
|
#
|
||||||
|
# Rule: no backslash-n inside a quoted pattern on a grep / tf /
|
||||||
|
# tr_ / tn line, anywhere under lib/tests/*.test.sh. Flip-tested
|
||||||
|
# below against a synthetic offender so the guard proves it bites.
|
||||||
|
# ============================================================
|
||||||
|
set -u
|
||||||
|
|
||||||
|
REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
SELF="no-vacuous-locks.test.sh"
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
# One scanner, reused for the real tree and the flip-test fixture.
|
||||||
|
# Matches a grep -*F/-*E call OR a tf/tr_/tn helper (at line start or after
|
||||||
|
# whitespace) whose quoted pattern contains a literal backslash-n.
|
||||||
|
scan() { # scan <dir containing *.test.sh>
|
||||||
|
grep -rnE '(grep +-[A-Za-z]*[EFqe]|(^|[[:space:]])(tf|tr_|tn)[[:space:]]).*"[^"]*\\n' \
|
||||||
|
"$1"/*.test.sh 2>/dev/null | grep -v "$SELF"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "-- LRN-093 backstop: scan lib/tests/*.test.sh --"
|
||||||
|
HITS="$(scan "$REPO/lib/tests")"
|
||||||
|
if [ -n "$HITS" ]; then
|
||||||
|
FAIL=1
|
||||||
|
printf '%s\n' "$HITS" | while IFS= read -r line; do
|
||||||
|
printf ' FAIL vacuous backslash-n lock: %s\n' "$line"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
printf ' PASS no vacuous backslash-n locks in lib/tests/*.test.sh\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Flip-test: the guard MUST catch a known offender (LRN-093 discipline —
|
||||||
|
# prove a lock CAN fail before trusting its green).
|
||||||
|
echo "-- flip-test: guard bites a synthetic offender --"
|
||||||
|
TMP="$(mktemp -d)"
|
||||||
|
# shellcheck disable=SC2016 # the single quotes are deliberate: literal backslash-n
|
||||||
|
printf '%s\n' 'tf "bad" "$F" "no\nforced loop"' > "$TMP/z.test.sh"
|
||||||
|
if [ -n "$(scan "$TMP")" ]; then
|
||||||
|
printf ' PASS guard catches the synthetic offender\n'
|
||||||
|
else
|
||||||
|
printf ' FAIL guard blind to a known offender (regex too weak)\n'
|
||||||
|
FAIL=1
|
||||||
|
fi
|
||||||
|
rm -rf "$TMP"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [ "$FAIL" -eq 0 ]; then echo "no-vacuous-locks: clean"; else echo "no-vacuous-locks: vacuous locks present"; fi
|
||||||
|
[ "$FAIL" -eq 0 ]
|
||||||
@@ -48,6 +48,14 @@ ls CLAUDE.md .claude/CLAUDE.md 2>/dev/null | head -1
|
|||||||
|
|
||||||
In both cases: MANDATORY STOP until user answers remaining questions. Produce PROJECT BRIEF.
|
In both cases: MANDATORY STOP until user answers remaining questions. Produce PROJECT BRIEF.
|
||||||
|
|
||||||
|
**Then run `$HOME/.claude/lib/contract-interview.md`** seeded from the BRIEF:
|
||||||
|
REQUEST verbatim = the user's project description; ACCEPTANCE CRITERIA = the
|
||||||
|
V1 FEATURES (each testable); FILE SCOPE = the planned tree. No new questions
|
||||||
|
(the interview already asked). It writes
|
||||||
|
`.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`; the DESIGN approved at STEP
|
||||||
|
4 ENRICHES it, and STEP 9's verifier judges the MVP against the enriched
|
||||||
|
contract.
|
||||||
|
|
||||||
## STEP 2 — ANALYZE
|
## STEP 2 — ANALYZE
|
||||||
Load `$HOME/.claude/agents/analyzer.md`. Analyze BRIEF: existing code, stack constraints, infra risks, open decisions. Produce ANALYSIS REPORT.
|
Load `$HOME/.claude/agents/analyzer.md`. Analyze BRIEF: existing code, stack constraints, infra risks, open decisions. Produce ANALYSIS REPORT.
|
||||||
|
|
||||||
@@ -70,6 +78,11 @@ Approve? (yes / request changes)
|
|||||||
```
|
```
|
||||||
Changes → back to STEP 3. Approved → continue.
|
Changes → back to STEP 3. Approved → continue.
|
||||||
|
|
||||||
|
**On approval — ENRICH the STEP 1 contract**: append the DESIGN-derived
|
||||||
|
acceptance criteria (resolved decisions, interfaces, test strategy) to the
|
||||||
|
contract, each tagged `[gated <date>]`. STEP 9's verifier judges against this
|
||||||
|
enriched contract.
|
||||||
|
|
||||||
## STEP 5 — SCAFFOLD
|
## STEP 5 — SCAFFOLD
|
||||||
Load `$HOME/.claude/agents/scaffolder.md`. Pass: BRIEF + DESIGN + `~/.claude/templates/project-CLAUDE.md` + `~/.claude/CLAUDE.md`.
|
Load `$HOME/.claude/agents/scaffolder.md`. Pass: BRIEF + DESIGN + `~/.claude/templates/project-CLAUDE.md` + `~/.claude/CLAUDE.md`.
|
||||||
Creates: CLAUDE.md, settings, structure, config, empty entry points, .gitignore, .env.example, .claude/tasks/TODO.md, .claude/memory/{decisions,learnings,blockers,journal,evals}.md, .claude/audits/. NO README, NO features.
|
Creates: CLAUDE.md, settings, structure, config, empty entry points, .gitignore, .env.example, .claude/tasks/TODO.md, .claude/memory/{decisions,learnings,blockers,journal,evals}.md, .claude/audits/. NO README, NO features.
|
||||||
@@ -175,8 +188,21 @@ If `graphify` CLI is installed AND complexity >= 30%:
|
|||||||
2. Print: `🔗 Full project graph updated at graphify-out/`
|
2. Print: `🔗 Full project graph updated at graphify-out/`
|
||||||
If `graphify` not installed or complexity < 30% → skip silently.
|
If `graphify` not installed or complexity < 30% → skip silently.
|
||||||
|
|
||||||
## STEP 9 — ANALYZE
|
## STEP 9 — VERIFY + SECURE (fresh gates, bounded loops)
|
||||||
Load `$HOME/.claude/agents/analyzer.md`. Check: no regressions, no deviations, no stale scaffold, conventions respected.
|
Run the two fresh gates per `$HOME/.claude/lib/verify-secure-loop.md` with
|
||||||
|
`CONTRACT` = the STEP 1 path (ENRICHED at STEP 4), `DIFF` = the MVP branch
|
||||||
|
diff (`develop..HEAD`), `TEST` = the project suite:
|
||||||
|
- GATE 1 — a FRESH verifier judges the MVP against the enriched contract (V1
|
||||||
|
features + `[gated]` design criteria). CONFORME → GATE 2. ECARTS → fix,
|
||||||
|
re-verify, max 3 → STOP + human escalation with the CRITERIA table.
|
||||||
|
- GATE 2 — a FRESH security-auditor (`MODE: gate`, `SCOPE: develop..HEAD`).
|
||||||
|
PASS → STEP 10. BLOCK → fix, re-verify request THEN re-scan, max 3 →
|
||||||
|
escalate.
|
||||||
|
|
||||||
|
This adds the security gate init-project previously lacked (security was only
|
||||||
|
deferred to a later /onboard) and turns the informal analyze into a verdict
|
||||||
|
against the founding contract. Distinct axis from STEP 10 code review
|
||||||
|
([[LRN-095]]) — both run.
|
||||||
|
|
||||||
## STEP 10 — CODE REVIEW
|
## STEP 10 — CODE REVIEW
|
||||||
Invoke `superpowers:requesting-code-review`. Fix all CRITICAL before proceeding.
|
Invoke `superpowers:requesting-code-review`. Fix all CRITICAL before proceeding.
|
||||||
|
|||||||
@@ -509,6 +509,15 @@ Agent(
|
|||||||
Si semgrep absent → l'agent rend DEGRADED (checklist seule) + recommande
|
Si semgrep absent → l'agent rend DEGRADED (checklist seule) + recommande
|
||||||
`make plugin` ; NON bloquant en onboard (audit, pas gate).
|
`make plugin` ; NON bloquant en onboard (audit, pas gate).
|
||||||
|
|
||||||
|
**Onboard n'a PAS de boucle verify→dev (`lib/verify-secure-loop.md`) — par
|
||||||
|
conception.** onboard produit un RAPPORT d'audit, pas une modification à
|
||||||
|
vérifier contre une demande : il n'y a ni contract de conformité, ni diff dev,
|
||||||
|
ni verifier, ni max-3. Le contract d'onboard est un contract de SCOPE (ce que
|
||||||
|
l'interview STEP 3 + `audit_stack` définissent comme périmètre d'audit), et
|
||||||
|
`security-auditor` tourne en `MODE: audit` (report-only), jamais en `MODE:
|
||||||
|
gate`. Ne PAS ajouter la boucle des flux dev ici par symétrie — l'audit et le
|
||||||
|
flux de dev sont deux formes distinctes ([[BDR-050]] pipeline dev ≠ audit).
|
||||||
|
|
||||||
#### Dispatch doc-syncer (si `doc` dans audit_stack)
|
#### Dispatch doc-syncer (si `doc` dans audit_stack)
|
||||||
```
|
```
|
||||||
Agent(
|
Agent(
|
||||||
|
|||||||
@@ -78,7 +78,16 @@ The returned digest (ANALYSIS + RELATED MEMORY) stays in the orchestrator's cont
|
|||||||
is FED to STEP 1 and STEP 2 and reconciled at STEP 3. Degradation: request too vague →
|
is FED to STEP 1 and STEP 2 and reconciled at STEP 3. Degradation: request too vague →
|
||||||
analyzer flags ambiguous zones, does not block (STEP 1 refines). `.claude/memory/` empty or
|
analyzer flags ambiguous zones, does not block (STEP 1 refines). `.claude/memory/` empty or
|
||||||
absent → analyzer omits RELATED MEMORY (no-op); the step still returns the code ANALYSIS.
|
absent → analyzer omits RELATED MEMORY (no-op); the step still returns the code ANALYSIS.
|
||||||
Additive — distinct from STEP 5 ANALYZE (post-impl regression) and STEP 4b DEBUG.
|
Additive — distinct from STEP 5 VERIFY + SECURE (post-impl) and STEP 4b DEBUG.
|
||||||
|
|
||||||
|
## STEP 0e — CONTRACT
|
||||||
|
|
||||||
|
Run `$HOME/.claude/lib/contract-interview.md`. REQUEST verbatim = the feature
|
||||||
|
request as typed; initial ACCEPTANCE CRITERIA from the request; FILE SCOPE
|
||||||
|
seeded from 0d's KEY COMPONENTS. It writes
|
||||||
|
`.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`; keep the path — the design
|
||||||
|
approved at STEP 3 ENRICHES it, and STEP 5's verifier judges the diff against
|
||||||
|
the ENRICHED contract. This is the only flow where the contract grows mid-run.
|
||||||
|
|
||||||
## STEP 1 — BRAINSTORM
|
## STEP 1 — BRAINSTORM
|
||||||
Invoke `superpowers:brainstorming` — but FEED it the STEP 0d digest as binding context,
|
Invoke `superpowers:brainstorming` — but FEED it the STEP 0d digest as binding context,
|
||||||
@@ -120,6 +129,13 @@ never a guarantee (same discipline as the memory-commit `✅<hash>`: show what's
|
|||||||
assert a check not performed). No RELATED MEMORY from 0d → omit the block.
|
assert a check not performed). No RELATED MEMORY from 0d → omit the block.
|
||||||
Changes → back to STEP 2. Approved → continue.
|
Changes → back to STEP 2. Approved → continue.
|
||||||
|
|
||||||
|
**On approval — ENRICH the STEP 0e contract.** The design just validated adds
|
||||||
|
detail the raw request lacked: append the design-derived acceptance criteria
|
||||||
|
to the contract's ACCEPTANCE CRITERIA, each tagged `[gated <date>]` (this is
|
||||||
|
the human micro-gate that authorizes contract growth). STEP 5's verifier
|
||||||
|
judges the diff against this ENRICHED contract, not the STEP 0e seed — so a
|
||||||
|
criterion the design introduced is verified, not lost.
|
||||||
|
|
||||||
## STEP 4 — IMPLEMENT
|
## STEP 4 — IMPLEMENT
|
||||||
Start the feature branch off develop, then implement on it:
|
Start the feature branch off develop, then implement on it:
|
||||||
```bash
|
```bash
|
||||||
@@ -157,8 +173,22 @@ OPTIONS :
|
|||||||
Skip them too? (yes / keep and accept partial implementation)"
|
Skip them too? (yes / keep and accept partial implementation)"
|
||||||
If no dependents → skip cleanly and continue.
|
If no dependents → skip cleanly and continue.
|
||||||
|
|
||||||
## STEP 5 — ANALYZE
|
## STEP 5 — VERIFY + SECURE (fresh gates, bounded loops)
|
||||||
Load `$HOME/.claude/agents/analyzer.md`. Check: no regressions, no stale code, no plan deviations.
|
Run the two fresh gates per `$HOME/.claude/lib/verify-secure-loop.md` with
|
||||||
|
`CONTRACT` = the STEP 0e path (ENRICHED at STEP 3), `DIFF` = the branch diff
|
||||||
|
(`develop..HEAD`), `TEST` = the project suite:
|
||||||
|
- GATE 1 — a FRESH verifier judges the branch against the ENRICHED contract
|
||||||
|
(all criteria, including the `[gated]` design ones). CONFORME → GATE 2.
|
||||||
|
ECARTS → hand the dev the gap list, fix, re-verify, max 3 → STOP + human
|
||||||
|
escalation with the CRITERIA table.
|
||||||
|
- GATE 2 — a FRESH security-auditor (`MODE: gate`, `SCOPE: develop..HEAD`)
|
||||||
|
scans the branch. PASS → STEP 6. BLOCK → fix, re-verify the request THEN
|
||||||
|
re-scan, max 3 → escalate.
|
||||||
|
|
||||||
|
This replaces the old informal "analyze for regressions" with a verdict
|
||||||
|
against the contract. It is a DISTINCT axis from STEP 6 code review (contract
|
||||||
|
conformity + security vs. craft/design) — both run, neither subsumes the
|
||||||
|
other ([[LRN-095]]).
|
||||||
|
|
||||||
## STEP 6 — CODE REVIEW
|
## STEP 6 — CODE REVIEW
|
||||||
Invoke `superpowers:requesting-code-review`. Fix all CRITICAL before proceeding.
|
Invoke `superpowers:requesting-code-review`. Fix all CRITICAL before proceeding.
|
||||||
|
|||||||
Reference in New Issue
Block a user