← back to Cli Printing Press
fix(cli): publish skill registry format and manuscript resolution (#106)
b037ac015cf20ce5eb645655b484fc501929f598 · 2026-04-01 18:23:59 -0700 · Trevin Chow
* fix(skills): fix publish skill registry.json format documentation
The skill documented registry.json as a flat array of objects with
cli_name/api_name fields, but the actual format is {schema_version,
entries: [{name, category, api, description, path}]}. This caused
the agent to iterate top-level dict values as objects, hitting
AttributeError on string keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): resolve manuscripts when api_name differs from archive slug
The generator derives api_name from the spec title (e.g., "steam-web")
but the skill archives manuscripts under a shorter slug (e.g., "steam").
The publish command now tries common suffix stripping (-web, -api, etc.)
and prefix matching when the exact directory doesn't exist.
Fixes manuscripts not being included in published CLIs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): key manuscripts by CLI name instead of API slug
Manuscripts were archived under the API slug (e.g., "steam") but looked
up by API name from the manifest (e.g., "steam-web"). Now:
- Skill archives under CLI name (e.g., "steam-web-pp-cli") — unambiguous
- Publish looks for CLI name first, then API name, then fuzzy resolve
- ArchivedManuscriptDir parameter renamed from apiName to key (generic)
- Backwards compatible: old manuscripts under API slugs still found
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): add hyphen-boundary prefix matching and comprehensive tests
Prefix matching in resolveManuscriptDir now requires a hyphen boundary:
"steam" matches "steam-web" but NOT "steamgames". Prevents false
positives on unrelated APIs that share a prefix.
17 tests covering: exact match, suffix stripping, prefix matching,
empty dirs, no matches, priority chain (CLI name > API name > fuzzy),
most-recent-run selection, and two adversarial prefix collision cases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
M internal/cli/publish.goA internal/cli/publish_resolve_test.goM internal/pipeline/paths.goM skills/printing-press-publish/SKILL.mdM skills/printing-press/SKILL.md
Diff
commit b037ac015cf20ce5eb645655b484fc501929f598
Author: Trevin Chow <trevin@trevinchow.com>
Date: Wed Apr 1 18:23:59 2026 -0700
fix(cli): publish skill registry format and manuscript resolution (#106)
* fix(skills): fix publish skill registry.json format documentation
The skill documented registry.json as a flat array of objects with
cli_name/api_name fields, but the actual format is {schema_version,
entries: [{name, category, api, description, path}]}. This caused
the agent to iterate top-level dict values as objects, hitting
AttributeError on string keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): resolve manuscripts when api_name differs from archive slug
The generator derives api_name from the spec title (e.g., "steam-web")
but the skill archives manuscripts under a shorter slug (e.g., "steam").
The publish command now tries common suffix stripping (-web, -api, etc.)
and prefix matching when the exact directory doesn't exist.
Fixes manuscripts not being included in published CLIs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): key manuscripts by CLI name instead of API slug
Manuscripts were archived under the API slug (e.g., "steam") but looked
up by API name from the manifest (e.g., "steam-web"). Now:
- Skill archives under CLI name (e.g., "steam-web-pp-cli") — unambiguous
- Publish looks for CLI name first, then API name, then fuzzy resolve
- ArchivedManuscriptDir parameter renamed from apiName to key (generic)
- Backwards compatible: old manuscripts under API slugs still found
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): add hyphen-boundary prefix matching and comprehensive tests
Prefix matching in resolveManuscriptDir now requires a hyphen boundary:
"steam" matches "steam-web" but NOT "steamgames". Prevents false
positives on unrelated APIs that share a prefix.
17 tests covering: exact match, suffix stripping, prefix matching,
empty dirs, no matches, priority chain (CLI name > API name > fuzzy),
most-recent-run selection, and two adversarial prefix collision cases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
internal/cli/publish.go | 81 +++++++++++++-
internal/cli/publish_resolve_test.go | 198 +++++++++++++++++++++++++++++++++
internal/pipeline/paths.go | 7 +-
skills/printing-press-publish/SKILL.md | 20 ++--
skills/printing-press/SKILL.md | 17 +--
5 files changed, 302 insertions(+), 21 deletions(-)
diff --git a/internal/cli/publish.go b/internal/cli/publish.go
index 43dbb96a..6f25db9f 100644
--- a/internal/cli/publish.go
+++ b/internal/cli/publish.go
@@ -225,17 +225,36 @@ func newPublishPackageCmd() *cobra.Command {
ModulePath: modulePath,
}
+ // Look for manuscripts: try CLI name first (new convention),
+ // then API name, then fuzzy resolve (backwards compatibility).
apiName := vResult.APIName
if apiName == "" {
apiName = naming.TrimCLISuffix(cliName)
}
msRoot := pipeline.PublishedManuscriptsRoot()
- msAPIDir := filepath.Join(msRoot, apiName)
- runID, err := findMostRecentRun(msAPIDir)
- if err == nil && runID != "" {
+ var msDir string
+ var runID string
+
+ // 1. Try CLI name (new convention: manuscripts/<cli-name>/<run>/)
+ cliMsDir := filepath.Join(msRoot, cliName)
+ if rid, err := findMostRecentRun(cliMsDir); err == nil && rid != "" {
+ msDir, runID = cliMsDir, rid
+ }
+ // 2. Try API name (old convention: manuscripts/<api-name>/<run>/)
+ if runID == "" {
+ apiMsDir := filepath.Join(msRoot, apiName)
+ if rid, err := findMostRecentRun(apiMsDir); err == nil && rid != "" {
+ msDir, runID = apiMsDir, rid
+ }
+ }
+ // 3. Fuzzy resolve (strip suffixes, prefix match)
+ if runID == "" {
+ msDir, runID = resolveManuscriptDir(msRoot, apiName)
+ }
+ if runID != "" {
result.RunID = runID
- srcMsDir := filepath.Join(msAPIDir, runID)
+ srcMsDir := filepath.Join(msDir, runID)
dstMsDir := filepath.Join(stagingCLIDir, ".manuscripts", runID)
if err := pipeline.CopyDir(srcMsDir, dstMsDir); err != nil {
cleanupTarget()
@@ -374,11 +393,19 @@ func runValidation(dir string) ValidateResult {
}
// 7. Manuscripts check (warn-only)
+ // Try CLI name first (new convention), then API name, then fuzzy resolve
apiName := result.APIName
if apiName == "" {
apiName = naming.TrimCLISuffix(cliName)
}
- msDir := filepath.Join(pipeline.PublishedManuscriptsRoot(), apiName)
+ msRoot := pipeline.PublishedManuscriptsRoot()
+ msDir := filepath.Join(msRoot, cliName)
+ if _, err := os.Stat(msDir); os.IsNotExist(err) {
+ msDir = filepath.Join(msRoot, apiName)
+ }
+ if _, err := os.Stat(msDir); os.IsNotExist(err) {
+ msDir, _ = resolveManuscriptDir(msRoot, apiName)
+ }
if _, err := os.Stat(msDir); os.IsNotExist(err) {
result.Checks = append(result.Checks, CheckResult{Name: "manuscripts", Passed: true, Warning: "no manuscripts found"})
} else {
@@ -565,6 +592,50 @@ func findMostRecentRun(msAPIDir string) (string, error) {
return runs[len(runs)-1], nil
}
+// resolveManuscriptDir attempts to find the manuscripts directory for an API
+// when the exact apiName doesn't match a directory. The generator and the skill
+// may use different slugs (e.g., "steam-web" vs "steam"). This function tries:
+// 1. Strip common suffixes: -web, -api, -service
+// 2. Scan directories for prefix matches (e.g., "steam" is a prefix of "steam-web")
+//
+// Returns the resolved directory path and the run ID, or empty strings if not found.
+func resolveManuscriptDir(msRoot, apiName string) (string, string) {
+ // Try stripping common suffixes
+ suffixes := []string{"-web", "-api", "-service", "-public", "-v2", "-v3"}
+ for _, suffix := range suffixes {
+ candidate := strings.TrimSuffix(apiName, suffix)
+ if candidate != apiName {
+ dir := filepath.Join(msRoot, candidate)
+ if runID, err := findMostRecentRun(dir); err == nil && runID != "" {
+ return dir, runID
+ }
+ }
+ }
+
+ // Scan directories for prefix/substring matches
+ entries, err := os.ReadDir(msRoot)
+ if err != nil {
+ return "", ""
+ }
+ for _, e := range entries {
+ if !e.IsDir() {
+ continue
+ }
+ name := e.Name()
+ // Check if either is a prefix of the other WITH a hyphen boundary.
+ // "steam" matches "steam-web" (prefix + hyphen) but NOT "steamgames" (no hyphen).
+ if (strings.HasPrefix(apiName, name+"-") || apiName == name) ||
+ (strings.HasPrefix(name, apiName+"-") || name == apiName) {
+ dir := filepath.Join(msRoot, name)
+ if runID, err := findMostRecentRun(dir); err == nil && runID != "" {
+ return dir, runID
+ }
+ }
+ }
+
+ return "", ""
+}
+
// hasContent checks if a directory contains at least one non-directory entry,
// recursively. Returns false for empty directories or directories containing
// only empty subdirectories.
diff --git a/internal/cli/publish_resolve_test.go b/internal/cli/publish_resolve_test.go
new file mode 100644
index 00000000..f58c7ed5
--- /dev/null
+++ b/internal/cli/publish_resolve_test.go
@@ -0,0 +1,198 @@
+package cli
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// createRunDir creates a manuscripts/<key>/<runID>/research/ structure with a dummy file.
+func createRunDir(t *testing.T, msRoot, key, runID string) {
+ t.Helper()
+ dir := filepath.Join(msRoot, key, runID, "research")
+ require.NoError(t, os.MkdirAll(dir, 0o755))
+ require.NoError(t, os.WriteFile(filepath.Join(dir, "brief.md"), []byte("test"), 0o644))
+}
+
+func TestResolveManuscriptDir(t *testing.T) {
+ t.Run("exact match on API name", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam-web", "20260401-120000")
+
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web")
+ assert.Equal(t, filepath.Join(msRoot, "steam-web"), dir)
+ assert.Equal(t, "20260401-120000", runID)
+ })
+
+ t.Run("suffix strip: steam-web from steam-web-api", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam-web", "20260401-120000")
+
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web-api")
+ assert.Equal(t, filepath.Join(msRoot, "steam-web"), dir)
+ assert.Equal(t, "20260401-120000", runID)
+ })
+
+ t.Run("suffix strip: steam from steam-web", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam", "20260401-120000")
+
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web")
+ assert.Equal(t, filepath.Join(msRoot, "steam"), dir)
+ assert.Equal(t, "20260401-120000", runID)
+ })
+
+ t.Run("prefix match: steam dir matches steam-web lookup", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam", "20260401-120000")
+
+ // steam-web-service doesn't strip to "steam" directly,
+ // but "steam" is a prefix of "steam-web-service"
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web-service")
+ assert.Equal(t, filepath.Join(msRoot, "steam"), dir)
+ assert.Equal(t, "20260401-120000", runID)
+ })
+
+ t.Run("no match at all", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "notion", "20260401-120000")
+
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web")
+ assert.Empty(t, dir)
+ assert.Empty(t, runID)
+ })
+
+ t.Run("empty manuscripts root", func(t *testing.T) {
+ msRoot := t.TempDir()
+
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web")
+ assert.Empty(t, dir)
+ assert.Empty(t, runID)
+ })
+
+ t.Run("directory exists but no runs (empty)", func(t *testing.T) {
+ msRoot := t.TempDir()
+ require.NoError(t, os.MkdirAll(filepath.Join(msRoot, "steam"), 0o755))
+
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web")
+ // Directory exists but findMostRecentRun returns "" — no match
+ assert.Empty(t, dir)
+ assert.Empty(t, runID)
+ })
+
+ t.Run("ADVERSARIAL: prefix collision — steam should NOT match steamgames", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam", "20260401-120000")
+
+ // "steamgames" is NOT prefixed by "steam-" — no hyphen boundary
+ dir, runID := resolveManuscriptDir(msRoot, "steamgames")
+ assert.Empty(t, dir, "steam should NOT match steamgames (no hyphen boundary)")
+ assert.Empty(t, runID)
+ })
+
+ t.Run("prefix WITH hyphen boundary works: steam matches steam-web", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam", "20260401-120000")
+
+ // "steam-web" IS prefixed by "steam-" — hyphen boundary present
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web")
+ assert.NotEmpty(t, dir, "steam should match steam-web (hyphen boundary)")
+ assert.Equal(t, "20260401-120000", runID)
+ })
+
+ t.Run("ADVERSARIAL: multiple candidates — picks first alphabetically", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam", "20260401-120000")
+ createRunDir(t, msRoot, "steam-web", "20260401-130000")
+
+ // With API name "steam-web-api", suffix strip finds "steam-web" first
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web-api")
+ assert.Equal(t, filepath.Join(msRoot, "steam-web"), dir)
+ assert.Equal(t, "20260401-130000", runID)
+ })
+
+ t.Run("picks most recent run when multiple exist", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam", "20260331-120000")
+ createRunDir(t, msRoot, "steam", "20260401-120000")
+ createRunDir(t, msRoot, "steam", "20260330-120000")
+
+ dir, runID := resolveManuscriptDir(msRoot, "steam-web")
+ assert.Equal(t, filepath.Join(msRoot, "steam"), dir)
+ assert.Equal(t, "20260401-120000", runID) // most recent by lexicographic sort
+ })
+}
+
+func TestManuscriptLookupPriority(t *testing.T) {
+ // Simulates the full lookup chain: CLI name → API name → fuzzy resolve
+ // This is what the publish package command does.
+
+ t.Run("prefers CLI name over API name", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam-web-pp-cli", "run-cli")
+ createRunDir(t, msRoot, "steam-web", "run-api")
+ createRunDir(t, msRoot, "steam", "run-slug")
+
+ cliName := "steam-web-pp-cli"
+
+ // Step 1: CLI name
+ cliDir := filepath.Join(msRoot, cliName)
+ runID, err := findMostRecentRun(cliDir)
+ assert.NoError(t, err)
+ assert.Equal(t, "run-cli", runID) // CLI name wins
+ })
+
+ t.Run("falls back to API name when CLI name missing", func(t *testing.T) {
+ msRoot := t.TempDir()
+ // No CLI name directory
+ createRunDir(t, msRoot, "steam-web", "run-api")
+ createRunDir(t, msRoot, "steam", "run-slug")
+
+ cliName := "steam-web-pp-cli"
+
+ // Step 1: CLI name — fails
+ cliDir := filepath.Join(msRoot, cliName)
+ cliRunID, _ := findMostRecentRun(cliDir)
+ assert.Empty(t, cliRunID)
+
+ // Step 2: API name
+ apiDir := filepath.Join(msRoot, "steam-web")
+ apiRunID, err := findMostRecentRun(apiDir)
+ assert.NoError(t, err)
+ assert.Equal(t, "run-api", apiRunID) // API name is second priority
+ })
+
+ t.Run("falls back to fuzzy when both CLI and API names missing", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "steam", "run-slug")
+
+ apiName := "steam-web"
+
+ // Steps 1+2 fail, step 3: fuzzy resolve
+ dir, runID := resolveManuscriptDir(msRoot, apiName)
+ assert.Equal(t, filepath.Join(msRoot, "steam"), dir)
+ assert.Equal(t, "run-slug", runID)
+ })
+
+ t.Run("returns empty when nothing matches", func(t *testing.T) {
+ msRoot := t.TempDir()
+ createRunDir(t, msRoot, "notion", "run-notion")
+
+ cliName := "steam-web-pp-cli"
+ apiName := "steam-web"
+
+ cliDir := filepath.Join(msRoot, cliName)
+ runID, _ := findMostRecentRun(cliDir)
+ assert.Empty(t, runID)
+
+ apiDir := filepath.Join(msRoot, apiName)
+ runID, _ = findMostRecentRun(apiDir)
+ assert.Empty(t, runID)
+
+ _, runID = resolveManuscriptDir(msRoot, apiName)
+ assert.Empty(t, runID)
+ })
+}
diff --git a/internal/pipeline/paths.go b/internal/pipeline/paths.go
index 47dc4328..0987c865 100644
--- a/internal/pipeline/paths.go
+++ b/internal/pipeline/paths.go
@@ -105,8 +105,11 @@ func PublishedManuscriptsRoot() string {
return filepath.Join(PressHome(), "manuscripts")
}
-func ArchivedManuscriptDir(apiName, runID string) string {
- return filepath.Join(PublishedManuscriptsRoot(), apiName, runID)
+// ArchivedManuscriptDir returns the path to a manuscript archive.
+// The key is typically the CLI name (e.g., "steam-web-pp-cli") but may be
+// an API slug (e.g., "steam") for backwards compatibility with older archives.
+func ArchivedManuscriptDir(key, runID string) string {
+ return filepath.Join(PublishedManuscriptsRoot(), key, runID)
}
func ArchivedResearchDir(apiName, runID string) string {
diff --git a/skills/printing-press-publish/SKILL.md b/skills/printing-press-publish/SKILL.md
index f494e6e2..8a68ff78 100644
--- a/skills/printing-press-publish/SKILL.md
+++ b/skills/printing-press-publish/SKILL.md
@@ -325,19 +325,25 @@ cp -r <staging-dir>/library/* "$PUBLISH_REPO_DIR/library/"
### Update registry.json
-Read `$PUBLISH_REPO_DIR/registry.json`, add or update the entry for this CLI:
+The registry file has this structure:
```json
{
- "cli_name": "<cli-name>",
- "api_name": "<api-name>",
- "category": "<category>",
- "description": "<from manifest or empty>",
- "printing_press_version": "<from manifest>",
- "published_date": "<today YYYY-MM-DD>"
+ "schema_version": 1,
+ "entries": [
+ {
+ "name": "<cli-name>",
+ "category": "<category>",
+ "api": "<api-display-name>",
+ "description": "<from manifest or README>",
+ "path": "library/<category>/<cli-name>"
+ }
+ ]
}
```
+Read `$PUBLISH_REPO_DIR/registry.json`, parse the `entries` array (not the top-level object), add or update the entry for this CLI. Match on `name` field. Preserve `schema_version` and any other top-level fields.
+
Write back with `jq` or via the Write tool.
### Commit and push
diff --git a/skills/printing-press/SKILL.md b/skills/printing-press/SKILL.md
index f7163826..434af4a6 100644
--- a/skills/printing-press/SKILL.md
+++ b/skills/printing-press/SKILL.md
@@ -286,7 +286,7 @@ Maintain a lightweight state file at `$STATE_FILE` so `/printing-press-score` ca
}
```
-Active mutable work lives under `$PRESS_RUNSTATE/`. Published CLIs live under `$PRESS_LIBRARY/`. Archived research and verification evidence live under `$PRESS_MANUSCRIPTS/<api>/<run-id>/`. Do not write mutable run artifacts into the repo checkout.
+Active mutable work lives under `$PRESS_RUNSTATE/`. Published CLIs live under `$PRESS_LIBRARY/`. Archived research and verification evidence live under `$PRESS_MANUSCRIPTS/<cli-name>/<run-id>/` (keyed by CLI name, e.g., `steam-web-pp-cli`, not the API slug). Do not write mutable run artifacts into the repo checkout.
Examples of the current naming/layout to preserve:
- `discord-pp-cli/internal/store/store.go`
@@ -295,7 +295,7 @@ Examples of the current naming/layout to preserve:
## Outputs
-Every run writes up to 5 concise artifacts under the current managed run and archives them to `$PRESS_MANUSCRIPTS/<api>/<run-id>/`:
+Every run writes up to 5 concise artifacts under the current managed run and archives them to `$PRESS_MANUSCRIPTS/<cli-name>/<run-id>/`:
1. `research/<stamp>-feat-<api>-pp-cli-brief.md`
2. `research/<stamp>-feat-<api>-pp-cli-absorb-manifest.md`
@@ -344,7 +344,7 @@ Before new research:
- If the user passed `--spec`, use it directly (existing behavior).
- Otherwise, proceed with normal discovery (catalog, KnownSpecs, apis-guru, web search).
2. Check for prior research in:
- - `$PRESS_MANUSCRIPTS/<api>/*/research/*`
+ - `$PRESS_MANUSCRIPTS/<cli-name>/*/research/*` (also check `$PRESS_MANUSCRIPTS/<api>/*/research/*` for backwards compatibility)
- `$REPO_ROOT/docs/plans/*<api>*` (legacy fallback)
3. Reuse good prior work instead of redoing it.
4. **Library Check** — Check if a CLI for this API already exists in the library and present the user with context and options.
@@ -1844,9 +1844,12 @@ future `/printing-press` runs on the same API. Publishing ships the CLI to the
library repo. A run that isn't ready to publish still produces valuable research.
```bash
-mkdir -p "$PRESS_MANUSCRIPTS/<api>/$RUN_ID"
-cp -r "$RESEARCH_DIR" "$PRESS_MANUSCRIPTS/<api>/$RUN_ID/research" 2>/dev/null || true
-cp -r "$PROOFS_DIR" "$PRESS_MANUSCRIPTS/<api>/$RUN_ID/proofs" 2>/dev/null || true
+# Archive under CLI name (e.g., steam-web-pp-cli), not API slug (e.g., steam).
+# The CLI name is unambiguous and matches what publish expects.
+CLI_NAME="$(basename "$PRESS_LIBRARY/<api>-pp-cli")"
+mkdir -p "$PRESS_MANUSCRIPTS/$CLI_NAME/$RUN_ID"
+cp -r "$RESEARCH_DIR" "$PRESS_MANUSCRIPTS/$CLI_NAME/$RUN_ID/research" 2>/dev/null || true
+cp -r "$PROOFS_DIR" "$PRESS_MANUSCRIPTS/$CLI_NAME/$RUN_ID/proofs" 2>/dev/null || true
# Archive discovery artifacts (sniff captures, URL lists, sniff report).
# Remove session state before archiving — contains authentication cookies/tokens.
@@ -1859,7 +1862,7 @@ if [ -d "$DISCOVERY_DIR" ]; then
jq 'del(.log.entries[].response.content.text)' "$har" > "${har}.stripped" 2>/dev/null && mv "${har}.stripped" "$har" || rm -f "${har}.stripped"
fi
done
- cp -r "$DISCOVERY_DIR" "$PRESS_MANUSCRIPTS/<api>/$RUN_ID/discovery" 2>/dev/null || true
+ cp -r "$DISCOVERY_DIR" "$PRESS_MANUSCRIPTS/$CLI_NAME/$RUN_ID/discovery" 2>/dev/null || true
fi
```
← fc94557c fix(skills): add secret leak prevention rules (#107)
·
back to Cli Printing Press
·
refactor(skills): extract conditional sections to reference 998908b5 →