Skip to content
Back to skills

Marketing

ASecurity

Write a SLICE''s marketing lens as a grounding doc (marketing.md) — how the slice is found and reached (SEO / AEO / GEO), the accessibility bar it meets, and the reach signals worth capturing — written directly in place on the live model. The END of the FUNCTIONAL realize pipe (ux → agentic → marketing): it closes the functional pipe (commit → propose → review → merge). An internal tool answers discoverability "not applicable", plainly. Reads the hub from the spine (functionality grounding + ...

  • 3 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 10, 2026
businesspythonbashnodegit

Security analysis

A100/100

Pro scans all 16 files and shows the line behind each finding

Scanned September 10, 2026

npx -y skills add kapilvirenahuja/garura --skill marketing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Marketing?

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

Security grade badge for Marketing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kapilvirenahuja-marketing/badge)](https://www.skillsdirectory.com/skills/kapilvirenahuja-marketing)

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: marketing
position: end
description: 'Write a SLICE''s marketing lens as a grounding doc (marketing.md) — how the slice is found and reached (SEO / AEO / GEO), the accessibility bar it meets, and the reach signals worth capturing — written directly in place on the live model. The END of the FUNCTIONAL realize pipe (ux → agentic → marketing): it closes the functional pipe (commit → propose → review → merge). An internal tool answers discoverability "not applicable", plainly. Reads the hub from the spine (functionality grounding + profile), never another lens. Writes only the slice''s marketing lens.'
user-invocable: true
---

# marketing

Write a shaped slice's **marketing lens** as the grounding doc `marketing.md`: how the slice is
found and reached, the accessibility bar it meets, and the reach signals worth capturing. The lens
is four things and only four — intent, discoverability (SEO / AEO / GEO), accessibility (moved here
from the profile), and marketing analytics. An internal tool behind auth answers discoverability
"not applicable", plainly and with the reason. /marketing reads the slice's **hub** — its
functionalities' grounding docs plus the profile (both from the spine) — and never another realize
lens.

**Pipeline position: end.** /marketing is the END of the functional realize pipe (ux → agentic →
marketing): it runs on the branch /ux already started, injects no `start-change` head, and — after
the lens is persisted and verified — injects the close sequence `commit-change → propose-change →
review-change → merge-change`, committing the functional pipe's lenses, opening the PR, taking the
verdict, and merging to main. It writes the persistent product model **directly, in place** (the
slice's marketing lens) on the already-started branch — there is no draft copy and no apply/promote
step; review is the branch git diff and the injected end PR. (#437; 3-pipe realize 2026-06-26; #500,
ADR 026)

**Write discipline (ADR 026, `standards/rules/direct-model-write.md`).** The LLM authoring skill
writes ONLY the per-node lens doc (`marketing.md`) straight to the live model — a re-derive that
overwrites a prior marketing lens for this slice; every shared-file mutation (the slice's
`decisions/`) is done by the deterministic keyed persist script, in place, keyed to the target slice
so it cannot touch another slice's decisions and never edits an accepted decision in place
(add-only). The authoring skill emits any material decision as structured data in its manifest; the
keyed persist writes the decision file. The model tree is asserted clean at entry (F15) and the play
commits its own model delta after approval (C14) — a `feat(model)` commit distinct from the injected
`commit-change` — so the working-tree diff vs HEAD is exactly this run's delta and the injected end
sequence carries a committed model change. Containment is a post-write scoped guard
(`scoped_write_guard.py`), not a draft.

## Compiled From

This play was compiled from the marketing ICE (`reference/ice.md`) by play-editor (#466 Batch C;
#467 Batch B — the checkpoint upgraded to a conditional learned gate, see
`standards/rules/gate-config.md`; #500 — migrated to direct-model-write per ADR 026 and
`standards/rules/direct-model-write.md`). Intent defines constraints (C1–C14) and failure conditions
(F1–F15); the expectation defines success scenarios (S1–S6), a Done means (D1–D3, baked to
`stop-condition.yaml`), and one recovery entry per failure condition. To modify this play, update
`reference/ice.md` and recompile with play-editor. Do NOT edit this file manually — it is a compiled
artifact.

## Role

You are the orchestrator. You own the workflow and step order. You delegate the domain work —
authoring the marketing lens grounding doc — to the `product-os-keeper` agent via a JSON contract
over files on disk, and you run the mechanical work (readiness/hub resolution, the shape linter, the
content-quality eval, grounding + coverage, KB grounding, the keyed in-place persist, and the
post-write scoped guard) through bundled scripts and an isolated judge. You never write the lens
yourself, you never write a shared model file (a decision) by any route other than
`scripts/persist_marketing.py`, and you never commit the model delta before the human approves the
single checkpoint (C12).

**Forbidden:** hand-writing the lens or a decision; writing anything other than this slice's
`marketing.md` and its decisions (C2); reading or grounding on another realize lens (C7);
manufacturing public-marketing reach where the functionalities and profile don't warrant it (C8);
deferring accessibility to the profile (C9); writing a decision by any route other than
`scripts/persist_marketing.py`; committing the model delta before the checkpoint gate resolves;
running against a dirty product-os tree (C14/F15); closing COMPLETED without the stop-condition
verdict held (C13).

**Agent boundaries:**

| Agent | Domain | Skill it invokes | Phases |
|-------|--------|------------------|--------|
| `product-os-keeper` | Author the slice's marketing lens (intent / discoverability / accessibility / analytics) from the hub + KB pattern grounding, written in place on the live model, and emit any decision into the manifest | `kb-search`, `author-marketing-lens` | Draft |

`product-os-keeper` is the single domain agent this play uses (1 of the ≤5 budget). The
content-quality judge always runs as an isolated, clean-context sub-agent (optionally on a configured
different model) — never the orchestrator's own context.

## Pre-flight

| Check | Constraint | Action on Failure |
|-------|-----------|-------------------|
| Resolve config + `product_base` (`.garura/core/config.yaml`) | — | Hard halt |
| Resolve `grounding-eval.judge` (optional model override) | C4 | Default: sub-agent on the session model |
| Slice ready + hub resolves (`check_ready_slice.py`) | C1 | Hard halt (REC1) |
| **Clean model tree** — `git status --porcelain -- <product_base>product-os` is empty | C14/F15 | Hard halt (REC15) |

Resolve the pre-flight facts mechanically with the bundled resolver:

```
python3 scripts/preflight.py --play marketing --config .garura/core/config.yaml
```

Then resolve the slice and its hub from the spine — the readiness gate every realize lens shares:

```
python3 scripts/check_ready_slice.py --product-base <product_base> --slice <slice-id>
```

It asserts the profile is `set` (from the spine), resolves the slice record, and resolves every
`functionality_ref` through the spine to its `functionality.md` grounding doc — the hub. If the slice
is absent, a functionality does not resolve, or the profile is not firmed, hard halt (C1/REC1).

**Clean-tree assertion (C14/F15, ADR 026).** /marketing runs on the branch the functional realize
pipe already started (ux → agentic → marketing); the prior lenses commit their own model deltas, so
the tree should be clean when /marketing enters. Assert it before any work — `HEAD` is only a correct
base for the scoped guard and the change-shape if the tree is clean at entry:

```
test -z "$(git status --porcelain -- <product_base>product-os)" || { echo "HALT: dirty product-os tree (REC15)"; exit 1; }
```

If dirty, halt at pre-flight and ask for a clean model tree (commit or revert the pending model
edits, or run the prior functional-pipe play to its persist) before /marketing proceeds.

The run's working root is `<working>` = `{stm_base}_realize/marketing/` — the marketing manifest, the
change-shape, the persist record (`persist-manifest.json`), the captured scoped-guard report
(`guard-report.json`), and status markers all live under it. These are STM, non-model artifacts (ADR
008/017) — the model itself is written IN PLACE under `<product_base>product-os/`, never into
`<working>`. The stop condition evaluates against `<working>`.

Right after the resolver, record the session identity stamp's start marker (#463 — soft-fail, never
a halt):

```
python3 scripts/session_stamp.py --phase start \
    --marker "{stm_base}_realize/marketing/status/session-stamp-marketing.json" \
    --cwd "$(pwd)" --branch "$(git branch --show-current)"
```

**Resume check:** if `{stm_base}_realize/marketing/status/<slice-id>.json` exists, resume — skip
completed steps, reset any in-progress step to pending, continue.

## Task DAG

Create ALL tasks immediately after resolving config — before any domain work.

Write-then-review (ADR 026): the FULL model delta — the LLM lens doc AND the keyed persist's
decisions — is written to the live model BEFORE the checkpoint, so the guard, the change-shape, and
the human all see the real delta. Nothing is COMMITTED before the gate resolves; cancel reverts the
uncommitted writes.

```
[T1] Draft the lens (doc to live)                  blockedBy: []
[T2] Validate the live doc                         blockedBy: [T1]
[T3] Persist (keyed, in place — decisions)         blockedBy: [T2]
[T4] Guard the full delta + classify the shape     blockedBy: [T3]
[T5] Checkpoint (approval over the full git diff)  blockedBy: [T4]
[T6] Commit the model delta (feat(model))          blockedBy: [T5]
[TE1] commit-change   (injected — end #1)          blockedBy: [T6]
[TE2] propose-change  (injected — end #2)          blockedBy: [TE1]
[TE3] review-change   (injected — end #3)          blockedBy: [TE2]
[TE4] merge-change    (injected — end #4)          blockedBy: [TE3]
[T7] Scenario Validation                           blockedBy: [TE4]
[T8] Close                                         blockedBy: [T7]
```

Mark each task in-progress before its step and completed right after its eval passes. No runtime
reordering. On resume, skip completed and reset in-progress to pending.

## Workflow

### Phase: Draft (write the doc to the live model, ADR 026)

**Step 1 — Draft the lens (doc to live)** · Owner: `product-os-keeper` · Depends on: pre-flight
The agent invokes `author-marketing-lens` to write the slice's `marketing.md` (intent /
discoverability / accessibility / marketing analytics, per the Marketing lens template) from the hub
(the functionality grounding docs + the profile) and KB pattern grounding — **straight to the live
model** (a re-derive, skip-nothing overwrite for this slice) — and to emit a `marketing-manifest.yaml`
(the grounding map plus any material decision as a full record). Per ADR 026 the skill writes NO
shared model file (`_spine.yaml`, the profile, the slice record, another lens, or a `decisions/*.yaml`
file) — the keyed persist (Step 3) writes any decision:

    {
      "task":    "author the slice's marketing lens (intent/discoverability/accessibility/analytics) from its hub; ground the reach in the functionalities + profile and the patterns in the KB; write marketing.md in place on the live model and emit any decision into the manifest",
      "inputs":  { "slice_ref": "<domain>/<slice>",
                   "slice_file": "<slice record>",
                   "functionality_groundings": "<from check_ready_slice>",
                   "profile": "<spine profile>", "product_base": "<product_base>",
                   "lens_rel": "product-os/<domain>/slices/<slice>/lens/marketing.md",
                   "manifest_path": "<working>/marketing-manifest.yaml",
                   "proposals_dir": "<working>/proposals/" },
      "outputs": { "manifest": "<working>/marketing-manifest.yaml" }
    }

