@erclx/aitk 0.36.0 → 0.38.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.
@@ -17,6 +17,12 @@ export const DEFAULT_FOLDERS: readonly string[] = [
17
17
  'wireframes',
18
18
  ]
19
19
 
20
+ /** The base every folder in the default list sits under. */
21
+ const CLAUDE_BASE = '.claude'
22
+
23
+ /** The project root, reached only by a name the caller asked for. */
24
+ const ROOT_BASE = '.'
25
+
20
26
  export interface AuditedFolder {
21
27
  /**
22
28
  * The requested folder name this was resolved under, which is what says
@@ -25,24 +31,51 @@ export interface AuditedFolder {
25
31
  * `.claude/context/claude-plugin` is governed as `context`.
26
32
  */
27
33
  readonly name: string
34
+ /**
35
+ * The base the name resolved under, which is what says whether the folder is
36
+ * in the citation check's scope.
37
+ */
38
+ readonly base: string
28
39
  /** Repo-relative folder path, used verbatim in every report line. */
29
40
  readonly rel: string
30
41
  readonly indexPath: string
31
42
  /** Absolute paths of the folder's own entries, excluding its `index.md`. */
32
43
  readonly entries: readonly string[]
44
+ /**
45
+ * Whether this is a sub-area a domain split into rather than the folder
46
+ * named under `.claude/`.
47
+ *
48
+ * The entries of a split folder describe one domain between them, so a rule
49
+ * about what a domain declares is answered by the folder. The entries of the
50
+ * named folder are one domain each, and a rule answered by a sibling there
51
+ * would let one entry stand in for domains it says nothing about.
52
+ */
53
+ readonly nested: boolean
33
54
  }
34
55
 
35
56
  /**
36
- * Names the requested folders that actually exist, which is the citation
37
- * check's scope.
57
+ * Names the requested `.claude/` folders that actually exist, which is the
58
+ * citation check's scope.
38
59
  *
39
60
  * A skill or seed pointing into `.claude/wireframes/` is a live instruction for
40
61
  * a project that carries the folder and says nothing about one that does not.
41
62
  * Checking a path into an absent folder would fail eight shipped references
42
63
  * here for the sole reason that this repository has no wireframes.
64
+ *
65
+ * A folder resolved at the project root is measured and stays out of this. The
66
+ * pattern the citation check builds spells the `.claude/` prefix, so admitting
67
+ * a root name there would check `.claude/<name>/` paths the audit never read.
68
+ * Widening the pattern to the root spelling is a separate change, since a bare
69
+ * `docs/x.md` appears in prose that references nothing.
43
70
  */
44
71
  export function presentNames(folders: readonly AuditedFolder[]): string[] {
45
- return [...new Set(folders.map((folder) => folder.name))]
72
+ return [
73
+ ...new Set(
74
+ folders
75
+ .filter((folder) => folder.base === CLAUDE_BASE)
76
+ .map((folder) => folder.name),
77
+ ),
78
+ ]
46
79
  }
47
80
 
