@erclx/aitk 0.36.0 → 0.37.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/docs/agents.md +9 -3
- package/package.json +1 -1
- package/src/commands/context.ts +53 -3
- package/src/context/audit.ts +119 -0
- package/src/context/folders.ts +11 -0
- package/tooling/base/seeds/.claude/context/ci.md +7 -1
- package/tooling/base/seeds/.claude/context/development.md +8 -1
- package/tooling/base/seeds/.claude/context/index.md +11 -0
- package/tooling/claude/seeds/.claude/context/index.md +0 -8
package/docs/agents.md
CHANGED
|
@@ -80,7 +80,7 @@ Full help: `aitk <command> --help`.
|
|
|
80
80
|
| `aitk transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
|
|
81
81
|
| `aitk tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
|
|
82
82
|
| `aitk comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
|
|
83
|
-
| `aitk context audit` | Report
|
|
83
|
+
| `aitk context audit` | Report required sections, length, depth, bullet weight, cited paths, provenance, and drift |
|
|
84
84
|
| `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
|
|
85
85
|
|
|
86
86
|
### Domain commands
|
|
@@ -387,10 +387,16 @@ aitk context audit --folder context,diagrams
|
|
|
387
387
|
|
|
388
388
|
Scope defaults to `context`, `diagrams`, and `wireframes`, and a folder the project does not carry is skipped rather than reported. A domain that outgrew one file and split into `<domain>/` is audited as its own folder, so a split entry measures at the same grain as a flat one.
|
|
389
389
|
|
|
390
|
-
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for an unresolved citation. Only the citation check sets a failing code.
|
|
390
|
+
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for an unresolved citation. Only the citation check sets a failing code. Required-section, length, depth, bullet weight, table, provenance, and index findings print and return `0`, because each is a judgment and failing a push on one would make the check something to route around.
|
|
391
391
|
|
|
392
392
|
### What each check reports
|
|
393
393
|
|
|
394
|
+
The required-section check reports what does not declare `## Overview` and `## Layout`, the two sections `.claude/standards/context.md` marks required. The list is held in code beside the numeric checkpoints rather than parsed out of the standard, so it fails on a defect rather than on a rewrite of the wording around it. A heading at any level counts, because a domain that split into a folder carries its overview in a sibling named `overview.md` where the section is the `#` title and an `##` beneath it would repeat the filename. Matching exactly is what keeps `## Layout catalog` from satisfying `Layout`.
|
|
395
|
+
|
|
396
|
+
Which unit answers depends on the folder. Entries of the folder named under `.claude/` are one domain each, so each answers for itself and a finding names the entry. Entries of a folder a domain split into describe that one domain between them, so any sibling answers and a finding names the folder. Holding a split folder to the rule per file would report every child beside its `overview.md`, and rolling the named folder up would let one conforming entry stand in for every other domain sitting next to it.
|
|
397
|
+
|
|
398
|
+
It reports rather than gates, the closer call because a missing section reads more like a fact than the other judgments. What settles it is that the standard sanctions omitting `## Layout` from a domain owning no paths in the repo, and no measure separates that from an entry that forgot it. A domain covering only external tools is that case, and it reports on every run. The JSON record carries the findings as `missingSections` and the list as `checkpoints.requiredSections`.
|
|
399
|
+
|
|
394
400
|
Length and depth quote their checkpoints from `.claude/standards/context.md`: roughly 150 rendered lines for an entry, roughly 40 for a run no heading breaks. Depth measures the longest such run rather than everything under one `##`, skips fenced blocks so a markdown example does not read as three headings, and exempts a run whose lines are all list items at one indent averaging under 130 characters. The weight condition is what separates a scannable catalog of one-liners from a stack of paragraph-bullets, which reach the same count and read nothing alike.
|
|
395
401
|
|
|
396
402
|
Both checks count rendered lines rather than source lines, wrapping each line at 80 columns and summing the heights. Entries here are authored one line per bullet, so a block of fifteen paragraph-bullets occupies fifteen source lines and renders past sixty, which source counting cannot see. Measuring one checkpoint in each unit would put an entry length beside a run length that mean different things. Their exclusions still differ: the file measure counts fenced blocks and frontmatter, while the run measure skips a fence so an example cannot break the run around it. A reference-heavy entry therefore ranks by its examples, which the length legend states on every run. Runs count blank lines, which the standard leaves open, so a hand reader who drops them lands a line or two lower. Both sections state the width on every run, since a number in rendered lines cannot be reproduced without it.
|
|
@@ -401,7 +407,7 @@ The table check reports a catalog that grows a row per shipped thing, not a tabl
|
|
|
401
407
|
|
|
402
408
|
The provenance check reports the markers narrating how a domain reached its shape rather than describing what it is: a date, a change number, or a release label. The standard admits a rejected alternative and the reasoning that killed it while refusing the provenance attached to it, so a marker names a line to read rather than a line to delete. Findings group by entry and sort left to right within a line, since what a reader acts on is which file to open. Fenced blocks are excluded, which keeps a pinned version in an install command from reading as a claim the entry makes. Frontmatter is excluded with them, since the content checks read the body alone, and that is what keeps a diagram entry's dated `verified` stamp a record of its last check rather than a marker to settle. Length is the exception, counting the whole file, so a reader applying the 150-rendered-line checkpoint against the body alone lands a few lines under what the tool reports.
|
|
403
409
|
|
|
404
|
-
The provenance
|
|
410
|
+
The provenance, bullet-weight, and required-section checks cover `.claude/context/` alone, while length, depth, and the table finding reach every audited folder. The rule is stated in `.claude/standards/context.md`, which opens its scope by handing diagrams and wireframes to `diagrams.md` and `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere. The split is between kinds of rule rather than kinds of folder, and what decides it is whether the remedy is actionable rather than what the check measures. Subdividing a run and splitting a file mean something in any entry, so length and depth generalize. Moving an incident out of a bullet and keeping the decision means nothing in a folder whose entries declare no decisions, which is why bullet weight narrows despite measuring a distance like the two that do not. Required sections narrow for a plainer reason: the names are the context standard's own, and neither sibling standard states a required section at all. The scoping key is the folder an entry was audited under, so `--folder` still reaches a folder the default list does not carry, and a domain split into `context/<sub-area>/` is governed as `context`. Every run states the reach, including a run where no audited folder is the governed one. The JSON record carries it as `checkpoints.provenanceFolder` and a per-folder `governsContent`.
|
|
405
411
|
|
|
406
412
|
Index drift compares an index against its siblings in both directions. An entry the index does not link is invisible to a session choosing what to open, and a linked name resolving to nothing sends one to a path that opens nothing.
|
|
407
413
|
|
package/package.json
CHANGED
package/src/commands/context.ts
CHANGED
|
@@ -6,10 +6,13 @@ import {
|
|
|
6
6
|
governsContent,
|
|
7
7
|
LENGTH_CHECKPOINT,
|
|
8
8
|
measureFolders,
|
|
9
|
+
missingSections,
|
|
9
10
|
PEER_BULLET_CHECKPOINT,
|
|
10
11
|
PROVENANCE_FOLDER,
|
|
11
12
|
RENDER_WIDTH,
|
|
13
|
+
REQUIRED_SECTIONS,
|
|
12
14
|
RUN_CHECKPOINT,
|
|
15
|
+
type SectionFinding,
|
|
13
16
|
} from '@/context/audit'
|
|
14
17
|
import { auditCitations, type CitationReport } from '@/context/citations'
|
|
15
18
|
import {
|
|
@@ -51,7 +54,7 @@ export function register(program: Command): void {
|
|
|
51
54
|
context
|
|
52
55
|
.command('audit')
|
|
53
56
|
.description(
|
|
54
|
-
'Report entry length, depth, bullet weight, citations, provenance, and index drift',
|
|
57
|
+
'Report required sections, entry length, depth, bullet weight, citations, provenance, and index drift',
|
|
55
58
|
)
|
|
56
59
|
.argument('[path]', 'Project root, defaulting to the current directory')
|
|
57
60
|
.helpOption('-h, --help', 'Show this help message')
|
|
@@ -67,8 +70,8 @@ export function register(program: Command): void {
|
|
|
67
70
|
' 1 refused, with the reason on stderr',
|
|
68
71
|
' 2 a cited path did not resolve',
|
|
69
72
|
'',
|
|
70
|
-
'Only unresolved citations set a failing exit code.
|
|
71
|
-
'bullet, table, provenance, and index findings are advisory.',
|
|
73
|
+
'Only unresolved citations set a failing exit code. Section, length,',
|
|
74
|
+
'depth, bullet, table, provenance, and index findings are advisory.',
|
|
72
75
|
'',
|
|
73
76
|
'Examples:',
|
|
74
77
|
' aitk context audit',
|
|
@@ -131,6 +134,7 @@ async function runAudit(
|
|
|
131
134
|
|
|
132
135
|
const entries = gateOnly ? [] : await measureFolders(root, folders)
|
|
133
136
|
const drift = gateOnly ? [] : await auditIndexes(folders)
|
|
137
|
+
const sections = gateOnly ? [] : missingSections(root, folders, entries)
|
|
134
138
|
|
|
135
139
|
if (gateOnly) {
|
|
136
140
|
reportGate(citations)
|
|
@@ -138,6 +142,7 @@ async function runAudit(
|
|
|
138
142
|
intro('aitk context audit')
|
|
139
143
|
reportScope(folders)
|
|
140
144
|
reportCitations(citations)
|
|
145
|
+
reportSections(sections, folders)
|
|
141
146
|
reportLength(entries)
|
|
142
147
|
reportDepth(entries)
|
|
143
148
|
reportBullets(entries, folders)
|
|
@@ -162,6 +167,7 @@ async function runAudit(
|
|
|
162
167
|
unresolved: citations.unresolved,
|
|
163
168
|
},
|
|
164
169
|
entries,
|
|
170
|
+
missingSections: sections,
|
|
165
171
|
indexDrift: drift,
|
|
166
172
|
checkpoints: {
|
|
167
173
|
lines: LENGTH_CHECKPOINT,
|
|
@@ -171,6 +177,7 @@ async function runAudit(
|
|
|
171
177
|
peerBullet: PEER_BULLET_CHECKPOINT,
|
|
172
178
|
bullet: BULLET_CHECKPOINT,
|
|
173
179
|
provenanceFolder: PROVENANCE_FOLDER,
|
|
180
|
+
requiredSections: REQUIRED_SECTIONS,
|
|
174
181
|
},
|
|
175
182
|
})}\n`,
|
|
176
183
|
)
|
|
@@ -250,6 +257,49 @@ function reportCitations(report: ScannedCitations): void {
|
|
|
250
257
|
)
|
|
251
258
|
}
|
|
252
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Names the path each finding belongs to, which is an entry in the folder named
|
|
262
|
+
* under `.claude/` and the folder itself in a domain split across one. States
|
|
263
|
+
* the reach on every run for the reason the provenance report does.
|
|
264
|
+
*
|
|
265
|
+
* This prints ahead of the four readability measures because a missing section
|
|
266
|
+
* asks whether the entry is the right shape at all, which precedes asking
|
|
267
|
+
* whether it has grown too long.
|
|
268
|
+
*/
|
|
269
|
+
function reportSections(
|
|
270
|
+
missing: readonly SectionFinding[],
|
|
271
|
+
folders: readonly AuditedFolder[],
|
|
272
|
+
): void {
|
|
273
|
+
logStep('Sections')
|
|
274
|
+
|
|
275
|
+
const governed = folders.filter(governsContent)
|
|
276
|
+
if (governed.length === 0) {
|
|
277
|
+
logInfo(
|
|
278
|
+
`Out of scope. The list is stated in the standard governing .claude/${PROVENANCE_FOLDER}/, and no audited folder is that one.`,
|
|
279
|
+
)
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
logInfo(
|
|
284
|
+
`Covers .claude/${PROVENANCE_FOLDER}/ alone, whose standard requires ${REQUIRED_SECTIONS.join(' and ')}.`,
|
|
285
|
+
)
|
|
286
|
+
logInfo(
|
|
287
|
+
'A heading at any level counts. Each entry answers for itself, except in a domain split across a folder, where a sibling answers for the rest.',
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if (missing.length === 0) {
|
|
291
|
+
logInfo('Every entry declares each required section.')
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
logWarn(`${plural(missing.length, 'path')} short a required section`)
|
|
296
|
+
pipeOutput(
|
|
297
|
+
missing
|
|
298
|
+
.map((found) => `${found.rel} missing: ${found.missing.join(', ')}`)
|
|
299
|
+
.join('\n'),
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
253
303
|
function reportLength(entries: readonly EntryReport[]): void {
|
|
254
304
|
logStep('Length')
|
|
255
305
|
logInfo(
|
package/src/context/audit.ts
CHANGED
|
@@ -50,9 +50,25 @@ export const CATALOG_ROW_CHECKPOINT = 6
|
|
|
50
50
|
/** Share of first cells that must name an artifact for a table to qualify. */
|
|
51
51
|
const CATALOG_NAMED_RATIO = 0.6
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Sections `standards/context.md` marks required, in the order it states them.
|
|
55
|
+
*
|
|
56
|
+
* The list is held here rather than read out of the standard, the way the four
|
|
57
|
+
* checkpoints above quote their numbers. A parser over the standard's prose
|
|
58
|
+
* would decide which sections are required from the wording around them, so it
|
|
59
|
+
* fails on a rewrite of that wording rather than on a defect in an entry.
|
|
60
|
+
*
|
|
61
|
+
* These names do not generalize the way a length threshold does, which is why
|
|
62
|
+
* the measure is scoped to the folder `governsContent` names. A diagram entry
|
|
63
|
+
* declares a heading per kind and a wireframe entry per screen, and neither
|
|
64
|
+
* sibling standard states a required section at all.
|
|
65
|
+
*/
|
|
66
|
+
export const REQUIRED_SECTIONS: readonly string[] = ['Overview', 'Layout']
|
|
67
|
+
|
|
53
68
|
const FRONTMATTER = /^---\n[\s\S]*?\n---\n?/
|
|
54
69
|
const FENCE = /^\s*(```|~~~)/
|
|
55
70
|
const HEADING = /^#{1,6}\s/
|
|
71
|
+
const HEADING_TEXT = /^#{1,6}\s+(.+?)\s*$/
|
|
56
72
|
const LIST_ITEM = /^(\s*)([-*+]|\d+\.)\s+/
|
|
57
73
|
const TABLE_ROW = /^\s*\|/
|
|
58
74
|
const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
|
|
@@ -144,6 +160,23 @@ export interface EntryReport {
|
|
|
144
160
|
readonly provenance: readonly ProvenanceFinding[]
|
|
145
161
|
/** Empty for the same reason `provenance` is, and under the same folder. */
|
|
146
162
|
readonly heavyBullets: readonly BulletFinding[]
|
|
163
|
+
/**
|
|
164
|
+
* Required sections this entry declares, in the standard's order, and empty
|
|
165
|
+
* outside the folder whose standard names them. What the folder is short of
|
|
166
|
+
* is `missingSections`, since one entry answers for its siblings.
|
|
167
|
+
*/
|
|
168
|
+
readonly sections: readonly string[]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface SectionFinding {
|
|
172
|
+
/**
|
|
173
|
+
* Repo-relative path of whatever owes the sections: the entry itself in the
|
|
174
|
+
* folder named under `.claude/`, and the folder in a domain split across
|
|
175
|
+
* one, since the split folder's entries answer for each other.
|
|
176
|
+
*/
|
|
177
|
+
readonly rel: string
|
|
178
|
+
/** Required sections the path above does not declare, never empty. */
|
|
179
|
+
readonly missing: readonly string[]
|
|
147
180
|
}
|
|
148
181
|
|
|
149
182
|
interface BodyLine {
|
|
@@ -422,6 +455,38 @@ function provenance(lines: readonly BodyLine[]): ProvenanceFinding[] {
|
|
|
422
455
|
.map((each) => each.finding)
|
|
423
456
|
}
|
|
424
457
|
|
|
458
|
+
/**
|
|
459
|
+
* Finds which required sections the entry declares.
|
|
460
|
+
*
|
|
461
|
+
* A heading at any level counts rather than the `##` the standard writes its
|
|
462
|
+
* examples at. A domain that split into a folder puts its overview in a sibling
|
|
463
|
+
* named for it, where the section is the `#` title and an `##` beneath it would
|
|
464
|
+
* repeat the file's own name. All three split folders in this repository are
|
|
465
|
+
* that shape, so matching `##` alone would report every one of them. Nothing is
|
|
466
|
+
* titled for a required section without being about it, so the looser match
|
|
467
|
+
* costs no precision.
|
|
468
|
+
*
|
|
469
|
+
* Fenced blocks are skipped for the reason the scans above skip them. A
|
|
470
|
+
* standard quoted inside an example declares nothing about the entry quoting it.
|
|
471
|
+
*/
|
|
472
|
+
function declaredSections(lines: readonly BodyLine[]): string[] {
|
|
473
|
+
const found = new Set<string>()
|
|
474
|
+
let fenced = false
|
|
475
|
+
|
|
476
|
+
for (const line of lines) {
|
|
477
|
+
if (FENCE.test(line.text)) {
|
|
478
|
+
fenced = !fenced
|
|
479
|
+
continue
|
|
480
|
+
}
|
|
481
|
+
if (fenced) continue
|
|
482
|
+
|
|
483
|
+
const match = line.text.match(HEADING_TEXT)
|
|
484
|
+
if (match && REQUIRED_SECTIONS.includes(match[1])) found.add(match[1])
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return REQUIRED_SECTIONS.filter((section) => found.has(section))
|
|
488
|
+
}
|
|
489
|
+
|
|
425
490
|
/**
|
|
426
491
|
* Measures one entry, scanning for provenance only when a standard claims it.
|
|
427
492
|
*
|
|
@@ -448,6 +513,7 @@ export function measureEntry(
|
|
|
448
513
|
catalogTables: catalogTables(lines),
|
|
449
514
|
provenance: governsContent ? provenance(lines) : [],
|
|
450
515
|
heavyBullets: governsContent ? heavyBullets(lines) : [],
|
|
516
|
+
sections: governsContent ? declaredSections(lines) : [],
|
|
451
517
|
}
|
|
452
518
|
}
|
|
453
519
|
|
|
@@ -484,3 +550,56 @@ export async function measureFolders(
|
|
|
484
550
|
export function governsContent(folder: AuditedFolder): boolean {
|
|
485
551
|
return folder.name === PROVENANCE_FOLDER
|
|
486
552
|
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Names what does not declare the sections the standard requires.
|
|
556
|
+
*
|
|
557
|
+
* Which unit answers depends on what the folder is. A split folder's entries
|
|
558
|
+
* describe one domain between them and carry the overview and the layout in a
|
|
559
|
+
* sibling named for them, so any one of them answers and a per-file rule there
|
|
560
|
+
* would report every other child of all three shipped splits. The entries of
|
|
561
|
+
* the folder named under `.claude/` are one domain each, so each answers for
|
|
562
|
+
* itself. Rolling those up too was the first shape of this check, and it let a
|
|
563
|
+
* single sibling stand in for thirteen domains it says nothing about.
|
|
564
|
+
*
|
|
565
|
+
* The judgment sits in the caller because the split case needs the folder's
|
|
566
|
+
* other entries, which `measureFolders` holds and `measureEntry` does not. A
|
|
567
|
+
* folder with no entries of its own is a split parent holding an index and
|
|
568
|
+
* subfolders, and it has nothing to require a section of.
|
|
569
|
+
*
|
|
570
|
+
* The standard sanctions omitting `## Layout` from a domain owning no paths,
|
|
571
|
+
* which no measure can tell from an entry that forgot it. An entry of that
|
|
572
|
+
* shape therefore reports, which is a reason this is printed and never gated on.
|
|
573
|
+
*/
|
|
574
|
+
export function missingSections(
|
|
575
|
+
root: string,
|
|
576
|
+
folders: readonly AuditedFolder[],
|
|
577
|
+
entries: readonly EntryReport[],
|
|
578
|
+
): SectionFinding[] {
|
|
579
|
+
const byRel = new Map(entries.map((entry) => [entry.rel, entry]))
|
|
580
|
+
const findings: SectionFinding[] = []
|
|
581
|
+
|
|
582
|
+
const shortOf = (declared: readonly string[]): string[] =>
|
|
583
|
+
REQUIRED_SECTIONS.filter((name) => !declared.includes(name))
|
|
584
|
+
|
|
585
|
+
for (const folder of folders) {
|
|
586
|
+
if (!governsContent(folder) || folder.entries.length === 0) continue
|
|
587
|
+
|
|
588
|
+
const reports = folder.entries
|
|
589
|
+
.map((path) => byRel.get(relative(root, path)))
|
|
590
|
+
.filter((entry) => entry !== undefined)
|
|
591
|
+
|
|
592
|
+
if (folder.nested) {
|
|
593
|
+
const missing = shortOf(reports.flatMap((entry) => entry.sections))
|
|
594
|
+
if (missing.length > 0) findings.push({ rel: folder.rel, missing })
|
|
595
|
+
continue
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
for (const entry of reports) {
|
|
599
|
+
const missing = shortOf(entry.sections)
|
|
600
|
+
if (missing.length > 0) findings.push({ rel: entry.rel, missing })
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return findings
|
|
605
|
+
}
|
package/src/context/folders.ts
CHANGED
|
@@ -30,6 +30,16 @@ export interface AuditedFolder {
|
|
|
30
30
|
readonly indexPath: string
|
|
31
31
|
/** Absolute paths of the folder's own entries, excluding its `index.md`. */
|
|
32
32
|
readonly entries: readonly string[]
|
|
33
|
+
/**
|
|
34
|
+
* Whether this is a sub-area a domain split into rather than the folder
|
|
35
|
+
* named under `.claude/`.
|
|
36
|
+
*
|
|
37
|
+
* The entries of a split folder describe one domain between them, so a rule
|
|
38
|
+
* about what a domain declares is answered by the folder. The entries of the
|
|
39
|
+
* named folder are one domain each, and a rule answered by a sibling there
|
|
40
|
+
* would let one entry stand in for domains it says nothing about.
|
|
41
|
+
*/
|
|
42
|
+
readonly nested: boolean
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
/**
|
|
@@ -91,6 +101,7 @@ export async function resolveFolders(
|
|
|
91
101
|
rel: relative(root, each),
|
|
92
102
|
indexPath: `${each}/${INDEX_FILE}`,
|
|
93
103
|
entries: await readEntries(each),
|
|
104
|
+
nested: each !== dir,
|
|
94
105
|
})
|
|
95
106
|
}
|
|
96
107
|
}
|
|
@@ -5,7 +5,13 @@ description: GitHub Actions workflow triggers and checks
|
|
|
5
5
|
|
|
6
6
|
# CI
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|