The skill reads the hub read-only and writes only the live `marketing.md` plus the STM manifest (any
decision as a full record whose `node_ref` is the slice) and any KB proposals. It returns the
contract with the output paths on disk — never inline content.
**SE-1 (F1/C1):** `check_ready_slice.py` passed at pre-flight — the slice is ready and its hub
resolves; an unready slice halted (REC1).
**SE-14 (F15/C14):** the product-os tree was clean at entry — the pre-flight assertion
(`git status --porcelain -- <product_base>product-os` empty) passed on a fresh start; a dirty model
tree halted at pre-flight (REC15), so the change-shape and the scoped guard reflect only this run's
delta.

### Phase: Validate (over the live doc)

**Step 2 — Validate the live doc** · Owner: play · Depends on: Step 1
Run the guards over the LIVE `marketing.md` this run wrote, before the checkpoint — shape first, then
content, then grounding. Under direct-model-write the doc is already written in place; the linter and
the validators run over the live tree.

```
python3 scripts/lint_grounding.py --doc <product_base>/product-os/<domain>/slices/<slice>/lens/marketing.md
python3 scripts/validate_marketing.py --manifest <working>/marketing-manifest.yaml --slice-file <product_base>/<slice_file>
python3 scripts/check_kb_grounding.py --manifest <working>/marketing-manifest.yaml --kb-root <kb_root> --proposals-dir <working>/proposals
```

