guild-cli 0.6.4__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/guild/SKILL.md +10 -0
  2. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/guild/scripts/create.sh +6 -1
  3. {guild_cli-0.6.4 → guild_cli-0.7.0}/CHANGELOG.md +19 -0
  4. {guild_cli-0.6.4 → guild_cli-0.7.0}/CLAUDE.md +1 -1
  5. {guild_cli-0.6.4 → guild_cli-0.7.0}/PKG-INFO +1 -1
  6. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/_provision_template.py +5 -1
  7. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/create.py +32 -2
  8. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/scaffold/instantiate.py +117 -5
  9. {guild_cli-0.6.4 → guild_cli-0.7.0}/pyproject.toml +1 -1
  10. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_cli_create.py +77 -0
  11. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_scaffold_instantiate.py +111 -1
  12. {guild_cli-0.6.4 → guild_cli-0.7.0}/uv.lock +1 -1
  13. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/agent-config/SKILL.md +0 -0
  14. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  15. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
  16. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  17. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  18. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/cicd/SKILL.md +0 -0
  19. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  20. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  21. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  22. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  23. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  24. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/communicate/SKILL.md +0 -0
  25. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  26. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  27. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  28. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  29. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  30. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  31. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  32. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  33. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/guild/scripts/configure-repo.sh +0 -0
  34. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/guild/scripts/overview.sh +0 -0
  35. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/onboard/SKILL.md +0 -0
  36. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/onboard/scripts/onboard.sh +0 -0
  37. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  38. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  39. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/run-tests/SKILL.md +0 -0
  40. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  41. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  42. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  43. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  44. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  45. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/teach/SKILL.md +0 -0
  46. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/teach/scripts/teach.sh +0 -0
  47. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/think/SKILL.md +0 -0
  48. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/think/scripts/think.sh +0 -0
  49. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/version-bump/SKILL.md +0 -0
  50. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  51. {guild_cli-0.6.4 → guild_cli-0.7.0}/.claude/skills.local.yaml.example +0 -0
  52. {guild_cli-0.6.4 → guild_cli-0.7.0}/.devague/frames/guildmaster-ships-teach-and-onboard-two-agent-firs.json +0 -0
  53. {guild_cli-0.6.4 → guild_cli-0.7.0}/.devague/plans/guildmaster-ships-teach-and-onboard-two-agent-firs.json +0 -0
  54. {guild_cli-0.6.4 → guild_cli-0.7.0}/.flake8 +0 -0
  55. {guild_cli-0.6.4 → guild_cli-0.7.0}/.github/workflows/publish.yml +0 -0
  56. {guild_cli-0.6.4 → guild_cli-0.7.0}/.github/workflows/tests.yml +0 -0
  57. {guild_cli-0.6.4 → guild_cli-0.7.0}/.gitignore +0 -0
  58. {guild_cli-0.6.4 → guild_cli-0.7.0}/.markdownlint-cli2.yaml +0 -0
  59. {guild_cli-0.6.4 → guild_cli-0.7.0}/LICENSE +0 -0
  60. {guild_cli-0.6.4 → guild_cli-0.7.0}/README.md +0 -0
  61. {guild_cli-0.6.4 → guild_cli-0.7.0}/culture.yaml +0 -0
  62. {guild_cli-0.6.4 → guild_cli-0.7.0}/docs/cutover.md +0 -0
  63. {guild_cli-0.6.4 → guild_cli-0.7.0}/docs/plans/2026-05-24-guildmaster-ships-teach-and-onboard-two-agent-firs.md +0 -0
  64. {guild_cli-0.6.4 → guild_cli-0.7.0}/docs/skill-sources.md +0 -0
  65. {guild_cli-0.6.4 → guild_cli-0.7.0}/docs/specs/2026-05-24-guildmaster-ships-teach-and-onboard-two-agent-firs.md +0 -0
  66. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/__init__.py +0 -0
  67. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/__main__.py +0 -0
  68. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/__init__.py +0 -0
  69. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/__init__.py +0 -0
  70. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/_broadcast.py +0 -0
  71. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/explain.py +0 -0
  72. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/learn.py +0 -0
  73. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/onboard.py +0 -0
  74. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/overview.py +0 -0
  75. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/show.py +0 -0
  76. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/teach.py +0 -0
  77. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_commands/whoami.py +0 -0
  78. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_errors.py +0 -0
  79. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_output.py +0 -0
  80. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/cli/_repo.py +0 -0
  81. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/scaffold/__init__.py +0 -0
  82. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/skills/__init__.py +0 -0
  83. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/skills/identity.py +0 -0
  84. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/skills/ledger.py +0 -0
  85. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/skills/render.py +0 -0
  86. {guild_cli-0.6.4 → guild_cli-0.7.0}/guild/skills/sources.py +0 -0
  87. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/__init__.py +0 -0
  88. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_broadcast_post.py +0 -0
  89. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_cli.py +0 -0
  90. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_cli_explain.py +0 -0
  91. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_cli_learn.py +0 -0
  92. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_cli_onboard.py +0 -0
  93. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_cli_overview.py +0 -0
  94. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_cli_show.py +0 -0
  95. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_cli_teach.py +0 -0
  96. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_cli_whoami.py +0 -0
  97. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_skills_convention.py +0 -0
  98. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_skills_identity.py +0 -0
  99. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_skills_ledger.py +0 -0
  100. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_skills_render.py +0 -0
  101. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_skills_sources.py +0 -0
  102. {guild_cli-0.6.4 → guild_cli-0.7.0}/tests/test_version_fallback.py +0 -0
