Skip to content
Back to skills

New Content Plan

ASecurity

Use when: kullanıcı "yeni içerik planı", "content plan üret", "yeni makale listesi", "new content plan", "içerik üretim sırası", "TIVL taglemesi", "hangi makaleyi yazalım" der ya da /pseo-new-content-plan çağırır. Phase 7 content-gaps staging JSON'ını (DFS keyword_ideas candidate inventory) okur, opsiyonel keyword_overview + search_intent enrichment ekler ve `master.xlsx#new_content_plan` sheet'ine 14-col schema-locked row üretir. Also use when: aktif projenin master.xlsx#cluster_keywords she...

  • 27 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 2, 2026
ai-agentspythongoshellbashsql

Works with

  • mcp

Security analysis

A100/100

Scanned October 2, 2026

npx -y skills add popiliadam/platinum-seo-engine --skill new-content-plan --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of New Content Plan?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for New Content Plan
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/popiliadam-new-content-plan/badge)](https://www.skillsdirectory.com/skills/popiliadam-new-content-plan)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: new-content-plan
description: |
  Use when: kullanıcı "yeni içerik planı", "content plan üret", "yeni
  makale listesi", "new content plan", "içerik üretim sırası", "TIVL
  taglemesi", "hangi makaleyi yazalım" der ya da /pseo-new-content-plan
  çağırır. Phase 7 content-gaps staging JSON'ını (DFS keyword_ideas
  candidate inventory) okur, opsiyonel keyword_overview + search_intent
  enrichment ekler ve `master.xlsx#new_content_plan` sheet'ine 14-col
  schema-locked row üretir.
  Also use when: aktif projenin master.xlsx#cluster_keywords sheet'i
  cluster-map skill tarafından doldurulmuş; budget pre-flight PASS;
  content-gaps staging mevcut (`_state/staging/content_gaps_*.json`);
  TIVL tag'i (T/I/V/L) atanacak; yeni makale priority/word_count
  hedeflenecek.
  Do not use when: cluster atama (cluster-map önce), content gap discovery
  (content-gaps), tek sayfa decay analizi (content-decay), mevcut içerik
  optimizasyonu (content-improve), URL çakışması (cannibalization)
  gerekiyor — ayrı planning / discovery skill'leri. Master.xlsx
  yokken çağırma; init-project önce çalışmalı (DURUR #6). cluster_keywords
  sheet'i boşsa cluster-map önce çalışmalı (DURUR #6 CLUSTER_KEYWORDS_EMPTY).
  Budget aşılmışsa fallback YASAK (DURUR #1).
version: "1.0"
status: active
category: planning
inputs:
  project_slug:
    type: string
    required: true
    description: "Slug; resolves projects/{slug}/master.xlsx + project.config.json."
  content_gaps_staging_path:
    type: string
    required: false
    description: "_state/staging/content_gaps_keyword_ideas_*.json yolu; default en yeni content_gaps_keyword_ideas_*.json glob pick."
  default_status:
    type: string
    required: false
    default: "TODO"
    description: "statusEnum seed value for new rows."
  default_tivl_tag:
    type: string
    required: false
    default: "I"
    description: "TIVL tag default (T=Transactional, I=Informational, V=Video, L=Local). Per-row override via search_intent enrichment."
  target_word_count:
    type: integer
    required: false
    default: 1500
    description: "Default target word count per row; overridden by intent heuristic."
  max_rows:
    type: integer
    required: false
    default: 50
    description: "DoS guard: en yüksek gap_score ile sıralanmış ilk N keyword projection edilir."
outputs:
  - "master.xlsx#new_content_plan"
  - "outputs/reports/{date}-new-content-plan.md"
  - "events.jsonl"
  - "inbox/dfs/{date}-keyword_ideas-newplan-{slug}.json"
consumes:
  - "init-project:projects/{slug}/master.xlsx"
  - "content-gaps:_state/staging/content_gaps_keyword_ideas_*.json"
  - "cluster-map:master.xlsx#cluster_keywords"
produces:
  - "drift-check"
  - "monthly-report"
triggers:
  manual: ["/pseo-new-content-plan"]
  natural_language: |
    "yeni içerik planı", "content plan üret", "yeni makale listesi",
    "new content plan", "içerik üretim sırası", "TIVL taglemesi",
    "hangi makaleyi yazalım"
  hooks: []
  scheduled:
    - cron: "0 9 * * 1"
      mode: "report-only"
mcp_tools:
  required:
    - "mcp__dataforseo__dataforseo_labs_google_keyword_ideas"
  optional:
    - "mcp__dataforseo__dataforseo_labs_google_keyword_overview"
    - "mcp__dataforseo__dataforseo_labs_search_intent"
budget:
  uses_paid_mcp: true
  estimated_credits: 6
autonomy:
  confidence: MEDIUM
  requires_approval: true
  safe_auto_execute: false
---

# new-content-plan — planning skill (Phase 8 Wave 1)

10-step protocol. Steps map 1:1 to `workflow_runner` invocations + the
spec §16.5 8-step MCP discipline + §16.8 budget pre-flight. Raw JSON
drift recovery is mandatory: every DFS response is dropped into
`inbox/dfs/` *before* any transform runs, so a transform bug never
costs us the upstream payload (which is paid; re-fetch costs credits).

This skill follows the **convention authority** of
`skills/discovery/cannibalization/SKILL.md` (Phase 7 Wave 1 — 10-step
protocol + DURUR + URL normalization), `skills/discovery/content-gaps/SKILL.md`
(Phase 7 Wave 2 — D-003 staging consume + budget pre-flight), and
`skills/ingestion/dfs-pull/SKILL.md` (Phase 6 — REST/flat shape
tolerance via `_normalize_dfs_response`). The 10-step protocol, raw
JSON inbox discipline, budget pre-flight, and provenance event format
are reused verbatim — only the domain content (target sheet, transform
script, intent → tivl heuristic) changes. Deviate only with an ADR.

## Inputs (frontmatter contract)

| Name                         | Type    | Default | Notes                                                                          |
|------------------------------|---------|---------|--------------------------------------------------------------------------------|
| `project_slug`               | string  | —       | Required. Resolves `projects/{slug}/master.xlsx`.                              |
| `content_gaps_staging_path`  | string  | newest  | Phase 7 staging JSON; default = newest `content_gaps_keyword_ideas_*.json`.    |
| `default_status`             | string  | "TODO"  | statusEnum seed (master-excel.schema.json#/definitions/statusEnum).             |
| `default_tivl_tag`           | string  | "I"     | TIVL tag default (T/I/V/L); per-row override via search_intent.                |
| `target_word_count`          | integer | 1500    | Default target word count; intent-heuristic override.                          |
| `max_rows`                   | integer | 50      | DoS guard: top-N rows by gap_score.                                            |

`workspace_root` is resolved via `PSEO_WORKSPACE_ROOT` env or explicit
test override (mirrors workflow_runner / events_writer).

## Outputs (artifacts produced)

- `projects/{slug}/master.xlsx#new_content_plan` — one row per planned
  article (14 cols, schema-locked).
- `projects/{slug}/outputs/reports/{date}-new-content-plan.md` —
  human-readable summary (top planned keywords, TIVL distribution,
  cluster coverage).
- `projects/{slug}/_state/events.jsonl` — `event_kind=provenance`
  entries (`source.kind=dataforseo_mcp`,
  `target_excel_sheet=new_content_plan`).
- `projects/{slug}/inbox/dfs/{date}-keyword_ideas-newplan-{slug}.json`
  — raw DFS payload (drift recovery).

## Dependencies (concurrent write awareness)

This planning skill **READS** `master.xlsx#cluster_keywords` (populated
by sibling `cluster-map` planning skill) and **WRITES** to
`master.xlsx#new_content_plan`. The two skills can be dispatched
concurrently by a manager session, but their interaction is governed
by a **snapshot-at-dispatch read pattern**:

  - new-content-plan reads `cluster_keywords.assigned_cluster` values
    at workflow_runner gate time (right before `transaction.append`),
    **NOT at transform time**. This avoids parallel-write races on the
    same workbook (`scripts/excel/transaction.py` provides atomic
    SQLite-style locking via `_acquire_lock` so concurrent writes are
    serialised at the I/O layer).
  - If `cluster_keywords` sheet is empty when this skill runs (i.e.
    cluster-map has not yet completed), the skill emits a DURUR with
    code `CLUSTER_KEYWORDS_EMPTY` and a Turkish-language message
    suggesting "önce cluster-map çalıştır". This is a **runtime
    ordering issue, not a parallel-write issue** — the manager is
    expected to dispatch cluster-map first, then new-content-plan.
  - Re-running new-content-plan after cluster-map populates the sheet
    is idempotent: the same content-gaps staging input + same
    cluster_keywords snapshot produce byte-identical row output.

## D-003 import discipline (paterni: content-gaps)

`_normalize_dfs_response` AND `StagingSchemaError` are **imported**
(never copied) from `scripts.ingestion.dfs_pull` so any future
shape-handling fix in dfs-pull is inherited automatically. The
KOPYA YASAK invariant is enforced by an identity test in
`tests/skills/test_new_content_plan.py`.

## 10-Step Body Protocol

> Each step name must match the `steps[*].name` passed to
> `workflow_runner.create_run`. Names are stable identifiers across
> runs.

### Step 1 — `preflight_budget` (§16.8, MANDATORY for paid-MCP skills)

Subprocess wrapper around `scripts/budget/check_budget.py` (W-A3/W-A4
paterni). DURUR #1 if exceeded — never silently downgrade.

```python
from scripts.planning import new_content_plan_transform as ncp

envelope = ncp.preflight_budget(
    project_config_path=project_root / "project.config.json",
    events_path=project_root / "_state" / "events.jsonl",
)
# envelope == {"budget_per_day": 500, "used_24h": 15, "remaining": 485, "exceeded": false}
```

The wrapper raises `BudgetExceededError` when the underlying script
exits non-zero. The error inherits from `NewContentPlanError` so a
single `except` handles every DURUR class this skill emits.

### Step 2 — `create_run`

Open a workflow run shell. The state file lives at
`projects/{slug}/_state/workflows/{run_id}.json` (ADR-021).

```python
from scripts.state import workflow_runner
handle = workflow_runner.create_run(
    skill="new-content-plan",
    project_slug=project_slug,
    steps=[
        {"name": "preflight_budget"},
        {"name": "consume_content_gaps_staging"},
        {"name": "fetch_keyword_overview"},      # optional enrichment
        {"name": "fetch_search_intent"},         # optional enrichment
        {"name": "read_cluster_keywords"},
        {"name": "transform"},
        {"name": "request_approval"},
        {"name": "write_excel"},
        {"name": "render_report"},
    ],
)
```

### Step 3 — `consume_content_gaps_staging`

Locate the newest `_state/staging/content_gaps_keyword_ideas_*.json`
(or use `content_gaps_staging_path` if explicitly passed). DURUR #5 if
no staging file exists AND no test fallback fixture is provided.

```python
staging_path = ncp.locate_content_gaps_staging(
    project_root=project_root,
    explicit_path=content_gaps_staging_path,
)
staging_payload = json.loads(staging_path.read_text("utf-8"))
```

### Step 4 — `fetch_keyword_overview` (OPTIONAL enrichment)

When the caller wants enriched volume + difficulty (for the keywords
that survive max_rows), pull `keyword_overview` only for the projected
subset (DoS guard: max_rows keywords). Drop the raw response into
`inbox/dfs/`.

```python
# location_code / language_code resolved config-first from
# project.config.dataforseo (no engine-level country default) — same
# contract as skills/ingestion/dfs-pull/SKILL.md "Locale resolution".
raw_overview = mcp__dataforseo__dataforseo_labs_google_keyword_overview(
    keywords=projected_keywords,           # ≤ max_rows
    location_code=location_code,
    language_code=language_code,
)
overview_inbox = (
    workspace_root / "projects" / project_slug
    / "inbox" / "dfs"
    / f"{today.isoformat()}-keyword_ideas-newplan-{project_slug}.json"
)
overview_inbox.parent.mkdir(parents=True, exist_ok=True)
overview_inbox.write_text(json.dumps(raw_overview, ensure_ascii=False, indent=2))
```

### Step 5 — `fetch_search_intent` (OPTIONAL enrichment)

When intent → TIVL mapping is desired, pull `search_intent` for the
projected subset. The `_extract_intent_label` helper maps DFS intent
levels (informational/commercial/transactional/navigational) into
the master-excel `tivl_tag` enum:

```
informational    → "I"
transactional    → "T"
commercial       → "T"  (transactional umbrella for tivl_tag)
navigational     → "I"  (informational fallback — no nav tag in TIVL)
local-pack hits  → "L"
video carousel   → "V"
unknown          → default_tivl_tag (caller-provided, default "I")
```

### Step 6 — `read_cluster_keywords` (snapshot-at-dispatch)

Read `master.xlsx#cluster_keywords` immediately before `transform`
(NOT at fetch time). Build a `{keyword: cluster}` map. If sheet is
empty → DURUR #6 (`CLUSTER_KEYWORDS_EMPTY`).

```python
cluster_map = ncp.read_cluster_keywords_snapshot(
    workbook_path=workspace_root / "projects" / project_slug / "master.xlsx",
)
if not cluster_map:
    raise ncp.ClusterKeywordsEmptyError(
        "cluster_keywords sheet is empty — run cluster-map first "
        "before new-content-plan (workflow ordering issue, not a "
        "parallel-write race)."
    )
```

### Step 7 — `transform`

Pure compute via `scripts/planning/new_content_plan_transform.py`:

```bash
python3 scripts/planning/new_content_plan_transform.py \
    --staging _state/staging/content_gaps_keyword_ideas_{date}_{slug}.json \
    --cluster-map _state/transform/{run_id}/cluster_map.json \
    --raw-keyword-overview inbox/dfs/{date}-keyword_ideas-newplan-{slug}.json \
    --raw-search-intent    inbox/dfs/{date}-search_intent-newplan-{slug}.json \
    --default-status TODO \
    --default-tivl-tag I \
    --target-word-count 1500 \
    --max-rows 50 \
    --date {date} \
    --output-dir _state/transform/{run_id}/
```

Produces a JSON array (`new_content_plan`) shaped to the master-excel
schema (14 columns: id, title, url_slug, primary_keyword,
monthly_volume, assigned_cluster, target_word_count, priority,
created_date, tivl_tag, lifecycle_status, image_prompt, alt_text,
content_type). The last three (Phase-10 additive: `image_prompt`,
`alt_text`, `content_type`) default to empty string at plan time and
are materialised later by `new-blog` / `generate-images` (R-71/R-72/R-77
image discipline + content_type enum). The transform is idempotent:
same input → byte-identical output.

If `meta.row_count == 0`, the skill SKIPS write_excel and emits a
"no new content candidates" notice (clean exit, not error).

### Step 8 — `request_approval` (skill EXIT awaiting_approval)

```python
workflow_runner.request_approval(
    handle.run_id, project_slug=project_slug,
    approver="user",
    subject=f"{len(rows)} yeni içerik planı bulundu, master.xlsx#new_content_plan'a yazalım mı?",
    step_index=6,
)
# Skill exits here. The user replies in a fresh session; resume below.
```

### Step 9 — `write_excel` (atomic, schema-validated)

Single `committer.commit` call for the new_content_plan sheet — the
orchestrator's idempotent commit path (whole-block `transaction.replace` from
the schema's `data_start_row`, so re-running never duplicates rows on the
`new_content_plan` snapshot sheet). Goes through the single approved write path
with backup, lock, schema validation, and post-write provenance event emission.

```python
from scripts.orchestration import committer
committer.commit(
    workspace_root/"projects"/project_slug/"master.xlsx",
    "new_content_plan",
    new_content_plan_rows,
    run_id=handle.run_id,
    project_slug=project_slug,
    writer="new-content-plan",
)
```

### Step 10 — Provenance event + report + complete

```python
from scripts.state import events_writer
events_writer.append_provenance(
    project_id=project_slug,
    source={"kind": "dataforseo_mcp", "mcp_server": "dataforseo",
            "mcp_tool": "dataforseo__dataforseo_labs_google_keyword_ideas",
            "response_bytes": staging_path.stat().st_size},
    operation="project_excel",
    target_excel_sheet="new_content_plan",
    rows_written=len(new_content_plan_rows),
    cost={"provider": "dataforseo",
          "credits": float(estimated_credits),
          "budget_key": "project.config.dataforseo.budget_credits_per_day"},
)
workflow_runner.complete(handle.run_id, project_slug=project_slug, outputs={
    # F5: outputs.* must be STRING-TYPED
    "row_count": str(len(new_content_plan_rows)),
    "report_path": str(report_path),
    "staging_consumed": str(staging_path),
})
```

## TIVL tag heuristic (transform domain)

```
search_intent.main_intent (when search_intent enrichment present):
    "informational"   → "I"
    "transactional"   → "T"
    "commercial"      → "T"   (transactional umbrella)
    "navigational"    → "I"   (informational fallback)
    "local"           → "L"
    "video"           → "V"
    unknown / missing → default_tivl_tag (frontmatter input, default "I")

When search_intent enrichment is absent, every row gets default_tivl_tag.
```

## Priority heuristic (transform domain)

Priority tier from gap_score (sourced from content-gaps staging):

```
gap_score >= 1000   → P1
gap_score >=  100   → P2
otherwise           → P3
```

Falls back to monthly_volume when gap_score is 0 (search_intent-only
fixtures): volume >= 1000 → P1, >= 100 → P2, else P3.

## Target word count heuristic (intent BASE → profile-floor clamp → R-08 override)

The transform emits an **intent-based BASE** from the TIVL tag
(`new_content_plan_transform` is profile-agnostic — it returns this base):

```
tivl_tag == "T"  → 1200  (transactional landing pages — concise + CTA-heavy)
tivl_tag == "L"  → 1000  (local listings — short, scannable)
tivl_tag == "V"  →  800  (video page — supporting copy only)
tivl_tag == "I"  → target_word_count input (default 1500)
```

The skill then applies two corrections on top of that base, in order:

1. **Profile-floor clamp (mandatory).** A fixed TIVL base can fall BELOW a
   profile's minimum word count (Principle 2). The published target is
   clamped UP to the **profile floor**:

   ```
   target = max(TIVL_base, profile_floor)
   ```

   `profile_floor` is the lower bound of the project's profile word-count
   band — **single source of truth: `rules/content-quality.md`** (the
   per-profile word-count row). It is resolved at the skill layer via
   `scripts/util/profile_aware_defaults.cascade_default` over
   `project.config.json[profiles]` — do NOT restate divergent numbers here.
   Floors per `content-quality.md`: **YMYL 1500**, b2b-saas 1800,
   e-commerce 800, local-service 1000, portfolio 800. (Example: a YMYL
   transactional page → `max(1200, 1500) = 1500` — the floor binds.)

2. **R-08 SERP override (when present).** R-08 (mandatory pre-write SERP
   analysis, `rules/content-seo-discipline.md`) yields a competitive
   word-count target from the top-ranking pages. When that data exists it
   **overrides** the TIVL base + profile-floor clamp entirely — the
   SERP-derived target wins (it reflects what actually ranks for the query).

**Worked examples** (base → clamp → final):

| profile        | tivl_tag | TIVL base | profile_floor | `max(base, floor)`     | with R-08 SERP data        |
|----------------|----------|-----------|---------------|------------------------|----------------------------|
| YMYL           | T        | 1200      | 1500          | **1500** (floor binds) | R-08 target overrides      |
| b2b-saas       | V        |  800      | 1800          | **1800** (floor binds) | R-08 target overrides      |
| e-commerce     | I        | 1500      |  800          | **1500** (base wins)   | R-08 target overrides      |
| local-service  | T        | 1200      | 1000          | **1200** (base wins)   | R-08 target overrides      |

> The transform's `target_word_count` is the BASE (covered by the transform
> tests); the profile-floor clamp + R-08 override are applied by the skill
> (which reads `project.config.json[profiles]`), not the pure transform.

## URL slug generation (idempotent + plugin-agnostik)

`make_url_slug(keyword)` lowercases, ASCII-folds Turkish characters
(ğ→g, ş→s, ı→i, ü→u, ö→o, ç→c), strips non-alphanum, collapses runs
of "-". The function is **idempotent**: `make_url_slug(make_url_slug(k)) == make_url_slug(k)`.

Within-batch uniqueness is enforced: if two keywords reduce to the same
slug, the transform raises `RowSchemaError` (DURUR #8 — caller can
re-run with disambiguating max_rows or alternate keyword set).

## DURUR conditions (8)

Stop and flag the manager — do not patch, do not fall back.

1. **`scripts/budget/check_budget.py` exits non-zero** → subprocess
   wrapper raises `BudgetExceededError`. STOP, await manager approval
   to lift the cap (W-A3/W-A4 paterni).
2. **REST/flat shape both fail** — imported `_normalize_dfs_response`
   raises `ValueError("Unrecognized DFS response shape")`. STOP, the
   wrapper version drift is an ADR-level event.
3. `master.xlsx#new_content_plan` column count or names don't match
   schema (`schemas/master-excel.schema.json#new_content_plan`, 14
   columns). STOP, schema-first violation.
4. `transaction.append` raises `RowSchemaError` (e.g.,
   `monthly_volume` not int, `status` not in statusEnum, `tivl_tag`
   not in `["T", "I", "V", "L"]`, `lifecycle_status` not in
   `["GREEN", "RED", "ON_HOLD", "REMOVED"]`). STOP.
5. `_state/staging/content_gaps_keyword_ideas_*.json` not found AND no
   explicit fallback fixture passed → `StagingNotFoundError`. STOP,
   request manager run content-gaps first.
6. **`master.xlsx#cluster_keywords` sheet empty at workflow_runner
   gate** → `ClusterKeywordsEmptyError` (code `CLUSTER_KEYWORDS_EMPTY`).
   STOP, run cluster-map before new-content-plan. Runtime ordering
   issue, not parallel-write.
7. `tivl_tag` value drift — produced row has tivl_tag not in the
   master-excel enum `["T", "I", "V", "L"]`. STOP, schema-first
   violation. Defensive: also enforced upstream by frontmatter input
   range check on `default_tivl_tag`.
8. **`url_slug` uniqueness violation within batch** — two keywords
   reduce to the same slug. STOP; re-run with disambiguating
   max_rows or alternate keyword set. (RowSchemaError subclass.)

## Cross-references

- Schemas: `schemas/master-excel.schema.json`
  (new_content_plan sheet, 14 required_columns + statusEnum +
  tivl_tag enum + lifecycle_status enum + content_type enum),
  `schemas/events.schema.json`
  (`source.kind=dataforseo_mcp`,
  `target_excel_sheet=new_content_plan`),
  `schemas/skill-frontmatter.schema.json` (this frontmatter; budget
  block additionalProperties:false enforced).
- Cross-modules (IMPORT-only): `scripts/state/workflow_runner.py`,
  `scripts/excel/transaction.py`, `scripts/state/events_writer.py`,
  `scripts/reporting/render_template.py`,
  `scripts/budget/check_budget.py` (subprocess wrapper). **From
  `scripts/ingestion/dfs_pull.py`: `_normalize_dfs_response` AND
  `StagingSchemaError` are imported — KOPYA YASAK; identity preserved
  by the import-discipline test.**
- Transform: `scripts/planning/new_content_plan_transform.py`
  (`transform`, `read_cluster_keywords_snapshot`,
  `locate_content_gaps_staging`, `make_url_slug`,
  `NEW_CONTENT_PLAN_COLUMNS`, `preflight_budget`).
- Tests: `tests/skills/test_new_content_plan.py` (≥6 cases incl.
  D-003 import-discipline identity check).
- Template: `templates/reports/new-content-plan.template.md` (created
  alongside this skill).
- Phase 7 producer: `skills/discovery/content-gaps/SKILL.md` provides
  the `_state/staging/content_gaps_keyword_ideas_*.json` consumed
  here.
- Sibling planning skill: `skills/planning/cluster-map/SKILL.md`
  populates `master.xlsx#cluster_keywords` whose `assigned_cluster`
  values this skill cross-references.

## Discipline checklist

- [x] TODO/fallback YASAK — every DURUR raises, none silently downgrade.
- [x] Schema-first — frontmatter validates against Draft 7 schema; budget
      block additionalProperties:false (Q-W-A4-01); new_content_plan row
      shape locked to 14-col master-excel tuple.
- [x] Plugin-agnostik — no slug literals; transform has 0 hardcoded slug.
- [x] ADR-013: `Use when`/`Also use when`/`Do not use when` are STRING
      content inside `description`, not separate fields.
- [x] Budget pre-flight integration — `scripts/budget/check_budget.py`
      invoked at Step 1 (subprocess wrapper) BEFORE any paid call.
- [x] Cross-module IMPORT discipline — `transaction` / `workflow_runner` /
      `events_writer` imported, never modified. From `dfs_pull`:
      `_normalize_dfs_response` + `StagingSchemaError` imported (KOPYA
      YASAK enforced by identity test).
- [x] F1: write target is `master.xlsx` (lowercase, schema-shaped).
- [x] F5: `outputs.*` values are STRING-TYPED (paths / stringified counts).
- [x] Append-only state — `events.jsonl` only grows.
- [x] Cross-ref snapshot-at-dispatch — `cluster_keywords` read right
      before `transaction.append`; concurrent W-C1 writes serialised
      by `transaction._acquire_lock`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.