Then run the **content-quality eval** over `marketing.md` at its live path: spawn an isolated,
clean-context sub-agent handed the judge prompt (`standards/rules/grounding-eval.md`), the doc, and
the Marketing lens per-section guidance (it sees neither the brief nor the author's reasoning), on the
model from `grounding-eval.judge.model`. Gate the verdict:

```
python3 scripts/grounding_gate.py --verdict <verdict.json>
```

**SE-2 (F3/C3):** `lint_grounding.py` exits 0 — `marketing.md` conforms to the Marketing lens template
("Intent", "Discoverability", "Accessibility", "Marketing analytics"), no missing/extra/empty section.
**SE-3 (F4/C4):** the content-quality eval gate (`grounding_gate.py`) passes — `marketing.md` is
self-explaining and clears the stranger test.
**SE-4 (F5/C5):** `validate_marketing.py` — the reach assessment grounds in the slice's
functionalities and the profile; any material marketing choice names a decision emitted in the
manifest.
**SE-5 (F6/C6):** `validate_marketing.py` — the assessment considers every functionality the slice
bundles (coverage).
**SE-6 (F7/C7):** `validate_marketing.py` — the assessment grounds on no other realize lens.
**SE-7 (F8/C8):** `validate_marketing.py` — reach is honest: a slice with no public surface answers
discoverability "not applicable", and public-marketing reach is not manufactured.
**SE-8 (F9/C9):** `validate_marketing.py` — accessibility is set concretely in the lens (a bar + the
means), not deferred to the profile or omitted.
**SE-9 (F11/C11):** `check_kb_grounding.py` exits 0 — the discoverability/accessibility patterns trace
to a KB learning or a recorded proposal.
On any GAP, apply the matching recovery (REC3–REC11) and re-run before the checkpoint — a content-eval
fail (SE-3) is REC4: rewrite the failing section to the judge's cited fixes and re-judge until the
gate passes.

### Phase: Persist (write the full delta first, ADR 026 write-then-review)

**Step 3 — Persist (keyed, in place — decisions)** · Owner: play · Depends on: Step 2
Write-then-review (ADR 026): the FULL model delta is written to the live model BEFORE the checkpoint,
so the guard, the change-shape, and the human all see the real delta. The lens is already on the live
model (Step 1). `persist_marketing.py` writes any material decision in place, keyed to the target
slice: it reads the manifest, writes each decision to the slice's `decisions/` **add-only** (an id
already present is left byte-untouched, so an accepted decision is superseded, never edited in place),
and REFUSES any decision whose `node_ref` names another slice (this is the node-level containment the
file-level guard cannot see across slices). No draft, no doc copy. Nothing is COMMITTED yet — the
commit (Step 6) happens only after the gate approves; on cancel the whole delta is reverted (Step 5):

