@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,341 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-task-ops.sh — the ONE canonical home for the kit-task pr|pr-merge|new|close
|
|
3
|
+
# git/GitHub mechanics (Family 1 of kit-engine-boundary.md, rule #1/#2).
|
|
4
|
+
#
|
|
5
|
+
# The four .claude/skills/kit-task-{pr,pr-merge,new,close} skills, the `scripts/kit task <op>`
|
|
6
|
+
# dispatcher, and any future surface CALL these functions — they never re-implement the logic.
|
|
7
|
+
# Each function is parameterized (no AskUserQuestion / no interactive prompts here — the caller
|
|
8
|
+
# collects inputs), and sources the existing helpers (role-identity, gh-project ids).
|
|
9
|
+
#
|
|
10
|
+
# Source it: source scripts/lib/kit-task-ops.sh
|
|
11
|
+
# Requires: git, gh, jq. bash 3.2 compatible; sourceable under zsh too (no bash-only indirection).
|
|
12
|
+
# Self-test: bash scripts/lib/kit-task-ops-test.sh (pure helpers only — no network)
|
|
13
|
+
#
|
|
14
|
+
# Functions:
|
|
15
|
+
# kto_repo echo the repo slug (owner/name)
|
|
16
|
+
# kto_branch_issue_number [branch] echo issue # parsed from a <kind>/<N>-<slug> branch
|
|
17
|
+
# kto_labels_role <labels-csv> echo the role slug from a labels CSV (role:<slug>)
|
|
18
|
+
# kto_labels_kind <labels-csv> echo the kind slug from a labels CSV (kind:<slug>)
|
|
19
|
+
# kto_compose_pr_body <num> <summary> <role-slug> echo a PR body from the template
|
|
20
|
+
# kto_compose_issue_body <user-body> <plan> <blocked> <role-slug> echo an issue body
|
|
21
|
+
# kto_closing_issue_numbers <pr-num> echo the issue #s a merged PR closes
|
|
22
|
+
# kto_task_pr <num> <summary> [commit-msg] commit+push+open PR (board → In Review)
|
|
23
|
+
# kto_task_pr_merge [pr-num] squash-merge + close issues + switch develop + GC
|
|
24
|
+
# kto_task_new <title> <role> <kind> <prio> <ms> [plan] [blocked] <body> create issue + board fields
|
|
25
|
+
# kto_task_close <num> <summary> [pr-num] close issue + board → Done
|
|
26
|
+
|
|
27
|
+
# --- repo resolution -----------------------------------------------------------------------------
|
|
28
|
+
# Resolve the repo slug from KIT_REPO (kit-config) when available, else the kit's home repo. A
|
|
29
|
+
# caller may export KIT_TASK_REPO to override.
|
|
30
|
+
if [[ -z "${KIT_REPO:-}" ]]; then
|
|
31
|
+
_kto_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
32
|
+
if [[ -n "$_kto_root" && -f "$_kto_root/scripts/lib/kit-config.sh" ]]; then
|
|
33
|
+
# shellcheck source=/dev/null
|
|
34
|
+
source "$_kto_root/scripts/lib/kit-config.sh" && load_kit_config >/dev/null 2>&1 || true
|
|
35
|
+
fi
|
|
36
|
+
unset _kto_root
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
kto_repo() {
|
|
40
|
+
printf '%s' "${KIT_TASK_REPO:-${KIT_REPO:-jeiemgi/cckit}}"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# The base branch a PR targets / pr-merge returns to (portable: KIT_BASE_BRANCH, default develop).
|
|
44
|
+
kto_base_branch() {
|
|
45
|
+
printf '%s' "${KIT_BASE_BRANCH:-develop}"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# True (rc 0) iff board updates should run. Off when KIT_PROJECTS_V2 is explicitly "false" — keeps
|
|
49
|
+
# the op portable to a project with no Projects v2 board (the plugin's KIT_PROJECTS_V2 toggle).
|
|
50
|
+
kto_board_enabled() {
|
|
51
|
+
[[ "${KIT_PROJECTS_V2:-true}" != "false" ]]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# --- pure parsers (network-free; covered by the self-test) ---------------------------------------
|
|
55
|
+
|
|
56
|
+
# Parse the issue number from a `<kind>/<N>-<slug>` branch (e.g. feat/173-x -> 173). Empty if none.
|
|
57
|
+
kto_branch_issue_number() {
|
|
58
|
+
local branch="${1:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null)}"
|
|
59
|
+
printf '%s' "$branch" | grep -oE '/[0-9]+-' | head -1 | tr -d '/-'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Echo the role slug from a comma-separated labels list (role:<slug>). Empty if absent.
|
|
63
|
+
kto_labels_role() {
|
|
64
|
+
printf '%s' "$1" | tr ',' '\n' | grep '^role:' | head -1 | cut -d: -f2
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Echo the kind slug from a comma-separated labels list (kind:<slug>). Empty if absent.
|
|
68
|
+
kto_labels_kind() {
|
|
69
|
+
printf '%s' "$1" | tr ',' '\n' | grep '^kind:' | head -1 | cut -d: -f2
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Compose a PR body from the kit-task-pr template. Args: <num> <summary> <role-slug>
|
|
73
|
+
kto_compose_pr_body() {
|
|
74
|
+
local num="$1" summary="$2" role="$3" sig=""
|
|
75
|
+
if type role_signature >/dev/null 2>&1; then sig="$(role_signature "$role")"; fi
|
|
76
|
+
cat <<EOF
|
|
77
|
+
## Closes
|
|
78
|
+
|
|
79
|
+
Closes #$num
|
|
80
|
+
|
|
81
|
+
## Summary
|
|
82
|
+
|
|
83
|
+
$summary
|
|
84
|
+
|
|
85
|
+
## Verification
|
|
86
|
+
|
|
87
|
+
- [ ] Reproduced manually
|
|
88
|
+
- [ ] No unrelated files in the diff
|
|
89
|
+
- [ ] Labels match the issue
|
|
90
|
+
- [ ] If plan deliverable: opened in browser, styles render
|
|
91
|
+
|
|
92
|
+
## Notes for review
|
|
93
|
+
|
|
94
|
+
—
|
|
95
|
+
|
|
96
|
+
## Out of scope
|
|
97
|
+
|
|
98
|
+
—
|
|
99
|
+
|
|
100
|
+
$sig
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
<sub>· \`claude-kit\` · kit-task-pr</sub>
|
|
104
|
+
EOF
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Compose an issue body. Args: <user-body> <plan-link> <blocked-by> <role-slug>
|
|
108
|
+
kto_compose_issue_body() {
|
|
109
|
+
local body_user="$1" plan="$2" blocked="$3" role="$4" repo base out="" sig=""
|
|
110
|
+
repo="$(kto_repo)"; base="$(kto_base_branch)"
|
|
111
|
+
[[ -n "$plan" ]] && out+="**Plan:** [\`$plan\`](https://github.com/$repo/blob/$base/$plan)"$'\n\n'
|
|
112
|
+
[[ -n "$blocked" ]] && out+="**Blocked by:** $blocked"$'\n\n'
|
|
113
|
+
out+="$body_user"
|
|
114
|
+
if type role_signature >/dev/null 2>&1; then sig="$(role_signature "$role")"; fi
|
|
115
|
+
[[ -n "$sig" ]] && out+=$'\n\n---\n'"$sig"
|
|
116
|
+
printf '%s' "$out"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Echo the issue numbers a merged PR closes — GitHub's parse first, regex over the body as fallback.
|
|
120
|
+
# Args: <pr-num>
|
|
121
|
+
kto_closing_issue_numbers() {
|
|
122
|
+
local pr="$1" repo issues body
|
|
123
|
+
repo="$(kto_repo)"
|
|
124
|
+
issues=$(gh pr view "$pr" --repo "$repo" --json closingIssuesReferences \
|
|
125
|
+
--jq '.closingIssuesReferences[].number' 2>/dev/null)
|
|
126
|
+
if [[ -z "$issues" ]]; then
|
|
127
|
+
body=$(gh pr view "$pr" --repo "$repo" --json body --jq .body 2>/dev/null)
|
|
128
|
+
issues=$(printf '%s\n' "$body" \
|
|
129
|
+
| grep -ioE '(close[sd]?|fix(e[sd])?|resolve[sd]?) +#[0-9]+' \
|
|
130
|
+
| grep -oE '[0-9]+' | sort -u)
|
|
131
|
+
fi
|
|
132
|
+
printf '%s' "$issues"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# --- the four ops (network; not exercised by the self-test) --------------------------------------
|
|
136
|
+
|
|
137
|
+
# Map a role slug to the board's Role single-select option id (uses gh-project's role_option_id,
|
|
138
|
+
# which takes a display name — translate slug -> display via role_display).
|
|
139
|
+
_kto_role_option_for_slug() {
|
|
140
|
+
local slug="$1" disp=""
|
|
141
|
+
if type role_display >/dev/null 2>&1; then disp="$(role_display "$slug")"; fi
|
|
142
|
+
[[ -z "$disp" ]] && return 0
|
|
143
|
+
if type role_option_id >/dev/null 2>&1; then role_option_id "$disp"; fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# kit task pr — commit current changes (role identity in a Co-authored-by TRAILER, never the
|
|
147
|
+
# author field — Vercel rejects unknown author emails), push, open the PR with the issue's
|
|
148
|
+
# labels/milestone, add it to the board In Review, and move the issue to In Review.
|
|
149
|
+
# Args: <issue-num> <summary> [commit-msg]
|
|
150
|
+
kto_task_pr() {
|
|
151
|
+
# Default-safe reads: callers (scripts/kit) may run under `set -u`.
|
|
152
|
+
local num="${1:-}" summary="${2:-}" commit_msg="${3:-}"
|
|
153
|
+
local repo branch meta title labels milestone role author url pr_num pr_node item role_opt issue_item
|
|
154
|
+
repo="$(kto_repo)"
|
|
155
|
+
branch="$(git rev-parse --abbrev-ref HEAD)"
|
|
156
|
+
|
|
157
|
+
[[ -n "$num" ]] || num="$(kto_branch_issue_number "$branch")"
|
|
158
|
+
[[ -n "$num" ]] || { echo "✗ kto_task_pr: branch '$branch' doesn't match <kind>/<N>-<slug> and no issue number passed" >&2; return 1; }
|
|
159
|
+
[[ -n "$summary" ]] || { echo "✗ kto_task_pr: a Summary is required (never invent one)" >&2; return 1; }
|
|
160
|
+
|
|
161
|
+
meta=$(gh issue view "$num" --repo "$repo" --json title,labels,milestone) || return 1
|
|
162
|
+
title=$(printf '%s' "$meta" | jq -r .title)
|
|
163
|
+
labels=$(printf '%s' "$meta" | jq -r '[.labels[].name] | join(",")')
|
|
164
|
+
milestone=$(printf '%s' "$meta" | jq -r '.milestone.title // ""')
|
|
165
|
+
role="$(kto_labels_role "$labels")"
|
|
166
|
+
[[ -z "$commit_msg" ]] && commit_msg="${role:-task}: $title"
|
|
167
|
+
|
|
168
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
169
|
+
git add -A
|
|
170
|
+
if type role_git_author >/dev/null 2>&1; then author="$(role_git_author "$role")"; fi
|
|
171
|
+
if [[ -n "$author" ]]; then
|
|
172
|
+
git commit -m "$commit_msg" -m "Co-authored-by: ${author%%|*} <${author##*|}>"
|
|
173
|
+
else
|
|
174
|
+
git commit -m "$commit_msg"
|
|
175
|
+
fi
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
git push -u origin "$branch" || return 1
|
|
179
|
+
|
|
180
|
+
url=$(gh pr create --repo "$repo" \
|
|
181
|
+
--base "$(kto_base_branch)" --head "$branch" \
|
|
182
|
+
--title "$title" \
|
|
183
|
+
--body "$(kto_compose_pr_body "$num" "$summary" "$role")" \
|
|
184
|
+
--label "$labels" \
|
|
185
|
+
${milestone:+--milestone "$milestone"} \
|
|
186
|
+
--assignee "@me") || return 1
|
|
187
|
+
echo "✓ $url"
|
|
188
|
+
|
|
189
|
+
# Board: add the PR (In Review) + move the issue to In Review. Best-effort — never fail the op.
|
|
190
|
+
if kto_board_enabled && type load_project_ids >/dev/null 2>&1 && load_project_ids 2>/dev/null; then
|
|
191
|
+
pr_num="$(basename "$url")"
|
|
192
|
+
pr_node=$(gh api "repos/$repo/pulls/$pr_num" --jq .node_id 2>/dev/null)
|
|
193
|
+
if [[ -n "$pr_node" ]]; then
|
|
194
|
+
item=$(project_add_item "$pr_node" 2>/dev/null || true)
|
|
195
|
+
if [[ -n "$item" ]]; then
|
|
196
|
+
role_opt="$(_kto_role_option_for_slug "$role")"
|
|
197
|
+
[[ -n "$role_opt" ]] && project_set_single_select "$item" "$ROLE_FIELD_ID" "$role_opt" 2>/dev/null || true
|
|
198
|
+
[[ -n "${STATUS_OPT_IN_REVIEW:-}" ]] && project_set_single_select "$item" "$STATUS_FIELD_ID" "$STATUS_OPT_IN_REVIEW" 2>/dev/null || true
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
issue_item=$(project_find_item_by_issue "$num" 2>/dev/null || true)
|
|
202
|
+
[[ -n "$issue_item" && -n "${STATUS_OPT_IN_REVIEW:-}" ]] && \
|
|
203
|
+
project_set_single_select "$issue_item" "$STATUS_FIELD_ID" "$STATUS_OPT_IN_REVIEW" 2>/dev/null || true
|
|
204
|
+
fi
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# kit task pr-merge — squash-merge the open PR for the current branch (rebase-fix conflicts onto
|
|
208
|
+
# develop first), close its linked issues (GitHub native auto-close only fires on main), switch the
|
|
209
|
+
# main checkout to develop + pull, then clean up the merged branch + worktree.
|
|
210
|
+
# Args: [pr-num] (defaults to the open PR for the current branch)
|
|
211
|
+
kto_task_pr_merge() {
|
|
212
|
+
local input_pr="${1:-}"
|
|
213
|
+
local repo branch base pr_num mergeable merge_state issues n state wt main_wt
|
|
214
|
+
repo="$(kto_repo)"
|
|
215
|
+
base="$(kto_base_branch)"
|
|
216
|
+
branch="$(git rev-parse --abbrev-ref HEAD)"
|
|
217
|
+
|
|
218
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
219
|
+
echo "✗ Working tree is dirty — commit or stash changes first" >&2; return 1
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
pr_num="$input_pr"
|
|
223
|
+
if [[ -z "$pr_num" ]]; then
|
|
224
|
+
pr_num=$(gh pr list --repo "$repo" --head "$branch" --state open --json number --jq '.[0].number // empty')
|
|
225
|
+
[[ -z "$pr_num" ]] && { echo "✗ No open PR found for branch $branch" >&2; return 1; }
|
|
226
|
+
fi
|
|
227
|
+
echo "→ PR #$pr_num on branch $branch"
|
|
228
|
+
|
|
229
|
+
mergeable=$(gh pr view "$pr_num" --repo "$repo" --json mergeable,mergeStateStatus \
|
|
230
|
+
--jq '{mergeable:.mergeable, status:.mergeStateStatus}')
|
|
231
|
+
merge_state=$(printf '%s' "$mergeable" | jq -r .mergeable)
|
|
232
|
+
echo " mergeable=$merge_state status=$(printf '%s' "$mergeable" | jq -r .status)"
|
|
233
|
+
|
|
234
|
+
if [[ "$merge_state" == "CONFLICTING" ]]; then
|
|
235
|
+
echo "→ Conflicts detected — rebasing onto $base..."
|
|
236
|
+
git fetch origin "$base"
|
|
237
|
+
if git rebase "origin/$base"; then
|
|
238
|
+
git push --force-with-lease origin "$branch"
|
|
239
|
+
echo "✓ Rebased and pushed — re-checking mergeability..."
|
|
240
|
+
else
|
|
241
|
+
echo "✗ Rebase had conflicts that need manual resolution — resolve, then run pr-merge again" >&2
|
|
242
|
+
git rebase --abort 2>/dev/null || true
|
|
243
|
+
return 1
|
|
244
|
+
fi
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
if ! gh pr merge "$pr_num" --repo "$repo" --squash 2>&1; then
|
|
248
|
+
echo "✗ Merge failed — check PR status: https://github.com/$repo/pull/$pr_num" >&2
|
|
249
|
+
return 1
|
|
250
|
+
fi
|
|
251
|
+
echo "✓ PR #$pr_num merged"
|
|
252
|
+
|
|
253
|
+
# Close linked issues (replaces issue-close-on-merge.yml; native auto-close only fires on main).
|
|
254
|
+
issues="$(kto_closing_issue_numbers "$pr_num")"
|
|
255
|
+
for n in $issues; do
|
|
256
|
+
state=$(gh issue view "$n" --repo "$repo" --json state --jq .state 2>/dev/null)
|
|
257
|
+
if [[ "$state" == "OPEN" ]]; then
|
|
258
|
+
gh issue close "$n" --repo "$repo" \
|
|
259
|
+
--comment "Closed automatically on merge of #$pr_num to $base (kit task pr-merge; GitHub native auto-close only fires on the default branch)." \
|
|
260
|
+
&& echo "✓ Closed issue #$n"
|
|
261
|
+
elif [[ -n "$state" ]]; then
|
|
262
|
+
echo " issue #$n already $state — skipped"
|
|
263
|
+
fi
|
|
264
|
+
done
|
|
265
|
+
|
|
266
|
+
# Locate the branch's worktree + the main worktree, switch develop + pull, clean up.
|
|
267
|
+
wt=$(git worktree list --porcelain | awk -v b="refs/heads/$branch" '/^worktree /{p=$2} /^branch /{if($2==b) print p}')
|
|
268
|
+
main_wt=$(git worktree list --porcelain | awk '/^worktree /{print $2; exit}')
|
|
269
|
+
git -C "$main_wt" checkout "$base" 2>/dev/null || git checkout "$base"
|
|
270
|
+
git -C "$main_wt" pull origin "$base" 2>/dev/null || git pull origin "$base"
|
|
271
|
+
|
|
272
|
+
if [[ -n "$wt" && "$wt" != "$main_wt" ]]; then
|
|
273
|
+
git worktree remove "$wt" --force && echo "✓ Removed worktree $wt"
|
|
274
|
+
fi
|
|
275
|
+
git branch -D "$branch" 2>/dev/null && echo "✓ Deleted local branch $branch"
|
|
276
|
+
git push origin --delete "$branch" 2>/dev/null && echo "✓ Deleted remote branch $branch" || echo " (remote branch already gone)"
|
|
277
|
+
git worktree prune
|
|
278
|
+
echo "✓ On $base, up to date — merged branch + worktree cleaned"
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# kit task new — create a GitHub issue with labels + milestone, add it to the board, set Role +
|
|
282
|
+
# Status(Todo) + Plan Link. Inputs are passed (the caller does the AskUserQuestion). No invented
|
|
283
|
+
# values; fails loudly on an unknown label family upstream (the caller validates the closed sets).
|
|
284
|
+
# Args: <title> <role-display-or-slug> <kind> <priority> <milestone> [plan-link] [blocked-by] <body>
|
|
285
|
+
kto_task_new() {
|
|
286
|
+
local title="${1:-}" role="${2:-}" kind="${3:-}" prio="${4:-}" milestone="${5:-}" plan="${6:-}" blocked="${7:-}" body_user="${8:-}"
|
|
287
|
+
local repo role_slug labels body url num node item role_opt
|
|
288
|
+
repo="$(kto_repo)"
|
|
289
|
+
[[ -n "$title" && -n "$role" && -n "$kind" && -n "$prio" && -n "$milestone" ]] || {
|
|
290
|
+
echo "✗ kto_task_new: title, role, kind, priority, milestone are required" >&2; return 1; }
|
|
291
|
+
|
|
292
|
+
role_slug="$(printf '%s' "$role" | tr 'A-Z ' 'a-z-')"
|
|
293
|
+
labels="kind:${kind},priority:${prio},role:${role_slug}"
|
|
294
|
+
body="$(kto_compose_issue_body "$body_user" "$plan" "$blocked" "$role_slug")"
|
|
295
|
+
|
|
296
|
+
url=$(gh issue create --repo "$repo" \
|
|
297
|
+
--title "$title" --body "$body" \
|
|
298
|
+
--label "$labels" --milestone "$milestone" --assignee "@me") || return 1
|
|
299
|
+
num=$(basename "$url")
|
|
300
|
+
echo "✓ $url"
|
|
301
|
+
|
|
302
|
+
if kto_board_enabled && type load_project_ids >/dev/null 2>&1 && load_project_ids 2>/dev/null; then
|
|
303
|
+
node=$(gh api "repos/$repo/issues/$num" --jq .node_id 2>/dev/null)
|
|
304
|
+
if [[ -n "$node" ]]; then
|
|
305
|
+
item=$(project_add_item "$node" 2>/dev/null || true)
|
|
306
|
+
if [[ -n "$item" ]]; then
|
|
307
|
+
role_opt="$(_kto_role_option_for_slug "$role_slug")"
|
|
308
|
+
[[ -n "$role_opt" ]] && project_set_single_select "$item" "$ROLE_FIELD_ID" "$role_opt" 2>/dev/null || true
|
|
309
|
+
[[ -n "${STATUS_OPT_TODO:-}" ]] && project_set_single_select "$item" "$STATUS_FIELD_ID" "$STATUS_OPT_TODO" 2>/dev/null || true
|
|
310
|
+
[[ -n "$plan" && -n "${PLAN_LINK_FIELD_ID:-}" ]] && project_set_text "$item" "$PLAN_LINK_FIELD_ID" "$plan" 2>/dev/null || true
|
|
311
|
+
fi
|
|
312
|
+
fi
|
|
313
|
+
fi
|
|
314
|
+
printf '%s' "$num"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# kit task close — set the board Status to Done and close the issue (with a summary comment).
|
|
318
|
+
# Plan-badge flip stays in the skill (it edits a repo file → needs a worktree/PR). Args: <num> <summary> [pr-num]
|
|
319
|
+
kto_task_close() {
|
|
320
|
+
local num="${1:-}" summary="${2:-}"
|
|
321
|
+
local repo issue_json state item
|
|
322
|
+
repo="$(kto_repo)"
|
|
323
|
+
[[ -n "$num" ]] || { echo "✗ kto_task_close: issue number required" >&2; return 1; }
|
|
324
|
+
[[ -n "$summary" ]] || { echo "✗ kto_task_close: a summary is required" >&2; return 1; }
|
|
325
|
+
|
|
326
|
+
issue_json=$(gh issue view "$num" --repo "$repo" --json state,projectItems) || return 1
|
|
327
|
+
state=$(printf '%s' "$issue_json" | jq -r .state)
|
|
328
|
+
|
|
329
|
+
if kto_board_enabled && type load_project_ids >/dev/null 2>&1 && load_project_ids 2>/dev/null; then
|
|
330
|
+
item=$(project_find_item_by_issue "$num" 2>/dev/null || true)
|
|
331
|
+
[[ -n "$item" && -n "${STATUS_OPT_DONE:-}" ]] && \
|
|
332
|
+
project_set_single_select "$item" "$STATUS_FIELD_ID" "$STATUS_OPT_DONE" 2>/dev/null && \
|
|
333
|
+
echo "✓ Board Status → Done for #$num" || true
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
if [[ "$state" == "OPEN" ]]; then
|
|
337
|
+
gh issue close "$num" --repo "$repo" --comment "$summary" && echo "✓ Closed issue #$num"
|
|
338
|
+
else
|
|
339
|
+
echo " issue #$num already $state — board status updated only"
|
|
340
|
+
fi
|
|
341
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pr-evidence.sh — attach evidence (build/typecheck logs, screenshots) to a PR as a comment.
|
|
3
|
+
#
|
|
4
|
+
# A PR should carry the proof that its gates passed (the log, the rendered screen), not just a
|
|
5
|
+
# prose claim — the "evidence" half of a no-mistakes agentic workflow. This is the sourceable
|
|
6
|
+
# helper the kit-effort-pr / kit-task-pr flow calls after the PR is open.
|
|
7
|
+
#
|
|
8
|
+
# Source it: source scripts/lib/pr-evidence.sh
|
|
9
|
+
#
|
|
10
|
+
# Functions:
|
|
11
|
+
# pr_attach_evidence <pr-number> <evidence-file> [caption] post the evidence into the PR
|
|
12
|
+
# pr_evidence_usage print how kit-effort-pr should call it
|
|
13
|
+
#
|
|
14
|
+
# Best-effort by contract: a missing gh, missing args, or missing file warns + returns 0 so it
|
|
15
|
+
# NEVER hard-fails the caller. Callers should still append `|| true` for set -e safety.
|
|
16
|
+
#
|
|
17
|
+
# Requires: gh, jq not needed; uses git/coreutils. bash 3.2 compatible.
|
|
18
|
+
#
|
|
19
|
+
# Env:
|
|
20
|
+
# PR_EVIDENCE_REPO target repo in OWNER/REPO form; empty (default) = let gh resolve it
|
|
21
|
+
# from the current repo (omits --repo)
|
|
22
|
+
# KIT_EVIDENCE_URL_BASE host base for images — set it and an image embeds as 
|
|
23
|
+
# KIT_EVIDENCE_MAX_BYTES inline truncation cap for text/log files (default 60000)
|
|
24
|
+
|
|
25
|
+
PR_EVIDENCE_REPO="${PR_EVIDENCE_REPO:-}"
|
|
26
|
+
|
|
27
|
+
# True when the file extension is a raster/vector image GitHub would render if uploaded.
|
|
28
|
+
_pr_evidence_is_image() {
|
|
29
|
+
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
|
|
30
|
+
*.png|*.jpg|*.jpeg|*.gif|*.webp|*.svg|*.bmp|*.tif|*.tiff) return 0 ;;
|
|
31
|
+
*) return 1 ;;
|
|
32
|
+
esac
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Map a file extension to a fenced-code language hint.
|
|
36
|
+
_pr_evidence_lang() {
|
|
37
|
+
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
|
|
38
|
+
*.json) echo json ;;
|
|
39
|
+
*.diff|*.patch) echo diff ;;
|
|
40
|
+
*.md|*.markdown) echo "" ;;
|
|
41
|
+
*.sh|*.bash) echo bash ;;
|
|
42
|
+
*.ts|*.tsx) echo ts ;;
|
|
43
|
+
*.js|*.jsx) echo js ;;
|
|
44
|
+
*) echo text ;;
|
|
45
|
+
esac
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Echo a backtick fence (>= 3) longer than any backtick run in the file, so log content
|
|
49
|
+
# that itself contains ``` cannot break out of the code block.
|
|
50
|
+
_pr_evidence_fence() {
|
|
51
|
+
local file="$1" longest n
|
|
52
|
+
longest=$(grep -oE '`+' "$file" 2>/dev/null | awk '{ if (length($0) > m) m = length($0) } END { print m+0 }')
|
|
53
|
+
n=3
|
|
54
|
+
[[ "${longest:-0}" -ge 3 ]] && n=$((longest + 1))
|
|
55
|
+
printf '%*s' "$n" '' | tr ' ' '`'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Build the comment body for a text/log evidence file (inlined, fenced, truncation-aware).
|
|
59
|
+
_pr_evidence_text_body() {
|
|
60
|
+
local caption="$1" file="$2"
|
|
61
|
+
local max="${KIT_EVIDENCE_MAX_BYTES:-60000}"
|
|
62
|
+
local lang fence size
|
|
63
|
+
lang="$(_pr_evidence_lang "$file")"
|
|
64
|
+
fence="$(_pr_evidence_fence "$file")"
|
|
65
|
+
size=$(wc -c <"$file" 2>/dev/null | tr -d '[:space:]'); size="${size:-0}"
|
|
66
|
+
|
|
67
|
+
printf '## Evidence — %s\n\n' "$caption"
|
|
68
|
+
printf '_Source: `%s`_\n\n' "$(basename "$file")"
|
|
69
|
+
printf '%s%s\n' "$fence" "$lang"
|
|
70
|
+
if [[ "$size" -gt "$max" ]]; then
|
|
71
|
+
head -c "$max" "$file"
|
|
72
|
+
printf '\n%s\n\n' "$fence"
|
|
73
|
+
printf '_…truncated — showing the first %s of %s bytes. Full file: `%s`._\n' "$max" "$size" "$file"
|
|
74
|
+
else
|
|
75
|
+
cat "$file"
|
|
76
|
+
printf '\n%s\n' "$fence"
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Build the comment body for an image evidence file.
|
|
81
|
+
# HONEST about the limitation: GitHub has no clean REST/CLI endpoint to upload a binary to a
|
|
82
|
+
# comment (uploads go through an undocumented, web-only multipart endpoint that gh does not
|
|
83
|
+
# expose). So an image is REFERENCED by local path + instructions; only when KIT_EVIDENCE_URL_BASE
|
|
84
|
+
# points at a host that already serves the file do we embed it with .
|
|
85
|
+
_pr_evidence_image_body() {
|
|
86
|
+
local caption="$1" abs="$2" base="$3"
|
|
87
|
+
printf '## Evidence — %s\n\n' "$caption"
|
|
88
|
+
if [[ -n "${KIT_EVIDENCE_URL_BASE:-}" ]]; then
|
|
89
|
+
printf '\n\n' "$caption" "${KIT_EVIDENCE_URL_BASE%/}" "$base"
|
|
90
|
+
printf '_Embedded via `KIT_EVIDENCE_URL_BASE`. A broken image means the file is not hosted there yet._\n\n'
|
|
91
|
+
fi
|
|
92
|
+
printf '> Image evidence is **referenced, not uploaded**: GitHub exposes no clean REST/CLI\n'
|
|
93
|
+
printf '> endpoint to attach a binary to a comment (the upload path is an undocumented,\n'
|
|
94
|
+
printf '> web-only multipart endpoint that `gh` does not surface). To inline this image,\n'
|
|
95
|
+
printf '> drag-and-drop the file into the PR comment box in the browser, or host it and set\n'
|
|
96
|
+
printf '> `KIT_EVIDENCE_URL_BASE` so this helper embeds it.\n\n'
|
|
97
|
+
printf 'Local file: `%s`\n' "$abs"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# pr_attach_evidence <pr-number> <evidence-file> [caption]
|
|
101
|
+
# Post the evidence into the PR as a comment. Text/log files inline in a fenced block; images are
|
|
102
|
+
# referenced (see _pr_evidence_image_body). Caption defaults to the file basename.
|
|
103
|
+
pr_attach_evidence() {
|
|
104
|
+
local pr="${1:-}" file="${2:-}" caption="${3:-}"
|
|
105
|
+
|
|
106
|
+
if [[ -z "$pr" || -z "$file" ]]; then
|
|
107
|
+
echo "pr_attach_evidence: need <pr-number> <evidence-file> [caption] — skipping" >&2
|
|
108
|
+
pr_evidence_usage >&2
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
112
|
+
echo "pr_attach_evidence: gh CLI not found — cannot post evidence to PR #$pr (skipping)" >&2
|
|
113
|
+
return 0
|
|
114
|
+
fi
|
|
115
|
+
if [[ ! -f "$file" ]]; then
|
|
116
|
+
echo "pr_attach_evidence: evidence file not found: $file (skipping)" >&2
|
|
117
|
+
return 0
|
|
118
|
+
fi
|
|
119
|
+
[[ -n "$caption" ]] || caption="$(basename "$file")"
|
|
120
|
+
|
|
121
|
+
local repo abs tmp
|
|
122
|
+
repo="${PR_EVIDENCE_REPO:-}"
|
|
123
|
+
abs="$(cd "$(dirname "$file")" 2>/dev/null && printf '%s/%s' "$(pwd)" "$(basename "$file")")"
|
|
124
|
+
[[ -n "$abs" ]] || abs="$file"
|
|
125
|
+
|
|
126
|
+
tmp="$(mktemp 2>/dev/null)" || { echo "pr_attach_evidence: mktemp failed (skipping)" >&2; return 0; }
|
|
127
|
+
|
|
128
|
+
if _pr_evidence_is_image "$file"; then
|
|
129
|
+
_pr_evidence_image_body "$caption" "$abs" "$(basename "$file")" >"$tmp"
|
|
130
|
+
else
|
|
131
|
+
_pr_evidence_text_body "$caption" "$file" >"$tmp"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# Omit --repo when PR_EVIDENCE_REPO is empty so gh resolves the repo from the current directory.
|
|
135
|
+
local posted=1
|
|
136
|
+
if [[ -n "$repo" ]]; then
|
|
137
|
+
gh pr comment "$pr" --repo "$repo" --body-file "$tmp" >/dev/null 2>&1 && posted=0
|
|
138
|
+
else
|
|
139
|
+
gh pr comment "$pr" --body-file "$tmp" >/dev/null 2>&1 && posted=0
|
|
140
|
+
fi
|
|
141
|
+
if [[ "$posted" -eq 0 ]]; then
|
|
142
|
+
echo "✓ evidence attached to PR #$pr ($caption)" >&2
|
|
143
|
+
rm -f "$tmp"
|
|
144
|
+
return 0
|
|
145
|
+
fi
|
|
146
|
+
echo "✗ failed to post evidence comment to PR #$pr — body kept at $tmp" >&2
|
|
147
|
+
return 1
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# pr_evidence_usage — how kit-effort-pr / kit-task-pr should call this helper.
|
|
151
|
+
pr_evidence_usage() {
|
|
152
|
+
cat <<'USAGE'
|
|
153
|
+
pr-evidence.sh — attach PR evidence (build/typecheck logs, screenshots) as a PR comment.
|
|
154
|
+
|
|
155
|
+
source scripts/lib/pr-evidence.sh
|
|
156
|
+
pr_attach_evidence <pr-number> <evidence-file> [caption]
|
|
157
|
+
|
|
158
|
+
kit-effort-pr should call it AFTER the PR is opened, once per gate artifact:
|
|
159
|
+
|
|
160
|
+
pr_num=$(gh pr list --head "$branch" --json number --jq '.[0].number')
|
|
161
|
+
pr_attach_evidence "$pr_num" build.log "build" || true
|
|
162
|
+
pr_attach_evidence "$pr_num" typecheck.log "typecheck" || true
|
|
163
|
+
pr_attach_evidence "$pr_num" screen.png "rendered — success state" || true
|
|
164
|
+
|
|
165
|
+
Text/log files are inlined in an auto-sized fenced block (truncated past
|
|
166
|
+
KIT_EVIDENCE_MAX_BYTES, default 60000). Images CANNOT be uploaded via gh/REST, so they
|
|
167
|
+
are referenced by local path + instructions; set KIT_EVIDENCE_URL_BASE to a host base
|
|
168
|
+
and the helper embeds  instead.
|
|
169
|
+
|
|
170
|
+
Env: PR_EVIDENCE_REPO (OWNER/REPO; empty = gh resolves it from the current repo) ·
|
|
171
|
+
KIT_EVIDENCE_URL_BASE · KIT_EVIDENCE_MAX_BYTES (default 60000).
|
|
172
|
+
USAGE
|
|
173
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# project-scan.sh — agnostic project detection. Reports what cckit is pointed at, from the
|
|
3
|
+
# filesystem only (no baked-in project knowledge). Usage: source it && project_scan [dir]
|
|
4
|
+
project_scan() {
|
|
5
|
+
local dir="${1:-$PWD}" root stack=() kit="none"
|
|
6
|
+
root="$(git -C "$dir" rev-parse --show-toplevel 2>/dev/null || printf '%s' "$dir")"
|
|
7
|
+
[ -f "$root/package.json" ] && stack+=("node")
|
|
8
|
+
[ -f "$root/pyproject.toml" ] && stack+=("python")
|
|
9
|
+
[ -f "$root/go.mod" ] && stack+=("go")
|
|
10
|
+
[ -f "$root/Cargo.toml" ] && stack+=("rust")
|
|
11
|
+
if [ -f "$root/cckit.config.json" ]; then kit="configured"
|
|
12
|
+
elif [ -d "$root/.cckit" ]; then kit="partial"
|
|
13
|
+
elif [ -d "$root/.claude" ]; then kit="claude-only"; fi
|
|
14
|
+
printf '{"root":"%s","stack":[%s],"kit":"%s"}\n' \
|
|
15
|
+
"$root" "$(printf '"%s",' "${stack[@]:-}" | sed 's/,$//;s/""//')" "$kit"
|
|
16
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# claude-kit — React + framework detection for /kit-annotate.
|
|
3
|
+
# Source it, then call `react_detect [target_dir]`; it sets/exports REACT_* vars.
|
|
4
|
+
# Specificity-ordered: Next.js → React Router v7 (framework mode) → Vite → generic React.
|
|
5
|
+
# (A flat "is vite present?" check is unreliable — Next/RR apps often carry vite for tests.)
|
|
6
|
+
# Requires: jq. Safe to source (no `set -e`); never aborts the caller.
|
|
7
|
+
|
|
8
|
+
react_detect() {
|
|
9
|
+
local dir="${1:-$PWD}" pkg deps
|
|
10
|
+
# defaults
|
|
11
|
+
REACT_DETECTED="false"; REACT_FRAMEWORK=""; REACT_VERSION=""
|
|
12
|
+
REACT_ENTRY_FILE=""; REACT_PKG_MANAGER="npm"; REACT_ROUTER_LIB="false"
|
|
13
|
+
export REACT_DETECTED REACT_FRAMEWORK REACT_VERSION REACT_ENTRY_FILE REACT_PKG_MANAGER REACT_ROUTER_LIB
|
|
14
|
+
|
|
15
|
+
pkg="$dir/package.json"
|
|
16
|
+
[[ -f "$pkg" ]] || return 0
|
|
17
|
+
command -v jq >/dev/null 2>&1 || return 0
|
|
18
|
+
|
|
19
|
+
# merged dependency name list (deps + devDeps); tolerate malformed JSON
|
|
20
|
+
deps="$(jq -r '((.dependencies // {}) + (.devDependencies // {})) | keys[]?' "$pkg" 2>/dev/null)" || return 0
|
|
21
|
+
_has() { printf '%s\n' "$deps" | grep -qx "$1"; }
|
|
22
|
+
|
|
23
|
+
_has react || return 0
|
|
24
|
+
REACT_DETECTED="true"
|
|
25
|
+
REACT_VERSION="$(jq -r '(.dependencies.react // .devDependencies.react // "")' "$pkg" 2>/dev/null | tr -d '^~ ')"
|
|
26
|
+
|
|
27
|
+
# package manager from lockfile
|
|
28
|
+
if [[ -f "$dir/pnpm-lock.yaml" ]]; then REACT_PKG_MANAGER="pnpm"
|
|
29
|
+
elif [[ -f "$dir/yarn.lock" ]]; then REACT_PKG_MANAGER="yarn"
|
|
30
|
+
elif [[ -f "$dir/bun.lockb" ]]; then REACT_PKG_MANAGER="bun"
|
|
31
|
+
else REACT_PKG_MANAGER="npm"; fi
|
|
32
|
+
|
|
33
|
+
local has_next_cfg=false has_vite_cfg=false has_rr_cfg=false
|
|
34
|
+
ls "$dir"/next.config.* >/dev/null 2>&1 && has_next_cfg=true
|
|
35
|
+
ls "$dir"/vite.config.* >/dev/null 2>&1 && has_vite_cfg=true
|
|
36
|
+
ls "$dir"/react-router.config.* >/dev/null 2>&1 && has_rr_cfg=true
|
|
37
|
+
|
|
38
|
+
local f
|
|
39
|
+
if _has next || $has_next_cfg; then
|
|
40
|
+
# 1. Next.js (match first — Next apps frequently also list vite/vitest)
|
|
41
|
+
REACT_FRAMEWORK="next"
|
|
42
|
+
for f in app/layout.tsx app/layout.jsx app/layout.js src/app/layout.tsx \
|
|
43
|
+
pages/_app.tsx pages/_app.jsx pages/_app.js src/pages/_app.tsx; do
|
|
44
|
+
[[ -f "$dir/$f" ]] && { REACT_ENTRY_FILE="$f"; break; }
|
|
45
|
+
done
|
|
46
|
+
elif _has @react-router/dev || $has_rr_cfg; then
|
|
47
|
+
# 2. React Router v7 framework mode (Vite-based, but NOT a plain Vite SPA)
|
|
48
|
+
REACT_FRAMEWORK="react-router"
|
|
49
|
+
for f in app/root.tsx app/root.jsx app/root.js src/root.tsx; do
|
|
50
|
+
[[ -f "$dir/$f" ]] && { REACT_ENTRY_FILE="$f"; break; }
|
|
51
|
+
done
|
|
52
|
+
elif _has @vitejs/plugin-react || _has @vitejs/plugin-react-swc || { _has vite && $has_vite_cfg; }; then
|
|
53
|
+
# 3. Vite (plain React SPA)
|
|
54
|
+
REACT_FRAMEWORK="vite"
|
|
55
|
+
for f in src/main.tsx src/main.jsx src/index.tsx src/index.jsx src/main.ts; do
|
|
56
|
+
[[ -f "$dir/$f" ]] && { REACT_ENTRY_FILE="$f"; break; }
|
|
57
|
+
done
|
|
58
|
+
else
|
|
59
|
+
# 4. React present but framework unrecognized (CRA / Gatsby / Preact-compat / custom)
|
|
60
|
+
REACT_FRAMEWORK="react"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# react-router(-dom) WITHOUT @react-router/dev = a routing library, not a framework target
|
|
64
|
+
if { _has react-router || _has react-router-dom; } && ! _has @react-router/dev; then
|
|
65
|
+
REACT_ROUTER_LIB="true"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
unset -f _has
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Run standalone → print a one-line summary (handy for --dry-run / debugging).
|
|
73
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
74
|
+
react_detect "${1:-$PWD}"
|
|
75
|
+
printf 'detected=%s framework=%s version=%s entry=%s pm=%s router_lib=%s\n' \
|
|
76
|
+
"$REACT_DETECTED" "$REACT_FRAMEWORK" "${REACT_VERSION:-?}" \
|
|
77
|
+
"${REACT_ENTRY_FILE:-?}" "$REACT_PKG_MANAGER" "$REACT_ROUTER_LIB"
|
|
78
|
+
fi
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# role-identity.sh — cheap per-role identity (NO separate GitHub accounts).
|
|
3
|
+
#
|
|
4
|
+
# Maps a role slug (designer, backend, tech-lead, …) to a git commit AUTHOR and a
|
|
5
|
+
# one-line signature. The GitHub actor stays the maintainer's token — this only sets
|
|
6
|
+
# the commit *author* and a signature line so a role's work READS as that role.
|
|
7
|
+
# Real distinct handles/avatars are a separate, heavier effort (bot accounts / GitHub App).
|
|
8
|
+
#
|
|
9
|
+
# Source it: source scripts/lib/role-identity.sh
|
|
10
|
+
#
|
|
11
|
+
# Tunables (env):
|
|
12
|
+
# KIT_ROLE_EMAIL_DOMAIN synthetic author-email domain (default: agents.local — not a real inbox)
|
|
13
|
+
# KIT_AGENT_LABEL suffix in the author/sig label (default: agent)
|
|
14
|
+
KIT_ROLE_EMAIL_DOMAIN="${KIT_ROLE_EMAIL_DOMAIN:-agents.local}"
|
|
15
|
+
KIT_AGENT_LABEL="${KIT_AGENT_LABEL:-agent}"
|
|
16
|
+
|
|
17
|
+
# role_display <slug> -> human label (empty for unknown/blank)
|
|
18
|
+
role_display() {
|
|
19
|
+
case "$1" in
|
|
20
|
+
designer) echo "Designer" ;;
|
|
21
|
+
devops) echo "DevOps" ;;
|
|
22
|
+
pm) echo "PM" ;;
|
|
23
|
+
frontend) echo "Frontend" ;;
|
|
24
|
+
tauri) echo "Tauri" ;;
|
|
25
|
+
ai-eng) echo "AI Eng" ;;
|
|
26
|
+
security) echo "Security" ;;
|
|
27
|
+
qa) echo "QA" ;;
|
|
28
|
+
tech-lead) echo "Tech Lead" ;;
|
|
29
|
+
research) echo "Research" ;;
|
|
30
|
+
backend) echo "Backend" ;;
|
|
31
|
+
*) echo "" ;;
|
|
32
|
+
esac
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# role_git_author <slug> -> "Name|email" (empty when no/unknown role: caller keeps the default identity)
|
|
36
|
+
role_git_author() {
|
|
37
|
+
local d; d="$(role_display "$1")"
|
|
38
|
+
[[ -z "$d" ]] && return 0
|
|
39
|
+
printf '%s (%s)|%s@%s' "$d" "$KIT_AGENT_LABEL" "$1" "$KIT_ROLE_EMAIL_DOMAIN"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# role_signature <slug> -> one markdown line for issue/PR bodies (empty when no role)
|
|
43
|
+
role_signature() {
|
|
44
|
+
local d; d="$(role_display "$1")"
|
|
45
|
+
[[ -z "$d" ]] && return 0
|
|
46
|
+
printf -- '— %s · %s (acting via the maintainer account)' "$d" "$KIT_AGENT_LABEL"
|
|
47
|
+
}
|