linkedin-studio/scripts/test-runner.sh
Kjell Tore Guttormsen 3276e44dbf feat(linkedin-studio): RE-R3c — autonomous trigger (scheduler + headless entry) [skip-docs]
Closes research-engine hulls (1) no autonomous trigger + (6) no headless entry.
Makes the daily research loop closed + headless: deterministic-brief-only (C1),
print-first (C2 — the tool never runs launchctl or the cron table; --install writes
only the inert launchd plist file).

- NEW scripts/trends/src/schedule.ts — pure string emitters (launchd plist + cron-line +
  install/uninstall instructions + defaultLabel). No clock/fs/env/AI; byte-deterministic.
- NEW scripts/trends/run-daily.sh — bash-3.2 headless wrapper: resolves node, cd's into the
  package so tsx resolves, logs via the data-path twin seam; runs the deterministic brief and
  appends one compact cron.log line per fire. The (e) AI-capture seam is documented, not built.
- EDIT cli.ts — schedule --pillars <a,b> [--at HH:MM] [--fresh-days N]
  [--platform auto|launchd|cron] [--install|--uninstall] [--store <p>]; print-first, no new
  exit code; logPath anchored to dirname(defaultStorePath()) (not the --store override).
- WIRE trend-spotter.md (one prose line) + README (scheduler + wrapper + the C1 boundary).
- Gate: TRENDS_TESTS_FLOOR 171->192, ASSERT_BASELINE_FLOOR 105->111, new UNCONDITIONAL
  Section 16l (6 deps-absent greps + non-vacuity self-test), header-enum + floor-history append.

TDD two-phase RED -> GREEN. trends 192/192, gate 126/0, hook-suite 139/0 (untouched), plutil
-lint OK. No schema change (SCHEMA_VERSION 4 / BRIEF_SCHEMA_VERSION 1). Counts 29/19/27 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vmzxpsFpc8q19LaogAWLD
2026-06-26 11:00:59 +02:00