```
python3 scripts/persist_marketing.py --marketing-manifest <working>/marketing-manifest.yaml \
        --product-base <product_base> --slice-ref <domain>/<slice> \
        --decisions-rel product-os/<domain>/slices/<slice>/decisions \
        --out-manifest <working>/persist-manifest.json
```

The persist manifest stamps `applied: true` on a completed run (even when the slice carries no
decision) — this is the Done-means record the close's stop-condition gate reads (D1/D2).
**SE-11 (F12/C12):** the model delta was written to the live model by Steps 1 + 3 but is COMMITTED
(made durable) only at Step 6 on approval — so no product-model change is committed before the gate
resolves; Step 6 is the sole committer and depends on the checkpoint.

### Phase: Guard + Classify (over the full delta)

**Step 4 — Guard the full delta + classify the shape** · Owner: play · Depends on: Step 3

**The run's write scope (the per-play guard policy, ADR 026).** The old `apply_marketing.py` encoded
/marketing's write scope by construction — the slice's `marketing.md` (re-derive) and its decisions
(skip-if-exists); under direct-model-write that same scope is the `scoped_write_guard.py` policy,
keyed to the target slice so no other slice's lens or decision can slip through:

    --allow    'product-os/*/slices/<slice>/lens/marketing.md'    # the slice's lens (re-derive — the LLM overwrites it)
    --add-only 'product-os/*/slices/<slice>/decisions/*.yaml'     # the slice's decisions (added by the keyed persist, never modified)

**Guard ONCE over the full delta (C10).** After ALL writes (the LLM lens from Step 1 and the keyed
persist's decisions from Step 3), run the scoped guard a single time over the whole delta. Capture its
report — its `ok` field is the stop condition's D3 input (this replaces the old before/after
`check_marketing.py` verify):

```
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
        --allow 'product-os/*/slices/<slice>/lens/marketing.md' \
        --add-only 'product-os/*/slices/<slice>/decisions/*.yaml' \
        --out <working>/guard-report.json
```

If the guard exits non-zero (a path outside the slice's write scope changed, or an accepted decision
was modified), re-run with `--restore` to revert the offending paths, apply REC2 (or REC10 for a
decision edited in place), and re-persist before the checkpoint.

**Classify the full working-tree delta (C12).** Classify the model tree's diff vs HEAD — now the FULL
delta (the lens + any decision), per ADR 026 write-then-review (no draft dir):

```
python3 scripts/classify_change.py --play marketing \
        --product-base <product_base> --base-ref HEAD --out <working>/shape.json
```

**SE-10 (F2/F10/C2/C10):** the scoped-write guard report reads `ok: true` — the model delta is
confined to the slice's `marketing.md` (re-derived) and its decisions (added, never edited in place);
the spine, the slice record, the profile, the other lenses, and the other slices are byte-identical,
and no path outside the slice's write scope changed.

### Phase: Checkpoint (conditional gate, C12)

**Step 5 — Human review (class: standard, conditional)** · Owner: play · Depends on: Step 4
**This is the single checkpoint (C12) — the agent never skips it on its own judgment.** It is a
**conditional gate** (#467) per `standards/rules/gate-config.md` — /marketing is one of the eleven
conditional document plays. Resolve it first match wins: pinned (n/a here) → `gates.plays.marketing` →
the learned policy → `gates.classes.standard` → `gates.default` (absent ⇒ on). For the policy lookup,
use the shape key classified in Step 4.

Look the shape key up in the config-resolved policy (`gates.conditional.policy`, default
`.garura/core/gate-policy.yaml`): **auto-pass** iff the shape is in the policy's `auto:` block AND not
in `never_auto:` AND Step 2 + Step 4 stand with no blocking finding (a `lint_grounding.py` gap, a
`grounding_gate.py` content-eval fail, or a guard violation). On auto-pass, do NOT wait: record
`gate auto-passed by learned policy (shape: <shape_key>, policy v<version>)` as a Checkpoint Decisions
row, include the working-tree diff summary in the run record, append the crossing's live-eval ledger
line, and proceed to Step 6 (commit):

```
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play marketing --issue <issue> \
    --shape <shape_key> --predicted auto --human auto_pass --policy-version <policy version> --ts <ts>
```

Anything else resolves the gate on (an explicit `gates.plays.marketing: off` instead records
`gate skipped by config (<resolution path>)` as a Checkpoint Decisions row and proceeds). When on,
present the proposed intent, discoverability, accessibility, and analytics **inline over the real
model git diff**, plus any decision — render the approval prompt
(`standards/templates/approval-prompt.md`) and wait for the typed response. Approve → continue to
Step 6 (commit). **Cancel → revert the working tree (ADR 026 step 6):** the full delta is already on
disk, so run the guard with `--restore` and an EMPTY allow set to `git restore` the modified model
paths and `git clean`/remove the new ones (byte-clean back to HEAD), then halt — nothing was
committed, and cancel means "revert what was written":

```
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
        --restore --out <working>/guard-report.json   # empty --allow ⇒ every model path reverted
```

Then append the crossing's live-eval ledger line with the human's real action:

```
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play marketing --issue <issue> \
    --shape <shape_key> --predicted gate --human <approved_clean|approved_edited|rejected> --ts <ts>
```

`<issue>` is the functional-pipe issue the run's branch carries (opened by /ux's start-change).
`<gates.conditional.ledger>` / `<gates.conditional.policy>` resolve from config `gates.conditional`
(defaults `.garura/core/gate-evals.jsonl` / `.garura/core/gate-policy.yaml`); `<policy version>` is
the policy file's `version:` field. `<ts>` is the run's own UTC timestamp, derived the same way the
close derives `ts` (`date -u +%Y%m%d-%H%M%S`), passed by the orchestrator.
EVERY crossing of this gate appends exactly one live-eval ledger line — gated or auto.
**SE-13 (F14/C12):** the crossing left exactly one live-eval ledger line, and any auto-pass fired
only on a shape the policy lists in `auto:` (and not `never_auto:`) with no blocking finding standing.

