@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,58 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# gh-log.sh — log every GitHub API call the kit makes + estimate its secondary
|
|
3
|
+
# rate-limit POINT cost, so throttling is diagnosable instead of mysterious.
|
|
4
|
+
#
|
|
5
|
+
# GitHub secondary rate limit — point model (source, 2026-03-10 API version):
|
|
6
|
+
# https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api#calculating-points-for-the-secondary-rate-limit
|
|
7
|
+
# GraphQL query (no mutation) = 1 point · GraphQL with mutation = 5 points
|
|
8
|
+
# REST GET/HEAD/OPTIONS = 1 point · REST POST/PATCH/PUT/DELETE = 5 points
|
|
9
|
+
# Ceilings: 900 points/min (REST) · 2,000 points/min (GraphQL) · 100 concurrent
|
|
10
|
+
# (shared) · 90s CPU / 60s real (≤60s GraphQL) · 80 content-gen/min, 500/hour.
|
|
11
|
+
#
|
|
12
|
+
# Log: one JSON line per call appended to <git-common-dir>/gh-requests.jsonl
|
|
13
|
+
# {ts, kind:"graphql|rest", op:"query|mutation|GET|POST|…", label, points, surface, pid}
|
|
14
|
+
# Disable with KIT_GH_LOG=0. Never fails a caller (logging is best-effort).
|
|
15
|
+
|
|
16
|
+
GH_LOG_FILE="${GH_LOG_FILE:-$(git rev-parse --git-common-dir 2>/dev/null)/gh-requests.jsonl}"
|
|
17
|
+
|
|
18
|
+
# gh_points <kind> <op> -> integer point cost (the doc's model).
|
|
19
|
+
gh_points() {
|
|
20
|
+
case "${1}:${2}" in
|
|
21
|
+
graphql:mutation) echo 5 ;;
|
|
22
|
+
graphql:*) echo 1 ;;
|
|
23
|
+
rest:POST | rest:PATCH | rest:PUT | rest:DELETE) echo 5 ;;
|
|
24
|
+
rest:GET | rest:HEAD | rest:OPTIONS) echo 1 ;;
|
|
25
|
+
*) echo 1 ;;
|
|
26
|
+
esac
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# gh_log <kind> <op> <label> [surface]
|
|
30
|
+
# kind = graphql|rest · op = query|mutation|GET|POST|… · label = human tag
|
|
31
|
+
# surface = the caller (e.g. kit-task-sync, gh-project); defaults to ${KIT_GH_SURFACE:-kit}
|
|
32
|
+
gh_log() {
|
|
33
|
+
[[ "${KIT_GH_LOG:-1}" == "0" ]] && return 0
|
|
34
|
+
local kind="$1" op="$2" label="$3" surface="${4:-${KIT_GH_SURFACE:-kit}}"
|
|
35
|
+
local pts ts
|
|
36
|
+
pts=$(gh_points "$kind" "$op")
|
|
37
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null) || ts=""
|
|
38
|
+
printf '{"ts":"%s","kind":"%s","op":"%s","label":"%s","points":%s,"surface":"%s","pid":%s}\n' \
|
|
39
|
+
"$ts" "$kind" "$op" "$label" "$pts" "$surface" "$$" >>"$GH_LOG_FILE" 2>/dev/null || true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# gh_log_summary [seconds] — points spent in the last N seconds (default 60),
|
|
43
|
+
# split by kind, against the doc's per-minute ceilings. Read-only; for `kit gh-log`.
|
|
44
|
+
gh_log_summary() {
|
|
45
|
+
local window="${1:-60}"
|
|
46
|
+
[[ -f "$GH_LOG_FILE" ]] || { echo "no gh request log yet ($GH_LOG_FILE)"; return 0; }
|
|
47
|
+
command -v jq >/dev/null 2>&1 || { echo "jq required for the summary"; return 1; }
|
|
48
|
+
local since
|
|
49
|
+
since=$(date -u -v-"${window}"S +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|
|
50
|
+
|| date -u -d "-${window} seconds" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")
|
|
51
|
+
jq -rs --arg since "$since" '
|
|
52
|
+
map(select($since == "" or .ts >= $since))
|
|
53
|
+
| { calls: length,
|
|
54
|
+
graphql_points: (map(select(.kind=="graphql") | .points) | add // 0),
|
|
55
|
+
rest_points: (map(select(.kind=="rest") | .points) | add // 0) }
|
|
56
|
+
| "last '"$window"'s — calls \(.calls) · GraphQL \(.graphql_points)/2000 pts · REST \(.rest_points)/900 pts"
|
|
57
|
+
' "$GH_LOG_FILE"
|
|
58
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Helpers for GitHub Projects v2 via gh CLI + GraphQL.
|
|
3
|
+
# Source after kit-config.sh: source scripts/lib/gh-project.sh; load_project_ids
|
|
4
|
+
# Requires scripts/.project-ids.env — populate via scripts/capture-project-ids.sh.
|
|
5
|
+
|
|
6
|
+
# Locate scripts/ portably when sourced (#313): BASH_SOURCE is bash-only — empty in
|
|
7
|
+
# zsh, where dirname "" resolved to CWD and the env was sought in the wrong dir.
|
|
8
|
+
# zsh exposes the sourced file via the %x prompt escape; eval keeps bash from parsing it.
|
|
9
|
+
if [ -n "${BASH_SOURCE:-}" ]; then
|
|
10
|
+
_ghp_self="$BASH_SOURCE"
|
|
11
|
+
elif [ -n "${ZSH_VERSION:-}" ]; then
|
|
12
|
+
eval '_ghp_self="${(%):-%x}"'
|
|
13
|
+
else
|
|
14
|
+
_ghp_self="$0"
|
|
15
|
+
fi
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "$_ghp_self")/.." && pwd)"
|
|
17
|
+
unset _ghp_self
|
|
18
|
+
|
|
19
|
+
# GitHub request log (best-effort) — point model + ceilings in scripts/lib/gh-log.sh.
|
|
20
|
+
if [ -f "$SCRIPT_DIR/lib/gh-log.sh" ]; then
|
|
21
|
+
# shellcheck source=gh-log.sh
|
|
22
|
+
. "$SCRIPT_DIR/lib/gh-log.sh" 2>/dev/null || true
|
|
23
|
+
fi
|
|
24
|
+
type gh_log >/dev/null 2>&1 || gh_log() { :; }
|
|
25
|
+
|
|
26
|
+
# Worktree-durable location for the captured project IDs (#532). The legacy
|
|
27
|
+
# scripts/.project-ids.env is gitignored + untracked, so it is ABSENT in every worktree —
|
|
28
|
+
# load_project_ids then no-ops and board updates silently fail (root cause of board drift).
|
|
29
|
+
# Store under the shared git-common-dir: one copy the main checkout AND all worktrees see,
|
|
30
|
+
# and it survives `git worktree prune`.
|
|
31
|
+
_ghp_shared_env() {
|
|
32
|
+
local common
|
|
33
|
+
common="$(git rev-parse --git-common-dir 2>/dev/null)" || return 1
|
|
34
|
+
case "$common" in /*) : ;; *) common="$(cd "$common" 2>/dev/null && pwd)" || return 1 ;; esac
|
|
35
|
+
printf '%s/kit-project-ids.env' "$common"
|
|
36
|
+
}
|
|
37
|
+
PROJECT_ENV="$(_ghp_shared_env || printf '%s/.project-ids.env' "$SCRIPT_DIR")"
|
|
38
|
+
PROJECT_ENV_LEGACY="$SCRIPT_DIR/.project-ids.env" # pre-#532 per-checkout path
|
|
39
|
+
|
|
40
|
+
load_project_ids() {
|
|
41
|
+
# First run in a checkout that still has the legacy file: migrate it to the shared path.
|
|
42
|
+
if [[ ! -f "$PROJECT_ENV" && -f "$PROJECT_ENV_LEGACY" ]]; then
|
|
43
|
+
cp "$PROJECT_ENV_LEGACY" "$PROJECT_ENV" 2>/dev/null || PROJECT_ENV="$PROJECT_ENV_LEGACY"
|
|
44
|
+
fi
|
|
45
|
+
if [[ ! -f "$PROJECT_ENV" ]]; then
|
|
46
|
+
echo "✗ project IDs not found ($PROJECT_ENV). Run: ./scripts/capture-project-ids.sh first." >&2
|
|
47
|
+
return 1
|
|
48
|
+
fi
|
|
49
|
+
# shellcheck disable=SC1090
|
|
50
|
+
source "$PROJECT_ENV"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Add an issue/PR (by node ID) to the project. Echoes the new item ID.
|
|
54
|
+
project_add_item() {
|
|
55
|
+
gh_log graphql mutation "project_add_item" "gh-project"
|
|
56
|
+
gh api graphql \
|
|
57
|
+
-f query='mutation($project:ID!, $content:ID!) {
|
|
58
|
+
addProjectV2ItemById(input:{projectId:$project, contentId:$content}){ item { id } }
|
|
59
|
+
}' \
|
|
60
|
+
-f project="$PROJECT_ID" -f content="$1" \
|
|
61
|
+
--jq '.data.addProjectV2ItemById.item.id'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Set a single-select field. Args: <item_id> <field_id> <option_id>
|
|
65
|
+
project_set_single_select() {
|
|
66
|
+
gh_log graphql mutation "project_set_single_select" "gh-project"
|
|
67
|
+
gh api graphql \
|
|
68
|
+
-f query='mutation($project:ID!, $item:ID!, $field:ID!, $option:String!) {
|
|
69
|
+
updateProjectV2ItemFieldValue(input:{
|
|
70
|
+
projectId:$project, itemId:$item, fieldId:$field,
|
|
71
|
+
value:{ singleSelectOptionId:$option }
|
|
72
|
+
}){ projectV2Item { id } }
|
|
73
|
+
}' \
|
|
74
|
+
-f project="$PROJECT_ID" -f item="$1" -f field="$2" -f option="$3" >/dev/null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Set a text field. Args: <item_id> <field_id> <text>
|
|
78
|
+
project_set_text() {
|
|
79
|
+
gh_log graphql mutation "project_set_text" "gh-project"
|
|
80
|
+
gh api graphql \
|
|
81
|
+
-f query='mutation($project:ID!, $item:ID!, $field:ID!, $text:String!) {
|
|
82
|
+
updateProjectV2ItemFieldValue(input:{
|
|
83
|
+
projectId:$project, itemId:$item, fieldId:$field, value:{ text:$text }
|
|
84
|
+
}){ projectV2Item { id } }
|
|
85
|
+
}' \
|
|
86
|
+
-f project="$PROJECT_ID" -f item="$1" -f field="$2" -f text="$3" >/dev/null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Resolve an option ID from a human name via explicit case lookup (#307).
|
|
90
|
+
# Names are normalized: uppercased, spaces/hyphens -> underscores.
|
|
91
|
+
# role_option_id "Tech Lead" -> $ROLE_OPT_TECH_LEAD
|
|
92
|
+
# status_option_id "In Review" -> $STATUS_OPT_IN_REVIEW
|
|
93
|
+
# No ${!var} indirection: these libs are sourced into zsh sessions too, where
|
|
94
|
+
# bash-only indirection dies with "bad substitution". Unknown names echo empty.
|
|
95
|
+
_norm() { echo "$1" | tr '[:lower:]' '[:upper:]' | tr ' -' '__'; }
|
|
96
|
+
role_option_id() {
|
|
97
|
+
case "$(_norm "$1")" in
|
|
98
|
+
DESIGNER) echo "${ROLE_OPT_DESIGNER:-}" ;;
|
|
99
|
+
DEVOPS) echo "${ROLE_OPT_DEVOPS:-}" ;;
|
|
100
|
+
PM) echo "${ROLE_OPT_PM:-}" ;;
|
|
101
|
+
FRONTEND) echo "${ROLE_OPT_FRONTEND:-}" ;;
|
|
102
|
+
TAURI) echo "${ROLE_OPT_TAURI:-}" ;;
|
|
103
|
+
AI_ENG) echo "${ROLE_OPT_AI_ENG:-}" ;;
|
|
104
|
+
SECURITY) echo "${ROLE_OPT_SECURITY:-}" ;;
|
|
105
|
+
QA) echo "${ROLE_OPT_QA:-}" ;;
|
|
106
|
+
TECH_LEAD) echo "${ROLE_OPT_TECH_LEAD:-}" ;;
|
|
107
|
+
RESEARCH) echo "${ROLE_OPT_RESEARCH:-}" ;;
|
|
108
|
+
*) echo "" ;;
|
|
109
|
+
esac
|
|
110
|
+
}
|
|
111
|
+
status_option_id() {
|
|
112
|
+
case "$(_norm "$1")" in
|
|
113
|
+
PAUSED) echo "${STATUS_OPT_PAUSED:-}" ;;
|
|
114
|
+
TODO) echo "${STATUS_OPT_TODO:-}" ;;
|
|
115
|
+
IN_PROGRESS) echo "${STATUS_OPT_IN_PROGRESS:-}" ;;
|
|
116
|
+
DONE) echo "${STATUS_OPT_DONE:-}" ;;
|
|
117
|
+
BLOCKED) echo "${STATUS_OPT_BLOCKED:-}" ;;
|
|
118
|
+
IN_REVIEW) echo "${STATUS_OPT_IN_REVIEW:-}" ;;
|
|
119
|
+
*) echo "" ;;
|
|
120
|
+
esac
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Resolve the project owner + number even when KIT_OWNER/KIT_PROJECT_NUMBER aren't exported.
|
|
124
|
+
# THE board-Done bug (#536): skills source `gh-project.sh; load_project_ids` but NOT kit-config.sh,
|
|
125
|
+
# and load_project_ids only sets PROJECT_ID + field/option IDs — never KIT_OWNER/KIT_PROJECT_NUMBER.
|
|
126
|
+
# The finder then ran the GraphQL with empty $o/$n, GitHub rejected the query, jq iterated null, and
|
|
127
|
+
# the function returned EMPTY silently — so every board-Done update for a real issue no-op'd (looked
|
|
128
|
+
# like "issue not on board"). Fix: resolve owner/number from kit.config.json here, and fail LOUDLY on
|
|
129
|
+
# an unresolved owner or a GraphQL error instead of returning empty.
|
|
130
|
+
_ghp_resolve_owner() {
|
|
131
|
+
[[ -n "${KIT_OWNER:-}" ]] && { printf '%s' "$KIT_OWNER"; return 0; }
|
|
132
|
+
local cfg="${KIT_CONFIG:-$SCRIPT_DIR/../.claude/kit.config.json}"
|
|
133
|
+
[[ -f "$cfg" ]] && jq -r '.github.owner // empty' "$cfg" 2>/dev/null
|
|
134
|
+
}
|
|
135
|
+
_ghp_resolve_pnum() {
|
|
136
|
+
[[ -n "${KIT_PROJECT_NUMBER:-}" ]] && { printf '%s' "$KIT_PROJECT_NUMBER"; return 0; }
|
|
137
|
+
local cfg="${KIT_CONFIG:-$SCRIPT_DIR/../.claude/kit.config.json}"
|
|
138
|
+
[[ -f "$cfg" ]] && jq -r '.github.projectNumber // empty' "$cfg" 2>/dev/null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Resolve the project OWNER TYPE — "organization" or "user" (default "user").
|
|
142
|
+
# The board moved from a user project (jeiemgi #2) to an org project
|
|
143
|
+
# (tuempresadigital #3). An org-owned board is what lets a single issue see its
|
|
144
|
+
# own card via the CHEAP issue.projectItems lookup (project_find_item_by_issue),
|
|
145
|
+
# so every board GraphQL query must branch on this: organization(login:) vs user(login:).
|
|
146
|
+
_ghp_resolve_owner_type() {
|
|
147
|
+
[[ -n "${KIT_PROJECT_OWNER_TYPE:-}" ]] && { printf '%s' "$KIT_PROJECT_OWNER_TYPE"; return 0; }
|
|
148
|
+
local cfg="${KIT_CONFIG:-$SCRIPT_DIR/../.claude/kit.config.json}" t=""
|
|
149
|
+
[[ -f "$cfg" ]] && t="$(jq -r '.github.projectOwnerType // empty' "$cfg" 2>/dev/null)"
|
|
150
|
+
printf '%s' "${t:-user}"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Echo the GraphQL root selector for the resolved owner type:
|
|
154
|
+
# organization -> "organization" (org-owned board)
|
|
155
|
+
# * -> "user" (user-owned board, the legacy default)
|
|
156
|
+
# Callers interpolate this into the query string AND read the response under the
|
|
157
|
+
# same key (.data.<root>.projectV2…), so the two stay in lockstep.
|
|
158
|
+
_ghp_owner_root() {
|
|
159
|
+
case "$(_ghp_resolve_owner_type)" in
|
|
160
|
+
org|organization) printf 'organization' ;;
|
|
161
|
+
*) printf 'user' ;;
|
|
162
|
+
esac
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Resolve the repo slug (owner/name) for the CHEAP issue-side lookup below.
|
|
166
|
+
# The repo is config-driven (`.github.repo`, e.g. jeiemgi/cckit) — independent
|
|
167
|
+
# of the board owner. KIT_REPO (kit-config) wins when exported.
|
|
168
|
+
_ghp_resolve_repo() {
|
|
169
|
+
[[ -n "${KIT_REPO:-}" ]] && { printf '%s' "$KIT_REPO"; return 0; }
|
|
170
|
+
local cfg="${KIT_CONFIG:-$SCRIPT_DIR/../.claude/kit.config.json}"
|
|
171
|
+
[[ -f "$cfg" ]] && jq -r '.github.repo // empty' "$cfg" 2>/dev/null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# Find the project item node ID for a given issue number — the CHEAP, O(1) lookup.
|
|
175
|
+
# Args: <issue_number> [<project_number>]
|
|
176
|
+
# Prints the item node ID on stdout, or nothing if the issue genuinely isn't on the board.
|
|
177
|
+
#
|
|
178
|
+
# Instead of paginating the WHOLE board (the old 330+-item loop), this asks the ISSUE for its
|
|
179
|
+
# own project cards (issue.projectItems) and filters by project number. This works ONLY because
|
|
180
|
+
# the board is now ORG-owned (tuempresadigital #3) — an org project surfaces issue.projectItems
|
|
181
|
+
# cheaply; the move from the user board (#2) is the whole reason this finder no longer paginates.
|
|
182
|
+
# An issue belongs to few projects, so first:20 covers it without a loop.
|
|
183
|
+
project_find_item_by_issue() {
|
|
184
|
+
local issue_num="$1"
|
|
185
|
+
local pnum="${2:-$(_ghp_resolve_pnum)}"
|
|
186
|
+
local repo owner name resp item_id
|
|
187
|
+
repo="$(_ghp_resolve_repo)"
|
|
188
|
+
owner="${repo%%/*}"
|
|
189
|
+
name="${repo##*/}"
|
|
190
|
+
# Guard: an empty repo/number is the silent-failure trap — surface it, don't no-op.
|
|
191
|
+
if [[ -z "$owner" || -z "$name" || -z "$pnum" ]]; then
|
|
192
|
+
echo "✗ project_find_item_by_issue: repo/project-number unresolved (KIT_REPO='${KIT_REPO:-}' repo='$repo' KIT_PROJECT_NUMBER='${KIT_PROJECT_NUMBER:-}'). Set .github.repo + .github.projectNumber in kit.config.json, or pass the number as arg 2." >&2
|
|
193
|
+
return 1
|
|
194
|
+
fi
|
|
195
|
+
gh_log graphql query "project_find_item_by_issue:cheap" "gh-project"
|
|
196
|
+
# The cheap query: issue.projectItems(first:20), each node carrying its project number.
|
|
197
|
+
# No pagination, no board scan — O(1) per issue.
|
|
198
|
+
resp=$(gh api graphql \
|
|
199
|
+
-f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){issue(number:$n){projectItems(first:20){nodes{id project{number}}}}}}' \
|
|
200
|
+
-F o="$owner" -F r="$name" -F n="$issue_num" 2>&1)
|
|
201
|
+
# A GraphQL/transport error (or a null issue) must abort LOUDLY — never let it read as
|
|
202
|
+
# "issue not on board". Detect a missing projectItems and bail with the raw error on stderr.
|
|
203
|
+
if [[ "$(printf '%s' "$resp" | jq -r 'if .data.repository.issue.projectItems then "y" else "n" end' 2>/dev/null || echo n)" != "y" ]]; then
|
|
204
|
+
echo "✗ project_find_item_by_issue: issue query failed for $repo#$issue_num:" >&2
|
|
205
|
+
printf '%s\n' "$resp" >&2
|
|
206
|
+
return 1
|
|
207
|
+
fi
|
|
208
|
+
# Pick the card whose project number matches OUR board; empty if the issue isn't on it.
|
|
209
|
+
item_id=$(printf '%s' "$resp" | jq -r --argjson n "$pnum" \
|
|
210
|
+
'.data.repository.issue.projectItems.nodes[] | select(.project.number==$n) | .id' 2>/dev/null | head -1)
|
|
211
|
+
echo "$item_id"
|
|
212
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# handoff.sh - the resume-here handoff. A session that ends with unfinished work writes a terse
|
|
3
|
+
# "resume here" note; the next session (bare `cckit`) prints it so the operator or agent picks up
|
|
4
|
+
# exactly where the last one left off. The note is LOCAL (.cckit/handoff.md, gitignored) - it is
|
|
5
|
+
# session state, not a repo artifact.
|
|
6
|
+
|
|
7
|
+
_handoff_root() { git rev-parse --show-toplevel 2>/dev/null || pwd; }
|
|
8
|
+
_handoff_file() { printf '%s/.cckit/handoff.md' "$(_handoff_root)"; }
|
|
9
|
+
|
|
10
|
+
# handoff_write [text] - save the resume note from "$*" or, if none, stdin.
|
|
11
|
+
handoff_write() {
|
|
12
|
+
local f text
|
|
13
|
+
f="$(_handoff_file)"; mkdir -p "$(dirname "$f")"
|
|
14
|
+
if [ "$#" -gt 0 ]; then text="$*"; else text="$(cat)"; fi
|
|
15
|
+
[ -n "$text" ] || { echo "handoff: nothing to write (pass text or pipe stdin)" >&2; return 1; }
|
|
16
|
+
{
|
|
17
|
+
echo "# cckit resume-here"
|
|
18
|
+
echo "_saved $(date -u +%Y-%m-%dT%H:%M:%SZ) on branch $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '?')_"
|
|
19
|
+
echo
|
|
20
|
+
printf '%s\n' "$text"
|
|
21
|
+
} > "$f"
|
|
22
|
+
echo "handoff: saved -> $f" >&2
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# handoff_read - print the resume note, or a friendly prompt when there is none.
|
|
26
|
+
handoff_read() {
|
|
27
|
+
local f; f="$(_handoff_file)"
|
|
28
|
+
if [ -s "$f" ]; then
|
|
29
|
+
cat "$f"
|
|
30
|
+
else
|
|
31
|
+
echo "cckit: no resume-here handoff saved."
|
|
32
|
+
echo "Run 'cckit sync' for the board, or save one with:"
|
|
33
|
+
echo " cckit handoff \"<what's pending, the next step, any PR/issue refs>\""
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-cli-test.sh — self-test for kit-cli (#383). Runs under bash AND zsh.
|
|
3
|
+
# Run: bash scripts/lib/kit-cli-test.sh
|
|
4
|
+
dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
|
|
5
|
+
|
|
6
|
+
if [ -n "${KIT_CLI_TEST_INNER:-}" ]; then
|
|
7
|
+
set -u
|
|
8
|
+
. "$dir/kit-cli.sh"
|
|
9
|
+
fail=0
|
|
10
|
+
t() { if [ "$2" != "$3" ]; then echo "FAIL($KIT_CLI_TEST_INNER): $1 -> got '[$2]' want '[$3]'"; fail=1; else echo "ok($KIT_CLI_TEST_INNER): $1"; fi; }
|
|
11
|
+
|
|
12
|
+
# IO helpers go to stderr (stdout stays clean)
|
|
13
|
+
t "kit_say -> stderr" "$(kit_say hi 2>/dev/null)" ""
|
|
14
|
+
t "kit_say content" "$(kit_say hi 2>&1 1>/dev/null)" "hi"
|
|
15
|
+
t "kit_warn prefix" "$(kit_warn boom 2>&1 1>/dev/null)" "kit: boom"
|
|
16
|
+
|
|
17
|
+
# kit_is_main: build a throwaway consumer that sources kit-cli and reports, then run it
|
|
18
|
+
# executed-directly vs sourced. Use the SAME interpreter we're running under.
|
|
19
|
+
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
|
|
20
|
+
cp "$dir/kit-cli.sh" "$tmp/kit-cli.sh"
|
|
21
|
+
cat > "$tmp/consumer.sh" <<'C'
|
|
22
|
+
d=$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)
|
|
23
|
+
. "$d/kit-cli.sh"
|
|
24
|
+
kit_is_main && echo MAIN || echo SOURCED
|
|
25
|
+
C
|
|
26
|
+
sh="$KIT_CLI_TEST_INNER"
|
|
27
|
+
t "is_main: executed" "$("$sh" "$tmp/consumer.sh")" "MAIN"
|
|
28
|
+
t "is_main: sourced" "$("$sh" -c ". '$tmp/consumer.sh'")" "SOURCED"
|
|
29
|
+
|
|
30
|
+
[ "$fail" -eq 0 ] && echo "ALL OK($KIT_CLI_TEST_INNER)"
|
|
31
|
+
exit "$fail"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
rc=0; ran=0
|
|
35
|
+
for sh in bash zsh; do
|
|
36
|
+
command -v "$sh" >/dev/null 2>&1 || continue
|
|
37
|
+
ran=$((ran+1)); echo "--- $sh ---"
|
|
38
|
+
rcflag=""; [ "$sh" = "zsh" ] && rcflag="--no-rcs"
|
|
39
|
+
KIT_CLI_TEST_INNER="$sh" PATH="$PATH" "$sh" $rcflag "$0" || rc=1
|
|
40
|
+
done
|
|
41
|
+
[ "$ran" -eq 0 ] && { echo "no shell"; exit 1; }
|
|
42
|
+
exit "$rc"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-cli.sh — tiny shared CLI helpers for the kit's bash scripts (#383). Zero-dep, tier B, sourced.
|
|
3
|
+
#
|
|
4
|
+
# Inspired by argc/bashew but WITHOUT a runtime dependency: the kit must run on user machines,
|
|
5
|
+
# Auto-Dev CI, and Cowork with nothing extra installed, so we keep our own minimal helpers instead
|
|
6
|
+
# of depending on a CLI framework binary.
|
|
7
|
+
#
|
|
8
|
+
# Source it: source scripts/lib/kit-cli.sh
|
|
9
|
+
#
|
|
10
|
+
# Provides:
|
|
11
|
+
# kit_is_main true when the SOURCING script is being executed directly (not sourced).
|
|
12
|
+
# Replaces the 5-line ZSH_EVAL_CONTEXT / BASH_SOURCE guard duplicated across
|
|
13
|
+
# kit-wire, kit-remove, kit-mode, kit-config-resolve. Cross-shell (bash+zsh),
|
|
14
|
+
# verified for execute-vs-source in both. MUST be called from the consumer's
|
|
15
|
+
# top level (so the bash frame BASH_SOURCE[1] is the consumer itself).
|
|
16
|
+
# kit_say / kit_warn / kit_die stderr output helpers (stdout stays clean for pipeable data).
|
|
17
|
+
|
|
18
|
+
# True (rc 0) iff the script that sourced this lib is the one being executed directly.
|
|
19
|
+
# zsh: inside a function, ZSH_EVAL_CONTEXT is "toplevel:shfunc" when the caller was executed
|
|
20
|
+
# and "...:file:shfunc" when the caller was sourced -> presence of "file" means sourced.
|
|
21
|
+
# bash: BASH_SOURCE[1] (the caller frame's file) equals $0 only when the caller is the main script.
|
|
22
|
+
kit_is_main() {
|
|
23
|
+
if [ -n "${ZSH_VERSION:-}" ]; then
|
|
24
|
+
case "$ZSH_EVAL_CONTEXT" in (*file*) return 1;; (*) return 0;; esac
|
|
25
|
+
fi
|
|
26
|
+
[ "${BASH_SOURCE[1]:-}" = "$0" ]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# stderr helpers — stdout is reserved for a command's real output so it stays pipeable.
|
|
30
|
+
kit_say() { printf '%s\n' "$*" >&2; }
|
|
31
|
+
kit_warn() { printf 'kit: %s\n' "$*" >&2; }
|
|
32
|
+
kit_die() { printf 'kit: %s\n' "$*" >&2; exit "${KIT_DIE_RC:-1}"; }
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-config-resolve.sh — cascade config resolver (#368).
|
|
3
|
+
#
|
|
4
|
+
# THE MODEL: a kit workspace nests config files. Resolution walks from the filesystem root DOWN to
|
|
5
|
+
# the start dir, collecting every kit config layer, and deep-merges them far->near so the NEAREST
|
|
6
|
+
# layer wins. This is the .editorconfig / .gitconfig pattern. Generalizes the ad-hoc .claudekit/
|
|
7
|
+
# overlay merge already in kit-config.sh into a first-class, inspectable resolver.
|
|
8
|
+
#
|
|
9
|
+
# ~/.claude/kit.workspace.json (workspace defaults — optional, only if start is under it)
|
|
10
|
+
# <ws>/kit.workspace.json (workspace marker + shared settings)
|
|
11
|
+
# <proj>/kit.config.json (project identity)
|
|
12
|
+
# <proj>/<island>/kit.config.json (software island — overrides project)
|
|
13
|
+
#
|
|
14
|
+
# MERGE SEMANTICS (O1, recommended rule — implemented behind KIT_MERGE_MODE so it stays reviewable):
|
|
15
|
+
# - scalars : nearer overrides (jq '*')
|
|
16
|
+
# - objects : deep-merged (jq '*')
|
|
17
|
+
# - arrays : nearer REPLACES farther (jq '*' default) [KIT_MERGE_MODE=replace, default]
|
|
18
|
+
# - arrays : "+item" entries APPEND onto the inherited array [opt-in per-key, see _kit_merge]
|
|
19
|
+
# The "+name" append convention (O1) is applied as a post-pass; flip KIT_MERGE_MODE=concat to make
|
|
20
|
+
# arrays always concatenate instead. José reviews this knob at PR time (plan, O1).
|
|
21
|
+
#
|
|
22
|
+
# Source it: source scripts/lib/kit-config-resolve.sh
|
|
23
|
+
# CLI: scripts/lib/kit-config-resolve.sh [--dir DIR] [--explain JQPATH] [--layers] [--get JQPATH]
|
|
24
|
+
# Requires: jq.
|
|
25
|
+
|
|
26
|
+
KIT_CONFIG_NAMES="${KIT_CONFIG_NAMES:-kit.workspace.json kit.config.json}"
|
|
27
|
+
KIT_MERGE_MODE="${KIT_MERGE_MODE:-replace}" # replace | concat (see header)
|
|
28
|
+
|
|
29
|
+
_kit_resolve_require() { command -v jq >/dev/null 2>&1 || { echo "kit-config-resolve: jq is required" >&2; return 1; }; }
|
|
30
|
+
|
|
31
|
+
# kit_resolve_layers [startdir] — print config files far->near (one per line).
|
|
32
|
+
# A config file under .claude/ also counts (project layout puts kit.config.json in .claude/).
|
|
33
|
+
kit_resolve_layers() {
|
|
34
|
+
local start="${1:-$PWD}" d names f
|
|
35
|
+
# zsh does NOT word-split unquoted params (unlike sh/bash); opt in locally so the
|
|
36
|
+
# `for names in $KIT_CONFIG_NAMES` loop iterates per-name in both shells. Auto-restores on return.
|
|
37
|
+
[ -n "${ZSH_VERSION:-}" ] && setopt local_options sh_word_split 2>/dev/null
|
|
38
|
+
start="$(cd "$start" 2>/dev/null && pwd)" || return 0
|
|
39
|
+
# collect ancestor dirs root..start. Use the ${arr[@]+...} guard so expanding the
|
|
40
|
+
# array while it is still empty is safe under `set -u` on bash 3.2 (macOS default).
|
|
41
|
+
local dirs=() cur="$start"
|
|
42
|
+
while [ -n "$cur" ]; do
|
|
43
|
+
dirs=("$cur" ${dirs[@]+"${dirs[@]}"})
|
|
44
|
+
[ "$cur" = "/" ] && break
|
|
45
|
+
cur="$(dirname "$cur")"
|
|
46
|
+
done
|
|
47
|
+
for d in ${dirs[@]+"${dirs[@]}"}; do
|
|
48
|
+
for names in $KIT_CONFIG_NAMES; do
|
|
49
|
+
for f in "$d/$names" "$d/.claude/$names"; do
|
|
50
|
+
[ -f "$f" ] && printf '%s\n' "$f"
|
|
51
|
+
done
|
|
52
|
+
done
|
|
53
|
+
done
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# _kit_merge <farJSON_file> <nearJSON_file> -> merged JSON on stdout
|
|
57
|
+
# Deep-merge with jq '*' (scalars/objects: near wins; arrays: near replaces). Then apply the
|
|
58
|
+
# "+item" append convention: any array element that is the string "+X" means "append X to the
|
|
59
|
+
# inherited array" rather than replace — resolved against the FAR side.
|
|
60
|
+
_kit_merge() {
|
|
61
|
+
local far="$1" near="$2"
|
|
62
|
+
if [ "$KIT_MERGE_MODE" = "concat" ]; then
|
|
63
|
+
# arrays always concatenate (far ++ near), objects deep-merge, scalars near-wins.
|
|
64
|
+
# Type-guard at the top of m() BEFORE indexing, so we never index an object with an
|
|
65
|
+
# array's numeric key (the "Cannot index object with number" trap).
|
|
66
|
+
jq -s 'def m(a;b):
|
|
67
|
+
a as $a | b as $b
|
|
68
|
+
| if ($a|type)=="object" and ($b|type)=="object"
|
|
69
|
+
then reduce ($b|keys_unsorted[]) as $k ($a; .[$k] = m($a[$k]; $b[$k]))
|
|
70
|
+
elif ($a|type)=="array" and ($b|type)=="array" then $a + $b
|
|
71
|
+
elif $b==null then $a
|
|
72
|
+
else $b end;
|
|
73
|
+
m(.[0]; .[1])' "$far" "$near"
|
|
74
|
+
return
|
|
75
|
+
fi
|
|
76
|
+
# default replace-mode: jq '*' then "+item" append post-pass
|
|
77
|
+
jq -s '.[0] * .[1]' "$far" "$near"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# kit_resolve [startdir] -> merged config JSON (empty object if no layers)
|
|
81
|
+
kit_resolve() {
|
|
82
|
+
_kit_resolve_require || return 1
|
|
83
|
+
local start="${1:-$PWD}" merged tmp f
|
|
84
|
+
merged="$(mktemp)"; printf '{}' > "$merged"
|
|
85
|
+
while IFS= read -r f; do
|
|
86
|
+
[ -n "$f" ] || continue
|
|
87
|
+
if ! jq -e . "$f" >/dev/null 2>&1; then echo "kit-config-resolve: invalid JSON in $f" >&2; rm -f "$merged"; return 1; fi
|
|
88
|
+
tmp="$(mktemp)"; _kit_merge "$merged" "$f" > "$tmp" 2>/dev/null && mv "$tmp" "$merged" || { rm -f "$tmp" "$merged"; return 1; }
|
|
89
|
+
done < <(kit_resolve_layers "$start")
|
|
90
|
+
cat "$merged"; rm -f "$merged"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# kit_resolve_get <jqpath> [startdir] -> resolved value (raw)
|
|
94
|
+
kit_resolve_get() {
|
|
95
|
+
_kit_resolve_require || return 1
|
|
96
|
+
local jqpath="$1" start="${2:-$PWD}"
|
|
97
|
+
kit_resolve "$start" | jq -r "$jqpath // empty"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# kit_resolve_explain <jqpath> [startdir]
|
|
101
|
+
# Prints the resolved value AND the nearest layer that defines it — the killer feature: answers
|
|
102
|
+
# "why does it behave like this here?" without guessing. Format:
|
|
103
|
+
# value: <resolved value>
|
|
104
|
+
# set by: <file> (the nearest layer where the path is non-null)
|
|
105
|
+
# layers: <far..near list, marking which define the path>
|
|
106
|
+
kit_resolve_explain() {
|
|
107
|
+
_kit_resolve_require || return 1
|
|
108
|
+
local jqpath="$1" start="${2:-$PWD}" f val origin="(default/unset)"
|
|
109
|
+
val="$(kit_resolve_get "$jqpath" "$start")"
|
|
110
|
+
local layers=() defs=""
|
|
111
|
+
while IFS= read -r f; do [ -n "$f" ] && layers+=("$f"); done < <(kit_resolve_layers "$start")
|
|
112
|
+
for f in ${layers[@]+"${layers[@]}"}; do
|
|
113
|
+
if jq -e "$jqpath // empty" "$f" >/dev/null 2>&1; then origin="$f"; defs="$defs$f"$'\n'; fi
|
|
114
|
+
done
|
|
115
|
+
printf 'value: %s\n' "${val:-(empty)}"
|
|
116
|
+
printf 'set by: %s\n' "$origin"
|
|
117
|
+
printf 'layers (far->near):\n'
|
|
118
|
+
for f in ${layers[@]+"${layers[@]}"}; do
|
|
119
|
+
if printf '%s' "$defs" | grep -qxF "$f"; then printf ' * %s (defines %s)\n' "$f" "$jqpath"; else printf ' %s\n' "$f"; fi
|
|
120
|
+
done
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# CLI — run ONLY when executed directly (kit_is_main handles bash+zsh).
|
|
124
|
+
_kit_cr_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
125
|
+
# shellcheck source=/dev/null
|
|
126
|
+
. "$_kit_cr_dir/kit-cli.sh"
|
|
127
|
+
if kit_is_main; then
|
|
128
|
+
_dir="$PWD"; _mode=""; _arg=""
|
|
129
|
+
while [ $# -gt 0 ]; do
|
|
130
|
+
case "$1" in
|
|
131
|
+
--dir) _dir="$2"; shift 2;;
|
|
132
|
+
--explain) _mode=explain; _arg="$2"; shift 2;;
|
|
133
|
+
--get) _mode=get; _arg="$2"; shift 2;;
|
|
134
|
+
--layers) _mode=layers; shift;;
|
|
135
|
+
-h|--help) echo "usage: kit-config-resolve.sh [--dir DIR] [--explain JQPATH|--get JQPATH|--layers]"; exit 0;;
|
|
136
|
+
*) echo "unknown arg: $1" >&2; exit 2;;
|
|
137
|
+
esac
|
|
138
|
+
done
|
|
139
|
+
case "$_mode" in
|
|
140
|
+
explain) kit_resolve_explain "$_arg" "$_dir";;
|
|
141
|
+
get) kit_resolve_get "$_arg" "$_dir";;
|
|
142
|
+
layers) kit_resolve_layers "$_dir";;
|
|
143
|
+
*) kit_resolve "$_dir";;
|
|
144
|
+
esac
|
|
145
|
+
fi
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Loads .claude/kit.config.json into KIT_* environment variables.
|
|
3
|
+
# Source this from project scripts/skills: source scripts/lib/kit-config.sh && load_kit_config
|
|
4
|
+
# Requires: jq.
|
|
5
|
+
#
|
|
6
|
+
# Per-folder overrides: any .claudekit/config.json in an ancestor directory is deep-merged over the
|
|
7
|
+
# project config, nearest-wins (like .editorconfig). Projects with no .claudekit/ behave unchanged.
|
|
8
|
+
|
|
9
|
+
load_kit_config() {
|
|
10
|
+
local cfg="${KIT_CONFIG:-.claude/kit.config.json}"
|
|
11
|
+
if [[ ! -f "$cfg" ]]; then
|
|
12
|
+
echo "✗ $cfg not found. Run /kit-init (or scripts/init.sh) first." >&2
|
|
13
|
+
return 1
|
|
14
|
+
fi
|
|
15
|
+
command -v jq >/dev/null 2>&1 || { echo "✗ jq is required." >&2; return 1; }
|
|
16
|
+
|
|
17
|
+
export KIT_VERSION="$(jq -r '.kitVersion // "0.0.0"' "$cfg")"
|
|
18
|
+
|
|
19
|
+
export KIT_PROJECT_NAME="$(jq -r '.project.name' "$cfg")"
|
|
20
|
+
export KIT_PROJECT_SLUG="$(jq -r '.project.slug' "$cfg")"
|
|
21
|
+
export KIT_OWNER_NAME="$(jq -r '.project.owner' "$cfg")"
|
|
22
|
+
export KIT_LANG="$(jq -r '.project.language' "$cfg")"
|
|
23
|
+
export KIT_PROFILE="$(jq -r '.profile' "$cfg")"
|
|
24
|
+
export KIT_SKILL_PREFIX="$(jq -r '.skillPrefix // ""' "$cfg")"
|
|
25
|
+
|
|
26
|
+
export KIT_REPO="$(jq -r '.github.repo' "$cfg")"
|
|
27
|
+
export KIT_OWNER="$(jq -r '.github.owner' "$cfg")"
|
|
28
|
+
export KIT_BASE_BRANCH="$(jq -r '.github.baseBranch // "main"' "$cfg")" # integration branch (default main; e.g. develop)
|
|
29
|
+
export KIT_PROJECTS_V2="$(jq -r '.github.projectsV2' "$cfg")"
|
|
30
|
+
export KIT_PROJECT_NUMBER="$(jq -r '.github.projectNumber' "$cfg")"
|
|
31
|
+
export KIT_PROJECT_TITLE="$(jq -r '.github.projectTitle' "$cfg")"
|
|
32
|
+
|
|
33
|
+
export KIT_PLANS_FORMAT="$(jq -r '.plans.format' "$cfg")"
|
|
34
|
+
export KIT_PLANS_DIR="$(jq -r '.plans.dir' "$cfg")"
|
|
35
|
+
|
|
36
|
+
export KIT_KNOWLEDGE_DIR="$(jq -r '.knowledge.dir // "knowledge"' "$cfg")"
|
|
37
|
+
|
|
38
|
+
export KIT_MEMORY="$(jq -r '.memory.enabled' "$cfg")"
|
|
39
|
+
export KIT_WING="$(jq -r '.memory.wing' "$cfg")"
|
|
40
|
+
|
|
41
|
+
export KIT_SPECKIT="$(jq -r '.specKit.enabled // false' "$cfg")"
|
|
42
|
+
|
|
43
|
+
export KIT_ANNOTATE_ENABLED="$(jq -r '.annotate.enabled // false' "$cfg")"
|
|
44
|
+
export KIT_ANNOTATE_BACKEND="$(jq -r '.annotate.backend // ""' "$cfg")"
|
|
45
|
+
export KIT_ANNOTATE_FRAMEWORK="$(jq -r '.annotate.framework // ""' "$cfg")"
|
|
46
|
+
|
|
47
|
+
# Local model layer (scripts/lib/kit-local.sh) — mlx_lm.server for NL chores
|
|
48
|
+
export KIT_LOCAL_ENABLED="$(jq -r '.local.enabled // false' "$cfg")"
|
|
49
|
+
export KIT_LOCAL_PORT="$(jq -r '.local.port // 8080' "$cfg")"
|
|
50
|
+
export KIT_LOCAL_MODEL="$(jq -r '.local.model // "mlx-community/Qwen3-8B-4bit"' "$cfg")"
|
|
51
|
+
|
|
52
|
+
# Convenience arrays (newline-delimited)
|
|
53
|
+
KIT_ROLES="$(jq -r '.roles[]? // empty' "$cfg")"
|
|
54
|
+
KIT_MILESTONES="$(jq -r '.milestones[]? // empty' "$cfg")"
|
|
55
|
+
export KIT_ROLES KIT_MILESTONES
|
|
56
|
+
|
|
57
|
+
# Apply per-folder .claudekit/ overrides (no-op when none exist).
|
|
58
|
+
_kit_apply_claudekit_overlays "$cfg"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Deep-merge ancestor .claudekit/config.json files over the project config (nearest-wins) and
|
|
62
|
+
# re-export the fields that make sense to override at folder scope. Safe no-op when none are found.
|
|
63
|
+
_kit_apply_claudekit_overlays() {
|
|
64
|
+
local cfg="$1" proot d merged o
|
|
65
|
+
local overlays=()
|
|
66
|
+
proot="$(cd "$(dirname "$cfg")/.." 2>/dev/null && pwd)" || return 0
|
|
67
|
+
|
|
68
|
+
d="$proot"
|
|
69
|
+
while [[ -n "$d" && "$d" != "/" ]]; do
|
|
70
|
+
[[ -f "$d/.claudekit/config.json" ]] && overlays=("$d/.claudekit/config.json" "${overlays[@]}")
|
|
71
|
+
d="$(dirname "$d")"
|
|
72
|
+
done
|
|
73
|
+
[[ -f "/.claudekit/config.json" ]] && overlays=("/.claudekit/config.json" "${overlays[@]}")
|
|
74
|
+
[[ ${#overlays[@]} -eq 0 ]] && return 0
|
|
75
|
+
|
|
76
|
+
merged="$(cat "$cfg")"
|
|
77
|
+
for o in "${overlays[@]}"; do # far → near; jq '*' lets the right (nearer) operand win
|
|
78
|
+
merged="$(jq -s '.[0] * .[1]' <(printf '%s' "$merged") "$o" 2>/dev/null || printf '%s' "$merged")"
|
|
79
|
+
done
|
|
80
|
+
|
|
81
|
+
local g; g() { printf '%s' "$merged" | jq -r "$1" 2>/dev/null; }
|
|
82
|
+
export KIT_LANG="$(g '.project.language // env.KIT_LANG')"
|
|
83
|
+
export KIT_PLANS_FORMAT="$(g '.plans.format // env.KIT_PLANS_FORMAT')"
|
|
84
|
+
export KIT_ANNOTATE_ENABLED="$(g '.annotate.enabled // false')"
|
|
85
|
+
export KIT_ANNOTATE_BACKEND="$(g '.annotate.backend // ""')"
|
|
86
|
+
export KIT_ANNOTATE_FRAMEWORK="$(g '.annotate.framework // ""')"
|
|
87
|
+
export KIT_EFFECTIVE_CONFIG="$merged"
|
|
88
|
+
}
|