@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.
- package/.claude-plugin/plugin.json +22 -0
- package/AGENTS.md +101 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +143 -0
- package/SECURITY.md +22 -0
- package/bin/cckit +215 -0
- package/cckit.config.json +34 -0
- package/commands/kit-add.md +42 -0
- package/commands/kit-docs.md +45 -0
- package/commands/kit-doctor.md +52 -0
- package/commands/kit-export-project.md +58 -0
- package/commands/kit-export-training.md +49 -0
- package/commands/kit-init.md +126 -0
- package/commands/kit-routines.md +59 -0
- package/commands/kit-update.md +132 -0
- package/docs/kit-annotate/01-explainer.html +225 -0
- package/docs/kit-annotate/02-implementation-plan.html +196 -0
- package/docs/media/.onboarding-capture.cast +5 -0
- package/docs/media/README.md +43 -0
- package/docs/media/build-demo.sh +63 -0
- package/docs/media/build-kit-init.sh +51 -0
- package/docs/media/build-onboarding.sh +51 -0
- package/docs/media/kit-dry-run.cast +107 -0
- package/docs/media/kit-dry-run.gif +0 -0
- package/docs/media/kit-init.cast +56 -0
- package/docs/media/kit-init.gif +0 -0
- package/docs/media/kit-onboarding.cast +148 -0
- package/docs/media/kit-onboarding.gif +0 -0
- package/githooks/pre-commit +18 -0
- package/kit.config.schema.json +105 -0
- package/package.json +54 -0
- package/privacy-denylist.example +8 -0
- package/profiles/automation.json +36 -0
- package/profiles/content.json +41 -0
- package/profiles/minimal.json +31 -0
- package/profiles/research.json +37 -0
- package/profiles/software.json +32 -0
- package/scripts/annotate-setup.sh +149 -0
- package/scripts/autopilot.sh +50 -0
- package/scripts/capture-project-ids.sh +53 -0
- package/scripts/check.sh +66 -0
- package/scripts/contribute.sh +48 -0
- package/scripts/debug.sh +54 -0
- package/scripts/init-upgrade-test.sh +99 -0
- package/scripts/init.sh +827 -0
- package/scripts/install.sh +24 -0
- package/scripts/kit-add-test.sh +62 -0
- package/scripts/kit-add.sh +115 -0
- package/scripts/kit-adopt-test.sh +61 -0
- package/scripts/kit-adopt.sh +122 -0
- package/scripts/kit-bump-version.sh +79 -0
- package/scripts/kit-digest.sh +126 -0
- package/scripts/kit-doctor.sh +663 -0
- package/scripts/kit-export-project-test.sh +82 -0
- package/scripts/kit-export-project.sh +245 -0
- package/scripts/kit-export-training-test.sh +51 -0
- package/scripts/kit-export-training.sh +175 -0
- package/scripts/kit-migrate-test.sh +80 -0
- package/scripts/kit-migrate.sh +190 -0
- package/scripts/kit-onboard-test.sh +63 -0
- package/scripts/kit-onboard.sh +69 -0
- package/scripts/kit-promote-test.sh +54 -0
- package/scripts/kit-promote.sh +102 -0
- package/scripts/kit-remove-test.sh +61 -0
- package/scripts/kit-remove.sh +84 -0
- package/scripts/kit-routines.sh +322 -0
- package/scripts/kit-version-check.sh +91 -0
- package/scripts/kit-wire-test.sh +54 -0
- package/scripts/kit-wire.sh +132 -0
- package/scripts/knowledge-lint.sh +96 -0
- package/scripts/lib/cckit-output.sh +36 -0
- package/scripts/lib/effort-metrics.sh +452 -0
- package/scripts/lib/effort-ops-test.sh +83 -0
- package/scripts/lib/effort-ops.sh +132 -0
- package/scripts/lib/effort-plan.sh +104 -0
- package/scripts/lib/effort.sh +191 -0
- package/scripts/lib/engine-adapter.sh +92 -0
- package/scripts/lib/gh-log.sh +58 -0
- package/scripts/lib/gh-project.sh +212 -0
- package/scripts/lib/handoff.sh +35 -0
- package/scripts/lib/kit-cli-test.sh +42 -0
- package/scripts/lib/kit-cli.sh +32 -0
- package/scripts/lib/kit-config-resolve.sh +145 -0
- package/scripts/lib/kit-config.sh +88 -0
- package/scripts/lib/kit-engine-test.sh +107 -0
- package/scripts/lib/kit-events.sh +62 -0
- package/scripts/lib/kit-gc.sh +117 -0
- package/scripts/lib/kit-interview-test.sh +77 -0
- package/scripts/lib/kit-interview.sh +203 -0
- package/scripts/lib/kit-local.sh +79 -0
- package/scripts/lib/kit-manifest.sh +127 -0
- package/scripts/lib/kit-mode-test.sh +49 -0
- package/scripts/lib/kit-mode.sh +67 -0
- package/scripts/lib/kit-operate.sh +105 -0
- package/scripts/lib/kit-profile-test.sh +62 -0
- package/scripts/lib/kit-profile.sh +115 -0
- package/scripts/lib/kit-task-ops-test.sh +63 -0
- package/scripts/lib/kit-task-ops.sh +341 -0
- package/scripts/lib/pr-evidence.sh +173 -0
- package/scripts/lib/project-scan.sh +16 -0
- package/scripts/lib/react-detect.sh +78 -0
- package/scripts/lib/role-identity.sh +47 -0
- package/scripts/lib/secret-guard.sh +96 -0
- package/scripts/lib/toon.sh +35 -0
- package/scripts/lib/ui.sh +42 -0
- package/scripts/lib/version-bump.sh +59 -0
- package/scripts/lib/worktree-issue-test.sh +45 -0
- package/scripts/lib/worktree-issue.sh +73 -0
- package/scripts/lib/worktree-start.sh +280 -0
- package/scripts/orchestrate.sh +160 -0
- package/scripts/portable-test.sh +53 -0
- package/scripts/publish.sh +94 -0
- package/scripts/setup-labels.sh +25 -0
- package/scripts/setup-milestones.sh +17 -0
- package/scripts/showcase.sh +64 -0
- package/scripts/status.sh +44 -0
- package/scripts/task-sync.sh +59 -0
- package/scripts/test.sh +48 -0
- package/scripts/web-install.sh +22 -0
- package/skills/kit-annotate/SKILL.md +107 -0
- package/skills/kit-autopilot/SKILL.md +108 -0
- package/skills/kit-contribute/SKILL.md +134 -0
- package/skills/kit-customize/SKILL.md +134 -0
- package/skills/kit-dev/SKILL.md +67 -0
- package/skills/kit-digest/SKILL.md +41 -0
- package/skills/kit-effort-close/SKILL.md +156 -0
- package/skills/kit-effort-new/SKILL.md +173 -0
- package/skills/kit-effort-pr/SKILL.md +139 -0
- package/skills/kit-effort-start/SKILL.md +85 -0
- package/skills/kit-gc/SKILL.md +80 -0
- package/skills/kit-onboard/SKILL.md +50 -0
- package/skills/kit-security-sweep/SKILL.md +57 -0
- package/skills/kit-ship/SKILL.md +43 -0
- package/skills/kit-task-close/SKILL.md +66 -0
- package/skills/kit-task-new/SKILL.md +51 -0
- package/skills/kit-task-pr/SKILL.md +43 -0
- package/skills/kit-task-pr-auto/SKILL.md +27 -0
- package/skills/kit-task-pr-merge/SKILL.md +53 -0
- package/skills/kit-task-start/SKILL.md +76 -0
- package/skills/kit-task-sync/SKILL.md +37 -0
- package/templates/CLAUDE.md.tmpl +106 -0
- package/templates/agents/analyst.md +55 -0
- package/templates/agents/auto-dev.md +93 -0
- package/templates/agents/backend.md +59 -0
- package/templates/agents/designer.md +73 -0
- package/templates/agents/devops.md +57 -0
- package/templates/agents/editor.md +48 -0
- package/templates/agents/frontend.md +81 -0
- package/templates/agents/generalist.md +46 -0
- package/templates/agents/local-delegate.md +70 -0
- package/templates/agents/n8n.md +65 -0
- package/templates/agents/pm.md +69 -0
- package/templates/agents/qa.md +66 -0
- package/templates/agents/researcher.md +57 -0
- package/templates/agents/security.md +65 -0
- package/templates/agents/tech-lead.md +75 -0
- package/templates/hooks/guard-base-branch-commit.sh.tmpl +45 -0
- package/templates/hooks/kit-local-status.sh.tmpl +34 -0
- package/templates/hooks/kit_version_check.sh.tmpl +6 -0
- package/templates/hooks/mempal_followup.sh.tmpl +97 -0
- package/templates/hooks/mempal_precompact.sh.tmpl +4 -0
- package/templates/hooks/mempal_save.sh.tmpl +4 -0
- package/templates/hooks/mempal_session_start.sh.tmpl +8 -0
- package/templates/hooks/prepush_gate.sh.tmpl +36 -0
- package/templates/hooks/repo-hygiene.sh.tmpl +72 -0
- package/templates/kit.config.json.tmpl +32 -0
- package/templates/knowledge-INDEX.md.tmpl +12 -0
- package/templates/lib/kit-sigil.sh.tmpl +124 -0
- package/templates/rules/branch-naming.md +104 -0
- package/templates/rules/communication-style.md +22 -0
- package/templates/rules/delegation-brief.md +40 -0
- package/templates/rules/design-routing.md +35 -0
- package/templates/rules/effort-model.md +122 -0
- package/templates/rules/knowledge-base.md +41 -0
- package/templates/rules/mempalace.md +110 -0
- package/templates/rules/plan-output-format.md +58 -0
- package/templates/rules/react-annotate.md +69 -0
- package/templates/rules/risk-tiered-review.md +62 -0
- package/templates/rules/skill-gaps.md +48 -0
- package/templates/rules/task-management.md +42 -0
- package/templates/settings/settings.local.json.tmpl +27 -0
- package/templates/skills/NAMESPACED +13 -0
- package/templates/skills/copywriting/SKILL.md +252 -0
- package/templates/skills/copywriting/references/copy-frameworks.md +344 -0
- package/templates/skills/copywriting/references/natural-transitions.md +272 -0
- package/templates/skills/feature-build-refine/SKILL.md +367 -0
- package/templates/skills/karpathy-guidelines/SKILL.md +69 -0
- package/templates/skills/morning-briefing/SKILL.md +46 -0
- package/templates/skills/speckit/SKILL.md +239 -0
- 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"
|