@erclx/aitk 0.20.0 → 0.22.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.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-address-review/REQUIREMENT.md +1 -0
- package/claude/skills/claude-address-review/SKILL.md +4 -3
- package/claude/skills/claude-diagram/SKILL.md +1 -1
- package/claude/skills/claude-pr-review/REQUIREMENT.md +1 -0
- package/claude/skills/claude-pr-review/SKILL.md +1 -1
- package/claude/skills/git-followup/REQUIREMENT.md +1 -1
- package/claude/skills/git-followup/SKILL.md +1 -1
- package/claude/skills/git-issue/REQUIREMENT.md +1 -1
- package/claude/skills/git-issue/SKILL.md +1 -1
- package/claude/skills/git-pr/REQUIREMENT.md +1 -1
- package/claude/skills/git-pr/SKILL.md +1 -1
- package/claude/skills/git-split/REQUIREMENT.md +1 -1
- package/claude/skills/git-split/SKILL.md +1 -1
- package/docs/agents.md +39 -0
- package/docs/target-projects.md +1 -1
- package/package.json +1 -1
- package/scripts/core/verify.sh +9 -0
- package/scripts/lib/sandbox-path.sh +75 -6
- package/src/cli.ts +4 -0
- package/src/commands/context.ts +321 -0
- package/src/context/audit.ts +227 -0
- package/src/context/citations.ts +167 -0
- package/src/context/folders.ts +91 -0
- package/src/context/index-drift.ts +64 -0
- package/standards/index.md +1 -1
- package/standards/prose.md +16 -2
- package/standards/versioning.md +11 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { dirname, relative, resolve } from 'node:path'
|
|
3
|
+
import { INDEX_FILE, listIndexes } from '@/indexes/walk'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Folder names under `.claude/` audited by default.
|
|
7
|
+
*
|
|
8
|
+
* The index-plus-entry contract on its own over-collects. `.claude/standards/`
|
|
9
|
+
* also carries a generated index beside entries with frontmatter, and auditing
|
|
10
|
+
* it would measure the consumed copy of `standards/` against a rule written for
|
|
11
|
+
* per-domain narrative. This list names the folders `standards/context.md`
|
|
12
|
+
* governs, and `--folder` admits another without an edit here.
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_FOLDERS: readonly string[] = [
|
|
15
|
+
'context',
|
|
16
|
+
'diagrams',
|
|
17
|
+
'wireframes',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
export interface AuditedFolder {
|
|
21
|
+
/** Repo-relative folder path, used verbatim in every report line. */
|
|
22
|
+
readonly rel: string
|
|
23
|
+
readonly indexPath: string
|
|
24
|
+
/** Absolute paths of the folder's own entries, excluding its `index.md`. */
|
|
25
|
+
readonly entries: readonly string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Names the requested folders that actually exist, which is the citation
|
|
30
|
+
* check's scope.
|
|
31
|
+
*
|
|
32
|
+
* A skill or seed pointing into `.claude/wireframes/` is a live instruction for
|
|
33
|
+
* a project that carries the folder and says nothing about one that does not.
|
|
34
|
+
* Checking a path into an absent folder would fail eight shipped references
|
|
35
|
+
* here for the sole reason that this repository has no wireframes.
|
|
36
|
+
*/
|
|
37
|
+
export function presentNames(folders: readonly AuditedFolder[]): string[] {
|
|
38
|
+
return [...new Set(folders.map((folder) => folder.rel.split('/')[1]))]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function readEntries(dir: string): Promise<string[]> {
|
|
42
|
+
const paths: string[] = []
|
|
43
|
+
|
|
44
|
+
for await (const name of new Bun.Glob('*.md').scan({
|
|
45
|
+
cwd: dir,
|
|
46
|
+
onlyFiles: true,
|
|
47
|
+
dot: true,
|
|
48
|
+
})) {
|
|
49
|
+
if (name === INDEX_FILE) continue
|
|
50
|
+
paths.push(resolve(dir, name))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return paths.sort()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolves the folders to audit under `root`.
|
|
58
|
+
*
|
|
59
|
+
* A named folder contributes its own entries plus those of every nested index
|
|
60
|
+
* folder beneath it, so a domain that outgrew one file and split is audited at
|
|
61
|
+
* the same grain as one that did not. Discovery of the nested folders runs
|
|
62
|
+
* through the shared walker, which is what keeps `.gitignore` and the vendored
|
|
63
|
+
* prune governing this scan as well as index regeneration.
|
|
64
|
+
*
|
|
65
|
+
* A requested folder that does not exist is dropped rather than reported. The
|
|
66
|
+
* default list names three folders and a project carrying one of them is the
|
|
67
|
+
* ordinary case.
|
|
68
|
+
*/
|
|
69
|
+
export async function resolveFolders(
|
|
70
|
+
root: string,
|
|
71
|
+
names: readonly string[] = DEFAULT_FOLDERS,
|
|
72
|
+
): Promise<AuditedFolder[]> {
|
|
73
|
+
const folders: AuditedFolder[] = []
|
|
74
|
+
|
|
75
|
+
for (const name of names) {
|
|
76
|
+
const dir = resolve(root, '.claude', name)
|
|
77
|
+
if (!existsSync(`${dir}/${INDEX_FILE}`)) continue
|
|
78
|
+
|
|
79
|
+
const dirs = [dir, ...(await listIndexes(dir)).map(dirname)]
|
|
80
|
+
|
|
81
|
+
for (const each of [...new Set(dirs)].sort()) {
|
|
82
|
+
folders.push({
|
|
83
|
+
rel: relative(root, each),
|
|
84
|
+
indexPath: `${each}/${INDEX_FILE}`,
|
|
85
|
+
entries: await readEntries(each),
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return folders
|
|
91
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { basename, dirname, resolve } from 'node:path'
|
|
4
|
+
import type { AuditedFolder } from '@/context/folders'
|
|
5
|
+
|
|
6
|
+
const ENTRY_LINK = /^-\s+\[[^\]]*\]\(([^)]+)\)/
|
|
7
|
+
|
|
8
|
+
export interface FolderDrift {
|
|
9
|
+
readonly rel: string
|
|
10
|
+
/** Entry files present in the folder that the index does not link. */
|
|
11
|
+
readonly unlisted: readonly string[]
|
|
12
|
+
/** Names the index links that resolve to nothing on disk. */
|
|
13
|
+
readonly missing: readonly string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Collects the link targets of an index's catalog bullets. */
|
|
17
|
+
export function listedTargets(source: string): string[] {
|
|
18
|
+
const targets: string[] = []
|
|
19
|
+
|
|
20
|
+
for (const line of source.split('\n')) {
|
|
21
|
+
const match = line.match(ENTRY_LINK)
|
|
22
|
+
if (match) targets.push(match[1])
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return targets
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compares one folder's index against its siblings in both directions.
|
|
30
|
+
*
|
|
31
|
+
* Both halves are reported because they fail differently. An unlisted entry is
|
|
32
|
+
* invisible to a session reading the catalog to choose what to open, while a
|
|
33
|
+
* listed name with no file sends one to a path that opens nothing. Neither
|
|
34
|
+
* surfaces from reading the index alone.
|
|
35
|
+
*/
|
|
36
|
+
export async function auditFolder(folder: AuditedFolder): Promise<FolderDrift> {
|
|
37
|
+
const dir = dirname(folder.indexPath)
|
|
38
|
+
const source = await readFile(folder.indexPath, 'utf8')
|
|
39
|
+
const targets = listedTargets(source)
|
|
40
|
+
|
|
41
|
+
// A sub-catalog is linked as `<name>/index.md` and its entries are audited
|
|
42
|
+
// as their own folder, so only the leading segment is compared here.
|
|
43
|
+
const listed = new Set(targets.map((target) => target.split('/')[0]))
|
|
44
|
+
|
|
45
|
+
const unlisted = folder.entries
|
|
46
|
+
.map((path) => basename(path))
|
|
47
|
+
.filter((name) => !listed.has(name))
|
|
48
|
+
|
|
49
|
+
const missing = targets.filter((target) => !existsSync(resolve(dir, target)))
|
|
50
|
+
|
|
51
|
+
return { rel: folder.rel, unlisted, missing }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function auditIndexes(
|
|
55
|
+
folders: readonly AuditedFolder[],
|
|
56
|
+
): Promise<FolderDrift[]> {
|
|
57
|
+
const drift: FolderDrift[] = []
|
|
58
|
+
|
|
59
|
+
for (const folder of folders) {
|
|
60
|
+
drift.push(await auditFolder(folder))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return drift
|
|
64
|
+
}
|
package/standards/index.md
CHANGED
|
@@ -18,5 +18,5 @@ Reference docs for consistent authoring across the toolkit and target projects.
|
|
|
18
18
|
- [Claude skill reference](skill.md): Claude skill structure and authoring rules
|
|
19
19
|
- [Standard reference](standard.md): Shape and content rules for authoring a standard
|
|
20
20
|
- [Tasks reference](tasks.md): Folder layout, filename convention, and content rules for .claude/tasks/
|
|
21
|
-
- [Versioning reference](versioning.md): Phase label vs semver discipline across tasks, PRs, commits, and tags
|
|
21
|
+
- [Versioning reference](versioning.md): Phase label vs semver discipline across tasks, PRs, reviews, issues, commits, and tags
|
|
22
22
|
- [Wireframe reference](wireframes.md): Shape and content rules for .claude/wireframes/<surface>.md files
|
package/standards/prose.md
CHANGED
|
@@ -74,10 +74,14 @@ Applies to markdown reference docs, READMEs, and inline documentation in repos.
|
|
|
74
74
|
- Do not address the reader as a participant (`Let's`, `Here's`, `Here are`). State the content directly.
|
|
75
75
|
- Commit to a position. Do not hedge in clusters (`It might be worth considering`) or use false balance (`While X is true, Y is also important`). Recommend, or state the tradeoff.
|
|
76
76
|
|
|
77
|
-
##
|
|
77
|
+
## Pre-publish scan
|
|
78
78
|
|
|
79
79
|
Wherever text leaves through a channel no automated check covers, the author is the only gate and runs this scan. Text sent to another service, written to a path the project's checks exclude, and text inside a fenced block are the usual cases. The surface that publishes the text is what knows which gap applies, so it names its own rather than reading one here.
|
|
80
80
|
|
|
81
|
+
Run the scan as an explicit step against the finished text. Having read this file before drafting does not cover it, because the check has to happen after the text exists.
|
|
82
|
+
|
|
83
|
+
### Banned characters
|
|
84
|
+
|
|
81
85
|
Scan the drafted text and rewrite each occurrence:
|
|
82
86
|
|
|
83
87
|
- `—` (em dash): split into two sentences, or use a comma
|
|
@@ -85,7 +89,17 @@ Scan the drafted text and rewrite each occurrence:
|
|
|
85
89
|
|
|
86
90
|
Restructure the sentence rather than substituting the character. A semicolon swapped for a period leaves both clauses in the order the semicolon chose, which is the shape the ban exists to remove.
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
### Phase labels
|
|
93
|
+
|
|
94
|
+
The versioning standard beside this file holds the label rule and the table of surfaces. Read it at scan time rather than working the format from memory.
|
|
95
|
+
|
|
96
|
+
Scope this check by destination. Text published to a remote takes it. Text scanned on its way into the repository, where the reader has the task board, takes the character checks alone.
|
|
97
|
+
|
|
98
|
+
### An unreadable source
|
|
99
|
+
|
|
100
|
+
Stop and name the source when one this scan reaches for cannot be read. Do not scan what resolved and report the result.
|
|
101
|
+
|
|
102
|
+
A run that covers half its sources and says nothing is worse than one that visibly did not happen, because the surfaces running this scan are the ones that describe themselves as the only gate. A clean result from a half-run scan is read as coverage.
|
|
89
103
|
|
|
90
104
|
## Frontmatter descriptions
|
|
91
105
|
|
package/standards/versioning.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Versioning reference
|
|
3
|
-
description: Phase label vs semver discipline across tasks, PRs, commits, and tags
|
|
3
|
+
description: Phase label vs semver discipline across tasks, PRs, reviews, issues, commits, and tags
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Versioning reference
|
|
@@ -31,6 +31,9 @@ External release identity used in git tags and release notes. Independent of pha
|
|
|
31
31
|
| `.claude/tasks/` | yes | no |
|
|
32
32
|
| Chat with the operator | yes | no |
|
|
33
33
|
| PR titles | no | only when the PR cuts a release |
|
|
34
|
+
| PR bodies | no | only when the PR cuts a release |
|
|
35
|
+
| Review comments | no | only when referencing a release |
|
|
36
|
+
| Issue titles and bodies | no | only when referencing a release |
|
|
34
37
|
| Commit messages | no | only when the commit cuts a release |
|
|
35
38
|
| Git tags | no | yes |
|
|
36
39
|
| README and `CHANGELOG.md` | no | yes |
|
|
@@ -41,6 +44,13 @@ External release identity used in git tags and release notes. Independent of pha
|
|
|
41
44
|
- Commit subjects do not embed phase labels.
|
|
42
45
|
- Git tags use semver only. Phase labels never become tags.
|
|
43
46
|
- A PR that cuts a release may reference its semver tag in the title or body. Phase labels still do not appear.
|
|
47
|
+
- PR bodies, review comments, and issue text name the change itself, never the internal stream that scheduled it. Describe the work rather than the label it was planned under.
|
|
48
|
+
|
|
49
|
+
## Pre-publish check
|
|
50
|
+
|
|
51
|
+
Text bound for a remote is checked for phase labels against the finished draft, before it is sent. A body, comment, or title reaches a reader who has no task board, so a label that survives to publication cannot be resolved by anyone downstream.
|
|
52
|
+
|
|
53
|
+
The surface publishing the text is the last gate on it. Where no automated check covers that surface, the author performs the check as an explicit step rather than relying on having read this file while drafting.
|
|
44
54
|
|
|
45
55
|
## Why
|
|
46
56
|
|