@erclx/aitk 0.81.0 → 0.82.0

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": "0.81.0",
4
+ "version": "0.82.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -9,6 +9,8 @@ description: What pull request generation is for, the gaps it closes, and what i
9
9
 
10
10
  Without this skill, a pull request body is written from memory of the branch rather than from its diff, so it describes the intent and omits what the work turned into. Testing boxes get ticked from intent, which records what was meant to run instead of what ran, and a reviewer trusts the list. A second push either errors on create or opens a duplicate pull request, and banned characters survive into a body the hook never sees.
11
11
 
12
+ Every pull request also lands unlabelled, so a merged list is a wall of titles with no way to filter it by surface. A reader looking for what changed in one domain reads all of them, and the conventional commit type in the title says what kind of change it is rather than where.
13
+
12
14
  A lookup that resolves by head branch alone carries its own failure. A branch name reused after an earlier pull request merged resolves to the closed one, so the run rewrites a merged pull request's title and body and reports its URL as the one it opened. Both fields are recoverable only through the issue timeline and the edit history, and nothing reports that the write landed on the wrong object.
13
15
 
14
16
  ## Must
@@ -21,6 +23,9 @@ A lookup that resolves by head branch alone carries its own failure. A branch na
21
23
  - Detect an open pull request and edit it in place, so a follow-up push keeps the body in sync instead of failing
22
24
  - Scope that detection to an open pull request on the current head and the default base, so neither a reused branch name nor a second base resolves the wrong one
23
25
  - Resolve the pull request once and reuse what that resolution returned, so the number recorded never depends on how a lookup ranks two pull requests sharing a head
26
+ - Label from the paths the branch changed, against a map the project declares, so the label set belongs to the project rather than to the skill
27
+ - Apply labels after the pull request exists, so a label the remote does not carry costs a warning rather than the pull request
28
+ - Report a refused label, since a warning nothing surfaces leaves the run indistinguishable from one that labelled
24
29
 
25
30
  ## Must not
26
31
 
@@ -28,12 +33,15 @@ A lookup that resolves by head branch alone carries its own failure. A branch na
28
33
  - Put a request for the reviewer in the Testing list, since a request is not a result
29
34
  - Create a second pull request when one is open
30
35
  - Edit a pull request that is not open, or record its number on a task
36
+ - Name a domain of any one project in the skill body or its references
37
+ - Create a label the map names and the remote lacks
31
38
  - Emit anything after the result line
32
39
 
33
40
  ## Guards
34
41
 
35
42
  - Branch name does not conform: stop and route to the skill that renames
36
43
  - No commits ahead of main: stop
44
+ - No label map in the project: label nothing and warn nothing, since an absent map is a decision rather than a gap
37
45
 
38
46
  ## Out of scope
39
47
 
@@ -11,6 +11,7 @@ Read these files in parallel:
11
11
 
12
12
  - `${CLAUDE_SKILL_DIR}/references/branch.md`: branch format, valid types, and constraints
13
13
  - `${CLAUDE_SKILL_DIR}/references/pr.md`: structure, rules, and banned phrases
14
+ - `${CLAUDE_SKILL_DIR}/references/labels.md`: label map format, matching, and the missing-label warning. Skip when the project has no `.claude/pr-labels.toml`.
14
15
  - `.claude/standards/prose.md` from the project root: voice and banned words for all generated text
15
16
  - `.claude/standards/markdown.md` from the project root: punctuation and formatting for all generated text
16
17
  - `.claude/standards/versioning.md` from the project root: phase label vs semver discipline
@@ -29,6 +30,7 @@ Then run these commands in parallel to gather git context:
29
30
  - `git branch --show-current 2>/dev/null || echo "unknown"`
30
31
  - `git log <base>..HEAD --oneline 2>/dev/null || echo "NO_COMMITS"`
31
32
  - `git diff <base> HEAD -- . ':(exclude)*.lock' ':(exclude)*-lock.json' 2>/dev/null || echo "NO_DIFF"`
33
+ - `git diff --name-only <base> HEAD 2>/dev/null || echo "NO_FILES"`
32
34
 
33
35
  ## Diff baseline
34
36
 
@@ -82,15 +84,24 @@ The lookup scopes to the base as well as the head. One head can carry open pull
82
84
 