@@ -116,6 +116,13 @@ Provision a new sibling by instantiating the GitHub template and customising it:
116
116
  --template myorg/my-template \
117
117
  --apply
118
118
 
119
+ # Custom PyPI dist name (command + import package stay the repo name)
120
+ .claude/skills/guild/scripts/create.sh \
121
+ --agent agentculture/jetson \
122
+ --desc "Edge-AI ops agent." \
123
+ --dist jetson-cli \
124
+ --apply
125
+
119
126
  # JSON output (works in both dry-run and apply)
120
127
  .claude/skills/guild/scripts/create.sh \
121
128
  --agent agentculture/new-agent \
@@ -135,6 +142,9 @@ Provision a new sibling by instantiating the GitHub template and customising it:
135
142
  - rewrites `README.md` first heading + intro
136
143
  - overwrites `CLAUDE.md` with a self-init seed (names the agent, embeds the
137
144
  description, carries a `/init` re-init instruction)
145
+ - if `--dist` differs from the repo name, retargets the PyPI dist
146
+ (`[project].name`, the `importlib.metadata` lookup, the TestPyPI install
147
+ pin) — the command + import package stay the repo name
138
148
  4. Runs `configure-repo.sh` to apply GitHub settings (branch protection,
139
149
  environments, `SONAR_TOKEN` placeholder).
140
150
  5. Stages, commits (`scaffold <bare> from culture-agent-template`), and pushes.
@@ -12,7 +12,12 @@ set -euo pipefail
12
12
  # Usage:
13
13
  # create.sh --agent OWNER/REPO --desc TEXT [--backend claude|acp]
14
14
  # [--workspace-root DIR] [--template OWNER/REPO]
15
- # [--org agentculture] [--apply] [--json]
15
+ # [--org agentculture] [--dist NAME] [--apply] [--json]
16
+ #
17
+ # --dist NAME sets the PyPI distribution name (default: the repo name); pass
18
+ # e.g. --dist jetson-cli to ship the dist as jetson-cli while keeping the
19
+ # command + import package as the repo name. Retargets [project].name, the
20
+ # importlib.metadata lookup, and the TestPyPI install pin.
16
21
  #
17
22
  # Exit codes:
18
23
  # 0 success (delegates to `guild create`; its exit code propagates)
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/). This project
6
6
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.0] - 2026-05-28
9
+
10
+ ### Added
11
+
12
+ - **`guild create --dist NAME`** — set the PyPI **distribution** name at
13
+ provision time. Defaults to the repo token (unchanged behaviour); pass e.g.
14
+ `--dist jetson-cli` to ship the dist as `jetson-cli` while the console command
15
+ and import package stay the repo token (`jetson`), matching the
16
+ `guild-cli`/`guild` convention. The transform's new step 6 retargets only the
17
+ three dist-name surfaces — `[project].name`, the `importlib.metadata` version
18
+ lookup, and the TestPyPI install pin — and is a strict no-op when `--dist`
19
+ equals (or is omitted and defaults to) the repo token, so the bare-name case
20
+ (e.g. `dominion-breaker`) is preserved. The name is validated as a PEP 503
21
+ distribution name before any external act. This removes the manual
22
+ post-`create` dist rename that `agenda`, `convertible`, and `jetson` each
23
+ needed. Note: renaming the dist still requires a Trusted Publisher registered
24
+ for that project name on PyPI/TestPyPI — `guild create` configures the GitHub
25
+ side only.
26
+
8
27
  ## [0.6.4] - 2026-05-28
9
28
 
10
29
  ### Added
@@ -108,7 +108,7 @@ two live broadcasters.
108
108
 
109
109
  **Provisioning verb — `guild create` (template-instantiate a new sibling).**
110
110
 
111
- - `guild create --agent OWNER/REPO --desc TEXT [--backend claude|acp] [--workspace-root DIR] [--template agentculture/culture-agent-template] [--apply] [--json]` — provision a brand-new AgentCulture sibling by instantiating the GitHub template, renaming identifiers throughout the clone, writing a self-init CLAUDE.md seed, configuring the GitHub repo (branch protection, environments, SONAR_TOKEN placeholder), pushing the genesis commit, and registering the agent in `docs/skill-sources.md`. Dry-run by default; `--apply` executes. The transform is **pure** (unit-testable against a fixture dir; no network): `culture_agent_template` → pkg (underscore form, e.g. `my_agent`), `culture-agent-template` → repo token (hyphen form, e.g. `my-agent`). The CLAUDE.md seed is a `/init`-style bootstrap placeholder — run `/init` in the new repo after provisioning to expand it into a full runtime prompt. Implementation: `guild/scaffold/instantiate.py` (pure transform), `guild/cli/_commands/create.py` (CLI), `guild/cli/_commands/_provision_template.py` (injectable-runner executor), `.claude/skills/guild/scripts/create.sh` (thin wrapper).
111
+ - `guild create --agent OWNER/REPO --desc TEXT [--backend claude|acp] [--workspace-root DIR] [--template agentculture/culture-agent-template] [--dist NAME] [--apply] [--json]` — provision a brand-new AgentCulture sibling by instantiating the GitHub template, renaming identifiers throughout the clone, writing a self-init CLAUDE.md seed, configuring the GitHub repo (branch protection, environments, SONAR_TOKEN placeholder), pushing the genesis commit, and registering the agent in `docs/skill-sources.md`. Dry-run by default; `--apply` executes. The transform is **pure** (unit-testable against a fixture dir; no network): `culture_agent_template` → pkg (underscore form, e.g. `my_agent`), `culture-agent-template` → repo token (hyphen form, e.g. `my-agent`). `--dist NAME` sets the **PyPI distribution name** (default: the repo token); pass e.g. `--dist jetson-cli` to publish as `jetson-cli` while keeping the command and import package as the repo token (`jetson`) — this retargets only `[project].name`, the `importlib.metadata` lookup, and the TestPyPI install pin, matching the `guild-cli`/`guild` convention without a manual post-create edit. (Renaming the dist needs a matching Trusted Publisher registered for that project name on PyPI/TestPyPI — `guild create` configures the GitHub side only.) The CLAUDE.md seed is a `/init`-style bootstrap placeholder — run `/init` in the new repo after provisioning to expand it into a full runtime prompt. Implementation: `guild/scaffold/instantiate.py` (pure transform), `guild/cli/_commands/create.py` (CLI), `guild/cli/_commands/_provision_template.py` (injectable-runner executor), `.claude/skills/guild/scripts/create.sh` (thin wrapper).
112
112
 
