@erclx/aitk 1.6.0 → 1.7.1

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "1.6.0",
4
+ "version": "1.7.1",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -110,7 +110,7 @@ aitk records push --json
110
110
  aitk records pull
111
111
  ```
112
112
 
113
- The backed folders are `groundwork`, `intake`, `memory`, `plans`, `plans-archive`, `review`, `review-archive`, `task-archive`, `tasks`, and `teach`, all under `.claude/`. They are the gitignored Claude group minus `.claude/.tmp`, which is deletable without loss, and `.claude/worktrees/`, whose contents belong to the project repository already. The list is a constant rather than configuration, matching the four folder names `validate` hardcodes.
113
+ The backed folders are `groundwork`, `intake`, `memory`, `plans`, `plans-archive`, `review`, `review-archive`, `task-archive`, `tasks`, and `teach`, all under `.claude/`. They are the Claude ignore group the claude manifest ships, minus three entries: `.claude/.tmp`, which is deletable without loss, `.claude/worktrees/`, whose contents belong to the project repository already, and `.claude/.records.git/`, which is the history the other ten are pushed into. The list is a constant rather than configuration, matching the four folder names `validate` hardcodes.
114
114
 
115
115
  Records are gitignored by design, so the history lives in a second git directory at `.claude/.records.git` with `.claude/` as its work tree. Every path stays where it is, which is what a separate checkout could not do. The verbs stage the ten folders by explicit pathspec with `--force`, so nothing outside them can enter the index however the ignore rules read, and the project working tree and its index are never touched.
116
116
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "1.6.0",
4
+ "version": "1.7.1",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+ set -o pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
7
+
8
+ SCRIPTS_ROOT="$PROJECT_ROOT/scripts"
9
+ PALETTE_SOURCE="$SCRIPTS_ROOT/lib/ui.sh"
10
+
11
+ # `find` writes to stderr and returns non-zero for a missing root, inside a
12
+ # process substitution whose status nothing reads. Without this the walk covers
13
+ # nothing and the check reports the tree clean having never measured it.
14
+ if [ ! -f "$PALETTE_SOURCE" ]; then
15
+ echo "No palette source at ${PALETTE_SOURCE#"$PROJECT_ROOT/"}, color sourcing unverifiable." >&2
16
+ exit 1
17
+ fi
18
+
19
+ # Any SGR sequence in either spelling. The cursor and erase sequences an
20
+ # interactive prompt writes end in a letter instead, and they run only where a
21
+ # terminal already exists, so they are not what this measures.
22
+ SGR='\\(033|e|x1b)\[[0-9;]*m'
23
+
24
+ # Sixteen copies of one decision is what made color impossible to turn off on
25
+ # the TypeScript side. A rule nothing enforces rebuilds them the next time a
26
+ # script wants grey, so the escape lives in the shared library and every other
27
+ # script reads the palette that library derives for its stream.
28
+ offenders=""
29
+ while IFS= read -r file; do
30
+ [ "$file" = "$PALETTE_SOURCE" ] && continue
31
+ if grep -qE "$SGR" "$file"; then
32
+ offenders="$offenders ${file#"$PROJECT_ROOT/"}"$'\n'
33
+ fi
34
+ done < <(find "$SCRIPTS_ROOT" -type f -name '*.sh')
35
+
36
+ if [ -n "$offenders" ]; then
37
+ echo "A color escape is defined outside the shared library:" >&2
38
+ printf '%s' "$offenders" >&2
39
+ echo "Source scripts/lib/ui.sh and read its palette, so one answer covers every writer." >&2
40
+ exit 1
41
+ fi
@@ -2,19 +2,13 @@
2
2
  set -e
3
3
  set -o pipefail
4
4
 
5
- GREEN='\033[0;32m'
6
- RED='\033[0;31m'
7
- WHITE='\033[1;37m'
8
- GREY='\033[0;90m'
9
- NC='\033[0m'
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
10
7
 
11
- NESTED="${VERIFY_NESTED:-false}"
8
+ # shellcheck source=/dev/null
9
+ source "$PROJECT_ROOT/scripts/lib/ui.sh"
12
10
 
13
- log_info() { echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1"; }
14
- log_error() {
15
- echo -e "${GREY}│${NC} ${RED}✗${NC} $1"
16
- exit 1
17
- }
11
+ NESTED="${VERIFY_NESTED:-false}"
18
12
 
19
13
  OUTPUT_FILE=".claude/.tmp/project/PROJECT-SNAPSHOT.md"
20
14
 
@@ -117,15 +111,23 @@ write_snapshot() {
117
111
  main() {
118
112
  check_dependencies
119
113
 
120
- if [ "$NESTED" = false ]; then echo -e "${GREY}┌${NC}"; fi
114
+ if [ "$NESTED" = false ]; then open_timeline; fi
121
115
 
122
- echo -e "${GREY}├${NC} ${WHITE}Snapshot${NC}"
116
+ log_step "Snapshot"
123
117
  write_snapshot
124
118
  log_info "Written to $OUTPUT_FILE"
125
119
 
120
+ # The whole frame goes to stderr, since the run writes its document to a file
121
+ # and leaves stdout carrying nothing. Edges on stdout with the body on stderr
122
+ # would capture as a frame with no body inside it.
126
123
  if [ "$NESTED" = false ]; then
127
- echo -e "${GREY}└${NC}\n"
128
- echo -e "${GREEN}✓ Snapshot complete${NC}"
124
+ close_timeline
125
+ # All six are declared to scope what `set_palette` assigns, so the four this
126
+ # line never spells stay out of the globals a sourcing script reads.
127
+ # shellcheck disable=SC2034
128
+ local GREEN RED YELLOW WHITE GREY NC
129
+ set_palette 2
130
+ echo -e "\n${GREEN}✓ Snapshot complete${NC}" >&2
129
131
  fi
130
132
  }
131
133
 
@@ -595,6 +595,7 @@ main() {
595
595
  log_step "Shell"
596
596
  if has_changed '\.sh$|^package\.json$'; then
597
597
  run_check "bun run check:shell" "Shell check failed"
598
+ run_check "bash $PROJECT_ROOT/scripts/core/check-color-source.sh" "A color escape is defined outside scripts/lib/ui.sh."
598
599
  log_info "Shell check passed"
599
600
  else
600
601
  log_info "Skipped, no shell changes"
package/scripts/lib/ui.sh CHANGED
@@ -12,26 +12,85 @@ fi
12
12
  # shellcheck source=/dev/null
13
13
  source "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/sandbox-path.sh"
14
14
 
15
- GREEN='\033[0;32m'
16
- RED='\033[0;31m'
17
- YELLOW='\033[0;33m'
18
- WHITE='\033[1;37m'
19
- GREY='\033[0;90m'
20
- NC='\033[0m'
21
-
22
- log_info() { echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1" >&2; }
23
- log_warn() { echo -e "${GREY}│${NC} ${YELLOW}!${NC} $1" >&2; }
15
+ # `NO_COLOR` follows the published convention, where any non-empty value turns
16
+ # color off whatever the value says.
17
+ supports_color() {
18
+ [ -z "${NO_COLOR:-}" ] && [ -t "$1" ]
19
+ }
20
+
21
+ # Assigns the six palette names for one file descriptor. The blank palette keeps
22
+ # every frame character and drops only the escapes, so a captured run still
23
+ # reads as one block.
24
+ #
25
+ # Bash returns a string and nothing richer, so the palette arrives through the
26
+ # caller's own scope. A writer declaring all six `local` before calling this
27
+ # keeps the assignment to itself, which is what leaves the source-time answer
28
+ # below standing for anything that sources this file.
29
+ set_palette() {
30
+ if supports_color "$1"; then
31
+ GREEN='\033[0;32m'
32
+ RED='\033[0;31m'
33
+ YELLOW='\033[0;33m'
34
+ WHITE='\033[1;37m'
35
+ GREY='\033[0;90m'
36
+ NC='\033[0m'
37
+ else
38
+ GREEN=''
39
+ RED=''
40
+ YELLOW=''
41
+ WHITE=''
42
+ GREY=''
43
+ NC=''
44
+ fi
45
+ }
46
+
47
+ # Every writer below frames to stderr and asks about that stream at write time,
48
+ # so no answer is frozen before a caller's redirect is in place. This one answers
49
+ # for stdout instead, because it is read by a sourcing script writing its own
50
+ # frame with a bare `echo`, and that is the stream a bare `echo` reaches.
51
+ set_palette 1
52
+
53
+ log_info() {
54
+ local GREEN RED YELLOW WHITE GREY NC
55
+ set_palette 2
56
+ echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1" >&2
57
+ }
58
+ log_warn() {
59
+ local GREEN RED YELLOW WHITE GREY NC
60
+ set_palette 2
61
+ echo -e "${GREY}│${NC} ${YELLOW}!${NC} $1" >&2
62
+ }
24
63
  log_error() {
64
+ local GREEN RED YELLOW WHITE GREY NC
65
+ set_palette 2
25
66
  echo -e "${GREY}│${NC} ${RED}✗${NC} $1" >&2
26
67
  exit 1
27
68
  }
28
- log_step() { echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}" >&2; }
29
- log_add() { echo -e "${GREY}│${NC} ${GREEN}+${NC} $1" >&2; }
30
- log_rem() { echo -e "${GREY}│${NC} ${RED}-${NC} $1" >&2; }
69
+ log_step() {
70
+ local GREEN RED YELLOW WHITE GREY NC
71
+ set_palette 2
72
+ echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}" >&2
73
+ }
74
+ log_add() {
75
+ local GREEN RED YELLOW WHITE GREY NC
76
+ set_palette 2
77
+ echo -e "${GREY}│${NC} ${GREEN}+${NC} $1" >&2
78
+ }
79
+ log_rem() {
80
+ local GREEN RED YELLOW WHITE GREY NC
81
+ set_palette 2
82
+ echo -e "${GREY}│${NC} ${RED}-${NC} $1" >&2
83
+ }
31
84
 
32
- pipe_output() { while IFS= read -r line; do echo -e "${GREY}│${NC} $line" >&2; done; }
85
+ pipe_output() {
86
+ local GREEN RED YELLOW WHITE GREY NC
87
+ set_palette 2
88
+ while IFS= read -r line; do echo -e "${GREY}│${NC} $line" >&2; done
89
+ }
33
90
 
34
91
  open_timeline() {
92
+ local GREEN RED YELLOW WHITE GREY NC
93
+ set_palette 2
35
94
  echo -e "${GREY}┌${NC}" >&2
36
95
  if [ -n "${1:-}" ]; then
37
96
  echo -e "${GREY}│${NC} ${WHITE}$1${NC}" >&2
@@ -39,6 +98,8 @@ open_timeline() {
39
98
  }
40
99
 
41
100
  close_timeline() {
101
+ local GREEN RED YELLOW WHITE GREY NC
102
+ set_palette 2
42
103
  echo -e "${GREY}└${NC}" >&2
43
104
  }
44
105
 
@@ -52,6 +113,8 @@ guard_root() {
52
113
  }
53
114
 
54
115
  require_project_root() {
116
+ local GREEN RED YELLOW WHITE GREY NC
117
+ set_palette 2
55
118
  local sandbox
56
119
  sandbox="$(resolve_sandbox_dir)"
57
120
  if [[ "$PWD" == "$sandbox" || "$PWD" == "$sandbox"/* ]]; then
@@ -65,6 +128,8 @@ require_project_root() {
65
128
  }
66
129
 
67
130
  ask() {
131
+ local GREEN RED YELLOW WHITE GREY NC
132
+ set_palette 2
68
133
  local prompt_text=$1
69
134
  local var_name=$2
70
135
  local default_val=$3
@@ -107,6 +172,8 @@ ask() {
107
172
  }
108
173
 
109
174
  select_or_route_scenario() {
175
+ local GREEN RED YELLOW WHITE GREY NC
176
+ set_palette 2
110
177
  local prompt_text=$1
111
178
  shift
112
179
  local options=("$@")
@@ -122,6 +189,8 @@ select_or_route_scenario() {
122
189
  }
123
190
 
124
191
  select_option() {
192
+ local GREEN RED YELLOW WHITE GREY NC
193
+ set_palette 2
125
194
  local prompt_text=$1
126
195
  shift
127
196
  local options=("$@")
@@ -320,6 +320,8 @@ execute_sandbox_and_commit() {
320
320
  }
321
321
 
322
322
  finalize_sandbox_run() {
323
+ local GREEN RED YELLOW WHITE GREY NC
324
+ set_palette 2
323
325
  trap - EXIT
324
326
  close_timeline
325
327
  echo "" >&2
@@ -327,6 +329,8 @@ finalize_sandbox_run() {
327
329
  }
328
330
 
329
331
  cmd_clean() {
332
+ local GREEN RED YELLOW WHITE GREY NC
333
+ set_palette 2
330
334
  log_step "Removing sandbox"
331
335
  rm -rf "$SANDBOX"
332
336
  log_rem "$SANDBOX"
@@ -337,6 +341,8 @@ cmd_clean() {
337
341
  }
338
342
 
339
343
  reset_sandbox() {
344
+ local GREEN RED YELLOW WHITE GREY NC
345
+ set_palette 2
340
346
  if [ ! -d "$SANDBOX/.git" ]; then
341
347
  log_error "No sandbox found. Run \`aitk sandbox\` first."
342
348
  fi
@@ -91,6 +91,12 @@ main() {
91
91
  local stack="$1"
92
92
  local target="${2:-.}"
93
93
 
94
+ # Every frame below goes to stderr, so it asks about that stream rather than
95
+ # reading the source-time answer, which is for stdout and is what `show_help`
96
+ # above wants. Declared after the help branch so that call keeps it.
97
+ local GREEN RED YELLOW WHITE GREY NC
98
+ set_palette 2
99
+
94
100
  echo -e "${GREY}┌${NC}" >&2
95
101
  echo -e "${GREY}│${NC} ${WHITE}aitk tooling ref${NC}" >&2
96
102
 
@@ -5,10 +5,17 @@ import { gitEnv } from '@/git-env'
5
5
 
6
6
  /**
7
7
  * The folders a backup carries, relative to `.claude/`. They are the `# Claude`
