Skip to content

fix(blog): audit archive editorial quality#39

Merged
drewstone merged 4 commits into
masterfrom
codex/blog-editorial-audit
Jul 1, 2026
Merged

fix(blog): audit archive editorial quality#39
drewstone merged 4 commits into
masterfrom
codex/blog-editorial-audit

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Summary\n- extend the blog audit script with all-post editorial scoring and reproducible report output\n- add body artifacts, FAQ extraction headings, source links, and internal reader paths across the flagged posts\n- remove template headings and close proof gaps caught by the stricter post checker\n\n## Verification\n- pnpm check:blog\n- pnpm check:blog:report\n- pnpm build\n- node .codex/skills/tangle-blog-proof/scripts/check-post.mjs on each edited blog post\n- git merge-tree --write-tree origin/master HEAD

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 32cab52b

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-01T20:08:50Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Value Audit — sound-with-nits

Verdict sound-with-nits
Concerns 3 (1 medium-concern, 2 low)
Heuristic 0.0s
Duplication 0.0s
Interrogation 79.3s (2 bridge agents)
Total 79.3s

💰 Value — sound-with-nits

Extends the archive-wide blog audit with per-post editorial scoring and a Markdown report, then fixes 17 posts to add FAQ extraction headings, source links, tables, and remove generic template headings; the content work is coherent and the tooling works, but it duplicates validation logic already pr

  • What it does: Adds a per-post auditPost function in scripts/audit-blog.mjs that scores every MDX post against heuristics for length, FAQ presence, internal/external links, artifacts, banned phrases, template headings, reader-pressure openings, Tangle mechanism placement, limitations, and CTA closings. It emits a 0-100 score, a categorized reasons list, and a new --report Markdown output (`pnpm check:blo
  • Goals it achieves: Make the blog archive editorially consistent and discoverable by search/agents; ensure every post carries proof, clear next steps, limitations, and machine-extractable FAQ structure; give editors a reproducible priority list for which posts need attention; and close the gaps flagged by the stricter post checker and proof checklist.
  • Assessment: Good change. The scoring gives a concrete, reproducible editorial pulse across 80 posts, and the content edits are substantive improvements (e.g., FAQ headings for JSON-LD, source links for verifiability, decision tables for deployment targets, removal of generic section headings). The audit passes (pnpm check:blog -> PASS) and the report is generated correctly. The frontmatter parser refactor i
  • Better / existing approach: The new scripts/audit-blog.mjs reinvents several checks that already exist in .codex/skills/tangle-blog-proof/scripts/check-post.mjs: required frontmatter fields, internal/external link counting, banned phrase detection, FAQ ### question-heading checks, code block / proof detection, and image reference validation. A shared scripts/lib/blog-validation.mjs module (or similar) exporting front
  • Model: opencode/kimi-for-coding/k2p7
  • Bridge attempts: 1

🎯 Usefulness — sound

Extends the existing archive-wide blog audit with per-post editorial scoring + a reproducible markdown report, and applies those gates as real content fixes across 18 posts; works as intended and fits the established pattern.

  • Integration: Wired in correctly. check:blog:report is added to package.json:18 alongside the existing check:blog. The report target audit-results/blog-editorial-audit.md lands in a dir that is already gitignored (.gitignore line: audit-results/) and already used as the regenerable-artifact convention by scripts/visual-audit.mjs:40 and scripts/bad-batch.sh. Verified `node scripts/audit-blog.mjs --
  • Fit with existing patterns: Extends the existing scripts/audit-blog.mjs in-place rather than forking it (templateHeadings, tangleMechanisms, auditPost, renderReport are all added to the same module). Complementary to — not competing with — .codex/skills/tangle-blog-proof/scripts/check-post.mjs: that one is a per-post hard publish gate (fails on missing proof blocks), while this is an archive-wide editorial scorer with su
  • Real-world viability: Holds up. Ran both modes against all 80 posts: clean exit, correct counts (0 errors / 0 warnings / 26 notices / 12 series), report renders with sane per-post scoring (lowest 89/100, most at 92-100). parseFrontmatter defaults to {} on missing frontmatter so data.title/data.tags degrade gracefully via the asArray/basename fallbacks. The --report flag-parsing is safe when the path arg is
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

🔎 Heuristic Signals

🟡 Cruft: console debug added scripts/audit-blog.mjs

  • console.log(Blog audit: ${posts.length} posts, ${Object.keys(summary.series).length} series, ${summary.errors} errors, ${summary.warnings} warnings)

🟡 Cruft: magic number added scripts/audit-blog.mjs

  • if (!hasLimitations && bodyWordCount > 1000) {

💰 Value Audit

🟠 audit-blog.mjs duplicates core checks from tangle-blog-proof/check-post.mjs [duplication] ``

Evidence: scripts/audit-blog.mjs lines 220-228 (required frontmatter), 240-248 (external links), 250-258 (internal links), 260-278 (artifacts / code blocks), 280-302 (template/repeated headings), 304-315 (banned phrases), 483-486 (FAQ ### headings), and 470-474 (image existence) all overlap with .codex/skills/tangle-blog-proof/scripts/check-post.mjs lines 31-37, 49-50, 64-84, 90-109, 111-126, and the proof checklist in .codex/skills/tangle-blog-proof/references/proof-checklist.md. The two


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260701T201217Z

@tangletools

Copy link
Copy Markdown
Contributor

✅ No Blockers — 32cab52b

Review health 100/100 · Reviewer score 36/100 · Confidence 75/100 · 15 findings (7 medium, 8 low)

deepseek kimi-code aggregate
Readiness 60 36 36
Confidence 75 75 75
Correctness 60 36 36
Security 60 36 36
Testing 60 36 36
Architecture 60 36 36

Reviewer score is advisory once the run is complete and the verdict has no blockers.

Full multi-shot audit completed 3/3 planned shots over 19 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 3/3 planned shots over 19 changed files. Global verifier still owns final merge decision.

🟠 MEDIUM --report parser treats the next flag as the output path — scripts/audit-blog.mjs

const reportPath = reportIndex >= 0 ? args[reportIndex + 1] : null does not validate that the following argument is actually a path. Running node scripts/audit-blog.mjs --report --json creates a file literally named --json in the repo root. Fix by checking that the candidate path exists and does not start with -, or by requiring --report=<path> syntax.

🟠 MEDIUM Code-block count can be non-integer on unmatched fences — scripts/audit-blog.mjs

const codeBlockCount = countMatches(body, /```/g) / 2 assumes every opening fence has a matching closing fence and ignores nested fences. An unmatched or nested fence yields a fractional count (e.g. 2.5), which leaks into the report and could affect the codeBlockCount >= 5 heuristic. Fix by counting complete fenced blocks, e.g. /^```\S*$/gm, or flooring the result.

🟠 MEDIUM summary.posts changed from number to Array — breaks JSON output contract — scripts/audit-blog.mjs

Old code set summary.posts = postFiles.length (a number). New code sets summary.posts = posts (an array of post objects). Any script/CI consuming --json output and reading .posts as a count will break. Verified: no in-repo consumers use --json (check:blog uses exit code; check:blog:report uses --report). Still a contract change that should be documented or reverted. renderReport at line 401 already uses summary.posts.length internally, confirming the new array type.

🟠 MEDIUM Broken RFC 8446 external link in new FAQ — src/content/blog/blueprint-deployment-architecture-remote-providers.mdx

New FAQ links to https://www.rfc-editor.org/rfc/rfc8446 for TLS client authentication guidance. That URL returns HTTP 404. Use the working datatracker URL (https://datatracker.ietf.org/doc/html/rfc8446) or the .txt RFC-Editor variant instead.

🟠 MEDIUM Live curl command points to non-existent endpoint (HTTP 404) — src/content/blog/why-ai-infrastructure-needs-decentralization.mdx

The added code block includes curl -fsS https://tangle.tools/.well-known/ai-plugin.json which returns HTTP 404. The post presents these three curl commands as proof that 'the product surface is machine-discoverable' but the first call fails. Verified: curl -sI https://tangle.tools/.well-known/ai-plugin.json returns 404. Impact: undermines the credibility of the machine-discoverability claim. Fix: either deploy the .well-known endpoint on tangle.tools before publishing, or remove/replace this curl command with one that resolves successfully (router and sandbox endpoints both return 200).

🟠 MEDIUM Public discovery curl example points to a 404 endpoint — src/content/blog/why-ai-infrastructure-needs-decentralization.mdx

The new paragraph claims curl -fsS https://tangle.tools/.well-known/ai-plugin.json proves the product surface is machine-discoverable, but the endpoint currently returns HTTP 404. Either the endpoint should be deployed before this post ships or the example should be removed/replaced with a working discovery URL.

🟠 MEDIUM Production checklist example uses unresolvable facilitator URL — src/content/blog/x402-blueprint-production-deployment-checklist.mdx

The example x402.toml sets facilitator_url = "https://facilitator.x402.org", which does not resolve (NXDOMAIN). The decentralizing-x402-facilitator post and x402 reference use facilitator.x402.rs (200 OK). Operators copying this config will hit DNS failures at runtime. Reconcile all facilitator references to the working domain.

🟡 LOW Breaking change in audit-blog.mjs JSON output structure — scripts/audit-blog.mjs

The --json output changed summary.posts from a number (postFiles.length) to an array of audit objects. Any downstream automation consuming node scripts/audit-blog.mjs --json will receive a different data type. This file is outside the shot scope but noted since the script change is part of this PR.

🟡 LOW Each post file is read twice — duplicate I/O — scripts/audit-blog.mjs

auditPost at line 448 reads every file via map. The main loop at line 455 iterates the same postFiles array and reads each file again. This doubles disk I/O. On a large blog directory with hundreds of posts, this adds measurable latency. The main loop could reuse the parsed {body, data} from auditPost's return value instead.

🟡 LOW Frontmatter delimiter regex requires Unix newlines — scripts/audit-blog.mjs

text.match(/^---\n([\s\S]*?)\n---\n/) requires \n before and after the closing ---. A file with CRLF line endings will fail to parse frontmatter and be treated as body-only, which would silently skip required-field checks on Windows. Consider normalizing \r\n to \n before matching.

🟡 LOW Frontmatter inline-array parser is comma-naive — scripts/audit-blog.mjs

cleanValue splits inline array values on every comma before unquoting, so a value like tags: ["a, b", c] is parsed as ['a', 'b', 'c'] instead of ['a, b', 'c']. The repo currently uses YAML list syntax, but the inline-array path is fragile. Consider using a small YAML parser or only supporting the newline - list form.

🟡 LOW Heuristic findings lose line numbers — scripts/audit-blog.mjs

The second pass copies post.reasons into findings with addFinding(findings, 'warning', file, reason.reason) but no longer passes needle/text, so every derived warning is reported at line 1. The original code passed the matched heading/phrase to lineOf. Restore the needle so editors/CI can jump to the issue.

🟡 LOW No per-post error isolation — one bad file crashes the audit — scripts/audit-blog.mjs

postFiles.map(auditPost) has no try-catch. If any single .mdx file has malformed content or triggers an unexpected error in auditPost, the entire audit crashes. Wrapping the map in a per-file try-catch would let the audit continue over the remaining posts and report the failed file.

🟡 LOW filter(Boolean) in cleanValue drops falsy array elements (0, false) — scripts/audit-blog.mjs

cleanValue converts '0' to Number(0) (line 92), then .filter(Boolean) on line 80 drops it because 0 is falsy. Same for false. Old code kept '0' as the truthy string '0' via its simpler quote-stripping cleanValue. In practice, blog frontmatter arrays (tags, etc.) never contain 0 or false, so impact is negligible. Still a correctness regression worth noting.

🟡 LOW Broken external link to RFC 8446 — src/content/blog/blueprint-deployment-architecture-remote-providers.mdx

The link https://www.rfc-editor.org/rfc/rfc8446 returns HTTP 404. The canonical working URL is https://datatracker.ietf.org/doc/html/rfc8446 (HTTP 200 verified). Fix: replace the RFC Editor URL with the IETF Datatracker URL.


tangletools · 2026-07-01T20:18:34Z · trace

tangletools
tangletools previously approved these changes Jul 1, 2026

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved — 15 non-blocking findings — 32cab52b

Full multi-shot audit completed 3/3 planned shots over 19 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 3/3 planned shots over 19 changed files. Global verifier still owns final merge decision.

Full immutable report for this review: trace

Summary comment for this run: full summary


tangletools · 2026-07-01T20:18:34Z · immutable trace

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 70953efc

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-01T20:44:48Z

@tangletools

Copy link
Copy Markdown
Contributor

❌ Needs Work — 70953efc

Review health 100/100 · Reviewer score 0/100 · Confidence 80/100 · 20 findings (3 high, 4 medium, 13 low)

deepseek kimi-code aggregate
Readiness 63 0 0
Confidence 80 80 80
Correctness 63 0 0
Security 63 0 0
Testing 63 0 0
Architecture 63 0 0

Reviewer score is advisory once the run is complete and the verdict has no blockers.

Full multi-shot audit completed 4/4 planned shots over 22 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 4/4 planned shots over 22 changed files. Global verifier still owns final merge decision.

Blocking

🔴 HIGH Facilitator URL is wrong and unresolvable — src/content/blog/blueprint-sdk-x402-payments-runnable-jobs.mdx

The post states the default facilitator is https://facilitator.x402.org, but facilitator.x402.org does not resolve (DNS failure). The live public facilitator is https://facilitator.x402.rs (returns 200 on /v2/x402/supported). Readers copying this URL into config will get a non-functional gateway. Change to https://facilitator.x402.rs.

🔴 HIGH Discovery-surface curl example includes a 404 endpoint — src/content/blog/why-ai-infrastructure-needs-decentralization.mdx

The post adds a claim that the public discovery surface is machine-inspectable and lists three curl commands. https://tangle.tools/.well-known/ai-plugin.json currently returns HTTP 404, while the other two return 200. The prose says these calls prove the paths are concrete enough for agents to inspect; a 404 undermines that claim. Either publish the ai-plugin.json endpoint, remove it from the example, or qualify the statement so it does not assert all three are live.

🔴 HIGH Deployment checklist examples use unresolvable facilitator URL — src/content/blog/x402-blueprint-production-deployment-checklist.mdx

All three x402.toml examples (dev, staging, production) set facilitator_url = "https://facilitator.x402.org". That host does not resolve; the production facilitator is facilitator.x402.rs. Because these are meant to be copied verbatim, this will break staging/production payment flows. Update every occurrence to https://facilitator.x402.rs.

Other

🟠 MEDIUM Unsafe access on potentially-undefined post from posts.find()scripts/audit-blog.mjs

const post = posts.find((item) => item.file === relativeFile(file)) may return undefined if path resolution between auditPost and the legacy loop ever diverges (e.g., symlinks, differing process.cwd() between calls, or code refactoring). The next line accesses post.reasons unconditionally, which would throw a TypeError and crash the script. Currently unlikely to trigger because both loops use the same root variable and postFiles is generated once, but the dependency is fragile. Fix: if (!post) { addFinding(...); continue; } before dereferencing.

🟠 MEDIUM Deployment target table conflates DeploymentTarget and CloudProvider — src/content/blog/blueprint-deployment-architecture-remote-providers.mdx

The table lists DockerLocal and BareMetal as deployment targets, but the DeploymentTarget enum shown earlier in the same post only defines VirtualMachine, ManagedKubernetes, GenericKubernetes, and Serverless. DockerLocal and BareMetal are CloudProvider variants, not DeploymentTarget variants. The table misrepresents the SDK type hierarchy and will confuse readers mapping text to code. Rename the table column/header to clarify provider-vs-target, or remove the entries that are not DeploymentTarget variants.

🟠 MEDIUM Series slugifier can produce empty slugs for non-Latin names — src/pages/blog/series/[slug].astro

The slug function uses .replace(/[^a-z0-9]+/g, '-') after lowercasing and replacing '&'. This strips all non-ASCII characters. The blog schema allows series: z.string().optional() with no character/ASCII restriction, so a future series name composed solely of non-Latin characters (e.g., emoji, CJK) would slugify to an empty string. Astro will reject an empty [slug] param in getStaticPaths, breaking the build and any matching index links. Evidence: current 11 series slugify safely, but the function is not defensive. Fix: centralize a single slug utility and guard against empty output (e.g., fall back to a hash or reject at content-validation time).

🟠 MEDIUM duplicate slug logic: seriesSlug() defined but not used in getStaticPaths — src/pages/blog/series/[slug].astro

seriesSlug() is defined at line 10 but getStaticPaths() (lines 44-49) inlines the identical .toLowerCase().replace(/&/g,'and').replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'') slug generation. The blog index at src/pages/blog/index.astro:98 correctly calls seriesSlug(group.name). If the slug formula is ever changed, updating only the function or only the inline version would break route matching, causing 404s or duplicate route params. Call seriesSlug(series) instead of inlining.

🟡 LOW --report accepts arbitrary file paths without validation — scripts/audit-blog.mjs

The --report value is resolved against process.cwd() and written unconditionally. A caller can specify an absolute or parent-relative path and overwrite any file the Node process can write. Because this is a maintainer-run local script the blast radius is limited, but it is a data-loss/footgun vector. Fix: validate that the resolved path is inside the project root (or a configured reports directory) before writing.

🟡 LOW Every .mdx file is read and frontmatter-parsed twice per run — scripts/audit-blog.mjs

postFiles.map(auditPost) at L448 calls fs.readFileSync + parseFrontmatter on each file. Then the legacy loop at L455-L487 reads and parses each file again independently. For N posts, both loops do O(N) full file reads. The auditPost return value doesn't expose raw data, so the second parse is needed for required-field/image checks that auditPostResult doesn't carry forward. Fix: have auditPost return data (or the raw text) so the second loop can reuse it.

🟡 LOW Warning findings lose accurate line numbers — scripts/audit-blog.mjs

The base code passed the matched needle and file text into addFinding for banned phrases and scaffold headings, so line numbers were accurate (e.g. line 9 for "delve", line 11 for a repeated heading in a synthetic test). The new reason-to-finding loop calls addFinding(findings, 'warning', file, reason.reason) without needle/text, so every surfaced warning defaults to line 1. This degrades the CLI output and makes it harder

🟡 LOW cleanValue array comma-split can tear quoted values containing commas — scripts/audit-blog.mjs

cleanValue detects arrays via startsWith('[') && endsWith(']') then splits on , without accounting for commas inside quoted substrings. Verified: cleanValue("['a,b', 'c']") produces ["'a","b'","c"] instead of ['a,b','c']. The inner cleanValue calls then fail to strip the broken quotes because opening/closing characters no longer match. Impact is limited to blog audit metadata (tags broken by mid-quote commas in YAML inline arrays), which is a rare frontmatter pattern. The previous cleanValue used replace(/^['"]|['"]$/g) which was more lenient but also incorrect for mid-quote commas.

🟡 LOW Phase 4 naming inconsistency — src/content/blog/decentralizing-x402-facilitator.mdx

The migration table calls Phase 4 Blueprint facilitation, but the detailed section calls it Facilitator Blueprint. Use the same name in both places to avoid readers thinking they are different concepts.

🟡 LOW Grammatical typo in FAQ — src/content/blog/rfq-job-quotes-tangle-operator-accountability.mdx

Sentence reads The contract does not restrict the grounds , any IPFS evidence reference is accepted. with a space before the comma and a missing conjunction/semicolon. Replace with The contract does not restrict the grounds; any IPFS evidence reference is accepted.

🟡 LOW Inconsistent trailing slash on internal links — src/content/blog/self-improving-stack-trace-systems.mdx

Internal links to /blog/self-improving-stack-evaluation-gates/ and /blog/self-improving-stack-harness-evolution/ use a trailing slash, while the rest of the site (including new cross-links in this PR) omits it. Pick one convention and apply it across the self-improving-stack series posts.

🟡 LOW Dead Standalone branches after filtering — src/pages/blog/index.astro

The seriesGroups array is already filtered with .filter(([name]) => name !== 'Standalone'), so the subsequent .sort() comparator's Standalone checks can never match. Fix: remove the two if (a.name === 'Standalone') / if (b.name === 'Standalone') branches and keep only localeCompare.

🟡 LOW Series slug logic duplicated between route and index — src/pages/blog/index.astro

The same slug transformation is implemented inline in getStaticPaths (src/pages/blog/series/[slug].astro lines 45-49) and as a helper in src/pages/blog/index.astro. Duplication risks the index link and the generated route diverging on future edits. Fix: move seriesSlug (and normalizeSeries) to a shared utility under src/lib/ or src/utils/ and import it in both files.

🟡 LOW dead code: Standalone sort guards unreachable after filter — src/pages/blog/index.astro

The .sort() comparing a.name === 'Standalone' (lines 69-73) is unreachable because Standalone entries are filtered out at line 48. Remove the Standalone cases from the comparator.

🟡 LOW Series detail summary source inconsistent with card summary — src/pages/blog/series/[slug].astro

The series detail page uses posts[0]?.data.summary (oldest/Part 1 after ascending sort), while src/pages/blog/index.astro uses group.latest.data.summary for the series card. This can show different descriptions for the same series and may confuse readers. Fix: decide on a canonical source (latest post summary or a dedicated series description field) and use it in both places.

🟡 LOW Unused normalizeSeries and seriesSlug helpers in series route — src/pages/blog/series/[slug].astro

normalizeSeries (line 6) and seriesSlug (line 10) are declared at module scope but not used; the route inlines equivalent logic in getStaticPaths. astro check reports them as unused (ts 6133). Fix: remove the dead helpers or refactor getStaticPaths to call them so the source of truth is the helper.

🟡 LOW dead code: normalizeSeries defined but never called — src/pages/blog/series/[slug].astro

Defined at lines 6-8 but never invoked. Normalization is done inline at line 29 with post.data.series?.replace(/^['"]|['"]$/g, ''). Remove or use the function.


tangletools · 2026-07-01T20:58:55Z · trace

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ 3 Blocking Findings — 70953efc

Full multi-shot audit completed 4/4 planned shots over 22 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 4/4 planned shots over 22 changed files. Global verifier still owns final merge decision.

Full immutable report for this review: trace

Summary comment for this run: full summary


tangletools · 2026-07-01T20:58:55Z · immutable trace

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Value Audit — better-approach-exists

Verdict better-approach-exists
Concerns 4 (1 medium-concern, 2 low, 1 weak-concern)
Heuristic 0.0s
Duplication 0.0s
Interrogation 900.1s (2 bridge agents)
Total 900.1s

💰 Value — better-approach-exists

Extends the blog audit with per-post editorial scoring and a markdown report, adds dedicated series landing pages, and edits 17 posts to fix template/voice/FAQ/link gaps; worthwhile, but it deepens duplication with the existing check-post.mjs instead of sharing rules.

  • What it does: Adds an auditPost() scorer to scripts/audit-blog.mjs that measures every blog post against word count, internal/external links, body artifacts, FAQ presence, Tangle-specific mechanisms, limitations, CTAs, banned phrases, and template/repeated headings, then computes a 0-100 score and can write a reproducible markdown report via --report. Adds a new /blog/series/[slug].astro page, wires the
  • Goals it achieves: Raise the editorial consistency of the blog archive; make posts easier for search/agent engines to extract answers from; give readers clearer next-step navigation and concrete decision close; reduce AI-cadence/template signals; organize content by series with its own landing page; provide a reproducible quality report the team can track over time.
  • Assessment: The change is coherent and domain-specific: the scoring heuristics map directly to the prior tangle-blog-proof checklist, the blog edits are substantive (tables, FAQ reformatting, source links), and the series page is a natural follow-up to the earlier archive-by-series work. pnpm check:blog passes and the report is generated correctly. The main drawback is maintainability: the PR extends `aud
  • Better / existing approach: Consolidate the overlapping blog validation rules. I looked at scripts/audit-blog.mjs, .codex/skills/tangle-blog-proof/scripts/check-post.mjs, src/pages/blog/index.astro, and src/pages/blog/series/[slug].astro. Both audit scripts already maintain separate copies of the banned phrase list, required frontmatter fields, FAQ question-heading check, internal/external link thresholds, and image-
  • Model: opencode/kimi-for-coding/k2p7
  • Bridge attempts: 1

🎯 Usefulness — error

usefulness agent produced no parseable value-audit JSON.

  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1
  • Bridge error: no parseable JSON response

🔎 Heuristic Signals

🟡 Cruft: console debug added scripts/audit-blog.mjs

  • console.log(Blog audit: ${posts.length} posts, ${Object.keys(summary.series).length} series, ${summary.errors} errors, ${summary.warnings} warnings)

🟡 Cruft: magic number added scripts/audit-blog.mjs

  • if (!hasLimitations && bodyWordCount > 1000) {

💰 Value Audit

🟠 audit-blog.mjs partially reinvents existing check-post.mjs rules [duplication] ``

The PR adds FAQ-heading validation, banned-phrase scanning, required-frontmatter checks, link counts, and image-existence checks to scripts/audit-blog.mjs (lines 13-28, 458-486, 470-474) that already exist in .codex/skills/tangle-blog-proof/scripts/check-post.mjs (lines 31-36, 80-84, 90-109, 111-126). Keeping two sources of truth means rules will drift; the right fix is a shared scripts/lib/blog-rules.mjs module imported by both scripts.

🟡 series slug normalization duplicated across two pages [better-architecture] ``

seriesSlug and normalizeSeries are defined inline in both src/pages/blog/index.astro:12-18 and src/pages/blog/series/[slug].astro:6-16. If the slug rules change, both pages must be updated or links will break. Extract them to a shared src/utils/blog.ts helper.


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260701T210135Z

tangletools
tangletools previously approved these changes Jul 1, 2026

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 0af5f5da

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-01T21:47:20Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Value Audit — better-approach-exists

Verdict better-approach-exists
Concerns 4 (1 medium-concern, 2 low, 1 weak-concern)
Heuristic 0.1s
Duplication 0.0s
Interrogation 121.0s (2 bridge agents)
Total 121.1s

💰 Value — better-approach-exists

A coherent archive-quality pass that upgrades the blog audit to score every post, generate a markdown report, add missing tables/FAQs/links across flagged posts, and ship dedicated series archive pages — but it inlines rules that already exist in the per-post proof checker instead of reusing them.

  • What it does: Expands scripts/audit-blog.mjs from a slop/artifact scanner into an archive-wide editorial scorer (0-100) with categorized reasons, a --report mode, and improved frontmatter parsing. Edits 17 posts to add comparison tables, FAQ sections with ### question headings, external source links, and internal reader paths. Adds src/pages/blog/series/[slug].astro so series cards route to a real series archiv
  • Goals it achieves: Make blog quality auditable and reproducible across the whole archive; close the proof gaps flagged by the stricter tangle-blog-proof checker (FAQ extraction, source links, internal paths, visual artifacts); remove template/scaffold headings and generic openings; organize the archive by series rather than raw post count.
  • Assessment: Good change on its merits. It directly addresses the proof checklist in .codex/skills/tangle-blog-proof/references/proof-checklist.md, passes pnpm check:blog and pnpm check:blog:report, and the new series archive page fills a real UX gap (previously series cards linked only to the latest post). The post edits are concrete (tables, FAQs, source links) rather than cosmetic.
  • Better / existing approach: The expanded audit in scripts/audit-blog.mjs partially reinvents rules already implemented in .codex/skills/tangle-blog-proof/scripts/check-post.mjs: both enforce required frontmatter fields (check-post.mjs:31-36 vs audit-blog.mjs:458-464), banned phrases (check-post.mjs:90-109 vs audit-blog.mjs:13-29,304-315), FAQ with ### question headings (check-post.mjs:80-84 vs audit-blog.mjs:483-486), intern
  • Model: opencode/kimi-for-coding/k2p7
  • Bridge attempts: 1

🎯 Usefulness — sound-with-nits

Extends the existing archive audit script with per-post editorial scoring and a reproducible report output, and upgrades the flagged posts with real decision tables, FAQ sections, and primary source links — all wired into package.json and reachable; the archive now passes its own stricter gate.

  • Integration: Fully reachable. The new check:blog:report npm script (package.json:18) wraps the existing audit-blog.mjs --report flag. The base check:blog is already consumed by the tangle-blog-proof skill (.codex/skills/tangle-blog-proof/SKILL.md:25) as the full-archive gate, and the per-post check-post.mjs is the pre-publish gate; the PR sits cleanly in that chain. Report output targets `audit-resul
  • Fit with existing patterns: Fits the grain. The change extends audit-blog.mjs in place rather than spawning a parallel checker: the cleanValue/asArray refactor generalizes the frontmatter parser (handles nested arrays, bools, numbers), and auditPost/renderReport reuse the existing addFinding/relativeFile helpers. Output dir (audit-results/) and PASS/NEEDS-WORK exit semantics match the sibling audit scripts. T
  • Real-world viability: Holds up. I ran both node scripts/audit-blog.mjs and --report against the full 80-post archive: 0 errors, 0 warnings, exit 0, and a 680-line report written correctly. Scoring is deterministic (warnings −8, notices −3, floored at 0) and asArray guards against non-array frontmatter values. No happy-path-only fragility observed. The only robustness nit is a minor double-read of each file (audit
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

🔎 Heuristic Signals

🟡 Cruft: console debug added scripts/audit-blog.mjs

  • console.log(Blog audit: ${posts.length} posts, ${Object.keys(summary.series).length} series, ${summary.errors} errors, ${summary.warnings} warnings)

🟡 Cruft: magic number added scripts/audit-blog.mjs

  • if (!hasLimitations && bodyWordCount > 1000) {

🎯 Usefulness Audit

🟡 Banned-phrase and heuristic lists drift between the two sibling checkers [ergonomics] ``

audit-blog.mjs (audit-blog.mjs:13-29) and check-post.mjs (check-post.mjs:90-103) maintain separate banned-phrase lists that have already diverged — the archive checker carries not just, more than just, and at its core, while the per-post proof checker does not. Since the proof skill tells authors to run both, drift means a post can pass one gate and fail the other. Consider deriving both lists from one shared source (e.g. a JSON module both scripts import) so the editorial bar stays co

💰 Value Audit

🟠 Audit script duplicates existing per-post proof-checker rules [duplication] ``

scripts/audit-blog.mjs now embeds the same frontmatter, banned-phrase, FAQ-heading, link-count, and image-existence checks that already exist in .codex/skills/tangle-blog-proof/scripts/check-post.mjs. Evidence: banned phrases overlap (delve, comprehensive, facilitate, etc.); both require title/slug/summary/date/author/tags; both check /^## FAQ\b/m and ### question headings; both count internal and external links; both validate coverImage/heroImage existence. The PR verification even runs both sc


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260701T215841Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 1869616a

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-01T22:04:10Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Value Audit — better-approach-exists

Verdict better-approach-exists
Concerns 4 (1 medium-concern, 2 low, 1 weak-concern)
Heuristic 0.1s
Duplication 0.0s
Interrogation 104.2s (2 bridge agents)
Total 104.3s

💰 Value — better-approach-exists

Extends the archive blog audit with per-post editorial scoring and a markdown report, then polishes ~17 posts with tables, FAQs, source links, and internal paths; useful, but it largely duplicates rules already in .codex/skills/tangle-blog-proof/scripts/check-post.mjs.

  • What it does: Adds a auditPost() scoring function to scripts/audit-blog.mjs that computes a 0-100 score across categories (reader pressure, Tangle proof, SEO/AEO, evidence, conversion, presentation, structure, voice), emits a --report markdown file, and refactors frontmatter parsing. It also edits 17 blog posts to add decision tables, FAQ sections with ### question headings, external source links, and i
  • Goals it achieves: Make archive-wide editorial quality visible and reproducible; catch thin posts, missing proof, weak CTAs, AI-cadence phrases, and poor internal navigation before publication; and upgrade existing posts so they pass the stricter checker referenced by the blog-proof skill.
  • Assessment: The content edits are coherent and improve the posts. The scoring/report capability is a real addition. However, the audit script reimplements a lot of logic that already lives in the per-post proof checker, and the two scripts now have divergent internal-link regexes and thresholds. That creates a maintenance surface and two sources of truth for the same quality gates.
  • Better / existing approach: Extract shared blog-quality rules into a common module (e.g., scripts/lib/blog-audit.mjs or under .codex/skills/tangle-blog-proof/scripts/lib/) and have both check-post.mjs and audit-blog.mjs import it. audit-blog.mjs would then add only the archive/scoring/report layer on top. I checked: .codex/skills/tangle-blog-proof/scripts/check-post.mjs existed before this PR (148 lines) and alre
  • Model: opencode/kimi-for-coding/k2p7
  • Bridge attempts: 1

🎯 Usefulness — sound-with-nits

Extends the blog audit script with editorial scoring + report output, refactors per-series archive routing into a dedicated page, and edits ~17 posts to match the new heuristics — all reachable, all runs clean.

  • Integration: check:blog:report is wired as an npm script (package.json:18) parallel to the existing check:blog; verified pnpm check:blog:report writes audit-results/blog-editorial-audit.md (gitignored via .gitignore:7, so no stray commits). The new src/pages/blog/series/[slug].astro is reached from the series cards on src/pages/blog/index.astro:95 via /blog/series/<slug>. The bodyClass prop on
  • Fit with existing patterns: Fits the codebase grain. The audit script extends the existing scripts/audit-blog.mjs heuristics incrementally (adds templateHeadings, tangleMechanisms, auditPost, renderReport) rather than forking. The content schema (src/content.config.ts) already treats tags as a strict array, so the defensive asArray() helper is belt-and-suspenders, not a competing pattern. The ### question heading style
  • Real-world viability: Runs end-to-end on the real 80-post archive: pnpm check:blog exits 0 with 0 errors, 0 warnings; pnpm check:blog:report produces a 34KB markdown report with per-post scores and reasons. The proof checker (.codex/skills/tangle-blog-proof/scripts/check-post.mjs) passes on the edited posts I sampled. One minor robustness gap: --report without a following path silently no-ops (args[reportInde
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

🔎 Heuristic Signals

🟡 Cruft: console debug added scripts/audit-blog.mjs

  • console.log(Blog audit: ${posts.length} posts, ${Object.keys(summary.series).length} series, ${summary.errors} errors, ${summary.warnings} warnings)

🟡 Cruft: magic number added scripts/audit-blog.mjs

  • if (!hasLimitations && bodyWordCount > 1000) {

💰 Value Audit

🟠 Archive audit reimplements the per-post proof checker instead of sharing logic [duplication] ``

The PR adds link counting, FAQ detection, banned-phrase scanning, frontmatter parsing, and artifact checks to scripts/audit-blog.mjs, but .codex/skills/tangle-blog-proof/scripts/check-post.mjs already implements nearly all of these same rules. For example, both count internal/external links, both check for ## FAQ with ### question headings, both scan for banned phrases, and both verify post artifacts/proof. The two scripts already diverge: audit-blog.mjs counts internal links against `

🎯 Usefulness Audit

🟡 Duplicated series-slug function between index and series page [ergonomics] ``

The slug transform is inlined identically in src/pages/blog/index.astro:13-18 (seriesSlug) and again inline in src/pages/blog/series/[slug].astro:36-41 inside getStaticPaths. If they ever diverge, the index page will link to 404s. Extract to a shared util (e.g. src/utils/series-slug.ts) so the link source and the route param share one definition. Does not gate shipping.


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260701T220745Z

@drewstone drewstone merged commit 823784a into master Jul 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants