taste-lint 0.0.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/LICENSE.md +9 -0
- package/README.md +141 -0
- package/data/config.schema.json +123 -0
- package/data/corpus/blode-co-review.jsonl +80 -0
- package/data/corpus/copywriting.jsonl +139 -0
- package/data/corpus/typography.jsonl +53 -0
- package/data/rule-drafts/README.md +9 -0
- package/data/rule-drafts/interaction/interaction-layout-shift.yaml +54 -0
- package/data/rule-drafts/typography/typography-long-content-safety.yaml +60 -0
- package/data/rules/authoring/authoring-approval-every-step.yaml +44 -0
- package/data/rules/authoring/authoring-fixed-agent-quota.yaml +44 -0
- package/data/rules/authoring/authoring-generic-coding-advice.yaml +44 -0
- package/data/rules/authoring/authoring-generic-verification.yaml +45 -0
- package/data/rules/authoring/authoring-plan-vague-acceptance.yaml +43 -0
- package/data/rules/copywriting/copywriting-announced-honesty.yaml +43 -0
- package/data/rules/copywriting/copywriting-anthropomorphism.yaml +29 -0
- package/data/rules/copywriting/copywriting-artificial-balance.yaml +35 -0
- package/data/rules/copywriting/copywriting-bare-confirm-label.yaml +23 -0
- package/data/rules/copywriting/copywriting-boilerplate-offer.yaml +38 -0
- package/data/rules/copywriting/copywriting-canned-phrasing.yaml +53 -0
- package/data/rules/copywriting/copywriting-changed-supplied-facts.yaml +31 -0
- package/data/rules/copywriting/copywriting-claim-without-evidence.yaml +59 -0
- package/data/rules/copywriting/copywriting-click-here-links.yaml +22 -0
- package/data/rules/copywriting/copywriting-conditions-first.yaml +50 -0
- package/data/rules/copywriting/copywriting-confirmshaming.yaml +43 -0
- package/data/rules/copywriting/copywriting-cta-names-outcome.yaml +54 -0
- package/data/rules/copywriting/copywriting-dead-weight-hedges.yaml +58 -0
- package/data/rules/copywriting/copywriting-drafting-instruction-echo.yaml +31 -0
- package/data/rules/copywriting/copywriting-emoji-in-ui.yaml +20 -0
- package/data/rules/copywriting/copywriting-empty-state-no-action.yaml +51 -0
- package/data/rules/copywriting/copywriting-error-descriptions.yaml +53 -0
- package/data/rules/copywriting/copywriting-excessive-hedging.yaml +48 -0
- package/data/rules/copywriting/copywriting-fabricated-specificity.yaml +35 -0
- package/data/rules/copywriting/copywriting-filler-words.yaml +55 -0
- package/data/rules/copywriting/copywriting-flat-description.yaml +37 -0
- package/data/rules/copywriting/copywriting-friction-cta.yaml +53 -0
- package/data/rules/copywriting/copywriting-generic-audience.yaml +55 -0
- package/data/rules/copywriting/copywriting-generic-framing.yaml +35 -0
- package/data/rules/copywriting/copywriting-generic-language.yaml +35 -0
- package/data/rules/copywriting/copywriting-generic-validation.yaml +46 -0
- package/data/rules/copywriting/copywriting-idea-repetition.yaml +34 -0
- package/data/rules/copywriting/copywriting-latinisms.yaml +25 -0
- package/data/rules/copywriting/copywriting-leaked-error-message.yaml +23 -0
- package/data/rules/copywriting/copywriting-live-region-feedback.yaml +24 -0
- package/data/rules/copywriting/copywriting-machine-prose.yaml +73 -0
- package/data/rules/copywriting/copywriting-mechanism-over-outcome.yaml +33 -0
- package/data/rules/copywriting/copywriting-meta-commentary.yaml +48 -0
- package/data/rules/copywriting/copywriting-missing-reasoning-link.yaml +33 -0
- package/data/rules/copywriting/copywriting-nominalisations.yaml +45 -0
- package/data/rules/copywriting/copywriting-over-explanation.yaml +34 -0
- package/data/rules/copywriting/copywriting-passive-voice.yaml +53 -0
- package/data/rules/copywriting/copywriting-permission-benefit.yaml +43 -0
- package/data/rules/copywriting/copywriting-profile-register-mismatch.yaml +31 -0
- package/data/rules/copywriting/copywriting-promotional-docs-voice.yaml +58 -0
- package/data/rules/copywriting/copywriting-prompt-restatement.yaml +35 -0
- package/data/rules/copywriting/copywriting-punctuation-tics.yaml +35 -0
- package/data/rules/copywriting/copywriting-reader-first.yaml +59 -0
- package/data/rules/copywriting/copywriting-register-shift.yaml +58 -0
- package/data/rules/copywriting/copywriting-repetitive-transitions.yaml +48 -0
- package/data/rules/copywriting/copywriting-requirements-language.yaml +50 -0
- package/data/rules/copywriting/copywriting-searchable-headings.yaml +52 -0
- package/data/rules/copywriting/copywriting-semantic-links.yaml +23 -0
- package/data/rules/copywriting/copywriting-structural-symmetry.yaml +35 -0
- package/data/rules/copywriting/copywriting-success-state-specific.yaml +43 -0
- package/data/rules/copywriting/copywriting-temporal-words.yaml +55 -0
- package/data/rules/copywriting/copywriting-toast-successfully.yaml +22 -0
- package/data/rules/copywriting/copywriting-unexplained-action.yaml +33 -0
- package/data/rules/copywriting/copywriting-unnatural-polish.yaml +48 -0
- package/data/rules/copywriting/copywriting-unraised-objection.yaml +43 -0
- package/data/rules/copywriting/copywriting-unverifiable-proof.yaml +23 -0
- package/data/rules/copywriting/copywriting-vague-error.yaml +58 -0
- package/data/rules/copywriting/copywriting-vague-personal-request.yaml +43 -0
- package/data/rules/copywriting/copywriting-visual-direction-instruction.yaml +43 -0
- package/data/rules/craft/craft-affordance-mismatch.yaml +23 -0
- package/data/rules/craft/craft-decoration-no-role.yaml +27 -0
- package/data/rules/craft/craft-faux-product-chrome.yaml +22 -0
- package/data/rules/craft/craft-hover-only-affordance.yaml +23 -0
- package/data/rules/craft/craft-i18n-rtl-untested.yaml +23 -0
- package/data/rules/craft/craft-i18n-untested.yaml +28 -0
- package/data/rules/craft/craft-image-dimensions-and-priority.yaml +23 -0
- package/data/rules/craft/craft-inline-styles.yaml +24 -0
- package/data/rules/craft/craft-lazy-load-offscreen.yaml +28 -0
- package/data/rules/craft/craft-near-duplicate-scale.yaml +29 -0
- package/data/rules/craft/craft-token-drift.yaml +22 -0
- package/data/rules/craft/craft-viewport-scaling.yaml +25 -0
- package/data/rules/craft/craft-virtualize-large-lists.yaml +24 -0
- package/data/rules/interaction/interaction-a11y-color-only-meaning.yaml +23 -0
- package/data/rules/interaction/interaction-a11y-data-table-semantics.yaml +24 -0
- package/data/rules/interaction/interaction-a11y-disabled-control-tooltip.yaml +23 -0
- package/data/rules/interaction/interaction-a11y-document-language.yaml +24 -0
- package/data/rules/interaction/interaction-a11y-icon-controls-labeled.yaml +23 -0
- package/data/rules/interaction/interaction-a11y-image-alt-text.yaml +24 -0
- package/data/rules/interaction/interaction-a11y-media-captions.yaml +24 -0
- package/data/rules/interaction/interaction-a11y-semantic-html-first.yaml +23 -0
- package/data/rules/interaction/interaction-a11y-skip-link-heading-order.yaml +24 -0
- package/data/rules/interaction/interaction-a11y-tooltip-no-interactive.yaml +23 -0
- package/data/rules/interaction/interaction-broken-focus-trap.yaml +25 -0
- package/data/rules/interaction/interaction-dont-block-paste-ime.yaml +23 -0
- package/data/rules/interaction/interaction-error-association.yaml +24 -0
- package/data/rules/interaction/interaction-focus-visible.yaml +25 -0
- package/data/rules/interaction/interaction-inline-errors-first-focus.yaml +24 -0
- package/data/rules/interaction/interaction-input-affix-hit-target.yaml +23 -0
- package/data/rules/interaction/interaction-keyboard-operable.yaml +23 -0
- package/data/rules/interaction/interaction-labels-and-autocomplete.yaml +23 -0
- package/data/rules/interaction/interaction-lost-data-on-error.yaml +23 -0
- package/data/rules/interaction/interaction-mobile-input-font-size.yaml +24 -0
- package/data/rules/interaction/interaction-no-disable-while-submitting.yaml +23 -0
- package/data/rules/interaction/interaction-no-empty-state.yaml +26 -0
- package/data/rules/interaction/interaction-no-error-boundary.yaml +27 -0
- package/data/rules/interaction/interaction-no-error-state.yaml +26 -0
- package/data/rules/interaction/interaction-no-suspense-boundary.yaml +23 -0
- package/data/rules/interaction/interaction-not-restored.yaml +25 -0
- package/data/rules/interaction/interaction-on-dynamic-content.yaml +25 -0
- package/data/rules/interaction/interaction-optimistic-without-rollback.yaml +23 -0
- package/data/rules/interaction/interaction-out-of-order-responses.yaml +27 -0
- package/data/rules/interaction/interaction-target-size.yaml +26 -0
- package/data/rules/interaction/interaction-use-form-status-misuse.yaml +23 -0
- package/data/rules/product/product-nested-modals.yaml +26 -0
- package/data/rules/product/product-select-for-few-options.yaml +24 -0
- package/data/rules/schema/rule.schema.json +262 -0
- package/data/rules/tuning.json +1 -0
- package/data/rules/typography/typography-dashes.yaml +28 -0
- package/data/rules/typography/typography-ellipsis.yaml +27 -0
- package/data/rules/typography/typography-hover-weight-shift.yaml +24 -0
- package/data/rules/typography/typography-midpoint-separators.yaml +21 -0
- package/data/rules/typography/typography-multiplication-sign.yaml +27 -0
- package/data/rules/typography/typography-nbsp-value-unit.yaml +27 -0
- package/data/rules/typography/typography-readable-scale.yaml +23 -0
- package/data/rules/typography/typography-straight-quotes.yaml +37 -0
- package/data/rules/typography/typography-title-case-heading.yaml +49 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6317 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +736 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5330 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Blode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# [taste-lint](https://github.com/mblode/taste-lint)
|
|
4
|
+
|
|
5
|
+
**Taste rules from [Agent Skills](https://github.com/mblode/agent-skills) and [Taste Training](https://blode.co/taste-training), run as a linter**
|
|
6
|
+
|
|
7
|
+
Point it at Markdown, MDX, TSX or a rendered page and get findings with a severity and a probability.
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
Requires Node 24.11 or later. Bring your own [Vercel AI Gateway API key](https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys): open **AI Gateway → API Keys → Create Key** in your Vercel dashboard.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g taste-lint
|
|
17
|
+
export AI_GATEWAY_API_KEY="your-vercel-ai-gateway-key"
|
|
18
|
+
|
|
19
|
+
# Run from your project directory
|
|
20
|
+
taste-lint scan .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
No taste-lint account or configuration file is required. Model usage is billed to your Vercel AI Gateway account. Taste-lint reads the key from your environment and sends selected text and rule context directly to the gateway.
|
|
24
|
+
|
|
25
|
+
Preview scope and estimated cost with `taste-lint scan . --dry-run`. Try local checks without a key using `taste-lint scan . --mechanical-only`. The default scan focuses on product interfaces; use `--profile writing` for documentation or `--profile instructions` for agent instructions.
|
|
26
|
+
|
|
27
|
+
Semantic rules remain advisory until calibrated against independent holdout evidence. Mechanical checks can fail a run immediately.
|
|
28
|
+
|
|
29
|
+
## Where the rules come from
|
|
30
|
+
|
|
31
|
+
[Agent Skills](https://github.com/mblode/agent-skills) provides reusable instructions for coding agents. [Taste Training](https://blode.co/taste-training) teaches people to spot and fix problems in typography, copy, interaction, and motion. Taste-lint turns applicable guidance into repeatable checks, with source references attached to individual rules.
|
|
32
|
+
|
|
33
|
+
## Example findings
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
PASS: 161 active rules in data/rules
|
|
37
|
+
|
|
38
|
+
notes.md
|
|
39
|
+
[MINOR] typography-straight-quotes (p=1.00) notes.md:3:1
|
|
40
|
+
Straight quotes in rendered copy
|
|
41
|
+
2 matches: "'", "'"
|
|
42
|
+
Fix: Replace with the matching curly mark. Opening after whitespace or at the start, closing otherwise; an apostrophe is always the right single quote.
|
|
43
|
+
[MINOR?] copywriting-claim-without-evidence (p=0.95) notes.md:3:1
|
|
44
|
+
Quality claimed, nothing the reader could check
|
|
45
|
+
3 matches: "fast", "powerful", "seamless"; p=0.95
|
|
46
|
+
Fix: Replace the adjective with the mechanism, the number or the standard it stands for. If none exists, cut the sentence.
|
|
47
|
+
|
|
48
|
+
Units: 3 | Rules: 161 | Act: 1 | Review: 4 | Unknown: 0
|
|
49
|
+
Jev: 2 requests, 0 cached answers, 7238 input tokens, $0.0003
|
|
50
|
+
FAIL - 1 finding in the act band
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Two answers per finding
|
|
54
|
+
|
|
55
|
+
- **Severity:** how bad the finding is if real, `major` or `minor`. Set by the rule, never by the model.
|
|
56
|
+
- **Band:** how sure the tool is. `act` fails the run (mechanical hits, or Jev at or above the rule's act threshold), `review` prints a note with a `?`, below that is silent.
|
|
57
|
+
- **Cost:** eligible questions about one unit are batched into requests. Preview estimated cost with `--dry-run`; runs report usage and reuse cached answers. Current rates are listed in the [Vercel AI Gateway model catalog](https://vercel.com/ai-gateway/models).
|
|
58
|
+
|
|
59
|
+
## Rule packs
|
|
60
|
+
|
|
61
|
+
- **Typography:** straight quotes, dashes, ellipses, primes and units from `typography-audit`, plus size, weight, tracking and line-height checks over resolved Tailwind classes or computed styles.
|
|
62
|
+
- **Copywriting:** claims without evidence, vague errors, friction CTAs, hedges and register shifts from `docs-writing` and the ui-design copy guideline, and Every's published AI-tell checker: 19 of its 21 questions, one rule each, with phrase candidates from the MIT `cw-ai-check` skill where it has them. Not ported: `uniform_cadence` (sentence-length arithmetic) and `formatting_overuse` (needs headings and bullets a paragraph never sees). The two authorship verdicts are excluded on purpose: taste-lint reports defects, not authorship.
|
|
63
|
+
- **Interaction and craft:** the static checks of `ui-design/rules` as whole-file patterns (focus traps, error and empty states, target size, i18n, lazy loading) plus the shadcn/lint class hygiene rules (raw palette colours, arbitrary values, interpolated class strings).
|
|
64
|
+
- **Motion and product:** the `ui-animation` flag-on-sight table (ease-in, linear easing, transitions over 300ms, `transition-all`, entrances from scale zero, no reduced-motion variant) and the two deterministic `product-design` rules.
|
|
65
|
+
|
|
66
|
+
Every rule names the file and line of the skill or lesson it came from, and `taste-lint rules list` prints tier, status and category per rule.
|
|
67
|
+
|
|
68
|
+
## Rendered mode
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
taste-lint lint --url https://example.com/pricing --selector main
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Runs [style-capture](https://www.npmjs.com/package/style-capture) in headless Chromium and lints computed styles: real pixel sizes, line heights, weights and letter-spacing, so the typography rules judge what the reader sees rather than what the class list implies. `--capture file.json` lints a saved capture.
|
|
75
|
+
|
|
76
|
+
## API
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { runLint } from "taste-lint";
|
|
80
|
+
|
|
81
|
+
const result = await runLint({ root: process.cwd(), targets: ["content"] });
|
|
82
|
+
console.log(result.scorecard.byDomain, result.usage.costUsd);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`runLint` takes the same options as the `lint` command and returns findings, unknowns, the scorecard and usage. `taste-lint schema` prints every command, flag and default as JSON, and `--output json` turns an error into a `{ error, code, message }` envelope on stdout.
|
|
86
|
+
|
|
87
|
+
## Agent skill
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx skills add mblode/taste-lint
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Installs the `taste-lint` skill for Claude Code, Codex, Cursor and OpenCode: how to read a finding, the dry-run-first workflow, and the gotchas. Source: [skills/taste-lint/SKILL.md](skills/taste-lint/SKILL.md).
|
|
94
|
+
|
|
95
|
+
## Options
|
|
96
|
+
|
|
97
|
+
| Flag | Default | Description |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| `--dry-run` | | Plan and print units, requests and estimated cost without calling Jev |
|
|
100
|
+
| `--mechanical-only` | | Skip every Jev-backed rule; no key needed |
|
|
101
|
+
| `--only <ids>` | | Comma-separated rule ids |
|
|
102
|
+
| `--exclude <globs>` | | Comma-separated globs to skip, added to `taste-lint.config.json` |
|
|
103
|
+
| `--fail-on <severity>` | `minor` | Lowest severity that fails the run |
|
|
104
|
+
| `--fix` | | Apply deterministic fixes (curly quotes, ellipsis, multiplication sign, unit spaces) to act-band findings |
|
|
105
|
+
| `--output <format>` | `tty` | `tty`, `json` or `sarif` |
|
|
106
|
+
| `--url <url>` | | Lint a rendered page through style-capture |
|
|
107
|
+
|
|
108
|
+
`taste-lint eval` scores every rule against its labelled corpus (precision, recall, Wilson intervals, a calibration table) and `taste-lint tune` picks act thresholds from the dev split, promoting a rule only when the fixed threshold also clears the precision lower bound on an independent holdout. Both label classes, enough evaluated items, complete scoring, and source/text separation are required. `taste-lint eval coverage` reports class balance and split leakage without API calls; add `--output json` to coverage or evaluation for structured results.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
113
|
+
|
|
114
|
+
## Reading a repository run
|
|
115
|
+
|
|
116
|
+
The default text report summarizes scope, top rules and up to 30 examples. Use `--verbose` for the full list; every completed or incomplete run also saves a complete JSON report and prints its path. JSON v1 keeps the original grouped `findings`; `ruleFindings` preserves every rule's evidence and `coverage` counts eligible checks. `summary.failing` respects `--fail-on`, and determines the exit code together with run completeness.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
taste-lint lint --root ../my-site apps/web --dry-run
|
|
120
|
+
taste-lint lint --root ../my-site apps/web --progress --output json > audit.json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Progress goes to stderr. A fully cached run needs no API key. An incomplete report includes a retry command that reuses successful answers. Cost is reported from known usage, excluding any unreported provider billing for failures.
|
|
124
|
+
|
|
125
|
+
Configuration is optional at `taste-lint.config.json` in the scan root. Its editor schema ships at `node_modules/taste-lint/data/config.schema.json`. Unknown fields and invalid types fail before evaluation. Select the scope explicitly: documentation and agent instructions remain included when you request a whole repository.
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"$schema": "./node_modules/taste-lint/data/config.schema.json",
|
|
130
|
+
"exclude": ["docs/archive/**"],
|
|
131
|
+
"docTypes": [
|
|
132
|
+
{ "glob": "apps/web/content/writing/**/*.mdx", "type": "explanation" }
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
See [TypeSafe contracts](docs/TYPESAFE.md) and the [review-rule evaluation](docs/evaluations/blode-co-review.md) for the implementation evidence and current calibration limits.
|
|
138
|
+
|
|
139
|
+
See [skill packs](docs/SKILL-PACKS.md) for repository checks, architecture policy, personal-writing context, and source discovery.
|
|
140
|
+
|
|
141
|
+
Use `taste-lint scan . --profile product --dry-run` to preview a focused scan. The [scan workflow](docs/SCANS.md) covers profiles, baselines, review decisions, changed-code SARIF, calibration samples, graph-tool reports, and remediation exports.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"additionalProperties": false,
|
|
4
|
+
"properties": {
|
|
5
|
+
"$schema": {
|
|
6
|
+
"type": "string"
|
|
7
|
+
},
|
|
8
|
+
"exclude": {
|
|
9
|
+
"type": "array",
|
|
10
|
+
"items": {
|
|
11
|
+
"type": "string"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"smartQuotesAtBuild": {
|
|
15
|
+
"type": "boolean"
|
|
16
|
+
},
|
|
17
|
+
"components": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"properties": {
|
|
21
|
+
"skip": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "string"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"unwrap": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "string"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"docTypes": {
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"additionalProperties": false,
|
|
40
|
+
"properties": {
|
|
41
|
+
"glob": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
},
|
|
44
|
+
"type": {
|
|
45
|
+
"enum": [
|
|
46
|
+
"tutorial",
|
|
47
|
+
"howto",
|
|
48
|
+
"reference",
|
|
49
|
+
"explanation",
|
|
50
|
+
"agent-instructions",
|
|
51
|
+
"lesson",
|
|
52
|
+
"marketing",
|
|
53
|
+
"ui",
|
|
54
|
+
"readme",
|
|
55
|
+
"skill",
|
|
56
|
+
"plan",
|
|
57
|
+
"personal",
|
|
58
|
+
"pr",
|
|
59
|
+
"slides",
|
|
60
|
+
"unknown"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"required": ["glob", "type"]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"tailwind": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": false,
|
|
70
|
+
"properties": {
|
|
71
|
+
"theme": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": {
|
|
74
|
+
"type": "string"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"architecture": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"additionalProperties": false,
|
|
82
|
+
"properties": {
|
|
83
|
+
"boundaries": {
|
|
84
|
+
"type": "array",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"additionalProperties": false,
|
|
88
|
+
"required": ["from", "disallow", "reason"],
|
|
89
|
+
"properties": {
|
|
90
|
+
"from": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"minLength": 1
|
|
93
|
+
},
|
|
94
|
+
"disallow": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"minLength": 1
|
|
97
|
+
},
|
|
98
|
+
"reason": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"minLength": 1
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"deprecatedImports": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"additionalProperties": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"minLength": 1
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"generated": {
|
|
113
|
+
"type": "array",
|
|
114
|
+
"items": {
|
|
115
|
+
"type": "string"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
122
|
+
"title": "taste-lint user configuration"
|
|
123
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{"id": "blode-co-generic-framing-01", "text": "Move quickly into the relevant workflow.", "context": {"docType": "agent-instructions", "headingAbove": "Coaching patterns", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/seo-coach/SKILL.md", "id": "29bf85fdf2a6ea95"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
2
|
+
{"id": "blode-co-generic-framing-02", "text": "app/writing/[slug]/opengraph-image.tsx on blode.co is exempt. Its per-post film backgrounds are a deliberate editorial system, not drift.", "context": {"docType": "agent-instructions", "headingAbove": "Rule 12: one OG template", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/zone-conventions.md", "id": "2c3e241b13bfe09f"}, "split": "holdout", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
3
|
+
{"id": "blode-co-generic-framing-03", "text": "competitor-analysis: when they know a competitor to study", "context": {"docType": "agent-instructions", "headingAbove": "8. Recommend first workflow", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/seo-project-setup/SKILL.md", "id": "d793d623aba8b5c2"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
4
|
+
{"id": "blode-co-generic-framing-04", "text": "Design without Figma Great artists stealConstraints = speedMake it run", "context": {"docType": "explanation", "headingAbove": "Team shape that ships", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/ship-fast-or-die.md", "id": "3ca78f0ecdf3845d"}, "split": "holdout", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
5
|
+
{"id": "blode-co-generic-framing-05", "text": "This is a manual-only execution brief for your LinkedIn profile. It uses the current export in linkedin.md and aligns it with the site copy now live on matthewblode.com.", "context": {"docType": "explanation", "headingAbove": "LinkedIn SEO Update - March 2026", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/linkedin-seo-update-march-2026.md", "id": "fdffefafe9b1e448"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
6
|
+
{"id": "blode-co-generic-framing-06", "text": "Do not pad them to 600 words. A paid lesson does not owe a search engine an essay, and padding would produce exactly the filler the site's editorial position rejects.", "context": {"docType": "explanation", "headingAbove": "6.8 The thin lesson pages", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/educational-resource-format-spec.md", "id": "57ff9a8f3b13097a"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
7
|
+
{"id": "blode-co-generic-framing-07", "text": "The barrier to shipping a real product keeps falling, and AI handles more of the specialised work every month. Putting something in front of users before it\u2019s ready still feels wrong.", "context": {"docType": "lesson", "headingAbove": "Shipping at 80 percent", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/ship-fast-or-die.mdx", "id": "914b0159c967d6e4"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": true}, "labelSource": "sweep", "labelNote": "Broad industry-change opener before any concrete case; the claimed trend supplies the frame."}
|
|
8
|
+
{"id": "blode-co-generic-framing-08", "text": "We grew to 70 people and thousands of messages with no marketing. I pushed for a hard cap at 100. Every invite was personal: someone we\u2019d worked with, met at an event, or whose work we respected.", "context": {"docType": "lesson", "headingAbove": "The cold start problem", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/seventy-people-in-a-group-chat.mdx", "id": "d49cae28738bae4b"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
9
|
+
{"id": "blode-co-generic-framing-09", "text": "What I keep wrestling with: even skills for CLI tools are mostly overkill, because Claude Code can figure it out. Give it a well-documented tool and it reads the docs and uses it correctly.", "context": {"docType": "lesson", "headingAbove": "The tension", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/skills-are-the-new-packages.mdx", "id": "c2e5e3aa37bfad34"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
10
|
+
{"id": "blode-co-generic-framing-10", "text": "Endpoint", "context": {"docType": "unknown", "headingAbove": "Endpoints an agent can use", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/public/auth.md", "id": "78a8d1c72062f090"}, "split": "holdout", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
11
|
+
{"id": "blode-co-generic-framing-11", "text": "ease-in-cubic: cubic-bezier(.550, .055, .675, .19)", "context": {"docType": "unknown", "headingAbove": "Easing rules", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/ANIMATION.md", "id": "c04fa507d3f2cf0b"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
12
|
+
{"id": "blode-co-generic-framing-12", "text": "signals-scout-data-pipelines", "context": {"docType": "unknown", "headingAbove": "Disabled (24)", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "posthog-self-driving-report.md", "id": "0c74ff57c8aa8dce"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
13
|
+
{"id": "blode-co-generic-framing-13", "text": "Explain the concept plainly.", "context": {"docType": "agent-instructions", "headingAbove": "Coaching patterns", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/seo-coach/SKILL.md", "id": "88fd9717ef2c162f"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
14
|
+
{"id": "blode-co-generic-framing-14", "text": "apps/web/next.config.ts for hosts this app answers directly: alternateHosts (www.blode.co, matthewblode.com, www.matthewblode.com), legacyProductHosts (dither, moon, react-vello), and retiredHosts (reel, ascii).", "context": {"docType": "agent-instructions", "headingAbove": "Those 301s are load-bearing until ~22 Jan 2027", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": ".claude/knowledge/architecture-boundaries.md", "id": "841766a7cd6da848"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
15
|
+
{"id": "blode-co-generic-framing-15", "text": "Then include a compact table:", "context": {"docType": "agent-instructions", "headingAbove": "Output format", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/keyword-research/SKILL.md", "id": "2cbcf83eba257ffd"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
16
|
+
{"id": "blode-co-generic-framing-16", "text": "Record the high-probability opportunities here: ________________________________ ________________________________ ________________________________", "context": {"docType": "explanation", "headingAbove": "3.7 Existing Press / Alumni / Recognition Pages", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/matthew-blode-manual-only-seo-tasks.md", "id": "c4152c5f1c9fad2c"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
17
|
+
{"id": "blode-co-generic-framing-17", "text": "DataForSEO Instant Pages on the GSC URLs in seeds.json. Spend this run: $0.015. Cumulative: $0.23 of $20. Raw: docs/seo/onpage-2026-08-21.json.", "context": {"docType": "explanation", "headingAbove": "On-page Instant Pages (2026-08-21)", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/seo/onpage-2026-08-21.md", "id": "9b5ebeec52ee50b6"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
18
|
+
{"id": "blode-co-generic-framing-18", "text": "\\ Charles Cook", "context": {"docType": "explanation", "headingAbove": "How we're spending our marketing budget in 2026 (with actual $ figures)", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/posthog-marketing-budget-2026.md", "id": "565ad51c1d588b25"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
19
|
+
{"id": "blode-co-generic-framing-19", "text": "It\u2019s also the only way I\u2019ve ever found out anything that mattered.", "context": {"docType": "lesson", "headingAbove": "Shipping at 80 percent", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/ship-fast-or-die.mdx", "id": "3b42eb62e7e60b84"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
20
|
+
{"id": "blode-co-generic-framing-20", "text": "Motion is the other one. motion.dev for the library, animations.dev and devouringdetails.com for the eye. I keep most transitions between 0.2 and 0.3 seconds, prefer ease-out, animate transforms and opacity, respect prefers-reduced-motion. They should feel responsive, not try to impress.", "context": {"docType": "lesson", "headingAbove": "Icons, type, and motion", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/you-dont-have-to-make-ai-slop.mdx", "id": "3cc1609c8bddea50"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
21
|
+
{"id": "blode-co-generic-framing-21", "text": "I stop when it feels deliberate. Past that, I\u2019m usually decorating.", "context": {"docType": "lesson", "headingAbove": "Between product and design", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/you-can-sense-carelessness.mdx", "id": "ceaea4c2d56ef2f1"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
22
|
+
{"id": "blode-co-generic-framing-22", "text": "Variable", "context": {"docType": "unknown", "headingAbove": "Credentials written", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "posthog-source-maps-report.md", "id": "169f2a742ecf7e1d"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
23
|
+
{"id": "blode-co-generic-framing-23", "text": "Google Search Console", "context": {"docType": "unknown", "headingAbove": "Connected Tools", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "posthog-self-driving-report.md", "id": "965438d019573875"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
24
|
+
{"id": "blode-co-generic-framing-24", "text": "https://blode.co/openapi.json", "context": {"docType": "unknown", "headingAbove": "Endpoints an agent can use", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/public/auth.md", "id": "3bbba8628d8287d3"}, "split": "holdout", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
25
|
+
{"id": "blode-co-generic-framing-25", "text": "\"Let's set up project context first.\"", "context": {"docType": "agent-instructions", "headingAbove": "Suggested next actions", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/seo-coach/SKILL.md", "id": "d287bf9a64fbb3fa"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
26
|
+
{"id": "blode-co-generic-framing-26", "text": "Two zones emitted exactly that for a few minutes today before it was caught by diffing served HTML. It does not fail a build and it does not show in the document title.", "context": {"docType": "agent-instructions", "headingAbove": "The template does not reach og:title", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/zone-conventions.md", "id": "8f3868f2eb1d88e6"}, "split": "holdout", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
27
|
+
{"id": "blode-co-generic-framing-27", "text": "Link: HTTP header values in next.config.ts", "context": {"docType": "agent-instructions", "headingAbove": "Child app gotchas (what basePath does and does not cover)", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": ".claude/knowledge/architecture-boundaries.md", "id": "7d9450cda3e54f78"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
28
|
+
{"id": "blode-co-generic-framing-28", "text": "What I had was good, but I could do better. I did the following:", "context": {"docType": "explanation", "headingAbove": "Do backlinks better! \u200b", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/how-we-fixed-my-seo.md", "id": "71a5d422652ecadd"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": true}, "labelSource": "sweep", "labelNote": "Generic improvement opener with no named case or concrete detail."}
|
|
29
|
+
{"id": "blode-co-generic-framing-29", "text": "2 startup exits is acceptable as a headline shorthand, but the body copy should still name the companies and acquirers.", "context": {"docType": "explanation", "headingAbove": "Copy rules", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/linkedin-seo-update-march-2026.md", "id": "ec07c42d704b9ce7"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
30
|
+
{"id": "blode-co-generic-framing-30", "text": "Send 7,174 impressions View analytics Promote this post to reach people who matter to you. Boost Feed post number 11 View Matthew Blode\u2019s graphic link Matthew BlodeMatthew Blode \u2022 YouVerified \u2022 You Co-head of LinkApps at Linktree | Forbes 30 under 30 | Exited to Linktree and me&uCo-head of LinkApps at Linktree | Forbes 30 under 30 | Exited to Linktree and me&u 3mo \u2022 Edited \u2022 3 months ago \u2022 Edited \u2022 Visible to anyone on or off LinkedIn", "context": {"docType": "explanation", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/linkedin.md", "id": "33276ea373544164"}, "split": "holdout", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
31
|
+
{"id": "blode-co-generic-framing-31", "text": "Every role where you coast is a year of zero returns. Every role where you\u2019re a little overwhelmed is a year of high-interest deposits. Returns from urgency stack on returns from scale, which stack on returns from platform ambition. You can\u2019t buy it or shortcut it, you earn it by showing up to hard environments for years.", "context": {"docType": "lesson", "headingAbove": "The compounding part", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/your-career-compounds.mdx", "id": "038908d23602df9a"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Makes a specific argument about difficult career roles through a financial metaphor, rather than a topic-neutral opener."}
|
|
32
|
+
{"id": "blode-co-generic-framing-32", "text": "Then I built agent-skills-creator: a skill that creates skills. Then allmd, which turns any URL, PDF, video or audio into clean markdown, tagline \u201cturn the whole universe into a skill.\u201d", "context": {"docType": "lesson", "headingAbove": "The meta moment", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/skills-are-the-new-packages.mdx", "id": "e68e21c2e829065a"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
33
|
+
{"id": "blode-co-generic-framing-33", "text": "AI goes rogue on decoration: glowing borders, gradients, illustrations, badges, tooltips, seventeen things nobody asked for. I delete most of it. If a paragraph can be a sentence, I make it a sentence. If a sentence can be a label, I make it a label. If a label can be removed, I remove it.", "context": {"docType": "lesson", "headingAbove": "Icons, type, and motion", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/you-dont-have-to-make-ai-slop.mdx", "id": "d38cb5a151f13863"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
34
|
+
{"id": "blode-co-generic-framing-34", "text": "The old matthewblode.com hosts redirect permanently to the matching path on blode.co.", "context": {"docType": "unknown", "headingAbove": "Structure", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "README.md", "id": "af607ba0ee56c097"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
35
|
+
{"id": "blode-co-generic-framing-35", "text": "Contact desktop", "context": {"docType": "unknown", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": ".captain/browser/report.md", "id": "6626adb2b06421d2"}, "split": "holdout", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
36
|
+
{"id": "blode-co-generic-framing-36", "text": "competitive-landscape: when the market is unclear", "context": {"docType": "agent-instructions", "headingAbove": "8. Recommend first workflow", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/seo-project-setup/SKILL.md", "id": "260bb53c62def28f"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
37
|
+
{"id": "blode-co-generic-framing-37", "text": "OpenSEO MCP tools provide SEO data such as keyword research, exact ranked keywords, search volume, SERPs, SERP competitors, local business and Maps data, domain overviews, backlinks, saved keywords, projects, and rank trackers.", "context": {"docType": "agent-instructions", "headingAbove": "Tool coaching", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/seo-coach/SKILL.md", "id": "5a3a49a727a7ddf1"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
38
|
+
{"id": "blode-co-generic-framing-38", "text": "Similar words do not guarantee the same cluster.", "context": {"docType": "agent-instructions", "headingAbove": "Workflow", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/keyword-clustering/SKILL.md", "id": "666234cc0811aa9f"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
39
|
+
{"id": "blode-co-generic-framing-39", "text": "What to include", "context": {"docType": "explanation", "headingAbove": "Writer content brief", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/index-bot-config.md", "id": "b6bae694ec541422"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
40
|
+
{"id": "blode-co-generic-framing-40", "text": "Sitemap lastmod is driven by the same field. Never auto-stamped on build. Mueller on auto-stamped sitemap dates: \"usually a sign they have a broken sitemap generator setup. It has no positive effect.\"", "context": {"docType": "explanation", "headingAbove": "3.6 Freshness, honestly", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/educational-resource-format-spec.md", "id": "388bd04fbb0454af"}, "split": "dev", "kind": "paragraph", "categoryId": "reader-first-framing", "labels": {"copywriting-generic-framing": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
41
|
+
{"id": "blode-co-unnatural-polish-01", "text": "For local SEO, use search_local_businesses and get_local_serp_results around the relevant business location(s) before drawing local-pack conclusions. Add get_google_business_questions only when Q&A evidence matters.", "context": {"docType": "agent-instructions", "headingAbove": "Workflow", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/competitor-analysis/SKILL.md", "id": "86b96455298fac7a"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
42
|
+
{"id": "blode-co-unnatural-polish-02", "text": "next start silently fails with EADDRINUSE when port 3000 is already taken, but the existing server keeps responding with the OLD build, leading to confusing \"my middleware change isn't taking effect\" debugging. Always run lsof -ti :3000 | xargs kill -9 before npm run start if a previous server may be running.", "context": {"docType": "agent-instructions", "headingAbove": "Always-fresh dev: free port 3000 before debugging stale responses", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/architecture-boundaries.md", "id": "c02f90921f9ae141"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
43
|
+
{"id": "blode-co-unnatural-polish-03", "text": "HTML pages are deliberately not captured here: posthog-js already records them, and adding a proxy capture would double count every pageview. lib/agent-surfaces.test.ts is the guard on that boundary, and it also excludes robots.txt and the sitemaps for volume.", "context": {"docType": "agent-instructions", "headingAbove": "Analytics for these surfaces lives in the proxy, not the handlers", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/architecture-boundaries.md", "id": "4002246453371276"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
44
|
+
{"id": "blode-co-unnatural-polish-04", "text": "Update the highest-leverage controlled profiles.", "context": {"docType": "explanation", "headingAbove": "Matthew Blode Manual-Only SEO Tasks", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/matthew-blode-manual-only-seo-tasks.md", "id": "5f2a6ca6c1923775"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
45
|
+
{"id": "blode-co-unnatural-polish-05", "text": "glide font is Typedepot / piracy SERPs on Google. Perplexity sometimes cites /glide as one of three families, sometimes not. Qualify the name, do not write a new post.", "context": {"docType": "explanation", "headingAbove": "Ranked backlog", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "docs/blog-seo-strategy-2026.md", "id": "2b2bc21279cdef6e"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
46
|
+
{"id": "blode-co-unnatural-polish-06", "text": "TanStack Query", "context": {"docType": "explanation", "headingAbove": "The front-end stack AI loves", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "docs/ship-fast-or-die.md", "id": "c18a976445a8a789"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
47
|
+
{"id": "blode-co-unnatural-polish-07", "text": "At Fingertip and VenueSafe we shipped early, watched real users, and iterated hard. Speed was survival.", "context": {"docType": "lesson", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/ship-fast-or-die.mdx", "id": "4e7c4436025d8578"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
48
|
+
{"id": "blode-co-unnatural-polish-08", "text": "If you have permission to modify the font, the free Static to Variable CLI runs that loop from a folder of separate weights:", "context": {"docType": "lesson", "headingAbove": "A frozen cent sign", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/static-fonts-to-one-variable-font.mdx", "id": "1cbdfc53fbd2f4c4"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
49
|
+
{"id": "blode-co-unnatural-polish-09", "text": "signals-scout-csp-violations", "context": {"docType": "unknown", "headingAbove": "Enabled (4)", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "posthog-self-driving-report.md", "id": "f6fcf1031ca51c87"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
50
|
+
{"id": "blode-co-unnatural-polish-10", "text": "signals-scout-web-analytics", "context": {"docType": "unknown", "headingAbove": "Enabled (4)", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "posthog-self-driving-report.md", "id": "6b44e06ebf3b150d"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
51
|
+
{"id": "blode-co-unnatural-polish-11", "text": "Several zones serve six or more separate ld+json blocks. Disconnected nodes cannot be merged into one entity. Emit a single script containing one @graph:", "context": {"docType": "agent-instructions", "headingAbove": "Rule 3: one @graph, not N scripts", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/zone-conventions.md", "id": "bc47b09e879e4911"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
52
|
+
{"id": "blode-co-unnatural-polish-12", "text": "An explicit openGraph.images outranks the file convention. So declaring the right URL does fix a doubled static card. This file previously claimed the opposite, inferred from glide, but glide had no explicit images block at all, so it never tested precedence. diffhub did test it, by building twice with and without: the explicit block wins, and deleting it is what breaks the card. Which makes the explicit block a trap of its own, because it reads as redundant sitting next to a same-named file.", "context": {"docType": "agent-instructions", "headingAbove": "The one configuration to aim for", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/zone-conventions.md", "id": "56c369b1c87e7833"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
53
|
+
{"id": "blode-co-unnatural-polish-13", "text": "The page\u2026", "context": {"docType": "agent-instructions", "headingAbove": "The template does not reach og:title", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/zone-conventions.md", "id": "05a524e685c38724"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
54
|
+
{"id": "blode-co-unnatural-polish-14", "text": "331 impressions and 14 clicks, which is 59% of all named-query impressions and 100% of all named-query clicks. Everything outside this table produced zero clicks.", "context": {"docType": "explanation", "headingAbove": "4. Striking distance", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/semrush-gsc-opportunity-brief.md", "id": "b8b52d13ecc145fa"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
55
|
+
{"id": "blode-co-unnatural-polish-15", "text": "Since the author profile at the top of each post didn't answer all the E-E-A-T points, enriching my about page was the way forward. I updated the about page to include a richer bio and a list of places where my site has been featured:", "context": {"docType": "explanation", "headingAbove": "2. About page \u200b", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/how-we-fixed-my-seo.md", "id": "1d2d0f0c0425c133"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
56
|
+
{"id": "blode-co-unnatural-polish-16", "text": "Live SERP notes that change the backlog:", "context": {"docType": "explanation", "headingAbove": "Ranked backlog", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/blog-seo-strategy-2026.md", "id": "2091a24823ad2531"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
57
|
+
{"id": "blode-co-unnatural-polish-17", "text": "For us it was usually one of a few things, none of them technical. Work blocked waiting on approval, meetings that existed out of habit, fear of shipping something imperfect, vague ownership where nobody owned the outcome, optimising systems that didn\u2019t need to scale yet, too many projects and too much context switching. You fix that by changing the shape of the team, not by adding process.", "context": {"docType": "lesson", "headingAbove": "The shape of a fast team", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/ship-fast-or-die.mdx", "id": "6ec6309b4649ad6c"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
58
|
+
{"id": "blode-co-unnatural-polish-18", "text": "An agent skill is a markdown file: instructions in plain language, saved as .md, and your agent knows how to do something new. No API, no SDK, no build step.", "context": {"docType": "lesson", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/skills-are-the-new-packages.mdx", "id": "6d96d6ee474b1a1a"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
59
|
+
{"id": "blode-co-unnatural-polish-19", "text": "The scout coordinator picks up fresh configs within ~30 minutes. Each enabled scout runs once per day and draws from the 24-run daily budget. Findings are clustered into reports and appear in your Self-driving inbox. Immediately-actionable reports can kick off coding tasks directly from the inbox.", "context": {"docType": "unknown", "headingAbove": "What Happens Next", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "posthog-self-driving-report.md", "id": "3e3c551883cbe1ac"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
60
|
+
{"id": "blode-co-unnatural-polish-20", "text": "signals-scout-ai-observability", "context": {"docType": "unknown", "headingAbove": "Disabled (24)", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "posthog-self-driving-report.md", "id": "178f4a2bd6e9a273"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
61
|
+
{"id": "blode-co-unnatural-polish-21", "text": "Render it visibly on the zone's root page, not only in JSON-LD.", "context": {"docType": "agent-instructions", "headingAbove": "Rule 4: breadcrumbs start at the blode.co root, and are visible", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/zone-conventions.md", "id": "1bdda646d9e5ef0f"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
62
|
+
{"id": "blode-co-unnatural-polish-22", "text": "One separator per zone, in both templates. The pipe is the fleet default, but matching the zone's existing document template beats importing a character: convene uses \u00b7 in both, so its <title> and og:title agree by mechanism rather than by someone reusing the same suffix twice. That is the false negative this rule warns about further down, so a zone that avoids it structurally is doing better than one that complies literally. What the rule is actually against is the five different separators the fleet had.", "context": {"docType": "agent-instructions", "headingAbove": "Rule 8: one title pattern", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/zone-conventions.md", "id": "d1ac726d9bb85633"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
63
|
+
{"id": "blode-co-unnatural-polish-23", "text": "Keep one-off bugs as code fixes and regression tests. Put deterministic mechanical checks in CI or lint instead of Greptile.", "context": {"docType": "agent-instructions", "headingAbove": "Preserve review learnings", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/merge-ready/SKILL.md", "id": "267df6c483336525"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
64
|
+
{"id": "blode-co-unnatural-polish-24", "text": "Example structure:", "context": {"docType": "explanation", "headingAbove": "Content format for AEO", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/matthew-blode-seo-plan-march-2026.md", "id": "ca52ee040e3b53b5"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
65
|
+
{"id": "blode-co-unnatural-polish-25", "text": "4. Voice-first setup for driving coding agents Cluster 1. Target \"voice coding setup\" and \"voice dictation for developers\", not the bare head term: voice coding is semantically contaminated by speech-codec patents (G.711, CELP) and only ~3 of 9 results concern programming. Why it wins: the entire informational surface is owned by dictation vendors writing about their own category, with Willow holding two slots. Nobody independent writes about dictating prompts to agents rather than dictating syntax, which is the actual 2026 workflow. Evidence in hand: Rubber Duck and Commandment, plus the barge-in and VAD notes in rubber-duck/docs.", "context": {"docType": "explanation", "headingAbove": "Second wave", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/blog-seo-strategy-2026.md", "id": "05f64138e3af4a35"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
66
|
+
{"id": "blode-co-unnatural-polish-26", "text": "Resource pages never duplicate a /writing title. They answer a question; posts make a claim.", "context": {"docType": "explanation", "headingAbove": "3.7 Separating this from /writing", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "docs/archive/educational-resource-format-spec.md", "id": "79e28012cf51793e"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
67
|
+
{"id": "blode-co-unnatural-polish-27", "text": "Typography is one of the highest-impact things I can change, and engineering-led teams almost always underinvest in it.", "context": {"docType": "lesson", "headingAbove": "Icons, type, and motion", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/you-dont-have-to-make-ai-slop.mdx", "id": "3aba3319605672f7"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
68
|
+
{"id": "blode-co-unnatural-polish-28", "text": "At Fingertip, care was deliberate improvement after the MVP shipped. I kept coming back to the same surfaces. The page editor went from a static list to a drag-and-drop grid. The default share preview went from the Fingertip logo to a live screenshot of the user\u2019s page. Stock shadcn became a considered system with central icons, custom typography, taller inputs and a curated colour palette. Each change was small.", "context": {"docType": "lesson", "headingAbove": "A thousand small iterations", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/you-can-sense-carelessness.mdx", "id": "953039f4b8af17af"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Specific firsthand changes to a named product retain personal texture."}
|
|
69
|
+
{"id": "blode-co-unnatural-polish-29", "text": "Run commands from the repository root:", "context": {"docType": "unknown", "headingAbove": "Develop", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/README.md", "id": "6107ae6833b2dc5f"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
70
|
+
{"id": "blode-co-unnatural-polish-30", "text": "signals-scout-experiments", "context": {"docType": "unknown", "headingAbove": "Disabled (24)", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "posthog-self-driving-report.md", "id": "f34b2c2bb2d0efc2"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
71
|
+
{"id": "blode-co-unnatural-polish-31", "text": "If keywords are not provided, use list_saved_keywords for saved sets, research_keywords for seed discovery, or get_ranked_keywords when the user starts from a target domain.", "context": {"docType": "agent-instructions", "headingAbove": "Required inputs", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/keyword-clustering/SKILL.md", "id": "b8b2eb9159032dd8"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
72
|
+
{"id": "blode-co-unnatural-polish-32", "text": "So: never change metadataBase without reading the emitted og:image and canonical out of the built HTML, before and after. check-zones asserts both against doubling, but only after a deploy.", "context": {"docType": "agent-instructions", "headingAbove": "The one configuration to aim for", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.claude/knowledge/zone-conventions.md", "id": "3741e0800d1d7d98"}, "split": "holdout", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
73
|
+
{"id": "blode-co-unnatural-polish-33", "text": "A keyword list, saved keyword tag, seed topic, or target domain", "context": {"docType": "agent-instructions", "headingAbove": "Required inputs", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/.agents/skills/keyword-clustering/SKILL.md", "id": "8caf3094965a7222"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
74
|
+
{"id": "blode-co-unnatural-polish-34", "text": "Prompt and AI visibility: Profound if connected, otherwise the connected equivalent", "context": {"docType": "explanation", "headingAbove": "SEO program onboarding", "role": "list-item"}, "source": {"repo": "mblode/blode-co", "path": "docs/index-bot-config.md", "id": "3beba146c5c7e1fa"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
75
|
+
{"id": "blode-co-unnatural-polish-35", "text": "AI has raised the floor for coding dramatically. In design, the floor has barely moved. A junior developer with Claude Code can ship production-quality code. A junior designer with AI tools still produces junior design. The next real leap in AI-assisted product building will not be faster code generation. It will be giving agents the ability to see. To evaluate their own output visually, to understand spatial relationships, to develop something approaching aesthetic judgment. Until then, design remains human territory.", "context": {"docType": "explanation", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/blog/ai-can-code-but-it-cant-design.md", "id": "05034b44c3ffd15e"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "A forceful, contestable opinion about design versus code; polish alone is not this defect."}
|
|
76
|
+
{"id": "blode-co-unnatural-polish-36", "text": "An SEO and AEO program teammate. On first run it reads what you already connected, recommends a stack, and only asks for gaps. It does not hard-require Slack, Semrush, or Profound. Use those when they are connected. Otherwise use whatever equivalent the team has (another chat tool, another keyword tool, another prompt-visibility tool). Recaps go to whatever channel you use. Call-demand mining uses whatever recording tool you have. Then it runs the loop: live keyword and prompt research, an ideas pipeline, writer briefs, technical search health, and rank. It writes briefs, not articles. Quiet unless something needs a decision or a new row.", "context": {"docType": "explanation", "headingAbove": "Instructions", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "docs/index-bot-config.md", "id": "5f1c6cedaad27348"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
77
|
+
{"id": "blode-co-unnatural-polish-37", "text": "The ones worth stealing from are the calm products rather than the feature-stuffed ones: Zed Editor, OpenAI, Mintlify, Raycast, Linear, Things 3, Family Crypto Wallet, ElevenLabs. They show only what matters and their interactions feel precise.", "context": {"docType": "lesson", "headingAbove": "Stealing from tasteful products", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/you-dont-have-to-make-ai-slop.mdx", "id": "e80cee618ab00f23"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
78
|
+
{"id": "blode-co-unnatural-polish-38", "text": "The loop only runs that fast with the right scaffolding around it. TypeScript gives it types to reason about. Ultracite keeps the formatting and linting consistent. Git lets you experiment and roll back. Logs give it context when it\u2019s debugging.", "context": {"docType": "lesson", "headingAbove": "The Claude Code loop", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "apps/web/content/writing/ship-fast-or-die.mdx", "id": "8df1bf05e89df695"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
79
|
+
{"id": "blode-co-unnatural-polish-39", "text": "All other tools (Linear, Jira, Sentry, Zendesk, etc.) \u2014 not used (not selected).", "context": {"docType": "unknown", "headingAbove": "Connected Tools", "role": "body"}, "source": {"repo": "mblode/blode-co", "path": "posthog-self-driving-report.md", "id": "a8655adf0e030803"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|
|
80
|
+
{"id": "blode-co-unnatural-polish-40", "text": "session_replay", "context": {"docType": "unknown", "headingAbove": "Signal Sources", "role": "cell"}, "source": {"repo": "mblode/blode-co", "path": "posthog-self-driving-report.md", "id": "659875a3a1971a30"}, "split": "dev", "kind": "paragraph", "categoryId": "machine-prose", "labels": {"copywriting-unnatural-polish": false}, "labelSource": "sweep", "labelNote": "Concrete information, instruction, fragment, or personal observation; no affirmative evidence of the stated defect."}
|