@orkestrel/scaffold 0.0.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.
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/bin/scaffold.js +1539 -0
- package/dist/bin/scaffold.js.map +1 -0
- package/dist/host/AGENTS.md +939 -0
- package/dist/host/CLAUDE.md +495 -0
- package/dist/host/LICENSE +21 -0
- package/dist/host/claude/agents/builder.md +48 -0
- package/dist/host/claude/agents/checker.md +37 -0
- package/dist/host/claude/agents/composer.md +64 -0
- package/dist/host/claude/agents/grok.md +50 -0
- package/dist/host/claude/agents/orkestrel.md +236 -0
- package/dist/host/claude/agents/planner.md +44 -0
- package/dist/host/claude/agents/researcher.md +38 -0
- package/dist/host/claude/agents/reviewer.md +47 -0
- package/dist/host/claude/agents/scout.md +35 -0
- package/dist/host/claude/agents/verifier.md +34 -0
- package/dist/host/claude/settings.json +26 -0
- package/dist/host/dotfiles/editorconfig +17 -0
- package/dist/host/dotfiles/gitattributes +3 -0
- package/dist/host/dotfiles/gitignore +40 -0
- package/dist/host/dotfiles/oxfmtrc.json +18 -0
- package/dist/host/dotfiles/oxlintignore +20 -0
- package/dist/host/dotfiles/oxlintrc.json +58 -0
- package/dist/host/dotfiles/prettierignore +5 -0
- package/dist/host/github/workflows/ci.yml +64 -0
- package/dist/host/guides/src/guide.md +312 -0
- package/dist/host/guides/src/scaffold.md +2152 -0
- package/dist/host/manifest.json +137 -0
- package/dist/host/scripts/cursor.sh +74 -0
- package/dist/host/scripts/deps.sh +38 -0
- package/dist/host/scripts/ollama.sh +163 -0
- package/dist/src/core/index.cjs +3728 -0
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +1941 -0
- package/dist/src/core/index.d.ts +1941 -0
- package/dist/src/core/index.js +3636 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/server/index.cjs +1595 -0
- package/dist/src/server/index.cjs.map +1 -0
- package/dist/src/server/index.d.cts +779 -0
- package/dist/src/server/index.d.ts +779 -0
- package/dist/src/server/index.js +1572 -0
- package/dist/src/server/index.js.map +1 -0
- package/package.json +113 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scout
|
|
3
|
+
description: 'Fast read-only recon. Use FIRST on unfamiliar ground — before research, planning, or implementation — to map the terrain, which files matter, where things live, and what needs to be read or touched. Cheap and quick; never reads files end-to-end.'
|
|
4
|
+
tools: Read, Grep, Glob
|
|
5
|
+
model: sonnet
|
|
6
|
+
effort: low
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are the **Scout** — the recon unit of this project's orchestration triad (see
|
|
10
|
+
CLAUDE.md). Your job is to map terrain cheaply so no other agent wastes context on
|
|
11
|
+
discovery. You are an Executor: do the work yourself, spawn nothing, return only
|
|
12
|
+
the map.
|
|
13
|
+
|
|
14
|
+
## Job
|
|
15
|
+
|
|
16
|
+
1. Locate the relevant files and directories — glob/grep first; open a file only to
|
|
17
|
+
confirm relevance, and skim, never read end-to-end.
|
|
18
|
+
2. Identify entry points, the contracts/types involved, the matching tests, and the
|
|
19
|
+
governing docs (AGENTS.md always; any guide that covers the area).
|
|
20
|
+
3. Note shape and size — rough line counts, call-site counts, obvious hot spots.
|
|
21
|
+
|
|
22
|
+
## Output contract — the Map
|
|
23
|
+
|
|
24
|
+
Return ONLY this, compact (well under ~60 lines):
|
|
25
|
+
|
|
26
|
+
- **Goal restated** — one line.
|
|
27
|
+
- **Files that matter** — path + one-line role each, in read-first order.
|
|
28
|
+
- **Off to the side** — related-looking paths that are NOT relevant, half a line why
|
|
29
|
+
(saves everyone else the detour).
|
|
30
|
+
- **Pointers** — entry points, key symbols, matching test files, governing guide paths.
|
|
31
|
+
- **Flags** — anything surprising (generated code, huge files, duplication), one
|
|
32
|
+
line each.
|
|
33
|
+
|
|
34
|
+
No file contents. No analysis. No recommendations. If the terrain exceeds the
|
|
35
|
+
budget, say which subarea needs its own scout pass instead of inflating the map.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verifier
|
|
3
|
+
description: 'Runs the authoritative quality gates — format, check, build, targeted tests — or the exact scoped gate set / evidence commands the dispatch names, and reports true pass/fail per gate with exact failure excerpts. Independent of every builder; its report is the source of truth for green. Never fixes anything.'
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
model: sonnet
|
|
6
|
+
effort: low
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are the **Verifier** — the gate-runner of this project's orchestration triad
|
|
10
|
+
(see CLAUDE.md). Independence is the point: no builder's self-report counts, yours
|
|
11
|
+
does. You are an Executor: run the gates yourself, spawn nothing.
|
|
12
|
+
|
|
13
|
+
## Job
|
|
14
|
+
|
|
15
|
+
1. Run EXACTLY the commands the dispatch names, in order. The default authoritative
|
|
16
|
+
sweep, when the dispatch says so: `format` → `check` → `build` → the targeted
|
|
17
|
+
test project(s) it names. Never invent broader or narrower gates than dispatched.
|
|
18
|
+
2. Evidence runs count as gates: when dispatched to reproduce a failure, run the
|
|
19
|
+
named command and capture its exact output — reproduce, capture, bisect
|
|
20
|
+
mechanically if told to; nothing more.
|
|
21
|
+
3. Record each gate's TRUE outcome by exit code. A gate that "mostly passes" FAILED.
|
|
22
|
+
4. On failure, capture the exact failing excerpt — trimmed to the failure, not the
|
|
23
|
+
noise — and the file:line it points to.
|
|
24
|
+
|
|
25
|
+
## Output contract — the Gate Report
|
|
26
|
+
|
|
27
|
+
- **Per gate** — command → PASS / FAIL (exit code) → on FAIL, the exact failure
|
|
28
|
+
excerpt plus the suspected owning file(s).
|
|
29
|
+
- **Overall verdict** — GREEN only if every gate passed; otherwise the first place
|
|
30
|
+
to look.
|
|
31
|
+
- **Anomalies** — cache weirdness, flakes on rerun, anything off — one line each.
|
|
32
|
+
|
|
33
|
+
You never edit files and never "quick-fix" a failure — you report it. Return only
|
|
34
|
+
the gate report, never your process.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|resume",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/scripts/deps.sh",
|
|
10
|
+
"timeout": 600
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "command",
|
|
14
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/scripts/ollama.sh",
|
|
15
|
+
"timeout": 600
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/scripts/cursor.sh",
|
|
20
|
+
"timeout": 120
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
end_of_line = lf
|
|
5
|
+
charset = utf-8
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
trim_trailing_whitespace = true
|
|
8
|
+
indent_style = tab
|
|
9
|
+
indent_size = 4
|
|
10
|
+
|
|
11
|
+
[*.{json,jsonc,yaml,yml}]
|
|
12
|
+
indent_style = tab
|
|
13
|
+
indent_size = 2
|
|
14
|
+
|
|
15
|
+
[*.md]
|
|
16
|
+
trim_trailing_whitespace = false
|
|
17
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
tmp
|
|
12
|
+
dist
|
|
13
|
+
dist-ssr
|
|
14
|
+
*.local
|
|
15
|
+
|
|
16
|
+
# Editor directories and files
|
|
17
|
+
.vscode/*
|
|
18
|
+
!.vscode/extensions.json
|
|
19
|
+
.idea
|
|
20
|
+
.DS_Store
|
|
21
|
+
*.suo
|
|
22
|
+
*.ntvs*
|
|
23
|
+
*.njsproj
|
|
24
|
+
*.sln
|
|
25
|
+
*.sw?
|
|
26
|
+
|
|
27
|
+
# Environment variables (keep secret)
|
|
28
|
+
.env
|
|
29
|
+
.env.*
|
|
30
|
+
!.env.example
|
|
31
|
+
|
|
32
|
+
# Test screenshots (generated by vitest browser mode)
|
|
33
|
+
tests/**/__screenshots__/
|
|
34
|
+
|
|
35
|
+
# Playwright
|
|
36
|
+
test-results/
|
|
37
|
+
playwright-report/
|
|
38
|
+
|
|
39
|
+
# Model files
|
|
40
|
+
*.gguf
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxfmt/configuration_schema.json",
|
|
3
|
+
"endOfLine": "lf",
|
|
4
|
+
"semi": false,
|
|
5
|
+
"singleQuote": true,
|
|
6
|
+
"trailingComma": "all",
|
|
7
|
+
"printWidth": 100,
|
|
8
|
+
"useTabs": true,
|
|
9
|
+
"tabWidth": 2,
|
|
10
|
+
"overrides": [
|
|
11
|
+
{
|
|
12
|
+
"files": ["*.json", "*.jsonc"],
|
|
13
|
+
"options": {
|
|
14
|
+
"trailingComma": "none"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["import", "vitest"],
|
|
4
|
+
"rules": {
|
|
5
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
6
|
+
"@typescript-eslint/no-non-null-assertion": "error",
|
|
7
|
+
"@typescript-eslint/no-unused-vars": [
|
|
8
|
+
"error",
|
|
9
|
+
{
|
|
10
|
+
"argsIgnorePattern": "^_",
|
|
11
|
+
"varsIgnorePattern": "^_",
|
|
12
|
+
"caughtErrorsIgnorePattern": "^_",
|
|
13
|
+
"ignoreRestSiblings": true
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
17
|
+
"error",
|
|
18
|
+
{
|
|
19
|
+
"prefer": "type-imports",
|
|
20
|
+
"fixStyle": "separate-type-imports"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
|
|
24
|
+
"import/no-default-export": "error",
|
|
25
|
+
"import/extensions": "off",
|
|
26
|
+
"import/no-unassigned-import": ["error", { "allow": ["**/*.css", "**/*.scss"] }],
|
|
27
|
+
|
|
28
|
+
"no-var": "error",
|
|
29
|
+
"prefer-const": "error",
|
|
30
|
+
"no-debugger": "error",
|
|
31
|
+
"no-underscore-dangle": "warn",
|
|
32
|
+
|
|
33
|
+
"vitest/warn-todo": "off"
|
|
34
|
+
},
|
|
35
|
+
"categories": {
|
|
36
|
+
"correctness": "error",
|
|
37
|
+
"suspicious": "warn",
|
|
38
|
+
"pedantic": "off",
|
|
39
|
+
"perf": "off",
|
|
40
|
+
"style": "off",
|
|
41
|
+
"restriction": "off"
|
|
42
|
+
},
|
|
43
|
+
"overrides": [
|
|
44
|
+
{
|
|
45
|
+
"files": ["*.config.ts", "*.config.js"],
|
|
46
|
+
"rules": {
|
|
47
|
+
"import/no-default-export": "off"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"files": ["*.vue"],
|
|
52
|
+
"rules": {
|
|
53
|
+
"import/no-default-export": "off",
|
|
54
|
+
"no-unused-expressions": "off"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: ci.yml
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
ci:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
timeout-minutes: 60
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout code
|
|
16
|
+
uses: actions/checkout@v6
|
|
17
|
+
|
|
18
|
+
- name: Set up Node.js
|
|
19
|
+
uses: actions/setup-node@v6
|
|
20
|
+
with:
|
|
21
|
+
node-version: '24'
|
|
22
|
+
cache: 'npm'
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm ci
|
|
26
|
+
|
|
27
|
+
- name: Install Playwright browsers
|
|
28
|
+
run: npx playwright install --with-deps chromium
|
|
29
|
+
|
|
30
|
+
- name: Install Ollama
|
|
31
|
+
run: curl -fsSL https://ollama.com/install.sh | sh
|
|
32
|
+
|
|
33
|
+
- name: Start Ollama
|
|
34
|
+
run: |
|
|
35
|
+
sudo systemctl stop ollama
|
|
36
|
+
OLLAMA_ORIGINS="*" OLLAMA_HOST="127.0.0.1:11434" nohup ollama serve > /dev/null 2>&1 &
|
|
37
|
+
for i in $(seq 1 30); do
|
|
38
|
+
if curl -sf http://127.0.0.1:11434/api/tags > /dev/null 2>&1; then
|
|
39
|
+
echo "Ollama is ready"
|
|
40
|
+
break
|
|
41
|
+
fi
|
|
42
|
+
echo "Waiting for Ollama... ($i/30)"
|
|
43
|
+
sleep 1
|
|
44
|
+
done
|
|
45
|
+
- name: Pull models
|
|
46
|
+
run: ollama pull qwen3.5:2b-q4_K_M
|
|
47
|
+
|
|
48
|
+
- name: Check formatting
|
|
49
|
+
run: npm run format:check
|
|
50
|
+
|
|
51
|
+
- name: Lint
|
|
52
|
+
run: npm run lint:check
|
|
53
|
+
|
|
54
|
+
- name: Typecheck
|
|
55
|
+
run: npm run check
|
|
56
|
+
|
|
57
|
+
- name: Typecheck src
|
|
58
|
+
run: npm run check:src
|
|
59
|
+
|
|
60
|
+
- name: Build
|
|
61
|
+
run: npm run build
|
|
62
|
+
|
|
63
|
+
- name: Run tests
|
|
64
|
+
run: npm test
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Guide
|
|
2
|
+
|
|
3
|
+
> A zero-dependency guides-parity toolkit: `Guide` extracts a markdown guide's documented
|
|
4
|
+
> surface, method groups, links, and test links; `Source` reflects the same shapes from a
|
|
5
|
+
> consumer-supplied file inventory via pure text scanners (no filesystem, no TypeScript
|
|
6
|
+
> compiler API); a small set of pure comparison helpers (`missingSymbols`, `findMissing`,
|
|
7
|
+
> `resolveLink`, …) reduce every guides-parity check to `expect([]).toEqual([])`
|
|
8
|
+
> (AGENTS §22). Source: [`src/core`](../../src/core). Surfaced through the `@src/core` barrel.
|
|
9
|
+
|
|
10
|
+
The doctrine: a guide is a contract, not prose. `createGuide(markdown)` parses a guide's
|
|
11
|
+
source once (via `@orkestrel/markdown`) into a `GuideInterface` — its `## Surface` identifiers
|
|
12
|
+
(kind-tagged), its `## Methods` interface/method groups, every link, and its `## Tests`
|
|
13
|
+
links, each cached at construction. `createSource({ files, module })` builds a
|
|
14
|
+
`SourceInterface` that reflects the SAME two shapes — exported symbols and interface/class
|
|
15
|
+
methods — by scanning a consumer-gathered file inventory with plain-text line scanners
|
|
16
|
+
(`exportsFrom`, `declarationBody` + `memberMethods`), never touching disk itself. A
|
|
17
|
+
guides-parity test then asserts these two views agree in both directions: nothing documented
|
|
18
|
+
is phantom, nothing exported is undocumented. `parseManifest` reads a `guides/README.md`'s
|
|
19
|
+
`## By concept` table into the list of `{ concept, spec, source, tests }` entries a suite
|
|
20
|
+
iterates to run this check once per documented concept.
|
|
21
|
+
|
|
22
|
+
## Surface
|
|
23
|
+
|
|
24
|
+
### Types
|
|
25
|
+
|
|
26
|
+
The manifest/extraction shapes every check is built from, from [`types.ts`](../../src/core/types.ts).
|
|
27
|
+
|
|
28
|
+
| Name | Kind | Shape |
|
|
29
|
+
| ----------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `ExportKind` | type | `'type' \| 'interface' \| 'const' \| 'function' \| 'class'` — the declaration kind half of a symbol's bijection key. |
|
|
31
|
+
| `SurfaceSymbol` | interface | `{ name, kind }` — one documented / exported symbol. |
|
|
32
|
+
| `GuideModule` | type | `string \| readonly string[]` — one source directory, or several for a layer guide spanning multiple directories. |
|
|
33
|
+
| `ManifestEntry` | interface | `{ concept, spec, source, tests }` — one `## By concept` manifest row, paths normalized to workspace root. |
|
|
34
|
+
| `MethodGroup` | interface | `{ interface, methods }` — one `#### \`Interface\`` block's documented method names, in table order. |
|
|
35
|
+
| `GuideInterface` | interface | `{ sections, surface, methods, links, tests, patterns }` — the structured, pure view over one parsed guide. See [`## Methods`](#methods). |
|
|
36
|
+
| `SourceInterface` | interface | `{ exports, methods, exists, hidden, examples }` — the reflected source truth a guide's surface is checked against. See [`## Methods`](#methods). |
|
|
37
|
+
| `SourceOptions` | interface | `{ files, module }` — the construction input for a `Source`: a consumer-supplied file inventory plus the module scope to reflect. |
|
|
38
|
+
| `DeclarationHead` | interface | `{ text, end }` — a declaration head joined into one line (across an oxfmt-wrapped signature) plus the index of the line ending in `{`. |
|
|
39
|
+
|
|
40
|
+
### Constants
|
|
41
|
+
|
|
42
|
+
The section-heading keys and external-link schemes every extractor and link check is keyed
|
|
43
|
+
on, from [`constants.ts`](../../src/core/constants.ts).
|
|
44
|
+
|
|
45
|
+
| Name | Kind | Behavior |
|
|
46
|
+
| ------------------ | ----- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| `SURFACE` | const | `'Surface'` — the `## Surface` heading text. |
|
|
48
|
+
| `METHODS` | const | `'Methods'` — the `## Methods` heading text. |
|
|
49
|
+
| `TESTS` | const | `'Tests'` — the `## Tests` heading text. |
|
|
50
|
+
| `MANIFEST` | const | `'By concept'` — the `## By concept` manifest heading text. |
|
|
51
|
+
| `EXTERNAL_SCHEMES` | const | `readonly string[]` — `['http:', 'https:', 'mailto:', 'tel:']`; a link with one of these prefixes is never filesystem-resolved. |
|
|
52
|
+
|
|
53
|
+
### Helpers
|
|
54
|
+
|
|
55
|
+
Pure, total leaves from [`helpers.ts`](../../src/core/helpers.ts) — the building blocks
|
|
56
|
+
`parsers.ts`'s extractors and a consumer's parity test both reach for directly.
|
|
57
|
+
|
|
58
|
+
| Name | Kind | Signature | Behavior |
|
|
59
|
+
| ---------------- | -------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `moduleDirs` | function | `(module: GuideModule) => readonly string[]` | Normalizes a `GuideModule` scope to its directory list — a single string becomes a one-element list. |
|
|
61
|
+
| `moduleKeys` | function | `(files: Readonly<Record<string, string>>, module: GuideModule) => readonly string[]` | The file inventory's keys belonging to a `GuideModule` scope: `.ts` files under a scope directory, excluding that directory's `index.ts` and `*.test.ts`; sorted. |
|
|
62
|
+
| `symbolKey` | function | `(symbol: SurfaceSymbol) => string` | The bijection key for a surface symbol — `${kind} ${name}` — so a symbol comparison diffs (name, kind) pairs, not names alone. |
|
|
63
|
+
| `findMissing` | function | `(names: readonly string[], source: readonly string[]) => readonly string[]` | The names present in `names` but absent from `source` — the set-difference behind a both-directions bijection assertion. |
|
|
64
|
+
| `missingSymbols` | function | `(symbols: readonly SurfaceSymbol[], source: readonly SurfaceSymbol[]) => readonly string[]` | The `symbolKey` set-difference between two symbol lists. |
|
|
65
|
+
| `isExternalLink` | function | `(href: string) => boolean` | Whether a link `href` should be skipped by guides-parity link checks — an external scheme (`EXTERNAL_SCHEMES`) or a bare `#` anchor. |
|
|
66
|
+
| `resolveLink` | function | `(from: string, target: string) => string` | Resolves a relative link `target` against `from` and normalizes the result, purely (no `node:path`). |
|
|
67
|
+
| `firstCode` | function | `(nodes: readonly InlineNode[]) => string \| undefined` | The first code-span value found by descending an inline node list, following into `emphasis` / `link` children. |
|
|
68
|
+
| `identifierOf` | function | `(code: string) => string` | The identifier prefix of a code-span text — everything before its first `<`, trimmed (strips generic-parameter annotation). |
|
|
69
|
+
| `kindIndex` | function | `(table: TableNode) => number \| undefined` | The index of a table's `Kind` column, found by its header text so it survives column reordering. |
|
|
70
|
+
| `cellLinks` | function | `(cell: readonly InlineNode[]) => readonly string[]` | The link hrefs found within one table cell's inline content, in walk order. |
|
|
71
|
+
| `findUnexampled` | function | `(names: readonly string[], fences: readonly string[], examples: readonly string[]) => readonly string[]` | The names with no fence mention (word boundary) and no `@example` membership — the EX check's core comparison. |
|
|
72
|
+
| `fenceImports` | function | `(fence: string) => readonly { specifier: string, names: readonly string[] }[]` | Parses a fence's `import` statements into per-specifier imported identifier names — the FI check's core comparison. |
|
|
73
|
+
|
|
74
|
+
### Parsers
|
|
75
|
+
|
|
76
|
+
The guide/manifest extraction pipeline, from [`parsers.ts`](../../src/core/parsers.ts) —
|
|
77
|
+
the orchestration `Guide` composes out of `helpers.ts`'s leaves.
|
|
78
|
+
|
|
79
|
+
| Name | Kind | Signature | Behavior |
|
|
80
|
+
| ----------------- | -------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
81
|
+
| `exportsFrom` | function | `(source: string) => readonly SurfaceSymbol[]` | The module-scope exports declared in one file's source text, deduped by (kind, name); a generator export scans as `function`. |
|
|
82
|
+
| `hiddenFrom` | function | `(source: string) => readonly SurfaceSymbol[]` | The module-scope declarations LACKING the `export` keyword — the export-discipline (AGENTS §5) mirror of `exportsFrom`, same five-kind grammar. |
|
|
83
|
+
| `joinHead` | function | `(lines: readonly string[], start: number) => DeclarationHead \| undefined` | Joins a declaration head starting at `start` into one space-separated line, consuming lines until the first ending in `{`. |
|
|
84
|
+
| `declarationBody` | function | `(source: string, keyword: 'class' \| 'interface', name: string) => readonly string[]` | The body lines of the named `export class` / `export interface` declaration in one file's source text. |
|
|
85
|
+
| `memberMethods` | function | `(lines: readonly string[]) => readonly string[]` | The declared callable-member names in a declaration body — plain / `async` / generator / optional; getters, setters, `static`, `#` privates never count. |
|
|
86
|
+
| `sectionBlocks` | function | `(document: MarkdownDocument, heading: string) => readonly BlockNode[]` | The block nodes under a named `##` heading, up to the next `##`-or-higher heading (or the document's end). |
|
|
87
|
+
| `extractSurface` | function | `(document: MarkdownDocument) => readonly SurfaceSymbol[]` | Every `## Surface` identifier: each table's rows union every backticked H3 entity heading, deduped by `symbolKey`. |
|
|
88
|
+
| `extractMethods` | function | `(document: MarkdownDocument) => readonly MethodGroup[]` | One `MethodGroup` per documented behavioral interface in `## Methods` — an H4 code span sets the interface, the following table lists its methods. |
|
|
89
|
+
| `extractLinks` | function | `(document: MarkdownDocument) => readonly string[]` | Every link href in the guide document, including table cells — a full, depth-first AST walk. |
|
|
90
|
+
| `extractTests` | function | `(document: MarkdownDocument) => readonly string[]` | The relative test links declared under `## Tests`. |
|
|
91
|
+
| `examplesFrom` | function | `(source: string) => readonly string[]` | The exported functions in one file's source text whose immediately preceding JSDoc block carries `@example`. |
|
|
92
|
+
| `exampleMethods` | function | `(lines: readonly string[]) => readonly string[]` | The `exampleMethods`-flavored `memberMethods` — callable-member names in a declaration body whose preceding JSDoc carries `@example`. |
|
|
93
|
+
| `extractPatterns` | function | `(document: MarkdownDocument) => readonly string[]` | Every fenced `ts` code block's body text anywhere in the guide document — a full AST walk. |
|
|
94
|
+
| `parseManifest` | function | `(markdown: string, base: string) => readonly ManifestEntry[]` | Parses a `## By concept` manifest table into `ManifestEntry` rows, resolving Spec / Source / Tests links against `base`. |
|
|
95
|
+
|
|
96
|
+
### Shapers
|
|
97
|
+
|
|
98
|
+
Declarative `ContractShape` values (from `@orkestrel/contract`) from
|
|
99
|
+
[`shapers.ts`](../../src/core/shapers.ts) — every documented data type here is
|
|
100
|
+
non-recursive, so each shapes directly.
|
|
101
|
+
|
|
102
|
+
| Name | Kind | Builds |
|
|
103
|
+
| -------------------- | ----- | ----------------------------------------------------------------------------------- |
|
|
104
|
+
| `surfaceSymbolShape` | const | The shape of a `SurfaceSymbol` — `{ name: string, kind: ExportKind }`. |
|
|
105
|
+
| `methodGroupShape` | const | The shape of a `MethodGroup` — `{ interface: string, methods: readonly string[] }`. |
|
|
106
|
+
| `manifestEntryShape` | const | The shape of a `ManifestEntry` — `source` accepting a single directory or several. |
|
|
107
|
+
|
|
108
|
+
### Validators
|
|
109
|
+
|
|
110
|
+
Total from-unknown guards composed from `@orkestrel/contract` combinators, from
|
|
111
|
+
[`validators.ts`](../../src/core/validators.ts).
|
|
112
|
+
|
|
113
|
+
| Name | Kind | Narrows to / Tests | Behavior |
|
|
114
|
+
| ----------------- | ----- | ------------------ | ------------------------------------------------------------------------ |
|
|
115
|
+
| `isExportKind` | const | `value: unknown` | `true` when `value` is one of the five documented `ExportKind` literals. |
|
|
116
|
+
| `isSurfaceSymbol` | const | `value: unknown` | `true` when `value` is a well-formed `SurfaceSymbol`. |
|
|
117
|
+
| `isMethodGroup` | const | `value: unknown` | `true` when `value` is a well-formed `MethodGroup`. |
|
|
118
|
+
| `isManifestEntry` | const | `value: unknown` | `true` when `value` is a well-formed `ManifestEntry`. |
|
|
119
|
+
|
|
120
|
+
### Factories
|
|
121
|
+
|
|
122
|
+
From [`factories.ts`](../../src/core/factories.ts).
|
|
123
|
+
|
|
124
|
+
| Name | Kind | Signature | Behavior |
|
|
125
|
+
| ----------------------------- | -------- | --------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
126
|
+
| `createGuide` | function | `(source: string) => GuideInterface` | Creates a structured `GuideInterface` view over one guide's markdown source. |
|
|
127
|
+
| `createSource` | function | `(options: SourceOptions) => SourceInterface` | Creates a pure `SourceInterface` over a consumer-supplied file inventory. |
|
|
128
|
+
| `createSurfaceSymbolContract` | function | `() => ContractInterface<SurfaceSymbol>` | Compiles `surfaceSymbolShape` into a guard / parser / schema / generator bundle. |
|
|
129
|
+
| `createMethodGroupContract` | function | `() => ContractInterface<MethodGroup>` | Compiles `methodGroupShape` into a guard / parser / schema / generator bundle. |
|
|
130
|
+
| `createManifestEntryContract` | function | `() => ContractInterface<ManifestEntry>` | Compiles `manifestEntryShape` into a guard / parser / schema / generator bundle. |
|
|
131
|
+
|
|
132
|
+
### `Guide`
|
|
133
|
+
|
|
134
|
+
The implementing class of `GuideInterface`, from [`Guide.ts`](../../src/core/Guide.ts). A
|
|
135
|
+
stateful, structured view over one parsed guide: parses `source` once via
|
|
136
|
+
`@orkestrel/markdown` and never touches the filesystem — `Guide` has no notion of "where"
|
|
137
|
+
the guide came from, only its markdown text. Every accessor returns the same cached,
|
|
138
|
+
readonly array on every call. See [`## Methods`](#methods) for its public call-signature
|
|
139
|
+
surface.
|
|
140
|
+
|
|
141
|
+
### `Source`
|
|
142
|
+
|
|
143
|
+
The implementing class of `SourceInterface`, from [`Source.ts`](../../src/core/Source.ts). A
|
|
144
|
+
pure reflection over a consumer-supplied file inventory (root-relative path → file text) plus
|
|
145
|
+
a module scope — reflects exported symbols (`exportsFrom`) and interface/class methods
|
|
146
|
+
(`declarationBody` + `memberMethods`) with text-only line scanners, never the TypeScript
|
|
147
|
+
compiler API or the filesystem. `exports()` is computed once on first access and cached; the
|
|
148
|
+
consumer gathers `files` however their environment allows (`node:fs` in a Node script,
|
|
149
|
+
`import.meta.glob` in a browser/vitest run). See [`## Methods`](#methods) for its public
|
|
150
|
+
call-signature surface.
|
|
151
|
+
|
|
152
|
+
## Methods
|
|
153
|
+
|
|
154
|
+
The public methods of each behavioral interface — one table per type, keyed by its
|
|
155
|
+
backticked name (AGENTS §22).
|
|
156
|
+
|
|
157
|
+
#### `GuideInterface`
|
|
158
|
+
|
|
159
|
+
| Method | Returns | Behavior |
|
|
160
|
+
| ---------- | -------------------------- | ------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `sections` | `readonly string[]` | The `##` heading names, in document order — the non-vacuousness guard for section presence. |
|
|
162
|
+
| `surface` | `readonly SurfaceSymbol[]` | Every `## Surface` identifier + kind — table rows union backticked entity headings. |
|
|
163
|
+
| `methods` | `readonly MethodGroup[]` | One `MethodGroup` per documented behavioral interface in `## Methods`. |
|
|
164
|
+
| `links` | `readonly string[]` | Every link href in the guide, including table cells. |
|
|
165
|
+
| `tests` | `readonly string[]` | The relative test links declared under `## Tests`. |
|
|
166
|
+
| `patterns` | `readonly string[]` | Every fenced `ts` code block's body text, whole document. |
|
|
167
|
+
|
|
168
|
+
#### `SourceInterface`
|
|
169
|
+
|
|
170
|
+
| Method | Returns | Behavior |
|
|
171
|
+
| ---------- | -------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
172
|
+
| `exports` | `readonly SurfaceSymbol[]` | Every module-scope export, including type-only, by (name, kind). |
|
|
173
|
+
| `methods` | `readonly string[]` | The call-signature members of the `class` / `interface` named `name`. |
|
|
174
|
+
| `exists` | `boolean` | Whether a workspace-root-relative path exists in the inventory. |
|
|
175
|
+
| `hidden` | `readonly SurfaceSymbol[]` | Every module-scope declaration LACKING `export` (AGENTS §5). |
|
|
176
|
+
| `examples` | `readonly string[]` | The exported functions (or, given `name`, the members of `name`) whose preceding JSDoc carries `@example`. |
|
|
177
|
+
|
|
178
|
+
## The extraction model
|
|
179
|
+
|
|
180
|
+
`Guide` parses a guide's markdown once (via `@orkestrel/markdown`'s `createMarkdown`) and
|
|
181
|
+
caches five projections at construction — `sections`, `surface`, `methods`, `links`, `tests`
|
|
182
|
+
— so every accessor is a cheap array return, not a re-parse. `extractSurface` scopes to the
|
|
183
|
+
`## Surface` section (`sectionBlocks`) and unions two sources of identifiers: every table's
|
|
184
|
+
column-0 code span (kind read from the column whose header text is `Kind`, located
|
|
185
|
+
positionally so it survives reordering) and every backticked H3 entity heading (a class
|
|
186
|
+
documented outside a table, kind fixed to `'class'`). `extractMethods` scopes to
|
|
187
|
+
`## Methods`: an H4 whose first code span sets the current interface name, and the very next
|
|
188
|
+
table becomes that interface's `MethodGroup`. Both extractors normalize every identifier
|
|
189
|
+
through `identifierOf`, stripping a generic-parameter annotation (`` `WidgetInterface<T>` ``
|
|
190
|
+
→ `WidgetInterface`) so the bijection key is always the bare name. `extractLinks` walks the
|
|
191
|
+
whole AST for every `link` node (table cells included); `extractTests` does the same walk
|
|
192
|
+
scoped to the `## Tests` section only.
|
|
193
|
+
|
|
194
|
+
`Source` never parses markdown or touches disk — it scans a consumer-supplied file
|
|
195
|
+
inventory's TEXT with regex-based line scanners. `exportsFrom` matches
|
|
196
|
+
`^export (?:async )?(function\*?|class|const|interface|type) (\w+)` per line, deduped by
|
|
197
|
+
(kind, name). `declarationBody` locates a named `export class` / `export interface` head
|
|
198
|
+
(joining an oxfmt-wrapped signature via `joinHead` until a line ends in `{`), then collects
|
|
199
|
+
body lines to the column-0 `}`. `memberMethods` matches
|
|
200
|
+
`^\t(?:async )?\*?(\w+)(<[^>]*>)?\??\(` against those body lines — plain / `async` /
|
|
201
|
+
generator / optional methods count; getters, setters, `static` members, and `#` privates
|
|
202
|
+
never match (their keyword or sigil breaks the `name(` shape), and `constructor` is filtered
|
|
203
|
+
out of `Source.methods`. `moduleKeys` scopes the inventory to one `GuideModule`'s `.ts`
|
|
204
|
+
files, excluding each scope directory's own `index.ts` and any `*.test.ts` file. `hiddenFrom`
|
|
205
|
+
runs the mirror-image scan — the same five-kind grammar, but only lines LACKING the `export`
|
|
206
|
+
keyword — so `Source.hidden()` mechanically asserts AGENTS §5's export-discipline rule and
|
|
207
|
+
catches a hidden declaration the surface bijection alone would never see (it never appears as
|
|
208
|
+
an export, so it never shows up as a missing Surface row either).
|
|
209
|
+
|
|
210
|
+
## The check catalog
|
|
211
|
+
|
|
212
|
+
Every guides-parity check reduces to `expect([]).toEqual([])`, paired with a non-vacuousness
|
|
213
|
+
guard so a renamed heading fails loudly instead of passing on an empty extraction:
|
|
214
|
+
|
|
215
|
+
- **SB — Surface bijection (kind folded in).** `guide.surface()` vs `source.exports()`,
|
|
216
|
+
`missingSymbols` both directions over `symbolKey` — a symbol may drift in neither name nor
|
|
217
|
+
kind. Guard: `guide.surface().length > 0`.
|
|
218
|
+
- **MB — Methods bijection + class-no-extra.** Per `MethodGroup`, its `methods` vs
|
|
219
|
+
`source.methods(group.interface)`, `findMissing` both directions; then, by the
|
|
220
|
+
`XInterface → X` naming convention, `findMissing(source.methods('X'), group.methods)` must
|
|
221
|
+
also be empty — the implementing class exposes no undocumented public method. Guard:
|
|
222
|
+
`group.methods.length > 0`.
|
|
223
|
+
- **LI — Link integrity.** `guide.links()`, dropping `isExternalLink` hrefs, `resolveLink`
|
|
224
|
+
the rest against the guide's own path, keep those failing `source.exists`.
|
|
225
|
+
- **TE — Tests-link existence.** `guide.tests()`, `resolveLink` + `source.exists`, keep the
|
|
226
|
+
missing.
|
|
227
|
+
- **NV — Non-vacuousness.** `parseManifest` yields at least one entry; each guide's
|
|
228
|
+
`surface()` and every `MethodGroup` is non-empty — the guard behind every other check.
|
|
229
|
+
- **EX — Examples presence.** A documented symbol "has an example" when its bare name
|
|
230
|
+
appears (word boundary) in any of `guide.patterns()`'s fence bodies, OR its source
|
|
231
|
+
JSDoc carries an `@example` block (`source.examples()` / `source.examples(name)`).
|
|
232
|
+
Applies to every `function`-kind `Surface` symbol and every `MethodGroup` member.
|
|
233
|
+
Presence-only — fence and JSDoc CONTENT are never checked. `findUnexampled` is the
|
|
234
|
+
comparison. Guard: the SB/MB extractions this check reuses already prove non-vacuous.
|
|
235
|
+
- **FI — Fence-import reality.** Every `import { ... } from 'specifier'` in a
|
|
236
|
+
`guide.patterns()` fence, for a SELF specifier (this repo's own package name / path
|
|
237
|
+
alias), imports only names that exist in `source.exports()`. `fenceImports` parses the
|
|
238
|
+
statement; `findMissing` diffs the imported names against `source.exports()`'s names.
|
|
239
|
+
|
|
240
|
+
## The pure file-inventory model
|
|
241
|
+
|
|
242
|
+
Neither `Guide` nor `Source` ever imports `node:fs` or any other I/O primitive — `Source`'s
|
|
243
|
+
construction input (`SourceOptions.files`) is a plain `Readonly<Record<string, string>>` the
|
|
244
|
+
CONSUMER gathers however their runtime allows: a recursive `node:fs` walk in a Node vitest
|
|
245
|
+
run, `import.meta.glob('/**/*.ts', { eager: true, query: '?raw', import: 'default' })` in a
|
|
246
|
+
browser/vitest run, or a static bundle in any other environment. This keeps the package
|
|
247
|
+
itself environment-agnostic while every check still runs against real, on-disk truth in the
|
|
248
|
+
consumer's own test.
|
|
249
|
+
|
|
250
|
+
## Patterns
|
|
251
|
+
|
|
252
|
+
### Construct a `Guide` from markdown text
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { createGuide } from '@orkestrel/guide'
|
|
256
|
+
|
|
257
|
+
const guide = createGuide('## Surface\n\n| Name | Kind |\n| --- | --- |\n| `X` | class |')
|
|
258
|
+
guide.surface() // [{ name: 'X', kind: 'class' }]
|
|
259
|
+
guide.sections() // ['Surface']
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Construct a `Source` from an inline files record
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { createSource } from '@orkestrel/guide'
|
|
266
|
+
|
|
267
|
+
const source = createSource({
|
|
268
|
+
files: {
|
|
269
|
+
'src/core/Guide.ts': 'export class Guide {}\n',
|
|
270
|
+
'src/core/types.ts': 'export interface GuideInterface {\n\tsections(): void\n}\n',
|
|
271
|
+
},
|
|
272
|
+
module: 'src/core',
|
|
273
|
+
})
|
|
274
|
+
source.exports() // [{ name: 'Guide', kind: 'class' }, { name: 'GuideInterface', kind: 'interface' }]
|
|
275
|
+
source.methods('GuideInterface') // ['sections']
|
|
276
|
+
source.exists('src/core/Guide.ts') // true
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### The bijection assertion shape
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { createGuide, createSource, missingSymbols } from '@orkestrel/guide'
|
|
283
|
+
|
|
284
|
+
const guide = createGuide('## Surface\n\n| Name | Kind |\n| --- | --- |\n| `Guide` | class |')
|
|
285
|
+
const source = createSource({
|
|
286
|
+
files: { 'src/core/Guide.ts': 'export class Guide {}\n' },
|
|
287
|
+
module: 'src/core',
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
// Every check collapses to an empty-array assertion, both directions.
|
|
291
|
+
missingSymbols(source.exports(), guide.surface()) // [] — every export is documented
|
|
292
|
+
missingSymbols(guide.surface(), source.exports()) // [] — every documented symbol is real
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Tests
|
|
296
|
+
|
|
297
|
+
- [`tests/src/core/helpers.test.ts`](../../tests/src/core/helpers.test.ts) — `moduleDirs` / `moduleKeys` / `symbolKey` / `findMissing` / `missingSymbols` / `isExternalLink` / `resolveLink` / `firstCode` / `identifierOf` / `kindIndex`.
|
|
298
|
+
- [`tests/src/core/parsers.test.ts`](../../tests/src/core/parsers.test.ts) — `exportsFrom` / `joinHead` / `declarationBody` / `memberMethods` / `sectionBlocks` / `extractSurface` / `extractMethods` / `extractLinks` / `extractTests` / `parseManifest`, incl. entity-heading surface and multi-dir Source cells.
|
|
299
|
+
- [`tests/src/core/validators.test.ts`](../../tests/src/core/validators.test.ts) — `isExportKind` / `isSurfaceSymbol` / `isMethodGroup` / `isManifestEntry`.
|
|
300
|
+
- [`tests/src/core/shapers.test.ts`](../../tests/src/core/shapers.test.ts) — per-shape guard exactness, JSON Schema essentials, seeded generate round-trips, parse rebuilds.
|
|
301
|
+
- [`tests/src/core/factories.test.ts`](../../tests/src/core/factories.test.ts) — `createGuide` / `createSource` + the compiled symbol/group/manifest contracts.
|
|
302
|
+
- [`tests/src/core/Guide.test.ts`](../../tests/src/core/Guide.test.ts) — `Guide`'s five cached projections, constructed once, called repeatedly.
|
|
303
|
+
- [`tests/src/core/Source.test.ts`](../../tests/src/core/Source.test.ts) — `Source`'s pure reflection against an in-memory fixture inventory, incl. `memberMethods` on every excluded shape (getters, setters, `static`, `#` privates, `constructor`).
|
|
304
|
+
- [`tests/guides/src/parity.test.ts`](../../tests/guides/src/parity.test.ts) — the drop-in guides-parity suite, run against THIS repo's own `guides/README.md` manifest — the self-dogfooding acceptance criterion.
|
|
305
|
+
|
|
306
|
+
## See also
|
|
307
|
+
|
|
308
|
+
- `AGENTS.md` (workspace root) — the rules; §22 documentation-as-contracts.
|
|
309
|
+
- `PROPOSAL.md` (workspace root) — the design proposal this package implements; §5 the check catalog, §6 the drop-in, §7 source-scanning fidelity.
|
|
310
|
+
- [`README.md`](../README.md) — the guides index.
|
|
311
|
+
- [`markdown.md`](markdown.md) — the dependency mirror for `@orkestrel/markdown`, the AST/parse layer `Guide` and `Source` are built on.
|
|
312
|
+
</content>
|