### Phase: Commit (make the delta durable, ADR 026 step 7)

**Step 6 — Commit the model delta (feat(model))** · Owner: play · Depends on: Step 5
The gate approved (or auto-passed / was skipped by config). Commit the full model delta on the branch
(C14, ADR 026 step 7) — a lightweight persist step that makes the writes durable and advances HEAD, so
the injected end sequence carries a committed model change. It is NOT the injected `commit-change`
(which then commits only what remains — STM evidence). A cancelled checkpoint never reaches this step —
its tree was already restored in Step 5:

```
git add -- <product_base>product-os
git commit -m "feat(model): marketing lens for <slice-id> (#<issue>)"
```
**SE-12 (F13/C13):** the close is stop-condition gated — `check_stop_condition.py` over the baked
`stop-condition.yaml` (D1 the persist record `persist-manifest.json` exists; D2 it stamps
`applied: true`; D3 the captured `guard-report.json` reads `ok: true`) must read **held** before any
COMPLETED close, and the model delta is committed (C14); a run whose persist or guard did not land
closes HALTED, never COMPLETED (REC13).

### Phase: End sequence (injected — D2 position: end)

After the lens is committed as a `feat(model)` delta (Step 6), the D2 rule injects the close sequence —
each a sub-play dispatched with `parent_run_id`, resolving its own context from the branch + config —
to commit whatever remains of the functional pipe, raise the PR, take the verdict, and merge to main.
The durable model change did not sit waiting on a manual `/commit-change`.

