Compare commits
24 Commits
research/c
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
357d13270f | |
|
|
930ac6e80d | |
|
|
4c71f65313 | |
|
|
cc2f2914c5 | |
|
|
f2fe45bab8 | |
|
|
06fff77d75 | |
|
|
23fa2fbdbd | |
|
|
5c18ba696f | |
|
|
4cc524b3a5 | |
|
|
65cc751c6f | |
|
|
0dd9bc1bee | |
|
|
79012c4fda | |
|
|
a2b329dd7f | |
|
|
3f76755e85 | |
|
|
e82bfcb0cd | |
|
|
b062ac354e | |
|
|
39356ebdbf | |
|
|
3466159f7c | |
|
|
0d303caf43 | |
|
|
9f11df3e2a | |
|
|
807dcc8d90 | |
|
|
fe04297604 | |
|
|
7b9afb1f6a | |
|
|
e1d22d83ba |
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "cc-os",
|
||||
"owner": {
|
||||
"name": "Jared Swanson"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "cc-architect",
|
||||
"source": "./plugins/cc-architect",
|
||||
"description": "Model-tier-optimized execution plans, scaffolding, and configuration operations for building and managing Claude Code extensions (plugins, skills, workflows, descriptions)"
|
||||
},
|
||||
{
|
||||
"name": "os-adr",
|
||||
"source": "./plugins/os-adr",
|
||||
"description": "Architecture Decision Records, consistently in every project: one template, docs/adr/NNNN-title.md + mechanical index, deterministic SessionStart existence check, non-destructive migration, and deterministic-first retrieval."
|
||||
},
|
||||
{
|
||||
"name": "os-backlog",
|
||||
"source": "./plugins/os-backlog",
|
||||
"description": "Git-issues backlog across projects: deterministic tracker routing (forgejo/github/repo), mid-session capture as labeled issues, pull-only listing (ADR-0042)."
|
||||
},
|
||||
{
|
||||
"name": "os-context",
|
||||
"source": "./plugins/os-context",
|
||||
"description": "Prompt-composer SessionStart plugin: concatenates prompts/session-start/*.md (session-orchestration + delegation-economics rules, kickoff conventions, standing safety rules, decision-memo format) into one additionalContext block, injected at session start and after compaction."
|
||||
},
|
||||
{
|
||||
"name": "os-doc-hygiene",
|
||||
"source": "./plugins/os-doc-hygiene",
|
||||
"description": "Monitor and manage stale and bloated project documentation. Deterministic SessionStart reminder + AI-assisted check/clean skills with git-safe, scoped cleanup."
|
||||
},
|
||||
{
|
||||
"name": "os-sdlc",
|
||||
"source": "./plugins/os-sdlc",
|
||||
"description": "Harness-driven software development lifecycle for cc-os: grill/wayfinder/to-spec/to-tickets/implement/review, adapted from Matt Pocock's skill lifecycle and Delta Refinery's multi-level pipeline, wired into os-backlog and os-adr."
|
||||
},
|
||||
{
|
||||
"name": "os-shortcuts",
|
||||
"source": "./plugins/os-shortcuts",
|
||||
"description": "User-invoked QOL shortcut commands for the cc-os plugin family — the 'keybindings' of cc-os, ritual multi-step sequences invoked on demand (not automatic hook injection). Starts with /os-shortcuts:wrap (session-close ritual); expect more shortcuts to accumulate here."
|
||||
},
|
||||
{
|
||||
"name": "os-status",
|
||||
"source": "./plugins/os-status",
|
||||
"description": "Aggregated deterministic SessionStart status checks for the cc-os plugin family: per-project artifacts (ADR system, vault hub note) and environment hazards (subagent model env override). At most one banner per session; snooze/suppress state in .cc-os/."
|
||||
},
|
||||
{
|
||||
"name": "os-vault",
|
||||
"source": "./plugins/os-vault",
|
||||
"description": "Vault and graph memory system for Claude Code — integrates SecondBrain knowledge graph, episodic journal, and convention summaries into every session"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"schema_version": 1,
|
||||
"rules": [
|
||||
{
|
||||
"glob": "docs/orchestration-audit/auditor-reports/S*-report.md",
|
||||
"lifetime": "delete-once-served",
|
||||
"served_when": "The run's synthesis findings doc (docs/orchestration-audit/<audit-date>-findings.md) has been written and verified; these per-session reports are the raw inputs it condenses.",
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "calibration pass #2 (2026-07-15)",
|
||||
"note": "Per-session raw auditor reports condensed by the run's synthesis findings doc; delete-once-served is always confirm-tier (ADR-0039)."
|
||||
},
|
||||
{
|
||||
"glob": "plugins/*/HANDOFF-*.md",
|
||||
"lifetime": "delete-once-served",
|
||||
"served_when": "The handoff it describes has been picked up and its follow-on work completed in a later session.",
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "calibration pass #1 (lifecycle-aware-doc-hygiene)",
|
||||
"note": "Classifier-judged served_when \u2014 always confirm-tier by ADR-0039; never auto-deleted."
|
||||
},
|
||||
{
|
||||
"glob": "autoresearch/classic-*/",
|
||||
"lifetime": "temporary",
|
||||
"retain_recent": 3,
|
||||
"max_age_days": 30,
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "calibration pass #1 (lifecycle-aware-doc-hygiene); narrowed from autoresearch/*/ 2026-07-15",
|
||||
"note": "Autoresearch run directories are disposable run output; keep the 3 newest, age out the rest. Narrowed to conventional prefixes so autoresearch/ can hold keep-worthy content."
|
||||
},
|
||||
{
|
||||
"glob": "autoresearch/improve-*/",
|
||||
"lifetime": "temporary",
|
||||
"retain_recent": 3,
|
||||
"max_age_days": 30,
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "calibration pass #1 (lifecycle-aware-doc-hygiene); narrowed from autoresearch/*/ 2026-07-15",
|
||||
"note": "Autoresearch run directories are disposable run output; keep the 3 newest, age out the rest. Narrowed to conventional prefixes so autoresearch/ can hold keep-worthy content."
|
||||
},
|
||||
{
|
||||
"glob": "docs/orchestration-audit/factsheets/*.md",
|
||||
"lifetime": "temporary",
|
||||
"retain_recent": 10,
|
||||
"max_age_days": 90,
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "calibration pass #1 (lifecycle-aware-doc-hygiene)",
|
||||
"note": "Regenerable precompute for the orchestration IRL audit; the audit skill rebuilds them on each run."
|
||||
},
|
||||
{
|
||||
"glob": "openspec/changes/archive/*/",
|
||||
"lifetime": "temporary",
|
||||
"retain_recent": 3,
|
||||
"max_age_days": 90,
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "conversation 2026-07-15 (openspec lifecycle rules)",
|
||||
"note": "Archived change dirs are redundant once synced into specs/ and stay recoverable from git history; age out after a quarter."
|
||||
},
|
||||
{
|
||||
"glob": "plugins/*/.pytest_cache/",
|
||||
"lifetime": "temporary",
|
||||
"retain_recent": 0,
|
||||
"max_age_days": 7,
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "calibration pass #1 (lifecycle-aware-doc-hygiene)",
|
||||
"note": "Regenerable pytest cache. Judge noted a .gitignore entry may be preferable long-term."
|
||||
},
|
||||
{
|
||||
"glob": "plugins/*/openspec/changes/archive/*/",
|
||||
"lifetime": "temporary",
|
||||
"retain_recent": 3,
|
||||
"max_age_days": 90,
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "conversation 2026-07-15 (openspec lifecycle rules)",
|
||||
"note": "Archived change dirs are redundant once synced into specs/ and stay recoverable from git history; age out after a quarter."
|
||||
},
|
||||
{
|
||||
"glob": "openspec/specs/**",
|
||||
"lifetime": "keep",
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "conversation 2026-07-15 (openspec lifecycle rules)",
|
||||
"note": "Live OpenSpec capability specs \u2014 source of truth future changes diff against; never age out."
|
||||
},
|
||||
{
|
||||
"glob": "plugins/*/openspec/specs/**",
|
||||
"lifetime": "keep",
|
||||
"confirmed_by": "human",
|
||||
"confirmed_on": "2026-07-15",
|
||||
"source": "conversation 2026-07-15 (openspec lifecycle rules)",
|
||||
"note": "Live OpenSpec capability specs \u2014 source of truth future changes diff against; never age out."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ graphify-out/
|
|||
|
||||
# Python bytecode
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
|
||||
# autoresearch skill run logs (eval iterations)
|
||||
/autoresearch/
|
||||
|
|
|
|||
14
CLAUDE.md
14
CLAUDE.md
|
|
@ -80,8 +80,9 @@ before modifying any of these:
|
|||
design-template skills, SessionStart/End hooks, memsearch + vault git sync. Write-behavior
|
||||
eval harness in `plugins/os-vault/eval/`.
|
||||
- **os-context** (`plugins/os-context/`, renamed 2026-07-13 from os-orchestration) — a
|
||||
prompt-composer SessionStart plugin: concatenates `prompts/session-start/*.md` (currently
|
||||
just `10-orchestration.md`, the session-orchestration + delegation-economics rules) into
|
||||
prompt-composer SessionStart plugin: concatenates `prompts/session-start/*.md`
|
||||
(orchestration/delegation rules, kickoff conventions, standing safety, decision-memo
|
||||
format) into
|
||||
one additionalContext block for all sessions; this repo carries no local override. Eval
|
||||
harness in `plugins/os-context/eval/`.
|
||||
- **os-status** (`plugins/os-status/`) — aggregated deterministic SessionStart checks
|
||||
|
|
@ -92,6 +93,10 @@ before modifying any of these:
|
|||
find skills; Eval A/B/C harnesses. cc-os retrofit done 2026-07-12 (promoted ahead of
|
||||
pilots — ADR-020 amendment / `docs/adr/0020`); remaining rollout: pilot projects one at a
|
||||
time, then wider.
|
||||
- **os-backlog** (`plugins/os-backlog/`) — git-issues backlog surface (ADR-0042 retired
|
||||
Planka 2026-07-16): tracker routing (`forgejo:`/`github:`/`repo:` in `.cc-os/config`),
|
||||
`issue-create`/`issues` CLI, capture/list/route skills, ten-label taxonomy with
|
||||
human-only `next`; non-repo/ops work lives in the private `jared/ops` Forgejo repo.
|
||||
- **Graphify** v0.8.31 (`~/.local/bin/graphify`; PyPI package is `graphifyy`, double-y) —
|
||||
vault graph at `~/Documents/SecondBrain/graphify-out/`, per-project graphs at
|
||||
`<project-root>/graphify-out/` (gitignored); both disposable/rebuildable.
|
||||
|
|
@ -108,7 +113,7 @@ next signal is production IRL session audits.
|
|||
|
||||
## Issue tracking
|
||||
|
||||
Issues (created via `/to-issues`) live on self-hosted Forgejo (`jared/cc-os`), queried with
|
||||
Issues (created via `/to-tickets`) live on self-hosted Forgejo (`jared/cc-os`), queried with
|
||||
the `tea` CLI — not GitHub/`gh`. See `docs/issue-workflow.md`.
|
||||
|
||||
## OpenSpec workflow
|
||||
|
|
@ -144,7 +149,8 @@ OpenSpec artifacts comes from `docs/` and this file.
|
|||
and never let the index file regrow into a changelog — the os-doc-hygiene `file_length`
|
||||
signal (400 lines / ~4k tokens) enforces this.
|
||||
- **Plugin and skill naming:** before naming ANY new cc-os plugin, skill, or slash command,
|
||||
Read `~/Documents/SecondBrain/cc-os-plugin-skill-naming-convention.md` and follow it. In
|
||||
Read `plugins/cc-architect/references/conventions/cc-os-naming.md` (canonical repo copy;
|
||||
the SecondBrain vault note of the same name now defers to it) and follow it. In
|
||||
brief: plugins are `os-[domain]`; skills are verb-first kebab-case, invoked as
|
||||
`/os-[domain]:[verb]`; no `commands/` dispatcher directories; **never set a `name:` field
|
||||
in SKILL.md frontmatter** (it collapses the slash command to a bare unnamespaced form).
|
||||
|
|
|
|||
|
|
@ -93,11 +93,11 @@ class PluginRefresher
|
|||
cache_base = File.expand_path("~/.claude/plugins/cache/#{marketplace_name}/#{plugin_name}")
|
||||
source_full = File.join(source_path, plugin_name)
|
||||
|
||||
# Find the version directory in cache
|
||||
# Old version directories linger after upgrades; diff the newest one
|
||||
versions = Dir.glob(File.join(cache_base, '*')).select { |p| File.directory?(p) }
|
||||
return false if versions.empty?
|
||||
|
||||
cache_path = versions.first
|
||||
cache_path = versions.max_by { |p| File.mtime(p) }
|
||||
|
||||
# Run diff check (exclude plugin manager state files)
|
||||
output = `diff -rq "#{cache_path}" "#{source_full}" --exclude='.git' --exclude='__pycache__' --exclude='.orphaned_at' 2>&1`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Repo-level test runner for the mixed minitest/pytest suites under plugins/.
|
||||
#
|
||||
# bin/test # run every plugin suite
|
||||
# bin/test os-backlog # run one plugin's suites (repeatable)
|
||||
#
|
||||
# Per plugin it runs `ruby tests/all.rb` when present (minitest) and
|
||||
# `pytest tests/` when any tests/*_test.py or tests/test_*.py exists.
|
||||
# pytest runs once per plugin so same-named test files (hook_test.py in
|
||||
# several plugins) never collide on module import.
|
||||
# os-vault's tests/ is an eval harness (harness.sh), not a unit suite,
|
||||
# and is intentionally not run here.
|
||||
|
||||
require "open3"
|
||||
|
||||
class PluginSuite
|
||||
RUNNERS = {
|
||||
minitest: ->(dir) { ["ruby", "tests/all.rb"] },
|
||||
pytest: ->(dir) { ["pytest", "-q", "tests"] }
|
||||
}.freeze
|
||||
|
||||
attr_reader :name
|
||||
|
||||
def initialize(dir)
|
||||
@dir = dir
|
||||
@name = File.basename(dir)
|
||||
end
|
||||
|
||||
def kinds
|
||||
kinds = []
|
||||
kinds << :minitest if File.exist?(File.join(@dir, "tests", "all.rb"))
|
||||
kinds << :pytest if pytest_files?
|
||||
kinds
|
||||
end
|
||||
|
||||
def run(kind)
|
||||
command = RUNNERS.fetch(kind).call(@dir)
|
||||
stdout, status = Open3.capture2e(*command, chdir: @dir)
|
||||
[status.success?, stdout]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pytest_files?
|
||||
Dir.glob(File.join(@dir, "tests", "{test_*,*_test}.py")).any?
|
||||
end
|
||||
end
|
||||
|
||||
repo_root = File.expand_path("..", __dir__)
|
||||
requested = ARGV
|
||||
suites = Dir.glob(File.join(repo_root, "plugins", "*"))
|
||||
.select { |d| File.directory?(d) }
|
||||
.map { |d| PluginSuite.new(d) }
|
||||
.select { |s| requested.empty? || requested.include?(s.name) }
|
||||
.sort_by(&:name)
|
||||
|
||||
unknown = requested - suites.map(&:name)
|
||||
abort "bin/test: no such plugin(s): #{unknown.join(", ")}" unless unknown.empty?
|
||||
|
||||
failures = []
|
||||
ran = 0
|
||||
suites.each do |suite|
|
||||
suite.kinds.each do |kind|
|
||||
ran += 1
|
||||
ok, output = suite.run(kind)
|
||||
puts "#{ok ? "PASS" : "FAIL"} #{suite.name} (#{kind})"
|
||||
unless ok
|
||||
failures << "#{suite.name} (#{kind})"
|
||||
puts output.lines.map { |l| " #{l}" }.join
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ran.zero?
|
||||
puts "bin/test: no test suites found"
|
||||
elsif failures.empty?
|
||||
puts "#{ran} suites passed"
|
||||
else
|
||||
abort "#{failures.size}/#{ran} suites failed: #{failures.join(", ")}"
|
||||
end
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
id: "0033"
|
||||
date: 2026-07-13
|
||||
status: Accepted
|
||||
status: Superseded
|
||||
supersedes:
|
||||
superseded-by:
|
||||
superseded-by: "0042"
|
||||
affected-paths: [plugins/os-backlog/skills/route/SKILL.md, plugins/os-backlog/hooks/session_start.py, plugins/os-backlog/bin/os-backlog, .cc-os/config]
|
||||
affected-components: [os-backlog, to-issues]
|
||||
---
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: "0037"
|
||||
date: 2026-07-14
|
||||
status: Accepted
|
||||
supersedes:
|
||||
superseded-by:
|
||||
affected-paths: [plugins/os-sdlc/]
|
||||
affected-components: [os-sdlc, os-backlog, os-adr, os-vault]
|
||||
---
|
||||
|
||||
# 0037 — os-sdlc lives inside cc-os as a new plugin, not a separate cc-sdlc marketplace
|
||||
|
||||
## Context
|
||||
|
||||
User is adopting Matt Pocock's v1.1 skill lifecycle (grill/to-spec/to-tickets/implement/wayfinder/research/code-review) and wants to blend it with ~/dev/delta-refinery's heavier multi-level pipeline pattern plus cc-os's existing os-backlog/os-adr systems, to automate large chunks of the dev process for upcoming projects. The open question was whether this SDLC layer should be a new plugin inside the cc-os monorepo (registered in the existing local-plugins marketplace) or a separate cc-sdlc marketplace/repo, motivated by wanting shared tooling reusable across harnesses (Claude Code/Codex/pi) and separation of concerns from general os-* tooling.
|
||||
|
||||
## Decision
|
||||
|
||||
os-sdlc lives at plugins/os-sdlc/ inside the cc-os repo, registered in the existing local-plugins marketplace, following the os-[domain] naming convention (cc-os-plugin-skill-naming-convention.md). It is scoped to include skills, agents, hooks, reference material, and scripts (not skills-only like most current os-* plugins). The first skill is /os-sdlc:review, cherry-picking Matt Pocock's standards-conformance + spec-fidelity + Fowler refactor-smell review axes, coexisting with the existing generic /code-review (correctness + reuse/simplification) rather than replacing it. Full pipeline design (how much of Delta Refinery's multi-level/resumable-handoff machinery to adopt, how implement/wayfinder are shaped) is deferred to a follow-up brainstorming session; plugins/os-sdlc/OVERVIEW.md is the launching-point doc for that session.
|
||||
|
||||
## Consequences
|
||||
|
||||
Adding a new SDLC lifecycle domain becomes easier: it reuses the existing marketplace manifest, refresh-plugins tooling, and os-[domain] naming convention instead of standing up new infrastructure. os-sdlc's skills can call directly into os-backlog (tickets) and os-adr (decisions) as sibling plugins in the same repo, keeping ADR-0023's cooperating-plugins bet intact. The tradeoff: os-sdlc is not independently reusable outside cc-os without extraction later if the user ever wants to publish/share just the SDLC layer decoupled from the personal vault/memory stack — that would require a future split, not ruled out here, just not chosen now. Running two coexisting review commands (/code-review and /os-sdlc:review) is an explicit, intentional consequence of this decision, not an oversight.
|
||||
|
||||
## Alternatives rejected
|
||||
|
||||
Separate cc-sdlc marketplace/repo — rejected because the user confirmed it would be installed globally too, so a split buys no functional/loading separation, only a second repo/manifest to keep in sync, and it fragments coupling to os-backlog/os-adr/os-vault across repos against ADR-0023's cooperating-plugins bet. Enhancing the existing generic /code-review in place instead of a namespaced /os-sdlc:review — rejected because the two review skills serve genuinely different axes (correctness/reuse/simplification vs standards-doc conformance/spec-fidelity/refactor-smells) and the user explicitly chose coexistence over a merge.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: "0038"
|
||||
date: 2026-07-14
|
||||
status: Accepted
|
||||
supersedes:
|
||||
superseded-by:
|
||||
affected-paths: [plugins/os-doc-hygiene/rulebook.json, plugins/os-doc-hygiene/lifecycle-spec.md, plugins/os-doc-hygiene/scripts/]
|
||||
affected-components: [os-doc-hygiene]
|
||||
---
|
||||
|
||||
# 0038 — Per-project doc-hygiene rulebook override lives at repo-root .dochygiene-rules.json, not in .cc-os/
|
||||
|
||||
## Context
|
||||
|
||||
The lifecycle-aware doc hygiene design (wayfinder map #31, ticket #40) gives os-doc-hygiene a global rulebook shipped in the plugin (plugins/os-doc-hygiene/rulebook.json) plus a thin per-project override for exceptions (e.g. repos where specs are the product). cc-os also has a gitignored .cc-os/ directory used for local machine config (e.g. tracker keys), which raised the question of where the override belongs. The override participates in deletion decisions, so it must be reviewable, versioned, and shared by every collaborator and agent working the repo.
|
||||
|
||||
## Decision
|
||||
|
||||
The per-project override is a committed repo-root dotfile: .dochygiene-rules.json. It follows the existing .dochygiene-ignore precedent. Envelope {"schema_version": 1, "rules": [...]}; merged add-only over the global rulebook with source-then-specificity precedence (project file-rule > project directory-rule > global file-rule > global directory-rule; ties by longest pattern, then last-defined). A project neutralizes a global rule by shadowing it with lifetime "keep" — no rule-removal mechanism.
|
||||
|
||||
## Consequences
|
||||
|
||||
Rule overrides are visible in review and travel with the repo — a rule that can authorize deletion is never invisible local state. One more root dotfile per opted-in project. Because the merge is add-only, a bad global rule is neutralized by shadowing, not deleted, keeping merge semantics trivially predictable. Full schema: plugins/os-doc-hygiene/lifecycle-spec.md.
|
||||
|
||||
## Alternatives rejected
|
||||
|
||||
(1) .cc-os/dochygiene-rules.json — rejected: .cc-os/ is gitignored local config; deletion-authorizing rules must be committed, reviewable, and shared, not per-machine. (2) Global rulebook only, no override — rejected: repos where specs/plans are the product need exceptions (ticket #34). (3) YAML/TOML with comments — rejected in #40: JSON parses with stdlib in both the Python pipeline and any tooling; a per-rule note field substitutes for comments.
|
||||
|
||||
## Amendment (2026-07-15 — wayfinder map #49)
|
||||
|
||||
The rules file's scope now includes **nominations memory**: an optional second top-level key `nominations` with two sub-keys — `consults` (open judge questions that resurface each `:calibrate` run until answered) and `rejected` (declined nominations, judge- or human-declined, that block exact glob+lifetime repeats at nomination intake and travel to the judge as context for variants). The key is additive to schema_version 1 (the v1 loader ignores unknown top-level keys); `rulebook.py` stays nomination-unaware — only the calibrate pipeline reads it. Serialization of the whole file is writer-enforced canonical ordering (rules grouped delete-once-served → temporary → keep, glob-sorted within groups; `nominations` after `rules`, `consults` before `rejected`) — no hook. The global rulebook additionally carries a `**/extracted.md → keep` rule, protecting the per-directory extract-index convention (map #49 ticket #57) in every project with zero per-project bookkeeping. Detail: plugins/os-doc-hygiene/lifecycle-spec.md (§1 extract index; §2 nominations memory + canonical ordering); decision tickets #51–#53, #56, #57.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: "0039"
|
||||
date: 2026-07-14
|
||||
status: Accepted
|
||||
supersedes:
|
||||
superseded-by:
|
||||
affected-paths: [plugins/os-doc-hygiene/lifecycle-spec.md, plugins/os-doc-hygiene/scripts/, plugins/os-doc-hygiene/skills/]
|
||||
affected-components: [os-doc-hygiene]
|
||||
---
|
||||
|
||||
# 0039 — Doc-hygiene deletion autonomy is tiered on evidence quality and recoverability, not file type
|
||||
|
||||
## Context
|
||||
|
||||
Lifecycle rules let os-doc-hygiene:clean delete files (true git deletion in a dedicated hygiene commit; git history is the archive — ticket #35). The question (ticket #43) was what deletes silently versus what requires confirmation. Re-confirming every run would reduce the rulebook to an advisory list and strip :calibrate of its payoff; but an LLM judgment must never silently destroy a file, and untracked or dirty files have no history to recover from. /os-adr:find confirmed no existing ADR governed clean's safety tiers.
|
||||
|
||||
## Decision
|
||||
|
||||
Rule-backed deletes are auto — a rule confirmed per the strong-model gate (#39) is the standing consent. The auto/confirm line is drawn on evidence quality plus recoverability: tracked + clean worktree + deterministic signal = auto; tracked-but-dirty or untracked = confirm (verified at runtime via git ls-files + dirty check, never trusting the rule); classifier-judged served_when signals are ALWAYS forced to confirm regardless of tracked status, while scanner-proven served_when_path signals may delete silently. The temporary tier defaults to retain_recent 3 + max_age_days 3, age measured from git commit time falling back to mtime for untracked entries (directory-inode mtime, no recursive walk — #48). graphify-out/** and .dochygiene/** form an explicit IGNORE surface that is never walked — the ignore surface is its own list, never inferred from .gitignore (gitignored ≠ deletable, gitignored ≠ keepable). A per-rule confirm:true escape hatch exists but is human-settable only; a model-proposed rule may only ask a human to set it.
|
||||
|
||||
## Consequences
|
||||
|
||||
Routine hygiene runs unattended on the safe population while every non-recoverable or judgment-based deletion stops at a human. The auto/confirm line moves exactly where evidence quality changes, so improving a rule's evidence (see the determinism-promotion ADR) directly buys silence. 3-day/retain-3 retention is aggressive by design; the newest 3 entries per rule always survive to show current trajectory. Full tier matrix: plugins/os-doc-hygiene/lifecycle-spec.md.
|
||||
|
||||
## Alternatives rejected
|
||||
|
||||
(1) Confirm everything — rejected: rulebook becomes advisory; confirm-fatigue makes the human rubber-stamp. (2) Tier by file type or a recoverable_via/regenerate class — rejected: recoverability is a property of git state at runtime, not of the rule's claim. (3) 90-day retention windows — rejected as far too slow in the age of AI. (4) mtime-only age — rejected: clone/branch-switch resets every mtime, silently putting the whole rulebook to sleep. (5) Model-settable confirm:true — rejected: used too liberally, everything drifts back to always-confirm.
|
||||
|
||||
## Amendment (2026-07-15 — wayfinder map #49)
|
||||
|
||||
Tier interactions decided on map #49: (1) the "class, never path" rule-quality test is relaxed for the **keep tier only** — exact-path singleton keep rules are allowed (the test exists to prevent bad deletion rules; a singleton keep merely protects) and leave the rulebook only by hand-deletion, never an automated revisit: an automated revisit would be the one place a machine argues a keep back toward deletion, the exact failure mode the keep tier prevents. Instance globs stay forbidden for temporary/delete-once-served. (2) Nominations memory (ADR-0038 amendment) is memory, not deletion authority: rejections and consults never filter files and are not individually gated at persistence; rules alone authorize deletion. (3) Consults resurface in `:calibrate` only — `:check`/`:clean` are unchanged. Detail: plugins/os-doc-hygiene/lifecycle-spec.md §2/§8; decision tickets #51, #52, #56.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: "0040"
|
||||
date: 2026-07-14
|
||||
status: Accepted
|
||||
supersedes:
|
||||
superseded-by:
|
||||
affected-paths: [plugins/os-doc-hygiene/lifecycle-spec.md, plugins/os-vault/, docs/adr/0017-project-graph-onboarding-assesses-the-repo-and-writes-graphifyignore-before-extracting.md]
|
||||
affected-components: [os-doc-hygiene, os-vault, graphify]
|
||||
---
|
||||
|
||||
# 0040 — Doc-hygiene rules never write other tools' ignore surfaces (no propagate_ignore)
|
||||
|
||||
## Context
|
||||
|
||||
Ticket #44 asked whether a lifecycle rule marking files temporary/delete-once-served should also propagate into .graphifyignore (keeping disposable docs out of the session-start knowledge graph) and the scanner's own .dochygiene-ignore. The #40 rulebook schema had reserved a propagate_ignore field pending this decision.
|
||||
|
||||
## Decision
|
||||
|
||||
No propagation, ever: a lifecycle rule never writes into any other tool's ignore/config surface, and the propagate_ignore field is removed from the rulebook schema entirely (not left as an empty slot). .dochygiene-ignore remains a hand-authored, human-only escape hatch unmanaged by the rulebook. os-doc-hygiene stays in its own lane — separation of concerns.
|
||||
|
||||
## Consequences
|
||||
|
||||
The glob-dialect mismatch problem (rulebook glob.translate vs gitignore vs fnmatch) dissolves — no rule glob is ever translated into a foreign dialect. Delete-once-served files leave the knowledge graph at the next rebuild anyway, so the lost payoff was only the narrow temporary-but-not-yet-served window. If disposable files polluting the knowledge graph later proves painful, the fix belongs to graphify or os-vault:onboard-project, not a hygiene rule reaching across the seam. Noted while verifying: ADR-0017's premise that graphify does not honor .gitignore is stale — graphifyy 0.8.31 (detect.py:707-711) falls back to .gitignore per-directory where no .graphifyignore exists; this does not change ADR-0017's decision.
|
||||
|
||||
## Alternatives rejected
|
||||
|
||||
(1) Propagate into .dochygiene-ignore — moot, not rejected on principle: the rulebook itself is already the scanner's skip instruction (directory-rule matches prune the walk), so propagation would create two sources of truth for one fact in a weaker dialect (fnmatch) with no precedence rule. (2) Append to .graphifyignore — rejected on ownership and on ADR-0017: onboard-project generates that file per-project WITH user confirmation, and ADR-0017 explicitly rejected auto-writing it without confirmation; automatic appends would reinstate the rejected alternative and add a second writer with no arbiter. (3) Propagate to memsearch — not applicable: memsearch indexes its own ~/.memsearch/memory/ daily files and has no ignore surface for project docs.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: "0041"
|
||||
date: 2026-07-14
|
||||
status: Accepted
|
||||
supersedes:
|
||||
superseded-by:
|
||||
affected-paths: [plugins/os-doc-hygiene/conventions.json, plugins/os-doc-hygiene/lifecycle-spec.md, plugins/os-doc-hygiene/skills/]
|
||||
affected-components: [os-doc-hygiene]
|
||||
---
|
||||
|
||||
# 0041 — Determinism promotion: hygiene nudges projects toward structurally-obvious completion conventions
|
||||
|
||||
## Context
|
||||
|
||||
The deletion-autonomy tiers force every classifier-judged served_when signal to confirm (the LLM may propose, never silently destroy). Left there, projects with subjective completion signals would accumulate permanent confirm-fatigue, and the human would rubber-stamp gates. Tickets #43 (item 7) and #47 asked how the tool should respond when a rule's served signal is subjective.
|
||||
|
||||
## Decision
|
||||
|
||||
When a rule's served signal is classifier-judged, the tool does not merely downgrade it to confirm: it names the subjectivity and recommends a concrete structural convention that would graduate the rule to a scanner-proven served_when_path and make it silent. Conventions live in a global-only, machine-readable catalog at plugins/os-doc-hygiene/conventions.json so the deterministic pipeline emits nudges without an LLM; v1 holds exactly two entries: archive-bucket (done = file moved to a sibling archive/ dir, precedent: openspec changes) and status-frontmatter (done = a status: shipped|done frontmatter key). Surfacing splits by capability: :check names promotion candidates in every report; :calibrate may draft the adoption (graduated rule + file moves) for human approval but never applies it unasked.
|
||||
|
||||
## Consequences
|
||||
|
||||
Confirm-fatigue is converted into the incentive to fix the convention rather than a cost to endure — adopting a convention directly buys silent automation. The catalog only recommends; adoption lands in the project's own rulebook, so no per-project catalog override is needed. A two-entry catalog that gets adopted beats a taxonomy; successor-artifact checks stay out until a calibration pass demands one. The :check report schema gains a promotion-candidates section.
|
||||
|
||||
## Alternatives rejected
|
||||
|
||||
(1) Just downgrade subjective rules to confirm and stop — rejected: permanent confirm-fatigue with no exit path. (2) A rich taxonomy of completion conventions — rejected: adoption beats coverage; entries are added when calibration passes demand them. (3) Per-project catalog override — rejected: the catalog only recommends, so there is nothing project-specific to override. (4) :calibrate applies adoptions automatically — rejected: violates the model-proposes/human-disposes constraint from #42.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: "0042"
|
||||
date: 2026-07-16
|
||||
status: Accepted
|
||||
supersedes: "0033"
|
||||
superseded-by:
|
||||
affected-paths: [plugins/os-backlog/, .cc-os/config, docs/adr/0033-tracker-routing-planka-is-state-git-issues-are-specs.md, docs/adr/0029-os-backlog-card-move-with-cli-enforced-column-ownership-afk-ready-cards-skip-review.md, docs/implementation-status/os-backlog.md]
|
||||
affected-components: [os-backlog, to-issues, os-status, planka-api]
|
||||
---
|
||||
|
||||
# 0042 — Retire Planka: git issues are the single tracker for state and specs
|
||||
|
||||
## Context
|
||||
|
||||
ADR-0033 split tracking across two surfaces: Planka for task state, git issues for durable specs, conceding up front the cost of 'two tracking surfaces per code project.' A reassessment on 2026-07-16 (four-perspective subagent review + live board snapshot) found the cost never earned itself back. Empirically: since os-backlog finished building (2026-07-13), the only Planka board with any activity is cc-os itself — the plugin tracking its own construction; the one client board (philly-search-engine-marketing) froze 2026-07-09 with no card ever moved past Backlog/Waiting; all other boards hold 0-2 onboarding-era cards. All real work flowed through Forgejo/GitHub issues. Structurally: the issue-wakeup automation (ADR-0035/0036) already polls git issues, not Planka, so the surface wired into the loop that drives sessions was always the issue tracker; and the Planka integration rests on a reverse-engineered undocumented API (see vault note reference/planka-v2-api-gotchas.md: direct Postgres workarounds, no scoped tokens per upstream issue #945, an unpublished lockstep gem) — a standing maintenance tax with no team to absorb it. The one workload Planka was designed for (non-repo, human-curated work) went untested, but the user has independently concluded client management belongs in dedicated tools (Invoice Ninja et al.), making that use case untestable by construction.
|
||||
|
||||
## Decision
|
||||
|
||||
Retire Planka from the operating layer. Git issues (Forgejo via tea, GitHub via gh, or in-repo via repo:<path>) become the single tracker for BOTH task state and durable specs. The tracker key grammar in .cc-os/config drops planka:<board>; forgejo:/github:/repo: remain. Task state is modeled with issue labels: priority (P0-P3) and autonomy (hitl/semi/afk-ready) labels carry over unchanged; column semantics collapse to open/closed plus a small state-label set, with human curation expressed as a human-applied 'next' label (AI never applies or removes it — preserving ADR-0029's human-curation intent without its Planka mechanics). Non-repo/process/ops work gets a home as issues in a private ops repo (repo:- or forgejo:-tracked) rather than a kanban board. ADR-0029's CLI-enforced column-ownership rules governed a surface this decision removes and are retired with it; its durable content (autonomy-label semantics, human-owned curation gate) is re-expressed in the label scheme above. The Planka server decommission, planka-api gem archival, and os-backlog plugin rework are executed via an OpenSpec change; existing live cards are migrated to issues or closed with human sign-off on the client board.
|
||||
|
||||
## Consequences
|
||||
|
||||
Easier: one tracking surface per project (the card-as-pointer and promotion rules of ADR-0033 become unnecessary and are dropped); the wakeup path and the tracking surface are now the same system; the os-backlog plugin sheds the planka-api gem dependency, board_ensurer/board_resolver/board_spec and most of cards.rb, the card-triage and board-audit agents, bot-credential lifecycle, and the name-only board-lookup defect; no self-hosted tracker server to patch and back up; the SessionStart process-rules note shrinks. Harder: no kanban/WIP visualization — a unified dashboard over git issues is explicitly deferred until it is a felt pain point; cross-column state expressiveness is reduced to labels; the untested non-repo/human-curated workload loses its designated surface and must prove out in an ops repo (revisit if that chafes); the retirement itself costs a migration pass over ~30 live non-Done cards and a plugin rework.
|
||||
|
||||
## Alternatives rejected
|
||||
|
||||
1) Keep ADR-0033 as-is — rejected: one week of maximally favorable live use produced zero organic pull toward the second surface, while the conceded two-surface cost (plus server, gem, credentials) kept accruing; the design's own automation already bypassed Planka. 2) Keep Planka dormant-but-installed as a hedge — rejected unanimously by all four review perspectives: dormant infra keeps the maintenance surface of 'kept' (unpatched server holding live bot credentials, gem rot, session-note tokens) while returning the value of 'removed'; if a board is ever wanted again, rebuild against the pain point that actually materializes. 3) Keep a single scoped Planka board for ops/recurring work only — rejected: the entire server+gem+credential stack would persist to serve one recurring card that an ops-repo issue models fine. 4) Columns-as-labels inside Planka — rejected: changes vocabulary, removes none of the infrastructure; the valid kernel (label-modeled state) is adopted on the git-issues side instead.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: "0043"
|
||||
date: 2026-07-17
|
||||
status: Accepted
|
||||
supersedes:
|
||||
superseded-by:
|
||||
affected-paths: [plugins/os-context/prompts/session-start/10-orchestration.md, plugins/os-context/skills/audit-sessions/references/rubric.md]
|
||||
affected-components: [os-context, audit-sessions]
|
||||
---
|
||||
|
||||
# 0043 — Absolute delegation policy: main loop as executive with enumerated exemptions
|
||||
|
||||
## Context
|
||||
|
||||
The shipped os-context orchestration rules (prompts/session-start/10-orchestration.md, wording tuned in the WS4 eval loop) gated delegation on a per-task judgment: delegate when a mechanical sequence exceeds ~3 tool calls, work directly when the chain is short, judgment-dependent, or interactive. IRL transcript review (2026-07-17) showed the failure mode of that frame: the main-loop model must predict chain length and follow-up probability at decision time, both unknowable, and the errors land systematically on the keep-it-in-session side. Each 'short chain' decision is locally defensible, is re-made fresh with no memory of accumulated cost, and one chain reliably leads to another — sessions blow past 100k tokens of carried tool results on a fable/opus-tier main loop. Judgment-shaped rules are also expensive to audit: /os-context:audit-sessions had to judge judgment calls rather than count violations.
|
||||
|
||||
## Decision
|
||||
|
||||
Delegation is the default, not a threshold call. The main loop acts as the session's executive: it interprets the user, plans, delegates, verifies, and reports — it never does grunt work, regardless of how short the coming chain looks. Direct work is limited to an enumerated, mechanical exemption list (user-named file reads; diff/verification review before sign-off; single state-inspection commands; acts only the main loop can perform). Grunt work routes to ONE persistent sonnet manager agent spawned at the first delegable work and continued via SendMessage, not spawn-per-errand; fresh spawns only for genuinely parallel independent work. Tier routing: haiku for fully specified mechanical batches; sonnet as default manager tier; opus entered on observed failure (sonnet wrong or stuck twice on the same problem) rather than forecast, plus judgment-dense end-to-end work. Verification stays executive: the main loop reads the diff, not the report of the diff.
|
||||
|
||||
## Consequences
|
||||
|
||||
Easier: main-loop context stays small, extending session lifetime before compaction/clear; the audit rubric becomes largely countable (non-exempt main-loop tool calls are violations) instead of interpretive; the economics stop depending on per-episode forecasts. Harder: trivial errands pay delegation overhead (spawn prompt, round-trip latency, report) that direct work would not; interactive tight-loop troubleshooting pays a wall-clock tax per round-trip since the old interactive carve-out was deliberately dropped as judgment-shaped; a main loop that never touches the repo can drift toward vague direction, mitigated by the exemption allowing grounding reads of user-named artifacts and by diff review. The WS4 eval sets validated the old wording and are contaminated, so behavioral validation of the new wording comes only from future IRL session audits.
|
||||
|
||||
## Alternatives rejected
|
||||
|
||||
1) Keep the per-task threshold heuristic (status quo): rejected — requires predicting chain length, which is unknowable at decision time and biased toward direct work; empirically produced 100k+-token main-loop sessions. 2) Cumulative session-budget trigger (delegate once main-loop tool-result tokens pass a budget): rejected as primary rule — better than per-task forecasting but still lets early episodes accrete and still needs a judged threshold; absorbed instead as the rationale (every byte re-billed per turn) rather than the mechanism. 3) Four-tier standing hierarchy with forecast routing (fable→opus→sonnet→haiku, choosing tier by predicted difficulty): rejected — 'does this need opus?' is the same unfalsifiable forecast the policy bans, and deep chains lose intent fidelity; replaced by failure-triggered escalation, which is observable. 4) Evaluative exemptions ('unless it's quick'): rejected — any judgment-shaped exemption reintroduces the broken prediction; exemptions must be enumerable.
|
||||
|
|
@ -39,8 +39,15 @@ One file per decision, `NNNN-kebab-title.md`, created via `/os-adr:create`.
|
|||
| 0030 | [Read-only work uses Claude Code plan mode; no /readonly shortcut command](0030-read-only-work-uses-claude-code-plan-mode-no-readonly-shortcut-command.md) | Accepted | 2026-07-13 |
|
||||
| 0031 | [os-context prompt-composer plugin supersedes single-file os-orchestration](0031-os-context-prompt-composer-plugin-supersedes-single-file-os-orchestration.md) | Accepted | 2026-07-13 |
|
||||
| 0032 | [AI-maintained knowledge files are indexes with progressive disclosure](0032-ai-maintained-knowledge-files-are-indexes-with-progressive-disclosure.md) | Accepted | 2026-07-13 |
|
||||
| 0033 | [Tracker routing: Planka is state, git issues are specs](0033-tracker-routing-planka-is-state-git-issues-are-specs.md) | Accepted | 2026-07-13 |
|
||||
| 0033 | [Tracker routing: Planka is state, git issues are specs](0033-tracker-routing-planka-is-state-git-issues-are-specs.md) | Superseded | 2026-07-13 |
|
||||
| 0034 | [Cross-project filing: file-dont-fix with a derived global project index](0034-cross-project-filing-file-dont-fix-with-a-derived-global-project-index.md) | Accepted | 2026-07-13 |
|
||||
| 0035 | [Issue-triggered project AI wakeup via polling, not webhooks](0035-issue-triggered-project-ai-wakeup-via-polling-not-webhooks.md) | Accepted | 2026-07-13 |
|
||||
| 0036 | [tmux session convention for AI-run sessions: cc-<project>-<purpose>](0036-tmux-session-convention-for-ai-run-sessions-cc-project-purpose.md) | Accepted | 2026-07-13 |
|
||||
| 0037 | [os-sdlc lives inside cc-os as a new plugin, not a separate cc-sdlc marketplace](0037-os-sdlc-lives-inside-cc-os-as-a-new-plugin-not-a-separate-cc-sdlc-marketplace.md) | Accepted | 2026-07-14 |
|
||||
| 0038 | [Per-project doc-hygiene rulebook override lives at repo-root .dochygiene-rules.json, not in .cc-os/](0038-per-project-doc-hygiene-rulebook-override-lives-at-repo-root-dochygiene-rules-json-not-in-cc-os.md) | Accepted | 2026-07-14 |
|
||||
| 0039 | [Doc-hygiene deletion autonomy is tiered on evidence quality and recoverability, not file type](0039-doc-hygiene-deletion-autonomy-is-tiered-on-evidence-quality-and-recoverability-not-file-type.md) | Accepted | 2026-07-14 |
|
||||
| 0040 | [Doc-hygiene rules never write other tools' ignore surfaces (no propagate_ignore)](0040-doc-hygiene-rules-never-write-other-tools-ignore-surfaces-no-propagate-ignore.md) | Accepted | 2026-07-14 |
|
||||
| 0041 | [Determinism promotion: hygiene nudges projects toward structurally-obvious completion conventions](0041-determinism-promotion-hygiene-nudges-projects-toward-structurally-obvious-completion-conventions.md) | Accepted | 2026-07-14 |
|
||||
| 0042 | [Retire Planka: git issues are the single tracker for state and specs](0042-retire-planka-git-issues-are-the-single-tracker-for-state-and-specs.md) | Accepted | 2026-07-16 |
|
||||
| 0043 | [Absolute delegation policy: main loop as executive with enumerated exemptions](0043-absolute-delegation-policy-main-loop-as-executive-with-enumerated-exemptions.md) | Accepted | 2026-07-17 |
|
||||
<!-- adr-index:end -->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# cc-os implementation status — index
|
||||
|
||||
_Last updated: 2026-07-13. This file is an **index with progressive disclosure** (see the
|
||||
_Last updated: 2026-07-17. This file is an **index with progressive disclosure** (see the
|
||||
index+PD convention ADR): headline timeline entries and per-component one-liners live here;
|
||||
full per-component detail lives in leaf files under `docs/implementation-status/`, read on
|
||||
demand. When a build step completes, add a **one-line** timeline entry here and put the
|
||||
|
|
@ -61,13 +61,38 @@ entry is in the leaf file named.
|
|||
(ADR-0031) + three new prompt files; `/readonly` decided as plan-mode convention, no
|
||||
skill (ADR-0030); os-doc-hygiene `file_length` scanner signal shipped (issue #25 part 1);
|
||||
this file distilled to index+PD (issue #25 parts 2–3, ADR-0032). os-backlog slice 7
|
||||
shipped: SessionStart injection note + /to-issues tracker routing (issue #16); board-id
|
||||
shipped: SessionStart injection note + /to-tickets tracker routing (issue #16); board-id
|
||||
cache via BoardResolver (issue #22); slice 8 — Operations board + tracker-routing rubric
|
||||
category + ADR-0033 canonizing Planka-state/git-issues-spec (issue #17); route inspect
|
||||
issue-shape classification + split-by-kind default (issue #26); cross-project filing
|
||||
convention + derived global project index (issue #27, ADR-0034); wakeup-polling +
|
||||
tmux-convention spike with dry-run pilot (issue #28, ADR-0035/0036). Detail:
|
||||
os-context / os-doc-hygiene / os-backlog leaves.
|
||||
- **2026-07-14** — os-sdlc plugin scaffolded (ADR-0037): new plugin inside cc-os (not a
|
||||
separate marketplace) for a harness-driven SDLC lifecycle, adapting Matt Pocock's v1.1
|
||||
skill lifecycle and Delta Refinery's multi-level pipeline; registered in local-plugins,
|
||||
no skills/agents/hooks implemented yet. Launching-point doc: `plugins/os-sdlc/OVERVIEW.md`;
|
||||
full design deferred to a follow-up brainstorming session.
|
||||
- **2026-07-15** — os-doc-hygiene lifecycle-aware hygiene shipped (ADR-0038–0041): rulebook
|
||||
layer, lifetime taxonomy + tier matrix, no-ignore-propagation, conventions.json promotion,
|
||||
new `:calibrate` skill; calibration pass #1 (cc-os) passed. Detail: os-doc-hygiene leaf.
|
||||
- **2026-07-16** — Planka retired (ADR-0042 supersedes ADR-0033): os-backlog reworked to
|
||||
git-issues-only via OpenSpec change `retire-planka-git-issues-only` — Planka lib/CLI/agents
|
||||
deleted, issue-create/issues helpers added, skills + SessionStart note rewritten, cards
|
||||
migrated (cc-os → jared/cc-os issues; philly board → jared/ops + jared/llf-schema;
|
||||
strays → new private jared/ops repo), boards snapshotted + pg-dumped (archive:
|
||||
ovh-vps `~/planka-final-snapshot-2026-07-16/`). Server decommissioned same day
|
||||
(containers/cron/creds removed); planka gem repo archived read-only. Detail:
|
||||
os-backlog leaf.
|
||||
- **2026-07-16** — os-* standards audit vs cc-architect (post Phase B): every plugin got an
|
||||
index-style CLAUDE.md, os-doc-hygiene's cc-plugins-era docs reconciled, invariants.md
|
||||
added (os-shortcuts, os-vault), naming convention folded into cc-architect references,
|
||||
repo-level `bin/test` runner added. Detail:
|
||||
[implementation-status/standards-audit.md](implementation-status/standards-audit.md).
|
||||
- **2026-07-17** — Absolute delegation policy shipped (ADR-0043): `10-orchestration.md`
|
||||
rewritten (executive frame, enumerated exemptions, persistent sonnet manager,
|
||||
failure-triggered opus escalation); audit rubric + `extract` driver updated to match;
|
||||
WS4 eval retired as instrument, kept as template. Detail: os-context leaf.
|
||||
|
||||
**Remaining optional items:** additional project onboarding (one at a time, per ADR-0013);
|
||||
bulk vault migration; os-adr rollout to pilot projects; os-backlog routing rollout (#14);
|
||||
|
|
@ -93,8 +118,9 @@ Read the leaf file before modifying a component.
|
|||
- **os-adr** — ADR system (Ruby `lib/adr/` + CLIs; create/init/migrate/find skills; Eval
|
||||
A/B/C harnesses; cc-os retrofit done) →
|
||||
[implementation-status/os-adr.md](implementation-status/os-adr.md)
|
||||
- **os-backlog** — Planka process surface: board lifecycle, capture/list/route skills,
|
||||
card CLI with column-ownership rules, triage/audit agents →
|
||||
- **os-backlog** — git-issues backlog surface (ADR-0042 retired Planka 2026-07-16):
|
||||
tracker routing (`forgejo:`/`github:`/`repo:`), issue-create/issues CLI,
|
||||
capture/list/route skills, ten-label taxonomy with human-only `next` →
|
||||
[implementation-status/os-backlog.md](implementation-status/os-backlog.md)
|
||||
- **os-shortcuts** — user-invoked QOL commands, skills only, never hooks (ADR-0028);
|
||||
resident: `/os-shortcuts:wrap` session-close ritual (shipped 2026-07-12). No leaf file
|
||||
|
|
|
|||
|
|
@ -3,42 +3,41 @@
|
|||
_Leaf file of [../implementation-status.md](../implementation-status.md). Read on demand._
|
||||
|
||||
**Global os-backlog plugin** — `cc-os/plugins/os-backlog/` (git-tracked, 2026-07-10);
|
||||
symlinked into `~/.claude/plugins/os-backlog`; PRD Forgejo issue #9 (state pointer: Planka
|
||||
card `1816234194716592051`), slices #10–#17. Decision records: ADR-0023 (own plugin, not an
|
||||
os-vault extension), ADR-0025 (standard Ruby plugin structure), ADR-0029 (card-move +
|
||||
column ownership).
|
||||
symlinked into `~/.claude/plugins/os-backlog`; PRD Forgejo issue #9, slices #10–#17.
|
||||
Decision records: ADR-0023 (own plugin, not an os-vault extension), ADR-0025 (standard Ruby
|
||||
plugin structure), ADR-0029 (column ownership — subject removed by ADR-0042), **ADR-0042
|
||||
(2026-07-16, supersedes ADR-0033): Planka retired; git issues are the single tracker for
|
||||
state and specs** (OpenSpec change `retire-planka-git-issues-only`).
|
||||
|
||||
## Component inventory
|
||||
## Component inventory (post-ADR-0042, v0.3.0)
|
||||
|
||||
- Purpose: the AI's process-management surface over Planka — deterministic tracker routing
|
||||
(Planka = task state by default; git issues = implementation specs only), mid-session
|
||||
capture, pull-only listing, board lifecycle, and the three-value autonomy convention
|
||||
(`hitl`/`semi`/`afk-ready`; amended per ADR-0029: afk-ready ships straight to Done, semi
|
||||
stops at Review).
|
||||
- Lib (Ruby, os-adr `lib/`+`bin/` pattern, installed planka-api gem only — no source
|
||||
coupling): `board_spec.rb` (uniform lists/labels contract), `board_ensurer.rb`
|
||||
(idempotent create/repair; Planka 2.1.1 `type` fields, shared-project + owner-manager
|
||||
visibility fix, label color candidates with fallback; `archived--` rename convention),
|
||||
`config.rb` (`.cc-os/config` parser), `resolver.rb` (pure repo→board decision:
|
||||
`use`/`activate`/`stop-and-discuss`; no network), `cards.rb` (add-at-Backlog, snapshot,
|
||||
attach_label, comment, move with ownership rules), `tracker.rb` + `inspector.rb`
|
||||
(2026-07-12, issue #14 — tracker-format validation/remote classification + in-repo
|
||||
issue-file discovery; `Config.merge` preserves other keys).
|
||||
- CLI: `bin/os-backlog` — `board-ensure`, `activate`, `archive`, `resolve`, `card-add`,
|
||||
`cards`, `snapshot`, `card-label`, `card-comment`, `card-move` (2026-07-12, ADR-0029),
|
||||
`inspect`, `config-write`; every path fails soft (one-line error, exit 1) when the
|
||||
gem/Planka is unavailable.
|
||||
- Skills: `/os-backlog:capture`, `/os-backlog:list` (pull-only per notification policy v2),
|
||||
`/os-backlog:route` (2026-07-12, issue #14 — tracker onboarding conversation; destination
|
||||
choice + live-history migration are named human gates) — carry the column-ownership rules
|
||||
(AI creates at Backlog, Doing→Review at most, never Next, never self-assigns `hitl`;
|
||||
"working a card means moving it" contract added 2026-07-12).
|
||||
- Agents: `agents/card-triage.md` (priority + autonomy labels only; ambiguity → `hitl`),
|
||||
`agents/board-audit.md` (four drift classes; writes at most comments).
|
||||
- Tests: `tests/` — 68 runs/153 assertions as of 2026-07-12 (35 → 54 → 58 → 68 across the
|
||||
slices) against an in-memory `FakePlankaClient`; no live API. Companion os-status check
|
||||
`tracker-configured` (issue #11) validates the `tracker` key grammar and nudges
|
||||
unconfigured git projects (daily-snoozed).
|
||||
- Purpose: the AI's process-management surface over git issues — deterministic tracker
|
||||
routing (`forgejo:<owner>/<repo>` | `github:<owner>/<repo>` | `repo:<path>`; `planka:`
|
||||
rejected fail-soft citing ADR-0042), mid-session capture as labeled issues, pull-only
|
||||
listing, cross-project filing via the global project index.
|
||||
- State model: open/closed + labels. Ten canonical labels: priority `P0`–`P3`; autonomy
|
||||
`hitl`/`semi`/`afk-ready` (semi ships to `review` label and stays open; afk-ready closes
|
||||
after verification); state `next` (human-only — AI never applies/removes), `waiting`
|
||||
(+ blocker comment), `review`. No `doing`/`done` labels: in-progress = assignment/branch
|
||||
activity, done = closed. Recurring-convention issues (e.g. `jared/ops#1` biweekly audit)
|
||||
are commented per occurrence and never closed by the AI.
|
||||
- Lib (Ruby): `tracker.rb` (grammar validation + `issues_destination`: `:git_issues` /
|
||||
`:repo_files` / `:unrouted`), `issues.rb` (2026-07-16 — capture/list dispatch: `tea` for
|
||||
forgejo, `gh` for github, `NNN-<slug>.md` frontmatter files for `repo:`; injected runner,
|
||||
no state labels reachable through `create`), `inspector.rb` (issue-shape classification),
|
||||
`config.rb`, `resolver.rb` (reduced to tracker-presence decision), `project_index.rb`,
|
||||
`wakeup.rb`.
|
||||
- CLI: `bin/os-backlog` — `resolve`, `inspect`, `config-write`, `projects`, `issue-create`
|
||||
(`--title/--body/--priority`), `issues` (grouped next/waiting/review/other). Fail-soft
|
||||
one-liners throughout.
|
||||
- Skills: `/os-backlog:capture` (issue-create; never state labels, never `next`),
|
||||
`/os-backlog:list` (pull-only), `/os-backlog:route` (destination proposal + idempotent
|
||||
ten-label ensure via tea/gh). SessionStart note rewritten 2026-07-16 (~444 tokens,
|
||||
down from ~687 measured): capture / routing / cross-project / human-only-`next` /
|
||||
working-state / autonomy / recurring rules.
|
||||
- Tests: 88 runs / 203 assertions (2026-07-16) with fake CLI runners; no live API.
|
||||
Companion os-status `tracker-configured` check validates the reduced grammar and rejects
|
||||
`planka:` with the ADR-0042 advisory.
|
||||
|
||||
## Build and verification history
|
||||
|
||||
|
|
@ -73,8 +72,8 @@ column ownership).
|
|||
(`hooks/session_start.py` + `hooks/hooks.json`, os-adr-style plugin-local hook): WHEN→THEN
|
||||
rules for capture / routing / promotion / column ownership / autonomy labels, ADR-0029
|
||||
semantics (the issue body's "afk-ready → Review" was wrong — caught in Codex review),
|
||||
rules only, zero board state, ~447 tokens. `/to-issues` (global skill,
|
||||
`~/.agents/skills/to-issues/`) gained a destination-tracker step reading the
|
||||
rules only, zero board state, ~447 tokens. `to-issues` (now `to-tickets`; global skill,
|
||||
then at `~/.agents/skills/to-issues/`) gained a destination-tracker step reading the
|
||||
`.cc-os/config` tracker key; the `planka:`/unset default publishes slices as git issues
|
||||
on the repo remote + one Planka pointer card (spec/state boundary preserved — human gate
|
||||
decided 2026-07-13). `Backlog::Tracker.issues_destination` + 6 routing tests.
|
||||
|
|
@ -89,7 +88,7 @@ column ownership).
|
|||
IRL audit (recurring)" (card `1818310715438531668`, P2 + semi, first progress comment
|
||||
`1818310889929966683`; recurrence contract: comment + move back to Backlog, never Done).
|
||||
Tracker-routing convention canonized as **ADR-0033** (Planka = state / git issues = specs,
|
||||
tracker key grammar, pointer + promotion rules; cites route/to-issues/session-start
|
||||
tracker key grammar, pointer + promotion rules; cites route/to-tickets/session-start
|
||||
rather than restating). os-context audit rubric gained category 8 `tracker-routing`
|
||||
(capture/route/promote compliance only). Bug found live and fixed: label color fallback
|
||||
only rescued `Planka::ValidationError`, but Planka rejects unknown colors as HTTP 400
|
||||
|
|
@ -135,5 +134,28 @@ column ownership).
|
|||
Known residual (captured as a Backlog card): board lookup is name-only across ALL Planka
|
||||
projects — duplicate board names in different projects can resolve to the wrong board.
|
||||
Suite 138 runs / 295 assertions / 0 failures.
|
||||
- **Planka retirement rework (2026-07-16, ADR-0042, OpenSpec change
|
||||
`retire-planka-git-issues-only`):** after a week live, all real work flowed through git
|
||||
issues while boards held only the plugin's own build cards — Planka retired. Deleted:
|
||||
`board_ensurer`/`board_resolver`/`board_spec`/`cards`/`triage_check` (+ triage hook),
|
||||
both agents, planka-api gem wiring, `FakePlankaClient`, nine card/board CLI subcommands.
|
||||
Added: `issues.rb` + `issue-create`/`issues` CLI (live-verified; fixed tea field
|
||||
`assignees` — `assignee` is not a valid tea list field). Skills + SessionStart note +
|
||||
`/to-issues` + os-status fix skill rewritten to the single-tracker model. Migration:
|
||||
all 9 boards snapshotted (JSON + comments) + Postgres dump on ovh-vps; snapshot archived
|
||||
in new private `jared/ops` Forgejo repo (canonical labels; recurring audit issue
|
||||
`jared/ops#1`); cc-os non-Done cards → issues jared/cc-os#61–70 (3 skipped: pointer to
|
||||
closed #25, duplicate of open #8, obsoleted board-lookup card); ovh-prod + ruby-gems
|
||||
strays → jared/ops#2–3; `planka` board's 18 cards obsolete (gem build history, covered
|
||||
by snapshot); `tracker=` rewritten in all 6 indexed projects (no `planka:` values
|
||||
remain). Suite 88 runs / 203 assertions; os-status 80 tests.
|
||||
- **Human gates closed (2026-07-16):** philly per-card pass done — 4 repo cards →
|
||||
jared/llf-schema#7–10, 18 client cards → jared/ops#4–21, guardrails pin closed (vault
|
||||
note is the record). Decommission done same day: bot creds deleted, Planka
|
||||
containers/cron/service dir removed from ovh-vps (snapshot + pg dump + service configs
|
||||
kept at `~/planka-final-snapshot-2026-07-16/`), github.com/jaredswanson/planka archived
|
||||
read-only with an ADR-0042 README banner.
|
||||
- **Outstanding:** #14 residual (onboard one more project — operational hitl); wakeup
|
||||
rollout (first opt-in project + poller machine + cadence).
|
||||
rollout (first opt-in project + poller machine + cadence); jared/cc-os#74 — fold
|
||||
ADR-0025 compliance into os-backlog (bin/wakeup-poll second entry point, README gem
|
||||
deps), filed by the standards audit.
|
||||
|
|
|
|||
|
|
@ -108,3 +108,19 @@ the rename refer to the plugin by its former name.
|
|||
decision, never a continuation of the main loop's review thread (issue #30). Applied
|
||||
directly (design pre-approved, no wording-loop re-run); `bin/refresh-plugins` run after
|
||||
the edit.
|
||||
- **Absolute delegation policy (2026-07-17, ADR-0043):** IRL transcript review showed the
|
||||
per-task delegation threshold fails by induction — chain length is unknowable at decision
|
||||
time, each "short chain" call is locally defensible, and sessions accrete 100k+ tokens of
|
||||
main-loop tool results. `10-orchestration.md` rewritten (design pre-approved in session):
|
||||
executive frame, enumerated E-a..E-d exemptions (no evaluative carve-outs), one persistent
|
||||
sonnet manager continued via SendMessage, opus escalation on observed failure only.
|
||||
Audit rubric rewritten to match, shaped by a sonnet perspectives fan-out (measurement +
|
||||
devils-advocate): exemption decision procedure with recorded steps per finding, new
|
||||
`exemption-chaining` category, `over-delegation` narrowed to `delegation-overhead-mismatch`
|
||||
(not deleted), old defenses demoted to LOW/`accepted-cost` severity modifiers,
|
||||
`missed-delegation/read-write` sub-tag. `audit/bin/extract` gains `read_write_profile`
|
||||
(total main-loop Read/Write/Edit incl. non-consecutive — largest prior session: 67 total,
|
||||
only 14 visible to the run detector) and drops its stale pro-direct-work trailer. Eval
|
||||
E1–E3/E5 retired as a measurement instrument (README banner): sets contaminated AND
|
||||
scenarios encode the superseded policy; kept as harness template. Validation of the new
|
||||
wording comes only from future IRL audits.
|
||||
|
|
|
|||
|
|
@ -29,3 +29,31 @@ _Leaf file of [../implementation-status.md](../implementation-status.md). Read o
|
|||
`check`/`clean`; the `commands/hygiene.md` dispatcher was removed in favor of two new
|
||||
skills (`status`, `sweep`), aligning with the no-`commands/`-directory pattern
|
||||
(2026-07-03).
|
||||
- **Lifecycle-aware hygiene (2026-07-15, ADR-0038–0041)** — adds a rulebook layer on top of
|
||||
the existing stale/bloat scanner: a global `rulebook.json` plus an optional per-project
|
||||
`.dochygiene-rules.json` declare lifetime rules for known-temporary artifacts (ADR-0038).
|
||||
Rules classify matched paths into the lifetime taxonomy and drive a tier matrix from
|
||||
scanner-proven+tracked+clean (`auto`) down to classifier-judged (`confirm`) (ADR-0039); an
|
||||
IGNORE sentinel prunes matched paths with zero emission and does not propagate ignore
|
||||
status to children (ADR-0040). Classifier-judged matches that recur are surfaced
|
||||
deterministically as `promotion_candidates` from `conventions.json`, not model-authored
|
||||
(ADR-0041). New `/os-doc-hygiene:calibrate` skill runs the cluster/nominate/judge/
|
||||
human-report protocol to tune rules against a real project. Calibration pass #1 (cc-os)
|
||||
ran 2026-07-14/15 and passed both the protected-set hard gate and the recall floor across
|
||||
all 4 mandatory rows; rule persistence to `.dochygiene-rules.json` is pending human
|
||||
approval. Results: `plugins/os-doc-hygiene/openspec/changes/lifecycle-aware-doc-hygiene/calibration-pass-1-results.md`.
|
||||
Design: `plugins/os-doc-hygiene/lifecycle-spec.md`. Suite: 407 passed.
|
||||
- **Calibrate assessment inventory implemented (2026-07-15, map #49 / ADR-0038-0039
|
||||
amendments)** — the locked nominations-memory design is now code: `RulesFileWriter`
|
||||
(single canonical serialization path for `.dochygiene-rules.json`; tier-grouped,
|
||||
glob-sorted, idempotent, unknown fields warn-and-round-trip) and `NominationIntakeFilter`
|
||||
(deterministic Step 3.5 — exact glob+lifetime repeats dropped, variants annotated via
|
||||
shortlist match-set intersection, open consults always forwarded) in
|
||||
`calibrate_helpers.py`; calibrate SKILL.md Steps 3.5/5/6 wired (Open-consults report
|
||||
section with the three exits; keep verdicts, human declines, and consults all persist
|
||||
through the writer); `patch_applier.py` extract-then-delete appends the `extracted.md`
|
||||
vault pointer (keyed off `extraction_target: "cross-repo"` + a skill-supplied
|
||||
`extraction_pointer`, fail-closed: no pointer → no delete); clean SKILL.md Step 6.5
|
||||
supplies the pointer metadata. OpenSpec change:
|
||||
`plugins/os-doc-hygiene/openspec/changes/calibrate-assessment-inventory/`. Suite: 434
|
||||
passed (26 new unit tests across writer/intake-filter/applier seams).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
# os-* standards audit vs cc-architect — 2026-07-16
|
||||
|
||||
One-time audit executed after cc-architect's Phase B migration into cc-os (commit
|
||||
3f76755), per the backlog ticket "Audit os-* plugins against cc-architect standards
|
||||
(post standards-centralization)". Ran cc-architect's `workflows/audit-plugin.md`
|
||||
Phase 1–2 checklist against all eight os-* plugins, then remediated.
|
||||
|
||||
## Results
|
||||
|
||||
All eight plugins pass manifest validation (name/version/description, kebab-case,
|
||||
semver), have no `name:` fields in SKILL.md frontmatter, and have no `commands/`
|
||||
dispatcher directories.
|
||||
|
||||
Remediation shipped:
|
||||
|
||||
- **Per-plugin CLAUDE.md indexes** (ADR-0032 index+PD style) created for os-adr,
|
||||
os-backlog (minimal — Planka-retirement rework in flight), os-context, os-sdlc,
|
||||
os-shortcuts, os-status, os-vault; os-doc-hygiene's stale pre-build cc-plugins-era
|
||||
CLAUDE.md rewritten.
|
||||
- **os-doc-hygiene cc-plugins references reconciled**: PRD.md target path and
|
||||
marketplace naming corrected; broken `../.claude/references/` path and retired
|
||||
`/install doc-hygiene@cc-plugins` instructions removed.
|
||||
- **invariants.md** added for os-shortcuts (4 invariants) and os-vault (8); already
|
||||
existed for os-adr, os-doc-hygiene, os-status. os-backlog deferred (rework in
|
||||
flight); os-sdlc/os-context not warranted yet.
|
||||
- **ADR-0025 / os-adr**: no action — the ADR itself records the per-verb-`bin/`
|
||||
exemption ("grandfathered... until os-adr is next touched for other reasons").
|
||||
- **Naming convention** folded into the repo:
|
||||
`plugins/cc-architect/references/conventions/cc-os-naming.md` is now canonical;
|
||||
the SecondBrain vault note and root CLAUDE.md point to it.
|
||||
- **Repo-level test runner**: `bin/test` runs every plugin's minitest
|
||||
(`tests/all.rb`) and pytest suite, one pytest invocation per plugin (avoids
|
||||
same-named `hook_test.py` module collisions). os-vault's `tests/` is an eval
|
||||
harness, deliberately excluded.
|
||||
- **os-vault hook wiring documented**: hooks are intentionally wired via user-level
|
||||
`~/.claude/settings.json` pointing at repo source scripts, not a plugin
|
||||
`hooks/hooks.json`; recorded in os-vault/CLAUDE.md so the absent hooks.json stops
|
||||
reading as a defect.
|
||||
|
||||
## Deferred (filed, not fixed)
|
||||
|
||||
- os-backlog ADR-0025 deviations (second entry point `bin/wakeup-poll`; no README
|
||||
naming gem deps) → Forgejo issue jared/cc-os#74, to be folded into the
|
||||
retire-planka-git-issues-only rework.
|
||||
- os-context `skills/audit-sessions/SKILL.md` stale Planka references and os-status
|
||||
`planka:` tracker-scheme handling — both already being fixed by the in-flight
|
||||
ADR-0042 rework on main; not touched here to avoid conflicts.
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
# Issue workflow (Forgejo + tea)
|
||||
|
||||
_Last updated: 2026-06-30._
|
||||
_Last updated: 2026-07-16._
|
||||
|
||||
How issues created by the `/to-issues` skill get tracked and implemented in this repo.
|
||||
Note (2026-07-16): the `/to-issues` skill was renamed to `/to-tickets` in Matt Pocock's v1.1 skill set.
|
||||
|
||||
How issues created by the `/to-tickets` skill get tracked and implemented in this repo.
|
||||
|
||||
## Tracker
|
||||
|
||||
|
|
@ -33,7 +35,7 @@ tea issues <N> --repo jared/cc-os # view one issue's body
|
|||
|
||||
## Implement
|
||||
|
||||
There is **no auto-trigger and no `/grab` command**. Issues from `/to-issues` are written as independently-grabbable tracer-bullet slices. To implement:
|
||||
There is **no auto-trigger and no `/grab` command**. Issues from `/to-tickets` are written as independently-grabbable tracer-bullet slices. To implement:
|
||||
|
||||
1. Pick the next **unblocked** issue (check the "Blocked by" line in the body — slices often form a linear chain).
|
||||
2. Hand it to a coding session: _"implement issue #N from forgejo jared/cc-os"_.
|
||||
|
|
@ -48,4 +50,4 @@ tea issues close <N> --repo jared/cc-os
|
|||
|
||||
## Caveat
|
||||
|
||||
The Matt-Pocock `to-issues` / `triage` skills default to **GitHub** and were never wired to this Forgejo/`tea` backend for cc-os (no `docs/agents/` config, no `.scratch/`). Re-running them assumes GitHub. Running `/setup-matt-pocock-skills` and describing the `tea`/Forgejo workflow would wire them to the right tracker.
|
||||
The Matt-Pocock `to-tickets` (formerly `to-issues`) / `triage` skills default to **GitHub** and were never wired to this Forgejo/`tea` backend for cc-os (no `docs/agents/` config, no `.scratch/`). Re-running them assumes GitHub. Running `/setup-matt-pocock-skills` and describing the `tea`/Forgejo workflow would wire them to the right tracker.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
---
|
||||
hygiene: frozen
|
||||
---
|
||||
# Implementation Process
|
||||
|
||||
_Last updated: 2026-07-02_ | _Status: Steps 2a/2b/2d executed — toolchain installed, context fix applied, vault graph built; model selected (qwen2.5-coder:7b); Step 2c (reference set) previously complete. Step 2 fully executed._
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
# Clutter pattern inventory (seed candidates)
|
||||
|
||||
_Last updated: 2026-07-14_ — Wayfinder research ticket #41 (map: issue #31)
|
||||
|
||||
Read-only survey of non-code / process-artifact file and directory patterns across four
|
||||
local repos, chosen by recent mtime: `cc-os`, `delta-refinery`, `hyperthrive_dev`,
|
||||
`llf-schema`. Purpose: seed candidates for a future clutter-management rulebook. **These are
|
||||
proposals only — a strong model confirms later. Nothing was deleted or modified.**
|
||||
|
||||
Lifetime taxonomy used below:
|
||||
- **keep-indefinitely** — durable project record, never auto-removed.
|
||||
- **temporary (age-triggered)** — safe to prune after N days/weeks of no relevance.
|
||||
- **delete-once-served (purpose-triggered)** — safe to remove once its one-time purpose
|
||||
(a merge, a migration, a handoff) has completed; optional **extract-then-delete** modifier
|
||||
means durable content should be pulled into a keep-indefinitely home first.
|
||||
|
||||
## Catalog
|
||||
|
||||
| Glob pattern | Repos seen in | What it is | Proposed lifetime | Confidence |
|
||||
|---|---|---|---|---|
|
||||
| `graphify-out/` (+ dated subdirs like `graphify-out/2026-06-30/`) | cc-os, delta-refinery, hyperthrive_dev, llf-schema | Disposable/rebuildable Graphify knowledge-graph output (already documented as gitignored, rebuildable per CLAUDE.md conventions) | temporary (age-triggered) — safe to delete any time, regenerable on demand; dated subfolders are stale snapshots left by re-runs | high |
|
||||
| `autoresearch/<run-id>/` (e.g. `classic-260703-1522/`, `improve-260710-1057/`) | cc-os | Per-run autoresearch loop working directories: round outputs, `results.tsv`, `loop-log.md`, `handoff.json`, candidate drafts | delete-once-served, extract-then-delete — keep the final chosen candidate/summary (already promoted to a skill/doc), prune raw round data once the loop concludes | high |
|
||||
| `.claude/skills/openspec-sync-specs/`, `.codex/skills/…`, `.pi/skills/…` (mirrored skill dirs) | cc-os, delta-refinery, hyperthrive_dev, llf-schema | Per-assistant mirrored copies of OpenSpec skills (CLAUDE.md notes these must stay in sync across three assistants) | keep-indefinitely | high |
|
||||
| `openspec/changes/<change-id>/` (active) | cc-os, hyperthrive_dev, llf-schema | Live OpenSpec change proposals (design/spec/tasks) | delete-once-served, extract-then-delete — archived automatically by `/opsx:archive` into `openspec/changes/archive/`; not clutter while active, becomes archive-managed after | medium |
|
||||
| `openspec/changes/archive/<change-id>/` | cc-os | Completed OpenSpec changes, retained per project convention | keep-indefinitely (or long temporary — project already treats these as historical record) | medium |
|
||||
| `docs/plans/*.md` | cc-os, delta-refinery, llf-schema | Point-in-time planning docs for specific efforts (e.g. `ws2-os-vault-write-eval.md`, eval overviews) | temporary (age-triggered), extract-then-delete — durable decisions belong in ADRs/status docs; raw plan often stale once work ships | medium |
|
||||
| `scratch-plans/` incl. `scratch-plans/archive/old-plans-<date>/` | hyperthrive_dev | Ad hoc planning docs with a **project-owned archival script** (`scripts/archive-scratch-plans.sh`) that already rotates them into dated archive folders | temporary (age-triggered) for the live `scratch-plans/` root; archived subfolders are effectively delete-once-served after some longer retention — project has already solved this locally, a good reference pattern | high |
|
||||
| `.scratch/active/*.md` (e.g. `progress.md`, `payment-platform-progress.md`) | hyperthrive_dev | Working "current progress" scratch notes, no archive step observed for this dir (distinct from `scratch-plans/`) | delete-once-served, extract-then-delete — once the tracked effort finishes, extract durable notes and delete | medium |
|
||||
| `handoffs/<project>/**` (large multi-stage handoff trees: `deep-research/`, `architecture/`, `implementation/`, `spec/`, `copywriting/`, plus `*.n8n-backup/` variants, `ARTIFACTS.md`, `HANDOFF.md`, `metadata.json`) | hyperthrive_dev | Multi-agent pipeline handoff artifacts (research → architecture → implementation → spec), including duplicate `.n8n-backup` snapshot folders | delete-once-served, extract-then-delete — final artifacts (`final-report.md`, `final-spec.md`) worth keeping; intermediate JSON/mmd/png working files and backup-suffixed duplicate folders are safe to prune once the pipeline completed and shipped | medium |
|
||||
| `HANDOFF-*.md` / `HANDOFF.md` (top-level, not in handoffs tree) | cc-os (`plugins/os-doc-hygiene/HANDOFF-add-deterministic-core.md`), hyperthrive_dev | Single-file handoff notes for a specific piece of work-in-progress | delete-once-served — once the receiving session/agent has consumed it and the work lands, no longer needed; check if content duplicates commit history first | high |
|
||||
| `PRD.md` (single file, per plugin/feature) | cc-os (`plugins/os-doc-hygiene/PRD.md`), delta-refinery (`docs/tiny-habit-streak-tracker-cli-prd.md`) | Product requirements doc for a specific feature/plugin | keep-indefinitely while the feature is active/unshipped; consider archiving alongside the shipped feature's docs afterward | medium |
|
||||
| `docs/adr/migration-report.md` | cc-os, delta-refinery | One-time report from ADR-system migration (`/os-adr:migrate`), explicitly documented as non-authoritative/uncertainty-flagged | delete-once-served, extract-then-delete — migration_confidence findings should be resolved into real ADRs; report itself is a one-time artifact | high |
|
||||
| `docs/orchestration-audit/auditor-reports/S*-report.md` | cc-os | Per-session auditor output from the biweekly orchestration IRL audit (`os-context:audit-sessions`) | temporary (age-triggered), extract-then-delete — the tune-up report synthesizes these; raw per-session reports are input data, prunable after synthesis and some retention window | medium |
|
||||
| `plugins/*/eval/results/`, `plugins/*/eval-b/`, `plugins/*/eval-c/results/` (eval run output only, not scenarios/fixtures/rubric) | cc-os | Eval harness run results (scores, transcripts) | temporary (age-triggered) — CLAUDE.md's eval discipline treats scenarios/reserves as sacred (never touch), but *results* are re-run outputs; keep latest, prune old runs | medium |
|
||||
| `plugins/*/eval/scenarios/`, `.../scenarios-reserve/`, `.../fixture/`, `judge-rubric.md` | cc-os | Held-out eval scenario sets and fixtures — explicitly protected by repo convention ("never run informally," "reserves never even read") | keep-indefinitely | high |
|
||||
| `reference-schemas/*-COMPARISON-REPORT.md` | llf-schema | One-time comparison/analysis reports generated during schema research | delete-once-served, extract-then-delete — conclusions likely already folded into schema decisions; raw report is reference-only | low |
|
||||
| `.dochygiene/report.json`, `.dochygiene/report.md`, `.cc-os/dochygiene/report.{json,md}` | cc-os, hyperthrive_dev | Generated hygiene-scan reports from `os-doc-hygiene`, regenerated each `/os-doc-hygiene:check` run | temporary (age-triggered) — always superseded by the next scan; effectively a cache | high |
|
||||
| `test/`, `.claude/agents/*.md`, `.claude/commands/scratch-plans/*.md` | delta-refinery, hyperthrive_dev | Code/tooling, not clutter — included here only to note they were correctly excluded from candidates | n/a (not a candidate) | high |
|
||||
| `features/archive/feature-*.md`, `requirements/feature-level-requirement-*.md` | delta-refinery | Feature-level requirement/handoff docs for an agent pipeline (requirement extraction → handoff), with project already using an `archive/` subfolder convention | keep-indefinitely for `archive/` (project's own durable record); live `requirements/*.md` closer to delete-once-served once implemented | low |
|
||||
| `docs/agent-pre-post-handoff.md`, `docs/superpowers/plans/*.md`, `docs/superpowers/specs/` | delta-refinery, hyperthrive_dev | "Superpowers" framework plan/spec docs — appear to be a shared third-party or template convention across repos, not repo-specific | keep-indefinitely (treat as vendored/framework docs) pending confirmation they're not stale copies | low |
|
||||
| `.claude/skills/**`, mirrored `.codex/`, `.pi/` skill dirs generally | all four | Not clutter — actively used tooling, included for completeness of what the scan touched | n/a (not a candidate) | high |
|
||||
|
||||
## Notable non-patterns / things ruled out
|
||||
|
||||
- `openspec/specs/` (stable, synced specs, not `changes/`) — this is the durable spec store,
|
||||
not clutter; excluded from candidates.
|
||||
- `node_modules/`, `.git/`, `vendor/`, `.bundle/`, `tmp/` were pruned from the search per
|
||||
ticket instructions and are out of scope (already well-understood, not novel clutter).
|
||||
|
||||
## Method
|
||||
|
||||
- Repos chosen by `.git` mtime recency under `~/dev`: cc-os, delta-refinery,
|
||||
hyperthrive_dev, llf-schema (most recent 4).
|
||||
- `find` with an `-iregex` filter for known process-artifact name/path fragments
|
||||
(HANDOFF, PRD, TODO, NOTES, scratch, research, transcript, report, openspec, specs,
|
||||
docs/plans, graphify-out, eval, CHANGELOG), pruning node_modules/.git/vendor/.bundle/tmp.
|
||||
- Spot-checked sizes (`du -sh`) for `graphify-out/` and `autoresearch/` to confirm they're
|
||||
worth flagging as prunable bulk, not just noise.
|
||||
- All reads were non-destructive; no files were modified, moved, or deleted in any surveyed
|
||||
repo.
|
||||
|
|
@ -0,0 +1,680 @@
|
|||
source: https://youtu.be/VQy50fuxI34?si=fENPEi7_hFppIftM
|
||||
Forget Loop Engineering
|
||||
0:00
|
||||
It's time for us to talk about loop engineering. I was hoping this would just blow over after a couple weeks. I
|
||||
0:05
|
||||
was hoping a cracked engineer would call this stupid phrase what it really is, but no one has. So, I'll do it myself.
|
||||
0:13
|
||||
Forget about loop engineering. It's the wrong way to think about building valuable software with agents at scale
|
||||
0:20
|
||||
consistently. Loop engineering is a terrible rebrand of the software development life cycle. It's as unclear
|
||||
0:28
|
||||
as it is hype-filled. In this video, we simplify loop engineering and call it
|
||||
0:33
|
||||
what it really is. If you understand this concept properly, we're going to break down in this video, [music]
|
||||
0:39
|
||||
you'll accelerate far ahead of the AI industry. Clarity and simplicity of
|
||||
0:45
|
||||
information gives you [music] speed and performance in your work. It's much more
|
||||
0:50
|
||||
valuable and helpful to think about building with [music] agents as if you're building developer workflows
|
||||
0:57
|
||||
inside your software factory. Your props [music] go into your software factory. A
|
||||
1:03
|
||||
specific workflow runs. Each workflow [music] is a combination of code plus agents and then your results [music]
|
||||
1:10
|
||||
come out. Forget about loop engineering. Focus your valuable engineering time and
|
||||
1:16
|
||||
tokens on building AI developer workflows.
|
||||
Who Is IndyDevDan?
|
||||
1:25
|
||||
So, first off, who am I to go up against big ideas from AI engineers like Boris
|
||||
1:31
|
||||
Churnney from Anthropic and Peter Steinberg, now from OpenAI? If you already know who I am and the work I've
|
||||
1:37
|
||||
done, or you don't care, skip to this timestamp. My name is Dan Eisler aka Indie Dev Dan. I'm a software engineer
|
||||
1:43
|
||||
with over 15 years of EXP. I started out building Adobe Flash games with Action Script 2 and three with my brothers. I
|
||||
1:50
|
||||
interned at Blizzard and then I quickly moved into the finance and accounting space programming in C, TypeScript,
|
||||
1:57
|
||||
Python, God awful React and God bless Evanu for creating Vue. I was AI coding
|
||||
2:03
|
||||
before anyone had a name for it with tools like Ader and classic models like GPT3.5 Turbo, GPT4, and Sonnet 3. Tools
|
||||
2:12
|
||||
and models you've probably never heard of or used. I own the domain name agenticengineer.com
|
||||
2:18
|
||||
where thousands of engineers you've heard of from companies you know have improved their agentic engineering
|
||||
2:24
|
||||
thanks to information products I've built by hand. But anyone can pick up a domain name and create a course though,
|
||||
2:31
|
||||
right? Sure. I also have an irrefutable trail of code and content for engineers
|
||||
2:37
|
||||
on GitHub and this YouTube channel every single week for years now. You can see
|
||||
2:42
|
||||
myself and engineers that follow this channel consistently ahead of the curve of the AI industry. I don't just farm
|
||||
2:49
|
||||
news for views like every other tech content creator. On this channel, we think, plan, and build. Every few
|
||||
2:56
|
||||
months, it's important for me to sit down and say this. I'm not a content creator. I'm a software engineer that
|
||||
3:03
|
||||
does content creation on the side. Why? Because this technology is too valuable
|
||||
3:09
|
||||
to fall into the hands of a lucky few. And yes, I sell courses. And yes, I
|
||||
3:14
|
||||
benefit from it. Big whoop. Don't do anything you're great at for free. Welcome to capitalism. Enough about me.
|
||||
3:19
|
||||
I don't show up here every single Monday to gloat and talk about myself. I show up here to give engineers like you an
|
||||
3:25
|
||||
advantage you can use to accelerate your career, your work, your business, your engineering in the age of AI, no hype.
|
||||
3:33
|
||||
So, forget about loop engineering and focus on this instead. There are now
|
||||
Your 3 Actors of Value Creation
|
||||
3:39
|
||||
three actors of value creation for engineering work. The engineers like you and I, there are the agents and there's
|
||||
3:46
|
||||
the code. Knowing when and where to place each of these is the name of the
|
||||
3:52
|
||||
game of agentic engineering. And you might be thinking, where is he going with this? How does this relate to loop engineering and developer workflows?
|
||||
3:59
|
||||
Stick with me here. We're going to work up to it. If you master the fundamentals, you'll master the compositions. Everyone in their AI
|
||||
4:05
|
||||
psychosis seems to forget code is fast, always runs the same way unless you tell
|
||||
4:11
|
||||
it not to. And guess what? It costs nothing. There are no token costs associated with code. the thing that
|
||||
4:17
|
||||
truly moves at light speed. There's a hidden cost to implementing every single one of these actors of value creation.
|
||||
4:25
|
||||
Everyone's talking about agents. We're all well aware of the cost of engineers, but code is the unsung hero of all of
|
||||
4:32
|
||||
this. Consistent value creation creates consistent business value. And out of these three, code is the most reliable
|
||||
4:39
|
||||
by miles followed by engineers [music] and then agents. So let's start with the
|
||||
Your First Ever AI Developer Workflow
|
||||
4:44
|
||||
most basic developer workflow.
|
||||
4:50
|
||||
An engineer prompts an LLM and the engineer reviews the result. This is the simple foundation that makes up every
|
||||
4:58
|
||||
single loop, every single workflow, every single piece of work moving forward. Now, of course, we're not just
|
||||
5:05
|
||||
using the LLM anymore. In this central node, we have an agent. Insert your favorite agent. Insert your favorite
|
||||
5:11
|
||||
model. It doesn't matter anymore. It's about the workflow that you and I execute every single day. Great. Let's
|
||||
5:19
|
||||
scale it up. Now, we have code, agents, and engineers all involved in the
|
||||
5:24
|
||||
process. We're building up to more and more advanced developer workflows. You'll notice something really important
|
||||
5:31
|
||||
here. We now have code. In this case, we're just running a llinter. We have a condition. If the llinter fails, the
|
||||
5:37
|
||||
results go back into our build agent. If they're successful, it passes. This condition and this routing back to our
|
||||
5:45
|
||||
build agent creates our first loop. Hence the term loop engineering. But
|
||||
5:50
|
||||
loop engineering is too simple. It's too inaccurate. And there's a lot more to this story. So what comes next? How can
|
||||
5:56
|
||||
we continue to enhance our developer workflow to get better results? We can of course add more deterministic code.
|
||||
Adding Code to Your ADW
|
||||
6:03
|
||||
Now we have multiple pass fill statements routing back into our build agent. Your codeex, your cloud code,
|
||||
6:08
|
||||
your pi coding agent, whatever your agent harness is. This is the foundation of what it means to build with agents.
|
||||
6:14
|
||||
Now you have three actors in this. Engineers, agents, and raw code. You
|
||||
6:20
|
||||
have to leverage them all in the right location at the right time. Here we're adding a code formatter. It doesn't
|
||||
6:25
|
||||
matter what language you're in. linting your code, formatting your code, type check your code, and then keep scaling
|
||||
6:31
|
||||
up the validation loops to run back into your agents. Once again, you'll see here
|
||||
6:37
|
||||
these conditions is what makes up what is called the loop. But there's a lot more going on here. This is really a
|
||||
6:44
|
||||
workflow of how information travels within a system. Okay, keep scaling it
|
||||
6:50
|
||||
up. What comes next? We can add more code. A very valuable piece of code here
|
||||
6:55
|
||||
is testing your code. So test. Now we take all these results, feed it back
|
||||
7:00
|
||||
into our build agent over and over and over until the results all pass. And
|
||||
7:05
|
||||
that gives us our final engineering review. You'll notice a pattern here. You and I always show up at the ends.
|
||||
7:12
|
||||
These are the two constraints of a gentic engineering. Prompting, also known as planning, and reviewing, also
|
||||
7:18
|
||||
known as validation. If you're a gent engineering at scale properly, you're showing up at the beginning and the end
|
||||
7:25
|
||||
with a few exceptions. Your AI developer workflows start simple like this, but they should continue to grow. Real
|
||||
7:31
|
||||
engineering work looks a lot more complex than this, [music] right? What do we do next?
|
||||
Scale Your Compute to Scale Your Impact
|
||||
7:40
|
||||
We can scale all of our testing, all of our validation, all of our linting, all of our type checking into a single test
|
||||
7:47
|
||||
agent. So now we're scaling our compute to scale our impact. We're adding compute to add confidence. Now you can
|
||||
7:54
|
||||
imagine we've handed this test agent all the things we want to do to test. And if something goes wrong, we send the
|
||||
8:01
|
||||
context back to the build agent. If it passes, the engineer reviews and then we can ship the deliverable. We can ship
|
||||
8:08
|
||||
the code. All right. So notice a couple themes working here. As you scale up your developer workflows, you add agents
|
||||
8:14
|
||||
and you add code. But what you don't want to add is more engineering effort outside of building the system that
|
||||
8:21
|
||||
builds the system. Let's keep scaling this up. Let's add planning to the workflow. You're very familiar with
|
||||
8:26
|
||||
these ideas, right? We're building workflows. These are all steps that you and I, the engineer, used to take and
|
||||
8:33
|
||||
used to execute ourself, right? We would plan work, we would build the work, we would test the work, we would then have
|
||||
8:40
|
||||
another engineer review the work, and then we would finally ship it into production. All right? It's a developer
|
||||
8:45
|
||||
workflow and all we've done here is added AI to it. The loops is just one
|
||||
8:50
|
||||
piece of it. You can call it loop engineering but it's inaccurate and it's not encapsulating the whole picture. If
|
||||
8:55
|
||||
we have loop engineering, we need to have condition engineering and then we need to have function engineering and then we need to have a word plus
|
||||
9:02
|
||||
engineering for every type of control flow inside of the software development life cycle which is going to go on
|
||||
9:07
|
||||
forever. Okay. So a very popular pattern is to push each one of your agents into
|
||||
9:13
|
||||
their own work tree. This creates isolation. This creates parallelism. This lets you do more work in parallel.
|
||||
9:19
|
||||
And there's a nice side effect here where the agents don't trip over each other. So, guess what we're going to do?
|
||||
9:24
|
||||
We're going to write another prompt. And this time, we're going to write a prompt into a piece of code that's going to
|
||||
9:31
|
||||
build our work trees. All right. So, here we have a deterministic piece of code that's going to kick off multiple
|
||||
9:37
|
||||
work trees based on the prompt. And then we're going to execute several different agents running in line. So we have once
|
||||
9:44
|
||||
again scaled our compute to scale our impact. We have multiple work trees
|
||||
9:49
|
||||
running our plan, build, test, review, merge, ship pipeline, our developer
|
||||
9:56
|
||||
workflow. The workflow you and I used to go through as engineers building by
|
||||
10:02
|
||||
hand. Now we have AI, hence AI developer workflows. And this is really important
|
||||
10:07
|
||||
to think through. This is where you should focus in your engineering time. How can I combine the three actors of
|
||||
10:14
|
||||
value creation, engineers, agents, and code to create workflows that execute
|
||||
10:20
|
||||
large amounts of work on my behalf, on behalf of my company, on behalf of your users and products? That's the name of
|
||||
10:27
|
||||
the game. Okay, so great, we have work trees. Work trees are um like I like to say, a great place to start, not a great
|
||||
10:34
|
||||
place to end. There are a lot of problems with work trees. We can do one better by giving our agents each their
|
||||
10:40
|
||||
own sandbox. Okay? So instead of spinning up work trees, we're now giving every single agent their own computer,
|
||||
10:47
|
||||
right? Their own agent sandbox to operate. Because once you do this, you have full isolation. You yourself can
|
||||
10:54
|
||||
jump into the sandbox to look at the work, look at the result, look at the web page, look at the tests, look at the
|
||||
11:00
|
||||
application, whatever you need to do, do your review. And then of course once all the work comes back in you merge and you
|
||||
11:06
|
||||
ship. So once again notice the three actors of value creation working together. And notice how your ability to
|
||||
11:14
|
||||
create these AI developer workflows is your ability to scale your impact with
|
||||
11:19
|
||||
agents. It's designing these AI developer workflows that is the most value accreative thing an engineer can
|
||||
11:26
|
||||
do. Hence the term I like to say on the channel all the time. You want to be building the system that builds the
|
||||
11:33
|
||||
system. Okay? As you can see here, you'll start to see similarities in these workflows with a lot of work
|
||||
11:39
|
||||
you've seen, a lot of work that you've done, and hopefully work that you're building into your teams, your
|
||||
11:44
|
||||
co-workers, your business, your tools themselves. Okay? We're just getting started, [laughter] right? Uh real work
|
||||
11:51
|
||||
gets more and more complex. And this is the art and science of agentic engineering. So, let's keep scaling it.
|
||||
The Kanban Queue
|
||||
12:02
|
||||
A very common thing to do as you continue to progress is to set up a conbon board, some type of ticket
|
||||
12:09
|
||||
system. Input comes from all over your organization, right? It comes from support, it comes from product, and it
|
||||
12:14
|
||||
comes from engineers, right? So now things get interesting because now we have a new unit, a new wrapper around
|
||||
12:20
|
||||
our code, right? We have some type of ticketing system. And so once again, like there's this really important delineation that I make inside of
|
||||
12:28
|
||||
tactical agentic coding. And it is this idea of the agentic layer. The agents,
|
||||
12:33
|
||||
the prompts, the skills, the system prompts that wrap your application are
|
||||
12:39
|
||||
the thing to be focused on right now. Because when you put those together with your code, with your system, with your
|
||||
12:45
|
||||
entire team and your organization, you are agentic engineering. Agentic engineering is not just about the
|
||||
12:51
|
||||
agents. Spoiler alert, it's about your team and most importantly your users and putting together the most valuable stack
|
||||
12:58
|
||||
of the actors of value creation. Okay, so conbon board, right? Let's let's jump into this. What does this look like?
|
||||
13:04
|
||||
Okay, so for most teams, your tickets are then analyzed by your engineer who actually knows what's going on and
|
||||
13:10
|
||||
they'll translate that into a mid to low-level prompt that you'll then pass into another full workflow, another
|
||||
13:17
|
||||
agent sandbox. But some advanced teams if you're teaching your organization how to write prompts well enough and as
|
||||
13:23
|
||||
models become more capable uh you can skip your engineer input prompt here right because your engineer's job should
|
||||
13:29
|
||||
be building the system we act on the meta layer we act on the layer that can compound across our organization okay so
|
||||
13:35
|
||||
advanced teams can skip the engineering prompt if this step is done properly all right but so you know this is code right
|
||||
13:42
|
||||
conbon boards it's just code there are no agents there then we enter the meat of our workflow where we run code to
|
||||
13:48
|
||||
move that ticket into planning. Guess what happens next? Our agents take over the pipeline. We'll have a scout agent
|
||||
13:54
|
||||
look for all the code, look for all the tickets, look for all the documentation, look for previous spec files, and then
|
||||
13:59
|
||||
it'll hand that to a plan agent. Right? So, we're splitting up our searching and our planning between two agents here.
|
||||
14:05
|
||||
Once again, scaling compute, scaling impact. And after that happens, you know, plan phase is complete. So, we run
|
||||
14:11
|
||||
code to update our ticket to move context to do some specific work inside of our sandbox. And then of course our
|
||||
14:17
|
||||
build agent kicks off. And you know what happens from here. You've done it a million times yourself. And now with
|
||||
14:23
|
||||
agents, your build agent moves it into testing after it's done. And then your test agent tests, right? This individual
|
||||
14:30
|
||||
loop, which is just one part of the developer workflow, executes until the result passes. And then we're going to
|
||||
14:37
|
||||
run our CI/CD. And guess what can happen here? This can pass or it can fail and
|
||||
14:42
|
||||
go right back to the build agent to resolve the issues. All right. And then we get outside the sandbox. Engineer
|
||||
14:47
|
||||
reviews the code. You know what this looks like now? Fail, pass, ship. Right? So you can see this is much more than
|
||||
14:54
|
||||
just prompt engineering. It's much more than context engineering. It's much more than harness engineering. It's much more than loop engineering. This is about how
|
||||
15:00
|
||||
teams move as an organism with all the actors inside. Okay? And if you're a
|
||||
15:07
|
||||
small solo dev shop, same deal, right? It's about how you and your agents work together with code to generate valuable
|
||||
15:15
|
||||
results. But this isn't the end. The future of engineering is vast. Okay, let's keep pushing this. What happens
|
||||
15:21
|
||||
next? Let's let's imagine another scenario here. Imagine you have a support crisis. Production is down.
|
||||
Production Goes Down
|
||||
15:28
|
||||
Okay, production has crashed. So, what AI developer workflow do you have planned in your organization right now
|
||||
15:35
|
||||
when production goes down? How are you leveraging engineers, agents, and code
|
||||
15:40
|
||||
to resolve this issue and to stop your business from leaking cash as your production system is down? Let's walk
|
||||
15:47
|
||||
through it, okay? Because we've thought through this, right? We've designed, we've architected the AI developer
|
||||
15:53
|
||||
workflow to account for this situation. Support files a ticket. In our case here, this goes right to Slack. It goes
|
||||
15:58
|
||||
right to Teams, goes right to your communication channel, and one of your cracked engineers picks this up immediately. What do they do? They
|
||||
16:04
|
||||
prompt a scout agent that routes right into a hot fix agent. Your hot fix agent
|
||||
16:10
|
||||
has a specialized set of mental memory. It's an agent expert that knows and is
|
||||
16:16
|
||||
prioritized to get things fixed. It's not doing things the right way. It's not doing things the fancy way. It's not
|
||||
16:21
|
||||
optimizing anything. It's getting the fix out ASAP and nothing else. This is a surgical hotfix agent, a custom agent
|
||||
16:28
|
||||
that you have specialized, that you've templated your engineering into. Now, what happens here? Human in the loop.
|
||||
16:34
|
||||
This is a hot fix. We need to know the solution is going to work. So you put in human effort, right? You use engineering
|
||||
16:39
|
||||
effort to approve or reject. This creates a single loop. All right? If we approve, guess what happens? We're going
|
||||
16:45
|
||||
to build up a bunch of sandboxes to run the solution in parallel. And guess what? We're using multiple sandboxes. I
|
||||
16:52
|
||||
want the first fastest agent that has the solution to win. Okay? Whatever your compute budget is, you'll scale this up.
|
||||
16:57
|
||||
You'll scale this down. If you're in a production system that's complex, you might want three, five, 10 agents
|
||||
17:02
|
||||
running and racing toward a solution in their own agent sandbox, you don't care. You have the compute, you've done the agentic engineering to scale your
|
||||
17:09
|
||||
impact. And guess what happens here? You already know what happens. It runs its individual loop in their sandbox. And
|
||||
17:15
|
||||
then it passes or fails. If it fails, it goes right back to your hot fix agent and to you to resolve. And of course, if
|
||||
17:21
|
||||
it's all successful, you the engineer validate it and you get the hot fix shipped ASAP. Okay. A question for you
|
||||
17:28
|
||||
and your organization. Do you have an agentic workflow for production crashes? Can you get that resolved in record time
|
||||
17:35
|
||||
using the three actors of value creation in the age of agents? Engineers, agents,
|
||||
17:41
|
||||
and code. All three. Okay, this continues to scale and scale and scale.
|
||||
17:46
|
||||
Let's push these workflows further. After some point, what you get is a structure like this.
|
||||
The Software Factory
|
||||
17:57
|
||||
And this is what really starts turning into a software factory. Okay, you'll see here we have many different types of
|
||||
18:04
|
||||
specialized agent sandbox workflows. Some are for chores, one is for a bug,
|
||||
18:09
|
||||
one is for a feature, one is for this hot fix that we just walked through. And you get the idea here, right? Any
|
||||
18:15
|
||||
specialized AI developer workflow you need can be built and routed to thanks
|
||||
18:21
|
||||
to your routing system. Okay, this is the art and science of agentic
|
||||
18:26
|
||||
engineering, right? This is all of it put together. The loops are just one small piece of this picture. I hope you
|
||||
18:32
|
||||
can see that. Now, in all this is a great level of prompt context harness
|
||||
18:38
|
||||
engineering. There are a million ways to do this. There are a million different multi- aent orchestration patterns to
|
||||
18:45
|
||||
build into this. The key here is this. is that you have the right combination
|
||||
18:51
|
||||
at the right time to push engineering work through end to end with agents with
|
||||
18:57
|
||||
code with engineers. Okay, I know I'm repeating myself. I'm doing it on purpose. Most success in any domain is
|
||||
19:04
|
||||
about doing a few things and saying a few things and focusing on a few things over and over and over. Let's walk
|
||||
19:11
|
||||
through a full software factory. You can imagine how this looks, right? Let's keep with a conbon ticket example. Now,
|
||||
19:18
|
||||
anyone can file a ticket. This is a feature. This is a bug. This is a chore. Advanced teams are going to skip wasting
|
||||
19:24
|
||||
engineering time transferring your conbon ticket into a low-level or mid-level engineering breakdown of what
|
||||
19:31
|
||||
needs to happen. Advanced teams are going to go right to kicking off a software factory. The moment your conbon ticket lands, once the factory starts,
|
||||
19:37
|
||||
it's going to mark that ticket in progress and move it. And now we have a factory router agent. This could just be
|
||||
19:43
|
||||
a simple LLM call. This could be some deterministic code. The exact nodes are
|
||||
19:48
|
||||
up to you to decide, but you get the idea here. I'm going to throw a factory agent here to intake the results. Do a
|
||||
19:55
|
||||
quick look at the codebase, understand what AI developer workflow we need to execute for the system. First though,
|
||||
20:01
|
||||
we're going to set up a sandbox. We're not limiting our agents anymore. We know that agents are going to continue to
|
||||
20:07
|
||||
expand. This is what the CPU crunch is all about. CPUs are getting wiped off the board outside of scaling RL and
|
||||
20:14
|
||||
other ML engineering related work. Agent sandboxes are going to, I can guarantee you this, be the majority of computers
|
||||
20:20
|
||||
out there in the world. You and I will be using fewer and fewer devices while our agents continue to scale up and use
|
||||
20:25
|
||||
more sandboxes. Okay, but set up the sandbox. After that, our agent has already decided what type of workflow we
|
||||
20:31
|
||||
need to get the job done at the best price, at the best performance, and at the right speed. Because as you likely
|
||||
20:37
|
||||
know, you're not going to run your hot fix AI developer workflow or your feature AI developer workflow where
|
||||
20:43
|
||||
you're scaling out your very best agents. Maybe your build agent is a workhorse model, but your planner and
|
||||
20:48
|
||||
your scouters are going to be state-of-the-art model so nothing gets missed. Of course, there's a whole slew of multi- aent orchestration work that
|
||||
20:55
|
||||
can happen here. But the whole point is you're not going to deploy your heavy AI developer workflows for a chore, right?
|
||||
21:00
|
||||
for your chore. Throw a single agent at this with a workhorse model, maybe even a lightweight model. Build it, run the
|
||||
21:06
|
||||
lint, run the CI/CD, engineer reviews it, and ship it out. We'll talk about ZTE in a second, but the best teams are
|
||||
21:12
|
||||
going to start dropping off engineering review because they've built the best system possible that they know is going
|
||||
21:18
|
||||
to execute for them. But every single unique workflow is unique for a reason,
|
||||
21:24
|
||||
right? There are multiple workflows you want to build out here, multiple AI developer workflows you should be building out, not just one. I'll give my
|
||||
21:30
|
||||
recommendation some really, really great practices you can use when building these out in a moment here. But the
|
||||
21:36
|
||||
general rule of thumb is just to start simple. Once you start scaling this up, what you're going to end up with is a
|
||||
21:41
|
||||
software factory. A software factory that can operate your application as
|
||||
21:46
|
||||
well. And if you're doing it right, better than you and your engineering team. This is why all your effort, all
|
||||
21:52
|
||||
of your effort goes into this. Now, the agentic layer, right? This is the al the
|
||||
21:57
|
||||
agentic layer, not the app layer. The app layer is for your agents. The the
|
||||
22:03
|
||||
best engineering teams never touch the product themselves. Okay, I know this might be like controversial. Some
|
||||
22:09
|
||||
engineers are going to hate hearing this, but the best teams are doing meta work on the agentic layer. They're
|
||||
22:16
|
||||
building the system that builds the system. That is the central thesis inside of tactical agentic coding.
|
||||
22:22
|
||||
Thousands of engineers know that and you're going to figure it out too sooner or later. Okay, that doesn't mean you can't jump into the app to do work. But
|
||||
22:29
|
||||
when you have a successful product scaled with users, the name of the game is this. Building a software factory
|
||||
22:37
|
||||
that operates everything better than you alone could, better than code alone could, and better than agents alone
|
||||
22:44
|
||||
could. Right? Three actors of value creation, agents, engineers, code. Where does that all lead us? It leads us to
|
||||
22:50
|
||||
the simple conclusion that it's not a loop you're after. It's an AI developer workflow. Okay, you might be, you know,
|
||||
22:56
|
||||
listening to this and thinking, but you you drew a million loops. Like, isn't this a loop? Fine. If you want to call
|
||||
23:02
|
||||
it a loop, I don't really care. I think a loop is too constrained. If you're going to call it a loop, we're going to need if engineer, we're going to need
|
||||
23:09
|
||||
throw engineering. We're going to need exception engineering, right? We're going to need to name all these things engineering. This is a developer
|
||||
23:14
|
||||
workflow. This used to be what engineers did. Engineers used to decide if something was a chore, if something was
|
||||
23:20
|
||||
a bug. We used to write the plan, we used to execute it, so on and so forth. But now we have a new tool and that's
|
||||
23:26
|
||||
all it is. It's a new tool. We have agents to work with that gives us AI
|
||||
23:32
|
||||
developer workflows. Okay. So at the highest levels of agentic engineering, you're building software factories that
|
||||
23:39
|
||||
execute the right work and the right combination of engineers, agents, and code across your organization. Once you
|
||||
23:45
|
||||
really start to scale it up, you're going to add your other teammates, right? Your other team members from other cross cutting concerns inside of
|
||||
23:52
|
||||
your business. But at the core of it, the engineers are responsible for the code. Okay? I think a lot of orgs are
|
||||
23:58
|
||||
going to have a a problem with this once they start scaling in and adding other team members, right? Especially ones
|
||||
24:04
|
||||
that can't write clear tickets for the life of them. You've seen this a million times, right? It's the most painful
|
||||
24:09
|
||||
thing when your product manager, your your CTO, your your tech lead [laughter]
|
||||
24:14
|
||||
just writes a ticket and you have to translate it, right? So there's there's a lot of like, you know, people
|
||||
24:19
|
||||
organizational level work to be done here. But you at the end of the day, you know, you the engineer plus the agents
|
||||
24:25
|
||||
plus the code making up the AI developer workflow. This is what it's all about. This is where value is going to be
|
||||
24:31
|
||||
created at absurd levels, at absurd scales. Because once you get this right,
|
||||
24:37
|
||||
here's the dirty secret of all software, right? You already know it. Once you get this right, you set up the right
|
||||
24:43
|
||||
guardrails, the right harness, right? Again, prompt, context, harness engineering, all of it. Once you do this
|
||||
24:48
|
||||
right, you have a repeatable workflow that you can run tens, hundreds, and thousands of times, delivering
|
||||
24:54
|
||||
consistent results to you over and over and over again if you template your
|
||||
25:00
|
||||
engineering into the fabric of your AI developer workflows. Okay? And so I've
|
||||
25:05
|
||||
been pushing against out of the box agents for a long time. Um, you know, specialization is the name of the game.
|
||||
25:11
|
||||
What is a product? What is a company? Right? Unless you're a big tech giant, like a product in a company is a a set
|
||||
25:18
|
||||
of people in technology that solve a specific problem for a specific avatar for a specific user for a specific
|
||||
25:24
|
||||
customer. By very definition is specialization, right? Your expertise is
|
||||
25:30
|
||||
the most valuable thing you have now. And you can template that into your engineering. You can template that into
|
||||
25:36
|
||||
your AI developer workflows. All right, this is the greatest leverage point of agentic coding. It's building out these
|
||||
25:43
|
||||
full AI developer workflows that puts it all together. Okay, and so you know once again we are pushing away from vibe
|
||||
25:51
|
||||
coding. This is not vibe coding. Vibe coding is not knowing how the system works and it's not looking at how the
|
||||
25:56
|
||||
system works. Okay, agentic engineering is knowing your system works so well you don't have to look. And that is because
|
||||
26:03
|
||||
you the engineer have moved up a layer. You're meta-engineering. You're compounding an advantage by optimizing
|
||||
26:10
|
||||
the three actors of agentic engineering engineer code agents into the right
|
||||
26:16
|
||||
developer workflow at the right time at the right performance at the right price with the right speed. And after time
|
||||
26:22
|
||||
you'll realize something really important is that you'll be building AI developer workflows into your products
|
||||
26:28
|
||||
for your customers right with your customers as nodes and then as mentioned with your companies every user that can
|
||||
26:35
|
||||
prompt into the system and receive results out the system right you have to design this system it's just another
|
||||
How to Build Great AI Developer Workflows
|
||||
26:40
|
||||
system
|
||||
26:45
|
||||
so I've written hundreds and probably thousands of these AI developer workflows by now. So, let me give you
|
||||
26:50
|
||||
the oil of everything I've learned so far with what I've seen and what I've recommended to engineers as they're
|
||||
26:56
|
||||
building out their ADWs. Um, first off, keep it simple. When you start building these out, start with the simplest
|
||||
27:02
|
||||
workflow you can think of, right? And typically that looks something like this, right? After you get an agent
|
||||
27:07
|
||||
running, you prompt back and forth and you're babysitting your agent. Everyone knows what this looks like. Just let it lent your code. You know, to be clear,
|
||||
27:13
|
||||
so that you can really feel this. Separate this out. I'm not saying write a skill, have your agent build, and then
|
||||
27:19
|
||||
at the bottom of the skill, run lint. Separate this out. Use an agent SDK, run
|
||||
27:24
|
||||
a build agent, do work, and then run a llinter. And when the llinter fails, pass that back into the build agent with
|
||||
27:30
|
||||
the same session ID. You have to separate your code and your agents. Otherwise, you just have an agent
|
||||
27:36
|
||||
calling code. That's not what we want. We want separation of concerns all the way through. If that doesn't make sense
|
||||
27:42
|
||||
to you right now, don't worry. It'll make sense once you start building it. This is not a big skill where you run a
|
||||
27:48
|
||||
hundred different nodes of workflows. There are massive testing, massive validation problems with doing that. And
|
||||
27:54
|
||||
then what do you do after that? You add a couple nodes, right? Start solving real problems. Run your type checker,
|
||||
28:00
|
||||
run your llinter. If things go wrong, funnel it back into your build agent. What you'll notice here is that you're
|
||||
28:05
|
||||
starting to build a larger unit, a larger system that operates without you.
|
||||
28:10
|
||||
You show the beginning and the end. the two constraints of agent coding, planning and reviewing, and your system
|
||||
28:16
|
||||
does everything else. Okay? And then once you get to a certain point, you'll start separating out your agents. You'll
|
||||
28:21
|
||||
start specializing your agents. Maybe you want to separate your front end and your back end. Maybe you want building
|
||||
28:26
|
||||
and testing. Again, the key here is just that you separate the context out so that your context can move between
|
||||
28:33
|
||||
individual agents and code. When you're starting, remember KISS, keep it simple, stupid. You can absolutely start with
|
||||
28:40
|
||||
pure skill-based workflows where it's all one skill outside of the prompt and the review. But as soon as you start
|
||||
28:47
|
||||
productionizing, as soon as you run to get serious about your AWS, you must separate code out of the skills because
|
||||
28:53
|
||||
that's still your agent running it, right? You have to be super super clear about those steps so that you can set up
|
||||
28:58
|
||||
the proper guard rails and information flows in your AI developer workflows. My next really big piece of advice here is
|
||||
Do It by Hand First
|
||||
29:04
|
||||
design your ADWs by doing the work yourself first. For a lot of engineers, this will sound insanely painful, but
|
||||
29:11
|
||||
you can like, you know, use your agent in the terminal. Run the build workflow. Do the testing, right? You can still use
|
||||
29:17
|
||||
your agent for that. I'm not saying do it by hand. That would be a waste of time now. But what I am saying is whatever workflow you're setting up, run
|
||||
29:23
|
||||
it end to end. Step into each node yourself. run the pass, run the condition, watch the functions get
|
||||
29:30
|
||||
executed, do the review, and then do the ship to production and then start writing this all as a combination of
|
||||
29:37
|
||||
agents, engineers, and code. And I recommend you take something like mermaid. And you know, by the way, this is like a adaptation of mermaid diagram.
|
||||
29:45
|
||||
I had an agent create using a plan build test AI developer workflow in one shot. I created a animated application which
|
||||
29:52
|
||||
is of course based on a mermaid diagram. Okay, so shout out to mermaid. Shout out to mermaid.live. But that's my second
|
||||
29:58
|
||||
piece of advice, right? Walk through it all yourself first. Sit down pencil and a piece of paper or use mermaid or use
|
||||
30:04
|
||||
whatever. Really sit down and like write out your workflow. And then lastly, make sure you're not just using agents,
|
||||
Make Sure You're Not Just Using Agents
|
||||
30:11
|
||||
right? Use agents and code. As I mentioned, you can always start with agents and skills, but as soon as you
|
||||
30:18
|
||||
start hitting production, as soon as you want to get serious, move some of that skill work into code. This is not just
|
||||
30:23
|
||||
about token cost. This is about performance, reliability, and speed. Again, everyone in their AI psychosis
|
||||
30:28
|
||||
has like forgotten that speed costs zero tokens. There's no hallucination. It
|
||||
30:34
|
||||
does the exact same thing every time. And it literally runs at the speed of light. So, don't overleverage on agents.
|
||||
30:41
|
||||
Okay? Balance it out with actual code, right? Code execution. And yes, there's
|
||||
30:46
|
||||
information orchestration. There is this is what context engineering is. You're going to need a place for all the
|
||||
30:52
|
||||
results in between each step. Yes, it's going to take some time. Yes, it's going to be a little knowing. Yes, during the
|
||||
30:57
|
||||
process, you'll wonder, I should just throw this all in the skill. You'll be wrong down the road. I can guarantee you that. I've been there. Don't waste your
|
||||
31:04
|
||||
time doing other engineers have already done wrong. Separate it out as you scale this. Okay, so that's the big
|
||||
31:09
|
||||
third tip. Use agents and code. Okay, because agents plus code beats either
|
||||
31:15
|
||||
alone, especially when you start really scaling these into legitimately large AI
|
||||
31:20
|
||||
developer workflows that do serious work for you and your organization. Why? Because you're going to need to test
|
||||
31:26
|
||||
this node. You're going to need to test plan into build. You're going to need to test plan into build and to update the
|
||||
31:32
|
||||
status and to testing and to fail. This is all still a system you the engineer are responsible for. So keep using great
|
||||
31:39
|
||||
classic engineering patterns, isolatable, decoupled, single interface, right? All that stuff matters probably
|
||||
31:47
|
||||
even more, right? It matters even more now because once you do it right and you set up your AI developer workflow, it
|
||||
31:52
|
||||
gets multiplied hundreds and thousands of times and your agents plus your code can drive the outcomes for you. Okay?
|
||||
31:59
|
||||
So, you know, let's step away from the vibes a little bit and let's step out away from the AI psychosis a little bit
|
||||
32:04
|
||||
because if we're going to do serious agentic engineering, you need to know what's going to happen in your system.
|
||||
Tactical Agentic Coding Pitch
|
||||
32:10
|
||||
So, if you made it to the end here, I want to just say thank you for trusting me. For everyone that's been with the channel for a while, for years now, you
|
||||
32:16
|
||||
know, big shout out to you. I appreciate you trusting me and following along this massive journey of Agentic Engineering
|
||||
32:21
|
||||
that we're on. Um, if you want more, I recommend you check out agenticengineer.com, specifically
|
||||
32:27
|
||||
tactical agentic coding. As I mentioned, I've been pretty early to a lot of these ideas. In tactical agent coding, you're
|
||||
32:34
|
||||
going to hear a lot of what I just said really broken down step by step across eight lessons and then six additional
|
||||
32:41
|
||||
upgradable lessons if you're interested. Okay, so the big idea here is AI developer workflows. It's building
|
||||
32:47
|
||||
systems that build systems. We're not touching the application layer anymore. We're touching the agentic system, the
|
||||
32:53
|
||||
agentic layer that builds it on our behalf. Okay? So, if you want to pay to play, you want to get a big advantage
|
||||
32:59
|
||||
that again thousands of engineers that you know of and have heard of at companies you know the names of, they
|
||||
33:05
|
||||
are in here and they have gotten the advantage and they are getting the advantage, right? It's tactical agent coding is the first eight lessons you
|
||||
33:11
|
||||
can upgrade to agentic horizon to get some upgradeable ideas. really big idea there is of course multi- aent
|
||||
33:16
|
||||
orchestration but agent experts is turning out to be a massively banger idea a massively important idea for
|
||||
33:23
|
||||
engineering in the age of agents if you want to build true specialists that outperform out of the box agent anyway
|
||||
33:29
|
||||
lots more in there's a very clear 30-day refund before you start lesson 4. So if
|
||||
33:34
|
||||
you don't like my style or you're not getting the core of it it's fine I don't want you in here if you don't want to be
|
||||
33:39
|
||||
30-day refund before you start lesson 4. This is going to be linked in the description for you. Also, I recommend if you vibe with the ideas here, if you
|
||||
33:46
|
||||
understand the ideas and you don't want to jump in the tactical agent coding right away, check out this blog. I'll
|
||||
33:51
|
||||
link in the description as well, thinking in threads. It covers a lot of the same ideas we've been discussing 99%
|
||||
33:58
|
||||
of everything I do here on this channel. It's all free. It's out there for you to understand and master agentic
|
||||
34:04
|
||||
engineering. If you made it to the end, do me a favor, like this video, leave a comment, and share it with your coworker
|
||||
34:09
|
||||
before your competition sends it to theirs. You know where to find me every single Monday. Stay focused and keep
|
||||
34:16
|
||||
building.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
schema: spec-driven
|
||||
created: 2026-07-16
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
# Design: retire-planka-git-issues-only
|
||||
|
||||
## Context
|
||||
|
||||
os-backlog today (post-slice-8, 2026-07-13): Ruby lib + CLI over both a Planka board model
|
||||
(board-ensure/resolve/card-*) and tracker inspection (`inspect`, `tracker.rb`,
|
||||
`project_index.rb`), skills (capture/list/route), two Planka agents, a SessionStart
|
||||
rules note (~447 tokens), an os-status grammar check, and a wakeup poller that already
|
||||
reads git issues. ADR-0042 retires the Planka half. The issue-side plumbing (`tea` for
|
||||
Forgejo, `gh` for GitHub, in-repo files for `repo:`) already exists and is what real work
|
||||
has used. Constraint: the plugin is globally installed and ambient on every machine — the
|
||||
rework must land whole (no half-migrated grammar), and source edits require
|
||||
`bin/refresh-plugins` to reach sessions.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- One tracker per project holds both state and specs; `planka:` gone from the grammar.
|
||||
- Preserve the durable process contracts: mid-session capture, pull-only listing,
|
||||
priority + autonomy labels, human-only curation gate, cross-project filing.
|
||||
- Leave zero dormant Planka infrastructure: cards migrated, server down, gem archived,
|
||||
credentials revoked.
|
||||
|
||||
**Non-Goals:**
|
||||
- A unified dashboard over git issues (explicitly deferred until it is a felt pain point).
|
||||
- Reworking the wakeup poller (already issue-based; only its label reads are re-checked).
|
||||
- Bulk re-onboarding of projects beyond rewriting their `tracker=` values.
|
||||
- Any new home for client management (Invoice Ninja et al. own that; out of scope).
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **State model = open/closed + labels, no status-label state machine.** Columns
|
||||
Backlog/Next/Doing/Waiting/Review/Done collapse to: open issue (backlog), `next` label
|
||||
(human-curated), assignee/linked-branch activity (doing — no label to maintain),
|
||||
`waiting` label + a blocker comment, `review` label (semi work shipped), closed (done).
|
||||
Rationale: every label the AI must maintain is a compliance cost the incentives review
|
||||
flagged; keep only labels that carry information a human filters on. Alternative
|
||||
rejected: full column-equivalent label set (`doing`, `done`) — duplicates what issue
|
||||
state and assignment already express.
|
||||
2. **Curation gate: `next` is human-only in skill/hook prose, not CLI-enforced.**
|
||||
ADR-0029's CLI enforcement existed because Planka moves were CLI-mediated; issue labels
|
||||
are set via tea/gh which os-backlog does not proxy. The rule lives in the SessionStart
|
||||
note + skills, and the session-audit rubric (os-context category 8) measures compliance.
|
||||
Alternative rejected: wrapping tea/gh in an enforcing CLI — rebuilds the proxy layer the
|
||||
retirement is deleting.
|
||||
3. **Non-repo/ops work: a private `ops` Forgejo repo, `tracker=forgejo:jared/ops`.**
|
||||
The recurring Operations card becomes a recurring-convention issue there (comment +
|
||||
reopen/leave-open, never close — mirrors the existing recurrence contract). Alternative
|
||||
rejected: `repo:<path>` files — loses labels and the wakeup/query surface for the one
|
||||
place recurring process work lives.
|
||||
4. **Migration before demolition, human gate on client data.** Card migration runs while
|
||||
Planka is still up; philly-search-engine-marketing (23 cards, real client backlog) is
|
||||
migrated only after per-card human sign-off (destination: the client project's tracker
|
||||
or the ops repo). cc-os non-Done cards → Forgejo `jared/cc-os` issues. Done cards are
|
||||
not migrated (history stays in a final JSON snapshot archived to the ops repo before
|
||||
decommission). Rollback: the snapshot + a Postgres dump taken before server teardown.
|
||||
5. **Code removal is deletion, not deprecation.** `board_ensurer/board_resolver/
|
||||
board_spec/cards` (Planka transport), both agents, and gem wiring are deleted with their
|
||||
tests; `tracker.rb`/`inspector.rb`/`config.rb`/`project_index.rb`/`wakeup.rb` survive.
|
||||
`Config#planka_board` and `Resolver` board logic go. Grammar validation rejects
|
||||
`planka:` with a pointer to ADR-0042. Alternative rejected: feature-flagging Planka off
|
||||
— dormant-code variant of the dormant-server trap.
|
||||
6. **planka-api gem: archive the repo, keep no dependency.** The gem is user-authored and
|
||||
unpublished; archival (read-only, README pointer to ADR-0042) preserves the
|
||||
reverse-engineering knowledge without implying maintenance. The vault gotchas note
|
||||
stays — it documents the API, not our use of it.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Untested non-repo workload loses its designated surface] → ops-repo issues are the
|
||||
cheap test ADR-0042 calls for; revisit via a new ADR if it chafes in practice.
|
||||
- [AI label discipline unenforced] → session-audit rubric already measures
|
||||
tracker-routing compliance; the label contract is smaller than the column contract it
|
||||
replaces (fewer states, fewer rules).
|
||||
- [Client board migration loses nuance (comments, label history)] → per-card human gate;
|
||||
full JSON snapshot archived first.
|
||||
- [Stragglers: `.cc-os/config` files on other machines still saying `planka:`] →
|
||||
config-write/os-status check rejects the value with a one-line re-route instruction;
|
||||
fail-soft, never blocks a session.
|
||||
- [Forgejo becomes a single point of failure for process state] → it already was for
|
||||
specs and wakeup; repos sync to it from every machine and it is backed up with the VPS.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Land the plugin rework + spec deltas (tests green, `bin/refresh-plugins`).
|
||||
2. Snapshot all boards to JSON; archive snapshot in the ops repo; Postgres dump on ovh-vps.
|
||||
3. Create `jared/ops` repo + label set; move the recurring audit issue there.
|
||||
4. Migrate cc-os non-Done cards → `jared/cc-os` issues (labels carried over).
|
||||
5. Human-gated pass over philly-search-engine-marketing cards.
|
||||
6. Rewrite `tracker=` in onboarded projects' `.cc-os/config`.
|
||||
7. Revoke bot credentials; decommission Planka on ovh-vps; archive the gem repo.
|
||||
8. Update docs: implementation-status leaf + index line, CLAUDE.md inventory line,
|
||||
backlog-pilot vault note.
|
||||
|
||||
Rollback (any step before 7): Planka is still running; restore config values. After 7:
|
||||
restore from Postgres dump — accepted as effectively one-way once step 7 runs.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Whether `philly-search-engine-marketing` cards land in a client repo tracker or the ops
|
||||
repo (human decision during step 5).
|
||||
- Which machine's cron (if any) hosts the wakeup poller — pre-existing open item, not
|
||||
blocked by this change.
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# Proposal: retire-planka-git-issues-only
|
||||
|
||||
## Why
|
||||
|
||||
ADR-0042 (2026-07-16, supersedes ADR-0033) retires Planka: after a week of live use, all
|
||||
real work flowed through git issues while Planka boards held only the plugin's own build
|
||||
cards; the wakeup automation already polls issues, not Planka; and the integration rests on
|
||||
a reverse-engineered API plus an unpublished lockstep gem — a standing maintenance tax a
|
||||
solo operator shouldn't carry for an unused surface. os-backlog must be reworked so git
|
||||
issues are the single tracker for both task state and durable specs.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**: `planka:<board>` is removed from the `.cc-os/config` tracker grammar;
|
||||
`forgejo:` / `github:` / `repo:` remain. Existing `planka:` configs must be re-routed.
|
||||
- os-backlog capture/list retarget to git issues: `/os-backlog:capture` files an issue with
|
||||
priority (P0–P3) and autonomy (hitl/semi/afk-ready) labels; `/os-backlog:list` queries
|
||||
issues instead of card snapshots.
|
||||
- Column state collapses to open/closed plus labels; human curation becomes a human-only
|
||||
`next` label (AI never applies or removes it — carries ADR-0029's curation gate forward
|
||||
without its Planka mechanics). ADR-0029's CLI column-ownership enforcement retires with
|
||||
the surface it governed.
|
||||
- Planka-specific code retires: `board_ensurer.rb`, `board_resolver.rb`, `board_spec.rb`,
|
||||
the Planka transport in `cards.rb`, `card-triage` and `board-audit` agents, the
|
||||
`planka-api` gem dependency, and bot-credential plumbing.
|
||||
- SessionStart process-rules note rewritten for the single-tracker model (smaller; drops
|
||||
column-ownership and promotion rules; keeps capture/routing/cross-project/autonomy rules).
|
||||
- `/os-backlog:route`, `/to-issues`, os-status `tracker-configured`, and the project index
|
||||
updated to the reduced grammar (the spec/state boundary and card-as-pointer rules vanish —
|
||||
one destination holds both).
|
||||
- Live-card migration: ~30 non-Done cards migrated to issues or closed (client board
|
||||
philly-search-engine-marketing gated on human sign-off); recurring Operations card moves
|
||||
to an ops home. Then: Planka server decommissioned on ovh-vps, `planka-api` gem archived.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `issue-backlog`: single-tracker backlog management over git issues — tracker key grammar
|
||||
(`forgejo:`/`github:`/`repo:`), deterministic routing, mid-session capture as labeled
|
||||
issues, pull-only listing, cross-project filing via the project index.
|
||||
- `issue-state-labels`: the label taxonomy that replaces board columns — priority and
|
||||
autonomy label semantics, the human-only `next` curation label, and the AI
|
||||
working-state contract (what the AI may set/clear on an issue and when).
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
_None — os-backlog predates OpenSpec coverage; no existing spec governs it._
|
||||
|
||||
## Impact
|
||||
|
||||
- `plugins/os-backlog/` (lib, bin, skills, agents, hooks, tests — major rework; ~700–900
|
||||
lib lines and both agents removed).
|
||||
- `~/.agents/skills/to-issues/SKILL.md` (destination step simplifies: no pointer card).
|
||||
- os-status `tracker-configured` check (grammar change).
|
||||
- `.cc-os/config` in every onboarded project currently set to `planka:` (cc-os itself plus
|
||||
boards created 2026-07-13).
|
||||
- External systems: Planka server on ovh-vps (decommission), `planka-api` gem repo
|
||||
(archive), Planka bot credentials (revoke; closes the open credvault-import card).
|
||||
- Decision records: ADR-0042 (recorded), ADR-0033 (superseded), ADR-0029 (subject removed),
|
||||
ADR-0034 (project index survives, tracker values change), ADR-0035/0036 (unaffected —
|
||||
already issue-based).
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# issue-backlog — delta spec
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Tracker key grammar excludes Planka
|
||||
The `.cc-os/config` `tracker` key SHALL accept exactly `forgejo:<owner>/<repo>`,
|
||||
`github:<owner>/<repo>`, or `repo:<path>`. `config-write` and the os-status
|
||||
`tracker-configured` check SHALL reject any other value, including `planka:<board>`, with a
|
||||
one-line error naming ADR-0042 and pointing to `/os-backlog:route`; rejection SHALL never
|
||||
block or fail a session (fail-soft).
|
||||
|
||||
#### Scenario: Legacy planka tracker value encountered
|
||||
- **WHEN** a config containing `tracker=planka:cc-os` is read by config-write validation or
|
||||
the os-status check
|
||||
- **THEN** the value is rejected with a one-line message citing ADR-0042 and suggesting
|
||||
`/os-backlog:route`, and the session continues normally
|
||||
|
||||
#### Scenario: Valid git tracker accepted
|
||||
- **WHEN** `config-write` is invoked with `tracker=forgejo:jared/cc-os`
|
||||
- **THEN** the key is written, other config keys and comments are preserved, and the
|
||||
project index is upserted
|
||||
|
||||
### Requirement: Capture files a labeled issue on the configured tracker
|
||||
`/os-backlog:capture` SHALL create an issue on the project's configured tracker (via `tea`
|
||||
for forgejo, `gh` for github, an issue file for `repo:`) carrying a priority label (P0–P3)
|
||||
when known and no state labels; capture SHALL never apply the `next` label. When no tracker
|
||||
key is configured in a git project, capture SHALL suggest `/os-backlog:route` once and
|
||||
otherwise degrade to reporting what it could not do.
|
||||
|
||||
#### Scenario: Mid-session capture on a Forgejo-tracked project
|
||||
- **WHEN** deferred work surfaces in a project with `tracker=forgejo:jared/cc-os`
|
||||
- **THEN** a new open issue is created on that repo via `tea` with the title, a body noting
|
||||
the originating context, and any known priority label — and nothing else
|
||||
|
||||
#### Scenario: Capture with no tracker configured
|
||||
- **WHEN** capture is invoked in a git project whose config has no tracker key
|
||||
- **THEN** no issue is created; the skill suggests `/os-backlog:route` once and exits
|
||||
without error
|
||||
|
||||
### Requirement: Listing is pull-only over issues
|
||||
`/os-backlog:list` SHALL query the configured tracker's open issues (title, number, labels,
|
||||
assignee) and present them grouped by state (next / waiting / review / other open). It
|
||||
SHALL run only on explicit user request; no hook or skill SHALL inject issue lists into a
|
||||
session unasked.
|
||||
|
||||
#### Scenario: User asks what's on the backlog
|
||||
- **WHEN** the user explicitly asks what is on the backlog or what's next
|
||||
- **THEN** open issues from the configured tracker are listed grouped by state labels, with
|
||||
`next`-labeled issues shown first
|
||||
|
||||
### Requirement: Cross-project filing targets the other project's tracker
|
||||
WHEN work belonging to a different project surfaces, the system SHALL resolve that
|
||||
project's tracker via the global project index (`os-backlog projects`) and file a labeled
|
||||
issue there using the Discoverer template, rather than editing the other project or filing
|
||||
locally. Priority/autonomy labeling of the filed issue remains the receiving project's job.
|
||||
|
||||
#### Scenario: Discovered defect in another indexed project
|
||||
- **WHEN** a session in project A finds a bug belonging to indexed project B
|
||||
- **THEN** an issue is created on B's configured tracker with the Discoverer template, no
|
||||
autonomy or priority labels are applied by A, and no local copy is kept
|
||||
|
||||
### Requirement: One tracker holds both state and specs
|
||||
A project SHALL have exactly one configured tracker destination; durable specs
|
||||
(tracer-bullet slices from /to-issues, PRDs) and task-state items are issues on that same
|
||||
tracker. The system SHALL NOT create pointer artifacts on a second tracking surface.
|
||||
|
||||
#### Scenario: /to-issues publishes a slice chain
|
||||
- **WHEN** /to-issues publishes tracer-bullet slices for a project with a configured git
|
||||
tracker
|
||||
- **THEN** the slices become issues on that tracker and no pointer card or secondary
|
||||
tracking artifact is created anywhere
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# issue-state-labels — delta spec
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Canonical label taxonomy
|
||||
The backlog label set on every routed tracker SHALL be: priority `P0` `P1` `P2` `P3`;
|
||||
autonomy `hitl` `semi` `afk-ready`; state `next` `waiting` `review`. `/os-backlog:route`
|
||||
SHALL ensure these labels exist (idempotently) on forgejo/github trackers when onboarding.
|
||||
No `doing` or `done` label exists: in-progress is expressed by assignment/branch activity,
|
||||
completion by closing the issue.
|
||||
|
||||
#### Scenario: Route onboards a Forgejo repo
|
||||
- **WHEN** `/os-backlog:route` configures `tracker=forgejo:<owner>/<repo>`
|
||||
- **THEN** all ten canonical labels exist on the repo afterwards, and re-running route
|
||||
creates nothing new
|
||||
|
||||
### Requirement: The next label is human-only
|
||||
The `next` label marks human-curated priority. The AI SHALL never apply or remove `next`
|
||||
on any issue, in any project, under any instruction short of an explicit direct user
|
||||
request naming the specific issue.
|
||||
|
||||
#### Scenario: AI triages a backlog
|
||||
- **WHEN** an AI session labels or files issues during triage or capture
|
||||
- **THEN** `next` is neither added to nor removed from any issue
|
||||
|
||||
### Requirement: Autonomy label semantics
|
||||
`hitl` issues are human-owned and SHALL never be picked up autonomously. `semi` issues MAY
|
||||
be worked autonomously through their mechanical parts, SHALL stop at named decision gates,
|
||||
and on shipping SHALL receive the `review` label instead of being closed. `afk-ready`
|
||||
issues, once shipped AND verified, SHALL be closed directly. Assigning autonomy labels is
|
||||
the responsible project's job; ambiguity resolves to `hitl`.
|
||||
|
||||
#### Scenario: Semi-autonomy work ships
|
||||
- **WHEN** an AI session completes the mechanical work of a `semi` issue
|
||||
- **THEN** the issue receives the `review` label, a comment summarizing what shipped, and
|
||||
remains open for human sign-off
|
||||
|
||||
#### Scenario: Afk-ready work ships and verifies
|
||||
- **WHEN** an AI session completes and verifies work on an `afk-ready` issue
|
||||
- **THEN** the issue is closed with a closing comment linking the verification evidence
|
||||
|
||||
### Requirement: Working an issue means recording state on it
|
||||
An AI session working an issue SHALL record state transitions on the issue itself: a
|
||||
comment when work starts, the `waiting` label plus a blocker comment when blocked, and the
|
||||
shipping transition per its autonomy label. Blocked issues SHALL have `waiting` removed
|
||||
when work resumes.
|
||||
|
||||
#### Scenario: Work blocks on an external dependency
|
||||
- **WHEN** in-progress issue work hits a blocker the session cannot resolve
|
||||
- **THEN** the issue gains the `waiting` label and a comment naming the blocker, and the
|
||||
session moves on
|
||||
|
||||
### Requirement: Recurring process issues are never closed
|
||||
An issue representing a recurring process (e.g. the biweekly orchestration audit in the
|
||||
ops repo) SHALL be worked by commenting each occurrence's outcome on it and leaving it
|
||||
open; closing it is a human-only action.
|
||||
|
||||
#### Scenario: Recurring audit completes an occurrence
|
||||
- **WHEN** an AI session completes one occurrence of a recurring-convention issue
|
||||
- **THEN** the outcome is added as a comment and the issue stays open with its labels
|
||||
unchanged
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# Tasks: retire-planka-git-issues-only
|
||||
|
||||
## 1. Plugin rework — grammar and lib (TDD throughout)
|
||||
|
||||
- [x] 1.1 Reject `planka:` in tracker grammar: `Backlog::Tracker` validation + `config-write`
|
||||
+ os-status `tracker-configured` check emit the one-line ADR-0042 message, fail-soft
|
||||
(tests first)
|
||||
- [x] 1.2 Delete Planka lib: `board_ensurer.rb`, `board_resolver.rb`, `board_spec.rb`,
|
||||
Planka transport in `cards.rb`, `Config#planka_board`, board logic in `resolver.rb`,
|
||||
planka-api gem wiring; delete their tests and the `FakePlankaClient`
|
||||
- [x] 1.3 Remove CLI subcommands `board-ensure`, `activate`, `archive`, `card-add`, `cards`,
|
||||
`snapshot`, `card-label`, `card-comment`, `card-move`; keep `resolve`, `inspect`,
|
||||
`config-write`, `projects`; suite green
|
||||
- [x] 1.4 Add issue-side capture/list helpers (tea/gh/repo-file dispatch per issue-backlog
|
||||
spec) with tests against fake CLI outputs
|
||||
|
||||
## 2. Plugin rework — skills, agents, hooks
|
||||
|
||||
- [x] 2.1 Rewrite `/os-backlog:capture` and `/os-backlog:list` to the issue model
|
||||
(labeled-issue create; pull-only grouped listing)
|
||||
- [x] 2.2 Rewrite `/os-backlog:route` for the reduced grammar: destination proposal,
|
||||
canonical ten-label ensure (idempotent), migration of in-repo items; drop
|
||||
spec/state-boundary and pointer-card prose
|
||||
- [x] 2.3 Delete `agents/card-triage.md` and `agents/board-audit.md`
|
||||
- [x] 2.4 Rewrite `hooks/session_start.py` note per ADR-0042: keep capture / routing /
|
||||
cross-project / autonomy-label / recurring-issue rules; add the human-only `next`
|
||||
rule; drop column-ownership and promotion rules; verify token count shrank
|
||||
- [x] 2.5 Update `/to-issues` (global, `~/.agents/skills/to-issues/`): destination step
|
||||
reads the reduced grammar; remove the Planka pointer-card branch
|
||||
- [x] 2.6 Confirm wakeup poller label reads match the new taxonomy (no `doing`/`done`
|
||||
labels); adjust tests if needed
|
||||
- [x] 2.7 Run `bin/refresh-plugins`; smoke-test capture + list live in cc-os
|
||||
|
||||
## 3. Data migration (Planka still up; order matters)
|
||||
|
||||
- [x] 3.1 Snapshot all boards to JSON via the API; take a Postgres dump on ovh-vps
|
||||
- [x] 3.2 Create private `jared/ops` Forgejo repo with the canonical label set; archive the
|
||||
board snapshot JSON in it
|
||||
- [x] 3.3 Recreate the recurring "Biweekly orchestration IRL audit" as a
|
||||
recurring-convention issue in `jared/ops`; update the os-context audit skill's
|
||||
pointer to it
|
||||
- [x] 3.4 Migrate cc-os non-Done cards (9 Backlog, 2 Waiting, 2 Review) to `jared/cc-os`
|
||||
Forgejo issues with labels carried over; note card-comment context in issue bodies
|
||||
- [x] 3.5 HUMAN GATE: per-card pass over philly-search-engine-marketing (17 Backlog,
|
||||
6 Waiting) — user decides destination (client tracker vs ops repo vs close) per card
|
||||
(2026-07-16: 4 repo cards → jared/llf-schema#7–10, 18 client cards → jared/ops#4–21,
|
||||
guardrails pin closed — vault note is the durable record)
|
||||
- [x] 3.6 Close/ignore remaining onboarding-era boards (infrastructure, ovh-prod,
|
||||
ruby-gems, planka, apprise-api) — contents are Done or stale; covered by snapshot
|
||||
- [x] 3.7 Rewrite `tracker=` in `.cc-os/config` of all onboarded projects (query
|
||||
`os-backlog projects` for the list); verify project index rows update
|
||||
|
||||
## 4. Decommission (one-way; only after 3.x verified)
|
||||
|
||||
- [x] 4.1 HUMAN GATE: confirm migration complete and snapshot/dump archived (user confirmed
|
||||
2026-07-16: decommission right after philly pass)
|
||||
- [x] 4.2 Revoke/delete the Planka bot credentials; close the open credvault-import card's
|
||||
successor issue as obsolete (bot-credentials.env deleted on ovh-vps; no successor
|
||||
issue existed — the credvault-import card sat in Done and was never migrated; the
|
||||
import is moot with the bot account gone)
|
||||
- [x] 4.3 Stop and remove the Planka service on ovh-vps (compose down, remove from
|
||||
backups/monitoring); keep the Postgres dump archived (containers/cron/service dir
|
||||
removed; no external backup/monitoring hooks found; dump + board JSON + service
|
||||
configs preserved at ovh-vps `~/planka-final-snapshot-2026-07-16/`)
|
||||
- [x] 4.4 Archive the `planka-api` gem repo (read-only, README pointing at ADR-0042)
|
||||
(github.com/jaredswanson/planka archived; README banner commit e0472c4)
|
||||
|
||||
## 5. Records
|
||||
|
||||
- [x] 5.1 Update `docs/implementation-status/os-backlog.md` (rework entry) + one-line index
|
||||
headline; update the CLAUDE.md inventory bullet for os-backlog
|
||||
- [x] 5.2 Update the backlog-pilot vault note (pilot outcome: Planka retired per ADR-0042)
|
||||
and the auto-memory `backlog-pilot-status` entry
|
||||
- [x] 5.3 Verify ADR index shows 0033 Superseded by 0042; add a status note to ADR-0029
|
||||
body is NOT needed (subject removed — covered by 0042 text); confirm via
|
||||
`/os-adr:find` on plugins/os-backlog/
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# issue-backlog
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Tracker key grammar excludes Planka
|
||||
The `.cc-os/config` `tracker` key SHALL accept exactly `forgejo:<owner>/<repo>`,
|
||||
`github:<owner>/<repo>`, or `repo:<path>`. `config-write` and the os-status
|
||||
`tracker-configured` check SHALL reject any other value, including `planka:<board>`, with a
|
||||
one-line error naming ADR-0042 and pointing to `/os-backlog:route`; rejection SHALL never
|
||||
block or fail a session (fail-soft).
|
||||
|
||||
#### Scenario: Legacy planka tracker value encountered
|
||||
- **WHEN** a config containing `tracker=planka:cc-os` is read by config-write validation or
|
||||
the os-status check
|
||||
- **THEN** the value is rejected with a one-line message citing ADR-0042 and suggesting
|
||||
`/os-backlog:route`, and the session continues normally
|
||||
|
||||
#### Scenario: Valid git tracker accepted
|
||||
- **WHEN** `config-write` is invoked with `tracker=forgejo:jared/cc-os`
|
||||
- **THEN** the key is written, other config keys and comments are preserved, and the
|
||||
project index is upserted
|
||||
|
||||
### Requirement: Capture files a labeled issue on the configured tracker
|
||||
`/os-backlog:capture` SHALL create an issue on the project's configured tracker (via `tea`
|
||||
for forgejo, `gh` for github, an issue file for `repo:`) carrying a priority label (P0–P3)
|
||||
when known and no state labels; capture SHALL never apply the `next` label. When no tracker
|
||||
key is configured in a git project, capture SHALL suggest `/os-backlog:route` once and
|
||||
otherwise degrade to reporting what it could not do.
|
||||
|
||||
#### Scenario: Mid-session capture on a Forgejo-tracked project
|
||||
- **WHEN** deferred work surfaces in a project with `tracker=forgejo:jared/cc-os`
|
||||
- **THEN** a new open issue is created on that repo via `tea` with the title, a body noting
|
||||
the originating context, and any known priority label — and nothing else
|
||||
|
||||
#### Scenario: Capture with no tracker configured
|
||||
- **WHEN** capture is invoked in a git project whose config has no tracker key
|
||||
- **THEN** no issue is created; the skill suggests `/os-backlog:route` once and exits
|
||||
without error
|
||||
|
||||
### Requirement: Listing is pull-only over issues
|
||||
`/os-backlog:list` SHALL query the configured tracker's open issues (title, number, labels,
|
||||
assignee) and present them grouped by state (next / waiting / review / other open). It
|
||||
SHALL run only on explicit user request; no hook or skill SHALL inject issue lists into a
|
||||
session unasked.
|
||||
|
||||
#### Scenario: User asks what's on the backlog
|
||||
- **WHEN** the user explicitly asks what is on the backlog or what's next
|
||||
- **THEN** open issues from the configured tracker are listed grouped by state labels, with
|
||||
`next`-labeled issues shown first
|
||||
|
||||
### Requirement: Cross-project filing targets the other project's tracker
|
||||
WHEN work belonging to a different project surfaces, the system SHALL resolve that
|
||||
project's tracker via the global project index (`os-backlog projects`) and file a labeled
|
||||
issue there using the Discoverer template, rather than editing the other project or filing
|
||||
locally. Priority/autonomy labeling of the filed issue remains the receiving project's job.
|
||||
|
||||
#### Scenario: Discovered defect in another indexed project
|
||||
- **WHEN** a session in project A finds a bug belonging to indexed project B
|
||||
- **THEN** an issue is created on B's configured tracker with the Discoverer template, no
|
||||
autonomy or priority labels are applied by A, and no local copy is kept
|
||||
|
||||
### Requirement: One tracker holds both state and specs
|
||||
A project SHALL have exactly one configured tracker destination; durable specs
|
||||
(tracer-bullet slices from /to-tickets, PRDs) and task-state items are issues on that same
|
||||
tracker. The system SHALL NOT create pointer artifacts on a second tracking surface.
|
||||
|
||||
#### Scenario: /to-tickets publishes a slice chain
|
||||
- **WHEN** /to-tickets publishes tracer-bullet slices for a project with a configured git
|
||||
tracker
|
||||
- **THEN** the slices become issues on that tracker and no pointer card or secondary
|
||||
tracking artifact is created anywhere
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# issue-state-labels
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Canonical label taxonomy
|
||||
The backlog label set on every routed tracker SHALL be: priority `P0` `P1` `P2` `P3`;
|
||||
autonomy `hitl` `semi` `afk-ready`; state `next` `waiting` `review`. `/os-backlog:route`
|
||||
SHALL ensure these labels exist (idempotently) on forgejo/github trackers when onboarding.
|
||||
No `doing` or `done` label exists: in-progress is expressed by assignment/branch activity,
|
||||
completion by closing the issue.
|
||||
|
||||
#### Scenario: Route onboards a Forgejo repo
|
||||
- **WHEN** `/os-backlog:route` configures `tracker=forgejo:<owner>/<repo>`
|
||||
- **THEN** all ten canonical labels exist on the repo afterwards, and re-running route
|
||||
creates nothing new
|
||||
|
||||
### Requirement: The next label is human-only
|
||||
The `next` label marks human-curated priority. The AI SHALL never apply or remove `next`
|
||||
on any issue, in any project, under any instruction short of an explicit direct user
|
||||
request naming the specific issue.
|
||||
|
||||
#### Scenario: AI triages a backlog
|
||||
- **WHEN** an AI session labels or files issues during triage or capture
|
||||
- **THEN** `next` is neither added to nor removed from any issue
|
||||
|
||||
### Requirement: Autonomy label semantics
|
||||
`hitl` issues are human-owned and SHALL never be picked up autonomously. `semi` issues MAY
|
||||
be worked autonomously through their mechanical parts, SHALL stop at named decision gates,
|
||||
and on shipping SHALL receive the `review` label instead of being closed. `afk-ready`
|
||||
issues, once shipped AND verified, SHALL be closed directly. Assigning autonomy labels is
|
||||
the responsible project's job; ambiguity resolves to `hitl`.
|
||||
|
||||
#### Scenario: Semi-autonomy work ships
|
||||
- **WHEN** an AI session completes the mechanical work of a `semi` issue
|
||||
- **THEN** the issue receives the `review` label, a comment summarizing what shipped, and
|
||||
remains open for human sign-off
|
||||
|
||||
#### Scenario: Afk-ready work ships and verifies
|
||||
- **WHEN** an AI session completes and verifies work on an `afk-ready` issue
|
||||
- **THEN** the issue is closed with a closing comment linking the verification evidence
|
||||
|
||||
### Requirement: Working an issue means recording state on it
|
||||
An AI session working an issue SHALL record state transitions on the issue itself: a
|
||||
comment when work starts, the `waiting` label plus a blocker comment when blocked, and the
|
||||
shipping transition per its autonomy label. Blocked issues SHALL have `waiting` removed
|
||||
when work resumes.
|
||||
|
||||
#### Scenario: Work blocks on an external dependency
|
||||
- **WHEN** in-progress issue work hits a blocker the session cannot resolve
|
||||
- **THEN** the issue gains the `waiting` label and a comment naming the blocker, and the
|
||||
session moves on
|
||||
|
||||
### Requirement: Recurring process issues are never closed
|
||||
An issue representing a recurring process (e.g. the biweekly orchestration audit in the
|
||||
ops repo) SHALL be worked by commenting each occurrence's outcome on it and leaving it
|
||||
open; closing it is a human-only action.
|
||||
|
||||
#### Scenario: Recurring audit completes an occurrence
|
||||
- **WHEN** an AI session completes one occurrence of a recurring-convention issue
|
||||
- **THEN** the outcome is added as a comment and the issue stays open with its labels
|
||||
unchanged
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "cc-architect",
|
||||
"version": "1.0.1",
|
||||
"description": "Model-tier-optimized execution plans, scaffolding, and configuration operations for building and managing Claude Code extensions (plugins, skills, workflows, descriptions)"
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Changelog
|
||||
|
||||
## [Unreleased] - 2026-01-26
|
||||
|
||||
### Breaking Changes
|
||||
- **agent-architect → workflow-architect:** The agent-architect skill has been renamed to workflow-architect. It now teaches creating workflow documents for general-purpose subagents instead of creating custom agent files.
|
||||
- **agents/ directory removed:** Custom agent files are no longer used. Skills dispatch general-purpose subagents with workflow documents.
|
||||
- **commands/ directory removed:** Standalone commands are deprecated. All functionality is accessed via skills (`/skill-name`).
|
||||
|
||||
### Changed
|
||||
- **slash-command-architect:** Now marked as legacy-maintenance only. For new functionality, use skill-architect to create skills instead.
|
||||
- **SKILL.md files condensed:** Reduced from ~40-55 lines to ~13-17 lines. Theory removed, workflow routing preserved.
|
||||
- **Workflow files condensed:** Reduced by ~38% on average. Goal/Why sections removed, step sequences and templates preserved.
|
||||
- **Task tool invocations:** Changed to use Skill tool with `/description-architect` instead of invoking custom agents.
|
||||
|
||||
### Migration Details
|
||||
- Aligned with `references/plugin-architecture-philosophy.md`
|
||||
- Backup available at `~/dev/backup/cc-architect-2026-01-25/` (delete after 2026-03-25)
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
# CC Architect
|
||||
|
||||
Build Claude Code extensions: plugins, skills, workflows, commands, and descriptions.
|
||||
|
||||
## Entry Point
|
||||
|
||||
**Use `/cc-architect` for everything.** It analyzes your request and orchestrates the appropriate workflows automatically.
|
||||
|
||||
Examples:
|
||||
- "Create a new plugin for X"
|
||||
- "Add a skill to the Y plugin"
|
||||
- "Audit my skill for compliance"
|
||||
- "Write a description for this component"
|
||||
|
||||
The orchestrator reads the workflow catalog, builds an execution plan with optimal model tiers, and coordinates subagents to complete your request.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
User request
|
||||
|
|
||||
v
|
||||
/cc-architect (reads SKILL.md)
|
||||
|
|
||||
v
|
||||
Planner subagent (Sonnet)
|
||||
- Reads workflows/catalog.md
|
||||
- Builds execution plan with model tiers
|
||||
|
|
||||
v
|
||||
Execution subagents (per plan)
|
||||
- Haiku: scaffolding, validation, cleanup
|
||||
- Sonnet: drafting, analysis
|
||||
- Opus: synthesis, quality decisions
|
||||
|
|
||||
v
|
||||
Final output presented to user
|
||||
```
|
||||
|
||||
## Available Workflows
|
||||
|
||||
The orchestrator can invoke any of these (see `workflows/catalog.md` for details):
|
||||
|
||||
| Category | Workflows |
|
||||
|----------|-----------|
|
||||
| Plugin | create-plugin, plugin-add-skill, audit-plugin, improve-plugin-docs |
|
||||
| Skill | create-skill, audit-skill, improve-skill |
|
||||
| Workflow | create-workflow, audit-workflow, improve-workflow |
|
||||
| Description | generate-description |
|
||||
| Command (legacy) | create-command, audit-command, improve-command |
|
||||
|
||||
## Shared References
|
||||
|
||||
**Knowledge philosophy** (`references/knowledge-philosophy.md`)
|
||||
Document what AI doesn't know from training: rubrics, templates, process steps, custom conventions.
|
||||
|
||||
**Progressive disclosure** (`references/progressive-disclosure/`)
|
||||
Knowledge architecture for non-code files. Creates navigable tree structure for just-in-time discovery.
|
||||
|
||||
**Self-improvement** (`references/self-improvement/`)
|
||||
Reflection step for every workflow. After completing any task, identify friction for future improvements.
|
||||
|
||||
**Tool patterns** (`references/tool-patterns/catalog.md`)
|
||||
Patterns for robust tools: audit, verification, brainstorming, reversion protection.
|
||||
|
||||
---
|
||||
|
||||
## Legacy Skills (Removed)
|
||||
|
||||
The following individual architect skills have been **removed**; their functionality was folded into `/cc-architect`'s workflows. Some workflow docs still reference these by their old names (`skill-architect`, `slash-command-architect`) — treat any such reference as a pointer to the replacement workflow below, not to a live skill directory.
|
||||
|
||||
| Legacy Skill | Replacement |
|
||||
|--------------|-------------|
|
||||
| `/plugin-architect` | `/cc-architect` + create-plugin workflow |
|
||||
| `/skill-architect` | `/cc-architect` + create-skill workflow |
|
||||
| `/workflow-architect` | `/cc-architect` + create-workflow workflow |
|
||||
| `/slash-command-architect` | `/cc-architect` + create-command workflow |
|
||||
| `/description-architect` | `/cc-architect` + generate-description workflow |
|
||||
|
||||
These no longer exist at `skills/{name}/SKILL.md`; only `skills/cc-architect/SKILL.md` remains.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
description: "Model-tier-optimized execution plans and scaffolding for building Claude Code extensions"
|
||||
---
|
||||
|
||||
Invoke the cc-architect skill.
|
||||
|
||||
Read and follow: `skills/cc-architect/SKILL.md`
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
# CC-Architect Migration Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Migrate cc-architect plugin to align with `plugin-architecture-philosophy.md`.
|
||||
|
||||
**Backup:** `~/dev/backup/cc-architect-2026-01-25/` (delete after 2026-03-25, 60 days)
|
||||
|
||||
**Philosophy Core Principles:**
|
||||
- Skills dispatch general subagents (no custom agents)
|
||||
- Commands consolidated into skills (skills are directly invocable)
|
||||
- SKILL.md as 6-8 line dispatcher
|
||||
- Workflows contain rubrics/templates/steps only (no theory)
|
||||
- Trust AI knowledge; don't pre-document what AI knows
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Pre-Flight Checks
|
||||
|
||||
**Subagent:** Sonnet
|
||||
**Purpose:** Resolve architectural paradoxes and validate prerequisites before migration.
|
||||
|
||||
### 0.1 Backup Validation
|
||||
- Verify `~/dev/backup/cc-architect-2026-01-25/` exists and is complete
|
||||
- Confirm backup is restorable (test copy one file back)
|
||||
- Check available disk space
|
||||
|
||||
### 0.2 Resolve Agent-Architect Paradox
|
||||
|
||||
**Problem:** agent-architect skill teaches custom agent creation, but philosophy says "Custom agents are relics."
|
||||
|
||||
**Decision options:**
|
||||
1. **Delete agent-architect** - Remove skill entirely
|
||||
2. **Transform to workflow-architect** - Teach general subagent + workflow pattern instead
|
||||
|
||||
**Resolution:** Transform agent-architect to **workflow-architect** that teaches:
|
||||
- Creating workflow documents (not agent files)
|
||||
- Using general-purpose subagent with workflow paths
|
||||
- This preserves the skill's purpose (helping users extend Claude) while aligning with philosophy
|
||||
|
||||
**Execution steps:**
|
||||
1. `git mv skills/agent-architect skills/workflow-architect`
|
||||
2. Update `skills/workflow-architect/SKILL.md` frontmatter: `name: workflow-architect`
|
||||
3. Update `.claude-plugin/plugin.json` skills list (if referenced)
|
||||
4. Grep for `/agent-architect` or `agent-architect` references → update to `/workflow-architect`
|
||||
5. Update CLAUDE.md architect table
|
||||
6. Update workflow files to teach workflow creation instead of agent creation
|
||||
|
||||
### 0.3 Resolve Slash-Command-Architect Fate
|
||||
|
||||
**Problem:** slash-command-architect teaches command creation, but philosophy says "Do not create new standalone commands."
|
||||
|
||||
**Decision:** Keep slash-command-architect for **legacy maintenance only**. Update SKILL.md to clearly state:
|
||||
- Use for maintaining existing commands
|
||||
- For new functionality, create skills instead
|
||||
- Link to philosophy document
|
||||
|
||||
### 0.4 Audit Task Tool Invocations
|
||||
|
||||
**Action:** Grep all workflow files for `Task tool` or `Task:` invocations of custom agents.
|
||||
|
||||
**Known issues:**
|
||||
- `new-agent.md` line 90 references description-architect agent
|
||||
- `new-command.md` line 77 references description-architect agent
|
||||
- `new-skill.md` line 155 references description-architect agent
|
||||
|
||||
**Fix strategy:** Identify all occurrences in Phase 0.4. Apply fixes during Phase 4 (workflow condensing) when those files are already being edited. This avoids duplicate file edits.
|
||||
|
||||
**Fix:** Update all workflows to use `Skill tool` with `/description-architect` instead of Task tool with custom agent.
|
||||
|
||||
### 0.5 Update Reference Docs
|
||||
|
||||
**Action:** Update plugin-structure.md and layout docs to mark `agents/` and `commands/` as deprecated/legacy patterns.
|
||||
|
||||
**Verification:**
|
||||
- [ ] Backup validated
|
||||
- [ ] Agent-architect transformation decision documented
|
||||
- [ ] Slash-command-architect scope clarified
|
||||
- [ ] All Task tool invocations of custom agents identified and fix strategy defined
|
||||
- [ ] Reference docs updated to deprecate agents/commands directories
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Delete Custom Agents
|
||||
|
||||
**Subagent:** Haiku
|
||||
**Prerequisite:** Phase 0 complete
|
||||
|
||||
**Files to delete (using `git rm`):**
|
||||
- `agents/skill-architect.md`
|
||||
- `agents/agent-architect.md`
|
||||
- `agents/slash-command-architect.md`
|
||||
- `agents/description-architect.md`
|
||||
- `agents/plugin-architect.md`
|
||||
|
||||
**Before deletion:**
|
||||
- Verify all workflows updated to not invoke these agents via Task tool (from Phase 0.4)
|
||||
|
||||
**After deletion:**
|
||||
- Remove `agents/` directory if empty
|
||||
|
||||
**Verification:**
|
||||
- `agents/` directory is empty or removed
|
||||
- No workflow files contain Task tool invocations to deleted agents
|
||||
- Check plugin.json doesn't reference deleted agents
|
||||
|
||||
**Rollback:** Restore from backup
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Delete Standalone Commands
|
||||
|
||||
**Subagent:** Haiku
|
||||
**Files to delete (using `git rm`):**
|
||||
- `commands/skill-architect.md`
|
||||
- `commands/agent-architect.md`
|
||||
- `commands/slash-command-architect.md`
|
||||
|
||||
**After deletion:**
|
||||
- Remove `commands/` directory if empty
|
||||
|
||||
**Verification:**
|
||||
- `commands/` directory is empty or removed
|
||||
- Skills remain invocable via `/skill-name`
|
||||
- Check plugin.json doesn't reference deleted commands
|
||||
|
||||
**Rollback:** Restore from backup
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Condense SKILL.md Files
|
||||
|
||||
**Subagent:** Sonnet (judgment required for what to keep)
|
||||
**Target files:**
|
||||
- `skills/skill-architect/SKILL.md`
|
||||
- `skills/agent-architect/SKILL.md` → rename to `skills/workflow-architect/SKILL.md`
|
||||
- `skills/slash-command-architect/SKILL.md`
|
||||
- `skills/description-architect/SKILL.md`
|
||||
- `skills/plugin-architect/SKILL.md`
|
||||
|
||||
**Transformation:**
|
||||
From ~40 lines to ~10-15 lines:
|
||||
```markdown
|
||||
---
|
||||
name: skill-architect
|
||||
description: [keep existing]
|
||||
---
|
||||
# Skill Architect
|
||||
|
||||
## Workflows
|
||||
- **New skill:** `workflows/new-skill.md`
|
||||
- **Improve skill:** `workflows/improve-skill.md`
|
||||
- **Audit skill:** `workflows/audit-skill.md`
|
||||
|
||||
Read `../../references/subagent-pattern.md` for dispatch rules.
|
||||
```
|
||||
|
||||
**Verification (focus-based, not line-count):**
|
||||
- Theory/explanation removed (architecture details, "why" sections)
|
||||
- Workflow routing preserved (list of workflows with use-case triggers)
|
||||
- Reference to subagent-pattern.md included
|
||||
- Fits on one screen (~25 lines max, but clarity over count)
|
||||
- All workflow paths correct and resolve
|
||||
- Description frontmatter preserved
|
||||
|
||||
**Rollback:** Restore from backup
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Condense Workflow Files
|
||||
|
||||
**Subagent:** Sonnet (judgment required for what to keep)
|
||||
**Prerequisite:** Read condensing rubric (below)
|
||||
|
||||
**Target:** All `workflows/*.md` files in each skill
|
||||
|
||||
### Condensing Rubric
|
||||
|
||||
**Remove (AI knows this):**
|
||||
- "Goal:" explanations (AI understands task goals)
|
||||
- "Why:" sections (trust AI reasoning)
|
||||
- Framework explanations (AI knows frameworks)
|
||||
- Conceptual overviews (AI has training data)
|
||||
|
||||
**Keep (AI doesn't know this):**
|
||||
- Step numbers and sequence
|
||||
- Specific file paths (project-specific)
|
||||
- Templates and output formats (custom conventions)
|
||||
- Rubrics and checklists (custom criteria)
|
||||
- Subagent delegation hints ("Subagent: haiku/sonnet/opus")
|
||||
- Checkpoints ("Confirm with user")
|
||||
- Task document instructions
|
||||
|
||||
**Preserve invariants:**
|
||||
- Workflows must include subagent hints (per skill-architect/invariants.md)
|
||||
- Format can be concise but delegation info must remain
|
||||
|
||||
**Example transformation:**
|
||||
```markdown
|
||||
# Before (verbose)
|
||||
### 2. Design the structure
|
||||
**Goal:** Plan the skill's directory layout before creating files.
|
||||
**Who:** Main agent or general subagent (cheap model).
|
||||
**Must read:**
|
||||
- `references/layout/reference.md`
|
||||
**Do:**
|
||||
- Map workflows to `workflows/*.md` files
|
||||
|
||||
# After (minimal)
|
||||
### 2. Design structure
|
||||
Subagent (haiku). Read: `references/layout/reference.md`
|
||||
- Map workflows → `workflows/*.md`
|
||||
- Write to task doc `## Proposed Structure`
|
||||
Checkpoint: Confirm with user.
|
||||
```
|
||||
|
||||
**User checkpoint after each workflow:**
|
||||
- Show before/after comparison
|
||||
- Highlight removed sections
|
||||
- User confirms: "Proceed" or "Restore section X"
|
||||
|
||||
**Red flags (stop and review with user):**
|
||||
- Step numbers changed or removed
|
||||
- File paths removed
|
||||
- Templates removed
|
||||
- Subagent hints removed
|
||||
- User checkpoints removed
|
||||
|
||||
**Verification:**
|
||||
- Workflow files condensed (aim for 40-60% reduction, prioritize clarity over target)
|
||||
- All action items preserved
|
||||
- All file path references preserved and correct
|
||||
- All templates/rubrics preserved
|
||||
- Subagent hints preserved (invariant requirement)
|
||||
|
||||
**Rollback:** Restore from backup
|
||||
|
||||
---
|
||||
|
||||
## Phase 4.5: Intermediate Review
|
||||
|
||||
**Subagent:** Sonnet
|
||||
**Purpose:** Catch major issues before final phases
|
||||
|
||||
**Check:**
|
||||
- All condensed SKILL.md files are valid
|
||||
- All condensed workflow files are functional
|
||||
- No critical information lost in condensing
|
||||
- Reference paths still resolve
|
||||
|
||||
**Output:** Quick P0-P1 issue list. Fix before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Update CLAUDE.md
|
||||
|
||||
**Subagent:** Haiku
|
||||
**File:** `CLAUDE.md`
|
||||
|
||||
**Changes:**
|
||||
- Remove references to deleted agents/commands
|
||||
- Update task-to-workflow mapping if needed
|
||||
- Update architect table if agent-architect renamed to workflow-architect
|
||||
- Ensure skill invocation instructions are clear
|
||||
|
||||
**Verification:**
|
||||
- No dangling references
|
||||
- All listed skills exist
|
||||
- Invoke instructions match current structure
|
||||
|
||||
**Rollback:** Restore from backup
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Validate Structure and Functionality
|
||||
|
||||
**Subagent:** Sonnet
|
||||
**Actions:**
|
||||
|
||||
### Structural Validation
|
||||
- Run any existing validation scripts
|
||||
- Verify all internal file references resolve
|
||||
- Check for broken links in reference paths
|
||||
- Verify no orphaned files
|
||||
|
||||
### Functional Testing
|
||||
For each architect skill:
|
||||
- Invoke via `/skill-name` (or simulate invocation)
|
||||
- Verify correct workflow is selected
|
||||
- Verify subagent dispatch would work
|
||||
- Complete one simple task if possible
|
||||
|
||||
**Output:** List of any remaining issues
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Post-Migration Review
|
||||
|
||||
**Subagent:** Opus (comprehensive review)
|
||||
**Scope:** Full review of migrated plugin
|
||||
|
||||
**Checklist:**
|
||||
- [ ] Alignment with plugin-architecture-philosophy.md
|
||||
- [ ] No regression in capability (all workflows still work)
|
||||
- [ ] Standards compliance (kebab-case, frontmatter, etc.)
|
||||
- [ ] Architecture integrity (progressive disclosure maintained)
|
||||
- [ ] No orphaned files
|
||||
- [ ] No broken references
|
||||
- [ ] Reversion protection preserved (invariants, golden examples, decisions)
|
||||
- [ ] Under-scripting blind spot documented as known issue (from philosophy doc)
|
||||
|
||||
**Output:** P0-P3 issue report with recommendations
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Documentation
|
||||
|
||||
**Subagent:** Haiku
|
||||
**Actions:**
|
||||
- Create/update `CHANGELOG.md` (at plugin root: `cc-architect/CHANGELOG.md`) with migration notes
|
||||
- Document breaking changes:
|
||||
- agent-architect → workflow-architect rename (if applicable)
|
||||
- commands/ directory removed
|
||||
- agents/ directory removed
|
||||
- Note that slash-command-architect is now legacy-only
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. **No custom agents** - `agents/` deleted
|
||||
2. **No standalone commands** - `commands/` deleted
|
||||
3. **Lean SKILL.md** - Theory removed, workflow routing preserved, fits one screen
|
||||
4. **Condensed workflows** - Reduced while preserving all action items
|
||||
5. **No broken references** - All paths resolve
|
||||
6. **No capability loss** - All workflows still executable
|
||||
7. **Philosophy aligned** - Passes checklist from philosophy doc
|
||||
8. **Functional testing passed** - Each skill invocable, each workflow path tested
|
||||
9. **Reversion protection intact** - invariants.md, examples/golden/, .decisions/ preserved
|
||||
|
||||
---
|
||||
|
||||
## Execution Order
|
||||
|
||||
```
|
||||
Phase 0 (pre-flight) → Phase 1 (agents) → Phase 2 (commands) → Phase 3 (SKILL.md) → Phase 4 (workflows) → Phase 4.5 (intermediate review) → Phase 5 (CLAUDE.md) → Phase 6 (validate) → Phase 7 (review) → Phase 8 (docs)
|
||||
```
|
||||
|
||||
Each phase completes before next begins. Review after each phase for early issue detection.
|
||||
|
||||
---
|
||||
|
||||
## Rollback Procedure
|
||||
|
||||
If any phase fails critically:
|
||||
1. Stop migration
|
||||
2. Document what failed and why
|
||||
3. Restore from `~/dev/backup/cc-architect-2026-01-25/`
|
||||
4. Address root cause before retrying
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
# Phase 0: Task Tool Invocation Issues
|
||||
|
||||
This document identifies Task tool invocations that reference custom agents (description-architect, skill-architect, etc.) in workflow files. These issues should be addressed in Phase 4 when those workflow files are being edited to avoid duplicate edits.
|
||||
|
||||
## Identified Issues
|
||||
|
||||
### Issue 1: new-skill.md
|
||||
**File:** `/home/jared/dev/cc-plugins/cc-architect/skills/skill-architect/workflows/new-skill.md`
|
||||
**Line:** 155
|
||||
**Status:** RESOLVED (Phase 4)
|
||||
|
||||
**Original text:**
|
||||
```markdown
|
||||
- Generate description via description-architect (Task tool with `description-architect` agent)
|
||||
- Input: type=skill, name, purpose, triggers from task document
|
||||
- Output: description string for frontmatter
|
||||
```
|
||||
|
||||
**Resolution:** Updated to use Skill tool invocation with `/description-architect` pattern instead of Task tool with custom agent reference.
|
||||
|
||||
---
|
||||
|
||||
### Issue 2: new-command.md
|
||||
**File:** `/home/jared/dev/cc-plugins/cc-architect/skills/slash-command-architect/workflows/new-command.md`
|
||||
**Line:** 77
|
||||
**Status:** RESOLVED (Phase 4)
|
||||
|
||||
**Original text:**
|
||||
```markdown
|
||||
2. Generate description via description-architect (Task tool with `description-architect` agent)
|
||||
- Input: type=command, name, purpose, triggers from requirements
|
||||
- Output: description string for frontmatter
|
||||
```
|
||||
|
||||
**Resolution:** Updated to use Skill tool invocation with `/description-architect` pattern. Note that this workflow is in the legacy-maintenance skill slash-command-architect.
|
||||
|
||||
---
|
||||
|
||||
### Issue 3: new-workflow.md (ALREADY FIXED)
|
||||
**File:** `/home/jared/dev/cc-plugins/cc-architect/skills/workflow-architect/workflows/new-workflow.md`
|
||||
**Original line:** ~90
|
||||
**Status:** ALREADY FIXED during Phase 0
|
||||
|
||||
The original file (new-agent.md) had a reference to description-architect on line 90, but this has been removed during the agent-architect → workflow-architect transformation in Phase 0.
|
||||
|
||||
---
|
||||
|
||||
## Additional Findings
|
||||
|
||||
### Other description-architect references (NOT Task tool invocations)
|
||||
|
||||
These are not Task tool invocations but references to the description-architect skill in documentation:
|
||||
|
||||
1. **Invariants files** - Multiple skills reference "Always generates descriptions via description-architect" as an invariant. These are policy statements, not invocations.
|
||||
|
||||
2. **Golden examples** - Multiple golden example files mention description-architect in their workflow descriptions. These are documentation, not invocations.
|
||||
|
||||
3. **Redirect files** (`descriptions.md`) - Multiple plugins have redirect files pointing to description-architect. These are navigation aids, not invocations.
|
||||
|
||||
These documentation references may need updating in Phase 4 depending on the final architecture decision, but they are not the critical Task tool invocations that need immediate attention.
|
||||
|
||||
---
|
||||
|
||||
## Resolution Summary
|
||||
|
||||
All identified Task tool invocation issues have been resolved in Phase 4:
|
||||
|
||||
1. **Issue #1 (new-skill.md):** RESOLVED - Updated to use Skill tool with `/description-architect`
|
||||
2. **Issue #2 (new-command.md):** RESOLVED - Updated to use Skill tool with `/description-architect`
|
||||
3. **Issue #3 (new-workflow.md):** RESOLVED during Phase 0 - No Task tool invocations present
|
||||
|
||||
The migration to the general subagent pattern with Skill tool invocations is complete.
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
# Domain Hooks
|
||||
|
||||
Architect-specific questions and anti-patterns for brainstorming. Reference the relevant section based on what's being designed.
|
||||
|
||||
---
|
||||
|
||||
## Skill
|
||||
|
||||
### Key Questions
|
||||
|
||||
- **Trigger**: When should this skill activate? What user intent or request invokes it?
|
||||
- **Context needs**: What does Claude need to know to execute? Where does that knowledge live?
|
||||
- **Boundaries**: What is explicitly out of scope? What should trigger a different skill?
|
||||
- **Output**: What does success look like? A file? A conversation? An action?
|
||||
|
||||
### Anti-patterns to Critique
|
||||
|
||||
| Anti-pattern | What to look for |
|
||||
|--------------|------------------|
|
||||
| Over-specification | Skill tells Claude HOW to do things instead of WHAT knowledge exists and WHERE |
|
||||
| Kitchen-sink scope | Skill tries to handle too many scenarios; should be split |
|
||||
| Missing escape hatch | No guidance on when NOT to use the skill |
|
||||
| Implementation in SKILL.md | SKILL.md contains code examples or step-by-step procedures instead of pointers to references |
|
||||
|
||||
### Progressive Disclosure Check
|
||||
|
||||
- Does SKILL.md fit in ~50 lines?
|
||||
- Are details in referenced docs, not inline?
|
||||
- Can Claude load context incrementally as needed?
|
||||
|
||||
---
|
||||
|
||||
## Plugin
|
||||
|
||||
### Key Questions
|
||||
|
||||
- **Cohesion**: What unifies the skills in this plugin? What's the theme?
|
||||
- **Boundary**: Where does this plugin's responsibility end? What's adjacent but separate?
|
||||
- **Dependencies**: Does this plugin depend on others? Should it?
|
||||
- **User**: Who installs this plugin? What problem are they solving?
|
||||
|
||||
### Anti-patterns to Critique
|
||||
|
||||
| Anti-pattern | What to look for |
|
||||
|--------------|------------------|
|
||||
| Kitchen-sink | Plugin contains unrelated skills that happen to be made by same author |
|
||||
| Overlapping scope | Skills within plugin have unclear boundaries with each other |
|
||||
| Missing entry point | No clear CLAUDE.md or SKILL.md directing users to capabilities |
|
||||
| Dependency bloat | Plugin requires many other plugins to function |
|
||||
|
||||
### Structure Check
|
||||
|
||||
- Does plugin have clear CLAUDE.md explaining what it offers?
|
||||
- Are skills cohesive (related purpose) not just co-located?
|
||||
- Is the plugin installable and usable independently?
|
||||
|
||||
---
|
||||
|
||||
## Agent
|
||||
|
||||
### Key Questions
|
||||
|
||||
- **Task fit**: What specific task type does this agent handle? When is it dispatched?
|
||||
- **Tools**: What tools does the agent need? What tools should it NOT have?
|
||||
- **Autonomy level**: How much should it decide on its own vs. ask for guidance?
|
||||
- **Output contract**: What does the agent return to the caller?
|
||||
|
||||
### Anti-patterns to Critique
|
||||
|
||||
| Anti-pattern | What to look for |
|
||||
|--------------|------------------|
|
||||
| Tool overload | Agent has access to tools it doesn't need |
|
||||
| Unclear boundaries | Agent's responsibility overlaps with other agents or main thread |
|
||||
| Missing output contract | Caller doesn't know what to expect back |
|
||||
| Over-autonomy | Agent makes decisions that should be escalated |
|
||||
|
||||
### Dispatch Check
|
||||
|
||||
- Is it clear when to use this agent vs. doing the work in main thread?
|
||||
- Does the agent description in Task tool registry accurately describe capability?
|
||||
- Can the agent complete its task with the tools provided?
|
||||
|
||||
---
|
||||
|
||||
## Slash Command
|
||||
|
||||
### Key Questions
|
||||
|
||||
- **Trigger**: What does the user type to invoke? Is it memorable?
|
||||
- **Input**: What arguments does it accept? What's required vs. optional?
|
||||
- **Output**: What happens when invoked? File created? Action taken? Skill loaded?
|
||||
- **Discoverability**: How does user learn this command exists?
|
||||
|
||||
### Anti-patterns to Critique
|
||||
|
||||
| Anti-pattern | What to look for |
|
||||
|--------------|------------------|
|
||||
| Overloaded command | Command does too many different things based on args |
|
||||
| Unclear invocation | User can't guess what to type |
|
||||
| Missing feedback | Command runs silently without confirming action |
|
||||
| Duplicates skill | Command is just an alias for a skill with no added value |
|
||||
|
||||
### Naming Check
|
||||
|
||||
- Is the command name a verb? (commit, audit, review)
|
||||
- Is it 1-2 words max?
|
||||
- Does it conflict with existing commands?
|
||||
|
||||
---
|
||||
|
||||
## Description (Frontmatter)
|
||||
|
||||
### Key Questions
|
||||
|
||||
- **Escape hatch**: Does the description help Claude know when NOT to use this?
|
||||
- **Trigger clarity**: Is it clear what user intent/request activates this?
|
||||
- **Specificity**: Is it specific enough to avoid false positives?
|
||||
|
||||
### Anti-patterns to Critique
|
||||
|
||||
| Anti-pattern | What to look for |
|
||||
|--------------|------------------|
|
||||
| Too broad | Description matches requests it shouldn't handle |
|
||||
| Too narrow | Description misses valid use cases |
|
||||
| Missing negative triggers | No "don't use when" guidance |
|
||||
| Marketing language | Description sells rather than describes |
|
||||
|
||||
### Test
|
||||
|
||||
Read the description and ask: "If I were Claude seeing a user request, would this description help me decide correctly whether to use this?"
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
# Brainstorming Workflow
|
||||
|
||||
Refines rough ideas into conceptual designs through drafting, self-critique, and trade-off surfacing.
|
||||
|
||||
## When to Use
|
||||
|
||||
**Explicit trigger only.** Use when user:
|
||||
- Asks to "brainstorm" a concept
|
||||
- Wants to "explore" or "think through" an idea
|
||||
- Has a nascent idea not ready for implementation
|
||||
- Requests interactive design refinement
|
||||
|
||||
**Do not use** when user provides clear instructions and expects execution. Most interactions are execution-focused - don't add friction by brainstorming unsolicited.
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Draft first, ask later.** Don't pepper the user with questions. Use your judgment to draft, then surface gaps.
|
||||
|
||||
2. **Conceptual, not implementation.** Output is design decisions and constraints. The implementing AI decides HOW.
|
||||
|
||||
3. **Always checkpoint.** Write a defer file after critique. User can leave or continue - nothing is lost.
|
||||
|
||||
4. **Subagents for execution.** If continuing to implementation, dispatch subagents. Keep main thread focused on coordination.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Phase 1: Understand Intent
|
||||
|
||||
Quickly gather context:
|
||||
- What problem does this solve?
|
||||
- Who/what consumes it?
|
||||
- What constraints exist?
|
||||
|
||||
If context is clear from the user's input, skip questions. If ambiguous, ask 1-2 clarifying questions max - don't interrogate.
|
||||
|
||||
Check domain hooks in `domain-hooks.md` for architect-specific questions.
|
||||
|
||||
### Phase 2: Draft Conceptual Design
|
||||
|
||||
Generate a complete conceptual draft:
|
||||
- Purpose and scope
|
||||
- Key design decisions
|
||||
- Structure/architecture (conceptual, not file-by-file)
|
||||
- Constraints and boundaries
|
||||
- What it explicitly does NOT do
|
||||
|
||||
**Adapt scope to input clarity:**
|
||||
- Nascent idea → high-level design, major decision points
|
||||
- Clear concept → fuller design with more specifics
|
||||
- Near-implementation ready → detailed design, edge cases
|
||||
|
||||
### Phase 3: Self-Critique
|
||||
|
||||
Before showing the user, critique your draft against:
|
||||
- Alignment with stated intent
|
||||
- Domain anti-patterns (see `domain-hooks.md`)
|
||||
- Missing pieces or unstated assumptions
|
||||
- Over-engineering or unnecessary complexity
|
||||
|
||||
Note issues found. Revise draft if issues are clear fixes. Flag trade-offs that need user input.
|
||||
|
||||
### Phase 4: Present & Checkpoint
|
||||
|
||||
Present the design to the user. Then immediately write a defer file (see `../defer-work/workflow.md`) containing:
|
||||
- The conceptual design
|
||||
- Trade-offs requiring decisions
|
||||
- Issues found during critique
|
||||
- Tasks to move toward implementation
|
||||
|
||||
Tell the user:
|
||||
|
||||
```
|
||||
Design captured to .claude/deferred/YYYY-MM-DD-<topic>.md
|
||||
|
||||
Trade-offs to decide:
|
||||
- <trade-off 1>
|
||||
- <trade-off 2>
|
||||
|
||||
You can:
|
||||
- Clear context and resume later with "pick up <topic>"
|
||||
- Continue here - I'll work from the checkpoint
|
||||
|
||||
What would you like to do?
|
||||
```
|
||||
|
||||
### Phase 5: Resolution (if continuing)
|
||||
|
||||
If user continues:
|
||||
1. Work through trade-offs with user
|
||||
2. Update defer file with decisions
|
||||
3. Use subagents to execute tasks from the defer file
|
||||
4. Subagents check off tasks as they complete
|
||||
5. Main thread coordinates, delegates execution
|
||||
|
||||
## Output Format
|
||||
|
||||
The brainstorming output is a **conceptual design**, not a specification. Example structure:
|
||||
|
||||
```markdown
|
||||
## Purpose
|
||||
<What this solves, in one paragraph>
|
||||
|
||||
## Design Decisions
|
||||
- <Decision 1>: <choice and why>
|
||||
- <Decision 2>: ...
|
||||
|
||||
## Structure
|
||||
<Conceptual architecture - components, relationships, boundaries>
|
||||
|
||||
## Constraints
|
||||
- <What this must do>
|
||||
- <What this must NOT do>
|
||||
|
||||
## Open Questions
|
||||
- <Questions that surfaced during drafting>
|
||||
```
|
||||
|
||||
Keep it scannable. User should understand the design in 60 seconds.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# cc-os Plugin and Skill Naming Convention
|
||||
|
||||
Canonical repo copy of the convention established 2026-07-02 (previously only in the
|
||||
SecondBrain vault note `cc-os-plugin-skill-naming-convention.md`, which now points here).
|
||||
Read this before naming ANY new cc-os plugin, skill, or slash command.
|
||||
|
||||
## Core principles
|
||||
|
||||
1. **Plugins use an `os-` prefix**: `os-[domain]` (e.g. `os-vault`, `os-backlog`). The
|
||||
prefix ties the plugin to cc-os and distinguishes it from unrelated tools
|
||||
(`memsearch` is NOT cc-os).
|
||||
2. **Skills are verb-first**: the plugin namespace answers "what is this about?", the
|
||||
skill name answers "what action?" — `query`, `write`, `reorganize`, producing
|
||||
invocations like `/os-vault:query`.
|
||||
3. **Multi-word skills are kebab-case, verb + noun**: `design-template`,
|
||||
`onboard-project`. Kebab-case in plugin `name` fields is a Claude Code requirement;
|
||||
cc-os extends it to skill names by convention.
|
||||
4. **No `commands/` dispatcher directories** — skills are the only invocation surface.
|
||||
|
||||
## Registration mechanics
|
||||
|
||||
**Never set a `name:` field in SKILL.md frontmatter.** The directory name is the skill
|
||||
name; with `name:` absent, Claude Code registers the namespaced `/plugin:skill` form.
|
||||
An explicit `name: find` collapses the command to bare unnamespaced `/find`
|
||||
(discovered 2026-07-04 in os-adr and os-doc-hygiene; fixed by deleting the lines).
|
||||
After any SKILL.md edit, run `bin/refresh-plugins` — installs copy files into
|
||||
`~/.claude/plugins/cache/`, so source edits don't reach sessions until refreshed.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- Plugin name without the `os-` prefix (`vault` instead of `os-vault`).
|
||||
- Noun-stacked skill names (`vault-query` instead of `query`).
|
||||
- Generic verbs duplicating the namespace context (`/memory-find` instead of
|
||||
`/os-vault:find` — the namespace provides the context).
|
||||
- CamelCase or snake_case in plugin or skill names.
|
||||
|
||||
## Open questions
|
||||
|
||||
Agent-name and hook-script-name governance are deferred until their use patterns
|
||||
stabilize; hook scripts currently use snake_case (Python convention) and are not
|
||||
externally visible.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- ADR-0025 (Ruby structure standard — the structural layer below this naming layer)
|
||||
- `references/layout/plugin-structure.md`, `references/layout/skill-structure.md`
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
# Plugin Data Convention
|
||||
|
||||
Standard convention for plugins that create files in user projects.
|
||||
|
||||
## Path Convention
|
||||
|
||||
```
|
||||
.claude/plugin-data/<plugin-name>/<data-type>/
|
||||
```
|
||||
|
||||
Plugins creating files in user projects MUST use this path structure.
|
||||
|
||||
## Examples
|
||||
|
||||
| Plugin | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| ux | `.claude/plugin-data/ux/audits/` | UX audit findings |
|
||||
| seo-audit | `.claude/plugin-data/seo-audit/reports/` | SEO audit reports |
|
||||
| commit | `.claude/plugin-data/commit/history/` | If commit needed state |
|
||||
|
||||
## File Naming
|
||||
|
||||
Use descriptive, sortable names:
|
||||
|
||||
```
|
||||
<date>-<type>-<subject>.md
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `2024-01-15-heuristic-checkout-flow.md`
|
||||
- `2024-01-15-accessibility-homepage.md`
|
||||
- `2024-01-15-report-full-audit.md`
|
||||
|
||||
## Gitignore
|
||||
|
||||
User projects should add to `.gitignore`:
|
||||
|
||||
```
|
||||
.claude/plugin-data/
|
||||
```
|
||||
|
||||
This data is transient and project-specific. It should not be committed.
|
||||
|
||||
For specific plugin types, consider more granular patterns:
|
||||
|
||||
```
|
||||
# Temporary files
|
||||
**/.claude/plugin-data/<plugin-name>/tmp/
|
||||
|
||||
# Cached data
|
||||
**/.claude/plugin-data/<plugin-name>/cache/
|
||||
|
||||
# Log files
|
||||
**/.claude/plugin-data/<plugin-name>/*.log
|
||||
```
|
||||
|
||||
## Artifact Lifecycle
|
||||
|
||||
When designing functionality that creates plugin data files, consider cleanup needs.
|
||||
|
||||
### Lifecycle Types
|
||||
|
||||
| Type | Example | Recommended Approach |
|
||||
|------|---------|---------------------|
|
||||
| Audit findings | UX heuristic evaluation | Manual cleanup, historical value |
|
||||
| Temporary state | In-progress work | Auto-archive or TTL-based cleanup |
|
||||
| Reports | SEO audit summary | Archival policy, version-dated |
|
||||
|
||||
### Documentation Requirements
|
||||
|
||||
1. **In the skill/workflow**: Document expected lifecycle of created artifacts
|
||||
2. **In plugin CLAUDE.md**: Add cleanup guidance section
|
||||
|
||||
## Plugin Author Checklist
|
||||
|
||||
When your plugin creates files in user projects:
|
||||
|
||||
- [ ] Use `.claude/plugin-data/<plugin>/` path
|
||||
- [ ] Choose semantic subdirectory name (audits/, reports/, state/)
|
||||
- [ ] Document cleanup expectations in workflow
|
||||
- [ ] Consider if automated cleanup is warranted
|
||||
- [ ] Add note to generated files about their transient nature
|
||||
- [ ] Include cleanup guidance in plugin CLAUDE.md
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# Defer File Template
|
||||
|
||||
Use this structure when creating defer files.
|
||||
|
||||
---
|
||||
|
||||
```markdown
|
||||
# Deferred: <Brief Title>
|
||||
|
||||
Created: YYYY-MM-DD
|
||||
Status: In Progress | Blocked | Ready to Resume
|
||||
|
||||
## Context
|
||||
|
||||
<2-3 sentences: What were we doing? What triggered this deferral?>
|
||||
|
||||
## Original Intent
|
||||
|
||||
<What is the user trying to accomplish? Capture the goal, not the implementation.>
|
||||
|
||||
## Findings
|
||||
|
||||
<What did the critique/check surface? List issues, trade-offs, or open questions.>
|
||||
|
||||
### Trade-offs Requiring Decision
|
||||
|
||||
- [ ] **<Trade-off 1>**: <Option A> vs <Option B>
|
||||
- A: <pros/cons>
|
||||
- B: <pros/cons>
|
||||
- Recommendation: <if any>
|
||||
|
||||
- [ ] **<Trade-off 2>**: ...
|
||||
|
||||
### Issues Found
|
||||
|
||||
- [ ] <Issue 1>: <brief description>
|
||||
- [ ] <Issue 2>: ...
|
||||
|
||||
## Tasks
|
||||
|
||||
<Concrete tasks to complete this work. Check off as completed.>
|
||||
|
||||
- [ ] Resolve trade-off decisions with user
|
||||
- [ ] <Task based on findings>
|
||||
- [ ] <Implementation task>
|
||||
- [ ] Verify work meets original intent
|
||||
- [ ] Delete this file when complete
|
||||
|
||||
## Resumption Notes
|
||||
|
||||
<Any context that would help a fresh session pick this up:>
|
||||
- Key files: <paths>
|
||||
- Related docs: <paths>
|
||||
- Commands to run: <if any>
|
||||
|
||||
## Implementation Guidance
|
||||
|
||||
When resuming this work:
|
||||
1. Read this file completely before starting
|
||||
2. Use subagents (Task tool) for individual tasks - keeps context focused
|
||||
3. Have subagents check off tasks in this file as they complete
|
||||
4. Coordinate from main thread, delegate execution to subagents
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Notes
|
||||
|
||||
**Keep it conceptual.** Capture decisions and constraints, not implementation details. The implementing AI has patterns and context to decide HOW.
|
||||
|
||||
**Tasks should be actionable.** Each task should be completable by a subagent with access to the file and codebase. Avoid vague tasks like "figure out the approach."
|
||||
|
||||
**Trade-offs need options.** Don't just flag a trade-off - present the options and your recommendation. User should be able to decide quickly.
|
||||
|
||||
**Include cleanup task.** Always end with "Delete this file when complete" so defer files don't accumulate.
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# Defer Work Pattern
|
||||
|
||||
Captures work-in-progress to a file so users can resume in a fresh session without losing context.
|
||||
|
||||
## When to Use
|
||||
|
||||
- After critique/trade-off checks surface issues requiring decisions
|
||||
- When context window is getting full
|
||||
- When work is too complex to complete in current session
|
||||
- When user explicitly requests deferral
|
||||
|
||||
## Core Principle
|
||||
|
||||
**Always write the file.** Don't wait for the user to say "defer." After any critique or trade-off check, write the defer file immediately. This gives the user options:
|
||||
|
||||
1. **Leave** - Context is captured. Start fresh session anytime.
|
||||
2. **Continue** - Work from the defer file as the task tracker.
|
||||
|
||||
Either way, the file exists and nothing is lost.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Write the Defer File
|
||||
|
||||
Location: `.claude/deferred/YYYY-MM-DD-<topic>.md`
|
||||
|
||||
Use the template in `template.md`. Include:
|
||||
- What we were doing and why
|
||||
- Findings from critique/check
|
||||
- Open questions requiring decisions
|
||||
- Concrete tasks to complete the work
|
||||
|
||||
### 2. Inform the User
|
||||
|
||||
After writing the file, tell the user:
|
||||
|
||||
```
|
||||
Checkpoint saved to .claude/deferred/YYYY-MM-DD-<topic>.md
|
||||
|
||||
You can:
|
||||
- Clear context now and resume later with "pick up <topic>"
|
||||
- Continue here - I'll work from the checkpoint file
|
||||
|
||||
What would you like to do?
|
||||
```
|
||||
|
||||
### 3. If Continuing
|
||||
|
||||
When the user chooses to continue (or in a resumed session):
|
||||
|
||||
**Use subagents for efficiency.** Dispatch Task tool agents to:
|
||||
- Read the defer file for context
|
||||
- Execute individual tasks
|
||||
- Mark tasks complete in the file
|
||||
|
||||
This keeps the main thread focused on coordination, not implementation. The defer file becomes the shared source of truth between main thread and subagents.
|
||||
|
||||
### 4. Cleanup
|
||||
|
||||
When all tasks are complete:
|
||||
- Verify the work meets original intent
|
||||
- Delete the defer file
|
||||
- Inform the user the deferred work is complete
|
||||
|
||||
## File Naming
|
||||
|
||||
Format: `YYYY-MM-DD-<topic>.md`
|
||||
|
||||
Examples:
|
||||
- `2026-01-20-skill-brainstorming.md`
|
||||
- `2026-01-20-plugin-audit-findings.md`
|
||||
- `2026-01-20-agent-design-tradeoffs.md`
|
||||
|
||||
Keep `<topic>` short but descriptive. It's used for resumption: "pick up <topic>".
|
||||
|
||||
## Resumption
|
||||
|
||||
Users can resume with natural language:
|
||||
- "pick up skill brainstorming"
|
||||
- "continue the plugin audit"
|
||||
- "what deferred work do I have?"
|
||||
|
||||
On resumption:
|
||||
1. Read the defer file
|
||||
2. Summarize current state
|
||||
3. Ask which task to tackle first (or recommend one)
|
||||
4. Use subagents to execute tasks
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
# Knowledge Philosophy
|
||||
|
||||
How to decide what to document in plugins, skills, and workflows.
|
||||
|
||||
## Core Principle: Trust AI Training First
|
||||
|
||||
AI models know frameworks, best practices, and domain knowledge from training. Don't re-document what they already know.
|
||||
|
||||
**Examples of what NOT to document:**
|
||||
- What Nielsen's 10 heuristics mean
|
||||
- How WCAG guidelines work
|
||||
- Standard programming patterns
|
||||
- Framework APIs and conventions
|
||||
|
||||
**What TO document:**
|
||||
- Rubrics (what to evaluate, severity scales)
|
||||
- Templates (output formats, task file structures)
|
||||
- Process steps (sequence of actions, decision points)
|
||||
- Your specific conventions (naming, file locations)
|
||||
|
||||
## Decision Framework
|
||||
|
||||
### When to add documentation
|
||||
|
||||
Only add explicit documentation when:
|
||||
1. **Consistent failure** - AI repeatedly gets something wrong
|
||||
2. **Output variance** - Results vary significantly across runs
|
||||
3. **Specific rubric needed** - You need consistent scoring/evaluation
|
||||
4. **Custom template required** - Output must match a specific format
|
||||
5. **Non-obvious convention** - Your codebase has conventions AI can't infer
|
||||
|
||||
### When NOT to add documentation
|
||||
|
||||
Skip documentation when:
|
||||
- AI already knows it from training
|
||||
- It's explaining theory rather than specifying process
|
||||
- It's describing a framework rather than applying it
|
||||
- A quick inline hint would suffice
|
||||
|
||||
## Iterate to Specificity
|
||||
|
||||
**Start minimal.** Run the skill/workflow. Observe what the AI gets wrong.
|
||||
|
||||
**Add incrementally.** Each addition should be:
|
||||
- The minimum change needed to fix the observed problem
|
||||
- Targeted at the specific failure, not general improvement
|
||||
- Testable (you can verify it fixed the issue)
|
||||
|
||||
**Resist preemptive documentation.** Don't document "just in case." Document when you have evidence it's needed.
|
||||
|
||||
## Workflow Document Formula
|
||||
|
||||
Workflow documents should contain:
|
||||
|
||||
```
|
||||
Rubric + Template + Process Steps
|
||||
(what to + (output + (sequence of
|
||||
evaluate) format) actions)
|
||||
```
|
||||
|
||||
They should NOT contain:
|
||||
|
||||
```
|
||||
Theory + Explanations + Framework descriptions
|
||||
(why it + (how it + (what the framework
|
||||
matters) works) is)
|
||||
```
|
||||
|
||||
## Guidance Levels
|
||||
|
||||
| Level | What to specify | What to delegate |
|
||||
|-------|-----------------|------------------|
|
||||
| Role/Orchestrator | What workflows to run, when to synthesize, what to present | How workflows execute their evaluations |
|
||||
| Workflow | What to evaluate, output format, severity scale | How to apply the framework, what the framework means |
|
||||
| Subagent execution | (nothing - receives workflow doc) | Everything about execution |
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
### The Encyclopedia
|
||||
**Wrong:** 500-line reference doc explaining a framework.
|
||||
**Right:** 50-line doc with checklist, severity scale, and output template.
|
||||
|
||||
### Preemptive Specification
|
||||
**Wrong:** Documenting every edge case before seeing if AI handles them.
|
||||
**Right:** Running the skill, noting failures, adding targeted fixes.
|
||||
|
||||
### Inline Theory
|
||||
**Wrong:** Workflow doc starts with "Nielsen's heuristics were developed in 1994..."
|
||||
**Right:** Workflow doc starts with "Evaluate against these 10 heuristics:"
|
||||
|
||||
### Copy-Paste Documentation
|
||||
**Wrong:** Duplicating framework descriptions from the web.
|
||||
**Right:** Referencing what AI knows, specifying only your application of it.
|
||||
|
||||
## Practical Test
|
||||
|
||||
Before adding documentation, ask:
|
||||
|
||||
1. **Would a senior AI researcher need this written down?** If they'd know it from training, skip it.
|
||||
2. **Is this a rubric, template, or process step?** If not, probably skip it.
|
||||
3. **Did I observe a failure this fixes?** If not, wait until you do.
|
||||
4. **Is this the minimum change needed?** If not, reduce scope.
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Writing Descriptions
|
||||
|
||||
Short guidance for description fields on skills, agents, commands, and plugins.
|
||||
|
||||
## Formula
|
||||
|
||||
`[Action verb] [what]. [Unambiguous trigger].`
|
||||
|
||||
- Action verb (Build, Generate, Create, Run)
|
||||
- What it does (2-5 words)
|
||||
- Trigger that prevents an "escape hatch" — a reader should not be able to
|
||||
rationalize skipping the tool while still doing the task it covers.
|
||||
|
||||
Examples:
|
||||
- skill: `Build Claude Code skills. Use when creating a skill.`
|
||||
- agent: `Review code against implementation plan. Use after completing a major project step.`
|
||||
- command: `Stage and commit changes. Use when ready to commit.`
|
||||
|
||||
## Full process
|
||||
|
||||
For the complete drafting process (reading the formula reference, the
|
||||
escape-hatch test, and how to return the result), run:
|
||||
→ `${CLAUDE_PLUGIN_ROOT}/workflows/generate-description.md`
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# Plugin Registration
|
||||
|
||||
How to make a plugin discoverable and installable in a marketplace.
|
||||
|
||||
## Registry file
|
||||
|
||||
`.claude-plugin/marketplace.json` at the host repo's root controls which
|
||||
plugins are discoverable and installable.
|
||||
|
||||
## Adding a plugin
|
||||
|
||||
Add an entry to the `plugins` array:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "plugin-name",
|
||||
"source": "./plugin-name",
|
||||
"description": "Brief description of what it does"
|
||||
}
|
||||
```
|
||||
|
||||
**Required fields:**
|
||||
- `name` - Plugin identifier (must match directory name)
|
||||
- `source` - Relative path to plugin directory (may be nested, e.g. `./plugins/plugin-name`)
|
||||
- `description` - One-line description for discovery
|
||||
|
||||
Prefer the `register`/`unregister` operations of
|
||||
`${CLAUDE_PLUGIN_ROOT}/workflows/plugin-config.md` (backed by
|
||||
`plugin_config.rb`) over hand-editing this file — the script verifies the
|
||||
write and supports `--marketplace_file` to target a marketplace.json
|
||||
anywhere on disk. If no marketplace file exists at all, marketplace checks
|
||||
are skipped rather than failing.
|
||||
|
||||
## Checklist
|
||||
|
||||
1. Plugin directory exists with `.claude-plugin/plugin.json`
|
||||
2. Entry added to `.claude-plugin/marketplace.json`
|
||||
3. Description matches between both files
|
||||
|
||||
## Verification
|
||||
|
||||
After registering, the plugin should be installable via:
|
||||
```
|
||||
/install plugin-name@<marketplace-id>
|
||||
```
|
||||
|
||||
## Project-Scoped Installation
|
||||
|
||||
After `/install plugin@marketplace --scope=project`:
|
||||
|
||||
1. Plugin is registered in `~/.claude/plugins/installed_plugins.json`
|
||||
2. **REQUIRED**: Enable in project via `/enable-plugin` or manually add to `.claude/settings.json`
|
||||
|
||||
Without step 2, the plugin will not activate in the project context.
|
||||
|
||||
### Manual Enablement
|
||||
|
||||
If not using `/enable-plugin`, create or update `.claude/settings.json` in the target project:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"{plugin-name}@{marketplace-id}": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"decomposer@cc-plugins": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Plugin Structure
|
||||
|
||||
Minimal structure for a Claude Code plugin.
|
||||
|
||||
## Required
|
||||
|
||||
```
|
||||
plugin-name/
|
||||
└── .claude-plugin/
|
||||
└── plugin.json # {"name": "plugin-name"}
|
||||
```
|
||||
|
||||
## Common additions
|
||||
|
||||
```
|
||||
plugin-name/
|
||||
├── .claude-plugin/
|
||||
│ └── plugin.json
|
||||
├── skills/
|
||||
│ └── skill-name/
|
||||
│ └── SKILL.md
|
||||
└── CLAUDE.md # Plugin-specific guidance
|
||||
```
|
||||
|
||||
## Marketplace layout
|
||||
|
||||
The standard pattern is a single flat marketplace: each plugin lives as a
|
||||
top-level directory alongside a `.claude-plugin/marketplace.json` that lists
|
||||
every plugin's `source` as `./plugin-name`.
|
||||
|
||||
Marketplace sources may also be nested (e.g. `./plugins/<name>`) — check the
|
||||
host repo's `.claude-plugin/marketplace.json` for its actual convention
|
||||
before assuming the flat layout.
|
||||
|
||||
## Creating new plugins
|
||||
|
||||
Follow the cc-architect workflow:
|
||||
→ `${CLAUDE_PLUGIN_ROOT}/workflows/create-plugin.md`
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# What Makes a Good reference.md
|
||||
|
||||
`reference.md` is the routing file required in every `references/`
|
||||
subdirectory (see
|
||||
`${CLAUDE_PLUGIN_ROOT}/references/layout/skill-structure.md`). It exists so
|
||||
a model can decide what to read next without opening every knowledge doc.
|
||||
|
||||
This file is a pointer to the full pattern, kept here so
|
||||
`references/layout/` stays a complete map of the layout — the actual
|
||||
authoring detail lives in progressive-disclosure:
|
||||
|
||||
→ `${CLAUDE_PLUGIN_ROOT}/references/progressive-disclosure/reference-md-pattern.md`
|
||||
(required sections: what's here table, when to use this module, model
|
||||
guidance; anti-patterns like duplicating child content or exceeding ~50
|
||||
lines)
|
||||
|
||||
## When to split a reference doc out
|
||||
|
||||
A `reference.md` should point to a separate knowledge doc — rather than
|
||||
inline the knowledge itself — whenever the content stops being "what's
|
||||
here and when to read it" and becomes the knowledge itself. Concretely:
|
||||
|
||||
- The explanation would push `reference.md` past ~50 lines
|
||||
- The content is only needed for a subset of workflows, not every reader
|
||||
of this directory
|
||||
- The doc covers a distinct concept that could stand alone under a
|
||||
different topic subdirectory
|
||||
|
||||
Full splitting criteria (the "always needed together?" test, size
|
||||
guidelines, how to split, depth limits) are in:
|
||||
→ `${CLAUDE_PLUGIN_ROOT}/references/progressive-disclosure/splitting-knowledge.md`
|
||||
|
||||
## Quick checklist
|
||||
|
||||
1. Table of files/subdirs with purpose + "when to read"
|
||||
2. "When to use this module" section
|
||||
3. "Model guidance" (cheap vs. stronger model)
|
||||
4. No duplication of child file content
|
||||
5. <=50 lines total
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
# Skill Structure
|
||||
|
||||
The standard anatomy of a Claude Code skill, and how content is split across
|
||||
it via progressive disclosure.
|
||||
|
||||
## Required
|
||||
|
||||
```
|
||||
skills/<skill-name>/
|
||||
└── SKILL.md
|
||||
```
|
||||
|
||||
`SKILL.md` is routing-only: "when to use" plus pointers to `workflows/*.md`
|
||||
and `references/*`. It should not inline substantial content — see
|
||||
`${CLAUDE_PLUGIN_ROOT}/references/progressive-disclosure/splitting-knowledge.md`
|
||||
for the size guidance that applies here too (SKILL.md itself should stay
|
||||
<=50 lines).
|
||||
|
||||
## Frontmatter expectations
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: skill-name
|
||||
description: [Action verb] [what]. [Unambiguous trigger].
|
||||
---
|
||||
```
|
||||
|
||||
- `name` - kebab-case, <=64 chars, matches the directory name
|
||||
- `description` - follows the formula in
|
||||
`${CLAUDE_PLUGIN_ROOT}/references/layout/descriptions.md`; this is the
|
||||
only field another model sees before deciding whether to open the skill,
|
||||
so the trigger must be unambiguous
|
||||
|
||||
## Common additions
|
||||
|
||||
```
|
||||
skills/<skill-name>/
|
||||
├── SKILL.md
|
||||
├── workflows/
|
||||
│ └── <workflow-name>.md # one file per distinct task the skill handles
|
||||
├── references/
|
||||
│ └── <topic>/
|
||||
│ ├── reference.md # required routing file, see reference.md below
|
||||
│ └── <knowledge-doc>.md
|
||||
├── templates/ # repetitive structures to copy/fill
|
||||
├── scripts/ # mechanical, deterministic operations
|
||||
└── state/ # gitignored working data, if the skill needs it
|
||||
```
|
||||
|
||||
- `workflows/` - one file per task the skill handles (e.g. "create X",
|
||||
"audit X"). Each workflow states inputs, subtasks with subagent-tier
|
||||
hints, and outputs.
|
||||
- `references/` - knowledge organized by topic subdirectory. Every
|
||||
subdirectory needs a `reference.md` (see
|
||||
`${CLAUDE_PLUGIN_ROOT}/references/layout/reference.md`).
|
||||
- `templates/` and `scripts/` are optional and only added when the skill
|
||||
has mechanical, repeatable work to offload from the model.
|
||||
|
||||
## Progressive disclosure
|
||||
|
||||
Splitting is not optional past a certain size — it's how the skill stays
|
||||
navigable for a model that only reads what it needs:
|
||||
|
||||
- SKILL.md: routing only, <=50 lines
|
||||
- `reference.md` files: <=50 lines, list what's here and when to read each
|
||||
file, never duplicate the child files' content
|
||||
- Knowledge docs: target <=150 lines, hard limit ~200 (split if approaching)
|
||||
- Directory depth: <=3 levels (`references/topic/subtopic/`)
|
||||
|
||||
Full splitting criteria (the "always needed together?" test, signs a doc
|
||||
should split, how to split) live in
|
||||
`${CLAUDE_PLUGIN_ROOT}/references/progressive-disclosure/splitting-knowledge.md`.
|
||||
|
||||
## Reversion protection (optional, added once a skill stabilizes)
|
||||
|
||||
- `invariants.md` at skill root - behaviors that must not change without
|
||||
explicit approval
|
||||
- `examples/golden/` - golden examples that pin correct behavior
|
||||
- `.decisions/` - dated decision records
|
||||
|
||||
See `${CLAUDE_PLUGIN_ROOT}/references/tool-patterns/reversion-protection.md`.
|
||||
|
||||
## Creating a new skill
|
||||
|
||||
Follow the cc-architect workflow:
|
||||
→ `${CLAUDE_PLUGIN_ROOT}/workflows/create-skill.md`
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# Workflow Structure
|
||||
|
||||
The standard anatomy of a `workflows/*.md` document, and what makes one
|
||||
compliant.
|
||||
|
||||
## Required sections
|
||||
|
||||
```
|
||||
# Workflow: <Name>
|
||||
|
||||
## When to use
|
||||
## Inputs required
|
||||
## Output
|
||||
## Subtasks (or "Phases")
|
||||
```
|
||||
|
||||
- **When to use** - trigger conditions, so a router (human or model) can
|
||||
match a request to this workflow without reading the whole file.
|
||||
- **Inputs required** - named parameters the caller must supply.
|
||||
- **Output** - what gets written and where (file path, or "returned to
|
||||
caller").
|
||||
- **Subtasks/Phases** - the ordered steps. Each one states:
|
||||
- **Who** does it - main thread or subagent, with model tier
|
||||
(Haiku/Sonnet/Opus) if a subagent
|
||||
- **Must read** - any file the step needs, if applicable
|
||||
- **Do** - the concrete instructions
|
||||
|
||||
## Ownership clarity
|
||||
|
||||
Every subtask/phase must unambiguously assign ownership:
|
||||
- Main-thread tasks require user interaction or judgment about scope
|
||||
- Subagent tasks are dispatched with an explicit model tier
|
||||
- A main-thread step must never silently perform subagent-tier work (file
|
||||
writes, content drafting) — delegate it
|
||||
|
||||
## Completeness checklist
|
||||
|
||||
- [ ] Purpose and trigger conditions are clear
|
||||
- [ ] Inputs are well-defined (names, not just prose)
|
||||
- [ ] Output format and location specified
|
||||
- [ ] Every subtask/phase has necessary context (what to read)
|
||||
- [ ] Verification step included if the workflow modifies files
|
||||
- [ ] No missing steps in the flow (a reader could execute this cold)
|
||||
|
||||
## Quality bar
|
||||
|
||||
- Logical step ordering — later steps don't need something an earlier step
|
||||
hasn't produced yet
|
||||
- Subagent model assignments match the work (mechanical → Haiku, drafting/
|
||||
analysis → Sonnet, judgment/synthesis → Opus)
|
||||
- File artifacts (task docs, scratch files) are passed between phases by
|
||||
path, not assumed shared context
|
||||
- No redundant steps
|
||||
|
||||
## Minimal skeleton
|
||||
|
||||
```markdown
|
||||
# Workflow: <name>
|
||||
|
||||
## When to use
|
||||
|
||||
- <trigger 1>
|
||||
- <trigger 2>
|
||||
|
||||
## Inputs required
|
||||
|
||||
- **<input_name>** - <description>
|
||||
|
||||
## Output
|
||||
|
||||
- <what gets written, and where>
|
||||
|
||||
---
|
||||
|
||||
## Subtasks
|
||||
|
||||
### 1. <step name>
|
||||
|
||||
Subagent: <main | haiku | sonnet | opus>.
|
||||
Read: <files this step needs, if any>
|
||||
|
||||
**Do:**
|
||||
- <instruction>
|
||||
```
|
||||
|
||||
## Creating a new workflow
|
||||
|
||||
Follow the cc-architect workflow:
|
||||
→ `${CLAUDE_PLUGIN_ROOT}/workflows/create-workflow.md`
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
# Plugin Architecture Philosophy - Review Report
|
||||
|
||||
## Summary
|
||||
|
||||
The document is well-structured and provides comprehensive guidance for building Claude Code plugins. It successfully establishes a coherent philosophy around skill orchestration, subagent delegation, and workflow composition. However, there are several gaps in implementation details (particularly around friction tracking infrastructure and phase transitions), some inconsistencies with referenced documents, and one reference to a non-existent directory structure.
|
||||
|
||||
## Document Reviewed
|
||||
|
||||
**Path:** `/home/jared/dev/cc-plugins/cc-architect/references/plugin-architecture-philosophy.md`
|
||||
|
||||
**Purpose:** Defines the architectural philosophy for Claude Code plugins, covering plugin hierarchy, lifecycle management, core components (skills, workflows, scripts, hooks), execution model, model selection, and anti-patterns.
|
||||
|
||||
**Recent additions reviewed:**
|
||||
- Plugin Lifecycle section with friction tracking phases
|
||||
- Command Consolidation decision tree
|
||||
- Model Selection friction tracking
|
||||
- Workflow Composition with UX plugin reference
|
||||
- Known Issues table
|
||||
- Under-scripting warnings
|
||||
|
||||
## Issues Found
|
||||
|
||||
### P0 - Blocking (must fix before using document)
|
||||
|
||||
**1. Friction directory does not exist**
|
||||
- **Section:** Plugin Lifecycle > Friction Files (lines 43-48)
|
||||
- **Problem:** The document specifies friction files live at `.claude/friction/{plugin-name}/{date}-{issue-slug}.md`, but this directory does not exist in the codebase. The friction tracking infrastructure is described but not implemented.
|
||||
- **Why it matters:** Someone following this document during Active Development would try to write friction files to a location that doesn't exist, with no template or example to follow.
|
||||
- **Suggested fix:** Either:
|
||||
1. Create the `.claude/friction/` directory with a README explaining the format and an example friction file, OR
|
||||
2. Add a note that friction tracking is planned but not yet implemented, with a TODO reference
|
||||
|
||||
**2. No definition of "reflection step" referenced**
|
||||
- **Section:** Plugin Lifecycle > Active Development (line 27), Enabling/Disabling Friction Tracking (lines 61-66)
|
||||
- **Problem:** The document says "Reflection step runs after every workflow execution" and "The reflection step itself is a workflow that can be attached to any skill's completion phase" but no such workflow exists, and there's no reference to where it would be defined.
|
||||
- **Why it matters:** A reader cannot implement friction tracking without knowing what this reflection step looks like or how to attach it to a skill.
|
||||
- **Suggested fix:** Either:
|
||||
1. Add a concrete example of what a reflection step contains and how it integrates with a skill (e.g. inline in the philosophy doc, alongside `references/self-improvement/friction-reflection.md`), OR
|
||||
2. Mark this as "planned but not implemented"
|
||||
|
||||
### P1 - High (fix soon)
|
||||
|
||||
**1. UX plugin reference path is incorrect**
|
||||
- **Section:** Workflow Composition (line 98), Integration (line 235)
|
||||
- **Problem:** The document references `../ux/` as the implementation reference. This is a relative path that only works from within the `references/` directory. The actual UX plugin is at `/home/jared/dev/cc-plugins/ux/`.
|
||||
- **Why it matters:** Someone reading the philosophy document from a different context (e.g., main CLAUDE.md) cannot find the UX plugin.
|
||||
- **Suggested fix:** Change to an absolute reference from the marketplace root: `ux/` (relative to marketplace root) with a note about where to find it.
|
||||
|
||||
**2. UX plugin example doesn't match actual implementation**
|
||||
- **Section:** Workflow Composition (lines 92-97)
|
||||
- **Problem:** The document describes three roles (UX Consultant, UX Strategist, UX Architect) with shared workflows. The actual UX plugin only implements UX Consultant in V1, with UX Strategist and UX Architect marked as "Planned for V2+".
|
||||
- **Why it matters:** The example suggests a more complex multi-role implementation than exists. Readers looking at the actual plugin will be confused by the mismatch.
|
||||
- **Suggested fix:** Update the example to accurately reflect V1 implementation, or add a note that the example shows the target architecture (V2+) not current state.
|
||||
|
||||
**3. Subagent pattern document uses different terminology**
|
||||
- **Section:** Core Components > Workflows (line 86), Integration (line 232)
|
||||
- **Problem:** The philosophy document references `subagent-pattern.md` and says findings go to "scratch workspace." But the actual `subagent-pattern.md` uses "scratch/" for findings, while the role-workflow pattern uses "task files." These are different mechanisms.
|
||||
- **Why it matters:** Unclear whether to use scratch files or task files for subagent findings.
|
||||
- **Suggested fix:** Clarify that:
|
||||
- Simple subagents can use scratch files
|
||||
- Role-Workflow pattern uses task files as shared state
|
||||
- Update `subagent-pattern.md` to mention task file pattern as an alternative or reference role-workflow pattern
|
||||
|
||||
**4. Missing link between "Task File Pattern" and where it's defined**
|
||||
- **Section:** Plugin Checklist (line 244)
|
||||
- **Problem:** Checklist item says "Task file template defined (if using task file pattern)" but doesn't link to where the task file pattern is documented.
|
||||
- **Why it matters:** Reader doesn't know if they should use the task file pattern or what it entails.
|
||||
- **Suggested fix:** Add a reference: "See `tool-patterns/role-workflow-pattern.md` for task file template structure"
|
||||
|
||||
**5. Phase transition criteria undefined**
|
||||
- **Section:** Plugin Lifecycle > Phases (lines 25-38)
|
||||
- **Problem:** The phases are described (Active Development, Tuned, Maintenance) but there's no guidance on when to transition between them. What makes a plugin "stable, producing reliable results"? How many sessions without friction before removing the reflection step?
|
||||
- **Why it matters:** Without criteria, phase transitions become subjective and inconsistent.
|
||||
- **Suggested fix:** Add concrete transition criteria:
|
||||
- Active Development → Tuned: "No P0-P1 friction for [N] executions" or "User explicitly marks as tuned"
|
||||
- Tuned → Maintenance: "No friction of any kind for [M] weeks"
|
||||
|
||||
### P2 - Medium (fix when convenient)
|
||||
|
||||
**1. Model Selection table differs from role-workflow pattern**
|
||||
- **Section:** Model Selection (lines 189-205)
|
||||
- **Problem:** Philosophy says "Workflow execution: Haiku." Role-workflow pattern says "Complex analysis workflows: Opus (when judgment required in workflow)." These aren't contradictory but could cause confusion about when to use Opus for subagent work.
|
||||
- **Why it matters:** Developers may unnecessarily use Haiku for workflows requiring judgment, degrading results.
|
||||
- **Suggested fix:** Add nuance to the Model Selection table: "Workflow execution | Haiku (mechanical), Opus (judgment-heavy)"
|
||||
|
||||
**2. Scripts section doesn't mention scripts directory convention**
|
||||
- **Section:** Core Components > Scripts (lines 100-112)
|
||||
- **Problem:** The section explains what scripts are and signs of under-scripting, but doesn't establish where scripts should live in a plugin structure.
|
||||
- **Why it matters:** Without a convention, scripts end up in inconsistent locations.
|
||||
- **Suggested fix:** Add: "Scripts live in `scripts/` directory at the plugin root."
|
||||
|
||||
**3. Hooks section missing example hooks.json**
|
||||
- **Section:** Core Components > Hooks (lines 115-139)
|
||||
- **Problem:** Mentions "Plugin-level: `hooks/hooks.json`" but provides no example of what this file looks like.
|
||||
- **Why it matters:** Someone implementing hooks has to guess the JSON schema.
|
||||
- **Suggested fix:** Either add a brief example or reference a document that shows the schema.
|
||||
|
||||
**4. Anti-patterns could link to positive patterns**
|
||||
- **Section:** Anti-Patterns table (lines 207-218)
|
||||
- **Problem:** Anti-patterns list fixes but don't link to the documents that explain the correct approach in detail.
|
||||
- **Why it matters:** Readers know what not to do but may not find the detailed guidance on what to do instead.
|
||||
- **Suggested fix:** Add references to each fix, e.g., "Use general subagent + workflow document (see `subagent-pattern.md`)"
|
||||
|
||||
**5. No example of 6-8 line dispatch**
|
||||
- **Section:** Execution Model (line 213), referenced multiple times
|
||||
- **Problem:** "6-8 line dispatch" is mentioned as the target, but no example is provided in this document.
|
||||
- **Why it matters:** The role-workflow pattern has an example, but someone reading only the philosophy document doesn't see it.
|
||||
- **Suggested fix:** Add a brief example or explicit reference: "See `tool-patterns/role-workflow-pattern.md` for dispatch template."
|
||||
|
||||
### P3 - Low (nice to have)
|
||||
|
||||
**1. "Community consensus (Jan 2026)" lacks citation**
|
||||
- **Section:** General Subagents Over Custom Agents (line 168)
|
||||
- **Problem:** Claims "Community consensus" but doesn't link to where this consensus was documented or discussed.
|
||||
- **Why it matters:** Minor credibility issue; readers may wonder where this consensus came from.
|
||||
- **Suggested fix:** Add a link to the discussion/decision record, or change to "Based on testing and team consensus"
|
||||
|
||||
**2. Plugin Hierarchy examples could include more context**
|
||||
- **Section:** Plugin Hierarchy table (lines 5-9)
|
||||
- **Problem:** Examples are single words (cc-architect, ux, commit) without brief descriptions of what they do.
|
||||
- **Why it matters:** New readers don't know what these plugins are.
|
||||
- **Suggested fix:** Add a brief description column or parenthetical: "cc-architect (meta-plugin for building plugins)"
|
||||
|
||||
**3. Known Issues table has only one entry**
|
||||
- **Section:** Known Issues (lines 220-226)
|
||||
- **Problem:** A single-entry table feels like placeholder content.
|
||||
- **Why it matters:** Minor visual inconsistency; suggests more issues should be tracked.
|
||||
- **Suggested fix:** Either populate with known issues or change format to a simple bullet point until more issues exist.
|
||||
|
||||
**4. Checklist could be a reusable template**
|
||||
- **Section:** Plugin Checklist (lines 237-249)
|
||||
- **Problem:** Checklist is embedded in the philosophy document. If it needs updates, this document must be modified.
|
||||
- **Why it matters:** Checklists are operational artifacts that may need independent versioning.
|
||||
- **Suggested fix:** Consider replacing the inline checklist with a pointer to `templates/audit-checklist-template.md`, which already covers plugin/skill/workflow-specific attributes and can be extended instead of duplicated.
|
||||
|
||||
## No Issues Found In
|
||||
|
||||
The following areas were reviewed and found to be clear, consistent, and well-documented:
|
||||
|
||||
- **Plugin Hierarchy concept** - Clear tiers with sensible dependency rules
|
||||
- **Core Components > Skills (Orchestrators)** - Clear responsibilities
|
||||
- **Core Components > Workflows** - Aligns well with knowledge-philosophy.md
|
||||
- **Main Thread Delegates** - Clear "3 file" heuristic, good anti-bloat guidance
|
||||
- **General Subagents Over Custom Agents** - Well-argued with clear migration path
|
||||
- **Command Consolidation** - Comprehensive decision tree
|
||||
- **knowledge-philosophy.md alignment** - Philosophy correctly defers framework knowledge to AI training
|
||||
- **deterministic-scripting.md alignment** - Under-scripting warnings are consistent
|
||||
- **tool-patterns/catalog.md alignment** - References are accurate and bidirectional
|
||||
|
||||
## Recommendations
|
||||
|
||||
**Immediate (before using document for new plugins):**
|
||||
1. Add note that friction tracking infrastructure is not yet implemented
|
||||
2. Fix UX plugin reference path and clarify it shows planned (V2) architecture
|
||||
3. Add reference to role-workflow pattern for task file template
|
||||
|
||||
**Short-term (next documentation pass):**
|
||||
4. Define phase transition criteria for Plugin Lifecycle
|
||||
5. Add scripts directory convention
|
||||
6. Clarify subagent findings location (scratch vs task file)
|
||||
7. Add dispatch example or explicit cross-reference
|
||||
|
||||
**When convenient:**
|
||||
8. Add example hooks.json schema
|
||||
9. Link anti-patterns to positive pattern documents
|
||||
10. Extract checklist to reusable template
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
# Plugin Architecture Philosophy
|
||||
|
||||
## Plugin Hierarchy
|
||||
|
||||
| Level | Scope | Examples |
|
||||
|-------|-------|----------|
|
||||
| High | Teams/orchestration | cc-architect |
|
||||
| Medium | Domain specialists | ux, rails-dev, n8n |
|
||||
| Low | Tools/services | invoice-ninja, commit, push |
|
||||
|
||||
Higher-level plugins may depend on lower-level. No circular dependencies.
|
||||
|
||||
## Plugin Lifecycle
|
||||
|
||||
Plugins move through phases. Friction tracking intensity varies by phase.
|
||||
|
||||
```
|
||||
New/Changed → Active Development → Tuned → Maintenance
|
||||
↑ | |
|
||||
└──────────────┴─────── (changes) ───────┘
|
||||
```
|
||||
|
||||
### Phases
|
||||
|
||||
**Active Development** - New plugins or plugins with recent changes.
|
||||
- Reflection step runs after every workflow execution
|
||||
- Friction logged to central friction directory
|
||||
- Model uncertainty documented as friction
|
||||
- Expect frequent iteration
|
||||
|
||||
Note: The friction tracking infrastructure described here is planned for V2+. Current V1 implementation tracks friction informally.
|
||||
|
||||
**Tuned** - Plugin is stable, producing reliable results.
|
||||
- Reflection step removed (reduces overhead)
|
||||
- Friction tracking disabled
|
||||
- Changes trigger return to Active Development
|
||||
|
||||
**Maintenance** - Long-term stable, minimal changes.
|
||||
- No active friction tracking
|
||||
- Major changes restart the lifecycle
|
||||
|
||||
### Friction Files
|
||||
|
||||
Friction files live at user level, outside any project:
|
||||
|
||||
```
|
||||
~/.claude/friction/
|
||||
{plugin-name}/
|
||||
{date}-{issue-slug}.md
|
||||
```
|
||||
|
||||
Each friction file uses YAML frontmatter for discoverability:
|
||||
|
||||
```yaml
|
||||
---
|
||||
plugin: cc-architect
|
||||
skill: skill-architect
|
||||
priority: P1
|
||||
date: 2026-01-25
|
||||
slug: audit-missing-step
|
||||
---
|
||||
```
|
||||
|
||||
Body contains:
|
||||
- What happened (symptoms)
|
||||
- Suspected cause
|
||||
- Recommended investigation path
|
||||
|
||||
This separation lets friction accumulate without polluting plugin directories. User chooses when to address—urgent issues (P0-P1) get handled fast; optimization opportunities (P2-P3) wait.
|
||||
|
||||
### Enabling/Disabling Friction Tracking
|
||||
|
||||
Like inserting a debugger breakpoint:
|
||||
- Add reflection step when developing or debugging a plugin
|
||||
- Remove when plugin is tuned
|
||||
- Re-add when making changes
|
||||
|
||||
The reflection step itself is a workflow that can be attached to any skill's completion phase.
|
||||
|
||||
## Core Components
|
||||
|
||||
### Skills (Orchestrators)
|
||||
|
||||
- Know available workflows and when to use each
|
||||
- Dispatch general-purpose subagents with workflow documents
|
||||
- Define reporting standards and persistence conventions
|
||||
- Handle user interaction in main thread
|
||||
- Do NOT execute detail work themselves
|
||||
|
||||
### Workflows
|
||||
|
||||
- Live in `workflows/` as `.md` files
|
||||
- Contain: rubrics, templates, process steps
|
||||
- Do NOT contain: theory, framework explanations
|
||||
- Can be static (files) or dynamic (runtime-selected)
|
||||
- Multiple skills/roles can dispatch the same workflow
|
||||
|
||||
See `knowledge-philosophy.md` for what belongs in workflows.
|
||||
|
||||
#### Workflow Composition
|
||||
|
||||
Roles dispatch workflows. Multiple roles can share workflows.
|
||||
|
||||
Example from UX plugin:
|
||||
- **UX Consultant** (audit-focused) dispatches: heuristic evaluation, accessibility audit
|
||||
- **UX Strategist** (planning-focused) dispatches: journey orchestration, feature prioritization
|
||||
- **UX Architect** (system-level) dispatches: navigation model design, state-machine mapping
|
||||
- **Shared workflows**: All roles might dispatch a common "reporting" workflow for consistent output format
|
||||
|
||||
Note: This example shows planned V2+ architecture with multiple specialized roles. Current V1 uses simpler single-role approach.
|
||||
|
||||
See `ux/` (marketplace root) for implementation reference.
|
||||
|
||||
### Scripts
|
||||
|
||||
- Deterministic, repeatable tasks
|
||||
- Binary correctness (valid/invalid, not good/better)
|
||||
- Pre-analysis before AI judgment
|
||||
- Verification of work completion
|
||||
|
||||
**Under-scripting is a common failure mode.** When AI runs multiple deterministic commands sequentially (e.g., `git status`, `git diff`, `git log`), a script should batch them and return a structured report. Signs of under-scripting:
|
||||
- AI running 3+ tool calls for data gathering that could be one script
|
||||
- Same commands executed repeatedly across sessions
|
||||
- Structured output parsed differently each time
|
||||
|
||||
See `tool-patterns/deterministic-scripting.md` for when to script.
|
||||
|
||||
### Hooks
|
||||
|
||||
Event-driven automation that runs at specific lifecycle points.
|
||||
|
||||
**When to use hooks:**
|
||||
|
||||
| Use Case | Hook Type | Example |
|
||||
|----------|-----------|---------|
|
||||
| Validate before execution | PreToolUse | Block writes to .env files |
|
||||
| Format/lint after changes | PostToolUse | Run prettier on edited files |
|
||||
| Inject session context | SessionStart | Load project-specific settings |
|
||||
| Custom notifications | Notification | Desktop alerts for permissions |
|
||||
| Control agent stopping | Stop, SubagentStop | Evaluate task completion |
|
||||
|
||||
**When NOT to use hooks:**
|
||||
|
||||
- Workflow logic that requires AI judgment (use subagents)
|
||||
- Complex multi-step processes (use workflows)
|
||||
- User interaction (use skills)
|
||||
|
||||
**Configuration levels:**
|
||||
|
||||
1. Plugin-level: `hooks/hooks.json` - applies when plugin enabled
|
||||
2. Component-level: skill/agent frontmatter - scoped to component execution
|
||||
|
||||
**Key principle:** Hooks handle mechanical guardrails and automation. Skills handle orchestration and judgment.
|
||||
|
||||
## Execution Model
|
||||
|
||||
### Main Thread Delegates
|
||||
|
||||
Main thread responsibilities:
|
||||
- Understand request
|
||||
- Select workflow
|
||||
- Create task file (if using task file pattern)
|
||||
- Dispatch subagent
|
||||
- Synthesize returned findings
|
||||
- Present to user
|
||||
|
||||
Main thread does NOT:
|
||||
- Read more than 3 files for workflow selection
|
||||
- Execute evaluations
|
||||
- Accumulate context through exploration
|
||||
|
||||
When exploration is needed, dispatch a subagent.
|
||||
|
||||
### General Subagents Over Custom Agents
|
||||
|
||||
**Decision:** Custom agents are relics. Use general-purpose + workflow documents.
|
||||
|
||||
Why:
|
||||
- Same 294-token base overhead (no efficiency difference)
|
||||
- More flexible (workflow docs can be composed, selected dynamically)
|
||||
- No "context gatekeeping" (custom agents hide information from main thread)
|
||||
- Community consensus (Jan 2026)
|
||||
|
||||
Migration path:
|
||||
1. Extract agent logic into workflow document
|
||||
2. Update skill to dispatch `general-purpose` subagent with workflow path
|
||||
3. Delete agent file
|
||||
|
||||
### Command Consolidation
|
||||
|
||||
**Decision:** Skills are directly invocable with `/skill-name`. Standalone commands are redundant.
|
||||
|
||||
**Decision tree for existing commands:**
|
||||
|
||||
1. **Plugin+skill already does this** → Delete command (confirm with user first)
|
||||
2. **Useful functionality, fits existing plugin** → Add as skill to that plugin, delete command
|
||||
3. **Multiple related commands exist** → Create a plugin to house them as skills
|
||||
- Example: `commit` command + `repo-init` command → `git` plugin with `commit` and `repo-init` skills
|
||||
4. **Doesn't fit anywhere** → Case-by-case; may warrant new plugin or remain standalone
|
||||
|
||||
**Do not create new standalone commands.** New functionality goes into plugins as skills.
|
||||
|
||||
## Model Selection
|
||||
|
||||
| Task | Model |
|
||||
|------|-------|
|
||||
| Orchestration/synthesis | Opus |
|
||||
| Workflow execution | Haiku |
|
||||
| Script implementation | Haiku |
|
||||
| Trade-off decisions | Opus |
|
||||
|
||||
**Default to Opus when uncertain.**
|
||||
|
||||
When model choice is uncertain during Active Development phase, log as friction:
|
||||
- What task triggered uncertainty
|
||||
- Why Haiku seemed potentially sufficient
|
||||
- Outcome (did Opus quality justify cost?)
|
||||
|
||||
This creates data for future model selection refinement. User can later test Haiku for that task type to evaluate cost/quality tradeoff.
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Anti-Pattern | Fix |
|
||||
|--------------|-----|
|
||||
| Custom agent proliferation | Use general subagent + workflow document |
|
||||
| Main thread execution | Dispatch subagent, synthesize returned findings |
|
||||
| Inline workflow content in dispatch | 6-8 line dispatch pointing to workflow path |
|
||||
| Over-scripting judgment | Script validates format; AI judges quality |
|
||||
| Under-scripting deterministic work | Batch sequential commands into scripts returning structured reports |
|
||||
| Command-skill duplication | Delete command or migrate to plugin skill |
|
||||
| Hooks for complex logic | Move to workflow document |
|
||||
| Theory in workflow docs | Move to skill description or delete |
|
||||
|
||||
## Known Issues
|
||||
|
||||
Issues identified but deferred for prioritization.
|
||||
|
||||
| Issue | Description | Priority |
|
||||
|-------|-------------|----------|
|
||||
| Architect under-scripting blind spot | skill-architect and plugin-architect do not flag sequential deterministic commands as scripting opportunities (e.g., commit skill runs git commands individually instead of via script) | P1 |
|
||||
|
||||
## Integration
|
||||
|
||||
Works with:
|
||||
- `knowledge-philosophy.md` - What to document
|
||||
- `subagent-pattern.md` - When/how to dispatch
|
||||
- `tool-patterns/role-workflow-pattern.md` - Multi-workflow orchestration
|
||||
- `tool-patterns/deterministic-scripting.md` - When to script
|
||||
- `ux/` (marketplace root) - Reference implementation for role-workflow pattern
|
||||
|
||||
## Plugin Checklist
|
||||
|
||||
When creating or auditing a plugin:
|
||||
|
||||
- [ ] Skills dispatch general subagents (no custom agents)
|
||||
- [ ] Workflows in `workflows/` as `.md` files
|
||||
- [ ] Scripts handle mechanical tasks (watch for under-scripting)
|
||||
- [ ] Task file template defined (if using `tool-patterns/role-workflow-pattern.md`)
|
||||
- [ ] Reporting standards documented
|
||||
- [ ] Model selection follows table
|
||||
- [ ] Commands migrated to skills or deleted
|
||||
- [ ] Hooks used only for guardrails/automation (not logic)
|
||||
- [ ] Friction tracking enabled (if Active Development phase)
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# The reference.md Pattern
|
||||
|
||||
Every directory in `references/` must have a `reference.md` file. It's the routing layer that tells the AI what's here and when to go deeper.
|
||||
|
||||
## Purpose
|
||||
|
||||
The reference.md file answers three questions:
|
||||
1. What knowledge lives in this directory?
|
||||
2. When should I read each file?
|
||||
3. Do I need to go deeper into subdirectories?
|
||||
|
||||
## Required sections
|
||||
|
||||
### What's here
|
||||
|
||||
A table listing each file/subdirectory with:
|
||||
- Name
|
||||
- Purpose (one line)
|
||||
- When to read it
|
||||
|
||||
```markdown
|
||||
## What's here
|
||||
|
||||
| File | Purpose | When to read |
|
||||
|------|---------|--------------|
|
||||
| `foo.md` | Explains X | When doing Y |
|
||||
| `bar/` | Details about Z | When you need deep Z knowledge |
|
||||
```
|
||||
|
||||
### When to use this module
|
||||
|
||||
Brief description of the scenarios where this directory's knowledge applies.
|
||||
|
||||
### Model guidance
|
||||
|
||||
Hints about whether cheap or stronger models should handle this content.
|
||||
|
||||
## Optional sections
|
||||
|
||||
- **Prerequisites**: What the AI should know/have before reading this
|
||||
- **Child topics**: If subdirectories exist, brief descriptions of when to descend
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- Don't duplicate knowledge from child files in reference.md
|
||||
- Don't make reference.md longer than ~50 lines
|
||||
- Don't skip the "when to read" column - it's the whole point
|
||||
|
||||
## Example
|
||||
|
||||
```markdown
|
||||
# Authentication References
|
||||
|
||||
## What's here
|
||||
|
||||
| File | Purpose | When to read |
|
||||
|------|---------|--------------|
|
||||
| `oauth-flow.md` | OAuth 2.0 implementation details | When the API uses OAuth |
|
||||
| `api-keys.md` | API key authentication patterns | When the API uses simple API keys |
|
||||
| `rate-limiting.md` | How to handle rate limits | After auth is working, before production |
|
||||
|
||||
## When to use this module
|
||||
|
||||
You're implementing authentication for an API wrapper and need to understand the auth mechanism.
|
||||
|
||||
## Model guidance
|
||||
|
||||
Factual/procedural content. Cheap model is fine.
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Progressive Disclosure References
|
||||
|
||||
How to organize knowledge so the AI reads only what it needs.
|
||||
|
||||
## What's here
|
||||
|
||||
| File | Purpose | When to read |
|
||||
|------|---------|--------------|
|
||||
| `reference-md-pattern.md` | How to write effective reference.md files | When creating a new references directory |
|
||||
| `splitting-knowledge.md` | When and how to split knowledge docs | When a doc is getting too long, or deciding structure upfront |
|
||||
|
||||
## When to use this module
|
||||
|
||||
- You're creating reference.md files for a new skill
|
||||
- You're deciding whether to split a knowledge doc
|
||||
- A knowledge doc has grown past ~150 lines and needs restructuring
|
||||
|
||||
## Model guidance
|
||||
|
||||
- Writing reference.md files: cheap model (it's templated)
|
||||
- Deciding *whether* to split: stronger model (requires judgment about cohesion)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# Splitting Knowledge Docs
|
||||
|
||||
When to keep knowledge together vs. split it apart.
|
||||
|
||||
## The core question
|
||||
|
||||
> "Will these pieces of knowledge always be needed together, or are there times you need one but not the other?"
|
||||
|
||||
If always together → keep in one file.
|
||||
If sometimes separate → split into multiple files.
|
||||
|
||||
## Size guidelines
|
||||
|
||||
- Target: under 150 lines per knowledge doc
|
||||
- Hard limit: 200 lines (split if approaching this)
|
||||
- reference.md files: under 50 lines
|
||||
|
||||
## Signs you should split
|
||||
|
||||
- The doc covers multiple distinct concepts
|
||||
- You find yourself scrolling past irrelevant sections
|
||||
- Different workflows need different parts of the doc
|
||||
- The doc has more than 3-4 top-level sections
|
||||
|
||||
## Signs you should NOT split
|
||||
|
||||
- The concepts are tightly coupled (understanding A requires B)
|
||||
- The doc is under 100 lines and cohesive
|
||||
- Splitting would create files under 30 lines (too granular)
|
||||
|
||||
## How to split
|
||||
|
||||
1. Identify the natural boundaries (usually top-level headings)
|
||||
2. Create a subdirectory if splitting creates 3+ files
|
||||
3. Move content to new files
|
||||
4. Update the parent reference.md to describe the new structure
|
||||
5. Add a reference.md to any new subdirectory
|
||||
|
||||
## Depth limits
|
||||
|
||||
- Maximum recommended depth: 3 levels (`references/topic/subtopic/`)
|
||||
- If you need deeper, consider whether this belongs in a separate skill
|
||||
|
||||
## Example split
|
||||
|
||||
Before:
|
||||
```
|
||||
references/
|
||||
└── api-design.md (250 lines covering auth, pagination, errors)
|
||||
```
|
||||
|
||||
After:
|
||||
```
|
||||
references/
|
||||
└── api-design/
|
||||
├── reference.md
|
||||
├── authentication.md
|
||||
├── pagination.md
|
||||
└── error-handling.md
|
||||
```
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# Friction Reflection
|
||||
|
||||
How to identify and record friction at the end of a task.
|
||||
|
||||
## What is friction?
|
||||
|
||||
Friction is anything that made the task harder than it should have been.
|
||||
|
||||
### Documentation friction
|
||||
Knowledge docs didn't serve you well:
|
||||
- Information was missing
|
||||
- Information was hard to find
|
||||
- Information was unclear or ambiguous
|
||||
- You made a mistake because docs were misleading
|
||||
|
||||
### Operational friction
|
||||
Process or architecture caused unnecessary work:
|
||||
- **Navigation friction**: Too many index files read to reach knowledge (tree too deep)
|
||||
- **Context friction**: Work in main thread that cluttered context (should've been subagent)
|
||||
- **Automation friction**: Deterministic subtask that would be faster/reliable as script
|
||||
|
||||
## The reflection prompt
|
||||
|
||||
At the end of every workflow, ask yourself:
|
||||
|
||||
> "Did I encounter friction during this task?"
|
||||
|
||||
Consider both documentation friction (docs missing, unclear, hard to find) and operational friction (too many hops, context clutter, scripting opportunities).
|
||||
|
||||
**If no:** Proceed to report. No action needed.
|
||||
|
||||
**If yes:** Record it in the task document under `## Friction / Improvement Notes`
|
||||
|
||||
## What to record
|
||||
|
||||
For each friction point, write:
|
||||
|
||||
```markdown
|
||||
## Friction / Improvement Notes
|
||||
|
||||
### [target-file-path or process-name]
|
||||
**Type:** Documentation | Navigation | Context | Automation
|
||||
**What happened:** Brief description of the friction
|
||||
**Suggestion:** What would have helped
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Good friction note
|
||||
```markdown
|
||||
### references/layout/skill-structure.md
|
||||
**What happened:** Didn't explain where project-level skills live vs plugin skills
|
||||
**Suggestion:** Add a "Where skills live" section with both paths
|
||||
```
|
||||
|
||||
### Bad friction note (too vague)
|
||||
```markdown
|
||||
### references/layout/skill-structure.md
|
||||
**What happened:** Confusing
|
||||
**Suggestion:** Make it better
|
||||
```
|
||||
|
||||
### Good operational friction notes
|
||||
|
||||
```markdown
|
||||
### Navigation
|
||||
**Type:** Navigation
|
||||
**What happened:** Read 4 index files before reaching the actual content I needed
|
||||
**Suggestion:** Add direct link from CLAUDE.md or flatten the tree
|
||||
```
|
||||
|
||||
```markdown
|
||||
### Subagent opportunity
|
||||
**Type:** Context
|
||||
**What happened:** Ran 15-step verification in main thread, cluttered context
|
||||
**Suggestion:** Document this as a subagent workflow or script
|
||||
```
|
||||
|
||||
```markdown
|
||||
### Script opportunity
|
||||
**Type:** Automation
|
||||
**What happened:** Manually checked 8 files for the same pattern
|
||||
**Suggestion:** Create a lint script that checks this automatically
|
||||
```
|
||||
|
||||
## When NOT to record friction
|
||||
|
||||
- One-time mistakes that won't recur
|
||||
- Friction caused by user's unclear requirements (not a docs problem)
|
||||
- Issues you already fixed in this task
|
||||
- Extremely minor issues (typos, formatting)
|
||||
|
||||
## The goal
|
||||
|
||||
Build a trail of actionable improvements that the improvement workflow can harvest later. Quality over quantity.
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# Patch Workflow
|
||||
|
||||
How to propose and apply improvements to knowledge docs.
|
||||
|
||||
## Overview
|
||||
|
||||
The improve-docs workflow harvests friction notes from task documents, prioritizes them, and applies targeted patches. This file describes the mechanics.
|
||||
|
||||
## Patch format
|
||||
|
||||
A patch is a proposed change to a specific file:
|
||||
|
||||
```markdown
|
||||
### Patch: references/layout/plugin-structure.md
|
||||
|
||||
**Source:** task doc `new-plugin-foo-2024-01-15.md`
|
||||
|
||||
**Change type:** Addition
|
||||
|
||||
**Location:** After "## plugin.json format" section
|
||||
|
||||
**Content to add:**
|
||||
> **Path requirements:** All paths in plugin.json must be relative and start with `./`
|
||||
|
||||
**Rationale:** Multiple tasks hit friction because this wasn't documented.
|
||||
```
|
||||
|
||||
## Change types
|
||||
|
||||
- **Addition**: Add new content (most common)
|
||||
- **Clarification**: Reword existing content for clarity
|
||||
- **Correction**: Fix incorrect information
|
||||
- **Restructure**: Move content or split files
|
||||
- **Removal**: Delete outdated or wrong content (rare)
|
||||
|
||||
## Patch size guidelines
|
||||
|
||||
- Keep patches small and focused
|
||||
- One friction point = one patch
|
||||
- If a patch would make a file exceed 150 lines, propose a split instead
|
||||
|
||||
## Approval levels
|
||||
|
||||
Not all patches need the same scrutiny:
|
||||
|
||||
| Change type | Approval |
|
||||
|-------------|----------|
|
||||
| Addition (small) | Auto-apply OK |
|
||||
| Clarification | Auto-apply OK |
|
||||
| Correction | Review recommended |
|
||||
| Restructure | User approval required |
|
||||
| Removal | User approval required |
|
||||
|
||||
## After applying patches
|
||||
|
||||
1. Update any parent reference.md if file structure changed
|
||||
2. Note applied patches in the task document
|
||||
3. Consider re-running a workflow that hit this friction to verify improvement
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Self-Improvement References
|
||||
|
||||
How skills learn from friction and improve their knowledge docs and processes.
|
||||
|
||||
## What's here
|
||||
|
||||
| File | Purpose | When to read |
|
||||
|------|---------|--------------|
|
||||
| `friction-reflection.md` | How to identify and record friction during tasks | At the end of any workflow, during reflection |
|
||||
| `patch-workflow.md` | How to propose and apply improvements to knowledge docs | When running an improve workflow |
|
||||
|
||||
## When to use this module
|
||||
|
||||
- You just finished a task and are reflecting on friction (documentation or operational)
|
||||
- You're running an improvement workflow
|
||||
- You want to understand how self-improvement works in this system
|
||||
- You noticed a scripting opportunity or context management issue
|
||||
|
||||
## Model guidance
|
||||
|
||||
- Recording friction: cheap model (templated)
|
||||
- Deciding what friction is worth fixing: stronger model (judgment)
|
||||
- Writing patches: depends on complexity of the change
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
# Subagent Architecture Pattern
|
||||
|
||||
## Principle
|
||||
|
||||
Main thread orchestrates and communicates with user. Subagents handle detail work.
|
||||
|
||||
This keeps the main conversation focused. Context bloat in the main thread degrades response quality and loses important details.
|
||||
|
||||
## When to Dispatch Subagents
|
||||
|
||||
- Task involves reading/analyzing multiple files
|
||||
- Work can be parallelized across independent concerns
|
||||
- Detail work would bloat main thread context
|
||||
- Task has clear, bounded scope a subagent can complete
|
||||
|
||||
## When NOT to Dispatch
|
||||
|
||||
- Simple, single-file operations
|
||||
- Tasks requiring ongoing user dialogue
|
||||
- Work that depends heavily on conversation context
|
||||
|
||||
## Model Selection
|
||||
|
||||
**Haiku:** Clear, mechanical, straightforward tasks
|
||||
- File existence checks, YAML validation, pattern matching
|
||||
- Tasks with unambiguous right/wrong answers
|
||||
- Cleanup operations (removing temp files, archiving artifacts)
|
||||
|
||||
**Sonnet:** Moderate complexity, filtering, intermediate synthesis
|
||||
- Filtering noise from Haiku research results
|
||||
- Applying frameworks and patterns to gathered data
|
||||
- Writing workflows, templates, documentation
|
||||
- Intermediate decisions within established guidelines
|
||||
- Pattern application (not pattern creation)
|
||||
|
||||
**Opus:** Judgment, analysis, higher-level thinking
|
||||
- Quality assessments, trade-off evaluation
|
||||
- Anything requiring interpretation of standards
|
||||
- Pattern creation and architecture decisions
|
||||
- Final synthesis and high-stakes decisions
|
||||
|
||||
**Default:** When uncertain, choose Opus
|
||||
- Better to spend more on accuracy than fail cheaply
|
||||
|
||||
## Workflow Structure
|
||||
|
||||
**Maximum 2-3 subagents per workflow**
|
||||
- More than 3 subagents indicates over-engineering
|
||||
- Break complex work into phases, not more subagents
|
||||
- Consider whether work truly needs delegation
|
||||
|
||||
## Phase Structure
|
||||
|
||||
Well-designed workflows follow a three-phase pattern:
|
||||
|
||||
**Phase 1: Setup + Research (Haiku)**
|
||||
- Create necessary directories and scratch workspace
|
||||
- Read files and gather data
|
||||
- Pattern matching and data collection
|
||||
- Output: Structured data files in scratch
|
||||
|
||||
**Phase 2: Processing (Sonnet)**
|
||||
- Filter noise from Phase 1 results
|
||||
- Apply frameworks and patterns to gathered data
|
||||
- Make intermediate decisions within established guidelines
|
||||
- Write drafts of workflows, templates, or documentation
|
||||
- Output: Processed artifacts ready for review
|
||||
|
||||
**Phase 3: Synthesis + Cleanup (Opus + Haiku)**
|
||||
- Opus: Final decisions, quality assessments, generate final output
|
||||
- Haiku: Remove scratch files, archive temp artifacts
|
||||
- Output: Final deliverables in correct locations
|
||||
|
||||
Not all workflows need all three phases. Simple workflows may only need Phase 1 + Phase 3.
|
||||
|
||||
## Single Responsibility
|
||||
|
||||
Each subagent should have one clear job:
|
||||
|
||||
| Good | Bad |
|
||||
|------|-----|
|
||||
| "Check frontmatter validity" | "Check frontmatter and also evaluate description quality and find patterns" |
|
||||
| "Assess description trigger quality" | "Review the whole skill" |
|
||||
| "Filter research findings to top 5 relevant items" | "Research everything and also decide what matters" |
|
||||
|
||||
## Cleanup Subagent Pattern
|
||||
|
||||
**When:** After workflow completion, when temp artifacts exist
|
||||
|
||||
**Model:** Haiku (mechanical operation)
|
||||
|
||||
**Tasks:**
|
||||
- Remove scratch files and directories
|
||||
- Archive temp artifacts if needed for audit trail
|
||||
- Delete intermediate processing files
|
||||
- Leave only final deliverables
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Haiku cleanup subagent prompt
|
||||
rm -rf .claude/scratch/skill-audit-2025-02-09/
|
||||
# OR if archiving needed
|
||||
mv .claude/scratch/skill-audit-2025-02-09/ .claude/archive/
|
||||
```
|
||||
|
||||
## Artifact Handoff
|
||||
|
||||
**Key principle:** Subagents write to files, not context.
|
||||
|
||||
Main thread should not read subagent outputs into context unless absolutely necessary for user communication. Instead:
|
||||
|
||||
1. Subagent writes findings to file in scratch workspace
|
||||
2. Next subagent reads the file directly (file path passed in prompt)
|
||||
3. Main thread only reads final deliverables for user summary
|
||||
|
||||
**Why:**
|
||||
- Prevents context bloat in main thread
|
||||
- Creates audit trail in scratch workspace
|
||||
- Allows parallel subagent execution
|
||||
- Enables cleanup without losing work
|
||||
|
||||
**Example flow:**
|
||||
```
|
||||
Main: Dispatch Haiku to gather data → writes scratch/data.json
|
||||
Main: Dispatch Sonnet to filter → reads scratch/data.json, writes scratch/filtered.md
|
||||
Main: Dispatch Opus to synthesize → reads scratch/filtered.md, writes final/output.md
|
||||
Main: Read final/output.md to summarize for user
|
||||
Main: Dispatch Haiku to cleanup → rm -rf scratch/
|
||||
```
|
||||
|
||||
## Reporting Back
|
||||
|
||||
Subagents write findings to scratch workspace in structured format. Main thread assembles final output.
|
||||
|
||||
Example scratch structure:
|
||||
```
|
||||
scratch/
|
||||
structure-findings.md
|
||||
content-findings.md
|
||||
quality-findings.md
|
||||
patterns-findings.md
|
||||
```
|
||||
|
||||
## Adoption for Other Plugins
|
||||
|
||||
**Migration steps for existing workflows:**
|
||||
|
||||
1. **Identify current subagent usage**
|
||||
- Count subagents per workflow
|
||||
- Classify by model tier
|
||||
- Map to phase structure
|
||||
|
||||
2. **Apply 2-3 subagent limit**
|
||||
- Consolidate over-dispatched workflows
|
||||
- Break complex workflows into phases
|
||||
- Consider whether delegation is needed
|
||||
|
||||
3. **Add Sonnet tier where appropriate**
|
||||
- Replace Opus for filtering/processing tasks
|
||||
- Use for documentation generation
|
||||
- Apply for pattern-following work
|
||||
|
||||
4. **Implement cleanup subagents**
|
||||
- Add Haiku cleanup at end of workflows
|
||||
- Remove scratch workspace after completion
|
||||
- Archive if audit trail needed
|
||||
|
||||
5. **Update artifact handoff**
|
||||
- Ensure subagents write to files
|
||||
- Pass file paths to next subagent
|
||||
- Keep main thread context minimal
|
||||
|
||||
6. **Document in workflow**
|
||||
- Mark phases clearly
|
||||
- Note model selection rationale
|
||||
- Include cleanup step
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
# Adaptive Verbosity Pattern
|
||||
|
||||
## Purpose
|
||||
|
||||
Scale output detail to match task complexity. Simple tasks get minimal output; complex tasks get thorough analysis.
|
||||
|
||||
## Applicability
|
||||
|
||||
Use this pattern when:
|
||||
- Skill handles both simple and complex scenarios
|
||||
- Output length/detail should vary based on input
|
||||
- User experience suffers from one-size-fits-all output
|
||||
|
||||
Do NOT use when:
|
||||
- Skill always produces fixed output (lookups, status checks)
|
||||
- User explicitly controls verbosity via flags
|
||||
- Output is inherently simple (single values, yes/no)
|
||||
|
||||
## Core Principle
|
||||
|
||||
Before generating output, assess complexity using multiple signals. Use the HIGHEST complexity level indicated by any signal.
|
||||
|
||||
## Complexity Assessment
|
||||
|
||||
| Signal Type | Minimal | Standard | Detailed |
|
||||
|-------------|---------|----------|----------|
|
||||
| Entity count | 1-5 | 6-10 | 10+ |
|
||||
| Conversation context | Clear what/why from recent messages | Partial context | No context provided |
|
||||
| Risk indicators | None | Minor (warnings) | Major (secrets, destructive) |
|
||||
| User request style | Terse ("do it", "commit") | Neutral | Explicit detail request |
|
||||
|
||||
**Note:** Entity count thresholds (1-5/6-10/10+) are defaults. Adjust based on domain - e.g., 1-3 may be appropriate for complex entities like database migrations.
|
||||
|
||||
**Precedence rule:** If ANY signal indicates higher complexity, use that level.
|
||||
|
||||
**Example:** 2 entities (Minimal) + secrets detected (Detailed risk) → Use Detailed level
|
||||
|
||||
## Output Formats
|
||||
|
||||
### Minimal (simple tasks)
|
||||
|
||||
Direct result with single confirmation. No explanatory sections.
|
||||
|
||||
**Example - Git commit:**
|
||||
```
|
||||
Committed: `fix: correct typo in README`
|
||||
```
|
||||
|
||||
**Example - File operation:**
|
||||
```
|
||||
Created: src/utils/helper.ts
|
||||
```
|
||||
|
||||
### Standard (moderate tasks)
|
||||
|
||||
Brief summary with key details. Clear action items if needed.
|
||||
|
||||
**Example - Git commit:**
|
||||
```
|
||||
Staged 4 files:
|
||||
- src/auth/login.ts (new)
|
||||
- src/auth/logout.ts (new)
|
||||
- src/auth/index.ts (updated exports)
|
||||
- tests/auth.test.ts (new)
|
||||
|
||||
Proposed: `feat(auth): add login and logout functions`
|
||||
|
||||
Commit? (y/n)
|
||||
```
|
||||
|
||||
### Detailed (complex tasks)
|
||||
|
||||
Full analysis with reasoning visible. Structured sections. Recommendations.
|
||||
|
||||
**Example - Git commit:**
|
||||
```
|
||||
Analysis of 15 changed files:
|
||||
|
||||
By concern:
|
||||
- Authentication (8 files): New OAuth integration
|
||||
- Validation (4 files): Extracted to shared module
|
||||
- Tests (3 files): Coverage for above
|
||||
|
||||
Recommendation: Split into 3 commits:
|
||||
1. `refactor(validation): extract shared module` [4 files]
|
||||
2. `feat(auth): implement OAuth integration` [8 files]
|
||||
3. `test: add auth and validation coverage` [3 files]
|
||||
|
||||
Rationale: Validation refactor is independent, should land first.
|
||||
|
||||
Proceed with split? (y/n/single commit)
|
||||
```
|
||||
|
||||
### Warning-focused (risky tasks)
|
||||
|
||||
Warnings FIRST and prominent. Risk explanation before any action. Explicit confirmation required.
|
||||
|
||||
**Example - Git commit with secrets:**
|
||||
```
|
||||
WARNING: Potential secrets detected
|
||||
|
||||
Files flagged:
|
||||
- config/api-keys.json (contains "api_key" field)
|
||||
- .env.production (environment file)
|
||||
|
||||
These files should NOT be committed.
|
||||
|
||||
Options:
|
||||
1. Remove from staging: `git reset HEAD config/api-keys.json .env.production`
|
||||
2. Add to .gitignore and remove
|
||||
3. Proceed anyway (NOT RECOMMENDED)
|
||||
|
||||
Choice? (1/2/3)
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
| Anti-pattern | Problem | Fix |
|
||||
|--------------|---------|-----|
|
||||
| One-size-fits-all | Same verbose output for 1 file and 50 files | Assess complexity first |
|
||||
| Always verbose | Detailed output when context is obvious | Trust conversation context |
|
||||
| Always terse | Missing details when complexity warrants | Check entity count and risk signals |
|
||||
| Ignoring risk | Burying warnings in normal output | Risk signals override other assessments |
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# Audit Pattern
|
||||
|
||||
Smart implicit audits that run automatically when modifying tools or handling vague requests.
|
||||
|
||||
## Purpose
|
||||
|
||||
Prevent drift and degradation by auditing before changes, not just when explicitly requested.
|
||||
|
||||
## When to Use
|
||||
|
||||
**Implicit audit triggers:**
|
||||
- User asks to "modify" or "update" an existing tool
|
||||
- User gives vague request like "improve this skill"
|
||||
- User asks to "add" something to an existing tool
|
||||
- Any change to a tool that has golden examples
|
||||
|
||||
**Explicit audit triggers:**
|
||||
- User asks to "audit" or "review" a tool
|
||||
- User pulls a tool from marketplace and wants evaluation
|
||||
|
||||
**Skip audit when:**
|
||||
- Creating a brand new tool (nothing to audit yet)
|
||||
- User gives specific, unambiguous instructions
|
||||
- Quick fixes to obvious bugs (typos, syntax errors)
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
Tools implementing this pattern must:
|
||||
|
||||
- [ ] Define audit scope in workflow (what gets checked)
|
||||
- [ ] Check for prior decisions in `.decisions/` before auditing
|
||||
- [ ] Run audit as subagent(s) to keep main thread light
|
||||
- [ ] Write findings to scratch workspace (gitignored)
|
||||
- [ ] Classify findings by severity
|
||||
- [ ] Check golden examples for behavioral impact
|
||||
- [ ] Record new decisions made during audit
|
||||
- [ ] Clean up scratch files after user acts on findings
|
||||
|
||||
## Workflow Structure
|
||||
|
||||
```
|
||||
1. Detect implicit trigger
|
||||
└─→ "modify" / "update" / "add to" / vague request
|
||||
|
||||
2. Load prior context
|
||||
└─→ Read .decisions/ for this tool
|
||||
└─→ Read invariants.md if exists
|
||||
└─→ Read golden examples
|
||||
|
||||
3. Run audit subtasks (parallel where possible)
|
||||
└─→ Structure audit (haiku)
|
||||
└─→ Content audit (opus)
|
||||
└─→ Quality audit (opus)
|
||||
└─→ Change impact analysis
|
||||
|
||||
4. Assemble findings
|
||||
└─→ Classify severity
|
||||
└─→ Flag golden example impacts
|
||||
|
||||
5. Present to user
|
||||
└─→ Summary with verdict
|
||||
└─→ What will change and why
|
||||
|
||||
6. Record decisions
|
||||
└─→ Write to .decisions/
|
||||
```
|
||||
|
||||
## Example Reference
|
||||
|
||||
See `${CLAUDE_PLUGIN_ROOT}/workflows/audit-skill.md` for a complete audit workflow implementation.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
**Audit theater:** Running audits but ignoring findings. Every significant finding must be addressed or explicitly deferred.
|
||||
|
||||
**Audit fatigue:** Auditing every tiny change. Reserve for modifications that could affect behavior.
|
||||
|
||||
**Silent audits:** Running audits without telling the user. Always surface findings.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- [Verification Pattern](verification-pattern.md) - Audits feed into verification
|
||||
- [Decisions Record](decisions-record.md) - Audits must record decisions
|
||||
- [Reversion Protection](reversion-protection.md) - Audits check golden examples
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# Brainstorming Pattern
|
||||
|
||||
Required design refinement for non-trivial concepts before implementation.
|
||||
|
||||
## Purpose
|
||||
|
||||
Refine rough ideas into clear designs through drafting, self-critique, and trade-off surfacing. Prevents building the wrong thing.
|
||||
|
||||
## When to Use
|
||||
|
||||
**Required for:**
|
||||
- New tool concepts (skills, agents, commands)
|
||||
- Significant architectural changes
|
||||
- User has nascent idea not ready for implementation
|
||||
- Multiple valid approaches exist
|
||||
|
||||
**Skip when:**
|
||||
- User gives clear, unambiguous instructions
|
||||
- Task is purely mechanical (file moves, renames)
|
||||
- Fixing specific bugs with obvious solutions
|
||||
- User explicitly says "just do it"
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Draft first, ask later.** Don't interrogate the user. Use judgment to draft, then surface gaps.
|
||||
|
||||
2. **Conceptual, not implementation.** Output is design decisions and constraints. Implementation details come later.
|
||||
|
||||
3. **Always checkpoint.** Write a defer file after critique. Nothing is lost if user leaves.
|
||||
|
||||
4. **Self-critique before presenting.** Find your own issues before the user does.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
Tools implementing this pattern must:
|
||||
|
||||
- [ ] Detect when brainstorming is needed (vs execution)
|
||||
- [ ] Draft a complete conceptual design first
|
||||
- [ ] Self-critique against domain anti-patterns
|
||||
- [ ] Surface trade-offs requiring user decisions
|
||||
- [ ] Write checkpoint to `.claude/deferred/`
|
||||
- [ ] Give user clear options: leave or continue
|
||||
|
||||
## Workflow Structure
|
||||
|
||||
```
|
||||
1. Understand intent (1-2 questions max)
|
||||
└─→ What problem? Who consumes it? Constraints?
|
||||
|
||||
2. Draft conceptual design
|
||||
└─→ Purpose, decisions, structure, constraints
|
||||
└─→ Identify script candidates (mechanical, repeatable components)
|
||||
|
||||
3. Self-critique
|
||||
└─→ Alignment with intent
|
||||
└─→ Domain anti-patterns
|
||||
└─→ Missing pieces
|
||||
└─→ Over-engineering
|
||||
|
||||
4. Present and checkpoint
|
||||
└─→ Show design
|
||||
└─→ Write defer file
|
||||
└─→ Offer: leave or continue
|
||||
|
||||
5. Resolution (if continuing)
|
||||
└─→ Work through trade-offs
|
||||
└─→ Dispatch subagents for implementation
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
## Purpose
|
||||
What this solves, in one paragraph.
|
||||
|
||||
## Design Decisions
|
||||
- Decision 1: choice and why
|
||||
- Decision 2: ...
|
||||
|
||||
## Structure
|
||||
Conceptual architecture - components, relationships, boundaries.
|
||||
|
||||
## Constraints
|
||||
- What this must do
|
||||
- What this must NOT do
|
||||
|
||||
## Open Questions
|
||||
- Questions that surfaced during drafting
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
**Question barrage:** Asking 5+ questions before drafting anything. Draft with assumptions, then validate.
|
||||
|
||||
**Implementation creep:** Including file paths, code snippets, exact structures. Stay conceptual.
|
||||
|
||||
**Skipping critique:** Presenting first draft without self-review. Always critique before showing.
|
||||
|
||||
**Forced brainstorming:** Brainstorming when user gave clear instructions. Most interactions are execution, not exploration.
|
||||
|
||||
## Script Identification
|
||||
|
||||
When refining a skill design, identify which components are mechanical vs. judgment-based. Mechanical components (validation, scaffolding, structured transformations) are script candidates—note these in the design output. See [Deterministic Scripting](deterministic-scripting.md) for heuristics and language selection.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- [Defer Work](../defer-work/workflow.md) - Checkpoint mechanism
|
||||
- [Domain Hooks](../brainstorming/domain-hooks.md) - Domain-specific questions
|
||||
- [Deterministic Scripting](deterministic-scripting.md) - When to use scripts vs AI
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
# Tool Patterns Catalog
|
||||
|
||||
Patterns for building robust, self-correcting Claude Code tools (skills, agents, commands).
|
||||
|
||||
## Pattern Summary
|
||||
|
||||
| Pattern | Purpose | When to Use |
|
||||
|---------|---------|-------------|
|
||||
| [Audit](audit-pattern.md) | Smart implicit audits | Modifying existing tools, vague requests |
|
||||
| [Verification](verification-pattern.md) | Mandatory completion checks | Before claiming any work complete |
|
||||
| [Brainstorming](brainstorming-pattern.md) | Design decision refinement | Design decisions, new concepts |
|
||||
| [Reversion Protection](reversion-protection.md) | Prevent behavior regression | All tools with defined behaviors |
|
||||
| [Decisions Record](decisions-record.md) | Capture audit decisions | During any audit workflow |
|
||||
| [Deterministic Scripting](deterministic-scripting.md) | Shift mechanical work to scripts | Planning/design, identifying automation |
|
||||
| [Adaptive Verbosity](adaptive-verbosity.md) | Scale output to complexity | Skills with variable complexity (simple to complex scenarios) |
|
||||
| [Role-Workflow](role-workflow-pattern.md) | Multi-workflow orchestration | Plugins with 2+ subagent workflows needing synthesis |
|
||||
|
||||
## Quick Decision Guide
|
||||
|
||||
**Starting a new tool?**
|
||||
1. Brainstorm the design first (if non-trivial)
|
||||
2. If orchestrating 2+ subagent workflows with synthesis → use Role-Workflow pattern
|
||||
3. Identify scripting opportunities (mechanical, repeatable tasks)
|
||||
4. Define invariants and golden examples (reversion protection)
|
||||
5. Build with verification hooks
|
||||
|
||||
**Modifying an existing tool?**
|
||||
1. Check for prior decisions (`.decisions/`)
|
||||
2. Run implicit audit before changes
|
||||
3. Verify against golden examples
|
||||
4. Record any new decisions made
|
||||
|
||||
**Reviewing a tool?**
|
||||
1. Run audit workflow
|
||||
2. Record decisions made
|
||||
3. Update golden examples if behavior changes were intentional
|
||||
|
||||
**During planning/design?**
|
||||
1. For each component, ask: "Is this mechanical, repeatable, and unambiguous?"
|
||||
2. If yes → recommend a script (see deterministic-scripting pattern)
|
||||
3. Choose language based on project context
|
||||
|
||||
## Integration Points
|
||||
|
||||
These patterns work together:
|
||||
|
||||
```
|
||||
Brainstorming ──→ Design decisions captured
|
||||
│
|
||||
↓
|
||||
Planning ──→ Script opportunities identified
|
||||
│ Multi-workflow orchestration? → Role-Workflow pattern
|
||||
↓
|
||||
Building ──→ Invariants + golden examples defined
|
||||
│ Scripts implement mechanical tasks
|
||||
│ Role-Workflow: Task files + dispatch structure
|
||||
↓
|
||||
Modifying ──→ Audit runs implicitly
|
||||
│ │
|
||||
│ ↓
|
||||
│ Decisions recorded
|
||||
│ │
|
||||
↓ ↓
|
||||
Verification ──→ Golden examples checked
|
||||
Scripts provide deterministic evidence
|
||||
Role-Workflow: Synthesis step verifies completeness
|
||||
```
|
||||
|
||||
## Model Guidance
|
||||
|
||||
| Pattern | Recommended Model |
|
||||
|---------|-------------------|
|
||||
| Audit execution - structure | Haiku |
|
||||
| Audit execution - content/quality | Opus |
|
||||
| Decision recording | Haiku |
|
||||
| Brainstorming | Opus |
|
||||
| Verification | Haiku |
|
||||
| Golden example comparison | Opus |
|
||||
| Role-Workflow - research | Haiku |
|
||||
| Role-Workflow - processing | Sonnet |
|
||||
| Role-Workflow - judgment | Opus |
|
||||
| Cleanup | Haiku |
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Cleanup Pattern
|
||||
|
||||
Systematic removal of temporary artifacts after workflow completion.
|
||||
|
||||
## When to Use
|
||||
- After audit workflows complete
|
||||
- After creation workflows succeed
|
||||
- After any workflow with scratch files
|
||||
|
||||
## Model Selection
|
||||
Always Haiku - cleanup is mechanical.
|
||||
|
||||
## What to Clean
|
||||
- scratch/ workspace files
|
||||
- Intermediate task file sections
|
||||
- Temporary validation outputs
|
||||
|
||||
## What to Keep
|
||||
- Final reports
|
||||
- Decision records
|
||||
- Task file Summary section
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
# Decisions Record Pattern
|
||||
|
||||
Capture and persist decisions made during audits and reviews.
|
||||
|
||||
## Purpose
|
||||
|
||||
Prevent re-litigating settled decisions. Create institutional memory for tools.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
<tool-root>/
|
||||
.decisions/
|
||||
2026-01-15-description-formula.md
|
||||
2026-01-18-routing-depth.md
|
||||
2026-01-20-golden-example-update.md
|
||||
```
|
||||
|
||||
Decisions live in the tool's `.decisions/` directory, committed to version control.
|
||||
|
||||
## File Format
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-20
|
||||
context: audit / review / modification
|
||||
decision_type: invariant_change / pattern_adoption / exception
|
||||
---
|
||||
|
||||
# [Decision Title]
|
||||
|
||||
## Context
|
||||
|
||||
What prompted this decision? What audit or review surfaced it?
|
||||
|
||||
## Options Considered
|
||||
|
||||
1. **Option A:** Description. Trade-offs.
|
||||
2. **Option B:** Description. Trade-offs.
|
||||
3. **Option C:** Description. Trade-offs.
|
||||
|
||||
## Decision
|
||||
|
||||
We chose **Option B** because [reasoning].
|
||||
|
||||
## Consequences
|
||||
|
||||
- Golden example X updated to reflect this
|
||||
- Invariant Y modified/added/removed
|
||||
- [Other impacts]
|
||||
|
||||
## Participants
|
||||
|
||||
- Human: [name/handle if known]
|
||||
- AI: [model used for audit]
|
||||
```
|
||||
|
||||
## When to Record
|
||||
|
||||
**Always record:**
|
||||
- Any change to an invariant
|
||||
- Updates to golden examples
|
||||
- Adoption of new patterns
|
||||
- Exceptions to existing patterns
|
||||
- Resolutions of audit findings
|
||||
|
||||
**Don't record:**
|
||||
- Obvious bug fixes
|
||||
- Typo corrections
|
||||
- Formatting changes
|
||||
- Decisions made by user without discussion
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
### During Audits
|
||||
|
||||
```
|
||||
1. Check .decisions/ before auditing
|
||||
└─→ Load relevant prior decisions
|
||||
|
||||
2. Reference prior decisions in audit
|
||||
└─→ "Per decision from 2026-01-15, X is intentional"
|
||||
|
||||
3. Record new decisions during audit
|
||||
└─→ Write to .decisions/ before completing
|
||||
```
|
||||
|
||||
### During Modifications
|
||||
|
||||
```
|
||||
1. Check .decisions/ for this area
|
||||
└─→ Understand why things are the way they are
|
||||
|
||||
2. If changing a decided behavior
|
||||
└─→ Create new decision explaining why
|
||||
|
||||
3. Reference chain of decisions
|
||||
└─→ "This supersedes decision from 2026-01-15"
|
||||
```
|
||||
|
||||
## Naming Convention
|
||||
|
||||
`YYYY-MM-DD-<topic>.md`
|
||||
|
||||
Keep `<topic>` short but descriptive:
|
||||
- `description-formula.md`
|
||||
- `routing-depth-exception.md`
|
||||
- `golden-example-auth-flow.md`
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Create `.decisions/` directory in tool root
|
||||
- [ ] Audit workflows check `.decisions/` first
|
||||
- [ ] Record decisions during audits (not after)
|
||||
- [ ] Include context, options, reasoning, consequences
|
||||
- [ ] Reference prior decisions when relevant
|
||||
- [ ] Commit decisions to version control
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
**Decision amnesia:** Not checking prior decisions before auditing. Always check first.
|
||||
|
||||
**Over-documentation:** Recording trivial decisions. Keep to substantive choices.
|
||||
|
||||
**Orphan decisions:** Decisions without context or reasoning. Future readers need the "why."
|
||||
|
||||
**Decision conflicts:** Multiple decisions on same topic without resolution. Use "supersedes" to clarify.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- [Audit Pattern](audit-pattern.md) - Audits read and write decisions
|
||||
- [Reversion Protection](reversion-protection.md) - Invariant changes require decisions
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
# Deterministic Scripting Pattern
|
||||
|
||||
## Purpose
|
||||
|
||||
Shift mechanical, repeatable work from AI context to deterministic scripts. Scripts improve consistency, reduce context bloat, and let AI focus on judgment-based work.
|
||||
|
||||
This pattern applies during skill design and planning phases. The AI should recognize scripting opportunities and recommend them, rather than asking the user unprompted.
|
||||
|
||||
## Core Principle
|
||||
|
||||
When the AI encounters a task component during planning, it should ask:
|
||||
|
||||
> "Is this mechanical, repeatable, and unambiguous? If yes, a script will do this more reliably than I can."
|
||||
|
||||
Scripts are not replacements for AI judgment—they're complements. Scripts handle the deterministic foundation; AI handles interpretation and decisions built on that foundation.
|
||||
|
||||
## Quick Heuristics
|
||||
|
||||
Self-check during planning:
|
||||
|
||||
1. **Binary correctness?** - Is the output valid/invalid rather than good/better/best?
|
||||
2. **Identical execution?** - Would I do this the same way every time?
|
||||
3. **Structured data?** - Does it involve parsing, transforming, or validating structured formats?
|
||||
4. **No interpretation?** - Can correctness be determined without judgment?
|
||||
|
||||
If 3+ answers are "yes" → strong script candidate.
|
||||
|
||||
## Examples
|
||||
|
||||
| Task | Script? | Reasoning |
|
||||
|------|---------|-----------|
|
||||
| Validate YAML frontmatter | Yes | Binary correctness, structured data |
|
||||
| Scaffold directory structure | Yes | Deterministic file operations |
|
||||
| Parse and normalize field values | Yes | Mechanical transformation |
|
||||
| Check description length limits | Yes | Quantitative constraint |
|
||||
| Verify naming conventions | Yes | Pattern matching, no judgment |
|
||||
| Generate boilerplate from template | Yes | Deterministic substitution |
|
||||
| Evaluate description quality | No | Qualitative judgment required |
|
||||
| Choose between design approaches | No | Trade-off analysis |
|
||||
| Assess code readability | No | Subjective evaluation |
|
||||
| Decide what to include in a skill | No | Requires understanding intent |
|
||||
|
||||
### Hybrid Tasks
|
||||
|
||||
Some tasks appear qualitative but have quantitative components scripts can handle:
|
||||
|
||||
| Task | Script Portion | AI Portion |
|
||||
|------|----------------|------------|
|
||||
| Review description | Length limits, forbidden characters, required fields | Clarity, specificity, tone |
|
||||
| Audit skill structure | File existence, naming conventions, size limits | Content quality, completeness |
|
||||
| Validate workflow | Required sections present, link validity | Logical flow, clarity |
|
||||
|
||||
**Principle:** Extract quantitative guardrails into scripts. Let AI focus on the qualitative judgment that remains.
|
||||
|
||||
## Language Selection
|
||||
|
||||
Choose language based on project context and tool fit, not personal preference.
|
||||
|
||||
### Principles
|
||||
|
||||
1. **Match project context** - A Ruby script in a Rails project integrates naturally. A Python script in a Rails project adds cognitive overhead.
|
||||
|
||||
2. **Use best-in-class for domains** - Some tools have clear language winners. Playwright is best supported in Python. Data science tasks favor Python. Shell automation favors Bash.
|
||||
|
||||
3. **Prefer OOP-capable languages** - Ruby, Python, JavaScript/TypeScript support clean object-oriented design. Bash does not—use it only for simple orchestration or when it's genuinely the best fit.
|
||||
|
||||
4. **Consider maintenance** - Who will maintain this script? If the project team knows Ruby, write Ruby. If you're building a general-purpose skill, Python has broader reach.
|
||||
|
||||
### Examples
|
||||
|
||||
| Project Context | Recommended | Reasoning |
|
||||
|-----------------|-------------|-----------|
|
||||
| Rails application | Ruby | Matches project, team knows it |
|
||||
| Ruby gem | Ruby | Same ecosystem, natural fit |
|
||||
| General-purpose skill | Python | Broad reach, well-supported |
|
||||
| Browser automation | Python | Playwright's best support |
|
||||
| Data transformation | Python | Rich ecosystem (pandas, etc.) |
|
||||
| Simple file operations | Bash | Lightweight, universal |
|
||||
| Node.js project | JavaScript/TypeScript | Matches project context |
|
||||
| Cross-platform CLI tool | Python or Go | Portability matters |
|
||||
|
||||
### Anti-patterns
|
||||
|
||||
- Writing Bash for complex logic (use a real language)
|
||||
- Choosing Python for a Ruby project because "Python is more popular"
|
||||
- Using JavaScript for non-JS projects just because you know it
|
||||
- Mixing languages within a single skill's scripts without good reason
|
||||
|
||||
## OOP Principles
|
||||
|
||||
Scripts should follow object-oriented principles for maintainability and evolution. These principles, drawn from Sandi Metz's teachings, apply to Ruby, Python, and JavaScript alike.
|
||||
|
||||
### Single Responsibility
|
||||
|
||||
Each class/module does one thing. Each method does one thing.
|
||||
|
||||
```ruby
|
||||
# Good: Single responsibility
|
||||
class FrontmatterValidator
|
||||
def validate(content)
|
||||
# Only validates frontmatter
|
||||
end
|
||||
end
|
||||
|
||||
class StructureValidator
|
||||
def validate(path)
|
||||
# Only validates directory structure
|
||||
end
|
||||
end
|
||||
|
||||
# Bad: Multiple responsibilities
|
||||
class SkillValidator
|
||||
def validate_everything(path)
|
||||
# Validates frontmatter AND structure AND content AND...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
Objects receive their dependencies; they don't create them.
|
||||
|
||||
```python
|
||||
# Good: Dependencies injected
|
||||
class SkillScaffolder:
|
||||
def __init__(self, file_system, template_loader):
|
||||
self.fs = file_system
|
||||
self.templates = template_loader
|
||||
|
||||
# Bad: Dependencies created internally
|
||||
class SkillScaffolder:
|
||||
def __init__(self):
|
||||
self.fs = RealFileSystem() # Hard to test
|
||||
self.templates = TemplateLoader() # Tightly coupled
|
||||
```
|
||||
|
||||
### Small, Composable Objects
|
||||
|
||||
Prefer many small objects over few large ones. Compose behavior through collaboration.
|
||||
|
||||
```ruby
|
||||
# Good: Small, composable
|
||||
validator = CompositeValidator.new([
|
||||
NameValidator.new,
|
||||
FrontmatterValidator.new,
|
||||
StructureValidator.new
|
||||
])
|
||||
|
||||
# Bad: Monolithic
|
||||
validator = MegaValidator.new # 500 lines, does everything
|
||||
```
|
||||
|
||||
### Immutable Data Where Possible
|
||||
|
||||
Prefer transformations that return new objects over mutations.
|
||||
|
||||
```python
|
||||
# Good: Returns new object
|
||||
def with_updated_name(skill, new_name):
|
||||
return Skill(name=new_name, **skill.other_attrs)
|
||||
|
||||
# Bad: Mutates in place
|
||||
def update_name(skill, new_name):
|
||||
skill.name = new_name # Side effect
|
||||
```
|
||||
|
||||
### Tell, Don't Ask
|
||||
|
||||
Tell objects what to do; don't ask for their data and make decisions externally.
|
||||
|
||||
```ruby
|
||||
# Good: Tell the object
|
||||
validator.validate_and_report(skill_path)
|
||||
|
||||
# Bad: Ask and decide externally
|
||||
if validator.has_frontmatter?(skill_path) && validator.frontmatter_valid?(skill_path)
|
||||
# External decision-making
|
||||
end
|
||||
```
|
||||
|
||||
## Integration with Workflows
|
||||
|
||||
This pattern integrates at two points:
|
||||
|
||||
### During Brainstorming
|
||||
|
||||
When refining a skill concept, the AI should identify script candidates:
|
||||
|
||||
> "This skill involves validating workflow YAML and scaffolding directories. Both are mechanical tasks—I recommend scripts for consistency. The qualitative review of workflow clarity stays with the AI."
|
||||
|
||||
### During Planning (new-skill workflow)
|
||||
|
||||
Step 2 (Analyze and Plan) should explicitly consider:
|
||||
- Which components are script candidates?
|
||||
- What language fits this project?
|
||||
- What quantitative guardrails can be extracted?
|
||||
|
||||
The planning output should list identified scripts before implementation begins.
|
||||
|
||||
## Model Guidance
|
||||
|
||||
| Task | Recommended Model |
|
||||
|------|-------------------|
|
||||
| Writing scripts | Haiku (mechanical, clear requirements) |
|
||||
| Designing script interfaces | Opus (API design is judgment) |
|
||||
| Reviewing script correctness | Haiku (mechanical verification) |
|
||||
| Deciding what to script | Opus (requires understanding intent) |
|
||||
|
||||
## Verification
|
||||
|
||||
Two distinct concepts:
|
||||
|
||||
**Scripts must be verifiable** - Scripts themselves should be reliable and testable:
|
||||
1. **Exit codes** - 0 for success, non-zero for failure
|
||||
2. **Structured output** - JSON or clear text for parsing
|
||||
3. **Idempotent** - Running twice produces same result
|
||||
4. **Testable** - Can be run in isolation with test inputs
|
||||
|
||||
**Scripts as verification tools** - Scripts can serve as verification mechanisms for skills:
|
||||
```bash
|
||||
python scripts/validate_skill.py path/to/skill
|
||||
# Exit code 0 = valid, 1 = invalid
|
||||
# Output describes any issues found
|
||||
```
|
||||
|
||||
This complements the Verification Pattern—scripts provide deterministic evidence that work is complete and correct.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
### Over-scripting
|
||||
|
||||
Not everything needs a script. If a task requires judgment, context, or interpretation, keep it in AI domain.
|
||||
|
||||
**Signs of over-scripting:**
|
||||
- Script has many special cases and edge case handling
|
||||
- Script needs to "understand" content, not just parse it
|
||||
- Script requires frequent updates as requirements evolve
|
||||
- Script is longer than the AI instructions it replaced
|
||||
|
||||
### Under-scripting
|
||||
|
||||
Repeated mechanical work that stays in AI context wastes tokens and introduces inconsistency.
|
||||
|
||||
**Signs of under-scripting:**
|
||||
- Same validation logic described in multiple places
|
||||
- AI makes occasional errors on mechanical tasks
|
||||
- Structured data processed differently each time
|
||||
- No verification possible because there's no script to run
|
||||
|
||||
### Wrong Abstraction Level
|
||||
|
||||
Scripts should operate at the right level—not too granular, not too broad.
|
||||
|
||||
**Too granular:** Separate scripts for checking each frontmatter field
|
||||
**Too broad:** One script that validates, scaffolds, and generates content
|
||||
**Right level:** One script for frontmatter validation, one for scaffolding, one for structure checks
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
# Reversion Protection Pattern
|
||||
|
||||
Four-layer system to prevent behavioral regression in tools.
|
||||
|
||||
## Purpose
|
||||
|
||||
Protect critical behaviors from accidental changes. Make regressions structurally difficult.
|
||||
|
||||
## The Four Layers
|
||||
|
||||
### Layer 1: Invariants Declaration
|
||||
|
||||
Tools declare their behavioral invariants in `invariants.md`.
|
||||
|
||||
```markdown
|
||||
# Invariants
|
||||
|
||||
Behaviors that must not change without explicit human approval.
|
||||
|
||||
## Critical Invariants
|
||||
|
||||
- Description always follows "When/Why, Not How" formula
|
||||
- Trigger must be unambiguous (no escape hatches)
|
||||
- SKILL.md must be routing-only (no prose)
|
||||
|
||||
## Structural Invariants
|
||||
|
||||
- Maximum file depth: 3 levels
|
||||
- Maximum routing file length: 50 lines
|
||||
- Required directories: workflows/, references/
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Create `invariants.md` in tool root
|
||||
- List behaviors that define the tool's identity
|
||||
- Distinguish critical (never change) from structural (rarely change)
|
||||
|
||||
### Layer 2: Golden Examples
|
||||
|
||||
3-5 canonical examples showing correct behavior.
|
||||
|
||||
```
|
||||
examples/
|
||||
golden/
|
||||
example-1.md # Input + expected output
|
||||
example-2.md
|
||||
example-3.md
|
||||
```
|
||||
|
||||
**Golden example format:**
|
||||
```markdown
|
||||
# Golden Example: [Name]
|
||||
|
||||
## Input
|
||||
[The input or request]
|
||||
|
||||
## Expected Output
|
||||
[What the tool should produce]
|
||||
|
||||
## Why This Matters
|
||||
[What invariant this tests]
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Create `examples/golden/` directory
|
||||
- Add 3-5 examples covering key invariants
|
||||
- Each example tests a specific behavior
|
||||
|
||||
### Layer 3: Change Impact Analysis
|
||||
|
||||
Every audit must analyze impact on invariants and golden examples.
|
||||
|
||||
```markdown
|
||||
## Change Impact Analysis
|
||||
|
||||
### Invariants Affected
|
||||
- [ ] None
|
||||
- [x] Critical: description formula (explain why)
|
||||
- [ ] Structural: ...
|
||||
|
||||
### Golden Examples Affected
|
||||
- [ ] None
|
||||
- [x] example-2.md: output will change because...
|
||||
|
||||
### Risk Assessment
|
||||
[LOW/MEDIUM/HIGH] - [explanation]
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Include change impact section in every audit
|
||||
- Flag any changes affecting invariants or examples
|
||||
- Require explicit acknowledgment for critical changes
|
||||
|
||||
### Layer 4: Human Review Gate
|
||||
|
||||
Changes affecting golden examples require human approval.
|
||||
|
||||
```
|
||||
Golden example affected?
|
||||
│
|
||||
↓
|
||||
Yes ──→ Present change to human
|
||||
│ │
|
||||
│ ↓
|
||||
│ Approved? ──→ No ──→ Do not proceed
|
||||
│ │
|
||||
│ ↓
|
||||
│ Yes
|
||||
│ │
|
||||
↓ ↓
|
||||
No ──────→ Proceed with change
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Detect golden example changes during audit
|
||||
- Present specific changes to user before applying
|
||||
- Get explicit approval: "This will change how X works. Proceed?"
|
||||
- Log approval in decisions record
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Create `invariants.md` listing behavioral invariants
|
||||
- [ ] Create `examples/golden/` with 3-5 canonical examples
|
||||
- [ ] Audit workflow includes change impact analysis
|
||||
- [ ] Changes affecting golden examples flagged for review
|
||||
- [ ] Human approval required for critical invariant changes
|
||||
- [ ] Approvals logged in `.decisions/`
|
||||
|
||||
## Verification
|
||||
|
||||
Before completing any modification:
|
||||
|
||||
1. Run tool against all golden examples
|
||||
2. Compare output to expected
|
||||
3. If any mismatch: flag for review
|
||||
4. If intentional change: update golden example + record decision
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
**Invariants creep:** Adding too many invariants makes everything "critical." Keep to 3-7 true invariants.
|
||||
|
||||
**Stale examples:** Golden examples that no longer represent real usage. Review annually.
|
||||
|
||||
**Rubber-stamp reviews:** Auto-approving golden example changes. Each should be a real decision.
|
||||
|
||||
**Missing layer:** Implementing some layers but not all. The system works together.
|
||||
|
||||
## Cross-references
|
||||
|
||||
- [Audit Pattern](audit-pattern.md) - Audits must check reversion protection
|
||||
- [Verification Pattern](verification-pattern.md) - Golden examples are verification
|
||||
- [Decisions Record](decisions-record.md) - Approvals are recorded as decisions
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
# Role-Workflow-Subagent Pattern
|
||||
|
||||
A pattern for building plugins where a Role skill orchestrates multiple Workflows through subagent dispatch.
|
||||
|
||||
## When to Use
|
||||
|
||||
**Good fit:**
|
||||
- Plugin has 2+ distinct workflows that produce structured findings
|
||||
- Workflows execute in subagent context (multi-file analysis, long-running evaluations)
|
||||
- Synthesis across workflow outputs is needed
|
||||
- Context bloat is a concern in the orchestrating thread
|
||||
|
||||
**Not a fit:**
|
||||
- Single workflow with no synthesis step
|
||||
- Workflows that need to share live state (not just task file)
|
||||
- Simple tools that don't need orchestration
|
||||
- Tight coupling between workflows requiring shared context
|
||||
|
||||
## Components
|
||||
|
||||
| Component | Responsibility | State |
|
||||
|-----------|----------------|-------|
|
||||
| Thread | User conversation | Persistent |
|
||||
| Role Skill | Orchestration, synthesis | Reads/writes task file Summary |
|
||||
| Workflow Document | Framework, rubrics, templates | Static (read-only) |
|
||||
| Task File | Shared state | Written by role (Context) and subagents (their sections) |
|
||||
| Subagent | Execution | Ephemeral (discarded after completion) |
|
||||
|
||||
## Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ THREAD (Main Conversation) │
|
||||
│ ┌───────────────────────────────────────────────────┐ │
|
||||
│ │ ROLE SKILL │ │
|
||||
│ │ 1. Understand request │ │
|
||||
│ │ 2. Create task file with Context section │ │
|
||||
│ │ 3. Dispatch subagents ───────────────────────────┼──┼──┐
|
||||
│ │ 4. Wait for completion │ │ │
|
||||
│ │ 5. Read task file │ │ │
|
||||
│ │ 6. Synthesize & present │ │ │
|
||||
│ └───────────────────────────────────────────────────┘ │ │
|
||||
└─────────────────────────────────────────────────────────┘ │
|
||||
│
|
||||
┌───────────────────────────────────────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ SUBAGENT (Fresh Context) │
|
||||
│ 1. Read workflow document │
|
||||
│ 2. Read task file Context section │
|
||||
│ 3. Execute evaluation using workflow's framework │
|
||||
│ 4. Write findings to workflow's section in task file │
|
||||
│ 5. Return summary to role │
|
||||
│ [Context discarded after completion] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
plugin/
|
||||
├── skills/
|
||||
│ └── <role>/
|
||||
│ └── SKILL.md # Role orchestration logic
|
||||
├── workflows/
|
||||
│ ├── <workflow-1>.md # Workflow document
|
||||
│ └── <workflow-2>.md # Workflow document
|
||||
└── .claude/<plugin>/tasks/ # Task files (runtime, gitignored)
|
||||
└── <date>-<role>-<subject>.md
|
||||
```
|
||||
|
||||
## Task File Template
|
||||
|
||||
```markdown
|
||||
# Task: [Workflow Name] - [Subject]
|
||||
|
||||
## Context
|
||||
[Role writes: input parameters, files, goals, concerns]
|
||||
|
||||
## [Workflow 1 Name]
|
||||
Status: Pending
|
||||
[Subagent writes findings here]
|
||||
|
||||
## [Workflow 2 Name]
|
||||
Status: Pending
|
||||
[Subagent writes findings here]
|
||||
|
||||
## Summary
|
||||
[Role synthesizes after all workflows complete]
|
||||
```
|
||||
|
||||
## Dispatch Instructions
|
||||
|
||||
Keep dispatch prompts brief (6-8 lines), pointing to documents:
|
||||
|
||||
```
|
||||
Execute the [workflow-name] workflow.
|
||||
|
||||
Task file: [path]
|
||||
Workflow: [path]
|
||||
|
||||
Read the workflow document for process and templates.
|
||||
Read task file Context for scope.
|
||||
Write findings to your section in the task file.
|
||||
Return a brief summary.
|
||||
```
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Subagent Containerization** - Fresh context per workflow. Prevents context bloat.
|
||||
|
||||
2. **Workflow Documents Over Inline Prompts** - Dispatch instructions reference documents, not duplicate content.
|
||||
|
||||
3. **Task File as Shared State** - Only communication channel between role and subagents.
|
||||
|
||||
4. **Trust AI Training** - Workflow docs contain rubrics and templates, not explanations of known frameworks.
|
||||
|
||||
5. **Role Owns Synthesis** - Role reads full task file, synthesizes across all workflows.
|
||||
|
||||
6. **No Concurrent Writes** - One subagent per section. Role owns Summary.
|
||||
|
||||
7. **User Drives Iteration** - No automatic retry. User provides feedback, role re-dispatches.
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Wrong | Right |
|
||||
|-------|-------|
|
||||
| 25-line dispatch prompts duplicating workflow steps | 6-8 line dispatch pointing to workflow document |
|
||||
| Multiple workflows in same subagent | Fresh subagent per workflow |
|
||||
| Workflow documents as separate skills | Workflows are `.md` documents read by subagents |
|
||||
| Explaining frameworks in workflow docs | Listing frameworks with severity scale and output template |
|
||||
| Role returns raw findings | Role synthesizes, prioritizes, presents top issues |
|
||||
| Subagent manages cross-workflow state | Role manages state, subagent executes one workflow |
|
||||
|
||||
## Model Guidance
|
||||
|
||||
Align model selection with workflow phases (typically 2-3 subagents maximum):
|
||||
|
||||
| Phase | Model | Use case |
|
||||
|-------|-------|----------|
|
||||
| Phase 1: Research | Haiku | Data gathering, file scanning, extracting information |
|
||||
| Phase 2: Processing | Sonnet | Pattern application, filtering research output, applying frameworks, moderate judgment |
|
||||
| Phase 3: Synthesis | Opus | Role orchestration, cross-workflow synthesis, high-stakes decisions |
|
||||
|
||||
**Sonnet tier characteristics:**
|
||||
- Intermediate processing between research and synthesis
|
||||
- Apply established rubrics/frameworks to gathered data
|
||||
- Filter and structure findings for final synthesis
|
||||
- Lower-stakes decisions (e.g., severity classification, pattern matching)
|
||||
|
||||
**Note:** Most workflows use 2-3 subagents. Avoid over-segmentation.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Role skill created at `skills/<role>/SKILL.md`
|
||||
- [ ] Workflow documents created at `workflows/<workflow>.md`
|
||||
- [ ] Task file template includes Context section
|
||||
- [ ] Dispatch instructions are 6-8 lines referencing workflow docs
|
||||
- [ ] Workflow docs have: framework/checklist, output template, severity scale
|
||||
- [ ] Role synthesizes findings after all workflows complete
|
||||
- [ ] No inline duplication of workflow content in skill
|
||||
- [ ] Task file location gitignored
|
||||
|
||||
## Related Patterns
|
||||
|
||||
- **[Subagent Pattern](../subagent-pattern.md)** - General subagent guidance
|
||||
- **[Audit Pattern](audit-pattern.md)** - Single audit workflow (simpler than role-workflow)
|
||||
- **[Brainstorming Pattern](brainstorming-pattern.md)** - Design refinement before building
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Audit Severity Guide
|
||||
|
||||
Shared three-tier severity classification used by all cc-architect audit
|
||||
workflows (`${CLAUDE_PLUGIN_ROOT}/workflows/audit-plugin.md`,
|
||||
`${CLAUDE_PLUGIN_ROOT}/workflows/audit-skill.md`,
|
||||
`${CLAUDE_PLUGIN_ROOT}/workflows/audit-workflow.md`,
|
||||
`${CLAUDE_PLUGIN_ROOT}/workflows/audit-command.md`).
|
||||
|
||||
## Tiers
|
||||
|
||||
| Tier | Meaning | Verdict impact |
|
||||
|------|---------|-----------------|
|
||||
| **Significant** | Breaks compliance or correctness: missing required structure, broken references, invalid manifest/frontmatter, ambiguous ownership, incomplete flow | 1+ significant → **Not Ready** |
|
||||
| **Minor** | Works but deviates from convention: missing optional files, inconsistent patterns, suboptimal structure, missing recommended infrastructure | Does not block verdict; must still be reported |
|
||||
| **Polish** | Cosmetic: wording, formatting, organization | Does not block verdict; optional to fix |
|
||||
|
||||
**Verdict rule:** Ready only if there are zero significant findings,
|
||||
regardless of minor/polish count.
|
||||
|
||||
## Per-artifact specifics
|
||||
|
||||
Each audit workflow defines what counts as significant/minor/polish for its
|
||||
artifact type (a plugin's significant findings differ from a skill's). Look
|
||||
for the "Severity for X" section in the specific audit workflow — this
|
||||
document only defines the shared tiers and verdict rule.
|
||||
|
||||
## Applying it
|
||||
|
||||
1. Classify every finding into exactly one tier.
|
||||
2. Group findings by tier in the report (significant → minor → polish).
|
||||
3. Compute the verdict from the significant count alone.
|
||||
4. Present significant findings first when reporting to the user.
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# Decision Record Template
|
||||
|
||||
Use this template to document significant decisions about tool behavior. Decision records prevent rehashing settled questions and preserve context for future maintainers.
|
||||
|
||||
## How to Use
|
||||
|
||||
1. Create a decision record when choosing between meaningful alternatives
|
||||
2. Write while the decision is fresh - context fades quickly
|
||||
3. Store in a `decisions/` directory alongside the tool
|
||||
4. Reference in related invariants or golden examples
|
||||
|
||||
## Template
|
||||
|
||||
```markdown
|
||||
# Decision: [Brief title]
|
||||
|
||||
**Date:** YYYY-MM-DD
|
||||
|
||||
**Context:** [Why this came up - what problem or question prompted this decision]
|
||||
|
||||
**Decision:** [What was decided - be specific and unambiguous]
|
||||
|
||||
**Rationale:** [Why this approach - what factors drove the choice]
|
||||
|
||||
**Alternatives Considered:**
|
||||
- [Alternative 1]: [Why rejected]
|
||||
- [Alternative 2]: [Why rejected]
|
||||
|
||||
**Future Considerations:** [When to revisit, if ever - what would change this decision]
|
||||
```
|
||||
|
||||
## Field Definitions
|
||||
|
||||
**Brief title**: Action-oriented summary. Start with verb when possible: "Use X for Y", "Require Z before W".
|
||||
|
||||
**Date**: When decided. Helps assess if context has changed enough to revisit.
|
||||
|
||||
**Context**: The situation that forced a decision. Without this, future readers can't evaluate if the decision still applies.
|
||||
|
||||
**Decision**: The actual choice. Should be clear enough to verify compliance.
|
||||
|
||||
**Rationale**: Why this choice over others. Focus on the deciding factors, not comprehensive analysis.
|
||||
|
||||
**Alternatives Considered**: What else was evaluated. Prevents re-evaluation of already-rejected options.
|
||||
|
||||
**Future Considerations**: Conditions that would invalidate this decision. Explicitly stating "never" is valid if true.
|
||||
|
||||
## Example: Filled-in Template
|
||||
|
||||
```markdown
|
||||
# Decision: Require subagent for all creation work
|
||||
|
||||
**Date:** 2026-01-15
|
||||
|
||||
**Context:** Architect skills were creating files directly, leading to inconsistent patterns and context pollution in main conversation.
|
||||
|
||||
**Decision:** All architect skills must spawn a subagent for creation work. The main agent handles only dispatch and summary.
|
||||
|
||||
**Rationale:**
|
||||
- Fresh context prevents pattern drift from accumulated conversation
|
||||
- Subagent can fail without losing main conversation state
|
||||
- Forces explicit instruction writing, which catches missing requirements
|
||||
- Enables parallel work on independent tasks
|
||||
|
||||
**Alternatives Considered:**
|
||||
- Direct creation with context management: Rejected because context pollution is subtle and hard to detect until patterns degrade
|
||||
- Optional subagent for complex tasks: Rejected because "complex" is subjective and would lead to inconsistent application
|
||||
|
||||
**Future Considerations:** Revisit if subagent spawn overhead becomes significant (currently ~2s). Would need 10x improvement in direct creation consistency to justify reverting.
|
||||
```
|
||||
|
||||
## When to Create Decision Records
|
||||
|
||||
**Do create for:**
|
||||
- Choosing between valid approaches
|
||||
- Rejecting commonly-requested features
|
||||
- Establishing patterns that constrain future work
|
||||
- Resolving disagreements or confusion
|
||||
|
||||
**Skip for:**
|
||||
- Obvious choices with no real alternatives
|
||||
- Temporary workarounds with clear end dates
|
||||
- Implementation details that don't affect behavior
|
||||
|
||||
## Organizing Decision Records
|
||||
|
||||
Store in chronological order with date prefix:
|
||||
```
|
||||
my-tool/
|
||||
SKILL.md
|
||||
decisions/
|
||||
2026-01-15-require-subagent.md
|
||||
2026-01-18-description-validation.md
|
||||
```
|
||||
|
||||
Link from SKILL.md or invariants when relevant.
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
# Golden Example Template
|
||||
|
||||
Use this template to document canonical examples that define correct tool behavior. Golden examples serve as regression tests for tool identity.
|
||||
|
||||
## How to Use
|
||||
|
||||
1. Create one golden example per critical use case
|
||||
2. Document actual behavior, not theoretical ideals
|
||||
3. Run golden examples after any tool modification
|
||||
4. If behavior changes, either fix the tool or update the example with justification
|
||||
|
||||
## Template
|
||||
|
||||
```markdown
|
||||
# Golden Example: [scenario name]
|
||||
|
||||
## Input
|
||||
|
||||
[What the user provides - be specific about context, phrasing, and state]
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
[What the tool should do - step by step if complex]
|
||||
|
||||
## Expected Output
|
||||
|
||||
[What the tool produces - include format, structure, key content]
|
||||
|
||||
## Why This Matters
|
||||
|
||||
[What would break if this changed - user impact, downstream effects]
|
||||
```
|
||||
|
||||
## Field Definitions
|
||||
|
||||
**scenario name**: Short identifier for this example. Should be memorable and searchable.
|
||||
|
||||
**Input**: The exact trigger for this behavior. Include user message, context state, any relevant conditions.
|
||||
|
||||
**Expected Behavior**: What the tool does internally. Focus on observable actions and decision points.
|
||||
|
||||
**Expected Output**: The artifact produced. Be specific enough to verify but not so rigid that cosmetic changes cause false failures.
|
||||
|
||||
**Why This Matters**: The user-facing impact of this behavior. Explains why this is a golden example rather than just an example.
|
||||
|
||||
## Example: Filled-in Template
|
||||
|
||||
```markdown
|
||||
# Golden Example: Simple skill creation
|
||||
|
||||
## Input
|
||||
|
||||
User says: "Create a skill that formats JSON files"
|
||||
|
||||
Current directory contains an existing plugin with skills/ folder.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
1. Spawn subagent with skill-architect instructions
|
||||
2. Subagent reads existing skills in plugin for patterns
|
||||
3. Subagent creates SKILL.md with:
|
||||
- Clear frontmatter description
|
||||
- Input/output specification
|
||||
- Usage examples
|
||||
4. Subagent runs description-architect for frontmatter
|
||||
5. Subagent runs audit workflow
|
||||
6. Returns to main agent with summary
|
||||
|
||||
## Expected Output
|
||||
|
||||
- `skills/json-formatter/SKILL.md` created
|
||||
- Frontmatter passes escape-hatch test
|
||||
- Audit workflow passes or documents issues
|
||||
- Summary includes file path and usage instructions
|
||||
|
||||
## Why This Matters
|
||||
|
||||
This is the minimal happy path. If this breaks:
|
||||
- Users can't create basic skills
|
||||
- Pattern adherence fails at the most common use case
|
||||
- Trust in the tool degrades
|
||||
```
|
||||
|
||||
## Guidelines for Good Golden Examples
|
||||
|
||||
**Choose scenarios that:**
|
||||
- Represent common use cases
|
||||
- Exercise critical decision points
|
||||
- Have clear success criteria
|
||||
- Would cause visible user impact if broken
|
||||
|
||||
**Avoid scenarios that:**
|
||||
- Test implementation details
|
||||
- Depend on external state
|
||||
- Have ambiguous success criteria
|
||||
- Overlap significantly with other golden examples
|
||||
|
||||
## Organizing Golden Examples
|
||||
|
||||
Store golden examples in a `golden-examples/` directory alongside the tool:
|
||||
```
|
||||
my-tool/
|
||||
SKILL.md
|
||||
golden-examples/
|
||||
simple-creation.md
|
||||
error-handling.md
|
||||
edge-case-empty-input.md
|
||||
```
|
||||
|
||||
Reference them in the tool's invariants documentation.
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
# Impact Analysis Template
|
||||
|
||||
Use this template before making changes to document what will be affected. Impact analysis prevents accidental regressions by forcing explicit consideration of downstream effects.
|
||||
|
||||
## How to Use
|
||||
|
||||
1. Complete this analysis before implementing changes
|
||||
2. Review against invariants and golden examples
|
||||
3. Get sign-off if impact is significant
|
||||
4. Store with the change (commit message, PR description, or dedicated file)
|
||||
|
||||
## Template
|
||||
|
||||
```markdown
|
||||
# Impact Analysis: [Brief description of change]
|
||||
|
||||
**Date:** YYYY-MM-DD
|
||||
**Proposed By:** [Who]
|
||||
|
||||
## What Will Change
|
||||
|
||||
[Specific, observable differences after the change]
|
||||
|
||||
- [Change 1]
|
||||
- [Change 2]
|
||||
|
||||
## What Could Degrade
|
||||
|
||||
[Potential negative effects - be honest about risks]
|
||||
|
||||
- [Risk 1]: [Likelihood and severity]
|
||||
- [Risk 2]: [Likelihood and severity]
|
||||
|
||||
## Invariants Affected
|
||||
|
||||
| Invariant | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| [Invariant text] | preserved / violated / modified | [Explanation] |
|
||||
|
||||
## Golden Examples Affected
|
||||
|
||||
| Example | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| [Example name] | passes / fails / needs update | [Explanation] |
|
||||
|
||||
## Recommendation
|
||||
|
||||
[ ] Proceed - changes are safe
|
||||
[ ] Proceed with monitoring - watch for [specific signals]
|
||||
[ ] Defer - needs [what's missing]
|
||||
[ ] Reject - violates [what constraint]
|
||||
|
||||
**Rationale:** [Why this recommendation]
|
||||
```
|
||||
|
||||
## Field Definitions
|
||||
|
||||
**What Will Change**: Observable differences. Focus on behavior, not implementation. Users should be able to notice these changes.
|
||||
|
||||
**What Could Degrade**: Honest risk assessment. Include low-probability high-impact risks. "Nothing could go wrong" is almost never true.
|
||||
|
||||
**Invariants Affected**: Check each documented invariant. "Preserved" means behavior unchanged. "Modified" means the invariant itself needs updating (rare and significant).
|
||||
|
||||
**Golden Examples Affected**: Run each golden example mentally or actually. "Needs update" means the example is wrong, not the change.
|
||||
|
||||
**Recommendation**: Clear action with justification. Defer and Reject are valid outcomes - not every change should proceed.
|
||||
|
||||
## Example: Filled-in Template
|
||||
|
||||
```markdown
|
||||
# Impact Analysis: Add caching to skill-architect subagent spawn
|
||||
|
||||
**Date:** 2026-01-20
|
||||
**Proposed By:** Developer
|
||||
|
||||
## What Will Change
|
||||
|
||||
- Subagent instructions cached for 5 minutes after first read
|
||||
- Subsequent skill creations in same session use cached instructions
|
||||
- Cache invalidated on any file change in skill-architect directory
|
||||
|
||||
## What Could Degrade
|
||||
|
||||
- Stale instructions if files change during session: Low likelihood, medium severity
|
||||
- Memory usage from cache: Low likelihood, low severity
|
||||
- Debugging difficulty if cache behavior unexpected: Medium likelihood, low severity
|
||||
|
||||
## Invariants Affected
|
||||
|
||||
| Invariant | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| Always uses subagent for creation work | preserved | Cache is for instructions, not execution |
|
||||
| Never creates files without reading existing patterns | preserved | Patterns still read, just cached |
|
||||
| Always runs description-architect for frontmatter | preserved | Unaffected |
|
||||
|
||||
## Golden Examples Affected
|
||||
|
||||
| Example | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Simple skill creation | passes | Behavior identical, just faster on repeat |
|
||||
| Error handling | passes | Errors still propagate correctly |
|
||||
|
||||
## Recommendation
|
||||
|
||||
[x] Proceed with monitoring - watch for stale instruction complaints
|
||||
|
||||
**Rationale:** Performance improvement with low risk. Cache invalidation on file change handles the main failure mode. Monitor for user reports of unexpected behavior in first week.
|
||||
```
|
||||
|
||||
## When to Create Impact Analysis
|
||||
|
||||
**Always create for:**
|
||||
- Changes to documented invariants
|
||||
- Changes affecting multiple tools
|
||||
- Performance optimizations (often have subtle behavior changes)
|
||||
- Removing or deprecating features
|
||||
|
||||
**Consider creating for:**
|
||||
- Adding new features (may affect existing behavior)
|
||||
- Refactoring (should be behavior-preserving but verify)
|
||||
- Dependency updates
|
||||
|
||||
**Skip for:**
|
||||
- Documentation-only changes
|
||||
- Adding tests without behavior changes
|
||||
- Fixing bugs to match documented behavior
|
||||
|
||||
## Using Impact Analysis Results
|
||||
|
||||
- **Proceed**: Implement, commit, done
|
||||
- **Proceed with monitoring**: Implement, set reminder to check signals, document what to watch
|
||||
- **Defer**: Document what's needed, create task to revisit
|
||||
- **Reject**: Document in decision record why this change was rejected
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
# Invariants Template
|
||||
|
||||
Use this template to document tool invariants - behaviors that must never change regardless of improvements or refactoring.
|
||||
|
||||
## How to Use
|
||||
|
||||
1. Copy the YAML block below into your tool's documentation
|
||||
2. Fill in each field based on actual behavior, not aspirations
|
||||
3. Review invariants after any significant change
|
||||
4. Add new invariants when discovered through use
|
||||
|
||||
## Template
|
||||
|
||||
```yaml
|
||||
# Tool: [name]
|
||||
|
||||
invariants:
|
||||
- "[Behavior that must not change]"
|
||||
- "[Constraint that defines the tool]"
|
||||
- "[Add more as needed]"
|
||||
|
||||
value_proposition: "[What makes this tool valuable - speed? reliability? flexibility?]"
|
||||
|
||||
deliberate_omissions:
|
||||
- reason: "[Why this was excluded]"
|
||||
pattern: "[Pattern intentionally NOT included]"
|
||||
- reason: "[Another reason]"
|
||||
pattern: "[Another omitted pattern]"
|
||||
```
|
||||
|
||||
## Field Definitions
|
||||
|
||||
**invariants**: Behaviors that define the tool's identity. If these change, the tool becomes something else. Test these after every modification.
|
||||
|
||||
**value_proposition**: The core reason users choose this tool. Guides prioritization when constraints conflict.
|
||||
|
||||
**deliberate_omissions**: Patterns you consciously decided NOT to include. Documents "why not" to prevent future reversion through well-intentioned additions.
|
||||
|
||||
## Example: Filled-in Template
|
||||
|
||||
```yaml
|
||||
# Tool: skill-architect
|
||||
|
||||
invariants:
|
||||
- "Always uses subagent for creation work"
|
||||
- "Never creates files without reading existing patterns first"
|
||||
- "Always runs description-architect for frontmatter"
|
||||
- "Produces skills that pass audit workflow"
|
||||
|
||||
value_proposition: "Consistent, high-quality skills through mandatory patterns and automated validation"
|
||||
|
||||
deliberate_omissions:
|
||||
- reason: "Skills should be standalone; coupling creates maintenance burden"
|
||||
pattern: "Multi-skill creation in single invocation"
|
||||
- reason: "Descriptions require specialized knowledge"
|
||||
pattern: "Inline description generation (use description-architect instead)"
|
||||
- reason: "Audits provide feedback loop for improvement"
|
||||
pattern: "Skipping audit step for 'simple' skills"
|
||||
```
|
||||
|
||||
## When to Update
|
||||
|
||||
- After discovering behavior that breaks user expectations
|
||||
- When refusing a "helpful" change that would violate core identity
|
||||
- When finding undocumented constraints through debugging
|
||||
- During retrospectives on tool failures
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# Verification Pattern
|
||||
|
||||
Mandatory verification before claiming any work complete.
|
||||
|
||||
## Purpose
|
||||
|
||||
Prevent false completion claims. Every tool must specify how to verify it works.
|
||||
|
||||
## Core Rule
|
||||
|
||||
**Never claim work is complete without running verification.**
|
||||
|
||||
This applies to:
|
||||
- Building new tools
|
||||
- Modifying existing tools
|
||||
- Fixing issues found in audits
|
||||
- Any task that changes behavior
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
Tools implementing this pattern must:
|
||||
|
||||
- [ ] Declare verification method in tool definition
|
||||
- [ ] Verification must be executable (not just "review")
|
||||
- [ ] Run verification before completion claim
|
||||
- [ ] Report verification output, not just "it passed"
|
||||
- [ ] If verification fails, do not claim completion
|
||||
|
||||
## Verification Types
|
||||
|
||||
### Script-based
|
||||
```yaml
|
||||
verification:
|
||||
type: script
|
||||
command: python scripts/validate_skill.py <skill-path>
|
||||
success_criteria: "Exit code 0, no errors"
|
||||
```
|
||||
|
||||
### Example-based
|
||||
```yaml
|
||||
verification:
|
||||
type: golden_examples
|
||||
examples_path: examples/
|
||||
check: "Output matches expected for all examples"
|
||||
```
|
||||
|
||||
### Behavioral
|
||||
```yaml
|
||||
verification:
|
||||
type: behavioral
|
||||
test: "Invoke with test input, check output matches invariants"
|
||||
invariants_path: invariants.md
|
||||
```
|
||||
|
||||
### Manual (last resort)
|
||||
```yaml
|
||||
verification:
|
||||
type: manual
|
||||
steps:
|
||||
- "Run skill with test case X"
|
||||
- "Verify output contains Y"
|
||||
- "Check file Z was created"
|
||||
```
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
```
|
||||
Work complete?
|
||||
│
|
||||
↓
|
||||
Run verification ──→ Failed ──→ Fix issues ──→ (loop)
|
||||
│
|
||||
↓
|
||||
Passed
|
||||
│
|
||||
↓
|
||||
Check golden examples ──→ Changed ──→ Flag for review
|
||||
│
|
||||
↓
|
||||
Report completion with verification output
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
**Verification-by-assertion:** "I verified it works" without running anything. Always show evidence.
|
||||
|
||||
**Partial verification:** Checking one thing when multiple things changed. Verify all affected behaviors.
|
||||
|
||||
**Verification deferral:** "Will verify later." Verify now or don't claim completion.
|
||||
|
||||
**Silent failure:** Verification failed but reporting success. If it fails, say so.
|
||||
|
||||
## Evidence Format
|
||||
|
||||
When reporting completion, include:
|
||||
|
||||
```markdown
|
||||
## Verification Results
|
||||
|
||||
**Method:** [script/examples/behavioral/manual]
|
||||
**Command:** [what was run]
|
||||
**Output:**
|
||||
[actual output or summary]
|
||||
|
||||
**Status:** PASSED / FAILED
|
||||
```
|
||||
|
||||
## Cross-references
|
||||
|
||||
- [Audit Pattern](audit-pattern.md) - Audits require verification of fixes
|
||||
- [Reversion Protection](reversion-protection.md) - Golden examples are a verification method
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
|
||||
module PluginConfig
|
||||
class SchemaError < StandardError
|
||||
attr_reader :check_result
|
||||
|
||||
def initialize(check_result)
|
||||
@check_result = check_result
|
||||
super(check_result[:error])
|
||||
end
|
||||
|
||||
def to_diagnostic_json
|
||||
{
|
||||
error: 'schema_mismatch',
|
||||
details: check_result
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
module Expectations
|
||||
SETTINGS = {
|
||||
description: 'Project settings file',
|
||||
required_keys: %w[enabledPlugins]
|
||||
}.freeze
|
||||
|
||||
MARKETPLACE = {
|
||||
description: 'Marketplace registry',
|
||||
required_keys: %w[name plugins]
|
||||
}.freeze
|
||||
|
||||
INSTALLED_PLUGINS = {
|
||||
description: 'Installed plugins registry',
|
||||
required_keys: %w[plugins]
|
||||
}.freeze
|
||||
|
||||
def self.check(file_path, expected_keys)
|
||||
unless File.exist?(file_path)
|
||||
return { ok: false, error: "File not found: #{file_path}", missing_keys: expected_keys }
|
||||
end
|
||||
|
||||
data = JSON.parse(File.read(file_path))
|
||||
missing = expected_keys.reject { |key| data.key?(key) }
|
||||
|
||||
if missing.empty?
|
||||
{ ok: true }
|
||||
else
|
||||
{ ok: false, error: "Missing required keys in #{file_path}", missing_keys: missing }
|
||||
end
|
||||
rescue JSON::ParserError => e
|
||||
{ ok: false, error: "Invalid JSON in #{file_path}: #{e.message}", missing_keys: expected_keys }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PluginConfig
|
||||
module FileLocations
|
||||
INSTALLED_PLUGINS_PATH = File.join(Dir.home, '.claude', 'plugins', 'installed_plugins.json')
|
||||
KNOWN_MARKETPLACES_PATH = File.join(Dir.home, '.claude', 'plugins', 'known_marketplaces.json')
|
||||
SETTINGS_RELATIVE_PATH = File.join('.claude', 'settings.json')
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
|
||||
module PluginConfig
|
||||
class FileVerifier
|
||||
attr_reader :path
|
||||
|
||||
def initialize(path)
|
||||
@path = path
|
||||
end
|
||||
|
||||
def verify_written(expected_content)
|
||||
return { verified: false, details: "File not found: #{path}" } unless File.exist?(path)
|
||||
|
||||
actual = File.read(path)
|
||||
if actual == expected_content
|
||||
{ verified: true, details: 'Write verified: content matches' }
|
||||
else
|
||||
{ verified: false, details: 'Write verification failed: content mismatch' }
|
||||
end
|
||||
rescue StandardError => e
|
||||
{ verified: false, details: "Verification error: #{e.message}" }
|
||||
end
|
||||
|
||||
def verify_key(section, key, expected_value)
|
||||
return { verified: false, details: "File not found: #{path}" } unless File.exist?(path)
|
||||
|
||||
data = JSON.parse(File.read(path))
|
||||
container = data.dig(section)
|
||||
|
||||
if container.nil?
|
||||
return { verified: false, details: "Section '#{section}' not found" }
|
||||
end
|
||||
|
||||
if container.key?(key) && container[key] == expected_value
|
||||
{ verified: true, details: "Key '#{key}' has expected value #{expected_value.inspect}" }
|
||||
else
|
||||
actual = container.key?(key) ? container[key].inspect : '(absent)'
|
||||
{ verified: false, details: "Key '#{key}' expected #{expected_value.inspect}, got #{actual}" }
|
||||
end
|
||||
rescue JSON::ParserError => e
|
||||
{ verified: false, details: "JSON parse error: #{e.message}" }
|
||||
rescue StandardError => e
|
||||
{ verified: false, details: "Verification error: #{e.message}" }
|
||||
end
|
||||
|
||||
def verify_key_absent(section, key)
|
||||
return { verified: false, details: "File not found: #{path}" } unless File.exist?(path)
|
||||
|
||||
data = JSON.parse(File.read(path))
|
||||
container = data.dig(section)
|
||||
|
||||
if container.nil? || !container.key?(key)
|
||||
{ verified: true, details: "Key '#{key}' is absent from '#{section}'" }
|
||||
else
|
||||
{ verified: false, details: "Key '#{key}' still present in '#{section}'" }
|
||||
end
|
||||
rescue JSON::ParserError => e
|
||||
{ verified: false, details: "JSON parse error: #{e.message}" }
|
||||
rescue StandardError => e
|
||||
{ verified: false, details: "Verification error: #{e.message}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'base'
|
||||
|
||||
module PluginConfig
|
||||
module Operations
|
||||
class Audit < Base
|
||||
def call
|
||||
checks = {}
|
||||
issues = []
|
||||
fixes = []
|
||||
|
||||
checks[:marketplace] = check_marketplace
|
||||
checks[:installed] = check_installed
|
||||
checks[:settings] = check_settings
|
||||
|
||||
checks.each_value do |check|
|
||||
next if check[:status] == 'ok'
|
||||
|
||||
issues << check[:issue]
|
||||
fixes << check[:fix] if check[:fix]
|
||||
end
|
||||
|
||||
{
|
||||
status: issues.empty? ? 'ok' : 'issues_found',
|
||||
checks: checks,
|
||||
issues: issues,
|
||||
fixes: fixes
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_marketplace
|
||||
path = marketplace_file
|
||||
return missing_check('marketplace', path, register_fix) unless path && File.exist?(path)
|
||||
|
||||
data = read_json(path)
|
||||
return parse_error_check('marketplace', path) unless data
|
||||
|
||||
registered = data['plugins']&.any? { |p| p['name'] == plugin }
|
||||
if registered
|
||||
{ status: 'ok', path: path }
|
||||
else
|
||||
{
|
||||
status: 'issue',
|
||||
path: path,
|
||||
issue: "Plugin '#{plugin}' not registered in #{path}",
|
||||
fix: register_fix
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def check_installed
|
||||
path = installed_plugins_path
|
||||
return missing_check('installed_plugins', path, install_fix) unless File.exist?(path)
|
||||
|
||||
data = read_json(path)
|
||||
return parse_error_check('installed_plugins', path) unless data
|
||||
|
||||
installed = data['plugins']&.key?(plugin_key)
|
||||
if installed
|
||||
{ status: 'ok', path: path }
|
||||
else
|
||||
{
|
||||
status: 'issue',
|
||||
path: path,
|
||||
issue: "Plugin '#{plugin_key}' not found in installed plugins",
|
||||
fix: install_fix
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def check_settings
|
||||
path = settings_path
|
||||
return missing_check('settings', path, enable_fix) unless project && File.exist?(path)
|
||||
|
||||
data = read_json(path)
|
||||
return parse_error_check('settings', path) unless data
|
||||
|
||||
enabled = data['enabledPlugins']&.key?(plugin_key)
|
||||
if enabled
|
||||
{ status: 'ok', path: path }
|
||||
else
|
||||
{
|
||||
status: 'issue',
|
||||
path: path,
|
||||
issue: "Plugin '#{plugin_key}' not enabled in #{path}",
|
||||
fix: enable_fix
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def missing_check(name, path, fix)
|
||||
{
|
||||
status: 'issue',
|
||||
path: path,
|
||||
issue: "#{name} file not found at #{path}",
|
||||
fix: fix
|
||||
}
|
||||
end
|
||||
|
||||
def parse_error_check(name, path)
|
||||
{
|
||||
status: 'issue',
|
||||
path: path,
|
||||
issue: "#{name} file at #{path} contains invalid JSON"
|
||||
}
|
||||
end
|
||||
|
||||
def register_fix
|
||||
"Register plugin: ruby plugin_config.rb register --plugin=#{plugin} --marketplace_file=<path>"
|
||||
end
|
||||
|
||||
def install_fix
|
||||
"Install plugin: /install #{plugin_key}"
|
||||
end
|
||||
|
||||
def enable_fix
|
||||
"Enable plugin: ruby plugin_config.rb enable --plugin=#{plugin} --marketplace=#{marketplace} --project=#{project}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
require 'fileutils'
|
||||
require_relative '../file_locations'
|
||||
require_relative '../file_verifier'
|
||||
|
||||
module PluginConfig
|
||||
module Operations
|
||||
class Base
|
||||
attr_reader :plugin, :marketplace, :project, :marketplace_file
|
||||
|
||||
def initialize(params)
|
||||
@plugin = params[:plugin]
|
||||
@marketplace = params[:marketplace]
|
||||
@project = params[:project]
|
||||
@marketplace_file = params[:marketplace_file]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def plugin_key
|
||||
"#{plugin}@#{resolved_marketplace}"
|
||||
end
|
||||
|
||||
# The marketplace id this plugin is actually installed under, per
|
||||
# installed_plugins.json, falling back to the --marketplace param when
|
||||
# no installed entry exists yet (e.g. before first install). Needed
|
||||
# because cc-os's os-* plugins install under a `local-plugins`
|
||||
# marketplace (symlink into ~/.claude/plugins) rather than whatever
|
||||
# marketplace id the caller assumes (e.g. the repo's own `cc-os`),
|
||||
# which otherwise produces false "not installed" results.
|
||||
def resolved_marketplace
|
||||
data = read_json(installed_plugins_path)
|
||||
if data && data['plugins']
|
||||
match = data['plugins'].keys.find { |k| k.start_with?("#{plugin}@") }
|
||||
return match.split('@', 2).last if match
|
||||
end
|
||||
marketplace
|
||||
end
|
||||
|
||||
def read_json(path)
|
||||
return nil unless File.exist?(path)
|
||||
|
||||
JSON.parse(File.read(path))
|
||||
rescue JSON::ParserError
|
||||
nil
|
||||
end
|
||||
|
||||
def write_json(path, data)
|
||||
FileUtils.mkdir_p(File.dirname(path))
|
||||
content = JSON.pretty_generate(data, indent: ' ') + "\n"
|
||||
File.write(path, content)
|
||||
|
||||
verifier = FileVerifier.new(path)
|
||||
verifier.verify_written(content)
|
||||
end
|
||||
|
||||
def settings_path
|
||||
File.join(project, '.claude', 'settings.json')
|
||||
end
|
||||
|
||||
def installed_plugins_path
|
||||
File.join(Dir.home, '.claude', 'plugins', 'installed_plugins.json')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'base'
|
||||
require_relative '../expectations'
|
||||
|
||||
module PluginConfig
|
||||
module Operations
|
||||
class Disable < Base
|
||||
def call
|
||||
# Validate settings schema if file exists
|
||||
if File.exist?(settings_path)
|
||||
check_result = Expectations.check(settings_path, %w[enabledPlugins])
|
||||
raise SchemaError, check_result unless check_result[:ok]
|
||||
end
|
||||
|
||||
data = read_json(settings_path)
|
||||
|
||||
unless data && data['enabledPlugins']&.key?(plugin_key)
|
||||
return {
|
||||
status: 'not_found',
|
||||
plugin: plugin_key,
|
||||
path: settings_path,
|
||||
verified: true,
|
||||
remaining_plugins: data ? (data['enabledPlugins'] || {}).keys.sort : []
|
||||
}
|
||||
end
|
||||
|
||||
data['enabledPlugins'].delete(plugin_key)
|
||||
data['enabledPlugins'] = data['enabledPlugins'].sort.to_h
|
||||
|
||||
write_json(settings_path, data)
|
||||
|
||||
verifier = FileVerifier.new(settings_path)
|
||||
key_check = verifier.verify_key_absent('enabledPlugins', plugin_key)
|
||||
|
||||
{
|
||||
status: 'disabled',
|
||||
plugin: plugin_key,
|
||||
path: settings_path,
|
||||
verified: key_check[:verified],
|
||||
remaining_plugins: data['enabledPlugins'].keys.sort
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'base'
|
||||
require_relative '../expectations'
|
||||
|
||||
module PluginConfig
|
||||
module Operations
|
||||
class Enable < Base
|
||||
def call
|
||||
# Validate settings schema if file exists
|
||||
if File.exist?(settings_path)
|
||||
check_result = Expectations.check(settings_path, %w[enabledPlugins])
|
||||
raise SchemaError, check_result unless check_result[:ok]
|
||||
end
|
||||
|
||||
data = read_json(settings_path) || { 'enabledPlugins' => {} }
|
||||
data['enabledPlugins'] ||= {}
|
||||
|
||||
already = data['enabledPlugins'].key?(plugin_key)
|
||||
|
||||
data['enabledPlugins'][plugin_key] = true
|
||||
data['enabledPlugins'] = data['enabledPlugins'].sort.to_h
|
||||
|
||||
created = !File.exist?(settings_path)
|
||||
write_result = write_json(settings_path, data)
|
||||
|
||||
verifier = FileVerifier.new(settings_path)
|
||||
key_check = verifier.verify_key('enabledPlugins', plugin_key, true)
|
||||
|
||||
status = if already
|
||||
'already_enabled'
|
||||
elsif created
|
||||
'created_and_enabled'
|
||||
else
|
||||
'enabled'
|
||||
end
|
||||
|
||||
{
|
||||
status: status,
|
||||
plugin: plugin_key,
|
||||
path: settings_path,
|
||||
verified: key_check[:verified],
|
||||
enabled_plugins: data['enabledPlugins'].keys.sort
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'base'
|
||||
|
||||
module PluginConfig
|
||||
module Operations
|
||||
class List < Base
|
||||
def call
|
||||
data = read_json(settings_path)
|
||||
|
||||
plugins = if data && data['enabledPlugins']
|
||||
data['enabledPlugins'].keys.sort
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
{
|
||||
status: 'ok',
|
||||
project: project,
|
||||
enabled_plugins: plugins,
|
||||
count: plugins.length
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'base'
|
||||
require_relative '../expectations'
|
||||
|
||||
module PluginConfig
|
||||
module Operations
|
||||
class Register < Base
|
||||
def initialize(params)
|
||||
super
|
||||
@description = params[:description] || ''
|
||||
end
|
||||
|
||||
def call
|
||||
path = marketplace_file
|
||||
|
||||
# Validate marketplace schema if file exists
|
||||
if path && File.exist?(path)
|
||||
check_result = Expectations.check(path, %w[name plugins])
|
||||
raise SchemaError, check_result unless check_result[:ok]
|
||||
end
|
||||
|
||||
data = read_json(path)
|
||||
|
||||
unless data
|
||||
return {
|
||||
status: 'error',
|
||||
error: "Marketplace file not found or invalid: #{path}"
|
||||
}
|
||||
end
|
||||
|
||||
data['plugins'] ||= []
|
||||
|
||||
existing = data['plugins'].find { |p| p['name'] == plugin }
|
||||
if existing
|
||||
return {
|
||||
status: 'already_registered',
|
||||
plugin: plugin,
|
||||
path: path,
|
||||
verified: true
|
||||
}
|
||||
end
|
||||
|
||||
data['plugins'] << {
|
||||
'name' => plugin,
|
||||
'source' => "./#{plugin}",
|
||||
'description' => @description
|
||||
}
|
||||
|
||||
data['plugins'].sort_by! { |p| p['name'] }
|
||||
|
||||
write_json(path, data)
|
||||
|
||||
verifier = FileVerifier.new(path)
|
||||
readback = read_json(path)
|
||||
found = readback && readback['plugins']&.any? { |p| p['name'] == plugin }
|
||||
|
||||
{
|
||||
status: 'registered',
|
||||
plugin: plugin,
|
||||
path: path,
|
||||
verified: found == true
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'base'
|
||||
require_relative '../expectations'
|
||||
|
||||
module PluginConfig
|
||||
module Operations
|
||||
class Unregister < Base
|
||||
def call
|
||||
path = marketplace_file
|
||||
|
||||
# Validate marketplace schema if file exists
|
||||
if path && File.exist?(path)
|
||||
check_result = Expectations.check(path, %w[name plugins])
|
||||
raise SchemaError, check_result unless check_result[:ok]
|
||||
end
|
||||
|
||||
data = read_json(path)
|
||||
|
||||
unless data
|
||||
return {
|
||||
status: 'error',
|
||||
error: "Marketplace file not found or invalid: #{path}"
|
||||
}
|
||||
end
|
||||
|
||||
data['plugins'] ||= []
|
||||
|
||||
original_count = data['plugins'].length
|
||||
data['plugins'].reject! { |p| p['name'] == plugin }
|
||||
|
||||
if data['plugins'].length == original_count
|
||||
return {
|
||||
status: 'not_found',
|
||||
plugin: plugin,
|
||||
path: path,
|
||||
verified: true
|
||||
}
|
||||
end
|
||||
|
||||
write_json(path, data)
|
||||
|
||||
readback = read_json(path)
|
||||
absent = readback && readback['plugins']&.none? { |p| p['name'] == plugin }
|
||||
|
||||
{
|
||||
status: 'unregistered',
|
||||
plugin: plugin,
|
||||
path: path,
|
||||
verified: absent == true
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'base'
|
||||
|
||||
module PluginConfig
|
||||
module Operations
|
||||
# Structural validation for a plugin directory: plugin.json shape,
|
||||
# marketplace registration/consistency, and skills/ layout.
|
||||
class Validate < Base
|
||||
def initialize(params)
|
||||
super
|
||||
@plugin_path = params[:plugin_path]
|
||||
end
|
||||
|
||||
attr_reader :plugin_path
|
||||
|
||||
def call
|
||||
return missing_plugin_path_result unless plugin_path && !plugin_path.to_s.strip.empty?
|
||||
|
||||
load_state
|
||||
|
||||
checks = {
|
||||
plugin_json: check_plugin_json,
|
||||
required_fields: check_required_fields,
|
||||
name_matches_directory: check_name_matches_directory,
|
||||
marketplace_entry: check_marketplace_entry,
|
||||
description_matches: check_description_matches,
|
||||
skill_md_present: check_skill_md_present,
|
||||
skills_array_matches: check_skills_array_matches,
|
||||
marketplace_source: check_marketplace_source
|
||||
}
|
||||
|
||||
build_result(checks)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# ─── State loading ──────────────────────────────────────────────────────
|
||||
|
||||
def load_state
|
||||
@plugin_json = load_plugin_json
|
||||
@marketplace_data = read_json(marketplace_file_path)
|
||||
@marketplace_entry = find_marketplace_entry
|
||||
end
|
||||
|
||||
# True when no --marketplace_file was given and the default marketplace
|
||||
# location can't be found. In this case marketplace-dependent checks are
|
||||
# skipped rather than failed, so the validator works against plugin
|
||||
# directories that aren't registered in any marketplace.json (e.g. when
|
||||
# invoked from a different repo). If --marketplace_file was explicitly
|
||||
# given, a missing/invalid file is still a hard failure.
|
||||
def marketplace_explicitly_given?
|
||||
!marketplace_file.to_s.strip.empty?
|
||||
end
|
||||
|
||||
def skip_marketplace_checks?
|
||||
!marketplace_explicitly_given? && !File.exist?(default_marketplace_file)
|
||||
end
|
||||
|
||||
def plugin_json_path
|
||||
File.join(plugin_path, '.claude-plugin', 'plugin.json')
|
||||
end
|
||||
|
||||
def load_plugin_json
|
||||
return nil unless File.exist?(plugin_json_path)
|
||||
|
||||
read_json(plugin_json_path)
|
||||
end
|
||||
|
||||
def marketplace_file_path
|
||||
marketplace_file || default_marketplace_file
|
||||
end
|
||||
|
||||
def default_marketplace_file
|
||||
File.join(File.dirname(File.expand_path(plugin_path)), '.claude-plugin', 'marketplace.json')
|
||||
end
|
||||
|
||||
def find_marketplace_entry
|
||||
return nil unless @plugin_json && @marketplace_data
|
||||
|
||||
(@marketplace_data['plugins'] || []).find { |p| p['name'] == @plugin_json['name'] }
|
||||
end
|
||||
|
||||
def skills_root
|
||||
File.join(plugin_path, 'skills')
|
||||
end
|
||||
|
||||
def skill_directories
|
||||
return [] unless Dir.exist?(skills_root)
|
||||
|
||||
Dir.children(skills_root).select { |entry| File.directory?(File.join(skills_root, entry)) }.sort
|
||||
end
|
||||
|
||||
# ─── Checks ─────────────────────────────────────────────────────────────
|
||||
|
||||
def check_plugin_json
|
||||
path = plugin_json_path
|
||||
return fail_check("plugin.json not found at #{path}") unless File.exist?(path)
|
||||
return fail_check("plugin.json at #{path} contains invalid JSON") unless @plugin_json
|
||||
|
||||
pass_check("plugin.json exists and is valid JSON at #{path}")
|
||||
end
|
||||
|
||||
def check_required_fields
|
||||
return fail_check('Cannot check required fields: plugin.json is missing or invalid') unless @plugin_json
|
||||
|
||||
missing = %w[name description].select { |key| blank?(@plugin_json[key]) }
|
||||
return fail_check("Missing or empty required field(s): #{missing.join(', ')}") unless missing.empty?
|
||||
|
||||
pass_check('Required fields present: name, description')
|
||||
end
|
||||
|
||||
def check_name_matches_directory
|
||||
return fail_check('Cannot check name: plugin.json is missing or invalid') unless @plugin_json
|
||||
|
||||
expected = directory_basename
|
||||
actual = @plugin_json['name']
|
||||
return fail_check("plugin.json name #{actual.inspect} does not match directory basename #{expected.inspect}") unless actual == expected
|
||||
|
||||
pass_check("plugin.json name '#{actual}' matches directory basename '#{expected}'")
|
||||
end
|
||||
|
||||
def check_marketplace_entry
|
||||
if skip_marketplace_checks?
|
||||
return skip_check(
|
||||
"No --marketplace_file given and default location #{default_marketplace_file} not found; " \
|
||||
'skipping marketplace registration check'
|
||||
)
|
||||
end
|
||||
return fail_check('Cannot check marketplace entry: plugin.json is missing or invalid') unless @plugin_json
|
||||
return fail_check("Marketplace file not found at #{marketplace_file_path}") unless File.exist?(marketplace_file_path)
|
||||
return fail_check("Marketplace file at #{marketplace_file_path} contains invalid JSON") unless @marketplace_data
|
||||
return fail_check("No marketplace entry found for plugin '#{@plugin_json['name']}' in #{marketplace_file_path}") unless @marketplace_entry
|
||||
|
||||
pass_check("Found marketplace entry for '#{@plugin_json['name']}' in #{marketplace_file_path}")
|
||||
end
|
||||
|
||||
def check_description_matches
|
||||
if skip_marketplace_checks?
|
||||
return skip_check(
|
||||
"No --marketplace_file given and default location #{default_marketplace_file} not found; " \
|
||||
'skipping description consistency check'
|
||||
)
|
||||
end
|
||||
unless @plugin_json && @marketplace_entry
|
||||
return fail_check('Cannot check description: plugin.json or marketplace entry is unavailable')
|
||||
end
|
||||
|
||||
plugin_description = @plugin_json['description']
|
||||
marketplace_description = @marketplace_entry['description']
|
||||
if plugin_description == marketplace_description
|
||||
pass_check('plugin.json description matches marketplace.json description byte-for-byte')
|
||||
else
|
||||
fail_check(
|
||||
"Description drift: plugin.json has #{plugin_description.inspect}, " \
|
||||
"marketplace.json has #{marketplace_description.inspect}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def check_skill_md_present
|
||||
return pass_check('No skills/ directory present; nothing to check') unless Dir.exist?(skills_root)
|
||||
|
||||
missing = skill_directories.reject { |dir| File.exist?(File.join(skills_root, dir, 'SKILL.md')) }
|
||||
return fail_check("Missing SKILL.md in skill directories: #{missing.join(', ')}") unless missing.empty?
|
||||
|
||||
pass_check('All skill directories contain SKILL.md')
|
||||
end
|
||||
|
||||
def check_skills_array_matches
|
||||
return fail_check('Cannot check skills array: plugin.json is missing or invalid') unless @plugin_json
|
||||
return pass_check('plugin.json declares no skills array; nothing to check') unless @plugin_json.key?('skills')
|
||||
|
||||
declared = Array(@plugin_json['skills']).sort
|
||||
actual = skill_directories
|
||||
if declared == actual
|
||||
pass_check('plugin.json skills array matches skills/ directories')
|
||||
else
|
||||
fail_check("plugin.json skills array #{declared.inspect} does not match actual directories #{actual.inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def check_marketplace_source
|
||||
if skip_marketplace_checks?
|
||||
return skip_check(
|
||||
"No --marketplace_file given and default location #{default_marketplace_file} not found; " \
|
||||
'skipping marketplace source check'
|
||||
)
|
||||
end
|
||||
return fail_check('Cannot check marketplace source: marketplace entry is unavailable') unless @marketplace_entry
|
||||
|
||||
actual = @marketplace_entry['source']
|
||||
return fail_check('Marketplace entry has no source field') if blank?(actual)
|
||||
|
||||
resolved = File.expand_path(actual, marketplace_root)
|
||||
expected = File.expand_path(plugin_path)
|
||||
if resolved == expected
|
||||
pass_check("Marketplace source '#{actual}' resolves to plugin directory")
|
||||
else
|
||||
fail_check(
|
||||
"Marketplace source #{actual.inspect} resolves to #{resolved.inspect}, " \
|
||||
"which does not match plugin directory #{expected.inspect}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# The directory a marketplace entry's `source` is resolved relative to.
|
||||
# marketplace.json conventionally lives at <root>/.claude-plugin/marketplace.json,
|
||||
# with sources relative to <root>. If the marketplace file isn't nested under a
|
||||
# .claude-plugin/ directory (e.g. a flat fixture file), fall back to its own
|
||||
# containing directory.
|
||||
def marketplace_root
|
||||
dir = File.dirname(File.expand_path(marketplace_file_path))
|
||||
File.basename(dir) == '.claude-plugin' ? File.dirname(dir) : dir
|
||||
end
|
||||
|
||||
# ─── Helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
def directory_basename
|
||||
File.basename(plugin_path.to_s.chomp('/'))
|
||||
end
|
||||
|
||||
def blank?(value)
|
||||
value.nil? || (value.is_a?(String) && value.strip.empty?)
|
||||
end
|
||||
|
||||
def pass_check(message)
|
||||
{ status: 'pass', message: message }
|
||||
end
|
||||
|
||||
def fail_check(message)
|
||||
{ status: 'fail', message: message }
|
||||
end
|
||||
|
||||
def skip_check(message)
|
||||
{ status: 'skipped', message: message }
|
||||
end
|
||||
|
||||
def build_result(checks)
|
||||
failed = checks.values.select { |check| check[:status] == 'fail' }
|
||||
|
||||
{
|
||||
status: failed.empty? ? 'ok' : 'issues_found',
|
||||
valid: failed.empty?,
|
||||
plugin_path: plugin_path,
|
||||
checks: checks,
|
||||
issues: failed.map { |check| check[:message] }
|
||||
}
|
||||
end
|
||||
|
||||
def missing_plugin_path_result
|
||||
{
|
||||
status: 'issues_found',
|
||||
valid: false,
|
||||
plugin_path: plugin_path,
|
||||
checks: {},
|
||||
issues: ['No plugin_path given. Usage: validate --plugin_path=<path-to-plugin>']
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
require_relative 'file_locations'
|
||||
require_relative 'file_verifier'
|
||||
require_relative 'runtime_verifier'
|
||||
require_relative 'expectations'
|
||||
require_relative 'operations/enable'
|
||||
require_relative 'operations/disable'
|
||||
require_relative 'operations/register'
|
||||
require_relative 'operations/unregister'
|
||||
require_relative 'operations/audit'
|
||||
require_relative 'operations/list'
|
||||
require_relative 'operations/validate'
|
||||
|
||||
module PluginConfig
|
||||
OPERATIONS = {
|
||||
'enable' => Operations::Enable,
|
||||
'disable' => Operations::Disable,
|
||||
'register' => Operations::Register,
|
||||
'unregister' => Operations::Unregister,
|
||||
'audit' => Operations::Audit,
|
||||
'list' => Operations::List,
|
||||
'validate' => Operations::Validate
|
||||
}.freeze
|
||||
|
||||
def self.run(argv)
|
||||
operation_name = argv.shift
|
||||
|
||||
unless operation_name && OPERATIONS.key?(operation_name)
|
||||
valid = OPERATIONS.keys.join(', ')
|
||||
$stderr.puts JSON.pretty_generate(
|
||||
error: 'invalid_operation',
|
||||
message: "Usage: plugin_config.rb <operation> [--key=value ...]",
|
||||
valid_operations: valid
|
||||
)
|
||||
exit 1
|
||||
end
|
||||
|
||||
params = {}
|
||||
snapshot_requested = false
|
||||
|
||||
argv.each do |arg|
|
||||
if arg == '--snapshot'
|
||||
snapshot_requested = true
|
||||
elsif arg.start_with?('--')
|
||||
key, value = arg.sub(/^--/, '').split('=', 2)
|
||||
params[key.to_sym] = value
|
||||
end
|
||||
end
|
||||
|
||||
runtime = RuntimeVerifier.new if snapshot_requested
|
||||
before = runtime&.snapshot
|
||||
|
||||
operation = OPERATIONS[operation_name].new(params)
|
||||
result = operation.call
|
||||
|
||||
if snapshot_requested && runtime
|
||||
after = runtime.snapshot
|
||||
result[:runtime_diff] = RuntimeVerifier.diff(before, after)
|
||||
end
|
||||
|
||||
puts JSON.pretty_generate(result)
|
||||
exit(result[:valid] == false ? 1 : 0)
|
||||
rescue SchemaError => e
|
||||
$stderr.puts JSON.pretty_generate(e.to_diagnostic_json)
|
||||
exit 2
|
||||
rescue StandardError => e
|
||||
$stderr.puts JSON.pretty_generate(
|
||||
error: 'unexpected_error',
|
||||
message: e.message,
|
||||
operation: operation_name
|
||||
)
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
PluginConfig.run(ARGV.dup) if __FILE__ == $PROGRAM_NAME
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
require 'open3'
|
||||
|
||||
module PluginConfig
|
||||
class RuntimeVerifier
|
||||
STATUSES = %w[enabled disabled failed_to_load not_present].freeze
|
||||
|
||||
def initialize(capture3: Open3.method(:capture3))
|
||||
@capture3 = capture3
|
||||
end
|
||||
|
||||
def snapshot
|
||||
stdout, _stderr, status = @capture3.call('claude', 'plugin', 'list')
|
||||
return {} unless status.success?
|
||||
|
||||
parse_plugin_list(stdout)
|
||||
rescue Errno::ENOENT
|
||||
{}
|
||||
end
|
||||
|
||||
def verify_status(plugin_key, expected_status)
|
||||
current = snapshot
|
||||
actual = current[plugin_key] || 'not_present'
|
||||
|
||||
if actual == expected_status
|
||||
{ verified: true, details: "Plugin '#{plugin_key}' status is '#{expected_status}'" }
|
||||
else
|
||||
{ verified: false, details: "Plugin '#{plugin_key}' expected '#{expected_status}', got '#{actual}'" }
|
||||
end
|
||||
end
|
||||
|
||||
def self.diff(before, after)
|
||||
all_keys = (before.keys + after.keys).uniq
|
||||
changed = 0
|
||||
unchanged = 0
|
||||
|
||||
all_keys.each do |key|
|
||||
if before[key] == after[key]
|
||||
unchanged += 1
|
||||
else
|
||||
changed += 1
|
||||
end
|
||||
end
|
||||
|
||||
{ changed: changed, unchanged: unchanged }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_plugin_list(output)
|
||||
plugins = {}
|
||||
|
||||
output.each_line do |line|
|
||||
line = line.strip
|
||||
next if line.empty?
|
||||
|
||||
# Expected format: "plugin-name@marketplace status"
|
||||
# or variations with columns/tabs
|
||||
parts = line.split(/\s{2,}|\t/)
|
||||
next unless parts.length >= 2
|
||||
|
||||
key = parts[0].strip
|
||||
status = parts[1].strip.downcase
|
||||
|
||||
next unless key.include?('@')
|
||||
|
||||
normalized = if STATUSES.include?(status)
|
||||
status
|
||||
elsif status.include?('enabled')
|
||||
'enabled'
|
||||
elsif status.include?('disabled')
|
||||
'disabled'
|
||||
elsif status.include?('fail')
|
||||
'failed_to_load'
|
||||
else
|
||||
status
|
||||
end
|
||||
|
||||
plugins[key] = normalized
|
||||
end
|
||||
|
||||
plugins
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'test_helper'
|
||||
require 'open3'
|
||||
|
||||
class TestEntryPoint < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
SCRIPT_PATH = File.expand_path('../plugin_config.rb', __dir__)
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def test_valid_enable_operation_exits_0_with_valid_json
|
||||
create_settings({})
|
||||
create_marketplace(plugins: [])
|
||||
|
||||
stdout, stderr, status = run_script(
|
||||
'enable',
|
||||
"--plugin=test-plugin",
|
||||
"--marketplace=test-marketplace",
|
||||
"--project=#{@project_dir}"
|
||||
)
|
||||
|
||||
assert status.success?, "Expected exit 0, got #{status.exitstatus}. stderr: #{stderr}"
|
||||
parsed = JSON.parse(stdout)
|
||||
assert_includes %w[created_and_enabled enabled], parsed['status']
|
||||
end
|
||||
|
||||
def test_unknown_operation_exits_1
|
||||
_stdout, stderr, status = run_script('bogus_operation')
|
||||
|
||||
assert_equal 1, status.exitstatus
|
||||
parsed = JSON.parse(stderr)
|
||||
assert_equal 'invalid_operation', parsed['error']
|
||||
end
|
||||
|
||||
def test_no_operation_exits_1
|
||||
_stdout, stderr, status = run_script
|
||||
|
||||
assert_equal 1, status.exitstatus
|
||||
parsed = JSON.parse(stderr)
|
||||
assert_equal 'invalid_operation', parsed['error']
|
||||
end
|
||||
|
||||
def test_list_operation_exits_0
|
||||
create_settings('a@b' => true)
|
||||
|
||||
stdout, _stderr, status = run_script(
|
||||
'list',
|
||||
"--project=#{@project_dir}"
|
||||
)
|
||||
|
||||
assert status.success?
|
||||
parsed = JSON.parse(stdout)
|
||||
assert_equal 'ok', parsed['status']
|
||||
assert_includes parsed['enabled_plugins'], 'a@b'
|
||||
end
|
||||
|
||||
def test_output_is_always_valid_json_on_error
|
||||
# Pass a bad project path so the script hits an error path
|
||||
_stdout, stderr, status = run_script('bogus')
|
||||
|
||||
refute status.success?
|
||||
# Should be valid JSON on stderr
|
||||
parsed = JSON.parse(stderr) # will raise if not valid JSON
|
||||
assert parsed.is_a?(Hash), 'Expected stderr to contain a JSON object'
|
||||
end
|
||||
|
||||
def test_disable_operation_exits_0
|
||||
create_settings('test-plugin@test-marketplace' => true)
|
||||
|
||||
stdout, _stderr, status = run_script(
|
||||
'disable',
|
||||
"--plugin=test-plugin",
|
||||
"--marketplace=test-marketplace",
|
||||
"--project=#{@project_dir}"
|
||||
)
|
||||
|
||||
assert status.success?
|
||||
parsed = JSON.parse(stdout)
|
||||
assert_equal 'disabled', parsed['status']
|
||||
end
|
||||
|
||||
def test_schema_error_exits_2_with_malformed_settings
|
||||
# Create a malformed settings.json missing required 'enabledPlugins' key
|
||||
# This will trigger schema validation in the Enable operation
|
||||
malformed_settings = '{"badKey": "value"}'
|
||||
FileUtils.mkdir_p(File.dirname(settings_path))
|
||||
File.write(settings_path, malformed_settings)
|
||||
|
||||
_stdout, stderr, status = run_script(
|
||||
'enable',
|
||||
"--plugin=test-plugin",
|
||||
"--marketplace=test-marketplace",
|
||||
"--project=#{@project_dir}"
|
||||
)
|
||||
|
||||
assert_equal 2, status.exitstatus, "Expected exit code 2 for schema error, got #{status.exitstatus}"
|
||||
parsed = JSON.parse(stderr)
|
||||
assert_equal 'schema_mismatch', parsed['error']
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def run_script(*args)
|
||||
Open3.capture3('ruby', SCRIPT_PATH, *args)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'test_helper'
|
||||
|
||||
class TestExpectations < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
@json_path = File.join(@tmpdir, 'test.json')
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def test_check_valid_file_with_all_expected_keys_returns_ok
|
||||
File.write(@json_path, JSON.pretty_generate('enabledPlugins' => {}))
|
||||
|
||||
result = PluginConfig::Expectations.check(@json_path, %w[enabledPlugins])
|
||||
|
||||
assert result[:ok]
|
||||
end
|
||||
|
||||
def test_check_with_missing_keys_returns_schema_mismatch
|
||||
File.write(@json_path, JSON.pretty_generate('other' => {}))
|
||||
|
||||
result = PluginConfig::Expectations.check(@json_path, %w[enabledPlugins])
|
||||
|
||||
refute result[:ok]
|
||||
assert_includes result[:missing_keys], 'enabledPlugins'
|
||||
assert_match(/Missing required keys/, result[:error])
|
||||
end
|
||||
|
||||
def test_check_missing_file_returns_file_missing
|
||||
result = PluginConfig::Expectations.check('/nonexistent/path.json', %w[enabledPlugins])
|
||||
|
||||
refute result[:ok]
|
||||
assert_match(/File not found/, result[:error])
|
||||
assert_includes result[:missing_keys], 'enabledPlugins'
|
||||
end
|
||||
|
||||
def test_check_invalid_json_returns_parse_error
|
||||
File.write(@json_path, '{{{not json')
|
||||
|
||||
result = PluginConfig::Expectations.check(@json_path, %w[enabledPlugins])
|
||||
|
||||
refute result[:ok]
|
||||
assert_match(/Invalid JSON/, result[:error])
|
||||
end
|
||||
|
||||
def test_check_with_multiple_expected_keys
|
||||
File.write(@json_path, JSON.pretty_generate('name' => 'test', 'plugins' => []))
|
||||
|
||||
result = PluginConfig::Expectations.check(@json_path, %w[name plugins])
|
||||
|
||||
assert result[:ok]
|
||||
end
|
||||
|
||||
# ─── SchemaError ─────────────────────────────────────────────────────
|
||||
|
||||
def test_schema_error_to_diagnostic_json_returns_structured_output
|
||||
check_result = { ok: false, error: 'Missing required keys', missing_keys: %w[enabledPlugins] }
|
||||
error = PluginConfig::SchemaError.new(check_result)
|
||||
|
||||
diag = error.to_diagnostic_json
|
||||
|
||||
assert_equal 'schema_mismatch', diag[:error]
|
||||
assert_equal check_result, diag[:details]
|
||||
end
|
||||
|
||||
def test_schema_error_message_matches_error_field
|
||||
check_result = { ok: false, error: 'test error message', missing_keys: [] }
|
||||
error = PluginConfig::SchemaError.new(check_result)
|
||||
|
||||
assert_equal 'test error message', error.message
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'test_helper'
|
||||
|
||||
class TestFileVerifier < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
@json_path = File.join(@tmpdir, 'test.json')
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
# ─── verify_key ───────────────────────────────────────────────────────
|
||||
|
||||
def test_verify_key_with_correct_value_returns_ok
|
||||
File.write(@json_path, JSON.pretty_generate('enabledPlugins' => { 'foo@bar' => true }))
|
||||
|
||||
v = PluginConfig::FileVerifier.new(@json_path)
|
||||
result = v.verify_key('enabledPlugins', 'foo@bar', true)
|
||||
|
||||
assert result[:verified]
|
||||
end
|
||||
|
||||
def test_verify_key_with_wrong_value_returns_not_ok
|
||||
File.write(@json_path, JSON.pretty_generate('enabledPlugins' => { 'foo@bar' => false }))
|
||||
|
||||
v = PluginConfig::FileVerifier.new(@json_path)
|
||||
result = v.verify_key('enabledPlugins', 'foo@bar', true)
|
||||
|
||||
refute result[:verified]
|
||||
assert_match(/false/, result[:details])
|
||||
end
|
||||
|
||||
def test_verify_key_missing_file_returns_file_missing
|
||||
v = PluginConfig::FileVerifier.new('/nonexistent/path.json')
|
||||
result = v.verify_key('enabledPlugins', 'foo@bar', true)
|
||||
|
||||
refute result[:verified]
|
||||
assert_match(/File not found/, result[:details])
|
||||
end
|
||||
|
||||
def test_verify_key_invalid_json_returns_parse_error
|
||||
File.write(@json_path, 'not valid json {{{')
|
||||
|
||||
v = PluginConfig::FileVerifier.new(@json_path)
|
||||
result = v.verify_key('enabledPlugins', 'foo@bar', true)
|
||||
|
||||
refute result[:verified]
|
||||
assert_match(/JSON parse error/, result[:details])
|
||||
end
|
||||
|
||||
def test_verify_key_missing_section_returns_not_found
|
||||
File.write(@json_path, JSON.pretty_generate('other' => {}))
|
||||
|
||||
v = PluginConfig::FileVerifier.new(@json_path)
|
||||
result = v.verify_key('enabledPlugins', 'foo@bar', true)
|
||||
|
||||
refute result[:verified]
|
||||
assert_match(/Section.*not found/, result[:details])
|
||||
end
|
||||
|
||||
# ─── verify_key_absent ────────────────────────────────────────────────
|
||||
|
||||
def test_verify_key_absent_when_key_is_absent_returns_ok
|
||||
File.write(@json_path, JSON.pretty_generate('enabledPlugins' => {}))
|
||||
|
||||
v = PluginConfig::FileVerifier.new(@json_path)
|
||||
result = v.verify_key_absent('enabledPlugins', 'foo@bar')
|
||||
|
||||
assert result[:verified]
|
||||
end
|
||||
|
||||
def test_verify_key_absent_when_key_is_present_returns_not_ok
|
||||
File.write(@json_path, JSON.pretty_generate('enabledPlugins' => { 'foo@bar' => true }))
|
||||
|
||||
v = PluginConfig::FileVerifier.new(@json_path)
|
||||
result = v.verify_key_absent('enabledPlugins', 'foo@bar')
|
||||
|
||||
refute result[:verified]
|
||||
assert_match(/still present/, result[:details])
|
||||
end
|
||||
|
||||
# ─── verify_written ───────────────────────────────────────────────────
|
||||
|
||||
def test_verify_written_matching_content_returns_ok
|
||||
content = '{"hello": "world"}'
|
||||
File.write(@json_path, content)
|
||||
|
||||
v = PluginConfig::FileVerifier.new(@json_path)
|
||||
result = v.verify_written(content)
|
||||
|
||||
assert result[:verified]
|
||||
end
|
||||
|
||||
def test_verify_written_mismatched_content_returns_not_ok
|
||||
File.write(@json_path, '{"hello": "world"}')
|
||||
|
||||
v = PluginConfig::FileVerifier.new(@json_path)
|
||||
result = v.verify_written('{"hello": "different"}')
|
||||
|
||||
refute result[:verified]
|
||||
assert_match(/mismatch/, result[:details])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'minitest/autorun'
|
||||
require 'tmpdir'
|
||||
require 'fileutils'
|
||||
require 'json'
|
||||
|
||||
# Add the plugin_config directory to the load path
|
||||
$LOAD_PATH.unshift(File.expand_path('..', __dir__))
|
||||
|
||||
require 'file_locations'
|
||||
require 'file_verifier'
|
||||
require 'runtime_verifier'
|
||||
require 'expectations'
|
||||
require 'operations/enable'
|
||||
require 'operations/disable'
|
||||
require 'operations/register'
|
||||
require 'operations/unregister'
|
||||
require 'operations/audit'
|
||||
require 'operations/list'
|
||||
require 'operations/validate'
|
||||
|
||||
module TestHelpers
|
||||
def setup_temp_dirs
|
||||
@tmpdir = Dir.mktmpdir('plugin_config_test')
|
||||
@project_dir = File.join(@tmpdir, 'project')
|
||||
@settings_dir = File.join(@project_dir, '.claude')
|
||||
@marketplace_dir = @tmpdir
|
||||
FileUtils.mkdir_p(@settings_dir)
|
||||
end
|
||||
|
||||
def teardown_temp_dirs
|
||||
FileUtils.rm_rf(@tmpdir) if @tmpdir && Dir.exist?(@tmpdir)
|
||||
end
|
||||
|
||||
def settings_path
|
||||
File.join(@project_dir, '.claude', 'settings.json')
|
||||
end
|
||||
|
||||
def marketplace_path
|
||||
File.join(@marketplace_dir, 'marketplace.json')
|
||||
end
|
||||
|
||||
def create_settings(enabled_plugins = {})
|
||||
data = { 'enabledPlugins' => enabled_plugins }
|
||||
FileUtils.mkdir_p(File.dirname(settings_path))
|
||||
File.write(settings_path, JSON.pretty_generate(data) + "\n")
|
||||
settings_path
|
||||
end
|
||||
|
||||
def create_marketplace(name: 'test-marketplace', plugins: [])
|
||||
data = { 'name' => name, 'plugins' => plugins }
|
||||
File.write(marketplace_path, JSON.pretty_generate(data) + "\n")
|
||||
marketplace_path
|
||||
end
|
||||
|
||||
def create_installed_plugins(path, plugins = {})
|
||||
data = { 'plugins' => plugins }
|
||||
FileUtils.mkdir_p(File.dirname(path))
|
||||
File.write(path, JSON.pretty_generate(data) + "\n")
|
||||
path
|
||||
end
|
||||
|
||||
def create_plugin_json(plugin_dir, name:, description:, extra: {})
|
||||
data = { 'name' => name, 'description' => description }.merge(extra)
|
||||
path = File.join(plugin_dir, '.claude-plugin', 'plugin.json')
|
||||
FileUtils.mkdir_p(File.dirname(path))
|
||||
File.write(path, JSON.pretty_generate(data) + "\n")
|
||||
path
|
||||
end
|
||||
|
||||
def create_skill(plugin_dir, skill_name, with_skill_md: true)
|
||||
skill_dir = File.join(plugin_dir, 'skills', skill_name)
|
||||
FileUtils.mkdir_p(skill_dir)
|
||||
File.write(File.join(skill_dir, 'SKILL.md'), "# #{skill_name}\n") if with_skill_md
|
||||
skill_dir
|
||||
end
|
||||
|
||||
def make_operation(klass, overrides = {})
|
||||
params = {
|
||||
plugin: 'test-plugin',
|
||||
marketplace: 'test-marketplace',
|
||||
project: @project_dir,
|
||||
marketplace_file: marketplace_path
|
||||
}.merge(overrides)
|
||||
klass.new(params)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,368 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'test_helper'
|
||||
|
||||
# ─── Enable ───────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestEnable < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def test_enable_new_plugin_creates_file_and_adds_entry
|
||||
op = make_operation(PluginConfig::Operations::Enable)
|
||||
result = op.call
|
||||
|
||||
assert_includes %w[created_and_enabled enabled], result[:status]
|
||||
assert_equal 'test-plugin@test-marketplace', result[:plugin]
|
||||
assert result[:verified], 'Expected verified to be true'
|
||||
assert_includes result[:enabled_plugins], 'test-plugin@test-marketplace'
|
||||
end
|
||||
|
||||
def test_enable_adds_to_existing_settings
|
||||
create_settings('other-plugin@other-marketplace' => true)
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Enable)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'enabled', result[:status]
|
||||
assert_includes result[:enabled_plugins], 'other-plugin@other-marketplace'
|
||||
assert_includes result[:enabled_plugins], 'test-plugin@test-marketplace'
|
||||
end
|
||||
|
||||
def test_enable_already_enabled_returns_already_enabled
|
||||
create_settings('test-plugin@test-marketplace' => true)
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Enable)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'already_enabled', result[:status]
|
||||
assert result[:verified]
|
||||
end
|
||||
|
||||
def test_enabled_plugins_sorted_alphabetically
|
||||
create_settings('z-plugin@z-marketplace' => true, 'a-plugin@a-marketplace' => true)
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Enable)
|
||||
result = op.call
|
||||
|
||||
expected = ['a-plugin@a-marketplace', 'test-plugin@test-marketplace', 'z-plugin@z-marketplace']
|
||||
assert_equal expected, result[:enabled_plugins]
|
||||
end
|
||||
|
||||
def test_verified_flag_is_true_on_success
|
||||
op = make_operation(PluginConfig::Operations::Enable)
|
||||
result = op.call
|
||||
|
||||
assert result[:verified]
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Disable ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestDisable < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def test_disable_existing_plugin_removes_entry
|
||||
create_settings('test-plugin@test-marketplace' => true)
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Disable)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'disabled', result[:status]
|
||||
refute_includes result[:remaining_plugins], 'test-plugin@test-marketplace'
|
||||
end
|
||||
|
||||
def test_disable_nonexistent_plugin_returns_not_found
|
||||
create_settings({})
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Disable)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'not_found', result[:status]
|
||||
end
|
||||
|
||||
def test_disable_preserves_other_plugins
|
||||
create_settings(
|
||||
'test-plugin@test-marketplace' => true,
|
||||
'keep-me@other' => true
|
||||
)
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Disable)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'disabled', result[:status]
|
||||
assert_includes result[:remaining_plugins], 'keep-me@other'
|
||||
refute_includes result[:remaining_plugins], 'test-plugin@test-marketplace'
|
||||
end
|
||||
|
||||
def test_verified_flag_confirms_key_absent
|
||||
create_settings('test-plugin@test-marketplace' => true)
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Disable)
|
||||
result = op.call
|
||||
|
||||
assert result[:verified]
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Register ─────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestRegister < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def test_register_new_plugin_adds_to_marketplace
|
||||
create_marketplace(plugins: [])
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Register, description: 'A test plugin')
|
||||
result = op.call
|
||||
|
||||
assert_equal 'registered', result[:status]
|
||||
assert result[:verified]
|
||||
|
||||
data = JSON.parse(File.read(marketplace_path))
|
||||
names = data['plugins'].map { |p| p['name'] }
|
||||
assert_includes names, 'test-plugin'
|
||||
end
|
||||
|
||||
def test_register_already_present_returns_already_registered
|
||||
create_marketplace(plugins: [{ 'name' => 'test-plugin', 'source' => './test-plugin', 'description' => '' }])
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Register)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'already_registered', result[:status]
|
||||
assert result[:verified]
|
||||
end
|
||||
|
||||
def test_plugins_array_sorted_by_name
|
||||
create_marketplace(plugins: [{ 'name' => 'z-plugin', 'source' => './z-plugin', 'description' => '' }])
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Register, plugin: 'a-plugin')
|
||||
result = op.call
|
||||
|
||||
data = JSON.parse(File.read(marketplace_path))
|
||||
names = data['plugins'].map { |p| p['name'] }
|
||||
assert_equal %w[a-plugin z-plugin], names
|
||||
end
|
||||
|
||||
def test_description_is_stored_correctly
|
||||
create_marketplace(plugins: [])
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Register, description: 'My description')
|
||||
result = op.call
|
||||
|
||||
data = JSON.parse(File.read(marketplace_path))
|
||||
plugin_entry = data['plugins'].find { |p| p['name'] == 'test-plugin' }
|
||||
assert_equal 'My description', plugin_entry['description']
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Unregister ───────────────────────────────────────────────────────────────
|
||||
|
||||
class TestUnregister < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def test_unregister_existing_plugin
|
||||
create_marketplace(plugins: [{ 'name' => 'test-plugin', 'source' => './test-plugin', 'description' => '' }])
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Unregister)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'unregistered', result[:status]
|
||||
assert result[:verified]
|
||||
|
||||
data = JSON.parse(File.read(marketplace_path))
|
||||
names = data['plugins'].map { |p| p['name'] }
|
||||
refute_includes names, 'test-plugin'
|
||||
end
|
||||
|
||||
def test_unregister_nonexistent_returns_not_found
|
||||
create_marketplace(plugins: [])
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Unregister)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'not_found', result[:status]
|
||||
end
|
||||
|
||||
def test_unregister_preserves_other_plugins
|
||||
create_marketplace(plugins: [
|
||||
{ 'name' => 'keep-me', 'source' => './keep-me', 'description' => '' },
|
||||
{ 'name' => 'test-plugin', 'source' => './test-plugin', 'description' => '' }
|
||||
])
|
||||
|
||||
op = make_operation(PluginConfig::Operations::Unregister)
|
||||
result = op.call
|
||||
|
||||
data = JSON.parse(File.read(marketplace_path))
|
||||
names = data['plugins'].map { |p| p['name'] }
|
||||
assert_includes names, 'keep-me'
|
||||
refute_includes names, 'test-plugin'
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Audit ────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestAudit < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
# Create installed_plugins in temp location; override the path
|
||||
@installed_path = File.join(@tmpdir, 'installed_plugins.json')
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def test_clean_state_returns_ok
|
||||
# Create all required files with correct entries
|
||||
create_marketplace(plugins: [{ 'name' => 'test-plugin', 'source' => './test-plugin', 'description' => '' }])
|
||||
create_settings('test-plugin@test-marketplace' => true)
|
||||
|
||||
# Need to stub installed_plugins_path - create a subclass
|
||||
op = make_audit_op_with_installed({ 'test-plugin@test-marketplace' => { 'status' => 'enabled' } })
|
||||
result = op.call
|
||||
|
||||
assert_equal 'ok', result[:status]
|
||||
assert_empty result[:issues]
|
||||
end
|
||||
|
||||
def test_missing_marketplace_entry_reports_issue_with_fix
|
||||
create_marketplace(plugins: []) # plugin not registered
|
||||
create_settings('test-plugin@test-marketplace' => true)
|
||||
|
||||
op = make_audit_op_with_installed({ 'test-plugin@test-marketplace' => { 'status' => 'enabled' } })
|
||||
result = op.call
|
||||
|
||||
assert_equal 'issues_found', result[:status]
|
||||
marketplace_check = result[:checks][:marketplace]
|
||||
assert_equal 'issue', marketplace_check[:status]
|
||||
assert marketplace_check[:fix]
|
||||
end
|
||||
|
||||
def test_missing_settings_entry_reports_issue_with_fix
|
||||
create_marketplace(plugins: [{ 'name' => 'test-plugin', 'source' => './test-plugin', 'description' => '' }])
|
||||
create_settings({}) # plugin not enabled
|
||||
|
||||
op = make_audit_op_with_installed({ 'test-plugin@test-marketplace' => { 'status' => 'enabled' } })
|
||||
result = op.call
|
||||
|
||||
assert_equal 'issues_found', result[:status]
|
||||
settings_check = result[:checks][:settings]
|
||||
assert_equal 'issue', settings_check[:status]
|
||||
assert settings_check[:fix]
|
||||
end
|
||||
|
||||
def test_multiple_issues_all_reported
|
||||
create_marketplace(plugins: []) # not registered
|
||||
create_settings({}) # not enabled
|
||||
|
||||
op = make_audit_op_with_installed({}) # not installed
|
||||
result = op.call
|
||||
|
||||
assert_equal 'issues_found', result[:status]
|
||||
assert result[:issues].length >= 2, "Expected at least 2 issues, got #{result[:issues].length}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Create an Audit subclass that overrides installed_plugins_path to use temp dir
|
||||
def make_audit_op_with_installed(installed_plugins)
|
||||
installed_path = @installed_path
|
||||
create_installed_plugins(installed_path, installed_plugins)
|
||||
|
||||
params = {
|
||||
plugin: 'test-plugin',
|
||||
marketplace: 'test-marketplace',
|
||||
project: @project_dir,
|
||||
marketplace_file: marketplace_path
|
||||
}
|
||||
|
||||
op = PluginConfig::Operations::Audit.new(params)
|
||||
# Override the installed_plugins_path method
|
||||
op.define_singleton_method(:installed_plugins_path) { installed_path }
|
||||
op
|
||||
end
|
||||
end
|
||||
|
||||
# ─── List ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class TestList < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def test_list_returns_all_enabled_plugins
|
||||
create_settings(
|
||||
'b-plugin@marketplace' => true,
|
||||
'a-plugin@marketplace' => true
|
||||
)
|
||||
|
||||
op = make_operation(PluginConfig::Operations::List)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'ok', result[:status]
|
||||
assert_equal %w[a-plugin@marketplace b-plugin@marketplace], result[:enabled_plugins]
|
||||
assert_equal 2, result[:count]
|
||||
end
|
||||
|
||||
def test_list_empty_settings_returns_empty_array
|
||||
create_settings({})
|
||||
|
||||
op = make_operation(PluginConfig::Operations::List)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'ok', result[:status]
|
||||
assert_empty result[:enabled_plugins]
|
||||
assert_equal 0, result[:count]
|
||||
end
|
||||
|
||||
def test_list_missing_file_returns_empty_array
|
||||
# Don't create settings file
|
||||
op = make_operation(PluginConfig::Operations::List)
|
||||
result = op.call
|
||||
|
||||
assert_equal 'ok', result[:status]
|
||||
assert_empty result[:enabled_plugins]
|
||||
assert_equal 0, result[:count]
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'test_helper'
|
||||
|
||||
class TestRuntimeVerifier < Minitest::Test
|
||||
# Hand-rolled fake status object; stands in for the Process::Status
|
||||
# returned by Open3.capture3. Minitest 6 moved Minitest::Mock out of
|
||||
# core (into the minitest-mock gem), so we avoid it entirely here.
|
||||
FakeStatus = Struct.new(:success) do
|
||||
def success?
|
||||
success
|
||||
end
|
||||
end
|
||||
|
||||
# ─── snapshot (injected capture3) ─────────────────────────────────────
|
||||
|
||||
def test_snapshot_parses_output_into_plugin_hash
|
||||
mock_output = <<~OUTPUT
|
||||
my-plugin@marketplace enabled
|
||||
other-plugin@marketplace disabled
|
||||
OUTPUT
|
||||
|
||||
verifier = verifier_with_capture3(mock_output, success: true)
|
||||
result = verifier.snapshot
|
||||
assert_equal 'enabled', result['my-plugin@marketplace']
|
||||
assert_equal 'disabled', result['other-plugin@marketplace']
|
||||
end
|
||||
|
||||
def test_snapshot_returns_empty_hash_on_failure
|
||||
verifier = verifier_with_capture3('', success: false)
|
||||
result = verifier.snapshot
|
||||
assert_equal({}, result)
|
||||
end
|
||||
|
||||
def test_snapshot_returns_empty_hash_when_command_not_found
|
||||
verifier = PluginConfig::RuntimeVerifier.new(
|
||||
capture3: ->(*_args) { raise Errno::ENOENT, 'claude' }
|
||||
)
|
||||
result = verifier.snapshot
|
||||
assert_equal({}, result)
|
||||
end
|
||||
|
||||
# ─── verify_status (injected capture3) ────────────────────────────────
|
||||
|
||||
def test_verify_status_with_matching_status_returns_ok
|
||||
mock_output = "my-plugin@marketplace enabled\n"
|
||||
|
||||
verifier = verifier_with_capture3(mock_output, success: true)
|
||||
result = verifier.verify_status('my-plugin@marketplace', 'enabled')
|
||||
assert result[:verified]
|
||||
end
|
||||
|
||||
def test_verify_status_with_mismatched_status_returns_not_ok
|
||||
mock_output = "my-plugin@marketplace disabled\n"
|
||||
|
||||
verifier = verifier_with_capture3(mock_output, success: true)
|
||||
result = verifier.verify_status('my-plugin@marketplace', 'enabled')
|
||||
refute result[:verified]
|
||||
assert_match(/expected 'enabled', got 'disabled'/, result[:details])
|
||||
end
|
||||
|
||||
def test_verify_status_missing_plugin_returns_not_present
|
||||
verifier = verifier_with_capture3('', success: true)
|
||||
result = verifier.verify_status('missing@marketplace', 'enabled')
|
||||
refute result[:verified]
|
||||
assert_match(/not_present/, result[:details])
|
||||
end
|
||||
|
||||
# ─── self.diff ───────────────────────────────────────────────────────
|
||||
|
||||
def test_diff_identifies_changes
|
||||
before = { 'a@m' => 'enabled', 'b@m' => 'enabled' }
|
||||
after = { 'a@m' => 'disabled', 'b@m' => 'enabled', 'c@m' => 'enabled' }
|
||||
|
||||
result = PluginConfig::RuntimeVerifier.diff(before, after)
|
||||
|
||||
assert_equal 2, result[:changed] # a changed, c added
|
||||
assert_equal 1, result[:unchanged] # b unchanged
|
||||
end
|
||||
|
||||
def test_diff_identical_snapshots_returns_empty_changes
|
||||
snap = { 'a@m' => 'enabled', 'b@m' => 'disabled' }
|
||||
|
||||
result = PluginConfig::RuntimeVerifier.diff(snap, snap)
|
||||
|
||||
assert_equal 0, result[:changed]
|
||||
assert_equal 2, result[:unchanged]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verifier_with_capture3(stdout, success:)
|
||||
status = FakeStatus.new(success)
|
||||
PluginConfig::RuntimeVerifier.new(capture3: ->(*_args) { [stdout, '', status] })
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'test_helper'
|
||||
require 'open3'
|
||||
|
||||
class TestValidate < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
@plugin_dir = File.join(@tmpdir, 'my-plugin')
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def build_valid_plugin(name: 'my-plugin', description: 'Does a thing', skills: nil)
|
||||
extra = skills ? { 'skills' => skills } : {}
|
||||
create_plugin_json(@plugin_dir, name: name, description: description, extra: extra)
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
create_marketplace(plugins: [
|
||||
{ 'name' => name, 'source' => "./#{File.basename(@plugin_dir)}", 'description' => description }
|
||||
])
|
||||
end
|
||||
|
||||
def make_validate_op(overrides = {})
|
||||
params = { plugin_path: @plugin_dir, marketplace_file: marketplace_path }.merge(overrides)
|
||||
PluginConfig::Operations::Validate.new(params)
|
||||
end
|
||||
|
||||
# ─── Happy path ───────────────────────────────────────────────────────────
|
||||
|
||||
def test_valid_plugin_passes_all_checks
|
||||
build_valid_plugin
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
assert_equal 'ok', result[:status]
|
||||
assert result[:valid]
|
||||
assert_empty result[:issues]
|
||||
result[:checks].each_value do |check|
|
||||
assert_equal 'pass', check[:status], "Expected pass, got #{check.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Failure modes ────────────────────────────────────────────────────────
|
||||
|
||||
def test_missing_plugin_json_fails
|
||||
FileUtils.mkdir_p(@plugin_dir)
|
||||
create_marketplace(plugins: [])
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'issues_found', result[:status]
|
||||
assert_equal 'fail', result[:checks][:plugin_json][:status]
|
||||
end
|
||||
|
||||
def test_invalid_json_plugin_json_fails
|
||||
path = File.join(@plugin_dir, '.claude-plugin', 'plugin.json')
|
||||
FileUtils.mkdir_p(File.dirname(path))
|
||||
File.write(path, '{ not valid json')
|
||||
create_marketplace(plugins: [])
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'fail', result[:checks][:plugin_json][:status]
|
||||
end
|
||||
|
||||
def test_missing_required_field_fails
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: '')
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
create_marketplace(plugins: [{ 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => '' }])
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'fail', result[:checks][:required_fields][:status]
|
||||
end
|
||||
|
||||
def test_name_mismatch_with_directory_fails
|
||||
build_valid_plugin(name: 'totally-different-name')
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'fail', result[:checks][:name_matches_directory][:status]
|
||||
end
|
||||
|
||||
def test_missing_marketplace_entry_fails
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
create_marketplace(plugins: []) # no entry for my-plugin
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'fail', result[:checks][:marketplace_entry][:status]
|
||||
end
|
||||
|
||||
def test_description_drift_fails
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing.')
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
create_marketplace(plugins: [
|
||||
{ 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
|
||||
])
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'fail', result[:checks][:description_matches][:status]
|
||||
end
|
||||
|
||||
def test_missing_skill_md_fails
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
|
||||
create_skill(@plugin_dir, 'my-skill', with_skill_md: false)
|
||||
create_marketplace(plugins: [
|
||||
{ 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
|
||||
])
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'fail', result[:checks][:skill_md_present][:status]
|
||||
end
|
||||
|
||||
def test_skills_array_mismatch_fails
|
||||
build_valid_plugin(skills: %w[my-skill a-second-skill-not-on-disk])
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'fail', result[:checks][:skills_array_matches][:status]
|
||||
end
|
||||
|
||||
def test_wrong_marketplace_source_fails
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
create_marketplace(plugins: [
|
||||
{ 'name' => 'my-plugin', 'source' => './wrong-name', 'description' => 'Does a thing' }
|
||||
])
|
||||
|
||||
result = make_validate_op.call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'fail', result[:checks][:marketplace_source][:status]
|
||||
end
|
||||
|
||||
def test_marketplace_source_resolves_path_for_nested_repo_layout
|
||||
# marketplace.json lives at <root>/.claude-plugin/marketplace.json, and plugins
|
||||
# live under <root>/plugins/<name> instead of directly under <root>.
|
||||
root = File.join(@tmpdir, 'nested-repo')
|
||||
plugin_dir = File.join(root, 'plugins', 'my-plugin')
|
||||
create_plugin_json(plugin_dir, name: 'my-plugin', description: 'Does a thing')
|
||||
create_skill(plugin_dir, 'my-skill')
|
||||
|
||||
nested_marketplace_dir = File.join(root, '.claude-plugin')
|
||||
FileUtils.mkdir_p(nested_marketplace_dir)
|
||||
nested_marketplace_path = File.join(nested_marketplace_dir, 'marketplace.json')
|
||||
File.write(nested_marketplace_path, JSON.pretty_generate(
|
||||
'name' => 'test-marketplace',
|
||||
'plugins' => [
|
||||
{ 'name' => 'my-plugin', 'source' => './plugins/my-plugin', 'description' => 'Does a thing' }
|
||||
]
|
||||
))
|
||||
|
||||
result = PluginConfig::Operations::Validate.new(
|
||||
plugin_path: plugin_dir, marketplace_file: nested_marketplace_path
|
||||
).call
|
||||
|
||||
assert_equal 'pass', result[:checks][:marketplace_source][:status], result[:checks][:marketplace_source].inspect
|
||||
end
|
||||
|
||||
# ─── Cross-repo: graceful skip when no marketplace can be resolved ────────
|
||||
|
||||
def test_no_marketplace_resolvable_skips_marketplace_checks_without_failing
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
# No marketplace_file override, and no default marketplace.json anywhere near @plugin_dir.
|
||||
|
||||
result = PluginConfig::Operations::Validate.new(plugin_path: @plugin_dir).call
|
||||
|
||||
assert_equal 'ok', result[:status]
|
||||
assert result[:valid]
|
||||
%i[marketplace_entry description_matches marketplace_source].each do |key|
|
||||
assert_equal 'skipped', result[:checks][key][:status], "Expected #{key} to be skipped, got #{result[:checks][key].inspect}"
|
||||
end
|
||||
%i[plugin_json required_fields name_matches_directory skill_md_present skills_array_matches].each do |key|
|
||||
assert_equal 'pass', result[:checks][key][:status]
|
||||
end
|
||||
end
|
||||
|
||||
def test_explicit_missing_marketplace_file_still_fails
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
nonexistent = File.join(@tmpdir, 'does-not-exist.json')
|
||||
|
||||
result = PluginConfig::Operations::Validate.new(
|
||||
plugin_path: @plugin_dir, marketplace_file: nonexistent
|
||||
).call
|
||||
|
||||
refute result[:valid]
|
||||
assert_equal 'fail', result[:checks][:marketplace_entry][:status]
|
||||
end
|
||||
|
||||
def test_default_marketplace_resolution_used_when_present
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
default_dir = File.join(@tmpdir, '.claude-plugin')
|
||||
FileUtils.mkdir_p(default_dir)
|
||||
File.write(File.join(default_dir, 'marketplace.json'), JSON.pretty_generate(
|
||||
'name' => 'test-marketplace',
|
||||
'plugins' => [
|
||||
{ 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
|
||||
]
|
||||
))
|
||||
|
||||
result = PluginConfig::Operations::Validate.new(plugin_path: @plugin_dir).call
|
||||
|
||||
assert_equal 'ok', result[:status]
|
||||
assert result[:valid]
|
||||
result[:checks].each_value { |check| assert_equal 'pass', check[:status] }
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Entry point (real exit code contract) ───────────────────────────────────
|
||||
|
||||
class TestValidateEntryPoint < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
SCRIPT_PATH = File.expand_path('../plugin_config.rb', __dir__)
|
||||
|
||||
def setup
|
||||
setup_temp_dirs
|
||||
@plugin_dir = File.join(@tmpdir, 'my-plugin')
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_temp_dirs
|
||||
end
|
||||
|
||||
def run_script(*args)
|
||||
Open3.capture3('ruby', SCRIPT_PATH, *args)
|
||||
end
|
||||
|
||||
def test_valid_plugin_exits_0
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
create_marketplace(plugins: [
|
||||
{ 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
|
||||
])
|
||||
|
||||
stdout, stderr, status = run_script(
|
||||
'validate',
|
||||
"--plugin_path=#{@plugin_dir}",
|
||||
"--marketplace_file=#{marketplace_path}"
|
||||
)
|
||||
|
||||
assert status.success?, "Expected exit 0, got #{status.exitstatus}. stderr: #{stderr}"
|
||||
parsed = JSON.parse(stdout)
|
||||
assert_equal 'ok', parsed['status']
|
||||
end
|
||||
|
||||
def test_invalid_plugin_exits_1
|
||||
create_marketplace(plugins: []) # no plugin.json created at all
|
||||
|
||||
stdout, _stderr, status = run_script(
|
||||
'validate',
|
||||
"--plugin_path=#{@plugin_dir}",
|
||||
"--marketplace_file=#{marketplace_path}"
|
||||
)
|
||||
|
||||
assert_equal 1, status.exitstatus
|
||||
parsed = JSON.parse(stdout)
|
||||
assert_equal 'issues_found', parsed['status']
|
||||
end
|
||||
|
||||
def test_validate_is_cwd_independent_with_absolute_plugin_path_and_default_marketplace
|
||||
create_plugin_json(@plugin_dir, name: 'my-plugin', description: 'Does a thing')
|
||||
create_skill(@plugin_dir, 'my-skill')
|
||||
default_dir = File.join(@tmpdir, '.claude-plugin')
|
||||
FileUtils.mkdir_p(default_dir)
|
||||
File.write(File.join(default_dir, 'marketplace.json'), JSON.pretty_generate(
|
||||
'name' => 'test-marketplace',
|
||||
'plugins' => [
|
||||
{ 'name' => 'my-plugin', 'source' => './my-plugin', 'description' => 'Does a thing' }
|
||||
]
|
||||
))
|
||||
|
||||
other_cwd = Dir.mktmpdir('elsewhere')
|
||||
begin
|
||||
stdout, stderr, status = Dir.chdir(other_cwd) do
|
||||
run_script('validate', "--plugin_path=#{File.expand_path(@plugin_dir)}")
|
||||
end
|
||||
|
||||
assert status.success?, "Expected exit 0, got #{status.exitstatus}. stderr: #{stderr}"
|
||||
parsed = JSON.parse(stdout)
|
||||
assert_equal 'ok', parsed['status']
|
||||
assert parsed['valid']
|
||||
ensure
|
||||
FileUtils.rm_rf(other_cwd)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'test_helper'
|
||||
require 'open3'
|
||||
require 'tmpdir'
|
||||
require 'fileutils'
|
||||
|
||||
class TestValidateMarketplaces < Minitest::Test
|
||||
include TestHelpers
|
||||
|
||||
SCRIPT_PATH = File.expand_path('../../validate_marketplaces.rb', __dir__)
|
||||
|
||||
def setup
|
||||
@tmpdir = Dir.mktmpdir('validate_marketplaces_test')
|
||||
end
|
||||
|
||||
def teardown
|
||||
FileUtils.rm_rf(@tmpdir) if @tmpdir && Dir.exist?(@tmpdir)
|
||||
end
|
||||
|
||||
# Builds <root>/.claude-plugin/marketplace.json plus one passing plugin
|
||||
# (good-plugin) and one failing plugin (bad-plugin, missing plugin.json).
|
||||
def build_marketplace_root(root)
|
||||
FileUtils.mkdir_p(File.join(root, '.claude-plugin'))
|
||||
|
||||
good_dir = File.join(root, 'good-plugin')
|
||||
create_plugin_json(good_dir, name: 'good-plugin', description: 'A good plugin')
|
||||
FileUtils.mkdir_p(File.join(good_dir, 'skills'))
|
||||
|
||||
bad_dir = File.join(root, 'bad-plugin')
|
||||
FileUtils.mkdir_p(bad_dir) # no plugin.json at all -> fails check_plugin_json
|
||||
|
||||
marketplace_data = {
|
||||
'name' => File.basename(root),
|
||||
'plugins' => [
|
||||
{ 'name' => 'good-plugin', 'source' => './good-plugin', 'description' => 'A good plugin' },
|
||||
{ 'name' => 'bad-plugin', 'source' => './bad-plugin', 'description' => 'A bad plugin' }
|
||||
]
|
||||
}
|
||||
File.write(File.join(root, '.claude-plugin', 'marketplace.json'), JSON.pretty_generate(marketplace_data))
|
||||
end
|
||||
|
||||
def build_all_passing_root(root)
|
||||
FileUtils.mkdir_p(File.join(root, '.claude-plugin'))
|
||||
|
||||
good_dir = File.join(root, 'good-plugin')
|
||||
create_plugin_json(good_dir, name: 'good-plugin', description: 'A good plugin')
|
||||
FileUtils.mkdir_p(File.join(good_dir, 'skills'))
|
||||
|
||||
marketplace_data = {
|
||||
'name' => File.basename(root),
|
||||
'plugins' => [
|
||||
{ 'name' => 'good-plugin', 'source' => './good-plugin', 'description' => 'A good plugin' }
|
||||
]
|
||||
}
|
||||
File.write(File.join(root, '.claude-plugin', 'marketplace.json'), JSON.pretty_generate(marketplace_data))
|
||||
end
|
||||
|
||||
def run_script(*args)
|
||||
Open3.capture3('ruby', SCRIPT_PATH, *args)
|
||||
end
|
||||
|
||||
def test_no_args_prints_usage_and_exits_2
|
||||
stdout, _stderr, status = run_script
|
||||
|
||||
assert_equal 2, status.exitstatus
|
||||
assert_match(/Usage: ruby validate_marketplaces\.rb/, stdout)
|
||||
assert_match(%r{ruby .*validate_marketplaces\.rb .*/home/jared/dev/cc-plugins .*/home/jared/dev/cc-os}, stdout)
|
||||
end
|
||||
|
||||
def test_all_passing_marketplace_exits_0
|
||||
root = File.join(@tmpdir, 'passing-root')
|
||||
build_all_passing_root(root)
|
||||
|
||||
stdout, stderr, status = run_script(root)
|
||||
|
||||
assert status.success?, "Expected exit 0, got #{status.exitstatus}. stdout: #{stdout} stderr: #{stderr}"
|
||||
assert_match(/PASS\s+good-plugin/, stdout)
|
||||
assert_match(/TOTAL: 1 plugin\(s\) checked, 1 passed, 0 failed/, stdout)
|
||||
end
|
||||
|
||||
def test_mixed_marketplace_reports_pass_and_fail_and_exits_1
|
||||
root = File.join(@tmpdir, 'mixed-root')
|
||||
build_marketplace_root(root)
|
||||
|
||||
stdout, _stderr, status = run_script(root)
|
||||
|
||||
assert_equal 1, status.exitstatus
|
||||
assert_match(/PASS\s+good-plugin/, stdout)
|
||||
assert_match(/FAIL\s+bad-plugin/, stdout)
|
||||
assert_match(/TOTAL: 2 plugin\(s\) checked, 1 passed, 1 failed/, stdout)
|
||||
end
|
||||
|
||||
def test_missing_marketplace_file_exits_2
|
||||
root = File.join(@tmpdir, 'empty-root')
|
||||
FileUtils.mkdir_p(root)
|
||||
|
||||
stdout, _stderr, status = run_script(root)
|
||||
|
||||
assert_equal 2, status.exitstatus
|
||||
assert_match(/ERROR: marketplace\.json not found/, stdout)
|
||||
end
|
||||
|
||||
def test_mixed_marketplace_output_collapses_to_one_line_per_failure
|
||||
root = File.join(@tmpdir, 'mixed-root-collapse')
|
||||
build_marketplace_root(root)
|
||||
|
||||
stdout, _stderr, _status = run_script(root)
|
||||
|
||||
# bad-plugin fails multiple checks (missing plugin.json cascades into
|
||||
# several "Cannot check..." failures); only the first issue should
|
||||
# appear, on the same line as the FAIL marker, not as a multi-line dump.
|
||||
fail_lines = stdout.lines.select { |l| l =~ /FAIL\s+bad-plugin/ }
|
||||
assert_equal 1, fail_lines.size
|
||||
assert_match(/FAIL\s+bad-plugin - plugin\.json not found/, fail_lines.first)
|
||||
refute_match(/Cannot check required fields/, stdout)
|
||||
end
|
||||
|
||||
def test_multiple_roots_one_failing_reports_both_and_exits_1
|
||||
root1 = File.join(@tmpdir, 'root1-pass')
|
||||
root2 = File.join(@tmpdir, 'root2-mixed')
|
||||
build_all_passing_root(root1)
|
||||
build_marketplace_root(root2)
|
||||
|
||||
stdout, _stderr, status = run_script(root1, root2)
|
||||
|
||||
assert_equal 1, status.exitstatus
|
||||
assert_match(/== #{Regexp.escape(File.expand_path(root1))} ==/, stdout)
|
||||
assert_match(/== #{Regexp.escape(File.expand_path(root2))} ==/, stdout)
|
||||
assert_match(/PASS\s+good-plugin/, stdout)
|
||||
assert_match(/FAIL\s+bad-plugin/, stdout)
|
||||
assert_match(/TOTAL: 3 plugin\(s\) checked, 2 passed, 1 failed/, stdout)
|
||||
end
|
||||
|
||||
def test_multiple_roots_are_all_reported
|
||||
root1 = File.join(@tmpdir, 'root1')
|
||||
root2 = File.join(@tmpdir, 'root2')
|
||||
build_all_passing_root(root1)
|
||||
build_all_passing_root(root2)
|
||||
|
||||
stdout, _stderr, status = run_script(root1, root2)
|
||||
|
||||
assert status.success?
|
||||
assert_match(/== #{Regexp.escape(File.expand_path(root1))} ==/, stdout)
|
||||
assert_match(/== #{Regexp.escape(File.expand_path(root2))} ==/, stdout)
|
||||
assert_match(/TOTAL: 2 plugin\(s\) checked, 2 passed, 0 failed/, stdout)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Cross-repo marketplace acceptance script.
|
||||
#
|
||||
# For one or more marketplace roots (repos containing a
|
||||
# .claude-plugin/marketplace.json), runs the plugin_config validate
|
||||
# operation against every plugin the marketplace declares and reports a
|
||||
# pass/fail table plus totals.
|
||||
#
|
||||
# Usage:
|
||||
# ruby validate_marketplaces.rb <marketplace-root>...
|
||||
#
|
||||
# With no arguments, prints usage (including the documented default
|
||||
# invocation for this machine) and exits 2.
|
||||
#
|
||||
# Exit status:
|
||||
# 0 every plugin in every given marketplace passed validation
|
||||
# 1 at least one plugin failed validation
|
||||
# 2 usage error (no args, missing/invalid marketplace.json, etc.)
|
||||
|
||||
require 'json'
|
||||
require 'open3'
|
||||
|
||||
SCRIPT_DIR = File.expand_path(__dir__)
|
||||
PLUGIN_CONFIG_RB = File.join(SCRIPT_DIR, 'plugin_config', 'plugin_config.rb')
|
||||
|
||||
DEFAULT_MARKETPLACE_ROOTS = [
|
||||
'/home/jared/dev/cc-plugins',
|
||||
'/home/jared/dev/cc-os'
|
||||
].freeze
|
||||
|
||||
def usage
|
||||
<<~USAGE
|
||||
Usage: ruby #{File.basename(__FILE__)} <marketplace-root>...
|
||||
|
||||
Each <marketplace-root> is a repo root containing
|
||||
.claude-plugin/marketplace.json. Every plugin declared in that
|
||||
marketplace is validated with the plugin_config `validate` operation,
|
||||
against that marketplace's manifest.
|
||||
|
||||
Documented default invocation for this machine:
|
||||
ruby #{__FILE__} #{DEFAULT_MARKETPLACE_ROOTS.join(' ')}
|
||||
USAGE
|
||||
end
|
||||
|
||||
# Runs `plugin_config.rb validate` for a single plugin and returns the
|
||||
# parsed JSON result (or a synthetic failure result if the subprocess
|
||||
# couldn't be run or produced unparseable output).
|
||||
def validate_plugin(plugin_path, marketplace_file)
|
||||
cmd = [
|
||||
'ruby', PLUGIN_CONFIG_RB, 'validate',
|
||||
"--plugin_path=#{plugin_path}",
|
||||
"--marketplace_file=#{marketplace_file}"
|
||||
]
|
||||
stdout, stderr, _status = Open3.capture3(*cmd)
|
||||
|
||||
begin
|
||||
JSON.parse(stdout, symbolize_names: true)
|
||||
rescue JSON::ParserError
|
||||
{
|
||||
status: 'issues_found',
|
||||
valid: false,
|
||||
plugin_path: plugin_path,
|
||||
checks: {},
|
||||
issues: ["validator produced unparseable output: #{stdout.strip}#{stderr.strip}"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Validates every plugin declared by one marketplace root. Returns
|
||||
# { root:, plugin_results: [{name:, plugin_path:, valid:, issues: []}], error: nil|String }
|
||||
def validate_marketplace(root)
|
||||
abs_root = File.expand_path(root)
|
||||
marketplace_file = File.join(abs_root, '.claude-plugin', 'marketplace.json')
|
||||
|
||||
unless File.exist?(marketplace_file)
|
||||
return { root: abs_root, marketplace_file: marketplace_file, plugin_results: [], error: "marketplace.json not found at #{marketplace_file}" }
|
||||
end
|
||||
|
||||
data = begin
|
||||
JSON.parse(File.read(marketplace_file))
|
||||
rescue JSON::ParserError => e
|
||||
return { root: abs_root, marketplace_file: marketplace_file, plugin_results: [], error: "invalid JSON in #{marketplace_file}: #{e.message}" }
|
||||
end
|
||||
|
||||
plugins = data['plugins'] || []
|
||||
if plugins.empty?
|
||||
return { root: abs_root, marketplace_file: marketplace_file, plugin_results: [], error: "no plugins declared in #{marketplace_file}" }
|
||||
end
|
||||
|
||||
plugin_results = plugins.map do |entry|
|
||||
name = entry['name']
|
||||
source = entry['source']
|
||||
plugin_path = source ? File.expand_path(source, abs_root) : nil
|
||||
|
||||
if plugin_path.nil?
|
||||
{ name: name || '(unnamed)', plugin_path: nil, valid: false, issues: ['marketplace entry has no source field'] }
|
||||
else
|
||||
result = validate_plugin(plugin_path, marketplace_file)
|
||||
{
|
||||
name: name || '(unnamed)',
|
||||
plugin_path: plugin_path,
|
||||
valid: result[:valid] == true,
|
||||
issues: Array(result[:issues])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
{ root: abs_root, marketplace_file: marketplace_file, plugin_results: plugin_results, error: nil }
|
||||
end
|
||||
|
||||
def print_report(marketplace_reports)
|
||||
total_plugins = 0
|
||||
total_failed = 0
|
||||
|
||||
marketplace_reports.each do |report|
|
||||
puts "== #{report[:root]} =="
|
||||
|
||||
if report[:error]
|
||||
puts " ERROR: #{report[:error]}"
|
||||
puts
|
||||
next
|
||||
end
|
||||
|
||||
report[:plugin_results].each do |pr|
|
||||
total_plugins += 1
|
||||
if pr[:valid]
|
||||
puts " PASS #{pr[:name]}"
|
||||
else
|
||||
total_failed += 1
|
||||
first_issue = pr[:issues].first
|
||||
summary = first_issue ? first_issue.to_s.lines.first.to_s.strip : '(no issue detail)'
|
||||
puts " FAIL #{pr[:name]} - #{summary}"
|
||||
end
|
||||
end
|
||||
puts
|
||||
end
|
||||
|
||||
[total_plugins, total_failed]
|
||||
end
|
||||
|
||||
def main(argv)
|
||||
if argv.empty?
|
||||
puts usage
|
||||
exit 2
|
||||
end
|
||||
|
||||
marketplace_reports = argv.map { |root| validate_marketplace(root) }
|
||||
|
||||
total_plugins, total_failed = print_report(marketplace_reports)
|
||||
|
||||
hard_error = marketplace_reports.any? { |r| r[:error] }
|
||||
|
||||
puts "TOTAL: #{total_plugins} plugin(s) checked, #{total_plugins - total_failed} passed, #{total_failed} failed"
|
||||
|
||||
if hard_error
|
||||
exit 2
|
||||
elsif total_failed.positive?
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
end
|
||||
end
|
||||
|
||||
main(ARGV) if __FILE__ == $PROGRAM_NAME
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
name: cc-architect
|
||||
description: Model-tier-optimized execution plans, scaffolding, and configuration operations for building and managing Claude Code extensions (plugins, skills, workflows)
|
||||
---
|
||||
|
||||
# CC Architect
|
||||
|
||||
Model-tier-optimized execution plans, scaffolding, and configuration operations for building and managing Claude Code extensions (plugins, skills, workflows).
|
||||
|
||||
## Trigger
|
||||
|
||||
Use this skill when creating, auditing, or improving any Claude Code extension component, or when managing plugin configuration (enable, disable, register, unregister, audit config, list plugins).
|
||||
|
||||
## Dispatch
|
||||
|
||||
Before building an execution plan, check if the request matches a fast-path operation. Fast-path operations bypass `${CLAUDE_PLUGIN_ROOT}/workflows/build-plan.md` and route directly to the appropriate workflow.
|
||||
|
||||
**Pattern match the user's request against these keywords:**
|
||||
|
||||
| Keywords | Operation | Route |
|
||||
|----------|-----------|-------|
|
||||
| enable plugin, turn on plugin, activate plugin | `enable` | `workflows/plugin-config.md` |
|
||||
| disable plugin, turn off plugin, deactivate plugin | `disable` | `workflows/plugin-config.md` |
|
||||
| register plugin, add to marketplace, add plugin to marketplace | `register` | `workflows/plugin-config.md` |
|
||||
| unregister plugin, remove from marketplace, remove plugin from marketplace | `unregister` | `workflows/plugin-config.md` |
|
||||
| audit config, check config, plugin config status, debug plugin, why isn't plugin working | `audit` | `workflows/plugin-config.md` |
|
||||
| list plugins, show plugins, installed plugins, what plugins | `list` | `workflows/plugin-config.md` |
|
||||
|
||||
**If a fast-path match is found:**
|
||||
1. Extract the operation and parameters from the user's request.
|
||||
2. Dispatch a Haiku subagent with: "Read workflow at `${CLAUDE_PLUGIN_ROOT}/workflows/plugin-config.md` and execute with operation=`<op>`, plugin_name=`<name>`, and other relevant parameters."
|
||||
3. Skip Step 1 (Build Execution Plan) and proceed directly to Step 2 execution with the single workflow step.
|
||||
|
||||
**If no fast-path match:** Continue to Step 1 (Build Execution Plan) as normal.
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 1: Build Execution Plan
|
||||
|
||||
Dispatch a Sonnet subagent:
|
||||
- Read: `workflows/catalog.md`
|
||||
- Input: User's request
|
||||
- Task: Select workflows and build structured execution plan with model tiers
|
||||
- Write plan to: `.claude/plugin-data/cc-architect/plans/current-plan.md`
|
||||
|
||||
### Step 2: Execute Plan
|
||||
|
||||
For each step in the execution plan:
|
||||
1. Read the step metadata from plan file (model tier, workflow path, input, output)
|
||||
2. Dispatch subagent using Task tool:
|
||||
- Use `subagent_type="general-purpose"`
|
||||
- Pass instructions: "Read workflow at {workflow_path} and execute with inputs: {input_context}. Write results to {output_path}"
|
||||
- The SUBAGENT reads the workflow document and executes it (NOT you)
|
||||
3. Wait for subagent completion
|
||||
4. Verify output file exists at specified location (do NOT read contents)
|
||||
5. Continue to next step
|
||||
|
||||
Example dispatch for a scaffolding step:
|
||||
```
|
||||
Plan metadata: Model=haiku, Workflow=workflows/create-plugin.md, Output=.claude/plugin-data/cc-architect/tasks/step-1-scaffold.md
|
||||
Dispatch: Task tool with instructions="Read workflow at ${CLAUDE_PLUGIN_ROOT}/workflows/create-plugin.md and execute. Write results to .claude/plugin-data/cc-architect/tasks/step-1-scaffold.md"
|
||||
```
|
||||
|
||||
ANTI-PATTERNS - DO NOT:
|
||||
- Read workflow documents yourself
|
||||
- Interpret workflow logic in main thread
|
||||
- Assemble context before dispatching
|
||||
- Read subagent output files (except final synthesis in Step 3)
|
||||
|
||||
ONLY read: plan file, final output. ONLY verify: intermediate files exist. ONLY pass: file paths to subagents.
|
||||
|
||||
### Step 3: Present Results
|
||||
|
||||
Read the final output file specified in the plan.
|
||||
Present to user.
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
# CC-Architect Audit Checklist
|
||||
|
||||
**Model**: Haiku (mechanical verification, no judgment)
|
||||
|
||||
## Instructions
|
||||
|
||||
Run this checklist against any cc-architect output (plugin, skill, workflow). Check each item and mark pass/fail. If any CRITICAL items fail, escalate to Sonnet for severity analysis.
|
||||
|
||||
---
|
||||
|
||||
## Universal Attributes (All Outputs)
|
||||
|
||||
### Structure
|
||||
|
||||
| # | Check | Command/Method | Pass | Fail |
|
||||
|---|-------|----------------|------|------|
|
||||
| 1 | Directory exists | `ls -d {target_path}` | ☐ | ☐ |
|
||||
| 2 | Required files present | See per-type checklist below | ☐ | ☐ |
|
||||
| 3 | No empty files | `find {path} -empty -type f` returns nothing | ☐ | ☐ |
|
||||
| 4 | Naming convention | All files/dirs are `kebab-case` | ☐ | ☐ |
|
||||
|
||||
### Frontmatter
|
||||
|
||||
| # | Check | Validation | Pass | Fail |
|
||||
|---|-------|------------|------|------|
|
||||
| 5 | Description present | `description:` field in frontmatter | ☐ | ☐ |
|
||||
| 6 | Description passes escape hatch | Would user skip reading full file? | ☐ | ☐ |
|
||||
| 7 | No placeholder text | No `TODO`, `FIXME`, `[PLACEHOLDER]` | ☐ | ☐ |
|
||||
|
||||
### Cross-References
|
||||
|
||||
| # | Check | Validation | Pass | Fail |
|
||||
|---|-------|------------|------|------|
|
||||
| 8 | All paths resolve | Every `references/` path exists | ☐ | ☐ |
|
||||
| 9 | No broken links | Every `[text](path)` link resolves | ☐ | ☐ |
|
||||
|
||||
### Model Annotations
|
||||
|
||||
| # | Check | Validation | Pass | Fail |
|
||||
|---|-------|------------|------|------|
|
||||
| 10 | Tasks have model tier | Each task annotated with Haiku/Sonnet/Opus | ☐ | ☐ |
|
||||
| 11 | Pre-planning present | Orchestrator-invoked skills have pre-planning step | ☐ | ☐ |
|
||||
|
||||
---
|
||||
|
||||
## Plugin-Specific Attributes
|
||||
|
||||
### Required Files
|
||||
- [ ] `CLAUDE.md` exists
|
||||
- [ ] `.claude-plugin/plugin.json` exists and valid JSON
|
||||
- [ ] At least one skill in `skills/` OR workflow in `workflows/`
|
||||
|
||||
### Plugin Manifest (plugin.json)
|
||||
- [ ] `name` field present
|
||||
- [ ] `description` field present
|
||||
- [ ] `skills` array lists all skills in `skills/` directory
|
||||
|
||||
---
|
||||
|
||||
## Skill-Specific Attributes
|
||||
|
||||
### Required Files
|
||||
- [ ] `SKILL.md` exists
|
||||
- [ ] `invariants.md` exists (may be empty if no invariants yet)
|
||||
- [ ] `examples/golden/` directory exists
|
||||
|
||||
### SKILL.md Content
|
||||
- [ ] Has frontmatter with `description`
|
||||
- [ ] Has "When to use" section
|
||||
- [ ] Has phase structure (Setup → Process → Synthesize or equivalent)
|
||||
- [ ] References to shared docs use relative paths
|
||||
|
||||
### Invariants
|
||||
- [ ] At least 1 invariant defined OR explicit "no invariants yet" note
|
||||
- [ ] Invariants are testable (not vague)
|
||||
|
||||
---
|
||||
|
||||
## Workflow-Specific Attributes
|
||||
|
||||
### Required Frontmatter Fields
|
||||
```yaml
|
||||
---
|
||||
name: required
|
||||
description: required
|
||||
triggers: required (array)
|
||||
model_tier: required (haiku|sonnet|opus)
|
||||
complexity: optional (1-5)
|
||||
requires_user_input: optional (true|false)
|
||||
depends_on: optional (array of workflow names)
|
||||
---
|
||||
```
|
||||
|
||||
### Workflow Content
|
||||
- [ ] "When to use" section present
|
||||
- [ ] "Inputs required" section present
|
||||
- [ ] "Phases" or "Steps" section present
|
||||
- [ ] Each phase has model tier annotation
|
||||
|
||||
---
|
||||
|
||||
## Domain Plugin Attributes (Specialized Fields)
|
||||
|
||||
Only applies to domain-specialist plugins (e.g. plugins that encode a professional discipline's frameworks and heuristics, not general-purpose tooling).
|
||||
|
||||
- [ ] Domain expertise documented in references/
|
||||
- [ ] Professional frameworks captured
|
||||
- [ ] Heuristics are specific, not generic
|
||||
- [ ] Workflow categories identified (if applicable)
|
||||
|
||||
---
|
||||
|
||||
## Escalation Criteria
|
||||
|
||||
**Escalate to Sonnet if**:
|
||||
- More than 2 FAIL items in Universal Attributes
|
||||
- Any CRITICAL item fails (marked below)
|
||||
- Subjective judgment needed (e.g., "is description good enough?")
|
||||
|
||||
**CRITICAL Items** (must pass):
|
||||
- #2 (Required files present)
|
||||
- #5 (Description present)
|
||||
- #10 (Tasks have model tier)
|
||||
|
||||
---
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
## Audit Report: {target_name}
|
||||
|
||||
**Date**: {date}
|
||||
**Auditor**: Haiku
|
||||
**Target**: {path}
|
||||
|
||||
### Summary
|
||||
- Universal: {X}/11 passed
|
||||
- Type-specific: {Y}/{Z} passed
|
||||
- **Status**: PASS | FAIL | ESCALATE
|
||||
|
||||
### Failed Items
|
||||
1. #{number}: {description}
|
||||
- Expected: {what should exist}
|
||||
- Actual: {what was found}
|
||||
|
||||
### Recommendations
|
||||
- {If ESCALATE, note what Sonnet should review}
|
||||
```
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
description: "View or change {{plugin_name}} configuration"
|
||||
---
|
||||
|
||||
Invoke the {{plugin_name}} config workflow.
|
||||
|
||||
Read and follow: `workflows/config.md`
|
||||
|
||||
Parse the user's request into `operation` (`get`/`set`/`list`), `key`, and
|
||||
`value` (for `set`) before following the workflow.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue