fix(blog): audit archive editorial quality#39
Conversation
tangletools
left a comment
There was a problem hiding this comment.
✅ 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
left a comment
There was a problem hiding this comment.
🟡 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
auditPostfunction inscripts/audit-blog.mjsthat 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 categorizedreasonslist, and a new--reportMarkdown 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.mjsreinvents 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 sharedscripts/lib/blog-validation.mjsmodule (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:reportis added to package.json:18 alongside the existingcheck:blog. The report targetaudit-results/blog-editorial-audit.mdlands in a dir that is already gitignored (.gitignoreline:audit-results/) and already used as the regenerable-artifact convention byscripts/visual-audit.mjs:40andscripts/bad-batch.sh. Verified `node scripts/audit-blog.mjs -- - Fit with existing patterns: Extends the existing
scripts/audit-blog.mjsin-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).
parseFrontmatterdefaults to{}on missing frontmatter sodata.title/data.tagsdegrade gracefully via theasArray/basename fallbacks. The--reportflag-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.mjslines 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.mjslines 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.
✅ No Blockers —
|
| 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] : nulldoes not validate that the following argument is actually a path. Runningnode scripts/audit-blog.mjs --report --jsoncreates a file literally named--jsonin 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) / 2assumes 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 thecodeBlockCount >= 5heuristic. 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/rfc8446for TLS client authentication guidance. That URL returns HTTP 404. Use the working datatracker URL (https://datatracker.ietf.org/doc/html/rfc8446) or the.txtRFC-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.jsonwhich 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.jsonreturns 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.jsonproves 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.tomlsetsfacilitator_url = "https://facilitator.x402.org", which does not resolve (NXDOMAIN). The decentralizing-x402-facilitator post and x402 reference usefacilitator.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
--jsonoutput changedsummary.postsfrom a number (postFiles.length) to an array of audit objects. Any downstream automation consumingnode scripts/audit-blog.mjs --jsonwill 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\nbefore 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\nto\nbefore matching.
🟡 LOW Frontmatter inline-array parser is comma-naive — scripts/audit-blog.mjs
cleanValuesplits inline array values on every comma before unquoting, so a value liketags: ["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.reasonsintofindingswithaddFinding(findings, 'warning', file, reason.reason)but no longer passesneedle/text, so every derived warning is reported at line 1. The original code passed the matched heading/phrase tolineOf. 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/rfc8446returns HTTP 404. The canonical working URL ishttps://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
left a comment
There was a problem hiding this comment.
✅ 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
left a comment
There was a problem hiding this comment.
✅ 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
❌ Needs Work —
|
| 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, butfacilitator.x402.orgdoes not resolve (DNS failure). The live public facilitator ishttps://facilitator.x402.rs(returns 200 on /v2/x402/supported). Readers copying this URL into config will get a non-functional gateway. Change tohttps://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.jsoncurrently 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 isfacilitator.x402.rs. Because these are meant to be copied verbatim, this will break staging/production payment flows. Update every occurrence tohttps://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 returnundefinedif path resolution betweenauditPostand the legacy loop ever diverges (e.g., symlinks, differingprocess.cwd()between calls, or code refactoring). The next line accessespost.reasonsunconditionally, which would throw a TypeError and crash the script. Currently unlikely to trigger because both loops use the samerootvariable andpostFilesis 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
DockerLocalandBareMetalas deployment targets, but theDeploymentTargetenum shown earlier in the same post only definesVirtualMachine,ManagedKubernetes,GenericKubernetes, andServerless.DockerLocalandBareMetalareCloudProvidervariants, notDeploymentTargetvariants. 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 notDeploymentTargetvariants.
🟠 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 allowsseries: 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 ingetStaticPaths, 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 callsseriesSlug(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. CallseriesSlug(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 callsfs.readFileSync+parseFrontmatteron 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. TheauditPostreturn value doesn't expose rawdata, so the second parse is needed for required-field/image checks thatauditPostResultdoesn't carry forward. Fix: haveauditPostreturndata(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
cleanValuedetects arrays viastartsWith('[') && 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 innercleanValuecalls 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 previouscleanValueusedreplace(/^['"]|['"]$/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 itFacilitator 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 withThe 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
seriesGroupsarray is already filtered with.filter(([name]) => name !== 'Standalone'), so the subsequent.sort()comparator's Standalone checks can never match. Fix: remove the twoif (a.name === 'Standalone')/if (b.name === 'Standalone')branches and keep onlylocaleCompare.
🟡 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(andnormalizeSeries) 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 usesgroup.latest.data.summaryfor 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) andseriesSlug(line 10) are declared at module scope but not used; the route inlines equivalent logic ingetStaticPaths.astro checkreports 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
left a comment
There was a problem hiding this comment.
❌ 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
left a comment
There was a problem hiding this comment.
🟠 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 toscripts/audit-blog.mjsthat 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].astropage, 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-proofchecklist, 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:blogpasses 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, andsrc/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 sharedscripts/lib/blog-rules.mjsmodule imported by both scripts.
🟡 series slug normalization duplicated across two pages [better-architecture] ``
seriesSlugandnormalizeSeriesare defined inline in bothsrc/pages/blog/index.astro:12-18andsrc/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 sharedsrc/utils/blog.tshelper.
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.
tangletools
left a comment
There was a problem hiding this comment.
✅ 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
left a comment
There was a problem hiding this comment.
🟠 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:reportnpm script (package.json:18) wraps the existingaudit-blog.mjs --reportflag. The basecheck:blogis already consumed by thetangle-blog-proofskill (.codex/skills/tangle-blog-proof/SKILL.md:25) as the full-archive gate, and the per-postcheck-post.mjsis 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.mjsin place rather than spawning a parallel checker: thecleanValue/asArrayrefactor generalizes the frontmatter parser (handles nested arrays, bools, numbers), andauditPost/renderReportreuse the existingaddFinding/relativeFilehelpers. 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.mjsand--reportagainst 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) andasArrayguards 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) andcheck-post.mjs(check-post.mjs:90-103) maintain separate banned-phrase lists that have already diverged — the archive checker carriesnot just,more than just, andat 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.
tangletools
left a comment
There was a problem hiding this comment.
✅ 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
left a comment
There was a problem hiding this comment.
🟠 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 toscripts/audit-blog.mjsthat computes a 0-100 score across categories (reader pressure, Tangle proof, SEO/AEO, evidence, conversion, presentation, structure, voice), emits a--reportmarkdown 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.mjsor under.codex/skills/tangle-blog-proof/scripts/lib/) and have bothcheck-post.mjsandaudit-blog.mjsimport it.audit-blog.mjswould then add only the archive/scoring/report layer on top. I checked:.codex/skills/tangle-blog-proof/scripts/check-post.mjsexisted 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:reportis wired as an npm script (package.json:18) parallel to the existingcheck:blog; verifiedpnpm check:blog:reportwrites audit-results/blog-editorial-audit.md (gitignored via.gitignore:7, so no stray commits). The newsrc/pages/blog/series/[slug].astrois reached from the series cards onsrc/pages/blog/index.astro:95via/blog/series/<slug>. ThebodyClassprop 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
tagsas a strict array, so the defensiveasArray()helper is belt-and-suspenders, not a competing pattern. The### question headingstyle - Real-world viability: Runs end-to-end on the real 80-post archive:
pnpm check:blogexits 0 with0 errors, 0 warnings;pnpm check:blog:reportproduces 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:--reportwithout 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.mjsalready implements nearly all of these same rules. For example, both count internal/external links, both check for## FAQwith###question headings, both scan for banned phrases, and both verify post artifacts/proof. The two scripts already diverge:audit-blog.mjscounts 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.
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