33# https://github.com/justi/context-revive
44set -euo pipefail
55
6- VERSION=" 0.1.13 "
6+ VERSION=" 0.1.14 "
77STATIC_FILE=" .revive/static.md"
88COMMANDS_FILE=" .revive/commands.md"
99BRIEF_CHAR_BUDGET=2200
@@ -20,12 +20,14 @@ usage() {
2020Usage: revive <command> [options]
2121
2222Commands:
23- init Generate .revive/static.md (PURPOSE, INVARIANTS, GOTCHAS).
24- Use `init --force` to regenerate PURPOSE from the chain
25- while preserving user-edited INVARIANTS and GOTCHAS.
23+ init Generate .revive/static.md (PURPOSE, DIFFERENTIATORS,
24+ INVARIANTS, GOTCHAS). Use `init --force` to regenerate
25+ PURPOSE from the chain while preserving user-edited
26+ DIFFERENTIATORS, INVARIANTS, and GOTCHAS.
2627 suggest Print an LLM-ready prompt that, when pasted into any
27- coding agent, generates PURPOSE + INVARIANTS + GOTCHAS
28- for this project. Tailored to the actual files in the repo.
28+ coding agent, generates PURPOSE + DIFFERENTIATORS +
29+ INVARIANTS + GOTCHAS for this project. Tailored to the
30+ actual files in the repo.
2931 show Print the brief that would be injected (force-emit)
3032 refresh Hook entry point — cadence-gated, silent on failure
3133 install-hook Wire UserPromptSubmit into .claude/settings.json
341343 }
342344 NR_buf = 0
343345 }
344- /^(PURPOSE|INVARIANTS|GOTCHAS):/ { flush(); buf[++NR_buf] = $0; next }
346+ /^(PURPOSE|DIFFERENTIATORS| INVARIANTS|GOTCHAS):/ { flush(); buf[++NR_buf] = $0; next }
345347 { buf[++NR_buf] = $0 }
346348 END { flush() }
347349 ' " $STATIC_FILE "
@@ -620,40 +622,51 @@ cmd_init() {
620622 exit 1
621623 fi
622624
623- # Preserve user-edited INVARIANTS and GOTCHAS across a --force regenerate;
624- # only PURPOSE is replaced. This is the common flow after upgrading
625- # revive — the chain got smarter, user rules haven't changed.
625+ # Preserve user-edited DIFFERENTIATORS / INVARIANTS / GOTCHAS across a
626+ # --force regenerate; only PURPOSE is replaced. This is the common flow
627+ # after upgrading revive — the chain got smarter, user rules haven't
628+ # changed.
629+ local differentiators=" - (what sets this project apart; edit this file)"
626630 local invariants=" - (top-5 architectural rules; edit this file)"
627631 local gotchas=" - (landmines you keep stepping on; edit this file)"
628632 if (( force )) && [[ -f " $STATIC_FILE " ]]; then
629633 local preserved
630634 preserved=$( awk '
631- /^INVARIANTS:/ { cap="inv"; next }
632- /^GOTCHAS:/ { cap="got"; next }
633- /^[A-Z][A-Z_]*:/ { cap="" }
634- cap == "inv" { inv = inv $0 "\n" }
635- cap == "got" { got = got $0 "\n" }
636- END { printf "INV_START\n%sINV_END\nGOT_START\n%sGOT_END\n", inv, got }
635+ /^DIFFERENTIATORS:/ { cap="diff"; next }
636+ /^INVARIANTS:/ { cap="inv"; next }
637+ /^GOTCHAS:/ { cap="got"; next }
638+ /^[A-Z][A-Z_]*:/ { cap="" }
639+ cap == "diff" { diff = diff $0 "\n" }
640+ cap == "inv" { inv = inv $0 "\n" }
641+ cap == "got" { got = got $0 "\n" }
642+ END {
643+ printf "DIFF_START\n%sDIFF_END\nINV_START\n%sINV_END\nGOT_START\n%sGOT_END\n",
644+ diff, inv, got
645+ }
637646 ' " $STATIC_FILE " )
638- local saved_inv saved_got
647+ local saved_diff saved_inv saved_got
648+ saved_diff=$( awk ' /^DIFF_START$/{p=1;next} /^DIFF_END$/{p=0} p' <<< " $preserved" | sed ' /^$/d' )
639649 saved_inv=$( awk ' /^INV_START$/{p=1;next} /^INV_END$/{p=0} p' <<< " $preserved" | sed ' /^$/d' )
640650 saved_got=$( awk ' /^GOT_START$/{p=1;next} /^GOT_END$/{p=0} p' <<< " $preserved" | sed ' /^$/d' )
641- [[ -n " $saved_inv " ]] && invariants=" $saved_inv "
642- [[ -n " $saved_got " ]] && gotchas=" $saved_got "
651+ [[ -n " $saved_diff " ]] && differentiators=" $saved_diff "
652+ [[ -n " $saved_inv " ]] && invariants=" $saved_inv "
653+ [[ -n " $saved_got " ]] && gotchas=" $saved_got "
643654 fi
644655
645656 mkdir -p " $( dirname " $STATIC_FILE " ) "
646657 local purpose
647658 purpose=$( compute_purpose)
648659 {
649660 printf ' PURPOSE: %s\n' " $purpose "
661+ echo " DIFFERENTIATORS:"
662+ printf ' %s\n' " $differentiators "
650663 echo " INVARIANTS:"
651664 printf ' %s\n' " $invariants "
652665 echo " GOTCHAS:"
653666 printf ' %s\n' " $gotchas "
654667 } > " $STATIC_FILE "
655668 if (( force )) ; then
656- echo " regenerated: $STATIC_FILE (INVARIANTS/GOTCHAS preserved)"
669+ echo " regenerated: $STATIC_FILE (DIFFERENTIATORS/ INVARIANTS/GOTCHAS preserved)"
657670 else
658671 echo " created: $STATIC_FILE "
659672 fi
@@ -699,17 +712,24 @@ PROMPT_META
699712 cat << 'PROMPT_HEADER '
700713Read the following artefacts in the current project, then fill any
701714placeholder sections of .revive/static.md. Treat existing human-
702- curated content as sacred: PURPOSE, INVARIANTS, and GOTCHAS that
703- already contain real content must be preserved verbatim — do not
704- rewrite substantive sections just because you can.
715+ curated content as sacred: PURPOSE, DIFFERENTIATORS, INVARIANTS,
716+ and GOTCHAS that already contain real content must be preserved
717+ verbatim — do not rewrite substantive sections just because you can.
705718
706719Regenerate ONLY sections that are still in their `revive init`
707720placeholder state. Recognise a placeholder by these markers:
708721
709722 - PURPOSE: literal `(describe this project in 1-3 sentences)`,
710- OR a single short fragment clearly extracted from a manifest/
711- README without context (a one-sentence pitch under ~80 chars
712- that doesn't frame the project).
723+ OR a marketing-style 1-liner that states WHAT the project does
724+ but doesn't spell out WHY it exists / what success looks like.
725+ A gh repo description or README tagline typically falls in this
726+ category — it's readable, but doesn't give an agent enough to
727+ understand the business value or operating priorities.
728+ Rule of thumb: if the current PURPOSE could fit on a single
729+ marketing banner and a reader couldn't answer "what does success
730+ for this project look like?" from it alone, treat as placeholder.
731+ - DIFFERENTIATORS: bullets containing `(what sets this project
732+ apart; edit this file)` or `(none defined)`.
713733 - INVARIANTS: bullets containing `(top-5 architectural rules;
714734 edit this file)` or `(none defined)`.
715735 - GOTCHAS: bullets containing `(landmines you keep stepping on;
@@ -752,30 +772,63 @@ Strict rules (violations are failures, not stylistic preferences):
752772 incident, OMIT it. Placeholder > false confidence.
753773
754774Deliverable 1 — PURPOSE (single line, ≤ 400 chars):
755- A tight 2-3 sentence summary that answers:
756- - what the project IS (product one-liner)
757- - its essential philosophy or hard constraint (what makes it different)
758- Include just enough context that someone reading only the brief could
759- pick up work without reading README/CLAUDE.md first.
775+ A tight 2-3 sentence summary that answers ALL THREE of:
776+ 1. WHAT the project is (product one-liner)
777+ 2. WHY it exists — business goal or success criterion the project
778+ is optimizing for (e.g. "cut X hours from weekly ops",
779+ "unlock audience Y", "prevent incident class Z", "reclaim
780+ DORMANT side projects")
781+ 3. Its essential philosophy or hard constraint (what makes it
782+ different, the one thing every design decision must respect)
783+
784+ All three must be present. A gh-style 1-liner that covers only (1)
785+ is NOT enough — it's a marketing tagline, not operating context.
760786
761787 Bad examples:
762- - "A Flutter app." (useless, inferable )
763- - single terse fragment under 60 chars (too terse )
788+ - "A Flutter app." (only 1, useless )
789+ - "Never forget a side project." (only 1, marketing tagline )
764790 - 800-char pasted README intro (too long, buries signal)
765791
766792 Good examples (generic):
767793 - "A CLI that turns Markdown invoices into tax-compliant PDFs.
768- Zero-config for sole traders in EU/UK; one-flag override for
769- custom templates."
770- - "Background-job scheduler for Go services. Unlike cron,
771- schedules are defined in code and survive deploys; job state
772- lives in the same Postgres the app already uses."
794+ Saves sole traders ~3h/month of invoicing; success = zero
795+ manual edits to the generated PDF. Zero-config by default;
796+ customization only via explicit template flag."
797+ - "Background-job scheduler for Go services. Goal: replace ops-
798+ managed cron with code-defined schedules that survive
799+ deploys; success = zero surprise job failures after a deploy.
800+ Constraint: job state lives in the app's own Postgres — no
801+ new infrastructure."
802+
803+ Deliverable 2 — DIFFERENTIATORS (up to 5, or empty):
804+ What makes this project different from the obvious alternatives.
805+ Competitive positioning, deliberate design choices that contradict
806+ what another project in the space would do. This class of signal
807+ prevents an agent from suggesting "why not just use X?" for an X
808+ you've already evaluated and rejected.
809+
810+ Each bullet: "<alternative> → <what we do instead>" or "unlike
811+ <alternative>, we <choice> because <reason>".
773812
774- If the CURRENT PURPOSE line in .revive/static.md is already a good
775- 2-3 sentence summary (clearly readable, frames the project well),
776- keep it verbatim — don't rewrite for its own sake.
813+ Bad examples:
814+ - "We're open source." (not differentiation)
815+ - "Better than the rest." (empty marketing)
816+ - Anything an agent can discover by reading competitor READMEs.
777817
778- Deliverable 2 — INVARIANTS (up to 5):
818+ Good examples:
819+ - "Unlike cron — schedules defined in code, survive deploys; no
820+ ops-managed crontab to drift from the repo."
821+ - "repomix / code2prompt: one-shot dump → we inject small briefs
822+ on cadence, no token flood."
823+ - "Cursor Rules: static prepend → we regenerate DYNAMIC sections
824+ per refresh from git + fs; recent work stays visible."
825+
826+ If you genuinely don't see meaningful differentiation signals in
827+ the artefacts (ADRs comparing approaches, README "vs" sections,
828+ CLAUDE.md rejected-alternatives notes), leave this section with
829+ its placeholder — better empty than filler.
830+
831+ Deliverable 3 — INVARIANTS (up to 5):
779832 Architectural or operational rules whose breakage causes non-obvious
780833 damage. Examples of the *class* of content to produce:
781834 - decisions captured in ADRs that contradict "obvious" approaches
@@ -794,7 +847,7 @@ Deliverable 2 — INVARIANTS (up to 5):
794847 - "Never run data migrations on Monday; the cron vacuum window
795848 overlaps and rollbacks stall."
796849
797- Deliverable 3 — GOTCHAS (3 to 5):
850+ Deliverable 4 — GOTCHAS (3 to 5):
798851 Landmines whose fix is not obvious from code alone. Dependency quirks,
799852 rate limits, migrations needing manual steps, config that looks like a
800853 bug but is deliberate, platform-specific behaviour.
@@ -813,10 +866,13 @@ Deliverable 3 — GOTCHAS (3 to 5):
813866Quality bar: 3 strong rules beat 5 weak ones. Same for gotchas.
814867
815868Required output format (exact indentation, no code fences, no
816- commentary). PURPOSE is a single line; INVARIANTS/GOTCHAS are bullet
817- lists with two-space indent:
869+ commentary). PURPOSE is a single line; DIFFERENTIATORS, INVARIANTS,
870+ and GOTCHAS are bullet lists with two-space indent:
818871
819872PURPOSE: <single 2-3 sentence summary, ≤ 400 chars>
873+ DIFFERENTIATORS:
874+ - <alternative → our choice / rationale>
875+ - <...>
820876INVARIANTS:
821877 - <rule 1>
822878 - <rule 2>
@@ -839,10 +895,10 @@ Do two things, in order — do NOT stop after step 1:
839895
840896 STEP 2 — Open .revive/static.md and edit it in place: replace
841897 ONLY the sections you identified as placeholders. Preserve file
842- ordering (PURPOSE → INVARIANTS → GOTCHAS). Do NOT touch sections
843- you decided to preserve. Use your editor tool (Edit / Write /
844- str_replace — whatever your agent framework exposes); do NOT
845- ask me to paste manually.
898+ ordering (PURPOSE → DIFFERENTIATORS → INVARIANTS → GOTCHAS).
899+ Do NOT touch sections you decided to preserve. Use your editor
900+ tool (Edit / Write / str_replace — whatever your agent framework
901+ exposes); do NOT ask me to paste manually.
846902
847903 After step 2, confirm with a one-line summary of which sections
848904 were rewritten and which were preserved, plus the final line
0 commit comments