**Step E1 — commit-change** · blockedBy: Step 6 — commit any work still uncommitted after the
`feat(model)` model-delta commit (STM evidence, other lenses' residue); the marketing lens and any
decision were already committed in Step 6.

    { "play": "commit-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "{stm_base}_realize/marketing/end/commit-change.json" } }

**Step E2 — propose-change** · blockedBy: E1

    { "play": "propose-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "{stm_base}_realize/marketing/end/propose-change.json" } }

**Step E3 — review-change** · blockedBy: E2

    { "play": "review-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "{stm_base}_realize/marketing/end/review-change.json" } }

**Step E4 — merge-change** · blockedBy: E3

    { "play": "merge-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "{stm_base}_realize/marketing/end/merge-change.json" } }

Each end member owns its own evals (commit grouped by concern, PR opened, verdict posted, branch
merged + cleaned); they are not re-checked here. A review-change reject stops the chain before merge.

### Phase: Scenario Validation

**Step 7 — Scenario evals** · Owner: play · Depends on: the end sequence
- **SCE-1 (S1 — growth lead, first run):** the live `marketing.md` is a valid Marketing Lens doc
  clearing the linter + the content eval, and the scoped-guard report reads `ok`, so the
  spine/slice/profile/other lenses are byte-identical; the stop-condition verdict reads held.
- **SCE-2 (S2 — product owner, honest reach):** an internal tool's discoverability reads "not
  applicable" with the reason rather than an invented SEO/AEO/GEO claim.
- **SCE-3 (S3 — accessibility reviewer):** accessibility names a concrete bar and how the slice meets
  it.
- **SCE-4 (S4 — architect, hub-only):** no other realize lens was read or written (the scoped-guard
  report confirms it).
- **SCE-5 (S5 — product owner, re-run):** a re-run re-derives only `marketing.md`; the scoped-guard
  report reads `ok` with everything else byte-identical; no accepted decision edited in place.
- **SCE-6 (S6 — reviewer, the checkpoint):** the checkpoint showed the lens inline over the real model
  git diff, and no product-model change was COMMITTED before approval — on cancel the working tree
  returns byte-clean to HEAD — or, on the auto-pass path (a policy-listed shape), the gate resolved
  with no wait and a recorded auto-pass + live-eval ledger line + diff summary exist.

### Phase: Evidence & Close

**Step 8 — Close** · Owner: play · Depends on: Step 7
Run the Standard Play Close. /marketing is a slice-realize play — record evidence per the D1 rule.
**SE-12 (F13/C13):** the close is stop-condition gated — `check_stop_condition.py` over the baked
`stop-condition.yaml` (D1–D3) reads **held** before the run closes COMPLETED, and the model delta is
committed (C14); anything else closes HALTED with the unmet clauses named.

```bash
# --- Standard Play Close (canonical; see standards/rules/play-close.md) ---
# Path tokens resolved at pre-flight (resolve here if not already):
#   ltm_project_target  = yq '.ltm.project-target' .garura/core/config.yaml
#   evidence_base, slug:
#     project-scoped play : evidence_base="${stm_base}${issue}/evidence/marketing/"   ; slug="#${issue}"
#     product-scoped play : evidence_base="${product_base}_evidence/marketing/"        ; slug="${slice_slug}"
evidence_template=$(cat "${ltm_project_target}standards/templates/evidence-file.md")
delivery_template=$(cat "${ltm_project_target}standards/templates/delivery-report.md")
ts=$(date -u +%Y%m%d-%H%M%S)
evidence_dest="${evidence_base}${ts}.md"
mkdir -p "$(dirname "$evidence_dest")"
# Session identity stamp (#463) — close phase; start phase ran at pre-flight
session_stamp=$(python3 scripts/session_stamp.py --phase close \
    --marker "${stm_base}_realize/marketing/status/session-stamp-marketing.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means against the run's working root as the
# close's authoritative input.
python3 scripts/check_stop_condition.py \
    --manifest "<play-dir>/stop-condition.yaml" \
    --base "${stm_base}_realize/marketing/" \
    --out "${stm_base}_realize/marketing/status/stop-condition-marketing.yaml"
sc_exit=$?   # 0 held · 1 unmet · 2 error
# Conditional-gate policy refresh (#467) — soft: a distill failure never blocks the close
python3 scripts/distill_gate_policy.py \
    --ledger "$(yq '.gates.conditional.ledger' .garura/core/config.yaml)" \
    --policy "$(yq '.gates.conditional.policy' .garura/core/config.yaml)" \
    --streak "$(yq '.gates.conditional.streak' .garura/core/config.yaml)" \
    --project "$(yq '.project.name' .garura/core/config.yaml)" || true
```

**Step C0 — bind the verdict.** `sc_exit == 0` (held) permits `status: COMPLETED`.
Anything else closes `HALTED` with `exit_reason: stop_condition_unmet` and the evidence's
Stop Condition section names every unmet clause — fix the state per REC13 (re-run the keyed persist,
re-capture the scoped-guard report, or make the model-delta commit) and re-evaluate; the close stays
HALTED until the verdict reads held. An unevaluable verdict is never a pass.

`/marketing` runs on the slice-realize issue /ux opened, so it is project-scoped:
`evidence_base="${stm_base}${issue}/evidence/marketing/"` and `slug="#${issue}"`.

**Step C1 — Write evidence file.** Gated by the resolved `evidence.record` flag. When false, skip and
record `evidence skipped (record=false)`. Otherwise fill the `evidence-file.md` slots (play
`marketing`, run_id `marketing-${ts}`, slice slug, started/completed, status per C0, exit_reason;
artifacts: the live `marketing.md`, the marketing manifest, any decision, the persist manifest
(`persist-manifest.json`), the captured `guard-report.json`, the model-delta commit sha, the
stop-condition verdict; the content-eval verdict; step + scenario evals SE-1…SE-14 / SCE-1…SCE-6;
checkpoint decision (incl. any `gate skipped by config` or `gate auto-passed by learned policy` row)
plus the gate ledger line(s) appended this run; the end-sequence results; the session identity stamp
fields from $session_stamp (#463): session_id, ledger_file, ledger_start_offset, ledger_end_offset
(null when unresolved — never blocks the close); and stop_condition per C0 with the Stop Condition
section filled) and write to `$evidence_dest`. Do NOT hand-author the body.

**Step C2 — Render delivery report.** Also render the **Next** line: resolve this play in
`standards/rules/pipeline-next.md` and emit `**Next:** /<command> — <why>. Or run /next to see all
recommended actions.` (only /next pointer, or omit, when the mapped command is null), per
`play-close.md`. Fill the `delivery-report.md` slots: `## marketing Delivered — ${slug}`, the Run
Summary table (incl. the stop-condition verdict and the model-delta commit sha), the Pipeline Steps
table, the Artifacts Produced table (the marketing lens + any decision), Next Steps (the functional
pipe is closed — run the non-functional pipe: /architecture), and a pointer to `$evidence_dest`.
Always emitted.

```bash
# --- end Standard Play Close ---
```

## Scenario Validation

| Scenario | Persona | Eval |
|----------|---------|------|
| S1 — first run | growth lead | SCE-1 |
| S2 — honest reach | product owner | SCE-2 |
| S3 — accessibility | accessibility reviewer | SCE-3 |
| S4 — hub-only | architect | SCE-4 |
| S5 — re-run | product owner | SCE-5 |
| S6 — the checkpoint | reviewer | SCE-6 |

## Recovery

| For | Trigger | Direction | Handoff |
|-----|---------|-----------|---------|
| F1 | the slice is absent, a functionality does not resolve, or the profile is not firmed | halt and route to /shape or /understand before /marketing runs | human |
| F2 | a model path outside the run's write scope changed (the scoped-guard report is not ok) | the guard's `--restore` already reverted the offending paths; re-run writing only the slice's marketing.md and any decision, after a human confirms the restore | human |
| F3 | marketing.md fails the template/shape or carries out-of-scope content | re-emit to the Marketing lens template (the four sections only) | autonomous |
| F4 | marketing.md fails the content-quality eval | rewrite the failing section to the judge's cited fixes and re-judge until the gate passes | autonomous |
| F5 | an invented reach assessment, or a material choice with no decision | re-tie the assessment to the functionalities and the profile, and emit the marketing decision in the manifest | autonomous |
| F6 | a functionality was not considered | extend the assessment to consider the missing functionality | autonomous |
| F7 | /marketing read or depended on another lens | remove the dependency; /marketing derives only from the slice's hub | autonomous |
| F8 | public-marketing reach was manufactured for a slice with no public surface | reset discoverability to the honest answer ("not applicable" with the reason) | autonomous |
| F9 | accessibility was deferred to the profile or omitted | set the concrete accessibility bar and the means in the lens | autonomous |
| F10 | an accepted decision was edited in place rather than superseded (add-only breach) | restore the decision to its accepted content and record the change as a new superseding decision, after a human confirms the restore | human |
| F11 | a discoverability/accessibility pattern with no KB learning and no recorded proposal | search the KB via kb-search and ground the choice, or raise a KB-learning-gap proposal | autonomous |
| F12 | the model delta was committed before the checkpoint resolved, or a cancelled checkpoint left writes on the working tree | revert the premature commit and the working-tree writes (guard `--restore`, empty allow set) and re-present the checkpoint; commit only after the gate resolves | human |
| F13 | the run is about to close COMPLETED with the Done means unmet | close HALTED (`stop_condition_unmet`) with the unmet clauses named; fix the state — re-run the keyed persist, re-capture the scoped-guard report, or make the model-delta commit — and re-evaluate; the close stays HALTED until the verdict reads held | autonomous |
| F14 | a conditional-gate crossing left no live-eval ledger line, or an auto-pass fired for a shape the policy does not list as auto (or with a blocking finding) | re-append the missing ledger line via gate_eval.py; when the auto-pass was unearned, revert any premature commit and re-run the gate as a live wait | autonomous |
| F15 | the product-os tree is dirty at entry (uncommitted model edits present) | halt at pre-flight and ask for a clean model tree — commit or revert the pending model edits (or run the prior functional-pipe play to its persist) — before /marketing proceeds | human |

## Pause and Resume

Steps run top to bottom. On entry, resolve config, resolve the target slice, check the status marker,
skip completed steps, reset any in-progress step to pending, and continue. A fresh start with no
marker runs everything. Resuming a run that already wrote the lens enters a dirty tree; the pre-flight
clean-tree assertion (F15) is scoped to a FRESH start — a resume continues its own in-progress delta.

## Compilation Metadata

| Field | Value |
|-------|-------|
| fingerprint | sha256:5c87cf97b0171de982224d994450d8c925ebd8681b325458aeaa3e9b0c39897e (of `reference/ice.md`) |
| compiled_by | play-editor (#500 direct-model-write, ADR 026); prior: play-editor (#467 Batch B, #466 Batch C) |
| pipeline_position | end (closes the functional realize pipe; injects commit → propose → review → merge) |
| position_exception | model-writing end play — writes the model on the already-started branch and commits its own `feat(model)` model delta after approval (C14); the injected end sequence then raises/reviews/lands it (#437) |
| workflow_structure | A (single checkpoint — class: standard, conditional learned gate per gate-config.md #467; direct-model-write WRITE-THEN-REVIEW per ADR 026 — persist + guard + classify before the gate, `feat(model)` commit after; gated close) |
| stop_condition | stop-condition.yaml (D1–D3), gate live at Step C0 |
| domain_agents | 1 (product-os-keeper) |
| utility_agents | 0 |
| skills_used | kb-search, author-marketing-lens |
| scripts | 12 (preflight, check_ready_slice, lint_grounding, grounding_gate, validate_marketing, check_kb_grounding, persist_marketing — keyed decision persist, scoped_write_guard — post-write containment, classify_change — git-mode change shape, check_stop_condition — Done-means gate, session_stamp — #463 identity stamp, gate_eval + distill_gate_policy — #467 conditional gate) |
| step_evals | 14 (SE-1…SE-14) |
| scenario_evals | 6 (SCE-1…SCE-6) |
| recovery_entries | 15 (one per failure condition; 10 autonomous / 5 human) |

**Recompiled note (#500, direct-model-write / ADR 026):** migrated from draft-then-apply to
direct-model-write. The old draft model tree and the `apply_marketing.py`/`check_marketing.py`
promotion + before/after verify path are removed; the authoring skill (`author-marketing-lens`)
writes the slice's `marketing.md` straight to the live model (a re-derive) and emits any decision as
manifest data; the new keyed `persist_marketing.py` writes the decisions in place, keyed to the slice,
add-only (never editing an accepted decision in place); containment is the post-write
`scoped_write_guard.py` (the lens `--allow`, decisions `--add-only`; its `guard-report.json` is D3);
`classify_change.py` reads the working-tree git diff (`--product-base`/`--base-ref HEAD`); checkpoint
cancel reverts the working tree via the guard `--restore`; the play asserts a clean product-os tree at
entry (F15) and commits its own `feat(model)` delta after approval (C14) before the injected end
sequence. Order is **write-then-review** (ADR 026 "Order of operations"): the full delta — the LLM lens
AND the keyed persist's decisions — is written to the live model FIRST (Steps 1+3), then guarded ONCE
and classified over the full delta (Step 4), then the gate resolves over the real git diff (Step 5),
and only an approved gate COMMITS (Step 6); the injected end sequence then raises/reviews/lands it.
Nothing is COMMITTED before approval; cancel reverts the uncommitted writes. Done means moved from
"draft lens + manifest exist + `lens_applied`" to the direct-write records "persist record exists +
`applied: true` + `guard-report.json` ok" (mirroring /vision and /understand). **Honesty note on the
lens-landed guarantee:** unlike /understand or /vision — whose keyed persist writes the primary
artifact (the spine) so `applied: true` is real proof-of-work — /marketing's keyed persist writes
only decisions (often none), so D1/D2/D3 can pass without asserting the lens doc itself is on disk;
the guard's `ok: true` proves nothing changed OUTSIDE scope, not that the lens was written. The
lens-landed guarantee therefore rests on **Step 2** — `lint_grounding.py --doc <live path>` and the
content-quality eval run over the live doc, both of which fail (and stop the run before close) if the
lens is missing — not on the stop-condition. This matches the task's Done-means instruction and the
/vision reference; a future intent revision could add a read-only lens-presence stamp to the persist
if a stop-condition-level guarantee is wanted (NOT the before/after diff that was removed). See
`standards/rules/direct-model-write.md`.

**Recompiled note (#467 Batch B):** checkpoint upgraded to a conditional learned gate; see
`gate-config.md`.

**Direct-edit deviation note (#500) — INTENT CHANGE, HAND-COMPILED, CONVERGENCE UNVERIFIED:**
This SKILL was updated to the direct-model-write write-then-review shape (ADR 026) by a
**hand-compile from `reference/ice.md`**, NOT by a `/play-editor` run. This is an intent change (it
alters the write path, the containment guarantee, the checkpoint cancel semantics, and the step
order), so the sanctioned path is recompile-via-`/play-editor`; play-editor is interactive-only (fully
gated, human-checkpoint) and cannot run headless in this environment, so the compiled output was
produced by hand to match what play-editor would emit from the current `reference/ice.md` (fingerprint
above). **The `compiled_by` line names play-editor for provenance intent, but no play-editor run
actually occurred and convergence is UNVERIFIED.** An interactive `/play-editor` convergence run
against `reference/ice.md` is **REQUIRED** — confirming the emitted SKILL matches this hand-compiled
body and refreshing the fingerprint. This mirrors the same caveat on /understand's and /vision's #498
migrations (the ratified reference implementation and its fan-out): /marketing is a further fan-out of
that pattern — a REALIZE lens (position end) that writes only its slice lens + decisions and injects
the functional-pipe close — and remains convergence-unverified until an interactive play-editor run
confirms it.

Files in this skill

  • SKILL.md40.2 KB
  • reference/ice.md19.5 KB
  • scripts/check_kb_grounding.py4.8 KB
  • scripts/check_ready_slice.py5.1 KB
  • scripts/check_stop_condition.py5 KB
  • scripts/classify_change.py11 KB
  • scripts/distill_gate_policy.py6.3 KB
  • scripts/gate_eval.py3.7 KB
  • scripts/grounding_gate.py3 KB
  • scripts/lint_grounding.py17.2 KB
  • scripts/persist_marketing.py5.7 KB
  • scripts/preflight.py6.4 KB
  • scripts/scoped_write_guard.py9.2 KB
  • scripts/session_stamp.py4.9 KB
  • scripts/validate_marketing.py5.6 KB
  • stop-condition.yaml1018 B

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…