feat(linkedin-studio): N8 — stemme-kjeden (chronicle-voice-profile + drift-logg + language-resolve + auto-gull) [skip-docs]
Bind the long-form VOICE before prose — the discipline the line already applies to SOURCES (validate-binding), its strongest link. - C-1/A2-F1: voice-trainer distils a chronicle-voice-profile from the published gold corpus (per language/format); newsletter Step 0/3a/3b bind drafting to it, with an explicit never-silent fallback to the short-form corpus (which the engine itself calls degrading for chronicle voice). - C-2: Step 3a Pass 0 READS the chronicle-voice-drift-log as a negative list (it was append-only, never consumed); Step 3b re-reads it in expansion. - C-3: Step 1 resolves + persists the top-level edition-state.language, making the template doc-string's "Resolved at Step 1" claim true; the Norwegian review axis (language-reviewer / voice-scrubber) now fires when the edition IS Norwegian instead of being silently off by default. Step 6.5 already consumed the field — resolving it closes the axis end-to-end. - A2-F2: Step 8 lock appends the locked edition to ingest/published (provenance=published), so the gold corpus the profile distils from grows one edition at a time — never the engine's drafts. Domain-general throughout (languages/formats from the user's own corpus). test-runner 140 -> 146 (Section 16o: 6 unconditional voice-chain greps + non-vacuity self-test; anti-erosion floor 123 -> 129). All six suites green (trends 300, brain 134, hooks 140, tests 35, render 60). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014bE7VbkmR3cqHFEeGfzgwb
This commit is contained in:
parent
9de38c4939
commit
b30b0e208f
4 changed files with 197 additions and 13 deletions
|
|
@ -16,6 +16,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- **`arcs` CLI verb + `/linkedin:trends --demand` mode** — render the §4 arc map (`node --import tsx src/cli.ts arcs`), reachable via the command's new demand mode (delegates to `demand-spotter`, keeps commands at 30). The morning brief also shows the per-candidate demand signal alongside the existing reader fields.
|
||||
- Trends suite 276 → 300; test-runner 139 → 140; `tsc --noEmit` clean.
|
||||
|
||||
### Added — voice chain: bind voice BEFORE prose (N8 / C-1, C-2, C-3, A2-F1, A2-F2)
|
||||
|
||||
- **`chronicle-voice-profile` artifact (C-1/A2-F1)** — `voice-trainer` now distils a dedicated long-form voice profile (`${DATA}/profile/chronicle-voice-profile.md`) from the **published gold corpus only** (`ingest/published/`, `provenance=published`), one section per `(language, format)` pair present in the corpus — never from the English short-form `authentic-voice-samples.md` the engine itself calls degrading for chronicle voice, never from `provenance=ai-draft`. `/linkedin:newsletter` binds long-form drafting (Step 0 / 3a / 3b) to THAT profile as the primary reference, with an **explicit, never-silent fallback** to the short-form corpus when it is absent.
|
||||
- **Drift log is READ, not just written (C-2)** — Step 3a gains a **Pass 0**: read `chronicle-voice-drift-log.md` as a *negative list* of the recurring AI-tells/drifts `voice-scrubber` logged on prior editions, so drafting avoids them BEFORE the scrub (the log was append-only, never consumed); Step 3b re-reads it during expansion.
|
||||
- **Review language resolved + persisted at Step 1 (C-3)** — the calibration now **resolves the review `language`** (detect/ask) and persists the top-level `edition-state.language` at the Step 1.5 checkpoint, making the template's "Resolved at Step 1" doc-string true. Previously the default `"en"` was never set, so `language-reviewer`'s Norwegian checks and `voice-scrubber`'s gold standard silently never fired even for a Norwegian edition; Step 6.5 already consumed the field, so resolving it closes the axis end-to-end.
|
||||
- **Auto-gull — the gold corpus grows on its own (A2-F2)** — Step 8 lock now appends the locked edition to `ingest/published/` tagged `provenance=published`, so the corpus the chronicle profile is distilled from grows one edition at a time (the author's own locked editions, never the engine's drafts). No manual curation.
|
||||
- Domain-general throughout: languages/formats come from the user's own corpus, never hardcoded. test-runner 140 → 146 (Section 16o: 6 unconditional voice-chain greps + self-test; anti-erosion floor 123 → 129).
|
||||
|
||||
### Built feedback (awaiting consumer-side proof)
|
||||
|
||||
- **MR-F9** (demand-sweep «innenfra og ut») — built; the (a)/(b)/(c) evidence gate is a runtime demonstration, proven consumer-side (plugin agents don't resolve in the dev repo).
|
||||
|
|
|
|||
|
|
@ -152,6 +152,39 @@ Architecture: [prose/sectioned/framework]
|
|||
|
||||
Flag if: fewer than 5 samples (low confidence), single time period (temporal bias), or contradictory patterns (possible ghostwriting).
|
||||
|
||||
### Long-form chronicle voice profile (per language / format)
|
||||
|
||||
Short-form feed posts and long-form editions (newsletters, essays, chronicles) are
|
||||
**different registers of the same author** — a device the author bans in a punchy
|
||||
short post (e.g. the em-dash) can be load-bearing rhythm in a long-form chronicle.
|
||||
Drafting long-form against the short-form corpus therefore *degrades* the chronicle
|
||||
voice (this is `voice-scrubber`'s canonical rule). So the long-form pipeline binds
|
||||
to its own distilled artifact, which this agent generates:
|
||||
|
||||
- **Artifact:** `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/chronicle-voice-profile.md`
|
||||
— a distilled long-form voice profile built with the same six-dimension
|
||||
framework as the general profile, but from **only the long-form gold corpus**.
|
||||
- **Corpus (published gold only):** the approved/published long-form editions under
|
||||
`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/ingest/published/`
|
||||
carrying `provenance=published`. The same model-collapse fence applies —
|
||||
**never distil chronicle voice from `provenance=ai-draft`**, and never from the
|
||||
short-form `voice-samples/authentic-voice-samples.md` corpus (wrong register).
|
||||
- **Per language AND format — domain-general.** Emit one profile section per
|
||||
`(language, format)` pair actually present in the corpus (e.g. `no` / newsletter),
|
||||
keyed by those axes; the author's languages and formats come from their OWN
|
||||
corpus, never hardcoded. If a `(language, format)` pair has no published editions
|
||||
yet, say so plainly and emit no section for it — do not invent a chronicle voice
|
||||
from short-form samples or from assumptions.
|
||||
- **Grows automatically.** Each locked edition is appended to the gold corpus at
|
||||
`/linkedin:newsletter` Step 8 (`provenance=published`), so re-running this
|
||||
distillation sharpens the chronicle profile over editions without manual curation.
|
||||
|
||||
Trigger this distillation on the same events as the general profile (a new
|
||||
published edition, quarterly audit, explicit request). The long-form pipeline
|
||||
(`/linkedin:newsletter` Step 0 / 3a / 3b) reads this artifact; when it is missing,
|
||||
that pipeline warns and falls back — it never silently drafts against the wrong
|
||||
corpus.
|
||||
|
||||
## Voice Drift Detection
|
||||
|
||||
### Drift Scoring
|
||||
|
|
|
|||
|
|
@ -176,9 +176,24 @@ the edition left off before doing anything.
|
|||
Step 2. Do not confuse `<serie>/STATE.md` (this edition's production state)
|
||||
with the plugin's own `STATE.md` / `docs/BUILD-HANDOVER.local.md` (which govern
|
||||
building the plugin itself).
|
||||
4. **Read the voice profile** — `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md`
|
||||
and anything else under `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`. Long-form must match the
|
||||
author's voice; this is the reference for every drafting and review phase.
|
||||
4. **Read the long-form voice profile** — the **chronicle-voice-profile**
|
||||
(`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/chronicle-voice-profile.md`),
|
||||
the distilled chronicle voice for THIS edition's language/format, built by
|
||||
`voice-trainer` from the published gold corpus. This is the **primary reference
|
||||
for every long-form drafting and review phase**. Short-form feed posts and
|
||||
long-form editions are different registers of the same author, so long-form must
|
||||
**not** be drafted against the English short-form corpus
|
||||
(`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/authentic-voice-samples.md`) —
|
||||
that corpus encodes rules (e.g. no em-dash) that are wrong for chronicle voice
|
||||
and would degrade it. Read the `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`
|
||||
corpus as a **secondary tributary** (register cues), never as the primary
|
||||
long-form reference.
|
||||
**Fallback (never silent).** If `chronicle-voice-profile.md` is absent — or has
|
||||
no section for this edition's language/format — say so plainly, note the
|
||||
chronicle voice is uncalibrated, and fall back to today's behavior (draft against
|
||||
`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`), while
|
||||
flagging the gap so the operator can seed the gold corpus (Step 8 auto-gull grows
|
||||
it from each locked edition).
|
||||
5. **Resolve the active personas (per-artifact).** Personas are configured **per
|
||||
edition**, not from one fixed global file. Resolve the set for
|
||||
`articles.<currentArticle>` in this order (Step 1 finalizes + persists it):
|
||||
|
|
@ -258,12 +273,12 @@ Then display a short status:
|
|||
Edition: <series title> — article <currentArticle> "<title>"
|
||||
Last completed phase: <currentPhase> (or: NEW edition)
|
||||
Resuming at: Step <N> — <step name> (from the resumption table above)
|
||||
Voice profile: loaded | MISSING
|
||||
Chronicle voice profile: loaded (<language>/<format>) | MISSING → fallback to short-form corpus
|
||||
Persona library: <N> personas loaded (active set chosen in Step 1)
|
||||
```
|
||||
|
||||
If the voice profile or persona library is missing, say so plainly and continue —
|
||||
do not fabricate either.
|
||||
If the chronicle voice profile or persona library is missing, say so plainly and
|
||||
continue on the Step-0 fallback — do not fabricate either.
|
||||
|
||||
## Step 1: Brief + calibration
|
||||
|
||||
|
|
@ -321,6 +336,16 @@ Settle these dimensions (most should come from context, not questions):
|
|||
- **Tone** — respected-peer vs. teaching-down; calibrated to the primær.
|
||||
- **Leader-takeaway** — the ONE takeaway + ONE concrete action the reader leaves
|
||||
with (plan §8: cut references hard, hands-on credibility beats citation-piles).
|
||||
- **Language** — **Resolve the review language** for this edition: detect it from
|
||||
the series brief / prior editions / the drafting language, else ask once. Persist
|
||||
it to `edition-state.json` → `language` (e.g. `"no"` for Norwegian, `"en"` for
|
||||
English) at the **Step 1.5 checkpoint**. This is not cosmetic — it gates the
|
||||
language-dependent review agents: `language-reviewer`'s Norwegian-idiom /
|
||||
«kanselli-stil» checks and `voice-scrubber`'s gold standard fire against THIS
|
||||
language, and never grade prose against Norwegian idiom when the edition is not
|
||||
Norwegian. Left unresolved, the template default `"en"` silently disables the
|
||||
Norwegian axis even for a Norwegian edition (the C-3 bug this closes) — so resolve
|
||||
it explicitly, do not assume the default.
|
||||
|
||||
Use `AskUserQuestion` only for the genuinely open dimensions (cap ≈3). Good
|
||||
candidates: which personas are in scope + which is primær; the angle if the
|
||||
|
|
@ -446,6 +471,11 @@ Edition brief
|
|||
- If Step 1 ran the **trend-intake bridge**, persist `articles.NN.sourceTrendId`
|
||||
(the trend id) here too — the durable provenance link Step 10 reads to auto-`act`
|
||||
the trend. Omit it (leave `null`) for a manually-started edition.
|
||||
- Persist the resolved **`language`** (Step 1) — write it to the **top-level**
|
||||
`language` (the series/edition-level review language; there is no per-article
|
||||
`language` field) so the language-dependent review agents grade against the
|
||||
right language from here on. This is the write that makes the template
|
||||
doc-string's "Resolved at Step 1" claim true.
|
||||
- Write `articles.NN.livedSpecifics` (the slot-map above, `status: "pending"`).
|
||||
- Set `currentPhase: "lived-specifics"` in `edition-state.json`.
|
||||
- Write a "lived-specifics bound → next: research (scoped by the binding)" line
|
||||
|
|
@ -771,9 +801,21 @@ Typically ~20–30 % of the edition's final length.
|
|||
|
||||
**Procedure:**
|
||||
|
||||
1. **Re-read the voice profile** (`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`) before writing a
|
||||
single sentence — this is the existing LTL rule and it is not optional for
|
||||
long-form. Voice match starts at the spine, not at expansion.
|
||||
1. **Pass 0 — load the voice reference + the drift negatives** before writing a
|
||||
single sentence (voice match starts at the spine, not at expansion — the
|
||||
existing LTL rule, not optional for long-form):
|
||||
- Re-read the **chronicle-voice-profile**
|
||||
(`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/chronicle-voice-profile.md`)
|
||||
for this edition's language/format — the reference the spine must match
|
||||
(fall back to `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`
|
||||
per Step 0 if it is missing).
|
||||
- Read the **chronicle-voice-drift-log**
|
||||
(`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/chronicle-voice-drift-log.md`)
|
||||
as a **negative list**: the recurring AI-tells and voice drifts `voice-scrubber`
|
||||
logged on prior editions. Do NOT reproduce them in the spine — the log is no
|
||||
longer write-only; it steers drafting BEFORE the scrub, so the Step-4/persona
|
||||
passes and the scrubber find less each edition. If the log is absent, note it
|
||||
and proceed.
|
||||
|
||||
2. **For each section in `NN-skjelett.md`, write ONE paragraph that delivers
|
||||
that section's pitch.** No examples yet, no anecdotes, no research citations
|
||||
|
|
@ -865,8 +907,11 @@ turning-points the spine already named.
|
|||
|
||||
**Procedure:**
|
||||
|
||||
1. **Re-read the voice profile** (`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`) before expanding —
|
||||
the voice was set at the spine; do not lose it in expansion.
|
||||
1. **Re-read the chronicle-voice-profile**
|
||||
(`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/chronicle-voice-profile.md`,
|
||||
fallback `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/`)
|
||||
and the drift-log negatives (Step 3a Pass 0) before expanding — the voice was set
|
||||
at the spine; do not lose it, and do not let expansion reintroduce a logged drift.
|
||||
|
||||
2. **Expand section by section, against the spine.** Each section's paragraph
|
||||
from Step 3a is the *contract* for that section: the expansion must
|
||||
|
|
@ -1707,6 +1752,19 @@ produces the editor's single delivery artifact — `POST.html`, the
|
|||
body is frozen — Step 9 may only adjust the distribution hook, never the
|
||||
locked edition text.
|
||||
|
||||
5. **Auto-gull — grow the gold corpus (A2-F2).** A locked edition is now
|
||||
published-grade voice signal, so append it to the gold corpus the chronicle
|
||||
voice profile is distilled from:
|
||||
- Record the locked body (`NN-utkast.md`) as a new record under
|
||||
`${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/ingest/published/`,
|
||||
tagged **`provenance=published`** (never `ai-draft`) with this edition's
|
||||
`language` and format in its frontmatter, so `voice-trainer`'s
|
||||
chronicle-voice-profile distillation (per language/format) picks it up.
|
||||
- This closes the loop the model-collapse fence protects: the corpus the
|
||||
chronicle voice is learned from is the author's OWN locked editions, growing
|
||||
one edition at a time — never the engine's drafts. The next edition's Step 0
|
||||
therefore binds to a sharper profile without any manual corpus curation.
|
||||
|
||||
```
|
||||
LOCK → delivery.
|
||||
- Preconditions: factcheck 🔴 = none, persona resonans primær = JA (else: STOP)
|
||||
|
|
|
|||
|
|
@ -1512,6 +1512,88 @@ fi
|
|||
|
||||
echo ""
|
||||
|
||||
# --- Section 16o: Voice Chain — chronicle-voice-profile binding (N8 / C-1,C-2,C-3,A2-F1,A2-F2) ---
|
||||
echo "--- Voice Chain (N8) ---"
|
||||
|
||||
# N8 binds the long-form VOICE before prose — the same discipline the line already applies to
|
||||
# SOURCES (validate-binding), its strongest link. Five invariants must hold in the .md/.json surfaces:
|
||||
# (C-1/A2-F1) voice-trainer.md distils a chronicle-voice-profile from the PUBLISHED gold corpus, and
|
||||
# newsletter.md binds drafting (Step 0/3a/3b) to THAT profile — not to the English short-form
|
||||
# authentic-voice-samples corpus the engine itself calls degrading for chronicle voice.
|
||||
# (C-2) Step 3a READS the chronicle-voice-drift-log as a Pass-0 negative list (today write-only).
|
||||
# (C-3) Step 1 RESOLVES + persists the review `language`, making the edition-state doc-string's
|
||||
# "Resolved at Step 1" claim true, so the Norwegian axis fires when content IS Norwegian.
|
||||
# (A2-F2) Step 8 lock appends the locked edition to the gold corpus (ingest/published,
|
||||
# provenance=published) so the corpus the profile distils from grows automatically.
|
||||
# All are UNCONDITIONAL .md/.json greps (no TS deps → they raise the deps-absent floor). Auto-gull needs
|
||||
# BOTH gold-corpus literals; a non-vacuity self-test guards that compound predicate (mirrors Section 16c).
|
||||
CVP_LIT='chronicle-voice-profile'
|
||||
DRIFTLOG_LIT='chronicle-voice-drift-log'
|
||||
LANG_RESOLVE_LIT='Resolve the review language'
|
||||
GOLD_PATH_LIT='ingest/published'
|
||||
GOLD_PROV_LIT='provenance=published'
|
||||
|
||||
autogull_wired() { # $1 = text; wired iff BOTH gold-corpus literals present (echo twice — grep consumes stdin)
|
||||
echo "$1" | grep -qF "$GOLD_PATH_LIT" && echo "$1" | grep -qF "$GOLD_PROV_LIT"
|
||||
}
|
||||
|
||||
VC_SELFTEST_OK=1
|
||||
if ! autogull_wired "lock appends the edition to ingest/published with provenance=published"; then
|
||||
VC_SELFTEST_OK=0; echo " non-vacuity FAIL: a fully-wired auto-gull probe was not detected"
|
||||
fi
|
||||
while IFS= read -r probe; do
|
||||
[ -z "$probe" ] && continue
|
||||
if autogull_wired "$probe"; then
|
||||
VC_SELFTEST_OK=0; echo " false-positive FAIL: under-wired auto-gull probe accepted -> $probe"
|
||||
fi
|
||||
done <<'NEGATIVE16O'
|
||||
lock appends the edition to ingest/published/ but never tags provenance
|
||||
marks provenance=published but never names the gold-corpus path
|
||||
NEGATIVE16O
|
||||
if [ "$VC_SELFTEST_OK" -eq 1 ]; then
|
||||
pass "voice-chain self-test: auto-gull predicate needs BOTH gold-corpus literals (1 accepted, 2 under-wired rejected)"
|
||||
else
|
||||
fail "voice-chain self-test failed — the auto-gull lint is vacuous or over-eager"
|
||||
fi
|
||||
|
||||
NL="commands/newsletter.md"
|
||||
VT_N8="agents/voice-trainer.md"
|
||||
|
||||
# (C-1/A2-F1) chronicle-voice-profile artifact generated + bound
|
||||
if grep -qF "$CVP_LIT" "$VT_N8"; then
|
||||
pass "voice-trainer.md distils the $CVP_LIT (per-language gold-corpus voice artifact)"
|
||||
else
|
||||
fail "voice-trainer.md missing '$CVP_LIT' — the chronicle voice artifact is never generated (C-1/A2-F1)"
|
||||
fi
|
||||
if grep -qF "$CVP_LIT" "$NL"; then
|
||||
pass "newsletter.md binds long-form drafting to the $CVP_LIT (C-1)"
|
||||
else
|
||||
fail "newsletter.md missing '$CVP_LIT' — drafting still binds only to the short-form corpus (C-1)"
|
||||
fi
|
||||
|
||||
# (C-2) drift-log READ (Pass 0 negative list), not just written
|
||||
if grep -qF "$DRIFTLOG_LIT" "$NL"; then
|
||||
pass "newsletter.md reads the $DRIFTLOG_LIT as a Pass-0 negative list (C-2)"
|
||||
else
|
||||
fail "newsletter.md missing '$DRIFTLOG_LIT' — the drift log is still write-only (C-2)"
|
||||
fi
|
||||
|
||||
# (C-3) language resolved + persisted at Step 1
|
||||
if grep -qF "$LANG_RESOLVE_LIT" "$NL"; then
|
||||
pass "newsletter.md Step 1 resolves + persists the review language ('$LANG_RESOLVE_LIT', C-3)"
|
||||
else
|
||||
fail "newsletter.md missing the Step 1 language-resolution instruction ('$LANG_RESOLVE_LIT', C-3)"
|
||||
fi
|
||||
|
||||
# (A2-F2) auto-gull at Step 8 lock — the gold corpus grows on its own
|
||||
if autogull_wired "$(cat "$NL")"; then
|
||||
pass "newsletter.md Step 8 auto-gull: locked edition appended to the gold corpus ('$GOLD_PATH_LIT' + '$GOLD_PROV_LIT', A2-F2)"
|
||||
else
|
||||
fail "newsletter.md missing auto-gull — lock does not grow the gold corpus (needs '$GOLD_PATH_LIT' + '$GOLD_PROV_LIT', A2-F2)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# --- Section 18: Assertion-Count Anti-Erosion (SC6) ---
|
||||
# The lint self-modifies its own checks, so a green run could mask a silently dropped
|
||||
# assertion. Pin the total pass()+fail() invocations as a monotonic floor; the count
|
||||
|
|
@ -1540,12 +1622,15 @@ echo ""
|
|||
# cli.ts saturation-at flag grep) = 117; +6 for RE-R3e's six UNCONDITIONAL Section-16n checks
|
||||
# (brief-diff self-test + brief.ts diffSurfaced grep + brief.ts parseSurfacedFrontmatter grep +
|
||||
# brief.ts 'Nytt siden sist' section grep + cli.ts selectPriorBriefFile wiring grep + brief.ts
|
||||
# 'surfaced: ' frontmatter grep) = 123.
|
||||
# 'surfaced: ' frontmatter grep) = 123; +6 for N8's six UNCONDITIONAL Section-16o checks (voice-chain
|
||||
# self-test + voice-trainer chronicle-voice-profile grep + newsletter chronicle-voice-profile grep +
|
||||
# newsletter chronicle-voice-drift-log read grep + newsletter Step-1 language-resolve grep + newsletter
|
||||
# Step-8 auto-gull compound grep) = 129.
|
||||
# NB: the floor tracks the deps-absent MINIMUM (conditional TS suites warn-skip and drop
|
||||
# the count), so it is bumped only by UNCONDITIONAL new checks — NOT pinned to the
|
||||
# deps-present TOTAL_CHECKS (that would zero the warn-skip margin and false-fail a fresh
|
||||
# clone). Runs last so TOTAL_CHECKS sees every prior check.
|
||||
ASSERT_BASELINE_FLOOR=123
|
||||
ASSERT_BASELINE_FLOOR=129
|
||||
TOTAL_CHECKS=$((PASS + FAIL))
|
||||
if [ "$TOTAL_CHECKS" -ge "$ASSERT_BASELINE_FLOOR" ]; then
|
||||
pass "assertion-count anti-erosion: $TOTAL_CHECKS checks >= baseline floor $ASSERT_BASELINE_FLOOR"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue