page-foundry 2.9.1 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -30
- package/package.json +2 -2
- package/skills/page-foundry/README.md +3 -3
- package/skills/page-foundry/SKILL.md +234 -60
- package/skills/page-foundry/TESTS.md +57 -2
- package/skills/page-foundry/assets/brief-template.md +15 -1
- package/skills/page-foundry/references/archetypes.md +552 -105
- package/skills/page-foundry/references/conversion-rules.md +2 -2
- package/skills/page-foundry/references/design-direction.md +50 -5
- package/skills/page-foundry/references/handoff.md +208 -34
- package/skills/page-foundry/references/ship-gates.md +115 -27
- package/skills/page-foundry/references/voice.md +6 -1
- package/skills/page-foundry/scripts/run_audit.py +1162 -0
- package/skills/page-foundry/scripts/voice_scan.py +46 -0
- package/skills/page-foundry/tests/build_fixture.sh +214 -0
- package/skills/page-foundry/tests/run_audit_test.sh +92 -0
|
@@ -175,6 +175,50 @@ def scan_structural(copy):
|
|
|
175
175
|
return warns
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
KICKER_PAIR = re.compile(
|
|
179
|
+
# A label element directly before a section heading. The body group refuses
|
|
180
|
+
# to cross its own closing tag or any block/heading boundary, so a long
|
|
181
|
+
# paragraph sitting before the real kicker cannot backtrack-swallow it.
|
|
182
|
+
r"<(p|span|div|small)\b([^>]*)>"
|
|
183
|
+
r"((?:[^<]|<(?!/\1\s*>|/?(?:p|div|small|h[1-6])\b)[^>]*>)*)"
|
|
184
|
+
r"</\1\s*>\s*<h([234])\b[^>]*>(.*?)</h\4\s*>",
|
|
185
|
+
re.S | re.I)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def scan_html_kickers(raw):
|
|
189
|
+
"""Repeated section kickers, absorbed from impeccable's detector (rule
|
|
190
|
+
repeated-section-kickers) under the v3.0 capability-ownership split: a short
|
|
191
|
+
uppercase label element sitting directly before an h2/h3/h4, three or more
|
|
192
|
+
times on a page, is AI editorial scaffolding. Impeccable gates on computed
|
|
193
|
+
font-size and letter-spacing; a static scanner cannot, so this gates on the
|
|
194
|
+
source-level tells instead: literal ALL-CAPS text, an `uppercase` utility
|
|
195
|
+
class or inline transform, or an eyebrow/kicker class name. Pairs split by
|
|
196
|
+
an intermediate wrapper element are invisible to this regex; the design
|
|
197
|
+
detect gate (impeccable detect, when installed) sees those with real styles.
|
|
198
|
+
WARN-level, threshold 3 per page, matching the source rule's advisory
|
|
199
|
+
severity and its 'once is fine; the pattern is the tell' doctrine."""
|
|
200
|
+
candidates = []
|
|
201
|
+
for m in KICKER_PAIR.finditer(raw):
|
|
202
|
+
attrs = m.group(2).lower()
|
|
203
|
+
kicker = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", m.group(3))).strip()
|
|
204
|
+
heading = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", m.group(5))).strip()
|
|
205
|
+
if not (2 <= len(kicker) <= 34) or len(heading) < 3:
|
|
206
|
+
continue
|
|
207
|
+
if re.match(r"step\s*\d+", kicker, re.I) or re.fullmatch(r"\d{1,2}", kicker):
|
|
208
|
+
continue
|
|
209
|
+
allcaps = re.search(r"[A-Z]", kicker) and not re.search(r"[a-z]", kicker)
|
|
210
|
+
styled = "uppercase" in attrs or "eyebrow" in attrs or "kicker" in attrs
|
|
211
|
+
if not (allcaps or styled):
|
|
212
|
+
continue
|
|
213
|
+
line_no = raw.count("\n", 0, m.start()) + 1
|
|
214
|
+
candidates.append((line_no, kicker, "h" + m.group(4), heading))
|
|
215
|
+
if len(candidates) < 3:
|
|
216
|
+
return []
|
|
217
|
+
return [(ln, "repeated section kickers ('%s' before %s, %d on page)"
|
|
218
|
+
% (kick, tag, len(candidates)), head[:80])
|
|
219
|
+
for ln, kick, tag, head in candidates]
|
|
220
|
+
|
|
221
|
+
|
|
178
222
|
def scan_file(path, banned, judgment, patterns, cfg):
|
|
179
223
|
fails, warns = [], []
|
|
180
224
|
raw = path.read_text(encoding="utf-8", errors="replace")
|
|
@@ -196,6 +240,8 @@ def scan_file(path, banned, judgment, patterns, cfg):
|
|
|
196
240
|
if dash in line and cfg.get(key) != "off":
|
|
197
241
|
(fails if cfg.get(key) == "fail" else warns).append((i, label, ctx))
|
|
198
242
|
warns += scan_structural(copy)
|
|
243
|
+
if path.suffix.lower() in (".html", ".htm"):
|
|
244
|
+
warns += scan_html_kickers(raw)
|
|
199
245
|
bangs = copy.count("!")
|
|
200
246
|
if bangs > cfg.get("max_exclamations", 1):
|
|
201
247
|
fails.append((0, f"{bangs} exclamation points (max {cfg.get('max_exclamations', 1)} per page)", ""))
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Build calibration fixtures for run_audit.py. Idempotent.
|
|
3
|
+
set -e
|
|
4
|
+
ROOT="$1"; AUDIT="$2"
|
|
5
|
+
rm -rf "$ROOT"; mkdir -p "$ROOT"/.agents "$ROOT"/pages/demo
|
|
6
|
+
D="$ROOT/.agents/foundry/demo"
|
|
7
|
+
mkdir -p "$D"/copy "$D"/tokens "$D"/comp
|
|
8
|
+
|
|
9
|
+
cat > "$ROOT/.agents/product-marketing.md" <<'E'
|
|
10
|
+
# Product Marketing Context
|
|
11
|
+
**One-liner:** demo product for the solo builder shipping their own page.
|
|
12
|
+
## Target Audience
|
|
13
|
+
solo indie founders who ship many pages
|
|
14
|
+
## Problems & Pain Points
|
|
15
|
+
they ship a page and cannot tell why nobody signs up
|
|
16
|
+
## Differentiation
|
|
17
|
+
the spec is written before the design
|
|
18
|
+
E
|
|
19
|
+
|
|
20
|
+
cat > "$D/preflight.md" <<'E'
|
|
21
|
+
## Preflight — 2026-07-24
|
|
22
|
+
| companion | tier | state | resolved-via |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| product-marketing | core | present | npx skills list |
|
|
25
|
+
| customer-research | core | present | npx skills list |
|
|
26
|
+
| marketing-psychology | core | present | filesystem |
|
|
27
|
+
| cro | core | present | filesystem |
|
|
28
|
+
| copywriting | core | present | filesystem |
|
|
29
|
+
| frontend-design | core | present | npx skills list |
|
|
30
|
+
| humanizer | core | present | filesystem |
|
|
31
|
+
| impeccable | core | present | npx impeccable |
|
|
32
|
+
override: none
|
|
33
|
+
E
|
|
34
|
+
|
|
35
|
+
cat > "$D/voc.md" <<'E'
|
|
36
|
+
# voc.md
|
|
37
|
+
## Verbatim
|
|
38
|
+
> I can't tell what this does.
|
|
39
|
+
— Hacker News, https://news.ycombinator.com/item?id=42901780 · vunderba · 2025-02-01 · comprehension
|
|
40
|
+
> after 10 days, I have zero signups. Not even one.
|
|
41
|
+
— r/SaaS, https://www.reddit.com/r/SaaS/comments/1mf2dgx/ · SetDependent1293 · 2025-08-01 · no-signups
|
|
42
|
+
> your landing page contains a lot of AI generated metaphors
|
|
43
|
+
— Hacker News, https://news.ycombinator.com/item?id=46836627 · flexagoon · 2026-01-31 · slop
|
|
44
|
+
## Themes
|
|
45
|
+
1. comprehension failure (High, 3 sources)
|
|
46
|
+
2. zero signups, no diagnosis (High)
|
|
47
|
+
3. looks AI-made (Medium)
|
|
48
|
+
E
|
|
49
|
+
|
|
50
|
+
cat > "$D/persuasion-map.md" <<'E'
|
|
51
|
+
# Persuasion map
|
|
52
|
+
Levers chosen for a skeptical solo builder who reads before trusting.
|
|
53
|
+
## Curse of Knowledge (primary lever)
|
|
54
|
+
Lands on: the lead claim and the whole register. The buyer cannot read their own page cold.
|
|
55
|
+
## Pratfall effect
|
|
56
|
+
Lands on: the objection answers. Admit newness and slowness plainly.
|
|
57
|
+
## Jobs to be Done
|
|
58
|
+
Lands on: every capability line, framed as the outcome not the artifact.
|
|
59
|
+
## Levers not chosen
|
|
60
|
+
Social proof: out of scope, adoption is thin and stays TK.
|
|
61
|
+
E
|
|
62
|
+
|
|
63
|
+
cat > "$D/message-architecture.md" <<'E'
|
|
64
|
+
# Message architecture
|
|
65
|
+
Positioning: for the solo builder, demo is the thing that writes the spec, unlike a design tool that only renders what you hand it.
|
|
66
|
+
## Hierarchy
|
|
67
|
+
1. primary claim: the page argues a case
|
|
68
|
+
2. secondary claim: the argument traces to a real quote
|
|
69
|
+
3. tertiary claim: nothing is fabricated
|
|
70
|
+
## Objection map
|
|
71
|
+
O1: another AI builder. Answer: output is yours, no template.
|
|
72
|
+
O2: I can just prompt an agent. Answer: the gates are the repeatability.
|
|
73
|
+
O3: is it safe. Answer: one stdlib script, read it.
|
|
74
|
+
## Proof inventory
|
|
75
|
+
- real: the voice scanner, the run artifacts on disk
|
|
76
|
+
- TK: adoption count
|
|
77
|
+
E
|
|
78
|
+
|
|
79
|
+
cat > "$D/page-spec.md" <<'E'
|
|
80
|
+
# Page spec (oss-project)
|
|
81
|
+
archetype: oss-project
|
|
82
|
+
1. hero — primary claim — proof: the demo itself
|
|
83
|
+
2. problem — the comprehension failure — no proof needed
|
|
84
|
+
3. how it works — secondary claim — proof: the artifact chain
|
|
85
|
+
4. objections — O1 O2 O3 — proof beside each
|
|
86
|
+
5. install — CTA — the one command
|
|
87
|
+
Answer block: the head title and meta description answer the query intent.
|
|
88
|
+
Measurement plan: install count via the CLI, no third-party analytics.
|
|
89
|
+
E
|
|
90
|
+
|
|
91
|
+
cat > "$D/copy/hero-candidates.md" <<'E'
|
|
92
|
+
# Hero candidates
|
|
93
|
+
1. One brief in, a page that argues its case out. — score 8, message match high
|
|
94
|
+
2. You already know what an AI page looks like. — score 9, specificity high
|
|
95
|
+
3. The spec your page never got. — score 7, budget ok
|
|
96
|
+
Pick: candidate 2.
|
|
97
|
+
E
|
|
98
|
+
|
|
99
|
+
cat > "$D/copy/approved-draft.md" <<'E'
|
|
100
|
+
**`<title>`:** demo: the spec your page never got
|
|
101
|
+
**meta description:** demo writes the marketing spec before anything gets designed.
|
|
102
|
+
# Copy (page order)
|
|
103
|
+
Hero: You already know what an AI page looks like.
|
|
104
|
+
Problem: nobody can tell what it does.
|
|
105
|
+
How: it writes the spec first, then builds from it.
|
|
106
|
+
E
|
|
107
|
+
|
|
108
|
+
cat > "$D/copy/copy-approved.md" <<'E'
|
|
109
|
+
**`<title>`:** demo: the spec your page never got
|
|
110
|
+
**meta description:** demo writes the marketing spec before anything gets designed.
|
|
111
|
+
# Copy (frozen snapshot, page order)
|
|
112
|
+
Hero: You already know what an AI page looks like.
|
|
113
|
+
Problem: nobody can tell what it does.
|
|
114
|
+
How: it writes the spec first, then builds from it.
|
|
115
|
+
Install: one command.
|
|
116
|
+
E
|
|
117
|
+
|
|
118
|
+
cat > "$D/imagery-plan.md" <<'E'
|
|
119
|
+
# Imagery plan
|
|
120
|
+
Consistency rules: one grade (cool, high-contrast), 16:9 crops, hairline frames.
|
|
121
|
+
- hero: real product screenshot, browser chrome cropped out, 16:9, cool grade.
|
|
122
|
+
- proof: no image by choice; the gate report card carries the visual weight.
|
|
123
|
+
- how-it-works: a diagram of the real component flow, on-token line weights.
|
|
124
|
+
Every image traces to a slot here; no product screenshot is a mockup.
|
|
125
|
+
E
|
|
126
|
+
|
|
127
|
+
cat > "$D/states-motion.md" <<'E'
|
|
128
|
+
# States and motion
|
|
129
|
+
- form: error state = red hairline + aria-live message under the field; success =
|
|
130
|
+
inline confirmation, no redirect. loading = disabled button with a spinner glyph.
|
|
131
|
+
- copy button: copied state swaps label to "Copied" for 1.4s.
|
|
132
|
+
- focus: 2px ring, brand color, offset 2px, :focus-visible only.
|
|
133
|
+
- hover: 120ms ease on transform/opacity only; nothing hover-only.
|
|
134
|
+
- motion identity: one scroll-reveal on section entry, 200ms ease-out;
|
|
135
|
+
prefers-reduced-motion disables it and shows the static end state.
|
|
136
|
+
E
|
|
137
|
+
|
|
138
|
+
cat > "$D/tokens/plan-a.md" <<'E'
|
|
139
|
+
# Plan A — instrument dark
|
|
140
|
+
brand #ff3b1d on #0a0a0a, mono display, tight grid
|
|
141
|
+
E
|
|
142
|
+
cat > "$D/tokens/plan-b.md" <<'E'
|
|
143
|
+
# Plan B — paper light
|
|
144
|
+
brand #0a0a0a on #ffffff, serif display, wide grid
|
|
145
|
+
E
|
|
146
|
+
cat > "$D/tokens/selection.md" <<'E'
|
|
147
|
+
Winner: plan-a. Why: the buyer reads at night in a terminal; instrument dark matches the register.
|
|
148
|
+
E
|
|
149
|
+
|
|
150
|
+
cat > "$D/comp/index.html" <<'E'
|
|
151
|
+
<!doctype html><html><head><title>comp</title></head>
|
|
152
|
+
<body><h1>You already know what an AI page looks like.</h1>
|
|
153
|
+
<p>nobody can tell what it does.</p></body></html>
|
|
154
|
+
E
|
|
155
|
+
cat > "$D/comp/rounds.md" <<'E'
|
|
156
|
+
Round 1: hero ran too tall at 1440, subhead lost. Fixed the scale.
|
|
157
|
+
Round 2: 390 wrap held, density confirmed. Approved.
|
|
158
|
+
E
|
|
159
|
+
|
|
160
|
+
cat > "$ROOT/pages/demo/index.html" <<'E'
|
|
161
|
+
<!doctype html><html><head><title>demo: the spec your page never got</title>
|
|
162
|
+
<meta name="description" content="demo writes the marketing spec before anything gets designed."></head>
|
|
163
|
+
<body><h1>You already know what an AI page looks like.</h1>
|
|
164
|
+
<p>nobody can tell what it does. demo writes the spec first, then builds from it.</p></body></html>
|
|
165
|
+
E
|
|
166
|
+
cat > "$ROOT/pages/demo/theme.css" <<'E'
|
|
167
|
+
:root{--brand:#ff3b1d;--bg:#0a0a0a}
|
|
168
|
+
E
|
|
169
|
+
cat > "$ROOT/pages/demo/llms.txt" <<'E'
|
|
170
|
+
# demo
|
|
171
|
+
The spec your page never got. Open source, MIT.
|
|
172
|
+
E
|
|
173
|
+
|
|
174
|
+
cat > "$ROOT/DESIGN.md" <<'E'
|
|
175
|
+
---
|
|
176
|
+
name: demo
|
|
177
|
+
version: alpha
|
|
178
|
+
---
|
|
179
|
+
## Brand
|
|
180
|
+
brand #ff3b1d on #0a0a0a; the signature is a hairline rule under the eyebrow.
|
|
181
|
+
## Type
|
|
182
|
+
display: mono, tight tracking; body: system sans, 1.6 line height.
|
|
183
|
+
## Color
|
|
184
|
+
--brand #ff3b1d; --bg #0a0a0a; --fg #f4f4f4; --muted #8a8a8a.
|
|
185
|
+
## Layout
|
|
186
|
+
container max 72ch; single column mobile, 12-col at 1024; 24px gutter.
|
|
187
|
+
## Motion
|
|
188
|
+
one scroll-reveal, 200ms ease-out; reduced-motion disables it.
|
|
189
|
+
E
|
|
190
|
+
|
|
191
|
+
cat > "$D/conversion-audit.md" <<'E'
|
|
192
|
+
# Gate 1 conversion audit
|
|
193
|
+
The heuristic scored twice: the builder self-score from full context, then the
|
|
194
|
+
score of record from a fresh agent given only the page and the brief.
|
|
195
|
+
Self score: 31. Independent score: 27. Divergence: 4 points, concentrated on the
|
|
196
|
+
install CTA which is the strongest lever and currently thinnest.
|
|
197
|
+
E
|
|
198
|
+
|
|
199
|
+
cat > "$D/foundry-log.md" <<'E'
|
|
200
|
+
# foundry-log.md: demo
|
|
201
|
+
Per-property memory. Phase 6 appends after every run.
|
|
202
|
+
|
|
203
|
+
### 2026-07-24 · build · oss-project
|
|
204
|
+
- headline: You already know what an AI page looks like.
|
|
205
|
+
- skeleton: hero, problem, how, objections, install · direct/static-hero/proof-adjacent/compressed
|
|
206
|
+
- meclabs: C=27 (M4 V3 I2 F3 A2)
|
|
207
|
+
- gates: all pass
|
|
208
|
+
- companions: product-marketing, customer-research, marketing-psychology, cro, copywriting, frontend-design
|
|
209
|
+
- degraded: none
|
|
210
|
+
- open items: adoption count TK
|
|
211
|
+
- conversion data: none exists yet
|
|
212
|
+
- learnings: the cold re-score caught the CTA thinness
|
|
213
|
+
E
|
|
214
|
+
echo "fixture built at $ROOT"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Calibration regression for run_audit.py (issue #38).
|
|
3
|
+
# Builds fixtures and asserts exit codes. No network, no deps.
|
|
4
|
+
# Run from anywhere: bash skills/page-foundry/tests/run_audit_test.sh
|
|
5
|
+
set -u
|
|
6
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
AUDIT="$HERE/../scripts/run_audit.py"
|
|
8
|
+
BUILD="$HERE/build_fixture.sh"
|
|
9
|
+
TMP="$(mktemp -d)"
|
|
10
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
11
|
+
fails=0
|
|
12
|
+
|
|
13
|
+
assert_exit() { # name expected dir extra...
|
|
14
|
+
local name="$1" expected="$2" dir="$3"; shift 3
|
|
15
|
+
python3 "$AUDIT" --mode build --archetype oss-project "$@" "$dir" >/dev/null 2>&1
|
|
16
|
+
local got=$?
|
|
17
|
+
if [ "$got" -eq "$expected" ]; then
|
|
18
|
+
echo " ok $name (exit $got)"
|
|
19
|
+
else
|
|
20
|
+
echo " FAIL $name (got $got, want $expected)"; fails=$((fails+1))
|
|
21
|
+
fi
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
bash "$BUILD" "$TMP/clean" "$AUDIT" >/dev/null
|
|
25
|
+
assert_exit "A: complete valid run passes" 0 "$TMP/clean/.agents/foundry/demo"
|
|
26
|
+
|
|
27
|
+
cp -r "$TMP/clean" "$TMP/missing"
|
|
28
|
+
rm "$TMP/missing/.agents/foundry/demo/message-architecture.md"
|
|
29
|
+
assert_exit "B: missing required artifact fails" 1 "$TMP/missing/.agents/foundry/demo"
|
|
30
|
+
|
|
31
|
+
cp -r "$TMP/clean" "$TMP/badlog"
|
|
32
|
+
printf '# log\n\n## Run 1 (started 2026-07-24)\n- headline: x\n' > "$TMP/badlog/.agents/foundry/demo/foundry-log.md"
|
|
33
|
+
assert_exit "C: foundry-log off documented format fails" 1 "$TMP/badlog/.agents/foundry/demo"
|
|
34
|
+
|
|
35
|
+
cp -r "$TMP/clean" "$TMP/tk"
|
|
36
|
+
sed -i.bak 's/one command\./[TK: the install command]/' "$TMP/tk/.agents/foundry/demo/copy/copy-approved.md" && rm -f "$TMP/tk/.agents/foundry/demo/copy/copy-approved.md.bak"
|
|
37
|
+
assert_exit "D: unresolved [TK in copy fails" 1 "$TMP/tk/.agents/foundry/demo"
|
|
38
|
+
|
|
39
|
+
cp -r "$TMP/clean" "$TMP/stale"; sleep 1; touch "$TMP/stale/pages/demo/index.html"
|
|
40
|
+
assert_exit "E: mtime inversion is advisory (WARN), still passes" 0 "$TMP/stale/.agents/foundry/demo"
|
|
41
|
+
|
|
42
|
+
# F: a core companion overridden at preflight may skip its artifact (honest PARTIAL).
|
|
43
|
+
cp -r "$TMP/clean" "$TMP/override"
|
|
44
|
+
D="$TMP/override/.agents/foundry/demo"
|
|
45
|
+
rm "$D/persuasion-map.md"
|
|
46
|
+
python3 - "$D/preflight.md" <<'PY'
|
|
47
|
+
import sys
|
|
48
|
+
p = sys.argv[1]; t = open(p).read()
|
|
49
|
+
t = t.replace("| marketing-psychology | core | present | filesystem |",
|
|
50
|
+
"| marketing-psychology | core | missing | — |")
|
|
51
|
+
t = t.replace("override: none", "override: marketing-psychology")
|
|
52
|
+
open(p, "w").write(t)
|
|
53
|
+
PY
|
|
54
|
+
assert_exit "F: overridden companion may skip its artifact" 0 "$D"
|
|
55
|
+
|
|
56
|
+
# G: a degraded claim naming a PRESENT, un-overridden companion fails.
|
|
57
|
+
cp -r "$TMP/clean" "$TMP/lie"
|
|
58
|
+
D="$TMP/lie/.agents/foundry/demo"
|
|
59
|
+
python3 - "$D/foundry-log.md" <<'PY'
|
|
60
|
+
import sys
|
|
61
|
+
p = sys.argv[1]; t = open(p).read()
|
|
62
|
+
open(p, "w").write(t.replace("- degraded: none",
|
|
63
|
+
"- degraded: cro missing, ran on the reference file"))
|
|
64
|
+
PY
|
|
65
|
+
assert_exit "G: false degraded claim (companion was present) fails" 1 "$D"
|
|
66
|
+
|
|
67
|
+
# H: a missing Phase 4 design output (imagery-plan/states-motion) fails.
|
|
68
|
+
cp -r "$TMP/clean" "$TMP/noimg"
|
|
69
|
+
rm "$TMP/noimg/.agents/foundry/demo/imagery-plan.md"
|
|
70
|
+
assert_exit "H: missing imagery-plan.md fails" 1 "$TMP/noimg/.agents/foundry/demo"
|
|
71
|
+
|
|
72
|
+
# I: impeccable is core — a missing DESIGN.md fails.
|
|
73
|
+
cp -r "$TMP/clean" "$TMP/nodesign"
|
|
74
|
+
rm "$TMP/nodesign/DESIGN.md"
|
|
75
|
+
assert_exit "I: missing DESIGN.md (impeccable core) fails" 1 "$TMP/nodesign/.agents/foundry/demo"
|
|
76
|
+
|
|
77
|
+
# J: impeccable overridden at preflight -> DESIGN.md may be absent (honest PARTIAL).
|
|
78
|
+
cp -r "$TMP/clean" "$TMP/impoverride"
|
|
79
|
+
rm "$TMP/impoverride/DESIGN.md"
|
|
80
|
+
python3 - "$TMP/impoverride/.agents/foundry/demo/preflight.md" <<'PY'
|
|
81
|
+
import sys
|
|
82
|
+
p = sys.argv[1]; t = open(p).read()
|
|
83
|
+
t = t.replace("| impeccable | core | present | npx impeccable |",
|
|
84
|
+
"| impeccable | core | missing | — |")
|
|
85
|
+
t = t.replace("override: none", "override: impeccable")
|
|
86
|
+
open(p, "w").write(t)
|
|
87
|
+
PY
|
|
88
|
+
assert_exit "J: impeccable overridden -> DESIGN.md absence is advisory" 0 "$TMP/impoverride/.agents/foundry/demo"
|
|
89
|
+
|
|
90
|
+
echo ""
|
|
91
|
+
if [ "$fails" -eq 0 ]; then echo "run_audit calibration: all fixtures pass"; else echo "run_audit calibration: $fails FAILED"; fi
|
|
92
|
+
exit $fails
|