@friedbotstudio/create-baseline 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +7 -3
  2. package/obj/template/.claude/commands/grant-push.md +19 -0
  3. package/obj/template/.claude/commands/init-project.md +26 -4
  4. package/obj/template/.claude/hooks/consent_gate_grant.mjs +107 -0
  5. package/obj/template/.claude/hooks/git_commit_guard.mjs +224 -0
  6. package/obj/template/.claude/hooks/harness_continuation.sh +101 -34
  7. package/obj/template/.claude/hooks/lib/common.mjs +283 -0
  8. package/obj/template/.claude/hooks/lib/common.sh +1 -1
  9. package/obj/template/.claude/hooks/memory_session_start.sh +20 -6
  10. package/obj/template/.claude/hooks/memory_stop.sh +161 -2
  11. package/obj/template/.claude/hooks/spec_approval_guard.sh +1 -1
  12. package/obj/template/.claude/hooks/swarm_approval_guard.sh +1 -1
  13. package/obj/template/.claude/hooks/tests/fixtures/ac008_byte_equal_reference.txt +7 -7
  14. package/obj/template/.claude/hooks/tests/fixtures/memory_stop_landmark_baseline.txt +21 -0
  15. package/obj/template/.claude/hooks/tests/fixtures/regenerate-ac008.sh +47 -0
  16. package/obj/template/.claude/hooks/tests/memory_session_start_test.sh +7 -3
  17. package/obj/template/.claude/hooks/tests/memory_stop_intent_test.sh +329 -0
  18. package/obj/template/.claude/hooks/tests/regenerate_ac008_test.sh +99 -0
  19. package/obj/template/.claude/memory/README.md +8 -3
  20. package/obj/template/.claude/memory/backlog.md +12 -0
  21. package/obj/template/.claude/project.json +6 -1
  22. package/obj/template/.claude/settings.json +3 -4
  23. package/obj/template/.claude/skills/audit-baseline/audit.sh +28 -16
  24. package/obj/template/.claude/skills/audit-baseline/tests/fixtures/_pending_opener_only.md +3 -0
  25. package/obj/template/.claude/skills/audit-baseline/tests/fixtures/preamble_full_empty_body.md +4 -0
  26. package/obj/template/.claude/skills/audit-baseline/tests/fixtures/preamble_full_with_entries.md +9 -0
  27. package/obj/template/.claude/skills/audit-baseline/tests/fixtures/preamble_no_opener.md +3 -0
  28. package/obj/template/.claude/skills/audit-baseline/tests/fixtures/preamble_opener_only.md +3 -0
  29. package/obj/template/.claude/skills/audit-baseline/tests/preamble_check_test.sh +147 -0
  30. package/obj/template/.claude/skills/chore/SKILL.md +5 -3
  31. package/obj/template/.claude/skills/commit/SKILL.md +5 -4
  32. package/obj/template/.claude/skills/copywriting/LICENSE +21 -0
  33. package/obj/template/.claude/skills/copywriting/NOTICE +23 -0
  34. package/obj/template/.claude/skills/copywriting/SKILL.md +1 -1
  35. package/obj/template/.claude/skills/design-ui/SKILL.md +23 -5
  36. package/obj/template/.claude/skills/design-ui/references/design-vs-development.md +26 -5
  37. package/obj/template/.claude/skills/design-ui/references/orchestration.md +1 -0
  38. package/obj/template/.claude/skills/design-ui/references/state-machine.md +5 -3
  39. package/obj/template/.claude/skills/documentation/LICENSE +202 -0
  40. package/obj/template/.claude/skills/documentation/NOTICE +22 -0
  41. package/obj/template/.claude/skills/harness/SKILL.md +3 -1
  42. package/obj/template/.claude/skills/humanizer/LICENSE +21 -0
  43. package/obj/template/.claude/skills/humanizer/NOTICE +21 -0
  44. package/obj/template/.claude/skills/impeccable/LICENSE +202 -0
  45. package/obj/template/.claude/skills/impeccable/NOTICE +24 -0
  46. package/obj/template/.claude/skills/memory-flush/SKILL.md +20 -4
  47. package/obj/template/.claude/skills/memory-flush/sweep.py +74 -6
  48. package/obj/template/.claude/skills/memory-flush/tests/run.sh +300 -1
  49. package/obj/template/.claude/skills/tdd/SKILL.md +2 -1
  50. package/obj/template/.claude/skills/tdd/drift_check.py +180 -0
  51. package/obj/template/.claude/skills/tdd/tests/drift_check_test.sh +190 -0
  52. package/obj/template/.claude/skills/tdd/tests/run.sh +21 -0
  53. package/obj/template/.claude/skills/technical-tutorials/LICENSE +21 -0
  54. package/obj/template/.claude/skills/technical-tutorials/NOTICE +23 -0
  55. package/obj/template/.claude/skills/technical-tutorials/SKILL.md +1 -1
  56. package/obj/template/.claude/skills/triage/SKILL.md +8 -3
  57. package/obj/template/CLAUDE.md +34 -23
  58. package/obj/template/docs/init/seed.md +36 -21
  59. package/obj/template/manifest.json +59 -33
  60. package/package.json +1 -1
  61. package/src/CLAUDE.template.md +34 -23
  62. package/src/memory/backlog.template.md +12 -0
  63. package/src/project.template.json +6 -1
  64. package/src/seed.template.md +36 -21
  65. package/src/settings.template.json +3 -4
  66. package/obj/template/.claude/hooks/consent_gate_grant.sh +0 -89
  67. package/obj/template/.claude/hooks/git_commit_guard.sh +0 -93
@@ -19,6 +19,14 @@ results = [] # (name, status, detail)
19
19
  def add(name, status, detail=""):
20
20
  results.append((name, status, detail))
21
21
 
