@jeiemgi/cckit 0.1.6

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 (191) hide show
  1. package/.claude-plugin/plugin.json +22 -0
  2. package/AGENTS.md +101 -0
  3. package/LICENSE-APACHE +202 -0
  4. package/LICENSE-MIT +21 -0
  5. package/README.md +143 -0
  6. package/SECURITY.md +22 -0
  7. package/bin/cckit +215 -0
  8. package/cckit.config.json +34 -0
  9. package/commands/kit-add.md +42 -0
  10. package/commands/kit-docs.md +45 -0
  11. package/commands/kit-doctor.md +52 -0
  12. package/commands/kit-export-project.md +58 -0
  13. package/commands/kit-export-training.md +49 -0
  14. package/commands/kit-init.md +126 -0
  15. package/commands/kit-routines.md +59 -0
  16. package/commands/kit-update.md +132 -0
  17. package/docs/kit-annotate/01-explainer.html +225 -0
  18. package/docs/kit-annotate/02-implementation-plan.html +196 -0
  19. package/docs/media/.onboarding-capture.cast +5 -0
  20. package/docs/media/README.md +43 -0
  21. package/docs/media/build-demo.sh +63 -0
  22. package/docs/media/build-kit-init.sh +51 -0
  23. package/docs/media/build-onboarding.sh +51 -0
  24. package/docs/media/kit-dry-run.cast +107 -0
  25. package/docs/media/kit-dry-run.gif +0 -0
  26. package/docs/media/kit-init.cast +56 -0
  27. package/docs/media/kit-init.gif +0 -0
  28. package/docs/media/kit-onboarding.cast +148 -0
  29. package/docs/media/kit-onboarding.gif +0 -0
  30. package/githooks/pre-commit +18 -0
  31. package/kit.config.schema.json +105 -0
  32. package/package.json +54 -0
  33. package/privacy-denylist.example +8 -0
  34. package/profiles/automation.json +36 -0
  35. package/profiles/content.json +41 -0
  36. package/profiles/minimal.json +31 -0
  37. package/profiles/research.json +37 -0
  38. package/profiles/software.json +32 -0
  39. package/scripts/annotate-setup.sh +149 -0
  40. package/scripts/autopilot.sh +50 -0
  41. package/scripts/capture-project-ids.sh +53 -0
  42. package/scripts/check.sh +66 -0
  43. package/scripts/contribute.sh +48 -0
  44. package/scripts/debug.sh +54 -0
  45. package/scripts/init-upgrade-test.sh +99 -0
  46. package/scripts/init.sh +827 -0
  47. package/scripts/install.sh +24 -0
  48. package/scripts/kit-add-test.sh +62 -0
  49. package/scripts/kit-add.sh +115 -0
  50. package/scripts/kit-adopt-test.sh +61 -0
  51. package/scripts/kit-adopt.sh +122 -0
  52. package/scripts/kit-bump-version.sh +79 -0
  53. package/scripts/kit-digest.sh +126 -0
  54. package/scripts/kit-doctor.sh +663 -0
  55. package/scripts/kit-export-project-test.sh +82 -0
  56. package/scripts/kit-export-project.sh +245 -0
  57. package/scripts/kit-export-training-test.sh +51 -0
  58. package/scripts/kit-export-training.sh +175 -0
  59. package/scripts/kit-migrate-test.sh +80 -0
  60. package/scripts/kit-migrate.sh +190 -0
  61. package/scripts/kit-onboard-test.sh +63 -0
  62. package/scripts/kit-onboard.sh +69 -0
  63. package/scripts/kit-promote-test.sh +54 -0
  64. package/scripts/kit-promote.sh +102 -0
  65. package/scripts/kit-remove-test.sh +61 -0
  66. package/scripts/kit-remove.sh +84 -0
  67. package/scripts/kit-routines.sh +322 -0
  68. package/scripts/kit-version-check.sh +91 -0
  69. package/scripts/kit-wire-test.sh +54 -0
  70. package/scripts/kit-wire.sh +132 -0
  71. package/scripts/knowledge-lint.sh +96 -0
  72. package/scripts/lib/cckit-output.sh +36 -0
  73. package/scripts/lib/effort-metrics.sh +452 -0
  74. package/scripts/lib/effort-ops-test.sh +83 -0
  75. package/scripts/lib/effort-ops.sh +132 -0
  76. package/scripts/lib/effort-plan.sh +104 -0
  77. package/scripts/lib/effort.sh +191 -0
  78. package/scripts/lib/engine-adapter.sh +92 -0
  79. package/scripts/lib/gh-log.sh +58 -0
  80. package/scripts/lib/gh-project.sh +212 -0
  81. package/scripts/lib/handoff.sh +35 -0
  82. package/scripts/lib/kit-cli-test.sh +42 -0
  83. package/scripts/lib/kit-cli.sh +32 -0
  84. package/scripts/lib/kit-config-resolve.sh +145 -0
  85. package/scripts/lib/kit-config.sh +88 -0
  86. package/scripts/lib/kit-engine-test.sh +107 -0
  87. package/scripts/lib/kit-events.sh +62 -0
  88. package/scripts/lib/kit-gc.sh +117 -0
  89. package/scripts/lib/kit-interview-test.sh +77 -0
  90. package/scripts/lib/kit-interview.sh +203 -0
  91. package/scripts/lib/kit-local.sh +79 -0
  92. package/scripts/lib/kit-manifest.sh +127 -0
  93. package/scripts/lib/kit-mode-test.sh +49 -0
  94. package/scripts/lib/kit-mode.sh +67 -0
  95. package/scripts/lib/kit-operate.sh +105 -0
  96. package/scripts/lib/kit-profile-test.sh +62 -0
  97. package/scripts/lib/kit-profile.sh +115 -0
  98. package/scripts/lib/kit-task-ops-test.sh +63 -0
  99. package/scripts/lib/kit-task-ops.sh +341 -0
  100. package/scripts/lib/pr-evidence.sh +173 -0
  101. package/scripts/lib/project-scan.sh +16 -0
  102. package/scripts/lib/react-detect.sh +78 -0
  103. package/scripts/lib/role-identity.sh +47 -0
  104. package/scripts/lib/secret-guard.sh +96 -0
  105. package/scripts/lib/toon.sh +35 -0
  106. package/scripts/lib/ui.sh +42 -0
  107. package/scripts/lib/version-bump.sh +59 -0
  108. package/scripts/lib/worktree-issue-test.sh +45 -0
  109. package/scripts/lib/worktree-issue.sh +73 -0
  110. package/scripts/lib/worktree-start.sh +280 -0
  111. package/scripts/orchestrate.sh +160 -0
  112. package/scripts/portable-test.sh +53 -0
  113. package/scripts/publish.sh +94 -0
  114. package/scripts/setup-labels.sh +25 -0
  115. package/scripts/setup-milestones.sh +17 -0
  116. package/scripts/showcase.sh +64 -0
  117. package/scripts/status.sh +44 -0
  118. package/scripts/task-sync.sh +59 -0
  119. package/scripts/test.sh +48 -0
  120. package/scripts/web-install.sh +22 -0
  121. package/skills/kit-annotate/SKILL.md +107 -0
  122. package/skills/kit-autopilot/SKILL.md +108 -0
  123. package/skills/kit-contribute/SKILL.md +134 -0
  124. package/skills/kit-customize/SKILL.md +134 -0
  125. package/skills/kit-dev/SKILL.md +67 -0
  126. package/skills/kit-digest/SKILL.md +41 -0
  127. package/skills/kit-effort-close/SKILL.md +156 -0
  128. package/skills/kit-effort-new/SKILL.md +173 -0
  129. package/skills/kit-effort-pr/SKILL.md +139 -0
  130. package/skills/kit-effort-start/SKILL.md +85 -0
  131. package/skills/kit-gc/SKILL.md +80 -0
  132. package/skills/kit-onboard/SKILL.md +50 -0
  133. package/skills/kit-security-sweep/SKILL.md +57 -0
  134. package/skills/kit-ship/SKILL.md +43 -0
  135. package/skills/kit-task-close/SKILL.md +66 -0
  136. package/skills/kit-task-new/SKILL.md +51 -0
  137. package/skills/kit-task-pr/SKILL.md +43 -0
  138. package/skills/kit-task-pr-auto/SKILL.md +27 -0
  139. package/skills/kit-task-pr-merge/SKILL.md +53 -0
  140. package/skills/kit-task-start/SKILL.md +76 -0
  141. package/skills/kit-task-sync/SKILL.md +37 -0
  142. package/templates/CLAUDE.md.tmpl +106 -0
  143. package/templates/agents/analyst.md +55 -0
  144. package/templates/agents/auto-dev.md +93 -0
  145. package/templates/agents/backend.md +59 -0
  146. package/templates/agents/designer.md +73 -0
  147. package/templates/agents/devops.md +57 -0
  148. package/templates/agents/editor.md +48 -0
  149. package/templates/agents/frontend.md +81 -0
  150. package/templates/agents/generalist.md +46 -0
  151. package/templates/agents/local-delegate.md +70 -0
  152. package/templates/agents/n8n.md +65 -0
  153. package/templates/agents/pm.md +69 -0
  154. package/templates/agents/qa.md +66 -0
  155. package/templates/agents/researcher.md +57 -0
  156. package/templates/agents/security.md +65 -0
  157. package/templates/agents/tech-lead.md +75 -0
  158. package/templates/hooks/guard-base-branch-commit.sh.tmpl +45 -0
  159. package/templates/hooks/kit-local-status.sh.tmpl +34 -0
  160. package/templates/hooks/kit_version_check.sh.tmpl +6 -0
  161. package/templates/hooks/mempal_followup.sh.tmpl +97 -0
  162. package/templates/hooks/mempal_precompact.sh.tmpl +4 -0
  163. package/templates/hooks/mempal_save.sh.tmpl +4 -0
  164. package/templates/hooks/mempal_session_start.sh.tmpl +8 -0
  165. package/templates/hooks/prepush_gate.sh.tmpl +36 -0
  166. package/templates/hooks/repo-hygiene.sh.tmpl +72 -0
  167. package/templates/kit.config.json.tmpl +32 -0
  168. package/templates/knowledge-INDEX.md.tmpl +12 -0
  169. package/templates/lib/kit-sigil.sh.tmpl +124 -0
  170. package/templates/rules/branch-naming.md +104 -0
  171. package/templates/rules/communication-style.md +22 -0
  172. package/templates/rules/delegation-brief.md +40 -0
  173. package/templates/rules/design-routing.md +35 -0
  174. package/templates/rules/effort-model.md +122 -0
  175. package/templates/rules/knowledge-base.md +41 -0
  176. package/templates/rules/mempalace.md +110 -0
  177. package/templates/rules/plan-output-format.md +58 -0
  178. package/templates/rules/react-annotate.md +69 -0
  179. package/templates/rules/risk-tiered-review.md +62 -0
  180. package/templates/rules/skill-gaps.md +48 -0
  181. package/templates/rules/task-management.md +42 -0
  182. package/templates/settings/settings.local.json.tmpl +27 -0
  183. package/templates/skills/NAMESPACED +13 -0
  184. package/templates/skills/copywriting/SKILL.md +252 -0
  185. package/templates/skills/copywriting/references/copy-frameworks.md +344 -0
  186. package/templates/skills/copywriting/references/natural-transitions.md +272 -0
  187. package/templates/skills/feature-build-refine/SKILL.md +367 -0
  188. package/templates/skills/karpathy-guidelines/SKILL.md +69 -0
  189. package/templates/skills/morning-briefing/SKILL.md +46 -0
  190. package/templates/skills/speckit/SKILL.md +239 -0
  191. package/templates/skills/supabase-patterns/SKILL.md +88 -0
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env bash
2
+ # kit-manifest.sh — ownership manifest for everything the kit writes into a project (#368).
3
+ #
4
+ # THE PROBLEM IT SOLVES: kit-wire / kit-init / kit-promote write files into a project. To update
5
+ # or UNINSTALL them safely we must distinguish "the kit wrote this and the user hasn't touched it"
6
+ # (safe to overwrite/remove) from "the user edited it" (ask first) from "not ours" (never touch).
7
+ # Guessing by filename is how you delete a user's work. The manifest records path + content hash +
8
+ # kit version + portability tier for each managed file, so every later op consults facts, not
9
+ # heuristics. This is the prerequisite for kit-remove (D12), kit-wire, kit-migrate, kit-promote.
10
+ #
11
+ # Source it: source scripts/lib/kit-manifest.sh
12
+ # Requires: jq + a sha256 tool (shasum -a 256 | sha256sum).
13
+ #
14
+ # Manifest shape (.claude/kit.manifest.json):
15
+ # {
16
+ # "schema": 1,
17
+ # "entries": {
18
+ # "<relpath>": { "hash": "<sha256>", "kitVersion": "<v>", "tier": "A|B", "op": "<who>", "ts": "<iso>" }
19
+ # }
20
+ # }
21
+ # tier A = portable (Cowork/claude.ai), tier B = CLI-only — see kit-v2 plan §5.
22
+
23
+ # --- location -------------------------------------------------------------
24
+ # Manifest lives at the project root's .claude/kit.manifest.json. Override with KIT_MANIFEST.
25
+ kit_manifest_path() {
26
+ if [ -n "${KIT_MANIFEST:-}" ]; then printf '%s\n' "$KIT_MANIFEST"; return 0; fi
27
+ printf '%s\n' ".claude/kit.manifest.json"
28
+ }
29
+
30
+ # --- portable sha256 ------------------------------------------------------
31
+ kit_manifest_hash() {
32
+ # hash of file "$1"; empty string if missing
33
+ [ -f "$1" ] || { printf ''; return 0; }
34
+ if command -v shasum >/dev/null 2>&1; then
35
+ shasum -a 256 -- "$1" | awk '{print $1}'
36
+ elif command -v sha256sum >/dev/null 2>&1; then
37
+ sha256sum -- "$1" | awk '{print $1}'
38
+ else
39
+ echo "kit-manifest: no sha256 tool (shasum/sha256sum)" >&2; return 1
40
+ fi
41
+ }
42
+
43
+ _kit_manifest_require() {
44
+ command -v jq >/dev/null 2>&1 || { echo "kit-manifest: jq is required" >&2; return 1; }
45
+ }
46
+
47
+ # --- lifecycle ------------------------------------------------------------
48
+ # Create an empty manifest if none exists. Idempotent.
49
+ kit_manifest_init() {
50
+ _kit_manifest_require || return 1
51
+ local mf; mf="$(kit_manifest_path)"
52
+ [ -f "$mf" ] && return 0
53
+ mkdir -p "$(dirname "$mf")"
54
+ printf '{\n "schema": 1,\n "entries": {}\n}\n' > "$mf"
55
+ }
56
+
57
+ # kit_manifest_record <relpath> <tier> [op] [kitVersion] [ts]
58
+ # Records (or updates) the entry, hashing the CURRENT content of <relpath>.
59
+ # ts/kitVersion are injectable so callers in deterministic contexts (tests, resumable workflows)
60
+ # can pass stable values instead of reading the clock.
61
+ kit_manifest_record() {
62
+ _kit_manifest_require || return 1
63
+ local relpath="$1" tier="${2:-A}" op="${3:-wire}" ver="${4:-${KIT_VERSION:-0.0.0}}" ts="${5:-}"
64
+ [ -n "$relpath" ] || { echo "kit-manifest record: path required" >&2; return 1; }
65
+ case "$tier" in A|B) ;; *) echo "kit-manifest: tier must be A or B (got '$tier')" >&2; return 1;; esac
66
+ local mf hash; mf="$(kit_manifest_path)"; kit_manifest_init || return 1
67
+ hash="$(kit_manifest_hash "$relpath")" || return 1
68
+ if [ -z "$ts" ]; then ts="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unknown)"; fi
69
+ local tmp; tmp="$(mktemp)" || return 1
70
+ jq --arg p "$relpath" --arg h "$hash" --arg t "$tier" --arg o "$op" --arg v "$ver" --arg ts "$ts" \
71
+ '.entries[$p] = {hash:$h, kitVersion:$v, tier:$t, op:$o, ts:$ts}' "$mf" > "$tmp" \
72
+ && mv "$tmp" "$mf" || { rm -f "$tmp"; return 1; }
73
+ }
74
+
75
+ # kit_manifest_get <relpath> -> the entry JSON (or empty + rc 1 if untracked)
76
+ kit_manifest_get() {
77
+ _kit_manifest_require || return 1
78
+ local mf; mf="$(kit_manifest_path)"; [ -f "$mf" ] || return 1
79
+ local out; out="$(jq -e --arg p "$1" '.entries[$p] // empty' "$mf" 2>/dev/null)" || return 1
80
+ [ -n "$out" ] || return 1
81
+ printf '%s\n' "$out"
82
+ }
83
+
84
+ # kit_manifest_verify <relpath> -> prints one of: intact | modified | missing | untracked
85
+ # intact = tracked and current content matches recorded hash (safe to overwrite/remove)
86
+ # modified = tracked but content changed since the kit wrote it (ASK before touching)
87
+ # missing = tracked but the file is gone
88
+ # untracked = not in the manifest (NEVER touch on the kit's behalf)
89
+ # rc: 0 intact, 1 modified, 2 missing, 3 untracked — so callers can branch on $?.
90
+ kit_manifest_verify() {
91
+ _kit_manifest_require || return 1
92
+ local relpath="$1" mf stored cur; mf="$(kit_manifest_path)"
93
+ if [ ! -f "$mf" ] || ! stored="$(jq -er --arg p "$relpath" '.entries[$p].hash // empty' "$mf" 2>/dev/null)" || [ -z "$stored" ]; then
94
+ echo untracked; return 3
95
+ fi
96
+ if [ ! -f "$relpath" ]; then echo missing; return 2; fi
97
+ cur="$(kit_manifest_hash "$relpath")" || return 1
98
+ if [ "$cur" = "$stored" ]; then echo intact; return 0; else echo modified; return 1; fi
99
+ }
100
+
101
+ # kit_manifest_remove_entry <relpath> — drop the entry (does NOT delete the file)
102
+ kit_manifest_remove_entry() {
103
+ _kit_manifest_require || return 1
104
+ local mf tmp; mf="$(kit_manifest_path)"; [ -f "$mf" ] || return 0
105
+ tmp="$(mktemp)" || return 1
106
+ jq --arg p "$1" 'del(.entries[$p])' "$mf" > "$tmp" && mv "$tmp" "$mf" || { rm -f "$tmp"; return 1; }
107
+ }
108
+
109
+ # kit_manifest_list [tier] — newline-list of tracked paths, optionally filtered by tier (A|B)
110
+ kit_manifest_list() {
111
+ _kit_manifest_require || return 1
112
+ local mf; mf="$(kit_manifest_path)"; [ -f "$mf" ] || return 0
113
+ if [ -n "${1:-}" ]; then
114
+ jq -r --arg t "$1" '.entries | to_entries[] | select(.value.tier==$t) | .key' "$mf"
115
+ else
116
+ jq -r '.entries | keys[]' "$mf"
117
+ fi
118
+ }
119
+
120
+ # kit_manifest_diff — print "<status>\t<path>" for every tracked path (intact/modified/missing)
121
+ kit_manifest_diff() {
122
+ _kit_manifest_require || return 1
123
+ local p; while IFS= read -r p; do
124
+ [ -n "$p" ] || continue
125
+ printf '%s\t%s\n' "$(kit_manifest_verify "$p")" "$p"
126
+ done < <(kit_manifest_list)
127
+ }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ # kit-mode-test.sh — self-test for kit-mode (#371). Runs under bash AND zsh.
3
+ # Run: bash scripts/lib/kit-mode-test.sh
4
+ dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
5
+
6
+ if [ -n "${KIT_MODE_TEST_INNER:-}" ]; then
7
+ set -u
8
+ . "$dir/kit-mode.sh"
9
+ fail=0
10
+ t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_MODE_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_MODE_TEST_INNER): $1"; fi; }
11
+
12
+ work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT
13
+ mkdir -p "$work/ws/proj/island"
14
+ printf '{"mode":"guided"}\n' > "$work/ws/kit.workspace.json"
15
+ printf '{"mode":"enforced"}\n' > "$work/ws/proj/kit.config.json"
16
+ printf '{"mode":"draft"}\n' > "$work/ws/proj/island/kit.config.json"
17
+ mkdir -p "$work/bare" # no config anywhere
18
+
19
+ unset KIT_MODE 2>/dev/null || true
20
+ t "mode workspace" "$(kit_mode "$work/ws")" "guided"
21
+ t "mode project" "$(kit_mode "$work/ws/proj")" "enforced"
22
+ t "mode island" "$(kit_mode "$work/ws/proj/island")" "draft"
23
+ t "mode default" "$(kit_mode "$work/bare")" "guided"
24
+
25
+ t "gate enforced" "$(kit_gate "$work/ws/proj")" "block"
26
+ t "gate guided" "$(kit_gate "$work/ws")" "warn"
27
+ t "gate draft" "$(kit_gate "$work/ws/proj/island")" "off"
28
+
29
+ # KIT_MODE env overrides the cascade
30
+ t "env override" "$(KIT_MODE=enforced kit_mode "$work/bare")" "enforced"
31
+
32
+ # kit_gate_apply exit codes: enforced -> rc 2 (blocks), guided -> rc 0, draft -> rc 0
33
+ kit_gate_apply "x" "$work/ws/proj" >/dev/null 2>&1; t "apply enforced rc" "$?" "2"
34
+ kit_gate_apply "x" "$work/ws" >/dev/null 2>&1; t "apply guided rc" "$?" "0"
35
+ kit_gate_apply "x" "$work/ws/proj/island" >/dev/null 2>&1; t "apply draft rc" "$?" "0"
36
+
37
+ [ "$fail" -eq 0 ] && echo "ALL OK($KIT_MODE_TEST_INNER)"
38
+ exit "$fail"
39
+ fi
40
+
41
+ rc=0; ran=0
42
+ for sh in bash zsh; do
43
+ command -v "$sh" >/dev/null 2>&1 || continue
44
+ ran=$((ran+1)); echo "--- $sh ---"
45
+ rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
46
+ KIT_MODE_TEST_INNER="$sh" PATH="$PATH" "$sh" $rcflag "$0" || rc=1
47
+ done
48
+ [ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
49
+ exit "$rc"
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env bash
2
+ # kit-mode.sh — resolve the effective enforcement MODE for a location, and map it to a gate (#371).
3
+ #
4
+ # `mode` (draft | guided | enforced) is a cascade key (workspace -> project -> island), so it is
5
+ # resolved by kit-config-resolve.sh like any other field. This lib gives hooks and skills a single
6
+ # place to ask "how strict am I HERE?" and "should I block, warn, or stay silent?".
7
+ #
8
+ # draft guardrails OFF — explore freely; hooks no-op
9
+ # guided guardrails WARN — hooks print a notice, never block (default)
10
+ # enforced guardrails BLOCK — hooks exit non-zero (worktree-only, PR-per-issue)
11
+ #
12
+ # Source it: source scripts/lib/kit-mode.sh (auto-sources kit-config-resolve.sh alongside)
13
+ # CLI: scripts/lib/kit-mode.sh [--dir DIR] -> prints the mode
14
+ # scripts/lib/kit-mode.sh --gate [--dir DIR] -> prints block|warn|off
15
+
16
+ _kit_mode_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
17
+ # shellcheck source=/dev/null
18
+ . "$_kit_mode_dir/kit-config-resolve.sh"
19
+ # shellcheck source=/dev/null
20
+ . "$_kit_mode_dir/kit-cli.sh" # kit_is_main
21
+
22
+ KIT_MODE_DEFAULT="${KIT_MODE_DEFAULT:-guided}"
23
+
24
+ # kit_mode [dir] -> draft|guided|enforced (default guided; KIT_MODE env overrides everything)
25
+ kit_mode() {
26
+ if [ -n "${KIT_MODE:-}" ]; then printf '%s\n' "$KIT_MODE"; return 0; fi
27
+ local m; m="$(kit_resolve_get '.mode' "${1:-$PWD}" 2>/dev/null)"
28
+ case "$m" in
29
+ draft|guided|enforced) printf '%s\n' "$m";;
30
+ *) printf '%s\n' "$KIT_MODE_DEFAULT";;
31
+ esac
32
+ }
33
+
34
+ # kit_gate [dir] -> block|warn|off (what a guardrail hook should do at this mode)
35
+ kit_gate() {
36
+ case "$(kit_mode "${1:-$PWD}")" in
37
+ enforced) printf 'block\n';;
38
+ draft) printf 'off\n';;
39
+ *) printf 'warn\n';; # guided
40
+ esac
41
+ }
42
+
43
+ # kit_gate_apply <message> [dir]
44
+ # The hook helper: emit + exit per the resolved gate. enforced -> stderr + exit 2 (blocks the
45
+ # tool call in Claude Code); guided -> stderr notice + exit 0; draft -> silent exit 0.
46
+ # Hooks call: kit_gate_apply "branch-naming: use <kind>/<N>-slug" (and let it set the exit code)
47
+ kit_gate_apply() {
48
+ local msg="$1" dir="${2:-$PWD}"
49
+ case "$(kit_gate "$dir")" in
50
+ block) printf 'kit[enforced]: %s\n' "$msg" >&2; return 2;;
51
+ warn) printf 'kit[guided]: %s\n' "$msg" >&2; return 0;;
52
+ off) return 0;;
53
+ esac
54
+ }
55
+
56
+ # CLI (direct execution only; zsh-safe guard)
57
+ # CLI (direct execution only)
58
+ if kit_is_main; then
59
+ _d="$PWD"; _gate=0
60
+ while [ $# -gt 0 ]; do case "$1" in
61
+ --dir) _d="$2"; shift 2;;
62
+ --gate) _gate=1; shift;;
63
+ -h|--help) echo "usage: kit-mode.sh [--dir DIR] [--gate]"; exit 0;;
64
+ *) echo "unknown arg: $1" >&2; exit 2;;
65
+ esac; done
66
+ [ "$_gate" = 1 ] && kit_gate "$_d" || kit_mode "$_d"
67
+ fi
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # kit-operate.sh — the single operation machine (D10, #368).
3
+ #
4
+ # Every kit mutation (init, wire, promote, migrate, remove) is the SAME four-beat machine:
5
+ # propose diff -> ask permission -> apply -> record in manifest
6
+ # Building it once means those five commands are configurations of one audited, idempotent,
7
+ # dry-runnable primitive — not five hand-rolled file-stompers. Honors KIT_DRY_RUN (preview only)
8
+ # and the conffiles rule from the manifest (never clobber user-modified files silently).
9
+ #
10
+ # Source it: source scripts/lib/kit-operate.sh (auto-sources kit-manifest.sh alongside)
11
+ # Env:
12
+ # KIT_DRY_RUN=1 show what would happen, write nothing
13
+ # KIT_ASSUME_YES=1 apply without the interactive confirm (CI / batch / Cowork)
14
+ # KIT_NO_COLOR / NO_COLOR plain output
15
+
16
+ _kit_op_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
17
+ # shellcheck source=/dev/null
18
+ . "$_kit_op_dir/kit-manifest.sh"
19
+
20
+ _kit_op_say() { printf '%s\n' "$*" >&2; }
21
+ _kit_op_dry() { [ -n "${KIT_DRY_RUN:-}" ]; }
22
+
23
+ # _kit_op_confirm <prompt> -> rc 0 yes / 1 no
24
+ _kit_op_confirm() {
25
+ [ -n "${KIT_ASSUME_YES:-}" ] && return 0
26
+ [ -n "${KIT_DRY_RUN:-}" ] && return 1
27
+ local reply; printf '%s [y/N] ' "$1" >&2
28
+ IFS= read -r reply || return 1
29
+ case "$reply" in y|Y|yes|YES) return 0;; *) return 1;; esac
30
+ }
31
+
32
+ # _kit_op_diff <src> <dest> — print a unified diff src->dest (best-effort, never fails the op)
33
+ _kit_op_diff() {
34
+ local src="$1" dest="$2"
35
+ if [ ! -f "$dest" ]; then _kit_op_say " + create $dest"; return 0; fi
36
+ if cmp -s "$src" "$dest"; then _kit_op_say " = unchanged $dest"; return 0; fi
37
+ _kit_op_say " ~ update $dest"
38
+ if command -v diff >/dev/null 2>&1; then diff -u "$dest" "$src" 2>/dev/null | sed 's/^/ /' >&2 || true; fi
39
+ }
40
+
41
+ # kit_op_write <src> <destrelpath> <tier> [op]
42
+ # Copy src -> dest with the four-beat machine. Respects conffiles: if dest is tracked and the user
43
+ # MODIFIED it, ask before overwriting even with KIT_ASSUME_YES.
44
+ # rc: 0 applied/created, 10 skipped (declined or dry-run), 1 error.
45
+ kit_op_write() {
46
+ local src="$1" dest="$2" tier="${3:-A}" op="${4:-wire}"
47
+ [ -f "$src" ] || { _kit_op_say "kit-operate: source missing: $src"; return 1; }
48
+
49
+ # conffiles guard: tracked + user-modified => never silent overwrite
50
+ local verdict; verdict="$(kit_manifest_verify "$dest" 2>/dev/null || true)"
51
+ _kit_op_say "propose: $dest (tier $tier, $op, current=$verdict)"
52
+ _kit_op_diff "$src" "$dest"
53
+
54
+ if cmp -s "$src" "$dest" 2>/dev/null; then
55
+ # content already identical — just (re)record, no write needed (idempotent)
56
+ _kit_op_dry || kit_manifest_record "$dest" "$tier" "$op" || return 1
57
+ return 0
58
+ fi
59
+
60
+ if [ "$verdict" = "modified" ]; then
61
+ if ! _kit_op_confirm " $dest was edited by you — overwrite?"; then
62
+ _kit_op_say " skip (kept your version): $dest"; return 10
63
+ fi
64
+ elif ! _kit_op_confirm " apply?"; then
65
+ _kit_op_say " skip: $dest"; return 10
66
+ fi
67
+
68
+ if _kit_op_dry; then _kit_op_say " [dry-run] would write $dest"; return 10; fi
69
+ mkdir -p "$(dirname "$dest")" || return 1
70
+ cp -- "$src" "$dest" || return 1
71
+ kit_manifest_record "$dest" "$tier" "$op" || return 1
72
+ _kit_op_say " applied: $dest"
73
+ }
74
+
75
+ # kit_op_write_content <destrelpath> <tier> <op> (content on stdin)
76
+ # Same machine but the source is piped — for generated files (KIT.md, shims).
77
+ kit_op_write_content() {
78
+ local dest="$1" tier="${2:-A}" op="${3:-wire}" tmp
79
+ tmp="$(mktemp)" || return 1
80
+ cat > "$tmp"
81
+ kit_op_write "$tmp" "$dest" "$tier" "$op"; local rc=$?
82
+ rm -f "$tmp"; return $rc
83
+ }
84
+
85
+ # kit_op_remove <destrelpath>
86
+ # Manifest-driven uninstall (D12 conffiles): intact->delete, modified->ask, missing->drop entry,
87
+ # untracked->refuse. rc: 0 removed/cleaned, 10 kept (declined/untracked), 1 error.
88
+ kit_op_remove() {
89
+ local dest="$1" verdict; verdict="$(kit_manifest_verify "$dest")"
90
+ case "$verdict" in
91
+ untracked) _kit_op_say "refuse: $dest is not kit-managed — not touching it"; return 10;;
92
+ missing) _kit_op_say "clean: $dest already gone, dropping manifest entry"
93
+ _kit_op_dry || kit_manifest_remove_entry "$dest"; return 0;;
94
+ modified) # Deleting a USER-EDITED file is NEVER silent — even under KIT_ASSUME_YES. Losing
95
+ # the user's work to an automated run is exactly what conffiles (D12) prevents.
96
+ if _kit_op_dry; then _kit_op_say " [dry-run] would ASK before deleting edited $dest"; return 10; fi
97
+ local _r=""; printf ' %s was edited by you — delete anyway? [y/N] ' "$dest" >&2
98
+ IFS= read -r _r </dev/tty 2>/dev/null || IFS= read -r _r 2>/dev/null || _r=""
99
+ case "$_r" in y|Y|yes|YES) ;; *) _kit_op_say " kept (your edits): $dest"; return 10;; esac;;
100
+ intact) : ;; # safe to remove
101
+ esac
102
+ if _kit_op_dry; then _kit_op_say " [dry-run] would remove $dest"; return 10; fi
103
+ rm -f -- "$dest" && kit_manifest_remove_entry "$dest" || return 1
104
+ _kit_op_say " removed: $dest"
105
+ }
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ # kit-profile-test.sh — self-test for kit-profile (#372). Runs under bash AND zsh.
3
+ # Run: bash scripts/lib/kit-profile-test.sh
4
+ dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
5
+
6
+ if [ -n "${KIT_PROFILE_TEST_INNER:-}" ]; then
7
+ set -u
8
+ . "$dir/kit-profile.sh"
9
+ fail=0
10
+ t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_PROFILE_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_PROFILE_TEST_INNER): $1"; fi; }
11
+ tt() { if [ "$2" = "$3" ]; then echo "FAIL($KIT_PROFILE_TEST_INNER): $1 -> '[$2]' should differ from '[$3]'"; fail=1; else echo "ok($KIT_PROFILE_TEST_INNER): $1"; fi; }
12
+
13
+ export KIT_PROFILE_HOME="$(mktemp -d)"; trap 'rm -rf "$KIT_PROFILE_HOME"' EXIT
14
+
15
+ # slug normalization (O8)
16
+ t "slug spaces+case" "$(KIT_PROFILE_USER='Jose Gutierrez' kit_profile_user_slug)" "jose-gutierrez"
17
+ t "slug explicit arg" "$(kit_profile_user_slug 'Ada Lovelace')" "ada-lovelace"
18
+ t "slug all-symbols -> user" "$(kit_profile_user_slug '!!!')" "user"
19
+
20
+ # path encodes the slug
21
+ case "$(kit_profile_path tester)" in
22
+ *"/kit.profile.tester.json") t "path encodes slug" yes yes;;
23
+ *) t "path encodes slug" "$(kit_profile_path tester)" "*/kit.profile.tester.json";;
24
+ esac
25
+
26
+ # exists is false before, true after init; init idempotent
27
+ kit_profile_exists tester; t "exists before" "$?" "1"
28
+ kit_profile_init tester; t "init rc" "$?" "0"
29
+ kit_profile_exists tester; t "exists after" "$?" "0"
30
+ h1="$(cat "$(kit_profile_path tester)")"; kit_profile_init tester; h2="$(cat "$(kit_profile_path tester)")"
31
+ t "init idempotent" "$h1" "$h2"
32
+
33
+ # set / get scalar + nested
34
+ kit_profile_set name "Jose" string tester
35
+ t "get name" "$(kit_profile_get .name tester)" "Jose"
36
+ kit_profile_set defaults.mode guided string tester
37
+ t "get nested mode" "$(kit_profile_get .defaults.mode tester)" "guided"
38
+ kit_profile_set defaults.memory_on true bool tester
39
+ t "get bool" "$(kit_profile_get .defaults.memory_on tester)" "true"
40
+
41
+ # read returns {} for an unknown user; get empty for unset path
42
+ t "read unknown" "$(kit_profile_read nobody)" "{}"
43
+ t "get unset empty" "$(kit_profile_get .nope tester)" ""
44
+
45
+ # the $PATH-tied 'path' var regression: a set must NOT break later jq calls (zsh)
46
+ kit_profile_set a.b.c "deep" string tester
47
+ t "deep set survives PATH" "$(kit_profile_get .a.b.c tester)" "deep"
48
+ t "jq still found after set" "$(command -v jq >/dev/null 2>&1 && echo ok)" "ok"
49
+
50
+ [ "$fail" -eq 0 ] && echo "ALL OK($KIT_PROFILE_TEST_INNER)"
51
+ exit "$fail"
52
+ fi
53
+
54
+ rc=0; ran=0
55
+ for sh in bash zsh; do
56
+ command -v "$sh" >/dev/null 2>&1 || continue
57
+ ran=$((ran+1)); echo "--- $sh ---"
58
+ rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
59
+ KIT_PROFILE_TEST_INNER="$sh" PATH="$PATH" "$sh" $rcflag "$0" || rc=1
60
+ done
61
+ [ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
62
+ exit "$rc"
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env bash
2
+ # kit-profile.sh — the global, per-USER profile (D7, O8, #372).
3
+ #
4
+ # THE MODEL: the wide-range interview runs ONCE in a user's life and writes a global profile at
5
+ # ~/.claude/kit.profile.<user>.json
6
+ # Multi-user by file (D7): the <user> slug names the file, so two humans on one machine never clash.
7
+ # This profile is the FAR end of the cascade — kit-config-resolve reads project layers on top of it,
8
+ # and the per-project interview (tier 2) pre-fills its answers FROM this profile. It lives in the
9
+ # user's home, OUTSIDE any project, so it is NOT manifest-tracked (the manifest is project-scoped).
10
+ #
11
+ # Source it: source scripts/lib/kit-profile.sh
12
+ # CLI: scripts/lib/kit-profile.sh [--user U] [--path | --user-slug | --show | --get JQPATH]
13
+ # Requires: jq.
14
+
15
+ _kit_profile_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
16
+ # shellcheck source=/dev/null
17
+ . "$_kit_profile_dir/kit-cli.sh" # kit_is_main, kit_say/warn/die
18
+
19
+ KIT_PROFILE_SCHEMA=1
20
+
21
+ _kit_profile_require() { command -v jq >/dev/null 2>&1 || { kit_warn "kit-profile: jq is required"; return 1; }; }
22
+
23
+ # kit_profile_slugify <text> -> lower-kebab slug (ASCII; drops non-alnum). Matches kit-task-start.
24
+ kit_profile_slugify() {
25
+ printf '%s' "$1" \
26
+ | tr '[:upper:]' '[:lower:]' \
27
+ | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g'
28
+ }
29
+
30
+ # kit_profile_user_slug [explicit] -> the <user> slug for the profile filename (O8).
31
+ # Source cascade: explicit arg > $KIT_PROFILE_USER > git config user.name > $USER/$LOGNAME > "user".
32
+ # Always normalized to a slug. The tier-1 interview can confirm/override before first write.
33
+ kit_profile_user_slug() {
34
+ local raw="${1:-${KIT_PROFILE_USER:-}}"
35
+ if [ -z "$raw" ]; then raw="$(git config user.name 2>/dev/null || true)"; fi
36
+ if [ -z "$raw" ]; then raw="${USER:-${LOGNAME:-}}"; fi
37
+ [ -z "$raw" ] && raw="user"
38
+ local slug; slug="$(kit_profile_slugify "$raw")"
39
+ [ -z "$slug" ] && slug="user"
40
+ printf '%s\n' "$slug"
41
+ }
42
+
43
+ # kit_profile_home -> the dir that holds profiles. Override with KIT_PROFILE_HOME (tests use this).
44
+ kit_profile_home() { printf '%s\n' "${KIT_PROFILE_HOME:-$HOME/.claude}"; }
45
+
46
+ # kit_profile_path [user] -> absolute path of the user's global profile JSON.
47
+ kit_profile_path() {
48
+ local u; u="$(kit_profile_user_slug "${1:-}")"
49
+ printf '%s/kit.profile.%s.json\n' "$(kit_profile_home)" "$u"
50
+ }
51
+
52
+ # kit_profile_exists [user] -> rc 0 if the profile file is present (drives "once in a lifetime").
53
+ kit_profile_exists() { [ -f "$(kit_profile_path "${1:-}")" ]; }
54
+
55
+ # kit_profile_init [user] -> create an empty, schema'd profile if none exists. Idempotent.
56
+ kit_profile_init() {
57
+ _kit_profile_require || return 1
58
+ local p; p="$(kit_profile_path "${1:-}")"
59
+ [ -f "$p" ] && return 0
60
+ mkdir -p "$(dirname "$p")" || return 1
61
+ local u; u="$(kit_profile_user_slug "${1:-}")"
62
+ jq -n --argjson s "$KIT_PROFILE_SCHEMA" --arg u "$u" --arg v "${KIT_VERSION:-0.0.0}" \
63
+ '{schema:$s, user:$u, defaults:{}, createdWith:$v}' > "$p"
64
+ }
65
+
66
+ # kit_profile_read [user] -> the profile JSON ({} if absent)
67
+ kit_profile_read() {
68
+ _kit_profile_require || return 1
69
+ local p; p="$(kit_profile_path "${1:-}")"
70
+ if [ -f "$p" ]; then cat "$p"; else printf '{}\n'; fi
71
+ }
72
+
73
+ # kit_profile_get <jqpath> [user] -> a resolved value (raw, empty if unset)
74
+ kit_profile_get() {
75
+ _kit_profile_require || return 1
76
+ kit_profile_read "${2:-}" | jq -r "$1 // empty"
77
+ }
78
+
79
+ # kit_profile_set <dotpath> <value> [valtype string|bool|number] [user]
80
+ # Merge a single value into the profile (deep, by path). Creates the profile if missing.
81
+ kit_profile_set() {
82
+ _kit_profile_require || return 1
83
+ local dotpath="$1" value="$2" valtype="${3:-string}" user="${4:-}"
84
+ [ -n "$dotpath" ] || { kit_warn "kit-profile set: path required"; return 1; }
85
+ kit_profile_init "$user" || return 1
86
+ local p tmp; p="$(kit_profile_path "$user")"; tmp="$(mktemp)" || return 1
87
+ # "a.b.c" -> a jq setpath array ["a","b","c"] (var named dotpath: `path` is $PATH-tied in zsh)
88
+ local setexpr
89
+ setexpr="$(printf '%s' "$dotpath" | awk -F. '{out="["; for(i=1;i<=NF;i++){ if(i>1)out=out","; out=out"\""$i"\""}; out=out"]"; print out}')"
90
+ case "$valtype" in
91
+ bool|boolean|number|int) jq --argjson v "$value" "setpath($setexpr; \$v)" "$p" > "$tmp" || { rm -f "$tmp"; return 1; };;
92
+ *) jq --arg v "$value" "setpath($setexpr; \$v)" "$p" > "$tmp" || { rm -f "$tmp"; return 1; };;
93
+ esac
94
+ mv "$tmp" "$p"
95
+ }
96
+
97
+ # CLI — direct execution only.
98
+ if kit_is_main; then
99
+ _u=""; _mode="path"; _arg=""
100
+ while [ $# -gt 0 ]; do case "$1" in
101
+ --user) _u="$2"; shift 2;;
102
+ --path) _mode="path"; shift;;
103
+ --user-slug) _mode="slug"; shift;;
104
+ --show) _mode="show"; shift;;
105
+ --get) _mode="get"; _arg="$2"; shift 2;;
106
+ -h|--help) echo "usage: kit-profile.sh [--user U] [--path|--user-slug|--show|--get JQPATH]"; exit 0;;
107
+ *) kit_warn "unknown arg: $1"; exit 2;;
108
+ esac; done
109
+ case "$_mode" in
110
+ path) kit_profile_path "$_u";;
111
+ slug) kit_profile_user_slug "$_u";;
112
+ show) kit_profile_read "$_u";;
113
+ get) kit_profile_get "$_arg" "$_u";;
114
+ esac
115
+ fi
@@ -0,0 +1,63 @@
1
+ #!/bin/sh
2
+ # kit-task-ops-test.sh — self-test for kit-task-ops.sh pure helpers under bash AND zsh.
3
+ # Network-free: covers the parsers + body composers only (the gh-calling ops are not exercised).
4
+ # Run: bash scripts/lib/kit-task-ops-test.sh
5
+
6
+ dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
7
+
8
+ if [ -n "${KTO_TEST_INNER:-}" ]; then
9
+ # role_signature is a no-op stub so body composers run without sourcing role-identity.
10
+ role_signature() { :; }
11
+ role_display() { :; }
12
+ . "$dir/kit-task-ops.sh"
13
+ fail=0
14
+
15
+ eq() { # <label> <got> <want>
16
+ if [ "$2" != "$3" ]; then
17
+ echo "FAIL($KTO_TEST_INNER): $1 -> '[$2]', want '[$3]'"
18
+ fail=1
19
+ fi
20
+ }
21
+
22
+ # kto_branch_issue_number
23
+ eq "branch feat/173-x" "$(kto_branch_issue_number 'feat/173-admin-clerk')" "173"
24
+ eq "branch fix/46-y" "$(kto_branch_issue_number 'fix/46-roadmap')" "46"
25
+ eq "branch develop" "$(kto_branch_issue_number 'develop')" ""
26
+ eq "branch effort/851-z" "$(kto_branch_issue_number 'effort/851-tail')" "851"
27
+
28
+ # kto_base_branch / kto_board_enabled (portability toggles)
29
+ eq "base default" "$(kto_base_branch)" "develop"
30
+ eq "base override" "$(KIT_BASE_BRANCH=main; kto_base_branch)" "main"
31
+ if kto_board_enabled; then eq "board default on" "on" "on"; else eq "board default on" "off" "on"; fi
32
+ ( KIT_PROJECTS_V2=false; if kto_board_enabled; then exit 0; else exit 1; fi ) \
33
+ && eq "board off" "on" "off" || eq "board off" "off" "off"
34
+
35
+ # kto_labels_role / kto_labels_kind
36
+ eq "role from labels" "$(kto_labels_role 'kind:feat,priority:p2,role:tech-lead')" "tech-lead"
37
+ eq "kind from labels" "$(kto_labels_kind 'kind:feat,priority:p2,role:tech-lead')" "feat"
38
+ eq "role absent" "$(kto_labels_role 'kind:chore,priority:p3')" ""
39
+
40
+ # kto_compose_pr_body must carry Closes #N and the Summary
41
+ body="$(kto_compose_pr_body 42 'Did the thing.' 'tech-lead')"
42
+ case "$body" in *"Closes #42"*) ;; *) echo "FAIL($KTO_TEST_INNER): pr body missing Closes #42"; fail=1 ;; esac
43
+ case "$body" in *"Did the thing."*) ;; *) echo "FAIL($KTO_TEST_INNER): pr body missing summary"; fail=1 ;; esac
44
+
45
+ # kto_compose_issue_body must include the user body, and the Plan + Blocked lines when given
46
+ ib="$(kto_compose_issue_body 'Context here' 'apps/admin/p.mdx' '#9' 'frontend')"
47
+ case "$ib" in *"Context here"*) ;; *) echo "FAIL($KTO_TEST_INNER): issue body missing user body"; fail=1 ;; esac
48
+ case "$ib" in *"**Plan:**"*) ;; *) echo "FAIL($KTO_TEST_INNER): issue body missing Plan line"; fail=1 ;; esac
49
+ case "$ib" in *"**Blocked by:** #9"*) ;; *) echo "FAIL($KTO_TEST_INNER): issue body missing Blocked line"; fail=1 ;; esac
50
+ ib2="$(kto_compose_issue_body 'Only body' '' '' 'frontend')"
51
+ case "$ib2" in *"**Plan:**"*) echo "FAIL($KTO_TEST_INNER): issue body has Plan line when none given"; fail=1 ;; *) ;; esac
52
+
53
+ if [ "$fail" -eq 0 ]; then echo "PASS($KTO_TEST_INNER): kit-task-ops helpers"; fi
54
+ exit "$fail"
55
+ fi
56
+
57
+ # Outer: re-run under each available shell.
58
+ rc=0
59
+ for sh in bash zsh; do
60
+ command -v "$sh" >/dev/null 2>&1 || continue
61
+ KTO_TEST_INNER="$sh" "$sh" "$0" || rc=1
62
+ done
63
+ exit "$rc"