1428 lines
71 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# LinkedIn Studio Plugin — Structure Validator
# Validates the REAL v3.1 layout: registration counts (derived dynamically),
# frontmatter shape, hook drift, and plugin.json fields. Counts are asserted
# against the declared contract below, which is kept in sync with the
# CLAUDE.md "## Agents (N)" / "## Commands (N)" headers (cross-checked here)
# and the STATE.md "Telling" block. Adding or removing an agent, command,
# reference, or skill breaks the count-equality and fails the lint — this is
# the registration guard that gates the remediation plan's later steps.
#
# The stat-consistency grep (one magnitude per algorithm effect across the
# tree) was added in remediation Step 3; the version-consistency grep in
# Step 21; the agent model-consistency guard (each agents/<name>.md frontmatter
# model: must match every surface declaration, and canonical rosters must list
# every agent) in S11; the render-chain propagation guard (no honesty pattern a
# command was cleaned of survives in the reference it renders from) in S12; the
# `$`-safety guard (no untrusted value reaches a String.replace replacement STRING
# in state-updater.mjs — proven behaviorally, coverage-complete, self-testing) in
# S13. The external data-dir convention guard (M0: no command/agent/skill/hook prose
# references a migrated user-data path in-plugin — bare or ${CLAUDE_PLUGIN_ROOT}-pinned;
# + no ANALYTICS_ROOT in-plugin pin; + an SC2 dry-run that the tree holds no in-plugin
# user data) in Section 13, each with a non-vacuity self-test; the contract-gate
# binding guard (Slice 3: the §B/§C1 gate's own suite stays green and rules.ts
# ratifies 1:1 against the maskinrommet §E-manifest — KTG-only, skipped for an adopter
# shipping no deps/contract) in Section 14; the specifics-bank binding guard (Fix #2
# slice 3: the lived-specifics store/binding suite stays green and its case count
# never erodes — KTG-only, skipped for an adopter shipping no deps) in Section 15;
# the trends-store binding guard (research-engine slice 2b: the trend store's suite
# stays green and its case count never erodes, now that trend-spotter persists its
# findings through it — KTG-only, skipped for an adopter shipping no deps) in Section
# 16; the trend-spotter de-niche guard (B-S1: agents/trend-spotter.md names no
# hardcoded vendor/sector beat — the domain comes from the user's pillars at runtime,
# never baked into the agent — with a non-vacuity self-test) in Section 17; the brain
# profile-reader guard (SB-S3a: strategy-advisor names brain/profile.md AND carries the
# anti-sycophancy literal 'evidence to TEST', with a non-vacuity self-test) in Section
# 16d; the brain operations-reader guard (SB-S3d: strategy-advisor names
# brain/operations.md AND carries the frozen-past-self literal 'deprecates older
# inferences', with a non-vacuity self-test) in Section 16e; the brain reconcile-wiring
# guard (SB-S3e: scripts/brain/src/cli.ts dispatches `reconcile` AND calls the core
# reconcileRecentPosts by literal name, with a non-vacuity self-test) in Section 16f;
# the trends-scorer wiring guard (RE-R1: scripts/trends/src/score.ts encodes both mode
# weight-sets AND agents/trend-spotter.md references the scorer CLI 'src/cli.ts score',
# with a non-vacuity self-test) in Section 16g; the trends-capture wiring guard (RE-R2a:
# scripts/trends/src/cli.ts dispatches `capture` AND agents/trend-spotter.md references the
# capture CLI 'src/cli.ts capture', with a non-vacuity self-test) in Section 16h; the
# trends-brief wiring guard (RE-R2b: scripts/trends/src/cli.ts dispatches `brief`,
# agents/trend-spotter.md references the brief CLI 'src/cli.ts brief', AND
# hooks/scripts/session-start.mjs surfaces it via 'latestMorningBrief', with a non-vacuity
# self-test) in Section 16i; the trends-score wiring guard (RE-R3a: scripts/trends/src/score.ts
# exports the 'export interface TrendScore' persist envelope, scripts/trends/src/types.ts carries
# 'score?: TrendScore' on the record, agents/trend-spotter.md carries the judgment via '"dimensions"',
# AND scripts/trends/src/brief.ts ranks on 'score?.composite', with a non-vacuity self-test) in
# Section 16j; the trends-lifecycle wiring guard (RE-R3b: scripts/trends/src/types.ts declares
# 'export type TrendStatus' AND carries 'surfacedCount', scripts/trends/src/store.ts owns
# 'export function markSurfaced', scripts/trends/src/brief.ts excludes handled via 'effectiveStatus',
# AND scripts/trends/src/cli.ts exposes 'command === "act"', with a non-vacuity self-test) in
# Section 16k; the trends-scheduler/headless wiring guard (RE-R3c: scripts/trends/src/schedule.ts
# emits 'export function launchdPlist' AND 'export function crontabLine', scripts/trends/src/cli.ts
# exposes 'command === "schedule"', scripts/trends/run-daily.sh runs 'cli.ts" brief' AND uses
# 'LINKEDIN_STUDIO_DATA:-', with a non-vacuity self-test) in Section 16l; the assertion-count
# anti-erosion floor (SC6) in Section 18. All
# are live below (Sections 818).
#
# Usage: bash scripts/test-runner.sh
# bash 3.2-safe: plain arrays only, no `declare -A`, no `mapfile`/`readarray`.
set -e
PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$PLUGIN_ROOT"
PASS=0
FAIL=0
WARN=0
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
pass() { echo -e "${GREEN}${NC} $1"; PASS=$((PASS + 1)); }
fail() { echo -e "${RED}${NC} $1"; FAIL=$((FAIL + 1)); }
warn() { echo -e "${YELLOW}${NC} $1"; WARN=$((WARN + 1)); }
# --- Declared registration contract (the "Telling" block) ---
# Source of truth: CLAUDE.md headers + STATE.md Telling. Bump these together
# with the files when adding/removing an agent, command, reference, or skill.
EXPECT_AGENTS=19
EXPECT_COMMANDS=29
EXPECT_REFS=27
EXPECT_SKILLS=6
# Pre-M0 references/ baseline was 25. Every ref doc added since is NAMED below, so the
# count bump always maps to an intended, named addition — never an incidental doc masked
# by the bump (m3/m11). M0 added data-path-convention.md; each later slice appends its
# doc to POSTM0_REFS. The assert below proves EXPECT_REFS == 25 + 1 (M0) + |POSTM0_REFS|
# AND that every named doc actually exists. bash 3.2-safe: plain indexed array.
REFS_BASELINE_PRE_M0=25
M0_REF="references/data-path-convention.md"
POSTM0_REFS=("references/trend-scoring-modes.md") # research-engine slice 2a (scoring SSOT)
echo "================================================"
echo "LinkedIn Studio Plugin — Structure Validator"
echo "Plugin root: $PLUGIN_ROOT"
echo "================================================"
echo ""
# --- Section 1: Core Files ---
echo "--- Core Files ---"
for f in ".claude-plugin/plugin.json" "CLAUDE.md" "CHANGELOG.md" "README.md" "config/REMEMBER.template.md"; do
if [ -f "$f" ]; then
pass "$f exists"
else
fail "$f MISSING"
fi
done
echo ""
# --- Section 2: Registration Counts (dynamic) ---
echo "--- Registration Counts ---"
AGENTS=$(ls agents/*.md 2>/dev/null | wc -l | tr -d ' ')
COMMANDS=$(ls commands/*.md 2>/dev/null | wc -l | tr -d ' ')
REFS=$(ls references/*.md 2>/dev/null | wc -l | tr -d ' ')
SKILLS=$(ls skills/*/SKILL.md 2>/dev/null | wc -l | tr -d ' ')
assert_count() {
# $1 label, $2 actual, $3 expected
if [ "$2" -eq "$3" ]; then
pass "$1: $2 (expected $3)"
else
fail "$1: $2 (expected $3) — registration drift"
fi
}
assert_count "agents/*.md" "$AGENTS" "$EXPECT_AGENTS"
assert_count "commands/*.md" "$COMMANDS" "$EXPECT_COMMANDS"
assert_count "references/*.md" "$REFS" "$EXPECT_REFS"
assert_count "skills/*/SKILL.md" "$SKILLS" "$EXPECT_SKILLS"
# references/ count must map 1:1 to the NAMED additions (M0 + every later slice's doc),
# and each named doc must exist. Guards against the bump silently absorbing an incidental
# extra ref doc (m3/m11). To add a ref doc: append it to POSTM0_REFS and bump EXPECT_REFS.
NAMED_REFS_OK=1
[ -f "$M0_REF" ] || NAMED_REFS_OK=0
for r in "${POSTM0_REFS[@]}"; do
[ -f "$r" ] || NAMED_REFS_OK=0
done
EXPECT_NAMED=$((REFS_BASELINE_PRE_M0 + 1 + ${#POSTM0_REFS[@]}))
if [ "$EXPECT_REFS" -eq "$EXPECT_NAMED" ] && [ "$NAMED_REFS_OK" -eq 1 ]; then
pass "references/ count maps to named additions (${REFS_BASELINE_PRE_M0} baseline +1 M0 +${#POSTM0_REFS[@]} post-M0 = ${EXPECT_REFS}; all named docs exist)"
else
fail "references/ count != named additions — bump EXPECT_REFS and name the doc in POSTM0_REFS (expected ${EXPECT_NAMED}, have ${EXPECT_REFS}; named-docs-exist=${NAMED_REFS_OK})"
fi
# Cross-check the CLAUDE.md declared headers against the contract (doc-drift guard)
DOC_AGENTS=$(grep -oE '^## Agents \([0-9]+\)' CLAUDE.md | grep -oE '[0-9]+' | head -1)
DOC_COMMANDS=$(grep -oE '^## Commands \([0-9]+\)' CLAUDE.md | grep -oE '[0-9]+' | head -1)
if [ "$DOC_AGENTS" = "$EXPECT_AGENTS" ]; then
pass "CLAUDE.md '## Agents ($DOC_AGENTS)' matches contract"
else
fail "CLAUDE.md agents header ($DOC_AGENTS) != contract ($EXPECT_AGENTS)"
fi
if [ "$DOC_COMMANDS" = "$EXPECT_COMMANDS" ]; then
pass "CLAUDE.md '## Commands ($DOC_COMMANDS)' matches contract"
else
fail "CLAUDE.md commands header ($DOC_COMMANDS) != contract ($EXPECT_COMMANDS)"
fi
# README shields commands-count badge must match the contract too. Added after an
# S14 /trekreview found the badge stale at commands-27 while the surface shipped 29:
# the version-consistency grep (Section 9) checks only the version badge, and the
# count guards above check the CLAUDE.md header, so the README count badge slipped
# both. This closes that gap (the count-badge analogue of the version-badge check).
if grep -q "badge/commands-${EXPECT_COMMANDS}-" README.md; then
pass "README commands badge declares ${EXPECT_COMMANDS}"
else
fail "README commands badge != ${EXPECT_COMMANDS} (expected shields badge/commands-${EXPECT_COMMANDS}-)"
fi
echo ""
# --- Section 3: Agent Frontmatter ---
echo "--- Agent Frontmatter ---"
for f in agents/*.md; do
if head -1 "$f" | grep -q "^---"; then
if grep -q "^name:" "$f" && grep -q "^description:" "$f"; then
pass "$f (frontmatter OK)"
else
fail "$f (missing name:/description:)"
fi
else
fail "$f (no YAML frontmatter)"
fi
done
echo ""
# --- Section 4: Command Frontmatter ---
echo "--- Command Frontmatter ---"
for f in commands/*.md; do
if head -1 "$f" | grep -q "^---"; then
if grep -q "^name:" "$f" && grep -q "^description:" "$f"; then
pass "$f (frontmatter OK)"
else
fail "$f (missing name:/description:)"
fi
else
fail "$f (no YAML frontmatter)"
fi
done
echo ""
# --- Section 5: Hook Configuration (drift) ---
echo "--- Hook Configuration ---"
if [ -f "hooks/hooks.json" ]; then
pass "hooks/hooks.json exists"
if python3 hooks/scripts/compile-hooks.py --check >/dev/null 2>&1; then
pass "hooks.json matches compiled template (no drift)"
else
fail "hooks.json DRIFT — run: python3 hooks/scripts/compile-hooks.py"
fi
else
fail "hooks/hooks.json MISSING"
fi
echo ""
# --- Section 6: Plugin.json Validation ---
echo "--- Plugin.json Validation ---"
if python3 -c "
import json, sys
with open('.claude-plugin/plugin.json') as f:
data = json.load(f)
required = ['name', 'version', 'description']
missing = [field for field in required if field not in data]
if missing:
print('Missing fields:', missing)
sys.exit(1)
print('Version:', data['version'])
" 2>/dev/null; then
pass "plugin.json structure valid (name/version/description)"
else
fail "plugin.json structure invalid"
fi
echo ""
# --- Section 7: Analytics Source ---
echo "--- Analytics Source ---"
if [ -f "scripts/analytics/src/cli.ts" ]; then
pass "scripts/analytics/src/cli.ts exists"
else
fail "scripts/analytics/src/cli.ts MISSING"
fi
echo ""
# --- Section 8: Algorithm-Stat Consistency ---
echo "--- Algorithm-Stat Consistency ---"
# The single source of truth for algorithm magnitudes is
# references/algorithm-signals-reference.md. After the Phase-0 reconciliation,
# stale/competing magnitudes — the retired engagement-coefficient folklore, the
# unpublishable model params/brand, and the deployment date — must not reappear
# anywhere else (cite the reference, do not restate). This enforces "one magnitude
# per algorithm effect" by forbidding EVERY retired-class value from returning, so
# the same grep that defines the Phase-0 Success Criterion fails on any survivor.
#
# S9 rebuild: the S8 list forbade only the two S7-named strings and went green
# over six more survivors (the coefficient system in analytics-interpreter/
# content-optimizer/pipeline/glossary, the playbook 15x/5x, the 150B model). This
# list is rebuilt to the FULL criterion. Forbidden classes (each maps to a
# canonical statement in the reference):
# - Carousel-rate folklore: 6.6% / 6.60% / 1.92% → reference: "~7% top format"
# - Link-penalty folklore: 40-50% / 25-40% / -40-60% → reference: one ~38% correlational band
# - Comment-multiplier folklore: "15x more reach/algorithmic", "5x more effective/
# less valuable/reach than" → reference: order only, comment ≈ 2x a like
# - Video-multiplier folklore: "5x more conversations" → reference: video declining, no multiplier
# - Engagement-coefficient system: 7-9x, 2.5x, 0.2x, (10x), (8x), "10x weight"
# → reference: "never hard coefficients to optimize against"
# - Model params/brand/date: the PATTERN CLASS [0-9]+[ -]?(B|billion)?[ -]?param
# (covers 150-parameter / 150B param / 150 billion param) / 360Brew / January 2026
# → reference: "Not publishable as fact"
#
# S10: the model-precision token is now the pattern CLASS, not a literal-token
# list. S9 forbade only "150 ?B param|150 billion param"; a hyphenated
# "150-parameter" (no "B") slipped both the discovery grep and the lint, surviving
# in glossary.md:10. The criterion is "no asserted model precision in ANY surface
# form", so the lint now enforces the shape (a number adjacent to "param"), not an
# enumeration. An adjacent digit is REQUIRED, so legitimate "param" uses with no
# leading number — "Language parameter", "parameterized", "different parameters",
# "«parametere»", "175-milliarders parametermodell" — do not match.
# Bare "10x"/"15x"/"5x" are deliberately NOT forbidden — they carry legitimate
# uses (collaboration "10x your reach" hyperbole, "5x5x5", posting cadence, pixel
# dims like 1080x1350), so each token targets the retired *phrasing*, not the bare
# number.
#
# Scope covers every dir the criterion's grep covers, including assets/checklists/
# (the 360Brew survivor lived there, outside the S8 scan), assets/templates/, and
# CHANGELOG.md (S10: the 360Brew/January-2026 survivor lived there, outside the S9
# scope). assets/{templates,checklists}/ — not all of assets/ — keeps the scan off
# gitignored runtime data (assets/analytics/, assets/drafts/, voice-samples/).
STALE_STATS='40-50%|25-40%|6\.6%|6\.60%|1\.92%|15x more reach|15x more algorithmic|5x more effective|5x less valuable|5x more reach than|5x more conversations|7-9x|2\.5x|0\.2x|\(10x\)|\(8x\)|10x weight|-40-60%|[0-9]+[ -]?(B|billion)?[ -]?param|360Brew|January 2026'
# Non-vacuity self-test (S10). A grep criterion is only meaningful if it actually
# MATCHES the forbidden forms and does NOT match legitimate ones. S7→S9 each
# shipped a lint that passed green while a survivor slipped, because the proof was
# run once by hand and never committed — so a hyphenated "150-parameter" form was
# never re-checked. This makes the proof PERMANENT: it runs on every invocation
# BEFORE the real scan, so narrowing STALE_STATS back to a literal-token list fails
# the suite instead of silently certifying an unenforced criterion. The positive
# set covers all three model-precision surface forms (incl. the exact S10
# "150-parameter" survivor); the negative set covers the legitimate "param"/x uses
# that live in the tree today.
SELFTEST_OK=1
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if ! echo "$probe" | grep -qE "$STALE_STATS"; then
SELFTEST_OK=0; echo " non-vacuity FAIL: forbidden form not caught -> $probe"
fi
done <<'POSITIVE'
40-50% link penalty
6.6% carousel rate
1.92% reach
15x more reach
5x more conversations
7-9x weight
(10x) coefficient
10x weight
150-parameter foundation model
150B parameter foundation model
150 billion parameter model
360Brew
January 2026 algorithm update
POSITIVE
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if echo "$probe" | grep -qE "$STALE_STATS"; then
SELFTEST_OK=0; echo " false-positive FAIL: legitimate form caught -> $probe"
fi
done <<'NEGATIVE'
5x5x5 pre-posting method
post 3x per week
1080x1350 pixels
10x your reach
Language parameter (configurable)
parameterized content-gatekeeper
Start over with different parameters
175-milliarders parametermodell
NEGATIVE
if [ "$SELFTEST_OK" -eq 1 ]; then
pass "STALE_STATS self-test: 13 forbidden forms caught, 8 legitimate forms ignored"
else
fail "STALE_STATS self-test failed — the lint no longer enforces the full criterion"
fi
STAT_HITS=$(grep -rnE "$STALE_STATS" references/ commands/ skills/ hooks/prompts/ agents/ assets/templates/ assets/checklists/ CLAUDE.md README.md CHANGELOG.md .claude-plugin/plugin.json 2>/dev/null | grep -v 'algorithm-signals-reference' || true)
if [ -z "$STAT_HITS" ]; then
pass "no stale algorithm magnitudes / model brand outside the canonical reference"
else
fail "stale algorithm stat(s) reintroduced — cite algorithm-signals-reference.md instead:"
echo "$STAT_HITS"
fi
echo ""
# --- Section 9: Version Consistency ---
echo "--- Version Consistency ---"
# Single source of truth for the plugin version: .claude-plugin/plugin.json.
# Its value must be declared identically in the README badge, the CLAUDE.md
# header, and the CHANGELOG top entry. Historical references to older versions
# (CHANGELOG history, the README version-history table, "vX added Y" prose) are
# NOT checked here — only the current-version DECLARATIONS must agree.
VERSION=$(python3 -c "import json; print(json.load(open('.claude-plugin/plugin.json'))['version'])" 2>/dev/null)
if [ -z "$VERSION" ]; then
fail "could not read version from plugin.json"
else
pass "plugin.json version: $VERSION"
if grep -q "version-${VERSION}-blue" README.md; then
pass "README badge declares v$VERSION"
else
fail "README badge does not declare v$VERSION (expected version-${VERSION}-blue)"
fi
if grep -q "LinkedIn Studio Plugin (v${VERSION})" CLAUDE.md; then
pass "CLAUDE.md header declares v$VERSION"
else
fail "CLAUDE.md header does not declare (v$VERSION)"
fi
if grep -q "^## \[${VERSION}\]" CHANGELOG.md; then
pass "CHANGELOG has a [$VERSION] entry"
else
fail "CHANGELOG missing a [$VERSION] entry"
fi
fi
echo ""
# --- Section 10: Agent Model-Consistency ---
echo "--- Agent Model-Consistency ---"
# Each agents/<name>.md frontmatter `model:` is the source of truth; every
# surface that DECLARES an agent's model (README, CLAUDE.md, the capability
# matrix, the SKILL rosters) must match it, and the canonical rosters must list
# every agent. Added in S11 after a cold full-brief review found
# post-feedback-monitor published as Haiku across four surfaces while the agent
# runs Opus — declaration drift the version/count/stat guards could not see. The
# checker self-tests its own non-vacuity on every run (see the .mjs header):
# a deliberately-mismatched probe must be caught and a correct one ignored, else
# the suite fails instead of certifying an unenforced criterion.
if node scripts/check-model-consistency.mjs; then
pass "agent model-consistency: all surface declarations match frontmatter + canonical rosters complete"
else
fail "agent model-consistency drift — see check-model-consistency.mjs output above"
fi
echo ""
# --- Section 11: Render-Chain Propagation ---
echo "--- Render-Chain Propagation ---"
# Commands render from the references they inline via
# ${CLAUDE_PLUGIN_ROOT}/references/…. An honesty pattern removed from a command
# surface must NOT survive in the reference that command renders from — otherwise
# the user still hits it. Added in S12 after a cold full-brief review found the
# banned A/B significance-verdict column (`Significant? (>20%)` with Yes/No cells)
# still shipping in references/ab-testing-framework.md while commands/ab-test.md had
# already been cleaned to the honest "Directional?" framing. The command-level fix
# never propagated to its render-source, and Section 8's STALE_STATS grep targets
# magnitudes, not this construct, so the survivor passed green. This generalizes the
# fix from "clean the command" to "the banned construct is forbidden across the
# WHOLE render chain (commands AND references)". Future propagation-class patterns
# get appended to PROP_FORBIDDEN, mirroring how Section 8's STALE_STATS grew to the
# full criterion rather than the single named token.
#
# Forbidden: the significance-VERDICT column — `Significant?` adjacent to a `(` (the
# `(>20%)` verdict parenthetical) or a table pipe (`| Significant?`). The defect is a
# column steering users to record a statistical-significance call that organic
# personal-post volume never reaches; "directional" is the honest frame. Legitimate
# descriptive prose ("Significantly higher", "Significant capability", "statistical
# significance", a bare sentence-final "significant?") carries no `(`/`|`-adjacency
# and is left alone.
PROP_FORBIDDEN='Significant\?[[:space:]]*\(|\|[[:space:]]*Significant\?'
# Non-vacuity self-test (mirrors Section 8): the criterion is only meaningful if it
# MATCHES the verdict-column forms and IGNORES legitimate prose. Runs on every
# invocation BEFORE the real scan, so weakening the pattern fails the suite instead
# of silently certifying an unenforced guard. The positive set covers the exact S12
# survivor + its bare-column variant; the negative set covers the honest
# "Directional?" fix and every legitimate "Significant"/"significance" string the
# tree carries today.
PROP_SELFTEST_OK=1
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if ! echo "$probe" | grep -qE "$PROP_FORBIDDEN"; then
PROP_SELFTEST_OK=0; echo " non-vacuity FAIL: forbidden form not caught -> $probe"
fi
done <<'PROP_POSITIVE'
| Difference | Significant? (>20%) |
Significant? (>20%)
| Significant? |
PROP_POSITIVE
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if echo "$probe" | grep -qE "$PROP_FORBIDDEN"; then
PROP_SELFTEST_OK=0; echo " false-positive FAIL: legitimate form caught -> $probe"
fi
done <<'PROP_NEGATIVE'
| Difference | Directional? (>20% gap) |
Significantly higher weight than generic responses
Significant capability breakthroughs
Significantly Behind (<50%)
LinkedIn analytics does not support statistical significance tests
Is the difference significant? Probably not.
PROP_NEGATIVE
if [ "$PROP_SELFTEST_OK" -eq 1 ]; then
pass "render-chain propagation self-test: 3 verdict-column forms caught, 6 legitimate forms ignored"
else
fail "render-chain propagation self-test failed — the guard no longer enforces the criterion"
fi
# Real scan across the whole user-facing render chain (commands + every reference
# they inline) plus the adjacent surfaces a copy could migrate the table into.
PROP_HITS=$(grep -rnE "$PROP_FORBIDDEN" references/ commands/ skills/ hooks/prompts/ agents/ assets/templates/ assets/checklists/ 2>/dev/null || true)
if [ -z "$PROP_HITS" ]; then
pass "no significance-verdict column survives in any command or its render-source reference"
else
fail "significance-verdict column reintroduced — use the honest 'Directional?' framing (see commands/ab-test.md):"
echo "$PROP_HITS"
fi
echo ""
# --- Section 12: `$`-Safety (String.replace replacement) ---
echo "--- \$-Safety (String.replace replacement) ---"
# state-updater.mjs mutates the state file from untrusted user content (post
# topics, hooks, targets, partners, …). In a JS replacement *string*, `$&`/`` $` ``/
# `$'`/`$$`/`$n` are special, so a `$`-bearing value rewrites the field; a
# replacement *function* inserts its return verbatim. Added in S13 after a cold
# full-brief review found the LAST member of this class: S12 converted the 5
# section-append sites to functions but left `replaceField` (the scalar writer) on a
# replacement string, and the S12 `$`-test asserted only the section entry — never
# the `last_post_topic` scalar — so the corruption shipped green. This is the S9→S12
# "close the class, not the line" lesson on the `$`-axis: rather than grep a
# syntactic proxy (which cannot tell a replacement-position template literal from a
# RegExp-pattern one across multi-line calls), check-replace-safety.mjs drives EVERY
# exported mutator with an adversarial payload of every special token in every
# free-text + date field and asserts verbatim survival. Two structural backstops run
# inside it on every invocation: COVERAGE-COMPLETENESS (a new export without
# `$`-coverage fails) and a NON-VACUITY SELF-TEST (a naive string-replace MUST
# corrupt the payload, a function MUST preserve it — else a PASS is meaningless),
# mirroring Section 8/10/11.
if node scripts/check-replace-safety.mjs; then
pass "\$-safety: no untrusted value reaches a String.replace replacement string (behavioral, coverage-complete, self-testing)"
else
fail "\$-safety guard failed — a state-updater String.replace replacement is \$-unsafe; see check-replace-safety.mjs output above"
fi
echo ""
# --- Section 13: External Data-Dir Convention (R1 — M0) ---
echo "--- External Data-Dir Convention ---"
# After the M0 migration (user data lives under ${LINKEDIN_STUDIO_DATA:-$HOME/.claude/
# linkedin-studio}/), no command/agent/skill/hook prose may reference a MIGRATED
# user-data path in-plugin — neither bare (assets/<x>/) nor ${CLAUDE_PLUGIN_ROOT}-
# prefixed — or a command writes the external dir while Claude follows stale in-plugin
# prose (R1, brief §12.6). Shipped read-only assets keep ${CLAUDE_PLUGIN_ROOT} and are
# exempt: the analytics README, every *-template.md seed, assets/checklists/,
# assets/quick-post-resources.md, the shipped assets/templates/*, config/*.template.*.
# config/personas.local.md is a deliberate in-plugin fallback library (un-migrated, out
# of M0 scope) and is NOT in the class. Scope = the actionable prose dirs (commands/
# agents/skills/hooks-prompts); references/ is documentation (the convention doc
# legitimately names scaffold paths) and is excluded.
BARE_DATA='(\$\{CLAUDE_PLUGIN_ROOT\}/)?assets/(voice-samples|analytics|drafts|plans|audience-insights|examples|frameworks|case-studies|network)/|(\$\{CLAUDE_PLUGIN_ROOT\}/)?assets/repurposing-tracker\.md|(\$\{CLAUDE_PLUGIN_ROOT\}/)?assets/templates/my-post-templates\.md|(\$\{CLAUDE_PLUGIN_ROOT\}/)?config/user-profile\.local\.md'
BARE_EXEMPT='README\.md|-template\.md'
# Non-vacuity self-test (mirrors Section 8): the full criterion is regex-match AND
# not-exempt. Positives MUST be violations; negatives (external token + every shipped
# exemption + the out-of-scope personas lib) MUST NOT be.
B13_OK=1
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if echo "$probe" | grep -qE "$BARE_DATA" && ! echo "$probe" | grep -qE "$BARE_EXEMPT"; then :; else
B13_OK=0; echo " non-vacuity FAIL: in-plugin data path not flagged -> $probe"
fi
done <<'POSITIVE13'
assets/voice-samples/authentic-voice-samples.md
${CLAUDE_PLUGIN_ROOT}/assets/analytics/posts/
assets/drafts/queue.json
assets/templates/my-post-templates.md
assets/frameworks/ai-maturity-model.md
assets/repurposing-tracker.md
config/user-profile.local.md
POSITIVE13
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if echo "$probe" | grep -qE "$BARE_DATA" && ! echo "$probe" | grep -qE "$BARE_EXEMPT"; then
B13_OK=0; echo " false-positive FAIL: legitimate path flagged -> $probe"
fi
done <<'NEGATIVE13'
${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics/posts/
assets/analytics/README.md
assets/frameworks/framework-template.md
assets/case-studies/case-study-template.md
assets/checklists/quality-scorecard.md
assets/quick-post-resources.md
config/user-profile.template.md
config/personas.local.md
NEGATIVE13
if [ "$B13_OK" -eq 1 ]; then
pass "no-bare-path self-test: 7 in-plugin data paths caught, 8 legitimate/exempt forms ignored"
else
fail "no-bare-path self-test failed — the R1 criterion no longer enforces in-plugin data-path detection"
fi
BARE_HITS=$(grep -rnE "$BARE_DATA" commands/ agents/ skills/ hooks/prompts/ 2>/dev/null | grep -vE "$BARE_EXEMPT" || true)
if [ -z "$BARE_HITS" ]; then
pass "no command/agent/skill/hook prose references a migrated user-data path in-plugin (R1)"
else
fail "in-plugin user-data path(s) in prose — route via \${LINKEDIN_STUDIO_DATA:-\$HOME/.claude/linkedin-studio}/ (see references/data-path-convention.md):"
echo "$BARE_HITS"
fi
# Sibling no-pin guard (M3): the no-bare-path grep cannot catch an ANALYTICS_ROOT alias
# pinned to the in-plugin analytics dir — Step 11 dropped these; forbid their return.
PIN='ANALYTICS_ROOT=.{0,2}\$\{CLAUDE_PLUGIN_ROOT\}/assets/analytics'
PIN_OK=1
echo 'ANALYTICS_ROOT="${CLAUDE_PLUGIN_ROOT}/assets/analytics"' | grep -qE "$PIN" || { PIN_OK=0; echo " non-vacuity FAIL: in-plugin pin not caught"; }
if echo 'ANALYTICS_ROOT="${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics"' | grep -qE "$PIN"; then PIN_OK=0; echo " false-positive FAIL: external alias caught"; fi
if [ "$PIN_OK" -eq 1 ]; then
pass "no-pin self-test: in-plugin ANALYTICS_ROOT pin caught, external alias ignored"
else
fail "no-pin self-test failed — the M3 pin criterion is not enforced"
fi
PIN_HITS=$(grep -rnE "$PIN" commands/ agents/ skills/ hooks/prompts/ 2>/dev/null || true)
if [ -z "$PIN_HITS" ]; then
pass "no prose pins ANALYTICS_ROOT to the in-plugin analytics dir (M3)"
else
fail "ANALYTICS_ROOT pinned to in-plugin analytics — drop the pin (external default applies):"
echo "$PIN_HITS"
fi
# SC2 dry-run: the migrated in-plugin user-data classes (.gitignore) must hold NO data
# files — after M0 they live external. --ignored surfaces gitignored stragglers a plain
# porcelain would hide; filtered to the data classes, it must be empty. Catches a flow
# (or a stray file) that wrote user data back into the plugin tree (SC2). personas is
# excluded (un-migrated, out of M0 scope — consistent with Section 13's class).
SC2_CLASSES='assets/analytics/(exports|posts|weekly-reports|monthly-reports)/|assets/drafts/queue\.json|assets/drafts/week-|assets/voice-samples/[^ ]*\.local\.md|config/user-profile\.local\.md'
SC2_DIRT=$(git status --porcelain --ignored 2>/dev/null | grep -E "$SC2_CLASSES" || true)
if [ -z "$SC2_DIRT" ]; then
pass "SC2 dry-run: no in-plugin user-data files (analytics/drafts/voice/profile) in the tree"
else
fail "SC2 violation — migrated user data sits in the plugin tree (should be external):"
echo "$SC2_DIRT"
fi
echo ""
# --- Section 14: Contract-Gate Binding (Slice 3) ---
echo "--- Contract-Gate Binding ---"
# The deterministic §B/§C1 contract-gate (scripts/contract-gate) is wired into
# /linkedin:newsletter as Step 4.5. Two invariants belong in CI, not per-edition:
# (14a) the gate's own test suite stays green and its case count never erodes; and
# (14b) rules.ts stays bound 1:1 to the maskinrommet skrivekontrakt §E-manifest
# (ratify). Both are KTG-internal: skipped (warn, never fail) for an adopter that
# ships no contract-gate deps or no contract — so the lint stays green everywhere.
# bash 3.2-safe; `set +e` inside the test command-substitution keeps a red npm test
# from aborting the whole runner (set -e is active), and ratify exit 1 is captured by
# the `if (subshell)` form (condition context is exempt from set -e).
CG_DIR="scripts/contract-gate"
if [ -x "$CG_DIR/node_modules/.bin/tsx" ]; then
# 14a: the gate suite is green (npm test exit 0) and holds its case-count floor.
CG_OUT=$( set +e; (cd "$CG_DIR" && npm test) 2>&1; echo "CG_EXIT:$?" )
CG_EXIT=$(echo "$CG_OUT" | grep -oE 'CG_EXIT:[0-9]+' | grep -oE '[0-9]+' | head -1)
CG_TESTS=$(echo "$CG_OUT" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)
CONTRACT_GATE_TESTS_FLOOR=33
if [ "$CG_EXIT" = "0" ] && [ -n "$CG_TESTS" ] && [ "$CG_TESTS" -ge "$CONTRACT_GATE_TESTS_FLOOR" ]; then
pass "contract-gate suite green: $CG_TESTS tests pass (floor $CONTRACT_GATE_TESTS_FLOOR)"
else
fail "contract-gate suite NOT green (exit=${CG_EXIT:-?}, tests=${CG_TESTS:-?}, floor $CONTRACT_GATE_TESTS_FLOOR) — run: (cd $CG_DIR && npm test)"
fi
# 14b: ratify — rules.ts <-> §E-manifest 1:1. Skipped when the contract is absent.
CONTRACT="${MASKINROMMET_CONTRACT:-$PLUGIN_ROOT/../../maskinrommet/docs/skrivekontrakt.md}"
if [ -f "$CONTRACT" ]; then
if (cd "$CG_DIR" && node --import tsx src/cli.ts --ratify) >/dev/null 2>&1; then
pass "contract-gate ratify: rules.ts bound 1:1 to the §E-manifest (in sync)"
else
fail "contract-gate ratify DRIFT — rules.ts != §E-manifest; run: (cd $CG_DIR && npm run ratify)"
fi
else
warn "contract-gate ratify skipped — contract absent ($CONTRACT); KTG-only invariant"
fi
else
warn "contract-gate skipped — deps absent ($CG_DIR/node_modules); run: (cd $CG_DIR && npm install)"
fi
echo ""
# --- Section 15: Specifics-Bank Binding (Fix #2 slice 3) ---
echo "--- Specifics-Bank Binding ---"
# The lived-specifics store + per-edition binding (scripts/specifics-bank) is wired
# into /linkedin:newsletter as Step 1.5 (elicitation + slot-map) and its
# validate-binding gate into the Step 2.5 skeleton-gate. The seam is now load-bearing,
# so its suite belongs in CI: the bank/binding/kilder tests stay green and the case
# count never erodes. KTG-internal: skipped (warn, never fail) for an adopter that
# ships no specifics-bank deps — so the lint stays green everywhere. Same set +e /
# subshell discipline as Section 14 (bash 3.2-safe; keeps a red npm test from
# aborting the runner under set -e).
SB_DIR="scripts/specifics-bank"
if [ -x "$SB_DIR/node_modules/.bin/tsx" ]; then
SB_OUT=$( set +e; (cd "$SB_DIR" && npm test) 2>&1; echo "SB_EXIT:$?" )
SB_EXIT=$(echo "$SB_OUT" | grep -oE 'SB_EXIT:[0-9]+' | grep -oE '[0-9]+' | head -1)
SB_TESTS=$(echo "$SB_OUT" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)
SPECIFICS_BANK_TESTS_FLOOR=28
if [ "$SB_EXIT" = "0" ] && [ -n "$SB_TESTS" ] && [ "$SB_TESTS" -ge "$SPECIFICS_BANK_TESTS_FLOOR" ]; then
pass "specifics-bank suite green: $SB_TESTS tests pass (floor $SPECIFICS_BANK_TESTS_FLOOR)"
else
fail "specifics-bank suite NOT green (exit=${SB_EXIT:-?}, tests=${SB_TESTS:-?}, floor $SPECIFICS_BANK_TESTS_FLOOR) — run: (cd $SB_DIR && npm test)"
fi
else
warn "specifics-bank skipped — deps absent ($SB_DIR/node_modules); run: (cd $SB_DIR && npm install)"
fi
echo ""
# --- Section 16: Trends-Store Binding (research-engine slice 2b) ---
echo "--- Trends-Store Binding ---"
# The persistent trend store (scripts/trends) is wired into the trend-spotter agent
# (slice 2b): the agent queries prior history and persists each kept trend through the
# store's deterministic add (dedup/union), so the store is now load-bearing for the
# research engine's de-amnesia — not a standalone inventory. Its suite therefore belongs
# in CI: the store/dedup/query tests stay green and the case count never erodes. Mirrors
# the specifics-bank binding guard (Section 15) — the trend-side twin. KTG-internal:
# skipped (warn, never fail) for an adopter that ships no trends deps. Same set +e /
# subshell discipline as Sections 1415 (bash 3.2-safe; keeps a red npm test from
# aborting the runner under set -e).
TR_DIR="scripts/trends"
if [ -x "$TR_DIR/node_modules/.bin/tsx" ]; then
TR_OUT=$( set +e; (cd "$TR_DIR" && npm test) 2>&1; echo "TR_EXIT:$?" )
TR_EXIT=$(echo "$TR_OUT" | grep -oE 'TR_EXIT:[0-9]+' | grep -oE '[0-9]+' | head -1)
TR_TESTS=$(echo "$TR_OUT" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)
TRENDS_TESTS_FLOOR=192 # store 24 + RE-R1: item 18 + score 16 + cli 4 + RE-R2a: store +9 + item +4 + cli +4 (capture bridge + publishedAt) + RE-R2b: brief +21 + cli +4 (morning-brief) + RE-R3a: score +6, item +12, store +6, brief +16, cli +2 (relevance score persist + rank) + RE-R3b: store +11, brief +8, cli +6 (lifecycle: re-score + status + seen-log) + RE-R3c: schedule +9, cli +8, run-daily +4 (scheduler + headless wrapper)
if [ "$TR_EXIT" = "0" ] && [ -n "$TR_TESTS" ] && [ "$TR_TESTS" -ge "$TRENDS_TESTS_FLOOR" ]; then
pass "trends-store suite green: $TR_TESTS tests pass (floor $TRENDS_TESTS_FLOOR)"
else
fail "trends-store suite NOT green (exit=${TR_EXIT:-?}, tests=${TR_TESTS:-?}, floor $TRENDS_TESTS_FLOOR) — run: (cd $TR_DIR && npm test)"
fi
else
warn "trends-store skipped — deps absent ($TR_DIR/node_modules); run: (cd $TR_DIR && npm install)"
fi
echo ""
# --- Section 16b: Brain Foundation (SB-S0) ---
echo "--- Brain Foundation ---"
# The second-brain foundation (scripts/brain, SB-S0) ships the brain/ scaffold
# initialiser, the two-layer profile.md line-grammar, the lossless+idempotent
# user-profile fold, and the canonical entity-id + provenance module. This is the
# spine later slices hang on (the id threads through tributaries in SB-S3, the
# evolution loop consumes the profile in SB-S2), so the suite stays green and its
# case count never erodes. KTG-internal: skipped (warn, never fail) for an adopter
# that ships no brain deps. Same set +e / subshell discipline as Sections 14-16
# (bash 3.2-safe; keeps a red npm test from aborting the runner under set -e).
BR_DIR="scripts/brain"
if [ -x "$BR_DIR/node_modules/.bin/tsx" ]; then
BR_OUT=$( set +e; (cd "$BR_DIR" && npm test) 2>&1; echo "BR_EXIT:$?" )
BR_EXIT=$(echo "$BR_OUT" | grep -oE 'BR_EXIT:[0-9]+' | grep -oE '[0-9]+' | head -1)
BR_TESTS=$(echo "$BR_OUT" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)
BRAIN_TESTS_FLOOR=127 # SB-S0 34 [id(11)+profile(6)+fold(12)+scaffold(5)] + SB-S1 29 [ingest(14)+publish(9)+cli(6)] + SB-S2 19 [consolidate(12)+consolidate-cli(7)] + SB-S3b 12 [consolidate(10)+consolidate-cli(2)] + SB-S3c 19 [ingest(4)+publish(3)+assemble(8)+cli(4)] + SB-S3d 1 [scaffold dated-anchor seed] + SB-S3e 13 [reconcile: parse(5)+tiers(7)+io(1)]
if [ "$BR_EXIT" = "0" ] && [ -n "$BR_TESTS" ] && [ "$BR_TESTS" -ge "$BRAIN_TESTS_FLOOR" ]; then
pass "brain suite green: $BR_TESTS tests pass (floor $BRAIN_TESTS_FLOOR)"
else
fail "brain suite NOT green (exit=${BR_EXIT:-?}, tests=${BR_TESTS:-?}, floor $BRAIN_TESTS_FLOOR) — run: (cd $BR_DIR && npm test)"
fi
else
warn "brain skipped — deps absent ($BR_DIR/node_modules); run: (cd $BR_DIR && npm install)"
fi
echo ""
# --- Section 16c: Brain Published-Only Invariant (SB-S1) ---
echo "--- Brain Published-Only Invariant ---"
# SB-S1 wires voice-trainer to learn from the published gold signal ONLY: the agent
# must (a) name ingest/published/ as a source AND (b) carry the explicit exclusion of
# provenance=ai-draft (the model-collapse guard — never train voice on the engine's own
# drafts). Both literals are required. voice-trainer.md ALREADY contains "AI-generated"
# (a drift-cause description, line ~175), so a loose ai.?draft pattern would pass
# vacuously — we grep the EXACT literals with grep -F. The contract doc must also state
# the rule. Non-vacuity self-test mirrors Sections 8/13/17: a probe counts as "wired"
# iff it carries BOTH literals; probes missing either (incl. one with "AI-generated" but
# not "ai-draft") must NOT count as wired. The wiring is gate-enforced; the agent's
# runtime OBEDIENCE is not provable here (stated honestly in the contract doc).
PUB_LIT='ingest/published'
DRAFT_LIT='provenance=ai-draft'
pub_wired() { # $1 = text; wired iff BOTH literals present (echo twice — grep consumes stdin)
echo "$1" | grep -qF "$PUB_LIT" && echo "$1" | grep -qF "$DRAFT_LIT"
}
PUB_SELFTEST_OK=1
if ! pub_wired "reads from ingest/published/ and excludes provenance=ai-draft"; then
PUB_SELFTEST_OK=0; echo " non-vacuity FAIL: a fully-wired probe was not detected"
fi
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if pub_wired "$probe"; then
PUB_SELFTEST_OK=0; echo " false-positive FAIL: under-wired probe accepted -> $probe"
fi
done <<'NEGATIVE16C'
reads from ingest/published/ but never states the draft exclusion
excludes provenance=ai-draft but never names the published source
flags AI-generated drift but carries neither required literal
NEGATIVE16C
if [ "$PUB_SELFTEST_OK" -eq 1 ]; then
pass "published-only self-test: full wiring detected; 3 under-wired forms rejected (incl. 'AI-generated'-only)"
else
fail "published-only self-test failed — the invariant lint is vacuous or over-eager"
fi
VT="agents/voice-trainer.md"
if grep -qF "$PUB_LIT" "$VT" && grep -qF "$DRAFT_LIT" "$VT"; then
pass "voice-trainer.md wired to published-only (names '$PUB_LIT', excludes '$DRAFT_LIT')"
else
fail "voice-trainer.md missing published-only wiring — needs both '$PUB_LIT' and '$DRAFT_LIT'"
fi
PUB_DOC="docs/second-brain/ingest-manual-import.md"
if grep -qF "$DRAFT_LIT" "$PUB_DOC"; then
pass "ingest-manual-import.md documents the published-only rule ('$DRAFT_LIT')"
else
fail "ingest-manual-import.md missing the published-only rule literal '$DRAFT_LIT'"
fi
echo ""
# --- Section 16d: Brain Profile Reader (SB-S3a) ---
echo "--- Brain Profile Reader ---"
# SB-S3a wires the FIRST reader of the evolving second-brain profile: strategy-advisor
# must (a) name brain/profile.md as a context source AND (b) carry the anti-sycophancy
# literal 'evidence to TEST' (architecture.md:58 — the profile informs, it never dictates
# or flatters; every fact is counter-pressured against analytics/state). Both literals
# are required, grepped EXACT with grep -F: a loose 'profile' pattern would match the
# legacy flat profile/user-profile.md vacuously, and a loose lowercase 'evidence' would
# match generic prose. Non-vacuity self-test mirrors Sections 8/13/16c/17: a probe counts
# as "wired" iff it carries BOTH exact literals; probes missing either (incl. a decoy
# naming the legacy path + lowercase "evidence") must NOT count as wired. The wiring is
# gate-enforced; the agent's runtime OBEDIENCE is verified manually (SC4/SC5 at land —
# agent-prompt behaviour is not unit-testable; the brief states this honestly).
READER_LIT='brain/profile.md'
ANTISYC_LIT='evidence to TEST'
reader_wired() { # $1 = text; wired iff BOTH literals present (echo twice — grep consumes stdin)
echo "$1" | grep -qF "$READER_LIT" && echo "$1" | grep -qF "$ANTISYC_LIT"
}
READER_SELFTEST_OK=1
if ! reader_wired "reads brain/profile.md as evidence to TEST"; then
READER_SELFTEST_OK=0; echo " non-vacuity FAIL: a fully-wired probe was not detected"
fi
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if reader_wired "$probe"; then
READER_SELFTEST_OK=0; echo " false-positive FAIL: under-wired probe accepted -> $probe"
fi
done <<'NEGATIVE16D'
reads brain/profile.md but never frames how to weigh it
treats facts as evidence to TEST but names no source file
reads profile/user-profile.md and tests the evidence
NEGATIVE16D
if [ "$READER_SELFTEST_OK" -eq 1 ]; then
pass "profile-reader self-test: full wiring detected; 3 under-wired forms rejected (incl. legacy-path + lowercase 'evidence' decoy)"
else
fail "profile-reader self-test failed — the reader-wiring lint is vacuous or over-eager"
fi
SA="agents/strategy-advisor.md"
if grep -qF "$READER_LIT" "$SA" && grep -qF "$ANTISYC_LIT" "$SA"; then
pass "strategy-advisor.md wired to brain profile reader (names '$READER_LIT', frames '$ANTISYC_LIT')"
else
fail "strategy-advisor.md missing brain profile reader wiring — needs both '$READER_LIT' and '$ANTISYC_LIT'"
fi
echo ""
# --- Section 16e: Brain Operations Reader (SB-S3d) ---
echo "--- Brain Operations Reader ---"
# SB-S3d wires strategy-advisor to read the second-brain OPERATIONS centre: it must
# (a) name brain/operations.md as a context source AND (b) carry the frozen-past-self
# literal 'deprecates older inferences' (architecture.md:59 — the dated "who I am now"
# anchor outranks/deprecates older profile inferences; advisory/reader-side). Both
# literals are required, grepped EXACT with grep -F: a loose 'operations' pattern would
# match generic prose, and naming the sibling brain/profile.md must NOT satisfy the
# operations literal. Non-vacuity self-test mirrors Sections 8/13/16c/16d: a probe counts
# as "wired" iff it carries BOTH exact literals; probes missing either (incl. a sibling-
# file decoy naming brain/profile.md) must NOT count as wired. The wiring is gate-enforced;
# the agent's runtime OBEDIENCE is verified manually (SC6/SC7 at land — agent-prompt
# behaviour is not unit-testable; the brief states this honestly).
OPS_LIT='brain/operations.md'
ANCHOR_LIT='deprecates older inferences'
ops_reader_wired() { # $1 = text; wired iff BOTH literals present (echo twice — grep consumes stdin)
echo "$1" | grep -qF "$OPS_LIT" && echo "$1" | grep -qF "$ANCHOR_LIT"
}
OPS_SELFTEST_OK=1
if ! ops_reader_wired "reads brain/operations.md; the anchor deprecates older inferences"; then
OPS_SELFTEST_OK=0; echo " non-vacuity FAIL: a fully-wired probe was not detected"
fi
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if ops_reader_wired "$probe"; then
OPS_SELFTEST_OK=0; echo " false-positive FAIL: under-wired probe accepted -> $probe"
fi
done <<'NEGATIVE16E'
reads brain/operations.md but never says the anchor outranks anything
the anchor deprecates older inferences but names no source file
reads brain/profile.md and tests the evidence
NEGATIVE16E
if [ "$OPS_SELFTEST_OK" -eq 1 ]; then
pass "ops-reader self-test: full wiring detected; 3 under-wired forms rejected (incl. sibling-file brain/profile.md decoy)"
else
fail "ops-reader self-test failed — the ops-reader-wiring lint is vacuous or over-eager"
fi
SA_OPS="agents/strategy-advisor.md"
if grep -qF "$OPS_LIT" "$SA_OPS" && grep -qF "$ANCHOR_LIT" "$SA_OPS"; then
pass "strategy-advisor.md wired to brain operations reader (names '$OPS_LIT', frames '$ANCHOR_LIT')"
else
fail "strategy-advisor.md missing brain operations reader wiring — needs both '$OPS_LIT' and '$ANCHOR_LIT'"
fi
echo ""
# --- Section 16f: Brain Reconcile Wiring (SB-S3e) ---
echo "--- Brain Reconcile Wiring ---"
# SB-S3e wires `brain reconcile` (read-side triple-post reconciliation: silo 1
# `## Recent Posts` ↔ the silo 2↔3 graph). The CLI must (a) dispatch the reconcile
# command AND (b) call the pure core by its literal name. Both literals live in
# scripts/brain/src/cli.ts (the dispatch `if` + runReconcile's literal-name call),
# grepped EXACT with grep -F. Non-vacuity self-test mirrors Section 16e: a probe is
# "wired" iff it carries BOTH literals; D1/D2 miss EXACTLY ONE literal (the load-
# bearing discriminators), D3 is a fully-wired SIBLING command (assemble) carrying
# neither reconcile literal (specificity — the predicate is reconcile-specific, not
# "any command + any core"). Wiring is gate-enforced; the reconcile CORE correctness
# is covered by the brain suite (reconcile.test.ts) and the end-to-end run is verified
# manually (SC4/SC6 at land — CLI behaviour is not unit-tested here; brief says so).
RECON_CLI_LIT='command === "reconcile"'
RECON_CORE_LIT='reconcileRecentPosts'
reconcile_wired() { # $1 = text; wired iff BOTH literals present (echo twice — grep consumes stdin)
echo "$1" | grep -qF "$RECON_CLI_LIT" && echo "$1" | grep -qF "$RECON_CORE_LIT"
}
RECON_SELFTEST_OK=1
if ! reconcile_wired 'if (command === "reconcile") return runReconcile via reconcileRecentPosts'; then
RECON_SELFTEST_OK=0; echo " non-vacuity FAIL: a fully-wired probe was not detected"
fi
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if reconcile_wired "$probe"; then
RECON_SELFTEST_OK=0; echo " false-positive FAIL: under-wired probe accepted -> $probe"
fi
done <<'NEGATIVE16F'
if (command === "reconcile") return runReconcile(flags) but never calls the core
const nodes = reconcileRecentPosts({ recentPosts, records, graph }) with no command dispatch
if (command === "assemble") return runAssemble(flags) using assemblePostGraph
NEGATIVE16F
if [ "$RECON_SELFTEST_OK" -eq 1 ]; then
pass "reconcile self-test: full wiring detected; D1/D2 single-literal forms + D3 assemble specificity decoy rejected"
else
fail "reconcile self-test failed — the reconcile-wiring lint is vacuous or over-eager"
fi
CLI_RECON="scripts/brain/src/cli.ts"
if grep -qF "$RECON_CLI_LIT" "$CLI_RECON" && grep -qF "$RECON_CORE_LIT" "$CLI_RECON"; then
pass "brain CLI wired to reconcile (dispatch + core call by literal name)"
else
fail "brain CLI missing reconcile wiring — needs both '$RECON_CLI_LIT' and '$RECON_CORE_LIT' in $CLI_RECON"
fi
echo ""
# --- Section 17: De-Niche Guard (B-S1 + B-S2) ---
echo "--- De-Niche Guard ---"
# The de-niche sweep removed the hardcoded KTG beat (Microsoft / public-sector) from
# three surfaces so the domain comes from the user's profile/pillars at runtime, never
# baked in (plugin-is-domain-general):
# - agents/trend-spotter.md (B-S1: pillar-driven scanning; the agent's own
# contract says "the niche lives in the source
# list and the user's pillars, never in this agent")
# - agents/content-planner.md (B-S2a: planner calendar recast off the beat)
# - references/content-framework.md (B-S2a: recast + renamed from ai-content-framework.md)
# Each must name NO specific vendor or sector beat; this guard forbids those KTG-beat
# proper nouns from returning to any of them. Scoped per file BY DESIGN: the check runs
# against this explicit allowlist, NOT the whole tree, because references/content-angles.md
# legitimately lists "Public Sector" as one of six example industry tables (a KEEP surface,
# generic illustration — not a hardcoded beat), and "AI" is the plugin's own subject, not a
# niche token. Non-vacuity self-test mirrors Sections 8/13: the criterion must catch the beat
# tokens and ignore generic prose (incl. the content-framework.md reference filename — B-S2a
# recast + renamed it from ai-content-framework.md — and "AI"-flavoured planner prose, locking
# in that "AI" is never treated as a beat). Case-insensitive: "Public sector" and "public
# sector" name the same beat, and a future reintroduction could use either case — the
# positive set locks that in.
NICHE_TOKENS='Microsoft|Azure|Copilot|public sector|offentlig sektor'
TS_SELFTEST_OK=1
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if ! echo "$probe" | grep -qiE "$NICHE_TOKENS"; then
TS_SELFTEST_OK=0; echo " non-vacuity FAIL: beat token not caught -> $probe"
fi
done <<'POSITIVE17'
Microsoft platform changes
Azure updates
Copilot rollout
Public sector leaders
trender i offentlig sektor
POSITIVE17
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if echo "$probe" | grep -qiE "$NICHE_TOKENS"; then
TS_SELFTEST_OK=0; echo " false-positive FAIL: generic prose caught -> $probe"
fi
done <<'NEGATIVE17'
platform changes in the user's stack
sector milestones in the user's domain
the user's content pillars and expertise areas
references/content-framework.md
major product/model releases
AI-driven content planning across the user's pillars
NEGATIVE17
if [ "$TS_SELFTEST_OK" -eq 1 ]; then
pass "de-niche self-test: 5 beat tokens caught (case-insensitive), 6 generic forms ignored (incl. 'AI' kept as the plugin's own subject)"
else
fail "de-niche self-test failed — the guard no longer enforces the no-beat criterion"
fi
for guarded in agents/trend-spotter.md agents/content-planner.md references/content-framework.md; do
NICHE_HITS=$(grep -niE "$NICHE_TOKENS" "$guarded" 2>/dev/null || true)
if [ -z "$NICHE_HITS" ]; then
pass "$guarded names no hardcoded vendor/sector beat (domain comes from pillars)"
else
fail "$guarded hardcodes a vendor/sector beat — generalize to pillar-driven prose:"
echo "$NICHE_HITS"
fi
done
echo ""
# --- Section 16g: Trends Scorer Wiring (research-engine RE-R1 / B2) ---
echo "--- Trends Scorer Wiring ---"
# RE-R1 lifts the composite/band/threshold arithmetic out of trend-spotter.md prose into
# tested code (scripts/trends/src/score.ts) behind a CLI seam. Two literals must hold,
# grepped EXACT (grep -F), deps-absent-safe (pure grep, no tsx):
# (1) score.ts encodes BOTH mode weight-sets (the 'kortform' + 'long-form' literals), so a
# silent collapse to one mode fails here (the per-mode arithmetic itself is unit-tested
# in score.test.ts, behind the deps guard / trends-suite floor);
# (2) agents/trend-spotter.md references the scorer CLI by the literal 'src/cli.ts score' —
# the lift is real and grep-able, not merely documented.
# Non-vacuity self-test mirrors Sections 16c-17: the weight-set predicate (AND of both mode
# literals) must accept a both-modes probe and reject single-mode probes; the wiring predicate
# must accept a probe carrying the scorer-pointer literal and reject one without it. Labelled
# 16g but placed after Section 17 / before Section 18 (anti-erosion must run last so it sees
# every prior check). UNCONDITIONAL (no tsx) -> counts toward ASSERT_BASELINE_FLOOR.
WEIGHT_KORT_LIT='kortform'
WEIGHT_LONG_LIT='long-form'
SCORER_WIRE_LIT='src/cli.ts score'
weights_both_modes() { # $1 = text; true iff BOTH mode literals present (echo twice — grep consumes stdin)
echo "$1" | grep -qF "$WEIGHT_KORT_LIT" && echo "$1" | grep -qF "$WEIGHT_LONG_LIT"
}
G16_SELFTEST_OK=1
if ! weights_both_modes 'the kortform weight-set and the long-form weight-set are both encoded'; then
G16_SELFTEST_OK=0; echo " non-vacuity FAIL: a both-modes weight probe was not detected"
fi
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if weights_both_modes "$probe"; then
G16_SELFTEST_OK=0; echo " false-positive FAIL: single-mode weight probe accepted -> $probe"
fi
done <<'NEGATIVE16G'
only the kortform weight-set is present here
only the long-form weight-set is present here
NEGATIVE16G
if ! echo 'pipe the scores to src/cli.ts score for the composite' | grep -qF "$SCORER_WIRE_LIT"; then
G16_SELFTEST_OK=0; echo " non-vacuity FAIL: a wired scorer-pointer probe was not detected"
fi
if echo 'the agent computes the composite itself' | grep -qF "$SCORER_WIRE_LIT"; then
G16_SELFTEST_OK=0; echo " false-positive FAIL: an unwired probe matched the scorer pointer"
fi
if [ "$G16_SELFTEST_OK" -eq 1 ]; then
pass "trends-scorer self-test: both-modes weight predicate + scorer-pointer predicate detect wiring, reject the under-wired forms"
else
fail "trends-scorer self-test failed — the scorer-wiring lint is vacuous or over-eager"
fi
SCORE_TS="scripts/trends/src/score.ts"
if grep -qF "$WEIGHT_KORT_LIT" "$SCORE_TS" 2>/dev/null && grep -qF "$WEIGHT_LONG_LIT" "$SCORE_TS" 2>/dev/null; then
pass "score.ts encodes both mode weight-sets ('$WEIGHT_KORT_LIT' + '$WEIGHT_LONG_LIT')"
else
fail "score.ts missing a mode weight-set — needs both '$WEIGHT_KORT_LIT' and '$WEIGHT_LONG_LIT' in $SCORE_TS"
fi
if grep -qF "$SCORER_WIRE_LIT" agents/trend-spotter.md; then
pass "trend-spotter.md references the scorer CLI ('$SCORER_WIRE_LIT') as the deterministic-step owner"
else
fail "trend-spotter.md does not reference the scorer CLI — add a '$SCORER_WIRE_LIT' pointer (RE-R1 lift)"
fi
echo ""
# --- Section 16h: Trends Capture Wiring (research-engine RE-R2a) ---
echo "--- Trends Capture Wiring ---"
# RE-R2a closes the capture loop: the item->store bridge (itemToInput) + a `capture` CLI that
# normalizes a raw batch from stdin and folds it into the store (persisting publishedAt). Two
# literals must hold, grepped EXACT (grep -F), deps-absent-safe (pure grep, no tsx):
# (1) cli.ts dispatches the `capture` subcommand (the handler exists), by the literal
# 'command === "capture"' (the capture path is real, not merely documented);
# (2) agents/trend-spotter.md re-points Step 4.5 to the capture CLI by the literal
# 'src/cli.ts capture' (one normalizing batch call, replacing the N x `add` block).
# Non-vacuity self-test mirrors Sections 16c-16g: the wiring predicate must accept a probe
# carrying the capture-pointer literal and reject one without it. Labelled 16h but placed after
# Section 17 / before Section 18 (anti-erosion must run last so it sees every prior check).
# UNCONDITIONAL (no tsx) -> counts toward ASSERT_BASELINE_FLOOR.
CAPTURE_HANDLER_LIT='command === "capture"'
CAPTURE_WIRE_LIT='src/cli.ts capture'
H16_SELFTEST_OK=1
if ! echo 'pipe the raw batch to src/cli.ts capture for the store fold' | grep -qF "$CAPTURE_WIRE_LIT"; then
H16_SELFTEST_OK=0; echo " non-vacuity FAIL: a wired capture-pointer probe was not detected"
fi
if echo 'the agent folds each trend into the store itself' | grep -qF "$CAPTURE_WIRE_LIT"; then
H16_SELFTEST_OK=0; echo " false-positive FAIL: an unwired probe matched the capture pointer"
fi
if [ "$H16_SELFTEST_OK" -eq 1 ]; then
pass "trends-capture self-test: capture-pointer predicate detects wiring, rejects the under-wired form"
else
fail "trends-capture self-test failed — the capture-wiring lint is vacuous or over-eager"
fi
if grep -qF "$CAPTURE_HANDLER_LIT" scripts/trends/src/cli.ts; then
pass "cli.ts dispatches the capture subcommand ('$CAPTURE_HANDLER_LIT')"
else
fail "cli.ts has no capture handler — add a '$CAPTURE_HANDLER_LIT' branch (RE-R2a capture loop)"
fi
if grep -qF "$CAPTURE_WIRE_LIT" agents/trend-spotter.md; then
pass "trend-spotter.md references the capture CLI ('$CAPTURE_WIRE_LIT') as the Step 4.5 store-fold owner"
else
fail "trend-spotter.md does not reference the capture CLI — re-point Step 4.5 to a '$CAPTURE_WIRE_LIT' batch call (RE-R2a wiring)"
fi
echo ""
# --- Section 16i: Trends Brief Wiring (research-engine RE-R2b) ---
echo "--- Trends Brief Wiring ---"
# RE-R2b makes the store VISIBLE: a `brief` CLI ranks the store by pillar-overlap + recency and
# writes a dated Markdown file the SessionStart hook surfaces. Three literals must hold, grepped
# EXACT (grep -F), deps-absent-safe (pure grep, no tsx):
# (1) cli.ts dispatches the `brief` subcommand, by the literal 'command === "brief"';
# (2) agents/trend-spotter.md generates the brief after capture, by the literal 'src/cli.ts brief';
# (3) hooks/scripts/session-start.mjs surfaces it, by the literal 'latestMorningBrief' (the
# surfacing is wired, not merely documented).
# Non-vacuity self-test mirrors Section 16h: the wiring predicate must accept a probe carrying the
# brief-pointer literal and reject one without it. Placed after Section 16h / before Section 18
# (anti-erosion must run last so it sees every prior check). UNCONDITIONAL (no tsx) -> counts
# toward ASSERT_BASELINE_FLOOR.
BRIEF_HANDLER_LIT='command === "brief"'
BRIEF_WIRE_LIT='src/cli.ts brief'
BRIEF_SURFACE_LIT='latestMorningBrief'
I16_SELFTEST_OK=1
if ! echo 'after capture, run src/cli.ts brief --pillars to write the dated brief' | grep -qF "$BRIEF_WIRE_LIT"; then
I16_SELFTEST_OK=0; echo " non-vacuity FAIL: a wired brief-pointer probe was not detected"
fi
if echo 'the agent renders the morning brief itself' | grep -qF "$BRIEF_WIRE_LIT"; then
I16_SELFTEST_OK=0; echo " false-positive FAIL: an unwired probe matched the brief pointer"
fi
if [ "$I16_SELFTEST_OK" -eq 1 ]; then
pass "trends-brief self-test: brief-pointer predicate detects wiring, rejects the under-wired form"
else
fail "trends-brief self-test failed — the brief-wiring lint is vacuous or over-eager"
fi
if grep -qF "$BRIEF_HANDLER_LIT" scripts/trends/src/cli.ts; then
pass "cli.ts dispatches the brief subcommand ('$BRIEF_HANDLER_LIT')"
else
fail "cli.ts has no brief handler — add a '$BRIEF_HANDLER_LIT' branch (RE-R2b morning brief)"
fi
if grep -qF "$BRIEF_WIRE_LIT" agents/trend-spotter.md; then
pass "trend-spotter.md references the brief CLI ('$BRIEF_WIRE_LIT') as the post-capture surfacing step"
else
fail "trend-spotter.md does not reference the brief CLI — wire Step 4.6 to a '$BRIEF_WIRE_LIT' call (RE-R2b)"
fi
if grep -qF "$BRIEF_SURFACE_LIT" hooks/scripts/session-start.mjs; then
pass "session-start.mjs surfaces the morning brief ('$BRIEF_SURFACE_LIT')"
else
fail "session-start.mjs does not surface the brief — add the '$BRIEF_SURFACE_LIT' reader (RE-R2b hull 4)"
fi
echo ""
# --- Section 16j: Trends Score Wiring (research-engine RE-R3a) ---
echo "--- Trends Score Wiring ---"
# RE-R3a persists the relevance score the trend-spotter agent already computes and ranks the
# morning brief on its composite. Four literals must hold, grepped EXACT (grep -F),
# deps-absent-safe (pure grep, no tsx):
# (1) score.ts exports the persist envelope, by the literal 'export interface TrendScore';
# (2) types.ts carries it on the record, by the literal 'score?: TrendScore';
# (3) agents/trend-spotter.md carries the judgment in the capture batch, by 'dimensions'
# (verified absent pre-R3a -> the grep is non-vacuous);
# (4) brief.ts ranks on the composite, by the literal 'score?.composite' (the payoff is wired,
# not merely doc'd).
# Non-vacuity self-test mirrors Section 16i: the rank predicate must accept a probe carrying the
# composite-rank literal and reject one without it. Placed after Section 16i / before Section 18
# (anti-erosion must run last so it sees every prior check). UNCONDITIONAL (no tsx) -> counts
# toward ASSERT_BASELINE_FLOOR.
SCORE_IFACE_LIT='export interface TrendScore'
SCORE_TYPE_LIT='score?: TrendScore'
SCORE_DIMS_LIT='"dimensions"'
SCORE_RANK_LIT='score?.composite'
I16J_SELFTEST_OK=1
if ! echo 'rankForBrief sorts on (b.trend.score?.composite ?? -1) first' | grep -qF "$SCORE_RANK_LIT"; then
I16J_SELFTEST_OK=0; echo " non-vacuity FAIL: a wired composite-rank probe was not detected"
fi
if echo 'the brief ranks on pillar overlap only' | grep -qF "$SCORE_RANK_LIT"; then
I16J_SELFTEST_OK=0; echo " false-positive FAIL: an unwired probe matched the composite-rank pointer"
fi
if [ "$I16J_SELFTEST_OK" -eq 1 ]; then
pass "trends-score self-test: composite-rank predicate detects wiring, rejects the under-wired form"
else
fail "trends-score self-test failed — the score-wiring lint is vacuous or over-eager"
fi
if grep -qF "$SCORE_IFACE_LIT" scripts/trends/src/score.ts; then
pass "score.ts exports the persist envelope ('$SCORE_IFACE_LIT')"
else
fail "score.ts has no TrendScore envelope — add '$SCORE_IFACE_LIT' (RE-R3a persist)"
fi
if grep -qF "$SCORE_TYPE_LIT" scripts/trends/src/types.ts; then
pass "types.ts carries the score on the record ('$SCORE_TYPE_LIT')"
else
fail "types.ts does not carry the score — add '$SCORE_TYPE_LIT' to TrendRecord (RE-R3a schema v3)"
fi
if grep -qF "$SCORE_DIMS_LIT" agents/trend-spotter.md; then
pass "trend-spotter.md carries the judgment in the capture batch ($SCORE_DIMS_LIT)"
else
fail "trend-spotter.md does not carry the judgment — add the per-item $SCORE_DIMS_LIT to Step 4.5 (RE-R3a wiring)"
fi
if grep -qF "$SCORE_RANK_LIT" scripts/trends/src/brief.ts; then
pass "brief.ts ranks on the composite ('$SCORE_RANK_LIT')"
else
fail "brief.ts does not rank on the composite — add the '$SCORE_RANK_LIT' comparator term (RE-R3a payoff)"
fi
echo ""
# --- Section 16k: Trends Lifecycle Wiring (research-engine RE-R3b) ---
echo "--- Trends Lifecycle Wiring ---"
# RE-R3b adds the trend lifecycle: re-score on re-capture, a status (new/acted/skipped) the brief
# EXCLUDES, and a seen-log (surfacedCount/lastSurfacedAt) the brief records. Five literals must hold,
# grepped EXACT (grep -F), deps-absent-safe (pure grep, no tsx):
# (1) types.ts declares the status type, by 'export type TrendStatus';
# (2) types.ts carries the seen-log field, by 'surfacedCount';
# (3) store.ts owns the seen-log writer, by 'export function markSurfaced';
# (4) brief.ts excludes handled trends, by 'effectiveStatus' (the filter is wired, not doc'd);
# (5) cli.ts exposes the lifecycle verb, by 'command === "act"'.
# Non-vacuity self-test mirrors Section 16j: the brief-filter predicate must accept a probe carrying
# the effectiveStatus pointer and reject one without it. Placed after Section 16j / before Section 18
# (anti-erosion must run last so it sees every prior check). UNCONDITIONAL (no tsx) -> counts toward
# ASSERT_BASELINE_FLOOR.
LIFECYCLE_STATUS_LIT='export type TrendStatus'
LIFECYCLE_SEEN_LIT='surfacedCount'
LIFECYCLE_MARK_LIT='export function markSurfaced'
LIFECYCLE_FILTER_LIT='effectiveStatus'
LIFECYCLE_VERB_LIT='command === "act"'
I16K_SELFTEST_OK=1
if ! echo 'rankForBrief drops a trend when effectiveStatus(trend) !== "new"' | grep -qF "$LIFECYCLE_FILTER_LIT"; then
I16K_SELFTEST_OK=0; echo " non-vacuity FAIL: a wired status-filter probe was not detected"
fi
if echo 'the brief shows every record regardless of status' | grep -qF "$LIFECYCLE_FILTER_LIT"; then
I16K_SELFTEST_OK=0; echo " false-positive FAIL: an unwired probe matched the status-filter pointer"
fi
if [ "$I16K_SELFTEST_OK" -eq 1 ]; then
pass "trends-lifecycle self-test: status-filter predicate detects wiring, rejects the unfiltered form"
else
fail "trends-lifecycle self-test failed — the lifecycle-wiring lint is vacuous or over-eager"
fi
if grep -qF "$LIFECYCLE_STATUS_LIT" scripts/trends/src/types.ts; then
pass "types.ts declares the lifecycle status type ('$LIFECYCLE_STATUS_LIT')"
else
fail "types.ts has no TrendStatus — add '$LIFECYCLE_STATUS_LIT' (RE-R3b lifecycle)"
fi
if grep -qF "$LIFECYCLE_SEEN_LIT" scripts/trends/src/types.ts; then
pass "types.ts carries the seen-log field ('$LIFECYCLE_SEEN_LIT')"
else
fail "types.ts does not carry the seen-log — add '$LIFECYCLE_SEEN_LIT' to TrendRecord (RE-R3b schema v4)"
fi
if grep -qF "$LIFECYCLE_MARK_LIT" scripts/trends/src/store.ts; then
pass "store.ts owns the seen-log writer ('$LIFECYCLE_MARK_LIT')"
else
fail "store.ts has no markSurfaced — add '$LIFECYCLE_MARK_LIT' (RE-R3b seen-log)"
fi
if grep -qF "$LIFECYCLE_FILTER_LIT" scripts/trends/src/brief.ts; then
pass "brief.ts excludes handled trends ('$LIFECYCLE_FILTER_LIT')"
else
fail "brief.ts does not exclude handled trends — wire '$LIFECYCLE_FILTER_LIT' into rankForBrief (RE-R3b)"
fi
if grep -qF "$LIFECYCLE_VERB_LIT" scripts/trends/src/cli.ts; then
pass "cli.ts exposes the lifecycle verb ('$LIFECYCLE_VERB_LIT')"
else
fail "cli.ts has no act/skip/reset — add '$LIFECYCLE_VERB_LIT' (RE-R3b lifecycle verbs)"
fi
echo ""
# --- Section 16l: Trends Scheduler / Headless Wiring (research-engine RE-R3c) ---
echo "--- Trends Scheduler / Headless Wiring ---"
# RE-R3c adds the autonomous trigger + headless entry: a `schedule` CLI verb emitting a launchd plist /
# cron line (print-first), and a bash wrapper that runs the DETERMINISTIC brief headless. Five literals
# must hold, grepped EXACT (grep -F), deps-absent-safe (pure grep, no tsx):
# (1) schedule.ts emits the launchd plist, by 'export function launchdPlist';
# (2) schedule.ts emits the cron line, by 'export function crontabLine';
# (3) cli.ts exposes the schedule verb, by 'command === "schedule"';
# (4) run-daily.sh runs the deterministic brief, by 'cli.ts" brief' (the wrapper owns the subcommand);
# (5) run-daily.sh uses the data-path twin seam, by 'LINKEDIN_STUDIO_DATA:-'.
# Non-vacuity self-test mirrors Section 16k: a probe carrying the launchdPlist pointer is accepted and
# one without it rejected. Placed after Section 16k / before Section 18 (anti-erosion must run last so it
# sees every prior check). UNCONDITIONAL (no tsx) -> counts toward ASSERT_BASELINE_FLOOR.
SCHED_PLIST_LIT='export function launchdPlist'
SCHED_CRON_LIT='export function crontabLine'
SCHED_VERB_LIT='command === "schedule"'
SCHED_BRIEF_LIT='cli.ts" brief'
SCHED_DATA_LIT='LINKEDIN_STUDIO_DATA:-'
I16L_SELFTEST_OK=1
if ! echo 'a wired scheduler declares: export function launchdPlist(spec)' | grep -qF "$SCHED_PLIST_LIT"; then
I16L_SELFTEST_OK=0; echo " non-vacuity FAIL: a wired scheduler-emit probe was not detected"
fi
if echo 'an unwired module emits no artifact at all' | grep -qF "$SCHED_PLIST_LIT"; then
I16L_SELFTEST_OK=0; echo " false-positive FAIL: an unwired probe matched the scheduler-emit pointer"
fi
if [ "$I16L_SELFTEST_OK" -eq 1 ]; then
pass "trends-scheduler self-test: the emit pointer is detected, the no-emit form rejected"
else
fail "trends-scheduler self-test failed — the scheduler-wiring lint is vacuous or over-eager"
fi
if grep -qF "$SCHED_PLIST_LIT" scripts/trends/src/schedule.ts; then
pass "schedule.ts emits the launchd plist ('$SCHED_PLIST_LIT')"
else
fail "schedule.ts has no launchd emitter — add '$SCHED_PLIST_LIT' (RE-R3c scheduler)"
fi
if grep -qF "$SCHED_CRON_LIT" scripts/trends/src/schedule.ts; then
pass "schedule.ts emits the cron line ('$SCHED_CRON_LIT')"
else
fail "schedule.ts has no cron-line emitter — add '$SCHED_CRON_LIT' (RE-R3c scheduler)"
fi
if grep -qF "$SCHED_VERB_LIT" scripts/trends/src/cli.ts; then
pass "cli.ts exposes the schedule verb ('$SCHED_VERB_LIT')"
else
fail "cli.ts has no schedule verb — add '$SCHED_VERB_LIT' (RE-R3c trigger)"
fi
if grep -qF "$SCHED_BRIEF_LIT" scripts/trends/run-daily.sh; then
pass "run-daily.sh runs the deterministic brief ('$SCHED_BRIEF_LIT')"
else
fail "run-daily.sh does not run the brief — wire '$SCHED_BRIEF_LIT' (RE-R3c headless entry)"
fi
if grep -qF "$SCHED_DATA_LIT" scripts/trends/run-daily.sh; then
pass "run-daily.sh uses the data-path twin seam ('$SCHED_DATA_LIT')"
else
fail "run-daily.sh has no data-path seam — add '$SCHED_DATA_LIT' (RE-R3c fourth twin)"
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
# may only grow (brief-reviewer assumption 3). History: 74 pre-M0; +1 for the SB-S0
# brain-suite floor (Section 16b) = 75; +3 for SB-S1's three UNCONDITIONAL Section-16c
# checks (published-only self-test + voice-trainer grep + contract-doc grep) = 78;
# +2 for SB-S3a's two UNCONDITIONAL Section-16d checks (profile-reader self-test +
# strategy-advisor wiring grep) = 80; +2 for SB-S3d's two UNCONDITIONAL Section-16e
# checks (ops-reader self-test + strategy-advisor ops-wiring grep) = 82; +2 for SB-S3e's
# two UNCONDITIONAL Section-16f checks (reconcile self-test + brain-CLI reconcile grep) = 84;
# +3 for RE-R1's three UNCONDITIONAL Section-16g checks (trends-scorer self-test + score.ts
# both-modes weight-set grep + trend-spotter scorer-pointer grep) = 87; +3 for RE-R2a's three
# UNCONDITIONAL Section-16h checks (trends-capture self-test + cli.ts capture-handler grep +
# trend-spotter capture-pointer grep) = 90; +4 for RE-R2b's four UNCONDITIONAL Section-16i checks
# (trends-brief self-test + cli.ts brief-handler grep + trend-spotter brief-pointer grep +
# session-start surfacing grep) = 94; +5 for RE-R3a's five UNCONDITIONAL Section-16j checks
# (trends-score self-test + score.ts TrendScore-iface grep + types.ts score-field grep +
# trend-spotter dimensions grep + brief.ts composite-rank grep) = 99; +6 for RE-R3b's six
# UNCONDITIONAL Section-16k checks (lifecycle self-test + types.ts TrendStatus grep + types.ts
# surfacedCount grep + store.ts markSurfaced grep + brief.ts effectiveStatus grep + cli.ts
# act-verb grep) = 105; +6 for RE-R3c's six UNCONDITIONAL Section-16l checks (scheduler self-test
# + schedule.ts launchdPlist grep + schedule.ts crontabLine grep + cli.ts schedule-verb grep +
# run-daily.sh brief-invocation grep + run-daily.sh data-twin grep) = 111.
# 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=111
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"
else
fail "assertion count $TOTAL_CHECKS < baseline floor $ASSERT_BASELINE_FLOOR — a check was silently removed"
fi
echo ""
# --- Summary ---
echo "================================================"
echo "RESULTS"
echo "================================================"
echo -e "${GREEN}Passed: $PASS${NC}"
echo -e "${RED}Failed: $FAIL${NC}"
echo -e "${YELLOW}Warnings: $WARN${NC}"
echo ""
if [ $FAIL -eq 0 ]; then
echo -e "${GREEN}All structural checks passed!${NC}"
exit 0
else
echo -e "${RED}$FAIL check(s) failed. Review above.${NC}"
exit 1
fi