83
85
  A detached HEAD gives `git branch --show-current` an empty result, which would read as no open pull request and create a second one. The branch-name guard above stops the run first, since an empty name does not match `<type>/<description>`.
84
86
 
87
+ ### Labels
88
+
89
+ Read `.claude/pr-labels.toml` from the project root. A project that has not declared a map gets no labels and no warning, since a label set this skill supplied would be a guess about that project's surfaces.
90
+
91
+ When the file resolves, match it against the name-only diff per `${CLAUDE_SKILL_DIR}/references/labels.md` and write the comma-separated result into `pr_labels` below. Leave `pr_labels` empty when no map resolves or no prefix matches, which skips the labelling command rather than running it against nothing.
92
+
85
93
  ### Final command
86
94
 
87
95
  Detect an open pull request on the current head and branch: edit it in place when one exists, create it otherwise. This keeps the body in sync on a follow-up push instead of erroring on `gh pr create`.
88
96
 
97
+ Labels apply after that branch converges, against a pull request that already exists. `gh pr create --label` refuses a label the remote does not carry and opens no pull request at all, so a mistyped row costs the run rather than the label. One command after the fact also covers the create and the edit path together.
98
+
89
99
  ```bash
90
100
  mkdir -p .claude/.tmp/pr
91
101
  cat <<'BODY' > .claude/.tmp/pr/body.md
92
102
  <body content following pr.md template exactly>
93
103
  BODY
104
+ pr_labels="<comma-separated labels, empty when the map resolves to nothing>"
94
105
  git push -u origin HEAD || exit 1
95
106
  base_branch=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name) || exit 1
96
107
  pr_number=$(gh pr list --head "$(git branch --show-current)" --base "$base_branch" --state open --json number --jq '.[0].number // empty')
@@ -100,6 +111,10 @@ else
100
111
  pr_url=$(gh pr create --title "<title>" --body-file .claude/.tmp/pr/body.md) || exit 1
101
112
  pr_number=${pr_url##*/}
102
113
  fi
114
+ if [ -n "$pr_labels" ]; then
115
+ gh pr edit "$pr_number" --add-label "$pr_labels" >/dev/null ||
116
+ printf 'Label apply failed. Create a missing label with: gh label create <name>\n' >&2
117
+ fi
103
118
  rm -rf .claude/.tmp/pr
104
119
  printf 'number=%s\nurl=%s\n' "$pr_number" "$pr_url"
105
120
  ```
@@ -124,8 +139,12 @@ The number is what lets the merge close the task. Every merge on `main` is a squ
124
139
 
125
140
  ## After execution
126
141
 
127
- Respond with exactly one line, using the `url` the final command printed:
142
+ Respond with one line, using the `url` the final command printed:
128
143
 
129
144
  `✅ PR: <url>`
130
145
 
146
+ Add a second line only when the labelling command printed its warning, quoting the label `gh` refused:
147
+
148
+ `⚠️ Labels not applied: <what gh reported>`
149
+
131
150
  Do not add any other text.
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: Label reference
3
+ description: Path-to-label map format, prefix matching against the changed set, and the one-time label creation a map requires
4
+ ---
5
+
6
+ # Label reference
7
+
8
+ ## Scope
9
+
10
+ Governs the labels a pull request carries: the map a project declares, how a changed path resolves to a label, and what a label missing from the remote costs.
11
+
12
+ Does not govern:
13
+
14
+ - Pull request title and body: `pr.md`
15
+ - Branch naming: `branch.md`
16
+ - Issue labels, which `git-issue` derives from the issue type rather than from a diff
17
+
18
+ ## Map format
19
+
20
+ The map lives at `.claude/pr-labels.toml` in the project root. Each key under `[domains]` is a label name and its value is the list of path prefixes that earn it.
21
+
22
+ ```toml
23
+ [domains]
24
+ api = ["services/api/"]
25
+ web = ["apps/web/", "packages/ui/"]
26
+ ```
27
+
28
+ A label takes more than one prefix when two folders read as one surface. Two labels may claim overlapping prefixes, and a path under both earns both.
29
+
30
+ The map is authored by hand and nothing detects a directory it fails to cover, so a surface added after the map was written labels nothing until someone adds a row.
31
+
32
+ ## Matching
33
+
34
+ - Take the changed set from `git diff --name-only <base> HEAD`, resolved against the same base as the diff the body is written from
35
+ - A path earns a label when the path starts with one of that label's prefixes
36
+ - Collect the distinct labels across the whole set, ordered as the map declares them, so two runs over one branch produce one string
37
+ - Pass the result as a single comma-separated value. An empty result runs no labelling step.
38
+
39
+ ## Applying
40
+
41
+ Apply labels after the pull request resolves, never as a flag on the create. `gh pr create --label` fails whole on a label the remote does not carry, so a name the map got wrong opens no pull request at all and the run stops with the branch pushed and nothing to review. A `gh pr edit --add-label` against a pull request that already exists costs a warning instead, and it is one command across both the create and the edit path rather than two flags that have to stay in step.
42
+
43
+ `--add-label` adds and never removes. A label a person applied by hand is not this skill's to strip, so a domain that stops applying between two pushes keeps its label until someone takes it off.
44
+
45
+ ## A label the remote does not carry
46
+
47
+ Warn and continue rather than creating it, naming the command in the warning:
48
+
49
+ ```bash
50
+ gh label create <name> --description "<text>"
51
+ ```
52
+
53
+ Creating a label writes to the repository settings from a run the user invoked to open a pull request, and a label created from a typo in the map is harder to notice than a warning is.
54
+
55
+ The refusal names the label it rejected and applies none of the set, so a warning that reaches nobody leaves the run reading exactly like one that labelled. Surface it beside the result line rather than letting the pull request URL stand alone.
56
+
57
+ ## Release pull requests
58
+
59
+ Release automation opens its own pull requests without this skill and applies its own labels, so nothing here needs a skip condition for them.
@@ -55,7 +55,9 @@ Every weight and depth measure counts the text a reader is shown. A link reduces
55
55
 
56
56
  A code span is walked around rather than through, so a path quoting link or angle-bracket syntax keeps the width the page gives it. Masking inside one takes back the decision to count it, and the placeholders this toolkit writes are where that shows.
57
57
 
58
- The paragraph check measures both halves of one rule. `markdown.md` caps a paragraph at four sentences, and a sentence cap on its own is satisfied by writing fewer and longer ones: 36 paragraphs in this corpus sit inside four sentences and past the weight checkpoint, and the heaviest of those runs 1121 characters. The standard therefore states a weight beside the sentence cap, and the verb reads it as its own checkpoint.
58
+ The paragraph check measures both halves of one rule. `markdown.md` caps a paragraph at four sentences, and a sentence cap on its own is satisfied by writing fewer and longer ones: 15 paragraphs in this corpus sit inside four sentences and past the weight checkpoint, and the heaviest of those runs 886 characters. The standard therefore states a weight beside the sentence cap, and the verb reads it as its own checkpoint.
59
+
60
+ A sentence boundary closes on terminal punctuation ahead of a capital or a code span. The capital is what keeps a version pin and a decimal from each reading as two sentences, and the code span is admitted beside it because a command name opening a sentence carries no capital to find.
59
61
 
60
62
  The paragraph weight sits at 700 and the bullet weight at 400. Both shipped at 400, because the paragraph number was borrowed from the bullet rule when the two checks landed together, and each has since been read against a sample of its own. They are separate checkpoints in the standard and separate patterns in the parser, so a read that moves one leaves the other where it is.
61
63
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.81.0",
4
+ "version": "0.82.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -15,8 +15,14 @@ const BLOCKQUOTE = /^\s*>/
15
15
  * one. An abbreviation ahead of a capitalized word still counts, which
16
16
  * over-reports by one on the sentence that carries it and is why this measure
17
17
  * reports rather than gates.
18
+ *
19
+ * A code span opens a sentence that carries no capital at all, since a command
20
+ * name is lowercase and the requirement above would catch nothing there. The
21
+ * backtick therefore stands alone rather than joining the delimiters a capital
22
+ * follows, and the price is that a span opening a fragment mid-paragraph reads
23
+ * as a sentence start.
18
24
  */
19
- const SENTENCE_END = /[.!?]["'’”)\]]*(?=\s+["'“(\[]*[A-Z]|\s*$)/g
25
+ const SENTENCE_END = /[.!?]["'’”)\]]*(?=\s+(?:["'“(\[]*[A-Z]|`)|\s*$)/g
20
26
 
21
27
  const NUMBER_WORDS: Record<string, number> = {
22
28
  one: 1,