8
- * group in `.gitignore` minus `.claude/.tmp`, which is defined as deletable
9
- * without loss, and `.claude/worktrees/`, whose contents belong to the
10
- * enclosing repository already. The list is spelled out rather than read off
11
- * that group so adding an ignore entry cannot silently enlarge the payload.
8
+ * group the claude manifest ships, minus three: `.claude/.tmp`, which is
9
+ * defined as deletable without loss, `.claude/worktrees/`, whose contents
10
+ * belong to the enclosing repository already, and `.claude/.records.git/`,
11
+ * which is the history the other ten are pushed into. The list is spelled out
12
+ * rather than read off that group so adding an ignore entry cannot silently
13
+ * enlarge the payload.
14
+ *
15
+ * The manifest group is the one this reads rather than the enclosing
16
+ * repository's own `.gitignore`, which spreads the same entries across two
17
+ * headers and carries `.claude/README.md` that no target receives. Subtracting
18
+ * three from that file instead yields eleven names against this list of ten.
12
19
  *
13
20
  * `RECORD_KINDS` in `validate.ts` overlaps this on five names and carries one
14
21
  * more that no backup reaches. The two lists differ on purpose: one is what a
@@ -8,4 +8,4 @@ runtime = ""
8
8
  scaffold = ""
9
9
 