113
113
  **Inventory verbs — `overview` & `show` (the read-only surfaces).** guildmaster
114
114
  owns the mesh's *inventory* surfaces per
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: guild-cli
3
- Version: 0.6.4
3
+ Version: 0.7.0
4
4
  Summary: guildmaster — an agent and CLI that manages skills for the AgentCulture mesh.
5
5
  Project-URL: Homepage, https://github.com/agentculture/guildmaster
6
6
  Project-URL: Issues, https://github.com/agentculture/guildmaster/issues
@@ -241,6 +241,7 @@ def apply(
241
241
  guildmaster_root: Path,
242
242
  runner: Runner | None = None,
243
243
  template: str = DEFAULT_TEMPLATE,
244
+ dist: str | None = None,
244
245
  poll_attempts: int = 30,
245
246
  poll_delay: float = 2.0,
246
247
  ) -> dict:
@@ -265,6 +266,9 @@ def apply(
265
266
  Callable ``(cmd, cwd=None) -> RunResult``. Defaults to real subprocess.
266
267
  template:
267
268
  The GitHub template repo to instantiate (default: ``DEFAULT_TEMPLATE``).
269
+ dist:
270
+ PyPI distribution name to set in the clone. ``None`` leaves it as the
271
+ repo name; a different value retargets the dist (see ``transform_clone``).
268
272
  poll_attempts / poll_delay:
269
273
  ``gh repo create --template`` copies the template content
270
274
  **asynchronously**; cloning immediately yields an empty repo. After
@@ -315,7 +319,7 @@ def apply(
315
319
  _check(clone, f"git clone {agent} failed", EXIT_ENV_ERROR)
316
320
 
317
321
  # Step 4 — pure transform (no runner needed).
318
- transform_clone(clone_dest, bare, desc, backend)
322
+ transform_clone(clone_dest, bare, desc, backend, dist=dist)
319
323
 
320
324
  # Step 5 — stage everything.
321
325
  add = runner(["git", "-C", str(clone_dest), "add", "-A"])
@@ -15,7 +15,7 @@ Usage
15
15
  guild create --agent OWNER/REPO --desc TEXT [--org agentculture]
16
16
  [--backend claude|acp] [--workspace-root DIR]
17
17
  [--template agentculture/culture-agent-template]
18
- [--apply] [--json]
18
+ [--dist NAME] [--apply] [--json]
19
19
  """
20
20
 
21
21
  from __future__ import annotations
@@ -24,6 +24,7 @@ import argparse
24
24
  import difflib
25
25
  import json
26
26
  import keyword
27
+ import re
27
28
  from pathlib import Path
28
29
 
29
30
  from guild.cli._commands import _broadcast
@@ -86,6 +87,17 @@ def register(sub: argparse._SubParsersAction) -> None:
86
87
  default=_provision.DEFAULT_TEMPLATE,
87
88
  help=f"GitHub template repo to instantiate (default: {_provision.DEFAULT_TEMPLATE}).",
88
89
  )
90
+ parser.add_argument(
91
+ "--dist",
92
+ metavar="NAME",
93
+ default=None,
94
+ help=(
95
+ "PyPI distribution name. Defaults to the repo name; pass e.g. "
96
+ "'jetson-cli' to ship the dist as 'jetson-cli' while keeping the "
97
+ "command and import package as the repo name ('jetson'). Retargets "
98
+ "[project].name, the importlib.metadata lookup, and the TestPyPI pin."
99
+ ),
100
+ )
89
101
  parser.add_argument(
90
102
  "--apply",
91
103
  action="store_true",
@@ -127,12 +139,26 @@ def _handle(args: argparse.Namespace) -> None:
127
139
  ),
128
140
  )
129
141
 
142
+ # Validate the optional PyPI dist name (fail fast, before any external act).
143
+ # PyPI normalises [-_.] runs, but the raw name must still be a non-empty run
144
+ # of letters/digits/.-_ that starts and ends alphanumerically (PEP 503).
145
+ if args.dist is not None and not re.match(
146
+ r"^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$", args.dist
147
+ ):
148
+ raise GuildError(
149
+ code=EXIT_USER_ERROR,
150
+ message=f"--dist {args.dist!r} is not a valid PyPI distribution name",
151
+ remediation=(
152
+ "use letters/digits/.-_ starting and ending alphanumerically (e.g. 'jetson-cli')"
153
+ ),
154
+ )
155
+
130
156
  # Resolve workspace root (parent of guildmaster if not supplied).
131
157
  workspace_root = args.workspace_root if args.workspace_root is not None else root.parent
132
158
  clone_dest = workspace_root / bare
133
159
 
134
160
  # Build the dry-run plan (pure — no writes, no subprocess).
135
- plan = _transform_plan(bare, args.desc)
161
+ plan = _transform_plan(bare, args.desc, dist=args.dist)
136
162
 
137
163
  # Ledger diff (pure — reads only).
138
164
  skills = _broadcast.canonical_skills(root)
@@ -171,6 +197,7 @@ def _handle(args: argparse.Namespace) -> None:
171
197
  clone_dest=clone_dest,
172
198
  guildmaster_root=root,
173
199
  template=args.template,
200
+ dist=args.dist,
174
201
  )
175
202
 
176
203
  # Register in the ledger idempotently.
@@ -186,6 +213,7 @@ def _handle(args: argparse.Namespace) -> None:
186
213
  "repo": apply_result["repo"],
187
214
  "clone_dest": apply_result["clone_dest"],
188
215
  "pushed": apply_result["pushed"],
216
+ "dist": plan["dist"],
189
217
  "ledger_written": ledger_written,
190
218
  }
191
219
  emit_result(json.dumps(result, indent=2) if args.json else _render_apply(result))
@@ -207,6 +235,8 @@ def _render_dry_run(result: dict) -> str:
207
235
  f" clone dest : {result['clone_dest']}",
208
236
  f" pkg : {plan['pkg']}",
209
237
  f" repo token : {plan['repo_token']}",
238
+ f" dist (PyPI): {plan['dist']}"
239
+ + ("" if plan["dist"] == plan["repo_token"] else " (command + import stay repo token)"),
210
240
  "",
211
241
  "── rename map ──",
212
242
  ]
@@ -10,12 +10,13 @@ rename_map(bare) -> dict[str, str]
10
10
  The two identifier substitutions: ``culture_agent_template`` → ``pkg`` and
11
11
  ``culture-agent-template`` → ``repo_token``. Pure function; no I/O.
12
12
 
13
- transform_plan(bare, desc) -> dict
13
+ transform_plan(bare, desc, dist=None) -> dict
14
14
  A human/JSON-renderable description of what ``transform_clone`` WOULD do
15
15
  (the rename map, package directory rename, pyproject description, README
16
- headline change, and CLAUDE.md seed replacement). No I/O.
16
+ headline change, CLAUDE.md seed replacement, and — when *dist* differs from
17
+ the repo token — the dist-name retarget). No I/O.
17
18
 
18
- transform_clone(dest, bare, desc, backend) -> None
19
+ transform_clone(dest, bare, desc, backend, dist=None) -> None
19
20
  The actual, irreversible in-place transform. Walks *dest*, skipping
20
21
  ``.git/``:
21
22
  1. Global text replace across every file: ``culture_agent_template`` → pkg,
@@ -29,6 +30,14 @@ transform_clone(dest, bare, desc, backend) -> None
29
30
  5. Overwrite ``CLAUDE.md`` with a self-init seed (prompt-file-present +
30
31
  backend-consistency compliant; carries an explicit ``/init`` re-init
31
32
  instruction; names the agent, embeds *desc*, uses *backend*).
33
+ 6. **Only when** *dist* is given and differs from the repo token: retarget
34
+ the PyPI distribution name in the three places that name the *dist* (not
35
+ the import package or the console command) — ``[project].name``, the
36
+ ``importlib.metadata`` lookup, and the TestPyPI install pin.
37
+
38
+ *dist* ``None`` means "leave the dist as the repo token" (the default). An
39
+ explicitly empty/whitespace *dist* is rejected (``ValueError``) so a stray
40
+ programmatic ``""`` is not silently treated as "unset".
32
41
 
33
42
  This module is PURE w.r.t. the filesystem: it only writes within *dest*, and
34
43
  only after the caller hands it a real directory. No subprocess, no network.
@@ -39,6 +48,7 @@ from __future__ import annotations
39
48
 
40
49
  import re
41
50
  from pathlib import Path
51
+ from typing import Callable
42
52
 
43
53
  # ---------------------------------------------------------------------------
44
54
  # Identifier derivation
@@ -70,6 +80,21 @@ def _derive(bare: str) -> tuple[str, str]:
70
80
  return pkg, repo_token
71
81
 
72
82
 
83
+ def _effective_dist(dist: str | None, repo_token: str) -> str:
84
+ """Resolve the distribution name, distinguishing ``None`` from ``""``.
85
+
86
+ ``None`` means "not provided" → default to *repo_token*. An explicitly
87
+ empty or whitespace-only *dist* is a programming error (the CLI rejects it
88
+ before this point) and raises ``ValueError`` rather than silently defaulting,
89
+ so plan and transform reason identically about their inputs.
90
+ """
91
+ if dist is None:
92
+ return repo_token
93
+ if not dist.strip():
94
+ raise ValueError("dist must be a non-empty string or None")
95
+ return dist
96
+
97
+
73
98
  # ---------------------------------------------------------------------------
74
99
  # Public helpers — no I/O
75
100
  # ---------------------------------------------------------------------------
@@ -95,7 +120,7 @@ def rename_map(bare: str) -> dict[str, str]:
95
120
  return {_TEMPLATE_PKG: pkg, _TEMPLATE_REPO: repo_token}
96
121
 
97
122
 
98
- def transform_plan(bare: str, desc: str) -> dict:
123
+ def transform_plan(bare: str, desc: str, dist: str | None = None) -> dict:
99
124
  """Return a human/JSON-renderable description of what ``transform_clone`` would do.
100
125
 
101
126
  Parameters
@@ -104,6 +129,13 @@ def transform_plan(bare: str, desc: str) -> dict:
104
129
  The bare repo/agent name.
105
130
  desc:
106
131
  The short description for the new agent.
132
+ dist:
133
+ The PyPI distribution name to set. ``None`` (the default) means leave it
134
+ as the global-replace result (``repo_token``) — the pure default. When it
135
+ differs from ``repo_token``, an extra retarget step renames just the dist
136
+ (``[project].name``, the ``importlib.metadata`` lookup, and the TestPyPI
137
+ install pin); the importable package and the console command stay
138
+ ``repo_token``.
107
139
 
108
140
  Returns
109
141
  -------
@@ -111,10 +143,12 @@ def transform_plan(bare: str, desc: str) -> dict:
111
143
  ``rename_map`` — ``{old: new}`` substitution pairs
112
144
  ``package_dir_rename`` — ``{old_dir: new_dir}``
113
145
  ``pyproject_desc`` — the value that would be written to ``description``
146
+ ``dist`` — the effective distribution name
114
147
  ``claude_md_seed`` — the first ~3 lines of the seed (synopsis only)
115
148
  ``steps`` — ordered list of step descriptions
116
149
  """
117
150
  pkg, repo_token = _derive(bare)
151
+ effective_dist = _effective_dist(dist, repo_token)
118
152
  m = rename_map(bare)
119
153
  steps = [
120
154
  f"global text replace in every file (skip .git/): "
@@ -124,6 +158,12 @@ def transform_plan(bare: str, desc: str) -> dict:
124
158
  "replace README.md first heading + intro with new agent name and description",
125
159
  "overwrite CLAUDE.md with a self-init seed (names agent, embeds desc, /init instruction)",
126
160
  ]
161
+ if effective_dist != repo_token:
162
+ steps.append(
163
+ f"retarget PyPI distribution name {repo_token!r} → {effective_dist!r} "
164
+ "([project].name, importlib.metadata lookup, TestPyPI install pin); "
165
+ f"command + import package stay {repo_token!r}"
166
+ )
127
167
  return {
128
168
  "bare": bare,
129
169
  "pkg": pkg,
@@ -131,6 +171,7 @@ def transform_plan(bare: str, desc: str) -> dict:
131
171
  "rename_map": m,
132
172
  "package_dir_rename": {_TEMPLATE_PKG: pkg},
133
173
  "pyproject_desc": desc,
174
+ "dist": effective_dist,
134
175
  "claude_md_seed_synopsis": f"# CLAUDE.md seed for {bare} — run /init to expand",
135
176
  "steps": steps,
136
177
  }
@@ -258,6 +299,61 @@ def _set_pyproject_description(pyproject: Path, desc: str) -> None:
258
299
  pyproject.write_text(new_text, encoding="utf-8")
259
300
 
260
301
 
302
+ def _rewrite_text_file(path: Path, transform: Callable[[str], str]) -> None:
303
+ """Read *path*, apply *transform* to its text, and write back if it changed.
304
+
305
+ Degrades gracefully: a missing or non-UTF-8 file is left untouched. Centralises
306
+ the read/guard/write boilerplate so callers describe only the substitution.
307
+ """
308
+ if not path.is_file():
309
+ return
310
+ try:
311
+ text = path.read_text(encoding="utf-8")
312
+ except (UnicodeDecodeError, OSError):
313
+ return
314
+ new_text = transform(text)
315
+ if new_text != text:
316
+ path.write_text(new_text, encoding="utf-8")
317
+
318
+
319
+ def _retarget_dist(dest: Path, pkg: str, repo_token: str, dist: str) -> None:
320
+ """Rename only the PyPI **distribution** from *repo_token* to *dist*.
321
+
322
+ Called after the global token replace has set every ``culture-agent-template``
323
+ occurrence to *repo_token*. This narrowly rewrites the three places that name
324
+ the *distribution* (as opposed to the importable package or the console
325
+ command, which stay *repo_token*):
326
+
327
+ 1. ``pyproject.toml`` — the ``[project].name = "<repo_token>"`` line (matched
328
+ by value, so ``authors = [{name = "..."}]`` and other ``name =`` lines are
329
+ never touched).
330
+ 2. ``<pkg>/__init__.py`` — the ``importlib.metadata`` version lookup argument
331
+ (the ``"<repo_token>"`` string literal).
332
+ 3. ``.github/workflows/publish.yml`` — the TestPyPI install hint's version
333
+ pin (``<repo_token>==`` → ``<dist>==``; the ``==`` anchors it so other
334
+ ``repo_token`` mentions are untouched).
335
+
336
+ Each step degrades gracefully if the file or pattern is absent. No-op is the
337
+ caller's responsibility (skip when ``dist`` equals ``repo_token``).
338
+ """
339
+ name_line = re.compile(r'(?m)^(\s*name\s*=\s*)["\']' + re.escape(repo_token) + r'["\']')
340
+
341
+ _rewrite_text_file(
342
+ dest / "pyproject.toml",
343
+ lambda text: name_line.sub(lambda m: m.group(1) + '"' + dist + '"', text),
344
+ )
345
+ _rewrite_text_file(
346
+ dest / pkg / "__init__.py",
347
+ lambda text: text.replace(f'"{repo_token}"', f'"{dist}"').replace(
348
+ f"'{repo_token}'", f"'{dist}'"
349
+ ),
350
+ )
351
+ _rewrite_text_file(
352
+ dest / ".github" / "workflows" / "publish.yml",
353
+ lambda text: text.replace(f"{repo_token}==", f"{dist}=="),
354
+ )
355
+
356
+
261
357
  def _is_desc_stub(line: str) -> bool:
262
358
  """Return True if *line* looks like a short plain-text description placeholder.
263
359
 
@@ -380,6 +476,7 @@ def transform_clone(
380
476
  bare: str,
381
477
  desc: str,
382
478
  backend: str = "claude",
479
+ dist: str | None = None,
383
480
  ) -> None:
384
481
  """Customise the cloned template tree at *dest* into the new agent *bare*.
385
482
 
@@ -392,6 +489,10 @@ def transform_clone(
392
489
  3. **Set description** in ``pyproject.toml``.
393
490
  4. **Rewrite README.md** first heading + intro.
394
491
  5. **Overwrite CLAUDE.md** (or ``AGENTS.md`` for acp) with a self-init seed.
492
+ 6. **Retarget the PyPI dist name** to *dist* (only when it differs from
493
+ ``repo_token``) — ``[project].name``, the ``importlib.metadata`` lookup,
494
+ and the TestPyPI install pin. The importable package and the console
495
+ command stay ``repo_token``.
395
496
 
396
497
  Parameters
397
498
  ----------
@@ -403,6 +504,9 @@ def transform_clone(
403
504
  Short description for the new agent.
404
505
  backend:
405
506
  ``"claude"`` (default) or ``"acp"``.
507
+ dist:
508
+ PyPI distribution name. ``None`` leaves it as ``repo_token`` (the pure
509
+ default); a different value retargets only the dist (step 6).
406
510
 
407
511
  Raises
408
512
  ------
@@ -418,7 +522,10 @@ def transform_clone(
418
522
  if not dest.is_dir():
419
523
  raise FileNotFoundError(f"dest is not a directory: {dest}")
420
524
 
421
- pkg, _ = _derive(bare)
525
+ pkg, repo_token = _derive(bare)
526
+ # Resolve the dist up front so an explicitly-empty value is rejected before
527
+ # any file is touched (None → repo_token; "" / whitespace → ValueError).
528
+ effective_dist = _effective_dist(dist, repo_token)
422
529
  replacements = rename_map(bare) # underscore first, then hyphen
423
530
 
424
531
  # Step 1 — global text replace (visit all files before the directory rename
@@ -442,3 +549,8 @@ def transform_clone(
442
549
  prompt_file = _BACKEND_PROMPT_FILE[backend]
443
550
  seed_content = _seed_prompt(bare, desc, backend)
444
551
  (dest / prompt_file).write_text(seed_content, encoding="utf-8")
552
+
553
+ # Step 6 — retarget the PyPI dist name (only when it differs from repo_token,
554
+ # so the default is a true no-op and the bare-name case is unchanged).
555
+ if effective_dist != repo_token:
556
+ _retarget_dist(dest, pkg, repo_token, effective_dist)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "guild-cli"
3
- version = "0.6.4"
3
+ version = "0.7.0"
4
4
  description = "guildmaster — an agent and CLI that manages skills for the AgentCulture mesh."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -159,6 +159,36 @@ def test_create_dry_run_custom_template(tmp_path, monkeypatch, capsys):
159
159
  assert out["template"] == "myorg/my-template"
160
160
 
161
161
 
162
+ def test_create_dry_run_dist_flag(tmp_path, monkeypatch, capsys):
163
+ monkeypatch.chdir(_seed(tmp_path))
164
+ monkeypatch.setattr(subprocess, "run", _never_called)
165
+
166
+ rc = main(["create", "--agent", "newsib", "--desc", "D.", "--dist", "newsib-cli", "--json"])
167
+ assert rc == 0
168
+ out = json.loads(capsys.readouterr().out)
169
+ assert out["plan"]["dist"] == "newsib-cli"
170
+ assert any("retarget" in s.lower() for s in out["plan"]["steps"])
171
+
172
+
173
+ def test_create_dry_run_dist_defaults_to_repo_token(tmp_path, monkeypatch, capsys):
174
+ monkeypatch.chdir(_seed(tmp_path))
175
+ monkeypatch.setattr(subprocess, "run", _never_called)
176
+
177
+ rc = main(["create", "--agent", "newsib", "--desc", "D.", "--json"])
178
+ assert rc == 0
179
+ out = json.loads(capsys.readouterr().out)
180
+ assert out["plan"]["dist"] == "newsib"
181
+
182
+
183
+ def test_create_rejects_invalid_dist_name(tmp_path, monkeypatch, capsys):
184
+ monkeypatch.chdir(_seed(tmp_path))
185
+ monkeypatch.setattr(subprocess, "run", _never_called)
186
+
187
+ rc = main(["create", "--agent", "newsib", "--desc", "D.", "--dist", "bad name!"])
188
+ assert rc != 0
189
+ assert "not a valid PyPI distribution name" in capsys.readouterr().err
190
+
191
+
162
192
  def test_create_dry_run_bare_agent_gets_org(tmp_path, monkeypatch, capsys):
163
193
  monkeypatch.chdir(_seed(tmp_path))
164
194
 
@@ -223,9 +253,18 @@ class _FakeRunner:
223
253
  (clone_dest / "culture_agent_template").mkdir()
224
254
  (clone_dest / "culture_agent_template" / "__init__.py").write_text(
225
255
  "# culture_agent_template\n"
256
+ "from importlib.metadata import version as _pkg_version\n"
257
+ '__version__ = _pkg_version("culture-agent-template")\n'
226
258
  )
227
259
  (clone_dest / "pyproject.toml").write_text(
228
260
  '[project]\nname = "culture-agent-template"\ndescription = "Template desc."\n'
261
+ "\n[project.scripts]\n"
262
+ 'culture-agent-template = "culture_agent_template.cli:main"\n'
263
+ )
264
+ wf = clone_dest / ".github" / "workflows"
265
+ wf.mkdir(parents=True)
266
+ (wf / "publish.yml").write_text(
267
+ 'run: echo "install culture-agent-template==${DEV_VERSION}"\n'
229
268
  )
230
269
  (clone_dest / "culture.yaml").write_text(
231
270
  "agents:\n- suffix: culture-agent-template\n backend: claude\n"
@@ -640,3 +679,41 @@ def test_create_apply_transform_renames_in_clone(tmp_path, monkeypatch):
640
679
  claude_md = (clone_dest / "CLAUDE.md").read_text()
641
680
  assert "newsib" in claude_md
642
681
  assert "/init" in claude_md
682
+
683
+
684
+ def test_create_apply_dist_retargets_clone(tmp_path, monkeypatch):
685
+ """--apply with --dist renames the dist in the clone, not the command/pkg."""
686
+ root = _seed(tmp_path)
687
+ workspace = tmp_path / "ws"
688
+ workspace.mkdir()
689
+ monkeypatch.chdir(root)
690
+
691
+ runner = _FakeRunner()
692
+ import guild.cli._commands._provision_template as _prov
693
+
694
+ monkeypatch.setattr(_prov, "default_runner", runner)
695
+
696
+ main(
697
+ [
698
+ "create",
699
+ "--agent",
700
+ "newsib",
701
+ "--desc",
702
+ "Dist test.",
703
+ "--workspace-root",
704
+ str(workspace),
705
+ "--dist",
706
+ "newsib-cli",
707
+ "--apply",
708
+ ]
709
+ )
710
+
711
+ clone_dest = workspace / "newsib"
712
+ pyproject = (clone_dest / "pyproject.toml").read_text()
713
+ assert 'name = "newsib-cli"' in pyproject
714
+ # Command stays the bare repo name.
715
+ assert 'newsib = "newsib.cli:main"' in pyproject
716
+ init = (clone_dest / "newsib" / "__init__.py").read_text()
717
+ assert '_pkg_version("newsib-cli")' in init
718
+ wf = (clone_dest / ".github" / "workflows" / "publish.yml").read_text()
719
+ assert "newsib-cli==${DEV_VERSION}" in wf
@@ -30,12 +30,27 @@ def _build_fixture(tmp_path: Path) -> Path:
30
30
  (tmp_path / ".git").mkdir()
31
31
  (tmp_path / ".git" / "config").write_text("[core]\n\trepositoryformatversion = 0\n")
32
32
 
33
- # Package directory with __init__.py that imports from the package
33
+ # Package directory with __init__.py that imports from the package and reads
34
+ # its version from the *distribution* metadata (hyphen form, like the real
35
+ # template's `importlib.metadata` lookup).
34
36
  pkg_dir = tmp_path / _TEMPLATE_PKG
35
37
  pkg_dir.mkdir()
36
38
  (pkg_dir / "__init__.py").write_text(
37
39
  '"""culture_agent_template package."""\n'
40
+ "from importlib.metadata import version as _pkg_version\n"
38
41
  "from culture_agent_template import cli # noqa: F401\n"
42
+ '\n__version__ = _pkg_version("culture-agent-template")\n'
43
+ )
44
+
45
+ # .github/workflows/publish.yml — the TestPyPI install hint pins the dist.
46
+ wf_dir = tmp_path / ".github" / "workflows"
47
+ wf_dir.mkdir(parents=True)
48
+ (wf_dir / "publish.yml").write_text(
49
+ "name: publish\n"
50
+ "jobs:\n"
51
+ " test-publish:\n"
52
+ " steps:\n"
53
+ ' - run: echo "install culture-agent-template==${DEV_VERSION}"\n'
39
54
  )
40
55
 
41
56
  # pyproject.toml
@@ -375,6 +390,101 @@ def test_transform_readme_consumes_wrapped_inline_code_prose(tmp_path):
375
390
  assert "## More" in readme
376
391
 
377
392
 
393
+ # ---------------------------------------------------------------------------
394
+ # --dist retarget tests
395
+ # ---------------------------------------------------------------------------
396
+
397
+
398
+ def test_transform_default_dist_leaves_repo_token(tmp_path):
399
+ """No dist → the dist name stays the global-replace result (repo_token)."""
400
+ dest = _build_fixture(tmp_path)
401
+ transform_clone(dest, "appsec", "AppSec agent.", "claude")
402
+ pyproject = (dest / "pyproject.toml").read_text()
403
+ assert 'name = "appsec"' in pyproject
404
+ init = (dest / "appsec" / "__init__.py").read_text()
405
+ assert '_pkg_version("appsec")' in init
406
+
407
+
408
+ def test_transform_dist_equal_repo_token_is_noop(tmp_path):
409
+ """Explicitly passing dist == repo_token changes nothing (no-op guard)."""
410
+ dest = _build_fixture(tmp_path)
411
+ transform_clone(dest, "appsec", "AppSec agent.", "claude", dist="appsec")
412
+ pyproject = (dest / "pyproject.toml").read_text()
413
+ assert 'name = "appsec"' in pyproject
414
+ assert "appsec-cli" not in pyproject
415
+
416
+
417
+ def test_transform_custom_dist_retargets_pyproject_name(tmp_path):
418
+ dest = _build_fixture(tmp_path)
419
+ transform_clone(dest, "appsec", "AppSec agent.", "claude", dist="appsec-cli")
420
+ pyproject = (dest / "pyproject.toml").read_text()
421
+ assert 'name = "appsec-cli"' in pyproject
422
+
423
+
424
+ def test_transform_custom_dist_retargets_init_metadata(tmp_path):
425
+ dest = _build_fixture(tmp_path)
426
+ transform_clone(dest, "appsec", "AppSec agent.", "claude", dist="appsec-cli")
427
+ init = (dest / "appsec" / "__init__.py").read_text()
428
+ assert '_pkg_version("appsec-cli")' in init
429
+
430
+
431
+ def test_transform_custom_dist_retargets_publish_pin(tmp_path):
432
+ dest = _build_fixture(tmp_path)
433
+ transform_clone(dest, "appsec", "AppSec agent.", "claude", dist="appsec-cli")
434
+ wf = (dest / ".github" / "workflows" / "publish.yml").read_text()
435
+ assert "appsec-cli==${DEV_VERSION}" in wf
436
+ assert "appsec==${DEV_VERSION}" not in wf
437
+
438
+
439
+ def test_transform_custom_dist_keeps_command_and_package(tmp_path):
440
+ """The dist rename must NOT touch the console command or the package dir."""
441
+ dest = _build_fixture(tmp_path)
442
+ transform_clone(dest, "appsec", "AppSec agent.", "claude", dist="appsec-cli")
443
+ # Package dir stays the import name.
444
+ assert (dest / "appsec").is_dir()
445
+ pyproject = (dest / "pyproject.toml").read_text()
446
+ # The console script (under [project.scripts]) stays the bare command.
447
+ assert 'appsec = "appsec.cli:main"' in pyproject
448
+ # packages list stays the import name.
449
+ assert 'packages = ["appsec"]' in pyproject
450
+
451
+
452
+ def test_transform_custom_dist_hyphenated_repo(tmp_path):
453
+ """A hyphenated repo (my-agent) with a custom dist still retargets correctly."""
454
+ dest = _build_fixture(tmp_path)
455
+ transform_clone(dest, "my-agent", "Hyphenated.", "claude", dist="my-agent-cli")
456
+ pyproject = (dest / "pyproject.toml").read_text()
457
+ assert 'name = "my-agent-cli"' in pyproject
458
+ # command + package (underscore) untouched.
459
+ assert (dest / "my_agent").is_dir()
460
+ assert 'my-agent = "my_agent.cli:main"' in pyproject
461
+
462
+
463
+ def test_transform_plan_includes_dist_default(tmp_path):
464
+ plan = transform_plan("appsec", "AppSec agent.")
465
+ assert plan["dist"] == "appsec"
466
+ # No retarget step when dist == repo_token.
467
+ assert not any("retarget" in s.lower() for s in plan["steps"])
468
+
469
+
470
+ def test_transform_plan_includes_dist_custom(tmp_path):
471
+ plan = transform_plan("appsec", "AppSec agent.", dist="appsec-cli")
472
+ assert plan["dist"] == "appsec-cli"
473
+ assert any("retarget" in s.lower() and "appsec-cli" in s for s in plan["steps"])
474
+
475
+
476
+ def test_transform_clone_empty_dist_raises(tmp_path):
477
+ """An explicitly-empty dist is a programming error, not "use the default"."""
478
+ dest = _build_fixture(tmp_path)
479
+ with pytest.raises(ValueError, match="dist"):
480
+ transform_clone(dest, "appsec", "AppSec agent.", "claude", dist="")
481
+
482
+
483
+ def test_transform_plan_whitespace_dist_raises():
484
+ with pytest.raises(ValueError, match="dist"):
485
+ transform_plan("appsec", "AppSec agent.", dist=" ")
486
+
487
+
378
488
  def test_transform_seed_avoids_md036_standalone_emphasis(tmp_path):
379
489
  """The CLAUDE.md seed must not place the agent name on a standalone
380
490
  emphasized line (markdownlint MD036 no-emphasis-as-heading — this broke
@@ -179,7 +179,7 @@ wheels = [
179
179
 
180
180
  [[package]]
181
181
  name = "guild-cli"
182
- version = "0.6.4"
182
+ version = "0.7.0"
183
183
  source = { editable = "." }
184
184
  dependencies = [
185
185
  { name = "pyyaml" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes