From 5a3f75df7ef6568d0720e17ae97172635940fe1c Mon Sep 17 00:00:00 2001 From: Ori Nachum Date: Sat, 12 Sep 2026 20:38:03 +0300 Subject: [PATCH 1/3] docs: re-init CLAUDE.md and align all four harness prompts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `/init` replaced the scaffold seed `CLAUDE.md` with a runtime prompt grounded in this repo, and the other three harness files were brought in line with it — each written for its own harness rather than restating the same text. - CLAUDE.md: command block (tests, single test, every lint/rubric/smoke gate), the cross-file CLI wiring (`_CliArgumentParser.error()` override, the `_json_hint` argv peek, the `parser_class` propagation trap, `CliError`, the stdout/stderr split, the catalog walked by `tests/test_cli.py`), a five-place checklist for adding a Substack noun, and `_PROMPT_FILE` vs `_RESIDENT_PROMPT`. - QWEN.md: the coding-session machinery for a Qwen Code session. - AGENTS.override.md: context only (identity stays in `.pi/SYSTEM.md`), and tells Pi's non-coding lane to report the scaffold gap rather than infer an implementation from the project description. - AGENTS.colleague.md: rewritten around colleague as the `/ask-colleague` second mind — per-verb expectations, the contracts a diff must respect, and the colleague#494 empty-skills caveat. - README.md: template-clone instructions replaced by this repo's own Status / CLI / Development sections. Fixes four drifts the docs carried: a Substack surface (posts, comments, statistics, subscribers) that does not exist on disk; a quickstart using `uv run substack-cli …` when `[project.scripts]` installs the binary as `substack`; a skill count of 11 against 19 on disk; and three files calling `CLAUDE.md` "the fullest write-up" while it was still the `/init` seed. Verified: 117 tests pass, `teken cli doctor . --strict` PASS, `harness-smoke --stage config` 6/6 PASS, scan-secrets clean, markdownlint clean, `devex pr lint` no violations. Qwen and Pi were asked "Who are you, and what can you do?" in this tree and both answered from their new files. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VaCYnmwSaYL2DLxDtd1fTs --- AGENTS.colleague.md | 132 +++++++++++++++++++---- AGENTS.override.md | 93 ++++++++++------ CHANGELOG.md | 15 +++ CLAUDE.md | 251 ++++++++++++++++++++++++++++++++++++++++---- QWEN.md | 165 ++++++++++++++++++++--------- README.md | 113 +++++++++++--------- pyproject.toml | 2 +- uv.lock | 66 ++++++------ 8 files changed, 629 insertions(+), 208 deletions(-) diff --git a/AGENTS.colleague.md b/AGENTS.colleague.md index 13f4fd2..ff6588d 100644 --- a/AGENTS.colleague.md +++ b/AGENTS.colleague.md @@ -1,16 +1,52 @@ # Colleague Resident — `substack-cli` -You are a colleague session working in a clone of this template — reading -this file because colleague's prompt cascade resolves it here, not because -`culture.yaml` selected you. That declaration says `backend: claude`, so -`CLAUDE.md` is this template's *mesh resident* prompt; colleague remains fully -usable interactively over the same clone, and this file is what it loads when -you run it. A clone that declares `backend: colleague` promotes this file to -its resident prompt as well — the guidance below holds either way. - -Your job is to assist with scoped tasks delegated by the operator or peer -agents, using the colleague tool-loop (`read_file` / `write_file` / -`edit_file` / `list_dir` / `run_command` / `finish`). +You are a **colleague** session working in this repo: the second, independent +mind a Claude Code session (or the operator directly) reaches for through the +`/ask-colleague` skill. Your value here is *diversity*, not seniority — you are +a different backend and model looking at the same code with none of the +asker's accumulated context, which is exactly why you catch what they glide +past. Say what you actually see; a confident restatement of the brief is worth +nothing to them. + +You are reading this file because colleague's prompt cascade resolves it here, +not because `culture.yaml` selected you. That declaration says +`backend: claude`, so `CLAUDE.md` is this repo's *mesh resident* prompt; +colleague remains fully usable over the same clone, and this file is what it +loads when you run. + +Your tool loop is `read_file` / `write_file` / `edit_file` / `list_dir` / +`run_command` / `finish`. + +## How you are usually invoked + +Most runs arrive through `.claude/skills/ask-colleague/scripts/ask-colleague.sh`, +which turns a verb into a `colleague work` item. Know which one you are in, +because it determines what a good answer looks like: + +- **`review "" [--base main]`** — the headline verb, and the standing + reflex before a PR. You get the **committed** diff (`...HEAD`) plus the + touched files, in a **throwaway worktree at HEAD**. Report findings, ranked, + each anchored to a file and line, each with the concrete failure it causes. + "Looks good" with no findings is a legitimate result — padding it with + style nits is not. +- **`explore ""`** — read-only investigation. Answer the + question and cite the files you read; distinguish what you observed from + what you inferred. +- **`write ""`** — implement a change. **Previews by default** (throwaway + worktree, prints the would-be diff); only `--apply` / `--pr` land a + `colleague/` branch, and those require the operator's explicit go-ahead — + never assume you have it. +- **`plan`**, **`resume`**, **`feedback`**, **`clean`** — planning, continuing a + cut run, the grading loop, and reaping crashed-run leftovers. + +In the read-only verbs your worktree is disposable and detached; nothing you +write there reaches the asker's tree or branch. What *does* reach them is your +result summary on stdout — so put the substance there, not in files nobody will +read. Per-step progress goes to stderr. + +Your output is a second opinion the asker must verify and own. Flag what you +did **not** check as plainly as what you did; an honest gap is more useful than +a smoothed-over one. ## The prompt cascade (and what this repo actually ships) @@ -23,21 +59,67 @@ colleague concatenates up to three files, in order, as its prompt cascade: **This repo ships only layer 2.** There is deliberately no `AGENTS.md` at the root (a shared base across the four harness files was proposed and rejected — -each harness gets its own, unrelated file; see `CLAUDE.md`'s "Prompt files by -harness"), so the cascade for colleague in this repo starts and ends at this -file. There is also no `AGENTS.colleague..md` — this repo -doesn't need per-model overrides today. If you add one of those files later, -update this section so the docs keep matching what's actually on disk. +each harness gets its own, unrelated file; see `CLAUDE.md`'s "Identity and the +four harnesses"), so the cascade here starts and ends at this file. There is +also no `AGENTS.colleague..md`. If you add one of those files +later, update this section so the docs keep matching what's on disk. + +`.colleague/skills` is a relative symlink onto `.claude/skills` — one skill +tree, four harnesses. (Known upstream gap: colleague 1.76.0 loads 0 of them — +[`agentculture/colleague#494`](https://github.com/agentculture/colleague/issues/494); +see `docs/harness-verification.md`. Don't read an empty skill list as a +defect in this repo.) ## What this project is -`substack-cli` is a clonable template for AgentCulture mesh agents — -an agent-first CLI, a mesh identity, the canonical skill kit, and a -buildable/deployable package baseline. `CLAUDE.md` in this repo is written for -a Claude Code session working *on* the repo — it is not your runtime prompt, -but it is the fullest write-up of the repo's conventions if you need more -context than fits here (worktree layout, memory discipline, `ask-colleague` -usage, the full skill kit list). +`substack-cli` is an **agent-first CLI to manage a Substack publication and +account** — publish and schedule posts, read posts and comments, run audience +and post statistics, and manage subscribers. Unofficial community tool, not +affiliated with Substack. + +**Status: scaffold.** None of that domain surface exists on disk yet. What is +checked in is the AgentCulture sibling baseline this repo was scaffolded from: +the CLI skeleton (`whoami`, `learn`, `explain`, `overview`, `doctor`, +`cli overview`), a mesh identity, the vendored skill kit, and a build/deploy +baseline. If a brief assumes a posts/subscribers/stats module exists, say so +rather than inventing where it lives. + +`CLAUDE.md` is written for a Claude Code session working *on* the repo — it is +not your runtime prompt, but it is the fullest write-up of the conventions if +you need more context than fits here. + +## Contracts to respect when you touch code + +These are enforced by CI and by `tests/`, so violating one turns your diff into +rework: + +- **No third-party runtime dependencies.** The CLI is cited from teken's + `python-cli` reference; `dependencies = []` in `pyproject.toml` is deliberate, + and even `culture.yaml` is parsed by hand in `_commands/whoami.py` rather than + importing PyYAML. A new library goes in the dev group or an optional extra. +- **Every failure raises `CliError(code, message, remediation)`** — never a bare + exception, never a traceback to stderr. Exit codes: `0` success, `1` user + error, `2` environment error, `3+` reserved. +- **Results to stdout, errors and diagnostics to stderr, never mixed** — in text + *and* JSON mode (`cli/_output.py`). +- **Every command takes `--json`**; any noun with action-verbs must also expose + `overview`; descriptive verbs never hard-fail on a bad target. Checked by + `uv run teken cli doctor . --strict`. +- **A new verb touches five places**: a module under `cli/_commands/` exposing + `register(sub)`, a line in `_build_parser()`, an entry in + `explain/catalog.py`, a row in `learn.py`'s text **and** JSON payload, and + tests. `tests/test_cli.py` walks `known_paths()`, so a missing catalog entry + fails the suite. +- **Nested subparsers need `parser_class=_CliArgumentParser`** (see + `_commands/cli.py`) or the noun silently drops out of the error contract. +- **Never commit credentials or non-localhost endpoints** — `scripts/scan-secrets.py` + is a CI gate; Substack cookies/tokens come from the environment. + +Verify before you hand back: `uv run pytest -n auto`, `uv run black --check +substack_cli tests`, `uv run isort --check-only substack_cli tests`, `uv run +flake8 substack_cli tests`, `uv run teken cli doctor . --strict`. Note the +installed binary is **`substack`**, though the CLI's help output says +`substack-cli`. ## How you work @@ -47,5 +129,9 @@ usage, the full skill kit list). - The vendored skills under `.claude/skills/` are cited **verbatim** from guildmaster — don't reformat or edit their scripts; a fix belongs upstream (see `docs/skill-sources.md` for the re-sync procedure). +- Four harness prompt files state overlapping conventions (`CLAUDE.md`, + `AGENTS.override.md`, `QWEN.md`, this one). Changing a convention in one + means changing it in all four; if you find them contradicting each other, + report it rather than picking a winner. - Every PR bumps the version (`version-bump` skill) — CI's `version-check` job blocks merge otherwise. diff --git a/AGENTS.override.md b/AGENTS.override.md index 797fd32..c56ade2 100644 --- a/AGENTS.override.md +++ b/AGENTS.override.md @@ -4,13 +4,12 @@ This file is the **context layer** for the Pi harness (the `pi` CLI, and the `associate` non-coding harness modelled on it) when it runs inside this repo. Pi's CONTEXT loader concatenates `AGENTS.md` or `CLAUDE.md` from its user-level config directory (see Pi's own docs), each parent directory, and the working -directory — but an `AGENTS.override.md` -present in a directory replaces that directory's `AGENTS.md`/`CLAUDE.md` entry -outright rather than adding to it. That is why this repo ships this file -instead of an `AGENTS.md`: Pi must **not** inherit `CLAUDE.md` (the Claude Code -guidance file) — the two harnesses read the same repository very differently, -and `CLAUDE.md` assumes a coding session with full repo-write authority that -Pi's non-coding lane does not have. +directory — but an `AGENTS.override.md` present in a directory replaces that +directory's `AGENTS.md`/`CLAUDE.md` entry outright rather than adding to it. +That is why this repo ships this file instead of an `AGENTS.md`: Pi must +**not** inherit `CLAUDE.md` (the Claude Code guidance file) — the two harnesses +read the same repository very differently, and `CLAUDE.md` assumes a coding +session with full repo-write authority that Pi's non-coding lane does not have. The identity and behavioral bounds for that lane — who Pi is here, what it may and may not do — live one layer up, in Pi's **system prompt** file, @@ -20,14 +19,25 @@ what the repo is and how it is laid out, not who is reading it. ## What this project is -`substack-cli` is a clonable template for AgentCulture mesh agents — -a working, minimal example of the sibling pattern every Culture agent follows: -an agent-first CLI, a mesh identity, the canonical skill kit, and a -buildable/deployable package baseline. It is a sibling to -[`guildmaster`](https://github.com/agentculture/guildmaster) (the skills -supplier), [`steward`](https://github.com/agentculture/steward) (alignment), -and [`teken`](https://github.com/agentculture/teken) (the CLI scaffolder this -package is cited from). +`substack-cli` is an **agent-first CLI to manage a Substack publication and +account** — publish and schedule posts, read posts and comments, run audience +and post statistics, and manage subscribers. Unofficial community tool, not +affiliated with Substack. + +**Status: scaffold — and this matters for every answer you give about the +repo.** None of that Substack surface exists on disk yet. What is checked in +today is the AgentCulture sibling baseline this repo was scaffolded from +(`culture-agent-template`): an agent-first CLI skeleton (`whoami`, `learn`, +`explain`, `overview`, `doctor`, `cli overview`), a mesh identity, the vendored +skill kit, and a build/deploy baseline. If you are asked where posts, +subscribers, comments or statistics are implemented, the honest answer is that +they are not — say so and point at what *is* there, rather than inferring an +implementation from the project description, the README, or this file. + +It is a sibling to [`guildmaster`](https://github.com/agentculture/guildmaster) +(the skills supplier), [`steward`](https://github.com/agentculture/steward) +(alignment), and [`teken`](https://github.com/agentculture/teken) (the CLI +scaffolder this package is cited from). ## Four harnesses, four files, no shared base @@ -47,6 +57,10 @@ repo's conventions and is the one to read first; the other three exist to keep each non-Claude harness from silently inheriting Claude-specific instructions it cannot act on the same way. +`.pi/skills` is a relative symlink onto `.claude/skills`, so a Pi session sees +the same single skill tree as the other three harnesses — the skills are not +duplicated per harness. + ## Identity Declared in `culture.yaml`: @@ -57,38 +71,57 @@ agents: backend: claude ``` -This template's *mesh* resident runs on `backend: claude`, so `CLAUDE.md` is -the live resident prompt. A Pi session working in a clone of this repo is a -**local tool session**, not the mesh resident — it reads this file and -`.pi/SYSTEM.md` regardless of what `culture.yaml` declares, and running `pi` -here neither requires nor changes that declaration. +This repo's *mesh* resident runs on `backend: claude`, so `CLAUDE.md` is the +live resident prompt. A Pi session working in this clone is a **local tool +session**, not the mesh resident — it reads this file and `.pi/SYSTEM.md` +regardless of what `culture.yaml` declares, and running `pi` here neither +requires nor changes that declaration. -(A clone that wants `associate` as its *mesh* resident declares -`backend: colleague` with `model: associate` — see `docs/skill-sources.md`. -That is a per-clone choice; this template does not ship it.) +(A sibling that wants `associate` as its *mesh* resident declares +`backend: colleague` with `model: associate`. That is a per-repo choice; this +one does not ship it.) ## Layout (what you can read/find/summarize here) ```text -substack_cli/ agent-first CLI (cited from teken's python-cli reference) +substack_cli/ agent-first CLI (cited from teken's python-cli reference) cli/ parser, error/output contract, _commands/ (verbs) explain/ markdown catalog for `explain` -tests/ pytest smoke + introspection tests +tests/ CLI smoke, introspection, harness-registry, script tests +scripts/ scan-secrets.py, harness-smoke.py (both CI gates) .claude/skills/ vendored guildmaster skill kit (cite-don't-import) -docs/skill-sources.md skill provenance ledger +docs/ skill provenance + the four-harness contract/verification culture.yaml mesh identity (suffix + backend) -.github/workflows/ tests + deploy (PyPI Trusted Publishing) +.github/workflows/ tests.yml (test/lint/harness-smoke/version-check), publish.yml ``` +Useful read-only commands for answering questions about the tree: +`uv run substack whoami`, `uv run substack learn`, `uv run substack doctor`, +`uv run substack explain ` — every one supports `--json`, writes results +to stdout and diagnostics to stderr, and changes nothing. + ## Conventions worth knowing before you answer a question about this repo +- **The installed binary is `substack`, not `substack-cli`.** `[project.scripts]` + names the command `substack`, while the CLI's own help output, the explain + catalog and most prose say `substack-cli` (the distribution name). If someone + reports that `substack-cli …` "does not exist", that mismatch is why — quote + it rather than guessing at a broken install. +- The CLI has **no third-party runtime dependencies** by design (it is cited + from teken's `python-cli` reference); even `culture.yaml` is parsed by hand + in `_commands/whoami.py` rather than importing PyYAML. If a question assumes + a library is available at runtime, check `pyproject.toml` before agreeing. +- Results go to **stdout**, errors and diagnostics to **stderr**, never mixed; + exit codes are `0` success, `1` user error, `2` environment error, `3+` + reserved. Errors print an `error:` line and a `hint:` line — no tracebacks. - The vendored skills under `.claude/skills/` are cited **verbatim** from guildmaster — never propose editing their scripts; the fix belongs upstream (`docs/skill-sources.md` has the re-sync procedure). -- The package/CLI name (`substack_cli` / `substack-cli`) - is hard-coded in roughly a hundred places; a rename is a `git grep` sweep, - not a hand edit (see `CLAUDE.md`'s "Cloning this template" section). - Every PR bumps the version (`version-bump` skill); CI's `version-check` job blocks merge otherwise. +- Four prompt files state overlapping conventions. If you spot one contradicting + another, **report the contradiction** — do not pick a winner silently; the + repo treats harness-config drift as a defect (CI's `harness-smoke` job exists + for exactly that). - This file describes the repo **as it exists on disk today**. If you are asked to update it, keep claims grounded in checked-in reality. diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6d5f4..3cba692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.1] - 2026-09-12 + +### Changed + +- **`CLAUDE.md` re-initialized from the scaffold seed into a full runtime prompt** — `/init` grounded in this repo: command block (tests, single test, every lint/rubric/smoke gate), the cross-file CLI wiring (`_CliArgumentParser.error()` override, the `_json_hint` argv peek, the `parser_class` propagation trap, `CliError`, the stdout/stderr split, the catalog walked by `tests/test_cli.py`), a five-place checklist for adding a Substack noun, and the `_PROMPT_FILE` vs `_RESIDENT_PROMPT` distinction. +- **All four harness prompt files now describe this repo rather than the template it was scaffolded from.** `QWEN.md` carries the coding-session machinery; `AGENTS.override.md` stays context-only for Pi's non-coding lane and tells it to report the scaffold gap instead of inferring an implementation; `AGENTS.colleague.md` is rewritten around colleague as the `/ask-colleague` second mind (per-verb expectations, the contracts a diff must respect, the colleague#494 empty-skills caveat). +- `README.md` reframed from template-clone instructions to this repo's own Status / CLI / Development sections; the clone-and-rename procedure is dropped now that the clone exists. + +### Fixed + +- Docs claimed a Substack surface (posts, comments, statistics, subscribers) that does not exist on disk. Every harness file and the README now state the scaffold status explicitly. +- `README.md` quickstart used `uv run substack-cli …`, which does not run — `[project.scripts]` installs the binary as `substack`. Quickstart corrected and the binary-vs-prog-name mismatch documented in all four harness files pending a code-side fix. +- Skill count corrected from 11 to 19 in `README.md` (and stated once in each harness file). +- `README.md` and `AGENTS.override.md` both called `CLAUDE.md` "the fullest write-up of the repo's conventions" and pointed at a "Cloning this template" section it did not contain, while `CLAUDE.md` was still the `/init` seed placeholder. + ## [0.9.0] - 2026-09-06 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index b90913e..02c9654 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,28 +1,241 @@ -# CLAUDE.md — seed / bootstrap placeholder +# CLAUDE.md -> **This is a self-initializing seed, not a finished runtime prompt.** -> Run `/init` (or describe the agent's domain to your AI assistant) to -> re-initialize this file into a full runtime prompt, using the description -> below and the scaffolded repo as context. +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Agent +## What this project is -This repository hosts the **substack-cli** agent. +`substack-cli` is an **agent-first CLI to manage a Substack publication and +account** — publish and schedule posts, read posts and comments, run audience +and post statistics, and manage subscribers. Unofficial community tool, not +affiliated with Substack. -## Description +**Status: scaffold.** None of that domain surface exists on disk yet. What is +checked in today is the AgentCulture sibling baseline this repo was scaffolded +from (`culture-agent-template`): the agent-first CLI skeleton (`whoami`, +`learn`, `explain`, `overview`, `doctor`, `cli overview`), a mesh identity, the +vendored guildmaster skill kit, and a buildable/deployable package baseline. +The Substack nouns/verbs are the work ahead — see +[Adding the Substack surface](#adding-the-substack-surface). -Agent-first CLI to manage a Substack publication and account: publish and schedule posts, read posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not affiliated with Substack. +It is a sibling to [`guildmaster`](https://github.com/agentculture/guildmaster) +(the **skills supplier**), [`steward`](https://github.com/agentculture/steward) +(**alignment** — `steward doctor`, the sibling-pattern baseline), and +[`teken`](https://github.com/agentculture/teken) (the **afi-cli** "Agent First +Interface" scaffolder this CLI is cited from) within the Organic Development +framework. -## Re-init instruction +## Commands -This file is a seed. To expand it into your full runtime prompt: +```bash +uv sync # install deps (dev group included) -1. Open this repo in Claude Code (or your preferred AI assistant). -2. Run `/init` — the assistant will read the repo, incorporate the description - above, and replace this seed with a complete `CLAUDE.md`. -3. Commit the result. +uv run substack whoami # note: the binary is `substack` +uv run substack learn --json +uv run substack doctor -Until you run `/init`, `substack-cli` satisfies the `steward doctor` -`prompt-file-present` and `backend-consistency` invariants (a `CLAUDE.md` -exists and `culture.yaml` declares `backend: claude`) but the prompt is not -yet tailored to this agent's domain. +uv run pytest -n auto # full suite, parallel +uv run pytest tests/test_cli.py -v # one file +uv run pytest tests/test_cli.py::test_whoami_text -v # one test +uv run pytest -n auto --cov=substack_cli --cov-report=term # coverage (fail_under=60) + +uv run black substack_cli tests # CI runs --check +uv run isort substack_cli tests # CI runs --check-only +uv run flake8 substack_cli tests # line length 100 +uv run bandit -c pyproject.toml -r substack_cli +markdownlint-cli2 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken" +python3 scripts/scan-secrets.py # committed-secret / non-localhost-endpoint gate +uv run teken cli doctor . --strict # the agent-first rubric gate CI enforces +uv run python scripts/harness-smoke.py --stage all --require config +``` + +**Binary vs. prog name.** `[project.scripts]` installs the command as +**`substack`**, while the argparse `prog` (and every doc, catalog entry and +help string) says `substack-cli`. Anything of the form `substack-cli whoami` in +this repo's prose is the *logical* command name; what you actually type is +`uv run substack whoami` (or `python -m substack_cli`). Fix the two names into +agreement before the first release rather than papering over it in docs. + +## CLI architecture + +Cited (cite-don't-import) from teken's `python-cli` reference, so the runtime +package has **no third-party dependencies** — `teken` is a dev dependency only, +and even `culture.yaml` is parsed by hand in `whoami.py` rather than pulling in +PyYAML. Keep it that way when you add domain verbs: a Substack HTTP client +belongs behind an optional extra or the stdlib, not in `dependencies`. + +The wiring that spans files, and matters before you add a verb: + +- `substack_cli/cli/__init__.py` — builds the parser, dispatches, and owns the + error contract. `_CliArgumentParser` overrides `.error()` so even *argparse* + failures (unknown verb, missing arg) render as the structured + `error:` / `hint:` pair and exit `1`, never argparse's default exit `2`. + Because parse-time errors happen before `args.json` exists, `main()` peeks at + raw argv for `--json` and stashes it on the class-level `_json_hint`; keep + passing `parser_class=_CliArgumentParser` to every `add_subparsers()` call + (see `_commands/cli.py`) or a nested noun silently drops out of the contract. + `_dispatch()` wraps any non-`CliError` exception so no traceback ever reaches + stderr. +- `substack_cli/cli/_errors.py` — `CliError(code, message, remediation)` and the + exit-code policy (`0` success, `1` user error, `2` environment error, `3+` + reserved). Every failure path raises `CliError`. +- `substack_cli/cli/_output.py` — the strict stream split: **results to stdout, + errors and diagnostics to stderr, never mixed**, in both text and JSON mode. +- `substack_cli/cli/_commands/*.py` — one module per verb/noun, each exposing + `register(sub)`. Register new noun groups in `_build_parser()` at the marked + comment. +- `substack_cli/explain/catalog.py` — markdown keyed by command-path tuple. + `tests/test_cli.py` walks `known_paths()`, so an unregistered or + uncatalogued path fails the suite. + +Three rubric-enforced rules constrain new commands (`teken cli doctor . --strict`): +every command takes `--json`; any noun with action-verbs must also expose +`overview`; descriptive verbs never hard-fail on a bad target (`overview +/no/such/path` exits `0` — see `_commands/overview.py`). `learn` must keep +covering purpose, command map, exit codes, `--json`, and `explain`. + +## Adding the Substack surface + +Work forwards from the existing shape, not around it: a new noun is a module +under `cli/_commands/` with `register(sub)`, a line in `_build_parser()`, a +catalog entry in `explain/catalog.py`, a row in `learn.py`'s text **and** JSON +payload, and tests. Credentials (Substack session cookies / API tokens) must +come from the environment — `scripts/scan-secrets.py` runs in CI and fails on +committed credentials and non-localhost endpoints. For anything non-trivial, +use `/think` → `/spec-to-plan` before writing code; that is what the vendored +devague skills are here for. + +## Identity and the four harnesses + +`culture.yaml` declares `suffix: substack-cli`, `backend: claude`. That single +`backend` key is the **mesh resident** selection: it fixes this file, +`CLAUDE.md`, as the prompt the Culture daemon reads, and it is what the two +invariants check — **prompt-file-present** and **backend-consistency** +(`claude` ↔ `CLAUDE.md`), verified by both `substack doctor` and +`steward doctor`. + +That is *not* the same as which harness you can run. Four harnesses are live +simultaneously over one clone, each reading exactly one root file, with no +shared `AGENTS.md` base for them to cascade from: + +| Harness | File(s) | +|---------|---------| +| Claude Code | `CLAUDE.md` (this file — the fullest write-up) | +| Pi / associate | `AGENTS.override.md` (context) + `.pi/SYSTEM.md` (system prompt) | +| colleague | `AGENTS.colleague.md` | +| Qwen Code | `QWEN.md` | + +`AGENTS.override.md` exists specifically so Pi does **not** inherit this file. +`.qwen/skills`, `.colleague/skills` and `.pi/skills` are relative symlinks onto +`.claude/skills` — one skill tree, four loaders. Forcing a harness is +invocation-level only (flags to one process); never rewrite `culture.yaml` to +do it — see `docs/harness-invocations.yaml` (source of truth), +`docs/automation-contract.md`, and `docs/harness-selection.md`. + +**When you edit this file, update the other three too.** They restate the same +conventions and CI's `harness-smoke` job fails if any one of the four configs +breaks. `doctor`'s `_PROMPT_FILE` table and +`.claude/skills/agent-config/data/backend-fingerprints.yaml` are two copies of +the same registry; `tests/test_harness_registries.py` fails if they drift. +`_PROMPT_FILE` (recognition — does *some* harness on this backend read this +file?) and `_RESIDENT_PROMPT` (health — does the *daemon's* file exist?) are +deliberately different tables; don't collapse them. + +## Skills + +`.claude/skills/` vendors the canonical guildmaster skill kit (19 skills, +cite-don't-import). Provenance and the re-sync procedure live in +`docs/skill-sources.md`; eight skills originate in `devague` and `ask-colleague` +is vendored directly from `colleague` as a tracked divergence. Every vendored +`SKILL.md` needs `type: command` — `core.skill_loader` silently skips one +without it. Tooling prerequisites: **`devex`** on PATH (the `cicd` skill +delegates the PR lifecycle to `devex pr`), **`agtag`** on PATH (the +`communicate` skill), and optionally **`colleague`** (only when `ask-colleague` +is invoked). + +The vendored skills are cited **verbatim** — do not reformat or edit their +scripts; a fix belongs upstream, then re-sync per `docs/skill-sources.md`. + +## Conventions + +- **Reach for `ask-colleague` reflexively.** Treat it as the teammate at the + next desk, not a last resort — its value is a *second, independent mind* (a + different backend/model), not a stronger one. Before presenting or opening a + PR on a non-trivial committed diff, run `review`; for a fresh read of an + unfamiliar area, run `explore`. Both are read-only (throwaway worktree, zero + side effects), so the reflex is always safe. The side-effecting + `write --apply` / `write --pr` still needs the user's go-ahead. Its output is + a second opinion to verify and own, never authority. +- **Every PR bumps the version** — even docs/config/CI. Use the `version-bump` + skill; the `version-check` CI job blocks merge otherwise. +- **PRs** go through the `cicd` skill (`devex pr` + SonarCloud gating against + project key `agentculture_substack-cli`). Sign online posts as + `- substack-cli (Claude)` — the `cicd` / `communicate` scripts resolve the + nick from `culture.yaml` automatically, so don't sign the body by hand. +- **Deploy**: pushing to `main` publishes to PyPI via Trusted Publishing + (`.github/workflows/publish.yml`); PRs do a TestPyPI dry-run. Configure the + `pypi` / `testpypi` GitHub environments and a PyPI Trusted Publisher before + the publish job can succeed. +- Keep this file grounded in **checked-in reality**. The Substack surface is + aspirational today; anything that runs ahead of disk goes under a `(planned)` + marker or a `## Roadmap` heading. + +## Layout + +```text +substack_cli/ agent-first CLI (cited from teken's python-cli reference) + cli/ parser, error/output contract, _commands/ (verbs) + explain/ markdown catalog for `explain` +tests/ CLI smoke, introspection, harness-registry, script tests +scripts/ scan-secrets.py, harness-smoke.py (both CI gates) +.claude/skills/ vendored guildmaster skill kit (cite-don't-import) +docs/ skill provenance + the four-harness contract/verification +culture.yaml mesh identity (suffix + backend) +.github/workflows/ tests.yml (test/lint/harness-smoke/version-check), publish.yml +``` + +## Worktrees + +**Git worktrees you create live in `../.worktrees.substack-cli//`** — one +repo-named directory beside the checkout, one subfolder per worktree: + +```bash +git worktree add ../.worktrees.substack-cli/ -b +``` + +Never a shared `../worktrees/`. This workspace holds many sibling projects, and +a generic shared folder accumulates orphaned trees from several repos with +nothing indicating who owns which — someone clearing stale trees cannot tell +yours from junk. Use a branch prefix scoped to the work (`posts/t2`, not +`agent/t2`): plain `agent/*` names collide with leftovers from earlier fan-outs +and `git worktree add -b` fails on an existing branch. + +The vendored `assign-to-workforce` skill's fan-out example uses both the shared +`../worktrees/` path and `agent/` branches — override *both* when you +follow it; the skill is cited verbatim and must not be edited. + +**Exception — tool-managed throwaways.** `ask-colleague`'s read-only verbs +create their own detached worktree under `${TMPDIR:-/tmp}` and delete it on an +EXIT trap; expect `git worktree list` to show one mid-command. Remove a +worktree you are done with via `git worktree remove ` — `git worktree +prune` only clears metadata for directories that are already gone. Never +`rm -rf` a worktree directory you did not create. + +## Memory discipline — recall before, remember after + +This repo keeps its eidetic memory **in-repo and public**: records resolve to +`/.eidetic/memory` — committed, and shared with mesh peers (the +`claude` and `colleague` backends both read the `substack-cli` scope), so +memory travels with the repo rather than a private home-dir store. + +- **`/recall` before you start** a non-trivial task — prior decisions, gotchas, + "have we done this before?" — so you build on what's known instead of + re-deriving it. +- **`/remember` when something worth keeping surfaces** — a non-obvious + decision and its rationale, a constraint, a fix and *why*. Capture it as it + happens. + +A plain `/remember` lands the note in `./.eidetic/memory` (the wrappers default +to `--visibility public`; in-repo routing needs `eidetic >= 0.10.0`). Keep +something out of the committed store with `--visibility private` (routes to +`$HOME`); `/recall` reads and merges both. Don't store what the repo already +records — store what you'd otherwise re-derive. diff --git a/QWEN.md b/QWEN.md index 8991c95..d98d7e0 100644 --- a/QWEN.md +++ b/QWEN.md @@ -9,11 +9,18 @@ Qwen Code session. ## What this project is -`substack-cli` is a **clonable template for AgentCulture mesh agents**. -It is a working, minimal example of the sibling pattern every Culture agent -follows: an agent-first CLI, a mesh identity, the canonical skill kit, and a -buildable/deployable package baseline. Clone it, rename the package, edit -`culture.yaml`, and you have a new agent that `steward doctor` recognizes. +`substack-cli` is an **agent-first CLI to manage a Substack publication and +account** — publish and schedule posts, read posts and comments, run audience +and post statistics, and manage subscribers. Unofficial community tool, not +affiliated with Substack. + +**Status: scaffold.** None of that domain surface exists on disk yet. What is +checked in today is the AgentCulture sibling baseline this repo was scaffolded +from (`culture-agent-template`): the agent-first CLI skeleton (`whoami`, +`learn`, `explain`, `overview`, `doctor`, `cli overview`), a mesh identity, the +vendored guildmaster skill kit, and a buildable/deployable package baseline. +The Substack nouns and verbs are the work ahead. Do not describe them as +existing, and do not assume a hidden module implements them — read the tree. It is a sibling to [`guildmaster`](https://github.com/agentculture/guildmaster) (the **skills supplier**), [`steward`](https://github.com/agentculture/steward) @@ -28,12 +35,16 @@ This repo's root carries one prompt file per agent harness, each read by exactly one of them — there is no shared base file for them to inherit from: - **Claude Code** → [`CLAUDE.md`](CLAUDE.md) (the fullest write-up; read it - first if you are new to the repo). + first if you need more than fits here). - **Pi / associate** → [`AGENTS.override.md`](AGENTS.override.md) for context, plus [`.pi/SYSTEM.md`](.pi/SYSTEM.md) for its system prompt. - **colleague** → [`AGENTS.colleague.md`](AGENTS.colleague.md). - **Qwen Code** → this file. +`.qwen/skills` is a relative symlink onto `.claude/skills`, so a Qwen Code +session loads the same one skill tree the other three harnesses do — no forked +copies. + ## Identity Declared in `culture.yaml`: @@ -45,80 +56,130 @@ agents: ``` `backend: claude` fixes the *mesh resident* prompt file to `CLAUDE.md` — the -mesh runtime reads that file, not this one. A Qwen Code session working in a -clone of this repo is a separate, local tool session; it reads `QWEN.md` -regardless of what `culture.yaml` declares, and running Qwen Code here neither -requires nor changes that declaration. The declaration and the resident prompt -together satisfy the two invariants `steward doctor` verifies: +mesh runtime reads that file, not this one. A Qwen Code session working in this +clone is a separate, local tool session; it reads `QWEN.md` regardless of what +`culture.yaml` declares, and running Qwen Code here neither requires nor +changes that declaration. The declaration and the resident prompt together +satisfy the two invariants `steward doctor` verifies: **prompt-file-present** and **backend-consistency** (`claude` ↔ `CLAUDE.md`). -## Cloning this template (re-initialization) +## Commands + +```bash +uv sync # install deps (dev group included) -When you start a new agent from this template: +uv run substack whoami # note: the binary is `substack` +uv run substack learn --json +uv run substack doctor -1. Rename the package directory `substack_cli/` → `/` - and replace `substack_cli` (module) / `substack-cli` - (CLI and dist name) throughout `pyproject.toml`, the package, `tests/`, - `sonar-project.properties`, and `README.md`. The name is hard-coded in - ~100 places, so list every occurrence first rather than renaming by hand - (`git grep` is portable and skips `.git` / untracked `__pycache__`): +uv run pytest -n auto # full suite, parallel +uv run pytest tests/test_cli.py -v # one file +uv run pytest tests/test_cli.py::test_whoami_text -v # one test +uv run pytest -n auto --cov=substack_cli --cov-report=term # coverage (fail_under=60) - ```bash - git grep -nF -e 'substack-cli' -e 'substack_cli' - ``` +uv run black substack_cli tests # CI runs --check +uv run isort substack_cli tests # CI runs --check-only +uv run flake8 substack_cli tests # line length 100 +uv run bandit -c pyproject.toml -r substack_cli +markdownlint-cli2 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken" +python3 scripts/scan-secrets.py # committed-secret / non-localhost-endpoint gate +uv run teken cli doctor . --strict # the agent-first rubric gate CI enforces +uv run python scripts/harness-smoke.py --stage all --require config +``` -2. Set your `suffix` (and `backend`) in `culture.yaml`. `whoami` and `doctor` - then reflect the new identity with no further code change. -3. Rewrite `CLAUDE.md` (and this file, and the other two harness files) to - describe your agent. -4. Re-vendor the skill kit you need from guildmaster (see - `docs/skill-sources.md`) — keep only the skills your agent uses. +**Binary vs. prog name.** `[project.scripts]` installs the command as +**`substack`**, while the argparse `prog` (and every doc, catalog entry and +help string) says `substack-cli`. Prose of the form `substack-cli whoami` is +the *logical* command name; what you actually type is `uv run substack whoami` +(or `python -m substack_cli`). -## The CLI +## The CLI contract The CLI is cited (cite-don't-import) from teken's `python-cli` reference (`teken cli cite`), so the runtime package has **no third-party dependencies**; -`teken` (a.k.a. `afi-cli`) is a dev dependency only. Agent-first verbs: - -- `substack-cli whoami` — identity from `culture.yaml`. -- `substack-cli learn` — structured self-teaching prompt. -- `substack-cli explain ` — markdown docs for any noun/verb. -- `substack-cli overview` — descriptive snapshot of the agent. -- `substack-cli doctor` — check the agent-identity invariants. -- `substack-cli cli overview` — describe the CLI surface itself. - -Conventions: every command supports `--json`; results go to stdout, errors and -diagnostics to stderr (never mixed); exit codes are `0` success, `1` user -error, `2` environment error, `3+` reserved. The agent-first rubric is -enforced in CI by `teken cli doctor . --strict`. +`teken` (a.k.a. `afi-cli`) is a dev dependency only, and `culture.yaml` is +parsed by hand in `_commands/whoami.py` rather than pulling in PyYAML. Keep it +that way when you add domain verbs — a Substack HTTP client belongs behind an +optional extra or in the stdlib, not in `dependencies`. + +Verbs today: `whoami`, `learn`, `explain `, `overview`, `doctor`, +`cli overview`. + +The wiring that spans files: + +- `substack_cli/cli/__init__.py` — parser, dispatch, error contract. + `_CliArgumentParser` overrides `.error()` so even *argparse* failures + (unknown verb, missing arg) render as the structured `error:` / `hint:` pair + and exit `1`, not argparse's default exit `2`. Parse-time errors happen + before `args.json` exists, so `main()` peeks at raw argv for `--json` and + stashes it on the class-level `_json_hint`. Pass + `parser_class=_CliArgumentParser` to every `add_subparsers()` call (see + `_commands/cli.py`) or a nested noun drops out of the contract silently. + `_dispatch()` wraps any non-`CliError` exception so no traceback reaches + stderr. +- `substack_cli/cli/_errors.py` — `CliError(code, message, remediation)` plus + the exit-code policy: `0` success, `1` user error, `2` environment error, + `3+` reserved. Every failure path raises `CliError`. +- `substack_cli/cli/_output.py` — results to **stdout**, errors and diagnostics + to **stderr**, never mixed, in both text and JSON mode. +- `substack_cli/cli/_commands/*.py` — one module per verb/noun, each exposing + `register(sub)`; register new noun groups in `_build_parser()` at the marked + comment. +- `substack_cli/explain/catalog.py` — markdown keyed by command-path tuple. + `tests/test_cli.py` walks `known_paths()`, so an unregistered or + uncatalogued path fails the suite. + +Rubric rules CI enforces via `teken cli doctor . --strict`: every command takes +`--json`; any noun with action-verbs must also expose `overview`; descriptive +verbs never hard-fail on a bad target (`overview /no/such/path` exits `0`); +`learn` must keep covering purpose, command map, exit codes, `--json`, and +`explain`. + +## Adding the Substack surface + +A new noun is a module under `cli/_commands/` with `register(sub)`, a line in +`_build_parser()`, a catalog entry in `explain/catalog.py`, a row in `learn.py`'s +text **and** JSON payload, and tests. Credentials (Substack session cookies or +API tokens) come from the environment — `scripts/scan-secrets.py` runs in CI +and fails on committed credentials and non-localhost endpoints. ## Skills -`.claude/skills/` vendors the **canonical guildmaster skill kit** -(cite-don't-import). Provenance and the re-sync procedure live in -`docs/skill-sources.md`. Do not reformat or edit vendored scripts — re-sync -from guildmaster instead. +`.claude/skills/` vendors the **canonical guildmaster skill kit** (19 skills, +cite-don't-import), reachable here through the `.qwen/skills` symlink. +Provenance and the re-sync procedure live in `docs/skill-sources.md`. Do not +reformat or edit vendored scripts — a fix belongs upstream, then re-sync. Every +vendored `SKILL.md` needs `type: command`; `core.skill_loader` silently skips +one without it. ## Conventions - **Every PR bumps the version** — even docs/config/CI. Use the `version-bump` skill; the `version-check` CI job blocks merge otherwise. -- **Tests**: `uv run pytest -n auto`. **Lint**: black, isort, flake8 (line - length 100), bandit, markdownlint. +- **Four harnesses, four files.** If you change a convention in this file, + change it in `CLAUDE.md`, `AGENTS.override.md` and `AGENTS.colleague.md` too + — CI's `harness-smoke` job fails when any one of the four configs breaks. +- `doctor`'s `_PROMPT_FILE` table and + `.claude/skills/agent-config/data/backend-fingerprints.yaml` are two copies + of one registry; `tests/test_harness_registries.py` fails if they drift. + `_PROMPT_FILE` (recognition — does *some* harness on this backend read this + file?) and `_RESIDENT_PROMPT` (health — does the *daemon's* file exist?) are + deliberately different tables; don't collapse them. - **Deploy**: pushing to `main` publishes to PyPI via Trusted Publishing (`.github/workflows/publish.yml`); PRs do a TestPyPI dry-run. ## Layout ```text -substack_cli/ agent-first CLI (cited from teken's python-cli reference) +substack_cli/ agent-first CLI (cited from teken's python-cli reference) cli/ parser, error/output contract, _commands/ (verbs) explain/ markdown catalog for `explain` -tests/ pytest smoke + introspection tests +tests/ CLI smoke, introspection, harness-registry, script tests +scripts/ scan-secrets.py, harness-smoke.py (both CI gates) .claude/skills/ vendored guildmaster skill kit (cite-don't-import) -docs/skill-sources.md skill provenance ledger +docs/ skill provenance + the four-harness contract/verification culture.yaml mesh identity (suffix + backend) -.github/workflows/ tests + deploy (PyPI Trusted Publishing) +.github/workflows/ tests.yml (test/lint/harness-smoke/version-check), publish.yml ``` This file describes the repository **as it exists on disk today**. When you diff --git a/README.md b/README.md index 57d361c..fb3e463 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,64 @@ # substack-cli -Agent-first CLI to manage a Substack publication and account: publish and schedule posts, read posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not affiliated with Substack. +Agent-first CLI to manage a Substack publication and account: publish and +schedule posts, read posts and comments, run audience and post statistics, and +manage subscribers. Unofficial community tool, not affiliated with Substack. -## What you get +## Status + +**Scaffold.** The Substack surface above is the goal, not what ships today. +What is on disk now is the AgentCulture sibling baseline this repo was +scaffolded from ([`culture-agent-template`](https://github.com/agentculture/culture-agent-template)): +an agent-first CLI skeleton, a mesh identity, the vendored skill kit, and a +buildable/deployable package baseline. Everything documented below is +checked-in reality; the post/subscriber/stats verbs are the work ahead. + +## What you get today - **An agent-first CLI** cited from [teken](https://github.com/agentculture/teken) (`afi-cli`) — the runtime package has no third-party dependencies. - **A mesh identity** — `culture.yaml` (`suffix` + `backend`) and the matching - resident prompt file (`CLAUDE.md`, since this template runs - `backend: claude`). The mesh resident is one of **two separate - selections** over this clone — see - [Two selections, not one](#two-selections-not-one) below. + resident prompt file (`CLAUDE.md`, since this repo runs `backend: claude`). + The mesh resident is one of **two separate selections** over this clone — + see [Two selections, not one](#two-selections-not-one) below. - **Four harness prompt files**, one per agent harness, each read by exactly one of them (see [Prompt files by harness](#prompt-files-by-harness) below). All four harnesses are usable interactively regardless of which one `culture.yaml` names as the mesh resident. -- **The canonical guildmaster skill kit** (11 skills) under `.claude/skills/`, +- **The canonical guildmaster skill kit** (19 skills) under `.claude/skills/`, vendored cite-don't-import. See [`docs/skill-sources.md`](docs/skill-sources.md). -- **A build + deploy baseline** — pytest, lint, the agent-first rubric gate, and - PyPI Trusted Publishing wired into GitHub Actions. +- **A build + deploy baseline** — pytest, lint, the agent-first rubric gate, a + committed-secret scanner, a per-harness smoke check, and PyPI Trusted + Publishing wired into GitHub Actions. + +## Quickstart + +```bash +uv sync +uv run pytest -n auto # run the test suite +uv run substack whoami # identity from culture.yaml +uv run substack learn # self-teaching prompt (add --json) +uv run teken cli doctor . --strict # the agent-first rubric gate CI runs +``` + +## CLI + +The installed command is **`substack`** (`[project.scripts]` in +`pyproject.toml`); `substack-cli` is the distribution name and the name the +help output prints. `python -m substack_cli` works too. + +| Verb | What it does | +|------|--------------| +| `whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. | +| `learn` | Print a structured self-teaching prompt. | +| `explain ` | Markdown docs for any noun/verb path. | +| `overview` | Read-only descriptive snapshot of the agent. | +| `doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). | +| `cli overview` | Describe the CLI surface itself. | + +Every command supports `--json`. Results go to stdout, errors/diagnostics to +stderr (never mixed). Exit codes: `0` success, `1` user error, `2` environment +error, `3+` reserved. ## Prompt files by harness @@ -54,7 +94,7 @@ unrelated file rather than cascading from a shared base. ## Two selections, not one It is tempting to read "switch harness" as one decision. It is actually two, -and this template exists partly to keep them separate: +and this repo's layout exists partly to keep them separate: 1. **The interactive harness** — which binary you run (`claude`, `pi`, `colleague`, `qwen`). `cd` into the clone and run any of them; all four @@ -69,51 +109,24 @@ and this template exists partly to keep them separate: `culture.yaml`'s `backend` affects (2) only. It never affects which harness you can invoke interactively in (1). See [`docs/harness-selection.md`](docs/harness-selection.md) for the full -writeup, including who reads this config and why existing siblings are not -retrofitted by this arc. +writeup. -## Quickstart +## Development ```bash -uv sync -uv run pytest -n auto # run the test suite -uv run substack-cli whoami # identity from culture.yaml -uv run substack-cli learn # self-teaching prompt (add --json) -uv run teken cli doctor . --strict # the agent-first rubric gate CI runs +uv run pytest -n auto # full suite +uv run pytest tests/test_cli.py -v # one file +uv run black substack_cli tests # CI runs --check +uv run isort substack_cli tests # CI runs --check-only +uv run flake8 substack_cli tests # line length 100 +uv run bandit -c pyproject.toml -r substack_cli +python3 scripts/scan-secrets.py # committed-secret gate +uv run python scripts/harness-smoke.py --stage all --require config ``` -## CLI - -| Verb | What it does | -|------|--------------| -| `whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. | -| `learn` | Print a structured self-teaching prompt. | -| `explain ` | Markdown docs for any noun/verb path. | -| `overview` | Read-only descriptive snapshot of the agent. | -| `doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). | -| `cli overview` | Describe the CLI surface itself. | - -Every command supports `--json`. Results go to stdout, errors/diagnostics to -stderr (never mixed). Exit codes: `0` success, `1` user error, `2` environment -error, `3+` reserved. - -## Make it your own - -1. Rename the package `substack_cli/` and the `substack-cli` - CLI/dist name throughout `pyproject.toml`, the package, `tests/`, - `sonar-project.properties`, and this `README.md`. The name is hard-coded in - ~100 places, so list every occurrence first — see the `git grep` discovery - command in [`CLAUDE.md`](CLAUDE.md), the authoritative rename procedure. -2. Edit `culture.yaml` with your `suffix` and `backend`. -3. Rewrite `CLAUDE.md` for your agent and run `/init`. Rewrite the other three - harness files (`AGENTS.override.md` + `.pi/SYSTEM.md`, `AGENTS.colleague.md`, - `QWEN.md`) too if your agent uses those harnesses — don't let them drift out - of sync with `CLAUDE.md`. -4. Re-vendor only the skills you need from guildmaster (see - [`docs/skill-sources.md`](docs/skill-sources.md)). - -See [`CLAUDE.md`](CLAUDE.md) for the full conventions (version-bump-every-PR, -the `cicd` PR lane, deploy setup). +Every PR bumps the version in `pyproject.toml` (CI's `version-check` job blocks +merge otherwise). See [`CLAUDE.md`](CLAUDE.md) for the full conventions (CLI +error/output contract, the `cicd` PR lane, worktree layout, deploy setup). ## License diff --git a/pyproject.toml b/pyproject.toml index 5e8d175..02a8bf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "substack-cli" -version = "0.9.0" +version = "0.9.1" description = "Agent-first CLI to manage a Substack publication and account: publish and schedule posts, read posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not affiliated with Substack." readme = "README.md" license = "Apache-2.0" diff --git a/uv.lock b/uv.lock index c0ae76e..7b4d9bd 100644 --- a/uv.lock +++ b/uv.lock @@ -154,39 +154,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" }, ] -[[package]] -name = "substack-cli" -version = "0.9.0" -source = { editable = "." } - -[package.dev-dependencies] -dev = [ - { name = "bandit" }, - { name = "black" }, - { name = "flake8" }, - { name = "isort" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "pytest-xdist" }, - { name = "pyyaml" }, - { name = "teken" }, -] - -[package.metadata] - -[package.metadata.requires-dev] -dev = [ - { name = "bandit", specifier = ">=1.7.5" }, - { name = "black", specifier = ">=23.7.0" }, - { name = "flake8", specifier = ">=6.1" }, - { name = "isort", specifier = ">=5.12.0" }, - { name = "pytest", specifier = ">=8.0" }, - { name = "pytest-cov", specifier = ">=4.1" }, - { name = "pytest-xdist", specifier = ">=3.0" }, - { name = "pyyaml", specifier = ">=6.0" }, - { name = "teken", specifier = ">=0.8" }, -] - [[package]] name = "execnet" version = "2.1.2" @@ -470,6 +437,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f5/ac/19f9941c74add59d17694930ec8105d5eddeee4ce56dd8632b765ca16d6c/stevedore-5.8.0-py3-none-any.whl", hash = "sha256:88eede9e66ca80e34085b9174e2327da2c61ac91f24f70e41c3ad76e4bb4872b", size = 54553, upload-time = "2026-05-18T09:15:25.82Z" }, ] +[[package]] +name = "substack-cli" +version = "0.9.1" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "bandit" }, + { name = "black" }, + { name = "flake8" }, + { name = "isort" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-xdist" }, + { name = "pyyaml" }, + { name = "teken" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "bandit", specifier = ">=1.7.5" }, + { name = "black", specifier = ">=23.7.0" }, + { name = "flake8", specifier = ">=6.1" }, + { name = "isort", specifier = ">=5.12.0" }, + { name = "pytest", specifier = ">=8.0" }, + { name = "pytest-cov", specifier = ">=4.1" }, + { name = "pytest-xdist", specifier = ">=3.0" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "teken", specifier = ">=0.8" }, +] + [[package]] name = "teken" version = "0.8.0" From e61b4dc5079c32c9cc8850c8ed61c7260925c17b Mon Sep 17 00:00:00 2001 From: Ori Nachum Date: Sat, 12 Sep 2026 20:44:22 +0300 Subject: [PATCH 2/3] docs: correct the assign-to-workforce worktree citation in CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Worktrees section claimed the vendored skill's fan-out example uses a shared `../worktrees/` path that must be overridden. It does not: the current `.claude/skills/assign-to-workforce/SKILL.md` mandates the same repo-named `.worktrees.` root this file does, and explicitly says "Never use a bare `../worktrees/`". The claim was inherited from culture-agent-template's CLAUDE.md and describes an older revision of the vendored file. Only the branch-name half of the override still applies (`agent/`). Found by `ask-colleague review` on this PR. Its other two findings: the "eight skills originate in devague" phrasing is correct as written (docs/skill-sources.md lists exactly eight devague-origin rows, all re-broadcast via guildmaster — the direct-from-devague vendoring it recalled is marked superseded in that ledger), and the code-side "clonable template" strings are filed as #3 rather than folded into a doc-only PR. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VaCYnmwSaYL2DLxDtd1fTs --- CLAUDE.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 02c9654..17e2079 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -209,9 +209,10 @@ yours from junk. Use a branch prefix scoped to the work (`posts/t2`, not `agent/t2`): plain `agent/*` names collide with leftovers from earlier fan-outs and `git worktree add -b` fails on an existing branch. -The vendored `assign-to-workforce` skill's fan-out example uses both the shared -`../worktrees/` path and `agent/` branches — override *both* when you -follow it; the skill is cited verbatim and must not be edited. +The vendored `assign-to-workforce` skill already mandates the same repo-named +worktree root (`.worktrees.`, `SKILL.md` §Fan-out) — that half needs +no override. Its example *branch* names are `agent/`, so scope those to +the work when you follow it; the skill is cited verbatim and must not be edited. **Exception — tool-managed throwaways.** `ask-colleague`'s read-only verbs create their own detached worktree under `${TMPDIR:-/tmp}` and delete it on an From dd96960e045ad16f3d124bcd21390985d064ff55 Mon Sep 17 00:00:00 2001 From: Ori Nachum Date: Sat, 12 Sep 2026 20:47:56 +0300 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20address=20Qodo=20review=20=E2=80=94?= =?UTF-8?q?=20planned=20markers,=20lint=20prereq,=20contract=20precision?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six inline findings from the Qodo review on PR #2, each verified against disk before acting: - Mark the Substack surface `(planned)` at first mention in all four harness files and on the "Adding the Substack surface" heading, per this repo's own rule that anything ahead of disk carries the marker. - Note that `markdownlint-cli2` is an npm prerequisite, not something `uv sync` installs — CI installs it separately, so a clean checkout following the documented sequence stopped there. - Stop implying all four harnesses load the shared skill tree: the symlinks are shared wiring, but colleague 1.76.0 loads 0 of the 19 (colleague#494). The caveat was in AGENTS.colleague.md only; it is now in CLAUDE.md and QWEN.md where the "one tree, four loaders" claim was made. - Qualify the CliError contract. "Every failure raises CliError" contradicted two deliberate paths: `_CliArgumentParser.error()` emits a CliError then raises SystemExit, and `doctor` returns 1 for an unhealthy report rather than raising. Both are now named so neither gets "fixed" by a future change. - AGENTS.colleague.md claimed nothing from a read-only run reaches the asker's checkout. Tracked files, correct — but the run artifact is copied into the gitignored `.colleague/` dir for grading. Stated. - docs/skill-sources.md listed 17 rows while `.claude/skills/` holds 19: `recall` and `remember` were vendored without ledger entries. Added both (guildmaster, scope literal adapted), plus a header line accounting for all 19. Qodo read the gap as "guildmaster does not supply these two" — it does ship both; the defect was the missing rows, not the supplier. Also corrected the kit description in CLAUDE.md, QWEN.md and README.md: 19 skills is 17 guildmaster + ask-colleague direct from colleague, not a single "canonical guildmaster kit". Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VaCYnmwSaYL2DLxDtd1fTs --- AGENTS.colleague.md | 23 ++++++++++++++--------- AGENTS.override.md | 4 ++-- CLAUDE.md | 32 ++++++++++++++++++++------------ QWEN.md | 25 ++++++++++++++++--------- README.md | 5 +++-- docs/skill-sources.md | 7 +++++++ 6 files changed, 62 insertions(+), 34 deletions(-) diff --git a/AGENTS.colleague.md b/AGENTS.colleague.md index ff6588d..1b31e90 100644 --- a/AGENTS.colleague.md +++ b/AGENTS.colleague.md @@ -39,10 +39,12 @@ because it determines what a good answer looks like: - **`plan`**, **`resume`**, **`feedback`**, **`clean`** — planning, continuing a cut run, the grading loop, and reaping crashed-run leftovers. -In the read-only verbs your worktree is disposable and detached; nothing you -write there reaches the asker's tree or branch. What *does* reach them is your -result summary on stdout — so put the substance there, not in files nobody will -read. Per-step progress goes to stderr. +In the read-only verbs your worktree is disposable and detached: no tracked +file you touch there reaches the asker's tree or branch. Two things do reach +their checkout — your result summary on **stdout** (put the substance there, +not in files nobody will read), and a run artifact copied into the gitignored +`.colleague/` directory so the run can be graded later. Per-step progress goes +to stderr. Your output is a second opinion the asker must verify and own. Flag what you did **not** check as plainly as what you did; an honest gap is more useful than @@ -73,8 +75,8 @@ defect in this repo.) ## What this project is `substack-cli` is an **agent-first CLI to manage a Substack publication and -account** — publish and schedule posts, read posts and comments, run audience -and post statistics, and manage subscribers. Unofficial community tool, not +account** *(planned — see Status below)* — publish and schedule posts, read +posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not affiliated with Substack. **Status: scaffold.** None of that domain surface exists on disk yet. What is @@ -97,9 +99,12 @@ rework: `python-cli` reference; `dependencies = []` in `pyproject.toml` is deliberate, and even `culture.yaml` is parsed by hand in `_commands/whoami.py` rather than importing PyYAML. A new library goes in the dev group or an optional extra. -- **Every failure raises `CliError(code, message, remediation)`** — never a bare - exception, never a traceback to stderr. Exit codes: `0` success, `1` user - error, `2` environment error, `3+` reserved. +- **Every command handler raises `CliError(code, message, remediation)`** on + failure — never a bare exception, never a traceback to stderr. Exit codes: + `0` success, `1` user error, `2` environment error, `3+` reserved. Two + existing paths differ downstream and are not bugs to "fix": + `_CliArgumentParser.error()` emits a `CliError` then raises `SystemExit`, and + `doctor` returns `1` for an unhealthy report instead of raising. - **Results to stdout, errors and diagnostics to stderr, never mixed** — in text *and* JSON mode (`cli/_output.py`). - **Every command takes `--json`**; any noun with action-verbs must also expose diff --git a/AGENTS.override.md b/AGENTS.override.md index c56ade2..6212228 100644 --- a/AGENTS.override.md +++ b/AGENTS.override.md @@ -20,8 +20,8 @@ what the repo is and how it is laid out, not who is reading it. ## What this project is `substack-cli` is an **agent-first CLI to manage a Substack publication and -account** — publish and schedule posts, read posts and comments, run audience -and post statistics, and manage subscribers. Unofficial community tool, not +account** *(planned — see Status below)* — publish and schedule posts, read +posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not affiliated with Substack. **Status: scaffold — and this matters for every answer you give about the diff --git a/CLAUDE.md b/CLAUDE.md index 17e2079..af84a11 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,8 +5,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## What this project is `substack-cli` is an **agent-first CLI to manage a Substack publication and -account** — publish and schedule posts, read posts and comments, run audience -and post statistics, and manage subscribers. Unofficial community tool, not +account** *(planned — see Status below)* — publish and schedule posts, read +posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not affiliated with Substack. **Status: scaffold.** None of that domain surface exists on disk yet. What is @@ -15,7 +15,7 @@ from (`culture-agent-template`): the agent-first CLI skeleton (`whoami`, `learn`, `explain`, `overview`, `doctor`, `cli overview`), a mesh identity, the vendored guildmaster skill kit, and a buildable/deployable package baseline. The Substack nouns/verbs are the work ahead — see -[Adding the Substack surface](#adding-the-substack-surface). +[Adding the Substack surface](#adding-the-substack-surface-planned). It is a sibling to [`guildmaster`](https://github.com/agentculture/guildmaster) (the **skills supplier**), [`steward`](https://github.com/agentculture/steward) @@ -42,6 +42,7 @@ uv run black substack_cli tests # CI runs --check uv run isort substack_cli tests # CI runs --check-only uv run flake8 substack_cli tests # line length 100 uv run bandit -c pyproject.toml -r substack_cli +# markdownlint-cli2 is npm, not uv: npm install -g markdownlint-cli2@0.21.0 markdownlint-cli2 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken" python3 scripts/scan-secrets.py # committed-secret / non-localhost-endpoint gate uv run teken cli doctor . --strict # the agent-first rubric gate CI enforces @@ -77,7 +78,10 @@ The wiring that spans files, and matters before you add a verb: stderr. - `substack_cli/cli/_errors.py` — `CliError(code, message, remediation)` and the exit-code policy (`0` success, `1` user error, `2` environment error, `3+` - reserved). Every failure path raises `CliError`. + reserved). Every *command handler* raises `CliError` on failure. Two paths + deliberately differ downstream: `_CliArgumentParser.error()` emits a + `CliError` and then raises `SystemExit`, and `doctor` *returns* `1` for an + unhealthy report (an unhealthy agent is a result, not a CLI failure). - `substack_cli/cli/_output.py` — the strict stream split: **results to stdout, errors and diagnostics to stderr, never mixed**, in both text and JSON mode. - `substack_cli/cli/_commands/*.py` — one module per verb/noun, each exposing @@ -93,7 +97,7 @@ every command takes `--json`; any noun with action-verbs must also expose /no/such/path` exits `0` — see `_commands/overview.py`). `learn` must keep covering purpose, command map, exit codes, `--json`, and `explain`. -## Adding the Substack surface +## Adding the Substack surface (planned) Work forwards from the existing shape, not around it: a new noun is a module under `cli/_commands/` with `register(sub)`, a line in `_build_parser()`, a @@ -126,9 +130,12 @@ shared `AGENTS.md` base for them to cascade from: `AGENTS.override.md` exists specifically so Pi does **not** inherit this file. `.qwen/skills`, `.colleague/skills` and `.pi/skills` are relative symlinks onto -`.claude/skills` — one skill tree, four loaders. Forcing a harness is -invocation-level only (flags to one process); never rewrite `culture.yaml` to -do it — see `docs/harness-invocations.yaml` (source of truth), +`.claude/skills` — one skill tree, wired to all four harnesses. Three of them +load it; colleague 1.76.0 loads 0 of the 19 for upstream reasons +([colleague#494](https://github.com/agentculture/colleague/issues/494), +`docs/harness-verification.md`), so the wiring is shared but the loading is +not. Forcing a harness is invocation-level only (flags to one process); never +rewrite `culture.yaml` to do it — see `docs/harness-invocations.yaml` (source of truth), `docs/automation-contract.md`, and `docs/harness-selection.md`. **When you edit this file, update the other three too.** They restate the same @@ -142,10 +149,11 @@ deliberately different tables; don't collapse them. ## Skills -`.claude/skills/` vendors the canonical guildmaster skill kit (19 skills, -cite-don't-import). Provenance and the re-sync procedure live in -`docs/skill-sources.md`; eight skills originate in `devague` and `ask-colleague` -is vendored directly from `colleague` as a tracked divergence. Every vendored +`.claude/skills/` vendors 19 skills, cite-don't-import: 17 from guildmaster +(eight of those devague-origin re-broadcasts) and `ask-colleague` direct from +`colleague` as a tracked divergence. Provenance and the re-sync procedure live +in `docs/skill-sources.md` — check a skill's row there before assuming +guildmaster is its upstream. Every vendored `SKILL.md` needs `type: command` — `core.skill_loader` silently skips one without it. Tooling prerequisites: **`devex`** on PATH (the `cicd` skill delegates the PR lifecycle to `devex pr`), **`agtag`** on PATH (the diff --git a/QWEN.md b/QWEN.md index d98d7e0..776cf48 100644 --- a/QWEN.md +++ b/QWEN.md @@ -10,8 +10,8 @@ Qwen Code session. ## What this project is `substack-cli` is an **agent-first CLI to manage a Substack publication and -account** — publish and schedule posts, read posts and comments, run audience -and post statistics, and manage subscribers. Unofficial community tool, not +account** *(planned — see Status below)* — publish and schedule posts, read +posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not affiliated with Substack. **Status: scaffold.** None of that domain surface exists on disk yet. What is @@ -42,8 +42,9 @@ exactly one of them — there is no shared base file for them to inherit from: - **Qwen Code** → this file. `.qwen/skills` is a relative symlink onto `.claude/skills`, so a Qwen Code -session loads the same one skill tree the other three harnesses do — no forked -copies. +session loads the same one skill tree the other harnesses are wired to — no +forked copies. (Wiring is shared; loading is not universal — colleague 1.76.0 +loads 0 of the 19 for upstream reasons, see `docs/harness-verification.md`.) ## Identity @@ -81,6 +82,7 @@ uv run black substack_cli tests # CI runs --check uv run isort substack_cli tests # CI runs --check-only uv run flake8 substack_cli tests # line length 100 uv run bandit -c pyproject.toml -r substack_cli +# markdownlint-cli2 is npm, not uv: npm install -g markdownlint-cli2@0.21.0 markdownlint-cli2 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken" python3 scripts/scan-secrets.py # committed-secret / non-localhost-endpoint gate uv run teken cli doctor . --strict # the agent-first rubric gate CI enforces @@ -119,7 +121,10 @@ The wiring that spans files: stderr. - `substack_cli/cli/_errors.py` — `CliError(code, message, remediation)` plus the exit-code policy: `0` success, `1` user error, `2` environment error, - `3+` reserved. Every failure path raises `CliError`. + `3+` reserved. Every *command handler* raises `CliError` on failure; two + paths differ downstream by design — `_CliArgumentParser.error()` emits a + `CliError` then raises `SystemExit`, and `doctor` *returns* `1` for an + unhealthy report rather than raising. - `substack_cli/cli/_output.py` — results to **stdout**, errors and diagnostics to **stderr**, never mixed, in both text and JSON mode. - `substack_cli/cli/_commands/*.py` — one module per verb/noun, each exposing @@ -135,7 +140,7 @@ verbs never hard-fail on a bad target (`overview /no/such/path` exits `0`); `learn` must keep covering purpose, command map, exit codes, `--json`, and `explain`. -## Adding the Substack surface +## Adding the Substack surface (planned) A new noun is a module under `cli/_commands/` with `register(sub)`, a line in `_build_parser()`, a catalog entry in `explain/catalog.py`, a row in `learn.py`'s @@ -145,9 +150,11 @@ and fails on committed credentials and non-localhost endpoints. ## Skills -`.claude/skills/` vendors the **canonical guildmaster skill kit** (19 skills, -cite-don't-import), reachable here through the `.qwen/skills` symlink. -Provenance and the re-sync procedure live in `docs/skill-sources.md`. Do not +`.claude/skills/` vendors 19 skills, cite-don't-import, reachable here through +the `.qwen/skills` symlink: 17 from guildmaster (eight of those devague-origin +re-broadcasts) and `ask-colleague` direct from `colleague`. Provenance and the +re-sync procedure live in `docs/skill-sources.md` — check a skill's row there +before assuming guildmaster is its upstream. Do not reformat or edit vendored scripts — a fix belongs upstream, then re-sync. Every vendored `SKILL.md` needs `type: command`; `core.skill_loader` silently skips one without it. diff --git a/README.md b/README.md index fb3e463..b951829 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ checked-in reality; the post/subscriber/stats verbs are the work ahead. one of them (see [Prompt files by harness](#prompt-files-by-harness) below). All four harnesses are usable interactively regardless of which one `culture.yaml` names as the mesh resident. -- **The canonical guildmaster skill kit** (19 skills) under `.claude/skills/`, - vendored cite-don't-import. See [`docs/skill-sources.md`](docs/skill-sources.md). +- **19 vendored skills** under `.claude/skills/`, cite-don't-import — 17 from + guildmaster (eight of those devague-origin re-broadcasts) and `ask-colleague` + direct from `colleague`. See [`docs/skill-sources.md`](docs/skill-sources.md). - **A build + deploy baseline** — pytest, lint, the agent-first rubric gate, a committed-secret scanner, a per-harness smoke check, and PyPI Trusted Publishing wired into GitHub Actions. diff --git a/docs/skill-sources.md b/docs/skill-sources.md index bb78efd..841a84f 100644 --- a/docs/skill-sources.md +++ b/docs/skill-sources.md @@ -27,6 +27,11 @@ renamed `convertible`. guildmaster's re-broadcast still carries the old `outsource` name, so `ask-colleague` is vendored **directly from colleague** as a tracked local divergence (see [below](#local-divergence--outsource--ask-colleague-2026-06-06)). +The ledger below covers all 19 vendored skill directories: 17 from +guildmaster (8 of them devague-origin re-broadcasts), `ask-colleague` +direct from colleague, and — added 2026-09-12 — `recall` / `remember`, +which were on disk but unlisted here until then. + Every vendored `SKILL.md` carries `type: command`. substack-cli declares a culture agent (`culture.yaml`, `backend: claude`), and `core.skill_loader` silently skips any `SKILL.md` lacking `type:` — so the field @@ -42,6 +47,8 @@ is load-bearing, even where guildmaster's upstream copy omits it. | `pypi-maintainer` | `../guildmaster/.claude/skills/pypi-maintainer/` | guildmaster | Switch a package install between PyPI / TestPyPI / local editable (`scripts/switch-source.sh`). Verbatim except added `type: command`. | 2026-05-26 (guildmaster 0.6.0) | | `run-tests` | `../guildmaster/.claude/skills/run-tests/` | guildmaster | pytest + xdist + coverage (`scripts/test.sh`). Verbatim except added `type: command`. | 2026-05-26 (guildmaster 0.6.0) | | `sonarclaude` | `../guildmaster/.claude/skills/sonarclaude/` | guildmaster | SonarCloud API queries (`scripts/sonar.sh`). Verbatim except added `type: command`. | 2026-05-26 (guildmaster 0.6.0) | +| `recall` | `../guildmaster/.claude/skills/recall/` | guildmaster | Search the shared `eidetic` memory store (`scripts/recall.sh`). Consumer-identifying prose adapted: the personal-scope literal (`--scope guildmaster` → `--scope substack-cli`) in the description and the scope section; the wrapper itself resolves the scope from `culture.yaml` at runtime, so no nick is hard-coded in the script. One local divergence: a bare ``` fence where upstream has ```text. Note the SKILL.md frontmatter still describes eidetic's *upstream* private/`$HOME` default — `scripts/remember.sh` overrides it to `--visibility public`, which is why this repo's records land in the committed `/.eidetic/memory`. | 2026-09-12 (guildmaster 0.26.4) | +| `remember` | `../guildmaster/.claude/skills/remember/` | guildmaster | Ingest records into the shared `eidetic` store (`scripts/remember.sh`). Same consumer-prose adaptation as `recall` (scope literal only). The script carries an explicit **policy override** over eidetic's upstream default — a plain `/remember` is `--visibility public` and lands in `/.eidetic/memory`, committed and mesh-shared; `--visibility private` routes to `$HOME` instead. | 2026-09-12 (guildmaster 0.26.4) | | `think` | `../guildmaster/.claude/skills/think/` | **devague** (re-broadcast via guildmaster) | idea→spec leg of the devague workflow chain. Verbatim (already carried `type: command` at guildmaster). Origin/broadcast prose left verbatim. | 2026-09-05 (devague 0.24.1 via guildmaster) | | `spec-to-plan` | `../guildmaster/.claude/skills/spec-to-plan/` | **devague** (re-broadcast via guildmaster) | spec→plan leg of the devague workflow chain. Verbatim (already carried `type: command`). | 2026-09-05 (devague 0.24.1 via guildmaster) | | `assign-to-workforce` | `../guildmaster/.claude/skills/assign-to-workforce/` | **devague** (re-broadcast via guildmaster) | plan→parallel-implementation leg of the devague workflow chain. Verbatim (already carried `type: command`). | 2026-09-05 (devague 0.24.1 via guildmaster) |