10
10
  [gitignore]
11
- "# Claude" = [".claude/.tmp/", ".claude/groundwork/", ".claude/intake/", ".claude/memory/", ".claude/plans/", ".claude/plans-archive/", ".claude/review/", ".claude/review-archive/", ".claude/task-archive/", ".claude/worktrees/", ".claude/tasks/"]
11
+ "# Claude" = [".claude/.records.git/", ".claude/.tmp/", ".claude/groundwork/", ".claude/intake/", ".claude/memory/", ".claude/plans/", ".claude/plans-archive/", ".claude/review/", ".claude/review-archive/", ".claude/task-archive/", ".claude/worktrees/", ".claude/tasks/", ".claude/teach/"]
@@ -50,7 +50,7 @@ A project installed before the diagram surface became a folder still holds `.cla
50
50
 
51
51
  ## Gitignore
52
52
 
53
- - `# Claude`: `.claude/.tmp/`, `.claude/groundwork/`, `.claude/intake/`, `.claude/memory/`, `.claude/plans/`, `.claude/plans-archive/`, `.claude/review/`, `.claude/review-archive/`, `.claude/task-archive/`, `.claude/worktrees/`, `.claude/tasks/`
53
+ - `# Claude`: `.claude/.records.git/`, `.claude/.tmp/`, `.claude/groundwork/`, `.claude/intake/`, `.claude/memory/`, `.claude/plans/`, `.claude/plans-archive/`, `.claude/review/`, `.claude/review-archive/`, `.claude/task-archive/`, `.claude/worktrees/`, `.claude/tasks/`, `.claude/teach/`
54
54
 
55
55
  ## CLI
56
56