48
81
  async function readEntries(dir: string): Promise<string[]> {
@@ -60,6 +93,40 @@ async function readEntries(dir: string): Promise<string[]> {
60
93
  return paths.sort()
61
94
  }
62
95
 
96
+ export interface FolderResolution {
97
+ /** Every folder that resolved, with the nested splits beneath each. */
98
+ readonly folders: readonly AuditedFolder[]
99
+ /**
100
+ * Requested names that resolved under no base, reported rather than dropped.
101
+ * Which absences are worth saying out loud is the caller's judgment: a
102
+ * default folder a project does not carry is ordinary, and a name passed by
103
+ * hand that resolves nowhere is a typo that would otherwise read as a pass.
104
+ */
105
+ readonly missing: readonly string[]
106
+ }
107
+
108
+ export interface ResolveOptions {
109
+ /**
110
+ * Whether a name may resolve at the project root when `.claude/` does not
111
+ * carry it. False for the default list, which names three folders a project
112
+ * is expected to hold under `.claude/` and nowhere else.
113
+ */
114
+ readonly canResolveAtRoot?: boolean
115
+ }
116
+
117
+ function locate(
118
+ root: string,
119
+ name: string,
120
+ bases: readonly string[],
121
+ ): { readonly dir: string; readonly base: string } | undefined {
122
+ for (const base of bases) {
123
+ const dir = resolve(root, base, name)
124
+ if (existsSync(`${dir}/${INDEX_FILE}`)) return { dir, base }
125
+ }
126
+
127
+ return undefined
128
+ }
129
+
63
130
  /**
64
131
  * Resolves the folders to audit under `root`.
65
132
  *
@@ -67,33 +134,49 @@ async function readEntries(dir: string): Promise<string[]> {
67
134
  * folder beneath it, so a domain that outgrew one file and split is audited at
68
135
  * the same grain as one that did not. Discovery of the nested folders runs
69
136
  * through the shared walker, which is what keeps `.gitignore` and the vendored
70
- * prune governing this scan as well as index regeneration.
137
+ * prune governing this scan as well as index regeneration. That prune is what
138
+ * lets a root folder be walked at all, since a name at the project root sits
139
+ * beside `node_modules` and a build output.
71
140
  *
72
- * A requested folder that does not exist is dropped rather than reported. The
73
- * default list names three folders and a project carrying one of them is the
74
- * ordinary case.
141
+ * The project root is reached only when the caller opts in, so a target holding
142
+ * a root `wireframes/` is not audited against a standard it never adopted by
143
+ * the mere act of running the command. `.claude/` still wins a name carried by
144
+ * both, and the scope line prints the resolved path so a caller reads which
145
+ * base was taken rather than inferring it.
146
+ *
147
+ * Nothing above this asks where a folder came from. A name that resolves at the
148
+ * root is measured by every rule that generalizes and gated out of the rules a
149
+ * single standard carries, which `governsContent` decides from the name.
75
150
  */
76
151
  export async function resolveFolders(
77
152
  root: string,
78
153
  names: readonly string[] = DEFAULT_FOLDERS,
79
- ): Promise<AuditedFolder[]> {
154
+ { canResolveAtRoot = false }: ResolveOptions = {},
155
+ ): Promise<FolderResolution> {
156
+ const bases = canResolveAtRoot ? [CLAUDE_BASE, ROOT_BASE] : [CLAUDE_BASE]
80
157
  const folders: AuditedFolder[] = []
158
+ const missing: string[] = []
81
159
 
82
160
  for (const name of names) {
83
- const dir = resolve(root, '.claude', name)
84
- if (!existsSync(`${dir}/${INDEX_FILE}`)) continue
161
+ const found = locate(root, name, bases)
162
+ if (!found) {
163
+ missing.push(name)
164
+ continue
165
+ }
85
166
 
86
- const dirs = [dir, ...(await listIndexes(dir)).map(dirname)]
167
+ const dirs = [found.dir, ...(await listIndexes(found.dir)).map(dirname)]
87
168
 
88
169
  for (const each of [...new Set(dirs)].sort()) {
89
170
  folders.push({
90
171
  name,
172
+ base: found.base,
91
173
  rel: relative(root, each),
92
174
  indexPath: `${each}/${INDEX_FILE}`,
93
175
  entries: await readEntries(each),
176
+ nested: each !== found.dir,
94
177
  })
95
178
  }
96
179
  }
97
180
 
98
- return folders
181
+ return { folders, missing }
99
182
  }
@@ -5,7 +5,13 @@ description: GitHub Actions workflow triggers and checks
5
5
 
6
6
  # CI
7
7
 
8
- GitHub Actions workflow for this project.
8
+ ## Overview
9
+
10
+ Owns the GitHub Actions workflow that gates a merge: which events start a run, and which checks have to pass before the branch can land. The checks call package scripts rather than defining commands of their own, so what each one runs is the development entry's subject.
11
+
12
+ ## Layout
13
+
14
+ - `.github/workflows/` owns the workflow definitions a trigger below starts
9
15
 
10
16
  ## Triggers
11
17
 
@@ -5,7 +5,14 @@ description: Local dev workflow, scripts, and husky hooks
5
5
 
6
6
  # Development
7
7
 
8
- Local dev workflow for this project.
8
+ ## Overview
9
+
10
+ Owns how the project runs on a developer machine: installing the toolchain, the scripts that verify a change, and the git hooks that run them before a commit or a push leaves. CI calls the same scripts from a workflow, which is the CI entry's subject.
11
+
12
+ ## Layout
13
+
14
+ - `scripts/` owns the shell scripts the package scripts below call
15
+ - `.husky/` owns the git hooks
9
16
 
10
17
  ## Setup
11
18
 
@@ -0,0 +1,11 @@
1
+ ---
2
+ title: Context
3
+ subtitle: Per-domain narrative loaded on demand
4
+ ---
5
+
6
+ # Context
7
+
8
+ Per-domain narrative loaded on demand
9
+
10
+ - [CI](ci.md): GitHub Actions workflow triggers and checks
11
+ - [Development](development.md): Local dev workflow, scripts, and husky hooks
@@ -1,8 +0,0 @@
1
- ---
2
- title: Context
3
- subtitle: Per-domain narrative loaded on demand
4
- ---
5
-
6
- # Context
7
-
8
- Per-domain narrative loaded on demand