@koda-sl/baker-cli 0.170.0-dev.9ace16483 → 0.173.0-dev.11e26a37c.1276eb11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -45
- package/dist/cli.js +2095 -1625
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -563,6 +563,13 @@ baker ads google asset-groups create --customer-id 1234567890 --campaign-ref g_t
|
|
|
563
563
|
--descriptions "Best widgets around.,Ships tomorrow." --business-name "Acme" --final-urls https://example.com
|
|
564
564
|
baker ads google asset-groups update g_temp_<assetgroup> --status ENABLED
|
|
565
565
|
|
|
566
|
+
# Refresh a LIVE asset group's creatives — attach the replacement first, then detach the old one
|
|
567
|
+
baker ads google assets create --customer-id 1234567890 --file new-image-asset.json # -> g_temp_<img>
|
|
568
|
+
baker ads google asset-groups attach --customer-id 1234567890 \
|
|
569
|
+
--asset-group-ref 6718302454 --asset-ref g_temp_<img> --field-type PORTRAIT_MARKETING_IMAGE
|
|
570
|
+
baker ads google asset-groups detach customers/1234567890/assetGroupAssets/6718302454~402654762568~PORTRAIT_MARKETING_IMAGE \
|
|
571
|
+
--customer-id 1234567890
|
|
572
|
+
|
|
566
573
|
# Review / undo / amend staged changes
|
|
567
574
|
baker ads google draft list # readable campaign ▸ ad group ▸ ad tree + completeness advisories
|
|
568
575
|
baker ads google draft list --json # raw JSON envelope for scripting
|
|
@@ -572,7 +579,7 @@ baker ads google draft remove g_temp_ab12 # cascades to dependents
|
|
|
572
579
|
baker ads google draft clear
|
|
573
580
|
```
|
|
574
581
|
|
|
575
|
-
Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets` (create/update/attach/detach), `asset-groups` (create/update — Performance Max), `audiences`, `conversions` (create/update/goal), `bidding-strategies`, `labels`, `campaign-criteria`, and `draft`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa`, `--max-cpc` take major units; `--target-roas` a ratio. `--max-cpc` sets the max CPC bid ceiling for `TARGET_IMPRESSION_SHARE` / `TARGET_SPEND` / `PERCENT_CPC` and must be paired with `--bidding-strategy`. Less-common ops accept a `--file <payload.json>` (flags override file keys). Updates target a resource name or bare id as the positional argument; a target that names an op staged earlier **amends it in place**.
|
|
582
|
+
Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets` (create/update/attach/detach), `asset-groups` (create/update/attach/detach — Performance Max), `audiences`, `conversions` (create/update/goal), `bidding-strategies`, `labels`, `campaign-criteria`, and `draft`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa`, `--max-cpc` take major units; `--target-roas` a ratio. `--max-cpc` sets the max CPC bid ceiling for `TARGET_IMPRESSION_SHARE` / `TARGET_SPEND` / `PERCENT_CPC` and must be paired with `--bidding-strategy`. Less-common ops accept a `--file <payload.json>` (flags override file keys). Updates target a resource name or bare id as the positional argument; a target that names an op staged earlier **amends it in place**.
|
|
576
583
|
|
|
577
584
|
**Primary vs secondary conversion actions** — `conversions update <id> --primary` makes an action a primary action (automated bidding optimizes toward it); `--no-primary` (or `--primary=false`) demotes it to secondary (reported only) — the spaced form `--primary false` is rejected, since a boolean flag written with a space is set to true and the word dropped. `conversions create` stages a new action as **secondary** unless its `--file` payload sets `"primaryForGoal": true`, so a new action never silently joins the bidding target of a Maximize Conversions or Target CPA campaign. The staged card shows an "Action optimization" row with the before/after role.
|
|
578
585
|
|
|
@@ -1047,7 +1054,7 @@ Each finding: `{id, area, check, status, severity, evidence, fix: {explanation,
|
|
|
1047
1054
|
|
|
1048
1055
|
- OAuth tokens auto-refresh server-side. On 401, reconnect LinkedIn from dashboard → integrations.
|
|
1049
1056
|
- Account scoping: backend rejects calls against an account not in the picker selection.
|
|
1050
|
-
- Pinned API version: `Linkedin-Version:
|
|
1057
|
+
- Pinned API version: `Linkedin-Version: 202606` (declared in `oauth/constants.ts`). Bump as a coordinated change — LinkedIn deprecates monthly with a ~12-month support window. The floor is `TRACKING_PARAM_DYNAMIC_MIN_API_VERSION` (`CREATIVE_NAME` needs 202606), asserted in `ads/linkedin/utils.test.ts`.
|
|
1051
1058
|
- Cache TTLs: accounts/account-detail 1h, listings 30m, analytics 15m–6h (depends on date range), facets 7d, urn-resolve 1h, bid-pricing/forecast 6h, audience-size 1h.
|
|
1052
1059
|
- `--skip-cache` (server-side) and `--no-cache` (client-side, where supported) on any command.
|
|
1053
1060
|
|
|
@@ -1159,46 +1166,6 @@ baker ga4 query --dimensions "date,sessionSourceMedium" --metrics "sessions,conv
|
|
|
1159
1166
|
|
|
1160
1167
|
---
|
|
1161
1168
|
|
|
1162
|
-
### Alarms (`baker alarm`)
|
|
1163
|
-
|
|
1164
|
-
How an unattended run reaches a human about something that cannot wait until tomorrow. This is the only thing that interrupts someone out of hours — anything that can wait is `baker actions create --priority urgent`.
|
|
1165
|
-
|
|
1166
|
-
Every alarm must carry the evidence that contradicts everything being fine, and the backend **refuses** it when the evidence does not. An all-zeros analytics reading is not evidence on its own: broken tracking, no traffic, a dead page and a failed check all look identical. Paid clicks over the same window are what turn zeros into proof.
|
|
1167
|
-
|
|
1168
|
-
---
|
|
1169
|
-
|
|
1170
|
-
### `baker alarm raise`
|
|
1171
|
-
|
|
1172
|
-
Wake a human. Reasons: `tracking_broken`, `spend_anomaly`, `access_lost`, `site_down`.
|
|
1173
|
-
|
|
1174
|
-
```bash
|
|
1175
|
-
baker alarm raise --reason tracking_broken --url https://acme.com/demo --window-hours 24 \
|
|
1176
|
-
--analytics-answered true --sessions 0 --clicks 47 --spend-micros 82000000
|
|
1177
|
-
|
|
1178
|
-
baker alarm raise --reason site_down --url https://acme.com/demo --http-status 503 # 'none' if it never responded
|
|
1179
|
-
baker alarm raise --reason spend_anomaly --platform google-ads --window-hours 24 \
|
|
1180
|
-
--spend-micros 400000000 --baseline-spend-micros 50000000
|
|
1181
|
-
baker alarm raise --reason access_lost --platform google-ads --provider-error "USER_PERMISSION_DENIED"
|
|
1182
|
-
```
|
|
1183
|
-
|
|
1184
|
-
`--analytics-answered false` means the provider errored, timed out, or the token expired. That is **not** zero sessions, and the gate refuses it — otherwise a broken check pages somebody about the client's tracking.
|
|
1185
|
-
|
|
1186
|
-
Response: `raised: true` (a human was contacted), `deduped: true` (already open — counted, not re-sent), or `raised: false` with a `refusal` explaining what the evidence failed to show. Repeats for an open condition never page twice.
|
|
1187
|
-
|
|
1188
|
-
---
|
|
1189
|
-
|
|
1190
|
-
### `baker alarm checked`
|
|
1191
|
-
|
|
1192
|
-
Record that a check came back clean.
|
|
1193
|
-
|
|
1194
|
-
```bash
|
|
1195
|
-
baker alarm checked --reason tracking_broken --scope "6 landings"
|
|
1196
|
-
```
|
|
1197
|
-
|
|
1198
|
-
Run it whenever a check finds nothing. Without it, "nothing was wrong" and "nobody looked" are the same silence. It never closes an alarm somebody was already woken for — one clean reading does not prove a fix.
|
|
1199
|
-
|
|
1200
|
-
---
|
|
1201
|
-
|
|
1202
1169
|
### Google Search Console (`baker gsc`)
|
|
1203
1170
|
|
|
1204
1171
|
GSC commands for PPC-SEO arbitrage, brand halo analysis, and negative keyword discovery.
|
|
@@ -2706,7 +2673,7 @@ Permissions enforced server-side:
|
|
|
2706
2673
|
|
|
2707
2674
|
### `baker mcp connected | list | add | remove`
|
|
2708
2675
|
|
|
2709
|
-
See
|
|
2676
|
+
See everything a chat can reach — managed integrations, custom servers, and connected platforms — and manage **custom MCP servers** by pointing the agent at any remote HTTPS MCP endpoint. Registered tools appear as `mcp__<name>__*` on the next chat message.
|
|
2710
2677
|
|
|
2711
2678
|
```bash
|
|
2712
2679
|
baker mcp connected
|
|
@@ -2717,6 +2684,7 @@ baker mcp add --name mine --url https://my.dev/mcp --scope user
|
|
|
2717
2684
|
baker mcp remove --name weather
|
|
2718
2685
|
```
|
|
2719
2686
|
|
|
2687
|
+
- `connected` returns three lists. `managed` and `custom` are MCP tools the agent calls directly; `platforms` are the accounts the company signed in to (HubSpot, Google Ads, GA4, Search Console, Tag Manager, LinkedIn Ads), each with its login and the resources it may read. A platform has **no** `mcp__*` tools — it is read through its own command family — so it is listed here to keep the agent from concluding a working connection is missing.
|
|
2720
2688
|
- `--url` must be the **full MCP endpoint** (commonly ending in `/mcp`), HTTPS and public; loopback / private / cloud-metadata hosts are rejected. A bare domain frequently accepts the OAuth sign-in and then serves no tools, so every address is handshake-checked after it changes: `connected` and `list` report `usable: false` plus a `problem` (and a `suggestedAddress` when the server advertises a better endpoint) for a server that isn't serving tools, or `lastCheck: failed` when the last check only hit an outage — that one may work anyway, and a later turn re-checks it automatically. Repointing a server at a different host clears its saved sign-in: a token belongs to the host that issued it, so the server has to be connected again.
|
|
2721
2689
|
- Scope is two independent choices — audience (just you / everyone) × level (this company / whole org):
|
|
2722
2690
|
- `--scope company` (default) — everyone in this company.
|
|
@@ -2727,6 +2695,52 @@ baker mcp remove --name weather
|
|
|
2727
2695
|
|
|
2728
2696
|
---
|
|
2729
2697
|
|
|
2698
|
+
### `baker facts list | record`
|
|
2699
|
+
|
|
2700
|
+
What the company's own people said about their business, settled with them in the guided Setup: what counts as a win, who buys, where leads land, which channels have worked before, how they want to be talked to, and their red lines. Each entry carries where it came from — a machine check, a human answer, a call, or the agent.
|
|
2701
|
+
|
|
2702
|
+
Read it before writing anything customer-facing. Record what the user tells you mid-conversation so it reaches Setup instead of dying in a paragraph.
|
|
2703
|
+
|
|
2704
|
+
```bash
|
|
2705
|
+
baker facts list # settled and contested entries
|
|
2706
|
+
baker facts list --full # plus unanswered questions and unconfirmed suggestions
|
|
2707
|
+
baker facts record --key crm --value salesforce --quote "we moved off HubSpot in March"
|
|
2708
|
+
```
|
|
2709
|
+
|
|
2710
|
+
**`list` flags:**
|
|
2711
|
+
|
|
2712
|
+
| Flag | Description |
|
|
2713
|
+
|----------|-----------------------------------------------------------------|
|
|
2714
|
+
| `--full` | Include unanswered questions and unconfirmed suggestions |
|
|
2715
|
+
|
|
2716
|
+
**`record` flags:**
|
|
2717
|
+
|
|
2718
|
+
| Flag | Description |
|
|
2719
|
+
|----------------|-------------------------------------------------------------------------|
|
|
2720
|
+
| `--key` | Which question this answers (see `baker facts list --full`) — required |
|
|
2721
|
+
| `--value` | The answer, in the user's own terms — required |
|
|
2722
|
+
| `--quote` | What the user actually said, shown when they confirm it |
|
|
2723
|
+
| `--selections` | Comma-separated option values, for questions that take a set |
|
|
2724
|
+
|
|
2725
|
+
**Response:**
|
|
2726
|
+
|
|
2727
|
+
```json
|
|
2728
|
+
{
|
|
2729
|
+
"ok": true,
|
|
2730
|
+
"data": {
|
|
2731
|
+
"facts": [
|
|
2732
|
+
{ "key": "crm", "question": "Where do your leads land?", "value": "hubspot", "source": "answered", "settled": true }
|
|
2733
|
+
],
|
|
2734
|
+
"missing": ["target-customer"]
|
|
2735
|
+
},
|
|
2736
|
+
"meta": { "count": 1 }
|
|
2737
|
+
}
|
|
2738
|
+
```
|
|
2739
|
+
|
|
2740
|
+
`record` always files a **suggestion**, never a settled answer — a human confirms it in Setup. Keys that are checked against the company's own systems (conversion tracking) are refused: nothing said in a conversation can satisfy them.
|
|
2741
|
+
|
|
2742
|
+
---
|
|
2743
|
+
|
|
2730
2744
|
### `baker history list`
|
|
2731
2745
|
|
|
2732
2746
|
Unified account history (audit log) — everything that changed on the account, newest first: publishes, chat lifecycle, backlog actions, team changes, setup links, tag and schedule changes, executed ad-platform writes, followed advertisers, media (images, videos, testimonials), creative generations, reports, domains, integrations, and API keys. Read-only.
|
|
@@ -2763,9 +2777,9 @@ baker history list --category ads --full # ad writes with raw metadata
|
|
|
2763
2777
|
|
|
2764
2778
|
---
|
|
2765
2779
|
|
|
2766
|
-
### `baker hubspot forms list | view` · `baker hubspot meetings list | view`
|
|
2780
|
+
### `baker hubspot forms list | view | submissions` · `baker hubspot workflows list` · `baker hubspot pipelines list` · `baker hubspot contacts summary | lookup` · `baker hubspot meetings list | view`
|
|
2767
2781
|
|
|
2768
|
-
Read-only view of the company's **connected HubSpot account** — the forms and meeting links (calendars) that live there,
|
|
2782
|
+
Read-only view of the company's **connected HubSpot account** — the forms and meeting links (calendars) that live there, the leads those forms received, and the workflows, deal pipelines, and contact records a lead runs through afterwards. Fetched from HubSpot live on every call; nothing is cached and nothing is ever written back.
|
|
2769
2783
|
|
|
2770
2784
|
The reason this exists: a form's **post-submit action** (inline thank-you vs redirect to a URL), its embed version, its consent mode, and a calendar's booking fields are *not* persisted onto the flow node when a form is picked. They cannot be read from `_data.json` or `baker flows show` — only from here.
|
|
2771
2785
|
|
|
@@ -2776,6 +2790,12 @@ baker hubspot forms list --embed-type legacy # only legacy-embed forms
|
|
|
2776
2790
|
baker hubspot forms view <formId> # fields, consent mode, full configuration
|
|
2777
2791
|
baker hubspot forms view <formId> --full # + the raw HubSpot payload
|
|
2778
2792
|
baker hubspot forms view <formId> --as-node # the form.external blob for a flow node
|
|
2793
|
+
baker hubspot forms submissions <formId> # how many leads it received, and when
|
|
2794
|
+
baker hubspot forms submissions <formId> --days 90 --full
|
|
2795
|
+
baker hubspot workflows list --enabled-only # what runs after a lead is captured
|
|
2796
|
+
baker hubspot pipelines list # where a lead lands, and the stages after it
|
|
2797
|
+
baker hubspot contacts summary --days 30 # are recent leads being worked? (counts only)
|
|
2798
|
+
baker hubspot contacts lookup lead@example.com # was this one lead picked up?
|
|
2779
2799
|
baker hubspot meetings list # every calendar + whether booking redirects
|
|
2780
2800
|
baker hubspot meetings view <slug> # booking-form fields for one calendar
|
|
2781
2801
|
baker hubspot meetings view <slug> --as-node # the form.external blob for a flow node
|
|
@@ -2783,9 +2803,16 @@ baker hubspot meetings view <slug> --as-node # the form.external blob for a f
|
|
|
2783
2803
|
|
|
2784
2804
|
**`forms list` flags:** `--search <text>` (name substring), `--redirecting-only`, `--embed-type legacy|v4|unknown`. Rows carry only what it takes to choose; `portalId` is returned once on `data`, not per row.
|
|
2785
2805
|
**`forms view`:** positional `<formId>`; `--full` adds the untouched HubSpot payload; `--as-node` returns the resource blob instead of the readable view.
|
|
2806
|
+
**`forms submissions`:** positional `<formId>`; `--days <1-365>` (default 30) sets the window; `--full` adds each submission's field values. The default response is counts and dates only — `total`, `inWindow`, `lastSubmittedAt`, and per-day counts — because submissions carry the lead's own contact details. `truncated: true` means the page budget ran out before the window was covered, so `inWindow` is a floor rather than an exact count.
|
|
2807
|
+
**`workflows list` flags:** `--search <text>`, `--enabled-only`. Disabled workflows are listed too — a form wired to one that is off looks connected and does nothing.
|
|
2808
|
+
**`pipelines list`:** no flags; stages come back in the order they run in HubSpot.
|
|
2809
|
+
**`contacts summary` flags:** `--days <1-365>` (default 30) over contact creation date. Returns counts only — total contacts, how many have an owner, and the lifecycle-stage split — never a name or an email. `truncated: true` means the page budget ran out, so the counts are floors.
|
|
2810
|
+
**`contacts lookup`:** positional `<email>`; returns that contact's lifecycle stage, owner, the account (company) it was filed under, and associated deals with their stage. `dealsReadable: false` / `companyReadable: false` mean the connection does not cover deals or accounts, so an empty `deals` or a null `company` is unknown rather than absent. `found: false` means no contact with that email — the lead never reached the CRM, which is a finding rather than an error.
|
|
2786
2811
|
**`meetings list` flags:** `--search <text>`, `--redirecting-only`.
|
|
2787
2812
|
**`meetings view`:** positional `<slug>` — the only reliable source for a calendar's booking fields (HubSpot omits `customParams` from the list endpoint, so `fieldCount: null` there means unknown, not zero); `--as-node` as above.
|
|
2788
2813
|
|
|
2814
|
+
**`HUBSPOT_RECONNECT_REQUIRED`** is returned when HubSpot is connected but that account never granted access to what the command needs — a HubSpot grant is fixed at the moment the user approves it, so an account connected before a capability shipped cannot serve it. It is not the same as `NO_HUBSPOT_CONNECTION`: the connection works, and only the user can widen it by reconnecting from Brain → Integrations → Tools. The error carries a `fix` describing exactly that.
|
|
2815
|
+
|
|
2789
2816
|
**`--as-node`** returns exactly the object a flow node's `form.external` expects, so an agent can wire a HubSpot step by editing the flow's `_data.json` instead of interrupting the user with the `request_flow_input` approval form. It includes two fields the dashboard picker omits and the renderer needs: `configuration.postSubmitAction` (drives post-submit redirect suppression) and `region` (selects the v4 embed host — without it a v4 form on a eu1/ap1 portal loads the wrong script host).
|
|
2790
2817
|
|
|
2791
2818
|
**Response:**
|
|
@@ -4939,6 +4966,10 @@ This CLI is designed for AI agent consumption. Key patterns:
|
|
|
4939
4966
|
|
|
4940
4967
|
- **0.168.0**: `baker ads google conversions goal` sets the **account-level** conversion goal (`CustomerConversionGoal`) — Google Ads' "Included in account-level goals" column, keyed by `(category, origin)`. `goal --category PURCHASE --origin WEBSITE --biddable` includes a conversion category in what the account bids toward; `--no-biddable` measures it only. This is the half the surface was missing: a conversion drives automated bidding only when BOTH its action is primary AND its category's account-level goal is biddable, which is why an account could show lead-form conversions as Primary and still bid toward nothing. Promoting an action whose goal is off now stages with a warning saying the promotion alone changes no spend, and names the command for the other half. One staged op per pair — a second op for the same `(category, origin)` is refused rather than silently overwriting the first at publish. Switching off the last goal that still has a primary action behind it is refused like a last demotion, and the "no primary left" guard now discounts actions whose goal is already off (they were never steering bidding), while still treating an unread goal as bidding. A goal staged against an account that does not own conversion tracking is refused up front with the id that does, instead of failing the whole atomic batch at publish. The staged card gains "Included in account-level goals", "Conversion category" and "Where it happens" rows.
|
|
4941
4968
|
|
|
4969
|
+
- **0.170.0**: `baker ads google asset-groups attach|detach` — link and unlink one asset on a Performance Max asset group that **already exists**, which is what refreshing a live group's creatives actually is. Until now nothing could do it: `asset-groups create` links a group's opening assets inline, `asset-groups update` takes only `--name --final-urls --status`, and `assets attach` reaches campaign/adGroup/customer but not an asset group — so replacing a low-performing PMax image had no route through Baker at all and ended in the Google Ads interface. `create` also gains `--portrait-image-assets`; the 4:5 slot was missing from the asset group contract entirely, so a portrait image could not be staged even at creation. Ordering is the agent's job and is now stated everywhere it matters: operations publish in **stage order**, Google Ads removals carry no dependency edge and are not reordered to run last (`docs/publishing.md` → Known gap), so a detach staged before its replacement reaches Google first and is rejected when it was the last asset of that ratio — attach first, always.
|
|
4970
|
+
|
|
4971
|
+
Shipped with it: **staging a second removal of the same shape silently did nothing.** Amend-matching reduced any composite ref to the segment after its last `~` — built for the two-segment `adGroupId~adId` refs that ad and keyword targets heal to, where that segment really is the entity's own id. It is not, in three other places: an asset-group asset (`{assetGroup}~{asset}~{FIELD_TYPE}`) and a customer-level asset link (`{asset}~{FIELD_TYPE}`) both end in a field type every asset of that ratio shares, and a campaign shared set (`{campaign}~{sharedSet}`) ends in the list's id, the same in every campaign it is attached to. So the second detach amended the first instead of staging, returned the first's ref and reported success: a PMax creative swap could only ever remove one image per ratio, and detaching one negative list from three campaigns removed it from one. Nothing in the output said the rest had been dropped. Refs now compare whole, with one one-sided exception — a **bare** id still collapses onto the two-segment composite whose child it names, which is the only case the reduction ever existed for. The duplicate copy of the same helper in the staging path is gone rather than fixed twice. Also: linking an asset Google created automatically is refused at stage time on an asset group, not just on a campaign/ad-group/customer link — a Performance Max group is where auto-created creative is most likely to be picked up. And the live-context resolver now reads the asset group and asset an attach/detach names, so the review of a swap shows names and the images themselves rather than the ids inside a resource name.
|
|
4972
|
+
|
|
4942
4973
|
- **0.169.0**: `baker capabilities` — one call, before any platform work, that answers what Baker **cannot** do. Everything else (`baker schema`, the family docs, `--help`) describes what it can, so the only way to learn a capability was absent was to try it, usually after telling the user it would happen. Per surface (`google-ads`, `meta-ads`, `linkedin-ads`, `x-ads`, `tag-manager`, `ga4`, `gsc`) it reports: what is connected and which accounts/properties/containers were picked; whether writes are **live** or **simulated** for this company, and that Tag Manager has neither mode — completing the chat changes the real container; and a `limits` list separating *not built yet* from *the platform forbids it* from *we hold no permission for it*, because each leads to something different to tell the user. Naming one surface (`baker capabilities google-ads`) also returns `ops`: every change with its required and optional fields and the exact values each accepts — so "is there a field to demote a conversion action, and how is it spelled?" is one call instead of four guesses. Almost everything is derived rather than written down: the accounts and permissions come from the connection, the write mode from the company's gate, the writable entities and field contracts from the same schemas the backend parses with, and read-only vs read-write from the OAuth scopes the app actually requests (which is why "GA4 is read-only" is a fact, not a sentence). The handful of hand-written entries each carry a machine-checkable claim about the code behind them, and a coverage test fails when one stops holding. A rejected staged write on Google, Meta or LinkedIn now points at this command instead of leaving the agent to guess another field name.
|
|
4943
4974
|
|
|
4944
4975
|
## Publishing
|