22
+ def is_valid_preamble(text):
23
+ if not text.startswith("---"):
24
+ return False, "missing frontmatter"
25
+ remainder = text[3:]
26
+ if "\n---\n" in remainder or remainder.endswith("\n---"):
27
+ return True, ""
28
+ return False, "malformed frontmatter: missing closing separator"
29
+
22
30
  # ---------- expected canonical sets (mirror seed.md §4) ----------
23
31
  EXPECTED_HOOKS = {
24
32
  # Write/Bash boundary guards (17)
@@ -65,12 +73,12 @@ def read_skill_owner(slug):
65
73
  m = re.search(r'^owner:\s*(\S+)\s*$', fm.group(1), re.MULTILINE)
66
74
  return m.group(1) if m else None
67
75
 
68
- EXPECTED_COMMANDS = {"approve-spec", "approve-swarm", "grant-commit", "init-project"}
76
+ EXPECTED_COMMANDS = {"approve-spec", "approve-swarm", "grant-commit", "grant-push", "init-project"}
69
77
 
70
78
  EXPECTED_MEMORY_FILES = {
71
- # Canonical files (six)
79
+ # Canonical files (seven)
72
80
  "landmarks", "libraries", "decisions", "landmines", "conventions",
73
- "pending-questions",
81
+ "pending-questions", "backlog",
74
82
  # Auto-extraction inbox (one); body gitignored, file committed
75
83
  "_pending",
76
84
  # Cross-session continuity snapshot (one); written by memory_stop &
@@ -128,7 +136,7 @@ agents_dir = root / ".claude/agents"
128
136
  skills_dir = root / ".claude/skills"
129
137
  cmds_dir = root / ".claude/commands"
130
138
 
131
- disk_hooks = {p.stem for p in hooks_dir.glob("*.sh")} if hooks_dir.exists() else set()
139
+ disk_hooks = ({p.stem for p in hooks_dir.glob("*.sh")} | {p.stem for p in hooks_dir.glob("*.mjs")}) if hooks_dir.exists() else set()
132
140
  disk_agents = {p.stem for p in agents_dir.glob("*.md")} if agents_dir.exists() else set()
133
141
  disk_skills = {p.name for p in skills_dir.iterdir() if p.is_dir()} if skills_dir.exists() else set()
134
142
  disk_commands = {p.stem for p in cmds_dir.glob("*.md")} if cmds_dir.exists() else set()
@@ -171,7 +179,7 @@ skills_claimed = find_count(
171
179
  r"\b(\d+|twenty-(?:four|five|six|seven|eight|nine)|"
172
180
  r"thirty|thirty-(?:one|two|three|four|five|six|seven|eight|nine)|forty)\s+skills?\b")
173
181
  gates_claimed = find_count(r"\b(\d+|three)\s+consent\s+gates?\b")
174
- cmds_claimed = 4 if re.search(r"three\s+consent\s+gates?\s*\+\s*one\s+bootstrap", seed, re.IGNORECASE) else None
182
+ cmds_claimed = 5 if re.search(r"four\s+consent\s+gates?\s*\+\s*one\s+bootstrap", seed, re.IGNORECASE) else None
175
183
 
176
184
  def check_count(label, claimed, actual):
177
185
  if claimed is None:
@@ -295,28 +303,29 @@ else:
295
303
  add("memory files present", "FAIL", "; ".join(bits))
296
304
  else:
297
305
  add("memory files present", "PASS", f"{len(disk_memory)} files")
298
- # Each canonical file should have frontmatter (--- ... ---) and at least one entry.
306
+ # Record count is not an audit signal a freshly-initialized project
307
+ # legitimately has zero entries in every canonical file (overlaid from
308
+ # pristine src/memory/<name>.template.md). Entry count is reported
309
+ # informationally.
299
310
  for name in sorted(EXPECTED_MEMORY_FILES):
300
311
  p = mem_dir / f"{name}.md"
301
312
  if not p.is_file():
302
313
  continue
303
314
  text = p.read_text(encoding="utf-8", errors="replace")
304
- if not text.startswith("---"):
305
- add(f"memory shape: {name}.md", "FAIL", "missing frontmatter")
315
+ ok, reason = is_valid_preamble(text)
316
+ if not ok:
317
+ add(f"memory shape: {name}.md", "FAIL", reason)
306
318
  continue
307
- # _pending body may be empty; canonical must have at least one entry.
308
319
  if name == "_pending":
309
320
  add(f"memory shape: {name}.md", "PASS", "")
310
321
  continue
311
- body = text.split("---", 2)[-1] if text.startswith("---") else text
322
+ body = text.split("---", 2)[-1]
312
323
  # Strip fenced code blocks so example "## <stable key>" lines inside
313
324
  # ```markdown ... ``` don't count as entries.
314
325
  body_no_fence = re.sub(r"(?ms)^```.*?^```\s*$", "", body)
315
326
  entry_count = len(re.findall(r'(?m)^##\s+\S', body_no_fence))
316
- if entry_count == 0:
317
- add(f"memory shape: {name}.md", "FAIL", "no entries (## headings) in body")
318
- else:
319
- add(f"memory shape: {name}.md", "PASS", f"{entry_count} entries")
327
+ detail = f"{entry_count} entries" if entry_count > 0 else "empty (preamble-only)"
328
+ add(f"memory shape: {name}.md", "PASS", detail)
320
329
  # README inside memory/ is a structural expectation
321
330
  add("memory README", "PASS" if (mem_dir / "README.md").is_file() else "FAIL",
322
331
  "" if (mem_dir / "README.md").is_file() else "missing .claude/memory/README.md")
@@ -419,7 +428,7 @@ else:
419
428
  try:
420
429
  s_text = src_settings.read_text(encoding="utf-8")
421
430
  json.loads(s_text)
422
- missing_wired = sorted(h for h in EXPECTED_HOOKS if f"{h}.sh" not in s_text)
431
+ missing_wired = sorted(h for h in EXPECTED_HOOKS if (f"{h}.sh" not in s_text and f"{h}.mjs" not in s_text))
423
432
  if missing_wired:
424
433
  head = missing_wired[:3]
425
434
  tail = f" + {len(missing_wired) - 3} more" if len(missing_wired) > 3 else ""
@@ -502,7 +511,7 @@ else:
502
511
  except Exception as e:
503
512
  add("settings.json parses", "FAIL", str(e))
504
513
  for h in sorted(EXPECTED_HOOKS):
505
- if f"{h}.sh" in settings_text:
514
+ if f"{h}.sh" in settings_text or f"{h}.mjs" in settings_text:
506
515
  add(f"hook wired: {h}", "PASS", "")
507
516
  else:
508
517
  add(f"hook wired: {h}", "FAIL", "not in settings.json")
@@ -535,6 +544,9 @@ else:
535
544
  ("swarm.enforced_path_prefixes", ["swarm", "enforced_path_prefixes"]),
536
545
  ("consent.commit_ttl_seconds", ["consent", "commit_ttl_seconds"]),
537
546
  ("consent.gate_marker_ttl_seconds", ["consent", "gate_marker_ttl_seconds"]),
547
+ ("consent.push_ttl_seconds", ["consent", "push_ttl_seconds"]),
548
+ ("git.protected_branches", ["git", "protected_branches"]),
549
+ ("git.branch_pattern", ["git", "branch_pattern"]),
538
550
  ("additions.agents", ["additions", "agents"]),
539
551
  ("additions.skills", ["additions", "skills"]),
540
552
  ("additions.hooks", ["additions", "hooks"]),
@@ -0,0 +1,3 @@
1
+ ---
2
+ owners: [memory_stop.sh writes; /memory-flush clears]
3
+ category: auto-extracted candidates awaiting curation
@@ -0,0 +1,4 @@
1
+ ---
2
+ owners: [test]
3
+ key: full-preamble-no-entries
4
+ ---
@@ -0,0 +1,9 @@
1
+ ---
2
+ owners: [test]
3
+ key: full-preamble-one-entry
4
+ ---
5
+
6
+ ## sample-entry
7
+
8
+ - role: synthetic test fixture
9
+ - verified-at: HEAD
@@ -0,0 +1,3 @@
1
+ # Some content without frontmatter
2
+
3
+ This file has no `---` opener at all.
@@ -0,0 +1,3 @@
1
+ ---
2
+ owners: [test]
3
+ key: opener-only-no-closer
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env bash
2
+ # Fixture-based integration tests for the audit-baseline preamble validator.
3
+ # Covers the strict-preamble tightening in .claude/skills/audit-baseline/audit.sh.
4
+ #
5
+ # Each test builds a synthetic .claude/memory/ tree under a tempdir (with all
6
+ # 9 expected canonical filenames), substitutes one file with a fixture, then
7
+ # runs audit.sh with CLAUDE_PROJECT_DIR pointed at the tempdir and greps the
8
+ # captured output for the "memory shape: <name>.md" line.
9
+ #
10
+ # The audit will exit non-zero in the stub tree because hook/skill/agent
11
+ # counts won't match — that's expected. We only assert on the specific
12
+ # memory-shape line each test cares about.
13
+ #
14
+ # Not wired into project.json -> test.cmd; run manually during /tdd and
15
+ # /integrate alongside .claude/hooks/tests/memory_session_start_test.sh.
16
+
17
+ set -uo pipefail
18
+
19
+ HERE="$(cd "$(dirname "$0")" && pwd)"
20
+ REPO_ROOT="$(cd "$HERE/../../../.." && pwd)"
21
+ AUDIT="$REPO_ROOT/.claude/skills/audit-baseline/audit.sh"
22
+ FIXTURES="$HERE/fixtures"
23
+
24
+ PASS=0; FAIL=0; FAILED=()
25
+
26
+ # --- assertion helpers --------------------------------------------------------
27
+
28
+ fail() { echo " FAIL: $*"; return 1; }
29
+
30
+ # Seed a stub .claude/memory/ tree under $1. The file basename $2 is replaced
31
+ # with the fixture content from $3; the other 8 expected files get a valid
32
+ # synthetic preamble so they don't pollute the audit output we grep against.
33
+ seed_stub_tree() {
34
+ local root="$1" under_test="$2" fixture_path="$3"
35
+ mkdir -p "$root/.claude/memory"
36
+ # README is checked separately by audit.sh; copy the real one so that check
37
+ # passes and doesn't tangle our grep.
38
+ cp "$REPO_ROOT/.claude/memory/README.md" "$root/.claude/memory/README.md"
39
+ local mem_name
40
+ for mem_name in landmarks libraries decisions landmines conventions \
41
+ pending-questions backlog _pending _resume; do
42
+ if [ "$mem_name" = "$under_test" ]; then
43
+ cp "$fixture_path" "$root/.claude/memory/${mem_name}.md"
44
+ else
45
+ cat > "$root/.claude/memory/${mem_name}.md" <<'EOF'
46
+ ---
47
+ owners: [test]
48
+ key: test
49
+ ---
50
+
51
+ # Synthetic valid preamble
52
+ EOF
53
+ fi
54
+ done
55
+ }
56
+
57
+ # Run audit.sh against the stub tree at $1 and print the line matching
58
+ # "memory shape: $2.md" to stdout. Returns 1 if no such line found.
59
+ audit_memory_shape_line() {
60
+ local root="$1" name="$2"
61
+ CLAUDE_PROJECT_DIR="$root" bash "$AUDIT" 2>&1 \
62
+ | grep -E "^memory shape: ${name}\.md[[:space:]]" \
63
+ | head -1
64
+ }
65
+
66
+ # Assert that the memory-shape line for $2 in the audit run against $1 has
67
+ # status $3 and a detail matching the extended-regex $4.
68
+ assert_memory_shape() {
69
+ local root="$1" name="$2" want_status="$3" want_detail_re="$4"
70
+ local line; line="$(audit_memory_shape_line "$root" "$name")"
71
+ if [ -z "$line" ]; then
72
+ fail "no 'memory shape: ${name}.md' line in audit output"
73
+ return 1
74
+ fi
75
+ if ! printf '%s' "$line" | grep -qE "[[:space:]]${want_status}[[:space:]]"; then
76
+ fail "expected status ${want_status} for ${name}.md; got: ${line}"
77
+ return 1
78
+ fi
79
+ if ! printf '%s' "$line" | grep -qE "${want_detail_re}"; then
80
+ fail "expected detail matching '${want_detail_re}' for ${name}.md; got: ${line}"
81
+ return 1
82
+ fi
83
+ return 0
84
+ }
85
+
86
+ run() {
87
+ local name="$1"
88
+ echo "RUN $name"
89
+ if "$name"; then
90
+ PASS=$((PASS+1)); echo "PASS $name"
91
+ else
92
+ FAIL=$((FAIL+1)); FAILED+=("$name"); echo "FAIL $name"
93
+ fi
94
+ }
95
+
96
+ # --- tests --------------------------------------------------------------------
97
+
98
+ test_when_memory_file_has_opener_only_then_audit_reports_fail() {
99
+ local tmp; tmp="$(mktemp -d)"; trap "rm -rf $tmp" RETURN
100
+ seed_stub_tree "$tmp" "landmarks" "$FIXTURES/preamble_opener_only.md"
101
+ assert_memory_shape "$tmp" "landmarks" "FAIL" \
102
+ "malformed frontmatter: missing closing separator"
103
+ }
104
+
105
+ test_when_memory_file_has_no_opener_then_audit_reports_fail() {
106
+ local tmp; tmp="$(mktemp -d)"; trap "rm -rf $tmp" RETURN
107
+ seed_stub_tree "$tmp" "libraries" "$FIXTURES/preamble_no_opener.md"
108
+ assert_memory_shape "$tmp" "libraries" "FAIL" \
109
+ "missing frontmatter"
110
+ }
111
+
112
+ test_when_memory_file_has_valid_full_preamble_no_body_then_audit_reports_pass_preamble_only() {
113
+ local tmp; tmp="$(mktemp -d)"; trap "rm -rf $tmp" RETURN
114
+ seed_stub_tree "$tmp" "decisions" "$FIXTURES/preamble_full_empty_body.md"
115
+ assert_memory_shape "$tmp" "decisions" "PASS" \
116
+ "empty \\(preamble-only\\)"
117
+ }
118
+
119
+ test_when_memory_file_has_valid_preamble_with_entries_then_audit_reports_pass_with_count() {
120
+ local tmp; tmp="$(mktemp -d)"; trap "rm -rf $tmp" RETURN
121
+ seed_stub_tree "$tmp" "landmines" "$FIXTURES/preamble_full_with_entries.md"
122
+ assert_memory_shape "$tmp" "landmines" "PASS" \
123
+ "1 entries"
124
+ }
125
+
126
+ test_when_pending_file_has_opener_only_then_audit_reports_fail() {
127
+ local tmp; tmp="$(mktemp -d)"; trap "rm -rf $tmp" RETURN
128
+ seed_stub_tree "$tmp" "_pending" "$FIXTURES/_pending_opener_only.md"
129
+ assert_memory_shape "$tmp" "_pending" "FAIL" \
130
+ "malformed frontmatter: missing closing separator"
131
+ }
132
+
133
+ # --- runner -------------------------------------------------------------------
134
+
135
+ run test_when_memory_file_has_opener_only_then_audit_reports_fail
136
+ run test_when_memory_file_has_no_opener_then_audit_reports_fail
137
+ run test_when_memory_file_has_valid_full_preamble_no_body_then_audit_reports_pass_preamble_only
138
+ run test_when_memory_file_has_valid_preamble_with_entries_then_audit_reports_pass_with_count
139
+ run test_when_pending_file_has_opener_only_then_audit_reports_fail
140
+
141
+ echo "----"
142
+ echo "Passed: $PASS Failed: $FAIL"
143
+ if [ "$FAIL" -gt 0 ]; then
144
+ echo "Failed tests:"
145
+ for t in "${FAILED[@]}"; do echo " - $t"; done
146
+ fi
147
+ exit $((FAIL > 0))
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: chore
3
3
  owner: baseline
4
- description: Workflow track for tasks that need no TDD — documentation edits, governance count bumps, vendored-skill content updates, configuration tweaks, formatting, typo fixes, dependency bumps where no project code changes. Skips `/scenario` and `/implement` (no failing test to drive) and runs the work directly. `verify`, `archive`, `/grant-commit`, and `/commit` remain mandatory. `simplify`, `integrate`, and `document` are conditional — required when the diff hits one of the listed triggers, optional otherwise. Chore is a stripped-down pipeline, not a bypass; never silently skip a conditional phase whose triggers apply.
4
+ description: Workflow track for tasks that need no TDD — documentation edits, governance count bumps, vendored-skill content updates, configuration tweaks, formatting, typo fixes, dependency bumps where no project code changes. Skips `/scenario` and `/implement` (no failing test to drive) and runs the work directly. `verify`, `archive`, `memory-flush`, `/grant-commit`, and `/commit` remain mandatory. `simplify`, `integrate`, and `document` are conditional — required when the diff hits one of the listed triggers, optional otherwise. Chore is a stripped-down pipeline, not a bypass; never silently skip a conditional phase whose triggers apply.
5
5
  argument-hint: "<one-line description of the chore>"
6
6
  ---
7
7
 
@@ -44,7 +44,8 @@ The classification rule is: *if there is no failing test that should exist for t
44
44
  1. **Edit** — apply the change directly. No `/scenario`, no `/implement` — there is no failing test to drive.
45
45
  2. **`verify`** — run the project test command and stamp `.claude/state/last_test_result`. FAIL means stop, surface, and route the user to `/triage` for a proper bugfix track. The verdict is binding (the `verify_pass_guard` hook reads this file).
46
46
  3. **`archive`** — empty bundle is fine; `/commit`'s prereq requires `archive` in `completed`.
47
- 4. **`/grant-commit` then `/commit`** — user-required consent + commit. Same as every other workflow.
47
+ 4. **`memory-flush`** — Phase 10.6. Empty pending is fine (fast-path runs Step 0 sweeps and short-circuits). `/commit`'s prereq requires `memory-flush` in `completed`.
48
+ 5. **`/grant-commit` then `/commit`** — user-required consent + commit. Same as every other workflow.
48
49
 
49
50
  ### Conditional phases (required when triggers apply, optional otherwise)
50
51
 
@@ -81,7 +82,8 @@ If a conditional phase is required, run it **before** `/grant-commit`. If you sk
81
82
  - **Required** → invoke the phase skill and append it to `workflow.json → completed`.
82
83
  - **Skipped** → record the rationale in your end-of-chore summary; do not append to `completed`.
83
84
  6. Invoke `Skill(archive)` — mandatory.
84
- 7. Append `"chore"`, `"archive"`, and any conditional phases that ran to `workflow.json completed`. Update `updated_at` to the current epoch.
85
+ 6.5. Invoke `Skill(memory-flush)` mandatory (Phase 10.6). Runs Step 0 canonical sweeps and, if `_pending.md` is non-empty, full triage. On empty pending the fast-path returns success in ≤ 3 sweep.py invocations.
86
+ 7. Append `"chore"`, `"archive"`, `"memory-flush"`, and any conditional phases that ran to `workflow.json → completed`. Update `updated_at` to the current epoch.
85
87
  8. **Marker op FIRST, then write `harness_state`, then emit end-of-chore summary.** On `state: "continue"` (more phases follow, e.g. archive is still pending): `echo "<slug>" > .claude/state/.harness_active` to refresh the active marker, then write `.claude/state/harness_state` with `{state: "continue", slug, reason}`. On `state: "done"` (archive just appended and no further phases remain): `rm -f .claude/state/.harness_active`, then write `harness_state` with `{state: "done", slug, reason}`. The state file carries exactly three keys; no `written_at`, no `tick_count`. Then tell the user:
86
88
  - "Chore green."
87
89
  - Files changed.
@@ -5,7 +5,7 @@ description: Workflow Phase 11 — Commit Preparation and Execution. Stages and
5
5
  argument-hint: "[optional commit message; otherwise drafted from the spec/intake]"
6
6
  ---
7
7
 
8
- Prereq: `archive` in `completed` (i.e., Phase 10.5 has moved all slug artifacts to `docs/archive/<date>/<slug>/`) AND a valid consent token at `.claude/state/commit_consent` (the Git Commit Guard hook enforces this independently).
8
+ Prereq: BOTH `archive` AND `memory-flush` in `completed` (Phase 10.5 moved slug artifacts to `docs/archive/<date>/<slug>/`; Phase 10.6 curated `_pending.md` and applied canonical memory writes) AND a valid consent token at `.claude/state/commit_consent` (the Git Commit Guard hook enforces this independently). On any workflow where `memory-flush` is in `exceptions` (rare), this skill SHALL refuse to proceed unless that exception is explicit in `workflow.json`.
9
9
 
10
10
  **Applicability.** This skill applies only when the project is a git repository. Non-git projects auto-except `commit` at `/triage` time (CLAUDE.md Article IV); the workflow ends after `/archive`.
11
11
 
@@ -13,9 +13,10 @@ Steps:
13
13
 
14
14
  0. **Git-repo precheck.** Run `git rev-parse --is-inside-work-tree 2>/dev/null`. If exit non-zero, exit cleanly with: "Not a git repository — `/commit` is inapplicable. Per CLAUDE.md Article IV, `commit` is auto-excepted on non-git projects; the workflow ended at `/archive`. Persistence outside git is your responsibility." Do not run any subsequent step.
15
15
  1. **Archive `workflow.json` itself.** This is the final piece of the archival bundle, held back until now so phase-ordering checks worked up through this point. Read `.claude/state/workflow.json` to get the slug, then move the file into the already-existing archive bundle: `docs/archive/<date>/<slug>/workflow.json`. Use the bundle's `<date>` directory (the one `/archive` created — inspect `docs/archive/` to find the most recent bundle matching the slug).
16
- 2. Verify workflow prereq: `archive` is the final non-commit entry in `completed`; no open consent gates remain.
16
+ 2. Verify workflow prereq: memory-flush is the final non-commit entry in `completed`; `archive` is the entry immediately before it; no open consent gates remain.
17
17
  3. `git status` + `git diff --stat` to confirm the change set. The diff now includes: production code changes + archive bundle additions + the workflow.json move. Stage named paths explicitly (never `git add -A` / `git add .` — seed.md forbids it).
18
18
  4. Draft the commit message from the spec + diff. Conventional-style prefix (`feat:` / `fix:` / `refactor:` / `docs:` / `test:`) followed by a 1-line summary and a short body explaining the WHY. The subject line is a fixed-register one-liner — leave it alone. The body is reviewer-facing prose — pass it through `Skill(humanizer)` before step 5 so AI-writing tells (em-dash overuse, rule of three, inflated verbs, vague attributions) get scrubbed. Keep the brief tight: tell humanizer the register is "factual reviewer-facing commit body — describe the diff faithfully, do not invent rationale, preserve any spec quotes verbatim".
19
19
  5. Run `git commit` with the message via HEREDOC. The Git Commit Guard hook will verify consent. If consent is missing/expired, stop and ask the user to run `/grant-commit`.
20
- 6. Do NOT run `git push`, `git commit --amend`, or pass `--no-verify`/`--no-gpg-sign` unless the user explicitly named the operation in their current request.
21
- 7. Append `"commit"` to `completed` but note this only matters for logs; the workflow.json is now in the archive and the live `.claude/state/workflow.json` no longer exists. Report the commit SHA to the user.
20
+ 6. **Stamp source backlog entries (post-commit, only when populated).** Read `workflow.json source_backlog_keys` BEFORE Step 1 archived the file (or read the archived copy at `docs/archive/<date>/<slug>/workflow.json`). If the array is absent OR empty, skip this step entirely. Otherwise invoke `python3 .claude/skills/memory-flush/sweep.py --mode stamp-closure --memory-dir .claude/memory --backlog-keys <comma-separated keys>`. Parse the JSON report and report `stamped`/`missing`/`already_closed` counts in the terminal message alongside the commit SHA. `sweep.py` is the only writer to `backlog.md`; `commit/SKILL.md` SHALL NOT edit canonical memory files directly. If the stamp invocation fails (filesystem error), surface it but do NOT roll back the commit — the entries can be stamped manually or by the next workflow's `/memory-flush`.
21
+ 7. Do NOT run `git push`, `git commit --amend`, or pass `--no-verify`/`--no-gpg-sign` unless the user explicitly named the operation in their current request.
22
+ 8. Append `"commit"` to `completed` — but note this only matters for logs; the workflow.json is now in the archive and the live `.claude/state/workflow.json` no longer exists. Report the commit SHA to the user.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) coreyhaines31 (https://github.com/coreyhaines31/marketingskills)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,23 @@
1
+ copywriting
2
+ ===========
3
+
4
+ This skill is vendored from the `copywriting` skill in the `marketingskills`
5
+ collection published by coreyhaines31 and is redistributed here under the
6
+ terms of the MIT License (see LICENSE in this directory).
7
+
8
+ Upstream:
9
+ Repository: https://github.com/coreyhaines31/marketingskills
10
+ Author: coreyhaines31
11
+ Skill: copywriting/
12
+
13
+ Vendored into: .claude/skills/copywriting/
14
+ Vendored on: 2026-05-15 (this commit) — prior install date unknown; LICENSE
15
+ + NOTICE added retroactively as part of the licensing-attribution
16
+ drift fix in the branch-aware-git-policy workflow.
17
+
18
+ Local changes:
19
+ - References to five companion skills (`copy-editing`, `email-sequence`,
20
+ `popup-cro`, etc.) that do not ship in this baseline are flagged in the
21
+ SKILL.md body with explicit "do not ship with this baseline" notes.
22
+ - Frontmatter `owner: baseline` added so the audit-baseline drift check
23
+ tracks this file as a shipped baseline artifact.
@@ -243,7 +243,7 @@ For headlines and CTAs, provide 2-3 options:
243
243
 
244
244
  ## Related Skills
245
245
 
246
- These five are upstream companion skills (from the `claude-code-setup` plugin family) and **do not ship with this baseline**. Listed for reference only — for the capabilities they describe, ask the user inline or install the plugin separately.
246
+ These five are upstream companion skills from the same source repo as `copywriting` ([`coreyhaines31/marketingskills`](https://github.com/coreyhaines31/marketingskills)) and **do not ship with this baseline**. Listed for reference only — for the capabilities they describe, ask the user inline or vendor them separately following the same MIT terms.
247
247
 
248
248
  - **copy-editing**: For polishing existing copy (use after your draft)
249
249
  - **page-cro**: If page structure/strategy needs work, not just copy
@@ -17,7 +17,7 @@ These are not preferences. They are structural commitments locked by spec `docs/
17
17
  - **You ALWAYS invoke impeccable.** Every design move goes through `Skill(impeccable, …)`. No exceptions, no shortcuts.
18
18
  - **You NEVER pick aesthetic direction.** Register, palette, type scale, motion vocabulary — all decided inside impeccable's subcommands in main context. You decide *which* subcommand to invoke, not *what* design to produce.
19
19
  - **You NEVER write product code.** Files under `app/`, `site-src/`, `components/`, `src/` — all flow through impeccable's writing subcommands (`craft`, `polish`, refines, enhances, fixes). You write only thin glue: state JSON, brief snapshots, audit snapshots.
20
- - **You ALWAYS classify before acting.** A misrouted `task_brief` (development or copy concern) returns immediately with `final_state: "not_a_design_task"` and a pointer to the correct lane. Design tasks proceed; everything else stops at Stage 0.
20
+ - **You ALWAYS classify before acting.** A misrouted `task_brief` returns at Stage 0 with one of two terminal states: **`not_a_design_task`** (when all surfaces classify as a single non-design lane — pure development or pure copy) with a `correct_lane` pointer, OR **`mixed_brief`** (when target_files span ≥ 2 lanes per the per-concern rule in `references/design-vs-development.md`) with a structured `lane_split` array. Design tasks proceed; everything else stops at Stage 0 without invoking impeccable or writing product code.
21
21
 
22
22
  ## Mandatory first step
23
23
 
@@ -47,7 +47,9 @@ Decide which lane this `task_brief` belongs to. The classification rule lives in
47
47
 
48
48
  Stage 0 evaluates two signals: (1) the intent string against the [`references/intent-table.md`](references/intent-table.md) rows, and (2) the `target_files` extensions as a tie-breaker.
49
49
 
50
- If the classification is anything other than **design**, return immediately:
50
+ If the classification is anything other than **design**, return immediately with one of two misroute terminals.
51
+
52
+ **Single-lane misroute** (all surfaces classify as pure development OR pure copy):
51
53
 
52
54
  ```jsonc
53
55
  {
@@ -58,7 +60,22 @@ If the classification is anything other than **design**, return immediately:
58
60
  }
59
61
  ```
60
62
 
61
- design-ui still writes a checkpoint state file even on misroute — the orchestration history is traceable.
63
+ **Multi-lane misroute** (target_files span 2 lanes per the per-concern rule in `references/design-vs-development.md`):
64
+
65
+ ```jsonc
66
+ {
67
+ "final_state": "mixed_brief",
68
+ "lane_split": [
69
+ { "surface": "<path>", "lane": "design" | "development" | "copy", "reason": "<plain-language>" }
70
+ ],
71
+ "reason": "task_brief spans <N> lanes",
72
+ "state_file": ".claude/state/design/<slug>.json"
73
+ }
74
+ ```
75
+
76
+ When `target_files` span ≥ 2 lanes, Stage 0 returns `mixed_brief` with a `lane_split` array (one entry per surface) instead of `not_a_design_task`. The caller fans out per row; design-ui does NOT execute any lane in this case (no impeccable invocation, no product-code writes). SKILL.md is the canonical source for this contract; `references/design-vs-development.md` mirrors it.
77
+
78
+ design-ui still writes a checkpoint state file even on misroute — the orchestration history is traceable. See `references/state-machine.md` for the sticky-resume rule that applies to both misroute terminals.
62
79
 
63
80
  ### Stage 1 — Capture
64
81
 
@@ -117,12 +134,13 @@ Return a structured `Report`:
117
134
  "slug": "<the slug>",
118
135
  "intent": "<the intent>",
119
136
  "recipe_executed": ["shape", "craft", "audit", "polish"],
120
- "final_state": "complete" | "needs_human" | "blocked" | "not_a_design_task",
137
+ "final_state": "complete" | "needs_human" | "blocked" | "not_a_design_task" | "mixed_brief",
121
138
  "files_touched": ["<path>", ...],
122
139
  "verifications": { "audit_score": "19/20", "p0": 0, "p1": 0 },
123
140
  "next_actions": ["<human-readable>"],
124
141
  "state_file": ".claude/state/design/<slug>.json",
125
- "thin_glue_written": ["docs/design/<slug>.brief.md", "docs/design/<slug>.audit.md"]
142
+ "thin_glue_written": ["docs/design/<slug>.brief.md", "docs/design/<slug>.audit.md"],
143
+ "lane_split": [ { "surface": "<path>", "lane": "design" | "development" | "copy", "reason": "<plain-language>" } ] // present only when final_state == "mixed_brief"
126
144
  }
127
145
  ```
128
146
 
@@ -21,7 +21,7 @@ When a `task_brief` arrives, Stage 0 evaluates **two signals** in order:
21
21
  - All paths match `tdd.ui_globs` AND no logic-file extensions → **design**.
22
22
  - All paths are logic files (`.ts`, `.js`, `.go`, `.py`, `.rs`, etc., excluding `.tsx` / `.jsx` / `.vue` / `.svelte`) → **development**.
23
23
  - All paths are `.md` / `.mdx` and the intent mentions "write", "rewrite", "improve", "draft" → **copy**.
24
- - Mixed → return to step 1 with the user surfaced; ask which concern they mean.
24
+ - Mixed → Stage 0 returns `final_state: "mixed_brief"` with a `lane_split` array (one entry per surface). See `SKILL.md` (canonical) for the return shape.
25
25
 
26
26
  ## Overlap is normal — same file, three lanes
27
27
 
@@ -67,7 +67,11 @@ Backend / data-fetching performance, query optimization, memoization for re-rend
67
67
 
68
68
  ## Misroute handling
69
69
 
70
- If Stage 0 classifies the intent as **development** or **copy** (not design), `design-ui` immediately returns:
70
+ *`SKILL.md` is the canonical source for Stage 0 misroute prose; this file mirrors it.*
71
+
72
+ Stage 0 has two misroute terminals.
73
+
74
+ **Single-lane misroute** — all surfaces classify as one non-design lane (pure development OR pure copy):
71
75
 
72
76
  ```jsonc
73
77
  {
@@ -78,12 +82,29 @@ If Stage 0 classifies the intent as **development** or **copy** (not design), `d
78
82
  }
79
83
  ```
80
84
 
81
- The caller (a workflow phase or the user) reads `correct_lane` and re-routes. `design-ui` never silently passes a non-design brief through to `impeccable` — that would muddy impeccable's contract.
85
+ The caller reads `correct_lane` and re-routes.
86
+
87
+ **Multi-lane misroute** — target_files span ≥ 2 lanes:
88
+
89
+ ```jsonc
90
+ {
91
+ "final_state": "mixed_brief",
92
+ "lane_split": [
93
+ { "surface": "<path>", "lane": "design" | "development" | "copy", "reason": "<plain-language>" }
94
+ ],
95
+ "reason": "task_brief spans <N> lanes",
96
+ "state_file": ".claude/state/design/<slug>.json"
97
+ }
98
+ ```
99
+
100
+ The caller reads `lane_split` and fans out per row; see `references/orchestration.md` caller-policy. `design-ui` never silently passes a non-design brief through to `impeccable` — that would muddy impeccable's contract. On a `mixed_brief`, `design-ui` invokes nothing and writes no product code: the structured `lane_split` is the entire response.
82
101
 
83
102
  ## When in doubt
84
103
 
85
- If the per-concern split is ambiguous *and* both signals (intent string + target_files) fail to resolve, surface to the user with a one-line question:
104
+ Multi-lane briefs (target_files span ≥ 2 lanes) route automatically to `mixed_brief` — Stage 0 returns the structured `lane_split` without asking the user. The interactive-ask path below is reserved for the rarer **single-lane ambiguity** case: the intent matches no row in `references/intent-table.md` AND `target_files` doesn't disambiguate (e.g., a single `.md` file but the intent reads like a design ask, not a copy ask).
105
+
106
+ In that case, surface to the user with a one-line question:
86
107
 
87
- > "This task seems to span design and development: <intent>. Which concern are you asking about? (a) design — surface, motion, visual a11y; (b) development — behavior, logic, data; (c) both, in sequence start with `/tdd` for behavior, then this skill for design."
108
+ > "This task is ambiguous within a single lane: <intent>. Which concern are you asking about? (a) design — surface, motion, visual a11y; (b) development — behavior, logic, data; (c) copyprose rewrite."
88
109
 
89
110
  Do not guess. The clean separation is what makes the lanes structural.
@@ -119,3 +119,4 @@ This is the same cap-3 shape as the polish loop, with `critique` as the gating s
119
119
  | `needs_human` | Warn and continue. design-ui has surfaced; the user can re-invoke later. /tdd Step 6 does NOT fail. The audit report path goes in /tdd's notes. |
120
120
  | `blocked` | Stop /tdd Step 6. Surface the blocker to the user. /tdd's `## 7. Decide on the result` step receives this and decides whether to escalate to a spec change. |
121
121
  | `not_a_design_task` | Stage 0 misroute. /tdd surfaces "design-ui returned not_a_design_task — was this design_call mis-classified in the spec?" and stops to reconcile. |
122
+ | `mixed_brief` | Stage 0 multi-lane misroute. Read `lane_split`. For each row: lane=design → re-invoke design-ui with a surface-scoped sub-brief; lane=development → record on `next_actions` and surface to the user; lane=copy → record on `next_actions` and surface to the user. Do NOT auto-invoke `/tdd` or `prose` in this tick — the spec author can split the `## Design calls` row deliberately. /tdd Step 6 surfaces a one-line summary and proceeds. |
@@ -21,7 +21,7 @@ The first invocation creates the directory if it does not exist (`mkdir -p`). Su
21
21
  "step_index": <int, 0-based; index of the NEXT step to run>,
22
22
  "invocations": [InvocationRecord, ...],
23
23
  "verifications": [VerificationRecord, ...],
24
- "state": "in_progress" | "complete" | "needs_human" | "blocked" | "not_a_design_task",
24
+ "state": "in_progress" | "complete" | "needs_human" | "blocked" | "not_a_design_task" | "mixed_brief",
25
25
  "next_actions": ["<human-readable action>", ...]
26
26
  }
27
27
  ```
@@ -69,7 +69,7 @@ Every state file MUST carry these fields. Stage 3's checkpoint writes them all o
69
69
  | `step_index` | yes | The position to resume from. 0 = nothing run yet; N = N steps completed. |
70
70
  | `invocations` | yes | Append-only list. Each step's invocation appears here. |
71
71
  | `verifications` | yes | Subset of invocations[] limited to evaluation steps (audit, critique). |
72
- | `state` | yes | One of the five terminal/transitional states. |
72
+ | `state` | yes | One of the six terminal/transitional states. |
73
73
 
74
74
  Optional fields:
75
75
  - `register`, `updated_at`, `next_actions` — present when the orchestration has produced them; absent before Stage 1 completes the capture.
@@ -77,7 +77,7 @@ Optional fields:
77
77
 
78
78
  ## Terminal states
79
79
 
80
- The five values of `state` and their meaning:
80
+ The six values of `state` and their meaning:
81
81
 
82
82
  | State | Meaning | Caller action |
83
83
  |---|---|---|
@@ -86,6 +86,7 @@ The five values of `state` and their meaning:
86
86
  | `needs_human` | Loop cap fired (3 iterations on audit→polish or critique-driven refine). P1 issues remain unresolved. | Caller decides whether to surface and stop, or warn and continue. Per `/tdd` Step 6 policy: warn and continue. |
87
87
  | `blocked` | A gate fired. P0 blockers, register conflict declined, target_files parent missing, malformed state file, user refused recipe. | Caller surfaces the reason and stops the immediate flow. |
88
88
  | `not_a_design_task` | Stage 0 classified the intent as development or copy. Set on the first checkpoint write of the orchestration. | Caller routes to `correct_lane` (`/tdd` or `/document`). |
89
+ | `mixed_brief` | Stage 0 classified the task_brief as spanning ≥ 2 lanes (multi-lane misroute). Set on the first checkpoint write of the orchestration; `lane_split` is persisted alongside. | Caller reads `lane_split` and fans out per row; see `references/orchestration.md` caller-policy. |
89
90
 
90
91
  ## Resume logic
91
92
 
@@ -99,6 +100,7 @@ On any `Skill(design-ui, task_brief)` invocation:
99
100
  - **Present and `state` is `needs_human`** → caller must signal intent to resume. If `task_brief` is identical to the stored intent and the caller is re-invoking explicitly, restart the loop from `step_index` (which points at the audit that fired the cap). The user is asserting that conditions have changed (e.g., P1 issues were addressed externally) and the loop should re-run.
100
101
  - **Present and `state` is `blocked`** → return the existing Report with the blocker reason. User must materially change the input (re-state intent, expand write_set, etc.) before progress.
101
102
  - **Present and `state` is `not_a_design_task`** → return the existing Report; the misroute is sticky for this slug.
103
+ - **Present and `state` is `mixed_brief`** → return the existing Report (including the cached `lane_split`); the misroute is sticky for this slug (mirrors `not_a_design_task`). Delete the state file to re-classify.
102
104
  - **Present but malformed JSON** → return `Report { final_state: "blocked", reason: "state file malformed", state_file }`. Do NOT overwrite — preserve for human inspection.
103
105
 
104
106
  The resume rule: **skip steps prior to `step_index`; start at `step_index`**. Completed steps are never re-run unless the user explicitly requests it (e.g., by deleting the state file and re-invoking).