@csark0812/skeleton 1.6.3 → 2.0.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/README.md +16 -11
- package/dist/audit/config/load.d.ts +18 -0
- package/dist/audit/config/types.d.ts +75 -0
- package/dist/audit/core/code-fit.d.ts +35 -0
- package/dist/audit/core/collect.d.ts +21 -0
- package/dist/audit/core/context.d.ts +46 -0
- package/dist/audit/core/draft.d.ts +11 -0
- package/dist/audit/core/fix.d.ts +29 -0
- package/dist/audit/core/git-meta.d.ts +1 -0
- package/dist/audit/core/markdown.d.ts +16 -0
- package/dist/audit/core/report.d.ts +31 -0
- package/dist/audit/core/review-proof.d.ts +23 -0
- package/dist/audit/core/shared.d.ts +25 -0
- package/dist/audit/core/skill-provenance.d.ts +37 -0
- package/dist/audit/core/skill-roots.d.ts +54 -0
- package/dist/audit/core/ssot-collect.d.ts +16 -0
- package/dist/audit/core/ssot-fit.d.ts +38 -0
- package/dist/audit/core/ssot.d.ts +28 -0
- package/dist/audit/fix/anchors.d.ts +6 -0
- package/dist/audit/fix/doc-meta.d.ts +4 -0
- package/dist/audit/fix/match-anchor.d.ts +8 -0
- package/dist/audit/fix/ssot.d.ts +3 -0
- package/dist/audit/policies/load.d.ts +13 -0
- package/dist/audit/policies/types.d.ts +46 -0
- package/dist/audit/rules/banned.d.ts +7 -0
- package/dist/audit/rules/code-fit.d.ts +13 -0
- package/dist/audit/rules/doc-meta.d.ts +8 -0
- package/dist/audit/rules/index.d.ts +26 -0
- package/dist/audit/rules/links.d.ts +7 -0
- package/dist/audit/rules/near-duplicate.d.ts +8 -0
- package/dist/audit/rules/prose-policy.d.ts +7 -0
- package/dist/audit/rules/review-proof.d.ts +1 -0
- package/dist/audit/rules/scan-gaps.d.ts +7 -0
- package/dist/audit/rules/scan-roots.d.ts +7 -0
- package/dist/audit/rules/skill-index.d.ts +10 -0
- package/dist/audit/rules/ssot-summary.d.ts +8 -0
- package/dist/audit/rules/ssot.d.ts +7 -0
- package/dist/cli.js +2394 -1829
- package/dist/hooks/customize-on-skill-read.js +27 -1
- package/dist/plugin-types.d.ts +12 -133
- package/dist/plugin-types.js +6 -3
- package/dist/references/check.d.ts +10 -0
- package/dist/references/constants.d.ts +9 -0
- package/dist/references/discover.d.ts +19 -0
- package/dist/result-types.d.ts +72 -0
- package/dist/result-types.js +0 -0
- package/package.json +26 -17
- package/schemas/config.schema.json +17 -0
- package/schemas/policy-file.schema.json +5 -2
- package/schemas/result.schema.json +207 -0
- package/templates/skeleton-init/skeleton.toml +6 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- source-of-truth: Package overview -->
|
|
4
4
|
|
|
5
|
-
<!-- doc-meta: owner=eng | last-reviewed=2026-08-
|
|
5
|
+
<!-- doc-meta: owner=eng | last-reviewed=2026-08-19 -->
|
|
6
6
|
|
|
7
7
|
<!-- code-fit: targets=src/cli.ts surface=audit,validate,catalog,init,build-plugin,references -->
|
|
8
8
|
|
|
@@ -23,14 +23,14 @@ That needs to be explicit — and stay true after the next 50 PRs. Skeleton turn
|
|
|
23
23
|
| Code repos | Agent repos |
|
|
24
24
|
| ------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
25
25
|
| ESLint catches broken imports, unused vars, style drift | Skeleton catches broken links, bad SSOT markers, stale doc-meta, deny.paths artifacts |
|
|
26
|
-
| `eslint --fix` on changed files | `skeleton validate changed` on changed docs and
|
|
26
|
+
| `eslint --fix` on changed files | `skeleton validate changed` on changed docs, skills, and docs linked to changed code |
|
|
27
27
|
| Pre-commit + CI gate | `--staged` pre-commit + `--base` CI gate |
|
|
28
28
|
|
|
29
29
|
Skill linters ask: _"Is this SKILL.md well-formed?"_
|
|
30
30
|
|
|
31
31
|
Skeleton asks the repo-level question: _"Does this whole thing still agree with itself?"_
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Preliminary agent-behavior evidence
|
|
34
34
|
|
|
35
35
|
### Question
|
|
36
36
|
|
|
@@ -38,19 +38,19 @@ Does an intact Skeleton contract change agent behavior — grounding on the righ
|
|
|
38
38
|
|
|
39
39
|
### What we did
|
|
40
40
|
|
|
41
|
-
We ran a paired live A/B
|
|
41
|
+
We ran a paired live A/B self-benchmark with [`@post-print/agent-test`](https://www.npmjs.com/package/@post-print/agent-test): `skeleton-clean` vs `skeleton-messy`. The same authored prompts compare an intact fixture with a conflicting fixture.
|
|
42
42
|
|
|
43
43
|
Scenarios covered contested grounding (conflicting docs), docs-only validation routing, canonical grounding, owned-skill routing, and customize ownership. Protocol: **N=10** sequential paired compares on 2026-07-17; McNemar on paired pass/fail; median token deltas with a bootstrap CI on the mean.
|
|
44
44
|
|
|
45
45
|
Full method: [refs/llm-harness.md](refs/llm-harness.md). Suites: [agent-suites/README.md](agent-suites/README.md). Aggregated numbers: [SUMMARY.md](agent-suites/evidence/SUMMARY.md). Side-by-side excerpts: [evidence/transcripts/](agent-suites/evidence/transcripts/).
|
|
46
46
|
|
|
47
|
-
### What
|
|
47
|
+
### What the benchmark observed
|
|
48
48
|
|
|
49
49
|

|
|
50
50
|
|
|
51
51
|

|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Inside this harness, tasks that depend on an intact SSOT favored the clean fixture:
|
|
54
54
|
|
|
55
55
|
- **Contested grounding** — In every paired run, clean settled on the SSOT canonical; messy never did (McNemar p = 0.002). Clean hops the catalog/SSOT path; messy thrashes across conflicting docs.
|
|
56
56
|
- **Docs routing** — Clean consistently chose the correct audit lane; messy invented a non-existent `audit all` path (McNemar p = 0.002).
|
|
@@ -65,7 +65,7 @@ Charts regenerate from `SUMMARY.json` via `bun run agent:evidence:charts`.
|
|
|
65
65
|
|
|
66
66
|
### Limits
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
This is preliminary evidence from Skeleton’s own authored fixture, prompt set, repository, and execution period. Repeated runs characterize variance inside that harness; they do not independently establish a causal product effect. It is not a general coding-task or SWE-bench claim. Skill/customize did not separate when the entry doc already taught the correct rule.
|
|
69
69
|
|
|
70
70
|
### Industry context
|
|
71
71
|
|
|
@@ -99,6 +99,8 @@ Flag details: [install](docs/developer/install.md).
|
|
|
99
99
|
- **deny.paths** — globs for files that must not exist (often outside `scan.include`)
|
|
100
100
|
- **Coverage gaps** — markdown outside the scan perimeter (warn-only)
|
|
101
101
|
- **Doc meta + stale dates** — owner and `last-reviewed` on indexes and SSOT-bearing files
|
|
102
|
+
- **Review proof** — optional hashes bind a human review to exact document and `code-fit` target bytes
|
|
103
|
+
- **Code impact routing** — changed source files automatically pull linked documents into validation
|
|
102
104
|
- **Prose policy** (optional plugins) — YAML pattern rules; idle with no plugins
|
|
103
105
|
- **Shell / JSON syntax** — lightweight checks on changed `.sh` and `.json` files
|
|
104
106
|
|
|
@@ -114,6 +116,7 @@ Config lives in **`skeleton.toml`** at the repo root (preferred). Optional under
|
|
|
114
116
|
skeleton.toml # scan perimeter, deny.paths, docsLint, …
|
|
115
117
|
.skeleton/
|
|
116
118
|
├── catalog.md # generated by `skeleton catalog` (gitignored)
|
|
119
|
+
├── review-lock.json # generated review evidence when reviewProof.mode = "hash"
|
|
117
120
|
├── plugins/ # optional consumer audit plugins (.ts + built .mjs)
|
|
118
121
|
└── customize/ # project-specific skill overrides (optional)
|
|
119
122
|
└── code-review.md
|
|
@@ -139,8 +142,9 @@ Synced skills stay pristine. Project overrides live in `.skeleton/customize/<slu
|
|
|
139
142
|
|
|
140
143
|
```bash
|
|
141
144
|
skeleton init [--skills] [--force-hooks]
|
|
142
|
-
skeleton catalog [--check]
|
|
143
|
-
skeleton audit docs|skills|self [--strict] [--paths=a,b] [--fix[=doc-meta|anchors|ssot]] [--dry-run]
|
|
145
|
+
skeleton catalog [--check] [--strict]
|
|
146
|
+
skeleton audit docs|skills|self [--strict] [--json] [--paths=a,b] [--fix[=doc-meta|anchors|ssot]] [--dry-run]
|
|
147
|
+
skeleton audit docs --paths=docs/a.md --fix=doc-meta --confirm-reviewed
|
|
144
148
|
skeleton build-plugin [path] [--check]
|
|
145
149
|
skeleton validate changed [--staged | --base <ref>] [paths…]
|
|
146
150
|
skeleton references sync|check
|
|
@@ -156,7 +160,8 @@ skeleton customize resolve <slug>
|
|
|
156
160
|
| Foreign / lockfile-synced skill bodies | skip → lint in the owning skills/toolbox repo |
|
|
157
161
|
| `.sh`, `.bash`, `.zsh` | shellcheck or `bash -n` |
|
|
158
162
|
| Other `.json` | JSONC-tolerant syntax check |
|
|
159
|
-
| `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.py
|
|
163
|
+
| `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.py` | native code gates + audit documents whose `code-fit` target changed |
|
|
164
|
+
| `package.json`, `project.json` | native code gates (exits 1 if no auditable paths) |
|
|
160
165
|
|
|
161
166
|
Pre-commit: `skeleton validate changed --staged` (path-scoped, fast).
|
|
162
167
|
|
|
@@ -195,7 +200,7 @@ bun run check
|
|
|
195
200
|
|
|
196
201
|
`bun run check` = lint + test + typecheck + build + `audit:self`.
|
|
197
202
|
|
|
198
|
-
`validate:changed`
|
|
203
|
+
`validate:changed` does not replace code tests. It classifies code separately and also audits every scanned document that names a changed file in `code-fit`. Owned skill-body edits need `audit skills`. Code-only changes with no linked document still exit non-zero locally and point to native gates.
|
|
199
204
|
|
|
200
205
|
For code: `bun test`, `bun run typecheck`, `bun run build`.
|
|
201
206
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SkeletonConfig } from "./types.ts";
|
|
2
|
+
export declare const ROOT_CONFIG_TOML = "skeleton.toml";
|
|
3
|
+
export declare const LEGACY_CONFIG_YAML: string;
|
|
4
|
+
/** Built-in exclude patterns applied to scan and deny.paths passes. */
|
|
5
|
+
export declare const BUILTIN_EXCLUDES: string[];
|
|
6
|
+
/** Built-in excludes for coverage-gaps candidate discovery. */
|
|
7
|
+
export declare const COVERAGE_BUILTIN_EXCLUDES: string[];
|
|
8
|
+
export declare function findRepoRoot(startDir?: string): string;
|
|
9
|
+
export interface LoadConfigResult {
|
|
10
|
+
config: SkeletonConfig;
|
|
11
|
+
source: "toml" | "yaml";
|
|
12
|
+
warnedDual?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function loadConfigDetailed(root: string): LoadConfigResult;
|
|
15
|
+
export declare function loadConfig(root: string): SkeletonConfig;
|
|
16
|
+
export declare function mergedExcludes(config: SkeletonConfig): string[];
|
|
17
|
+
export declare function denyPaths(config: SkeletonConfig): string[];
|
|
18
|
+
export declare function nonPublicSkills(config: SkeletonConfig): string[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export interface ScanConfig {
|
|
2
|
+
include: string[];
|
|
3
|
+
exclude: string[];
|
|
4
|
+
/** Slugs present on disk but excluded from README Taxonomy (internal skills). */
|
|
5
|
+
nonPublicSkills?: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface DenyConfig {
|
|
8
|
+
/** Repo-wide globs that must not exist — audit fails if matched. */
|
|
9
|
+
paths?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface CustomizeConfig {
|
|
12
|
+
/** Basenames under `.skeleton/customize/` appended on every customize inject. */
|
|
13
|
+
alwaysInclude?: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Ownership policy for skill-body linting.
|
|
17
|
+
* Lockfile `sourceType` other than `local` (e.g. `github`) marks skills foreign by default.
|
|
18
|
+
*/
|
|
19
|
+
export interface SkillOwnershipConfig {
|
|
20
|
+
/** Repo-relative path to skills-lock.json (default: skills-lock.json). */
|
|
21
|
+
lockfile?: string;
|
|
22
|
+
/** Force these slugs owned even if the lockfile marks them foreign. */
|
|
23
|
+
ownedSlugs?: string[];
|
|
24
|
+
/** Force these slugs foreign even if absent from the lockfile / local. */
|
|
25
|
+
foreignSlugs?: string[];
|
|
26
|
+
}
|
|
27
|
+
/** Verifiable evidence that a human review covered exact document and code bytes. */
|
|
28
|
+
export interface ReviewProofConfig {
|
|
29
|
+
/** Hash mode stores reviewed document and code-target digests in a lockfile. */
|
|
30
|
+
mode: "hash";
|
|
31
|
+
/** Repo-relative proof lockfile (default: .skeleton/review-lock.json). */
|
|
32
|
+
lockfile?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Tunables for near-duplicate, SSOT-summary, and code-fit docs lint. */
|
|
35
|
+
export interface DocsLintConfig {
|
|
36
|
+
/** Jaccard threshold on word shingles (0–1). Default 0.72. */
|
|
37
|
+
nearDuplicateThreshold?: number;
|
|
38
|
+
/** Min fraction of SSOT tokens that must appear in evidence (default 0.35). */
|
|
39
|
+
ssotOverlapMin?: number;
|
|
40
|
+
/** Margin by which another file must beat own overlap for better-match (default 0.15). */
|
|
41
|
+
ssotBetterMatchMargin?: number;
|
|
42
|
+
/** When overlap fails, mention missing SSOT phrase in the message (default true). */
|
|
43
|
+
ssotPhraseCheck?: boolean;
|
|
44
|
+
/** Repo-relative path pairs to skip in near-dupe / duplicate-SSOT checks. */
|
|
45
|
+
ignorePairs?: [string, string][];
|
|
46
|
+
/** Globs excluded from near-dupe / duplicate-SSOT (still catalogued if they have SSOT). */
|
|
47
|
+
ignoreGlobs?: string[];
|
|
48
|
+
/**
|
|
49
|
+
* Min fraction of code identifiers that must appear in the doc (code-fit).
|
|
50
|
+
* Default 0.03 — separate from ssotOverlapMin (doc-grounding vs large modules).
|
|
51
|
+
*/
|
|
52
|
+
codeFitOverlapMin?: number;
|
|
53
|
+
/** Max auto-extracted surface names before surface= is required (default 25). */
|
|
54
|
+
codeFitSurfaceCap?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface SkeletonConfig {
|
|
57
|
+
scan: ScanConfig;
|
|
58
|
+
/** Re-read cadence (days) for doc-meta last-reviewed; separate from git edit-behind-review. */
|
|
59
|
+
daysUntilStale: number;
|
|
60
|
+
deny?: DenyConfig;
|
|
61
|
+
customize?: CustomizeConfig;
|
|
62
|
+
skillOwnership?: SkillOwnershipConfig;
|
|
63
|
+
reviewProof?: ReviewProofConfig;
|
|
64
|
+
docsLint?: DocsLintConfig;
|
|
65
|
+
/**
|
|
66
|
+
* Plugin entry paths relative to `.skeleton/` (e.g. `plugins/example.ts`).
|
|
67
|
+
* Each entry must have a built sibling `.mjs` (`skeleton build-plugin`).
|
|
68
|
+
*/
|
|
69
|
+
plugins?: string[];
|
|
70
|
+
/**
|
|
71
|
+
* Path prefixes where `draft-marker` prose-policy placement is allowed
|
|
72
|
+
* (in addition to `_draft-*.md` filenames). Not the same as `scan.exclude`.
|
|
73
|
+
*/
|
|
74
|
+
draftPathPrefixes?: string[];
|
|
75
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs↔code surface fit (no LLM): opt-in markers, public-name coverage, identifier overlap.
|
|
3
|
+
*/
|
|
4
|
+
export declare const DEFAULT_CODE_FIT_OVERLAP_MIN = 0.03;
|
|
5
|
+
export declare const DEFAULT_CODE_FIT_SURFACE_CAP = 25;
|
|
6
|
+
export interface CodeFitMarker {
|
|
7
|
+
targets: string[];
|
|
8
|
+
surface: string[] | null;
|
|
9
|
+
raw: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CodeFitOptions {
|
|
12
|
+
overlapMin?: number;
|
|
13
|
+
surfaceCap?: number;
|
|
14
|
+
root: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CodeFitIssue {
|
|
17
|
+
path: string;
|
|
18
|
+
message: string;
|
|
19
|
+
link?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Parse all code-fit HTML comment markers in a markdown body (fences + inline code stripped). */
|
|
22
|
+
export declare function parseCodeFitMarkers(content: string): CodeFitMarker[];
|
|
23
|
+
/** Strip // and /* *\/ comments and string literals for identifier harvest. */
|
|
24
|
+
export declare function stripCodeNoise(source: string): string;
|
|
25
|
+
/** Auto-extract public-ish names: export forms + case "…" dispatch labels. */
|
|
26
|
+
export declare function extractPublicSurface(source: string): string[];
|
|
27
|
+
/** Identifier tokens from source (for lexical overlap). */
|
|
28
|
+
export declare function codeIdentifiers(source: string): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Fraction of unique doc content tokens that also appear as identifiers in the
|
|
31
|
+
* code file (doc grounded in module vocabulary). Separate from name coverage.
|
|
32
|
+
*/
|
|
33
|
+
export declare function identifierOverlap(docContent: string, codeSource: string): number;
|
|
34
|
+
/** Evaluate one markdown doc's code-fit markers. */
|
|
35
|
+
export declare function evaluateCodeFitDoc(docPath: string, docContent: string, options: CodeFitOptions): CodeFitIssue[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SkeletonConfig } from "../config/types.ts";
|
|
2
|
+
import { type SkillIndex } from "./skill-roots.ts";
|
|
3
|
+
export declare function collectScanFiles(config: SkeletonConfig, root: string, skillIndex?: SkillIndex): string[];
|
|
4
|
+
export declare function collectBannedFiles(config: SkeletonConfig, root: string): string[];
|
|
5
|
+
export declare function collectCoverageCandidateFiles(root: string, exclude: string[]): string[];
|
|
6
|
+
/** Drop foreign lockfile-synced skill trees from doc-meta scope (linted upstream). */
|
|
7
|
+
export declare function excludeForeignSkillDocMetaPaths(docMetaPaths: string[], skillIndex?: SkillIndex): string[];
|
|
8
|
+
interface DocMetaCollectContext {
|
|
9
|
+
config: SkeletonConfig;
|
|
10
|
+
root: string;
|
|
11
|
+
registryPaths: string[];
|
|
12
|
+
skillIndex?: SkillIndex;
|
|
13
|
+
}
|
|
14
|
+
export declare function collectDocMetaPaths(ctx: DocMetaCollectContext): string[];
|
|
15
|
+
export declare function validateScanRoots(config: SkeletonConfig, root: string): string[];
|
|
16
|
+
export declare function filterDocMetaPaths(docMetaPaths: string[], paths: string[], skillIndex?: SkillIndex): string[];
|
|
17
|
+
export declare function filterToPaths(files: string[], paths: string[], root: string): string[];
|
|
18
|
+
export declare function includeExplicitMarkdownPaths(files: string[], paths: string[], root: string): string[];
|
|
19
|
+
export declare function readFileContent(absPath: string): string;
|
|
20
|
+
export declare function relPath(absPath: string, root: string): string;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SkeletonConfig } from "../config/types.ts";
|
|
2
|
+
import type { PolicyFile } from "../policies/types.ts";
|
|
3
|
+
import { type SkillIndex } from "./skill-roots.ts";
|
|
4
|
+
import type { SsotForm } from "./ssot.ts";
|
|
5
|
+
import { type SsotFileEntry } from "./ssot-collect.ts";
|
|
6
|
+
export interface AuditContext {
|
|
7
|
+
root: string;
|
|
8
|
+
config: SkeletonConfig;
|
|
9
|
+
files: string[];
|
|
10
|
+
docMetaPaths: string[];
|
|
11
|
+
/** Opt-in SSOT-bearing files (catalog membership). */
|
|
12
|
+
ssotEntries: SsotFileEntry[];
|
|
13
|
+
ssotErrors: Array<{
|
|
14
|
+
path: string;
|
|
15
|
+
kind: "dual" | "malformed";
|
|
16
|
+
detail: string;
|
|
17
|
+
forms?: SsotForm[];
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated Prefer ssotEntries — kept empty for plugin type stability during transition.
|
|
21
|
+
*/
|
|
22
|
+
registryPaths: string[];
|
|
23
|
+
/** @deprecated Always false — hand registry removed. */
|
|
24
|
+
registryHasTableHeader: boolean;
|
|
25
|
+
skillIndex: SkillIndex;
|
|
26
|
+
/**
|
|
27
|
+
* Foreign (synced) skill slugs — same as `skillIndex.foreignSlugs`.
|
|
28
|
+
* Used by doc-meta to skip consumer-side git-date freshness on upstream bodies.
|
|
29
|
+
*/
|
|
30
|
+
lockedSkillSlugs: Set<string>;
|
|
31
|
+
/** Compiled prose policies from plugins (empty when no plugins / no policy globs). */
|
|
32
|
+
policies: PolicyFile[];
|
|
33
|
+
}
|
|
34
|
+
export interface AuditOptions {
|
|
35
|
+
root?: string;
|
|
36
|
+
changed?: boolean;
|
|
37
|
+
paths?: string[];
|
|
38
|
+
policies?: PolicyFile[];
|
|
39
|
+
/**
|
|
40
|
+
* Union skill-tree markdown (SKILL.md + references/**) into the corpus even when
|
|
41
|
+
* `scan.exclude` dropped them. Used by bare `audit skills` so skill-scoped prose
|
|
42
|
+
* matches path-scoped / validate `--base` prove.
|
|
43
|
+
*/
|
|
44
|
+
includeExcludedSkillTrees?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare function createContext(options?: AuditOptions): AuditContext;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** `_draft-foo.md` anywhere in the tree. */
|
|
2
|
+
export declare const DRAFT_FILENAME_RE: RegExp;
|
|
3
|
+
/** Treat configured prefixes as directories (trailing `/` implied). */
|
|
4
|
+
export declare function normalizeDraftPrefix(prefix: string): string;
|
|
5
|
+
/** Fail closed on prefixes that would never match repo-relative paths. */
|
|
6
|
+
export declare function validateDraftPathPrefixes(prefixes: string[] | undefined): void;
|
|
7
|
+
/**
|
|
8
|
+
* Draft markers (`draft-marker` prose entry) are allowed in `_draft-*.md`
|
|
9
|
+
* filenames or under any configured `draftPathPrefixes` path prefix.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isDraftPlacementAllowed(relPath: string, draftPathPrefixes: string[]): boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AuditContext } from "./context.ts";
|
|
2
|
+
export type FixKind = "doc-meta" | "anchors" | "ssot";
|
|
3
|
+
export interface FixEdit {
|
|
4
|
+
file: string;
|
|
5
|
+
description: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ApplyFixesOptions {
|
|
9
|
+
kinds: FixKind[];
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ApplyFixesResult {
|
|
13
|
+
edits: FixEdit[];
|
|
14
|
+
modifiedFiles: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Merge per-file snapshots so default `--fix` (doc-meta + anchors) does not
|
|
18
|
+
* last-write-win. Prefer anchors content, then overlay last-reviewed from meta.
|
|
19
|
+
*/
|
|
20
|
+
/** Merge per-file fix edits: anchors content, overlay last-reviewed, then SSOT rewrite. */
|
|
21
|
+
export declare function coalesceFixEdits(metaEdits: FixEdit[], anchorEdits: FixEdit[], ssotEdits?: FixEdit[]): FixEdit[];
|
|
22
|
+
/** Resolve a write path and refuse escapes outside the repo root (incl. symlinks). */
|
|
23
|
+
export declare function resolveWritePath(root: string, relFile: string): string;
|
|
24
|
+
export declare function applyFixes(ctx: AuditContext, options: ApplyFixesOptions): ApplyFixesResult;
|
|
25
|
+
export declare function parseFixKinds(raw: string | true): FixKind[];
|
|
26
|
+
/** Owning audit rule id for each fix kind (used to scope `--fix` under `--only`). */
|
|
27
|
+
export declare const FIX_KIND_RULE: Record<FixKind, string>;
|
|
28
|
+
/** When `--only` is set, keep fix kinds whose owning rules are selected. */
|
|
29
|
+
export declare function fixKindsForOnly(kinds: FixKind[], only: Set<string> | null): FixKind[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function lastGitCommitDate(relPath: string, root: string): string | null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ExtractedLink {
|
|
2
|
+
target: string;
|
|
3
|
+
line?: number;
|
|
4
|
+
/** Inclusive start offset of the URL in the source (when known). */
|
|
5
|
+
urlStart?: number;
|
|
6
|
+
/** Exclusive end offset of the URL in the source (when known). */
|
|
7
|
+
urlEnd?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Strip leading YAML frontmatter so a closing `---` is not parsed as a setext
|
|
11
|
+
* underline (which yields false heading slugs like `title-getting-started`).
|
|
12
|
+
*/
|
|
13
|
+
export declare function stripYamlFrontmatter(content: string): string;
|
|
14
|
+
export declare function extractLinksFromMarkdown(content: string, _filePath?: string): ExtractedLink[];
|
|
15
|
+
export declare function extractHeadingSlugs(content: string, _filePath?: string): Set<string>;
|
|
16
|
+
export declare function slugifyAnchor(anchor: string): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type Severity = "error" | "warning";
|
|
2
|
+
export interface Issue {
|
|
3
|
+
rule: string;
|
|
4
|
+
code?: string;
|
|
5
|
+
file: string;
|
|
6
|
+
link?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
remediation?: string;
|
|
9
|
+
severity: Severity;
|
|
10
|
+
}
|
|
11
|
+
export interface ReportOptions {
|
|
12
|
+
strict?: boolean;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
label?: string;
|
|
15
|
+
fileCount?: number;
|
|
16
|
+
successSuffix?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface IssueOptions {
|
|
19
|
+
link?: string;
|
|
20
|
+
severity?: Severity;
|
|
21
|
+
code?: string;
|
|
22
|
+
remediation?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface IssueDetails extends IssueOptions {
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function issue(rule: string, file: string, details: string | IssueDetails): Issue;
|
|
28
|
+
/** @deprecated Prefer the details-object form. Retained for 1.x plugin compatibility. */
|
|
29
|
+
export declare function issue(rule: string, file: string, message: string, options?: IssueOptions): Issue;
|
|
30
|
+
export declare function finalizeIssues(issues: Issue[], strict: boolean): Issue[];
|
|
31
|
+
export declare function printReport(issues: Issue[], options: ReportOptions): number;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AuditContext } from "./context.ts";
|
|
2
|
+
import { type Issue } from "./report.ts";
|
|
3
|
+
export declare const DEFAULT_REVIEW_LOCKFILE = ".skeleton/review-lock.json";
|
|
4
|
+
export interface AttestDocumentsOptions {
|
|
5
|
+
root: string;
|
|
6
|
+
paths: string[];
|
|
7
|
+
reviewedAt?: string;
|
|
8
|
+
dryRun?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface AttestDocumentsResult {
|
|
11
|
+
reviewedAt: string;
|
|
12
|
+
documents: string[];
|
|
13
|
+
lockfile: string | null;
|
|
14
|
+
modifiedFiles: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function formatLocalReviewDate(date: Date): string;
|
|
17
|
+
/** Record explicit review evidence for exactly the selected documents. */
|
|
18
|
+
export declare function attestDocuments(options: AttestDocumentsOptions): AttestDocumentsResult;
|
|
19
|
+
export declare function runReviewProofRule(ctx: AuditContext): Issue[];
|
|
20
|
+
export declare const reviewProofRule: {
|
|
21
|
+
id: string;
|
|
22
|
+
run: typeof runReviewProofRule;
|
|
23
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const REGISTRY_REL_PATH = ".skeleton/registry.md";
|
|
2
|
+
export declare const CATALOG_REL_PATH = ".skeleton/catalog.md";
|
|
3
|
+
export declare const REGISTRY_DIR_REL = ".skeleton";
|
|
4
|
+
export declare const EXTERNAL_LINK_RE: RegExp;
|
|
5
|
+
export declare const SOURCE_OF_TRUTH_BANNER_RE: RegExp;
|
|
6
|
+
export declare const SOURCE_OF_TRUTH_BANNER_LINE_RE: RegExp;
|
|
7
|
+
export declare const DOC_META_RE: RegExp;
|
|
8
|
+
export declare const DOC_META_LAST_REVIEWED_RE: RegExp;
|
|
9
|
+
/** Extract last-reviewed date from the doc-meta comment only (ignore prose examples). */
|
|
10
|
+
export declare function docMetaLastReviewed(content: string): string | null;
|
|
11
|
+
/** Replace last-reviewed inside the doc-meta comment only. Returns null if unchanged/missing. */
|
|
12
|
+
export declare function replaceDocMetaLastReviewed(content: string, date: string): string | null;
|
|
13
|
+
export declare const SKILL_LINK_IN_TARGET_RE: RegExp;
|
|
14
|
+
export declare const SKILL_LINK_RE: RegExp;
|
|
15
|
+
/**
|
|
16
|
+
* Normalize repo-relative paths for bucket / filter matching.
|
|
17
|
+
* Converts backslashes and strips leading `./` segments. Absolute paths are left as-is
|
|
18
|
+
* (callers that accept only repo-relative paths must reject or remap them separately).
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeRelPath(p: string): string;
|
|
21
|
+
export declare function isExternalLink(target: string): boolean;
|
|
22
|
+
export declare function isPlaceholderLink(target: string): boolean;
|
|
23
|
+
export declare function matchesGlobScope(relPath: string, scope: string | undefined): boolean;
|
|
24
|
+
export declare function extractScanRootsFromInclude(include: string[]): string[];
|
|
25
|
+
export declare function resolveFromRegistry(root: string, linkTarget: string): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SkillOwnershipConfig } from "../config/types.ts";
|
|
2
|
+
export declare const DEFAULT_SKILLS_LOCKFILE = "skills-lock.json";
|
|
3
|
+
export type SkillSourceType = "github" | "local" | string;
|
|
4
|
+
export interface SkillLockEntry {
|
|
5
|
+
source: string;
|
|
6
|
+
sourceType: SkillSourceType;
|
|
7
|
+
skillPath?: string;
|
|
8
|
+
computedHash?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SkillsLockFile {
|
|
11
|
+
version: number;
|
|
12
|
+
skills: Record<string, SkillLockEntry>;
|
|
13
|
+
}
|
|
14
|
+
export type SkillOwnership = "owned" | "foreign";
|
|
15
|
+
export interface SkillProvenanceMap {
|
|
16
|
+
/** Repo-relative lockfile path that was read, or null when absent. */
|
|
17
|
+
lockfile: string | null;
|
|
18
|
+
/** Parsed lock entries keyed by slug (empty when no lock / empty lock). */
|
|
19
|
+
entries: Record<string, SkillLockEntry>;
|
|
20
|
+
/** Non-fatal parse / version problems. */
|
|
21
|
+
warnings: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* External lock provenance is treated as foreign (synced) by default.
|
|
25
|
+
* `local` stays owned so same-repo installs (e.g. `skills add .`) keep linting.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isForeignLockSourceType(sourceType: string): boolean;
|
|
28
|
+
export declare function loadSkillsLock(root: string, lockfileRel?: string): SkillProvenanceMap;
|
|
29
|
+
/**
|
|
30
|
+
* Classify a discovered skill slug.
|
|
31
|
+
* Precedence: config ownedSlugs → config foreignSlugs → lock provenance → owned default.
|
|
32
|
+
*/
|
|
33
|
+
export declare function classifySkillOwnership(slug: string, provenance: SkillProvenanceMap, ownership?: SkillOwnershipConfig): SkillOwnership;
|
|
34
|
+
export declare function resolveOwnershipForSlugs(slugs: string[], provenance: SkillProvenanceMap, ownership?: SkillOwnershipConfig): {
|
|
35
|
+
ownedSlugs: string[];
|
|
36
|
+
foreignSlugs: string[];
|
|
37
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SkillOwnershipConfig } from "../config/types.ts";
|
|
2
|
+
import { type SkillProvenanceMap } from "./skill-provenance.ts";
|
|
3
|
+
export declare const NESTED_SKILL_ROOTS: readonly [".claude/skills", ".agents/skills"];
|
|
4
|
+
/** Directories at repo root that must not be treated as flat skill slugs. */
|
|
5
|
+
export declare const FLAT_SKILL_DENYLIST: Set<string>;
|
|
6
|
+
export type SkillRootKind = "nested" | "flat";
|
|
7
|
+
export interface SkillRoot {
|
|
8
|
+
kind: SkillRootKind;
|
|
9
|
+
relPath: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SkillIndex {
|
|
12
|
+
roots: SkillRoot[];
|
|
13
|
+
/** All discovered skill slugs (owned + foreign) — used for link resolution. */
|
|
14
|
+
slugs: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Slugs that exist as flat trees (`<slug>/SKILL.md` at repo root).
|
|
17
|
+
* Flat path matching must use this set — never the union `slugs` — so a
|
|
18
|
+
* nested-only foreign slug cannot poison top-level dirs with the same name.
|
|
19
|
+
*/
|
|
20
|
+
flatSlugs: string[];
|
|
21
|
+
/** Skill slugs whose bodies this repo owns and should lint. */
|
|
22
|
+
ownedSlugs: string[];
|
|
23
|
+
/** Synced / lockfile foreign skills — skipped for body lint. */
|
|
24
|
+
foreignSlugs: string[];
|
|
25
|
+
provenance: SkillProvenanceMap;
|
|
26
|
+
}
|
|
27
|
+
export declare function detectSkillRoots(root: string): SkillRoot[];
|
|
28
|
+
export declare function buildSkillIndex(root: string, ownership?: SkillOwnershipConfig): SkillIndex;
|
|
29
|
+
export declare function isOwnedSkillSlug(index: SkillIndex, slug: string): boolean;
|
|
30
|
+
export declare function isForeignSkillSlug(index: SkillIndex, slug: string): boolean;
|
|
31
|
+
export declare function resolveSkillPath(index: SkillIndex, root: string, slug: string): string | null;
|
|
32
|
+
export declare function isSkillPath(relPath: string, index: SkillIndex): boolean;
|
|
33
|
+
export declare function skillSlugForPath(relPath: string, index: SkillIndex): string | null;
|
|
34
|
+
/** True when path is under a skill tree classified foreign for body lint. */
|
|
35
|
+
export declare function isForeignSkillPath(relPath: string, index: SkillIndex): boolean;
|
|
36
|
+
export declare function slugFromSkillPath(relPath: string): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a skill slug from a read path.
|
|
39
|
+
* Covers `…/SKILL.md`, nested `.claude|agents/skills/<slug>/**`, and flat
|
|
40
|
+
* `<slug>/references/**` (optionally verified via workspaceRoot + SKILL.md).
|
|
41
|
+
*/
|
|
42
|
+
export declare function slugFromPath(filePath: string, workspaceRoot?: string): string | null;
|
|
43
|
+
export declare function skillCollectAugments(index: SkillIndex): string[];
|
|
44
|
+
/**
|
|
45
|
+
* Repo-relative markdown paths for owned skill trees (SKILL.md + references/**,
|
|
46
|
+
* including under scan.exclude). Used by validate --base policy prove so skill-scoped
|
|
47
|
+
* prose still runs against the full skill body, not just SKILL.md.
|
|
48
|
+
*
|
|
49
|
+
* Walks each nested root independently so the same slug under both `.claude/skills`
|
|
50
|
+
* and `.agents/skills` (distinct dirs) is fully covered — not first-wins only.
|
|
51
|
+
*/
|
|
52
|
+
export declare function listSkillMarkdownPaths(root: string, index: SkillIndex): string[];
|
|
53
|
+
export declare function listSkillSlugs(index: SkillIndex): string[];
|
|
54
|
+
export declare function listOwnedSkillSlugs(index: SkillIndex): string[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type SsotEntry, type SsotForm } from "./ssot.ts";
|
|
2
|
+
export interface SsotFileEntry extends SsotEntry {
|
|
3
|
+
path: string;
|
|
4
|
+
}
|
|
5
|
+
export interface CollectSsotResult {
|
|
6
|
+
entries: SsotFileEntry[];
|
|
7
|
+
/** Path → dual forms or malformed detail for the ssot rule. */
|
|
8
|
+
errors: Array<{
|
|
9
|
+
path: string;
|
|
10
|
+
kind: "dual" | "malformed";
|
|
11
|
+
detail: string;
|
|
12
|
+
forms?: SsotForm[];
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
/** Collect opt-in SSOT membership from scanned markdown abs paths. */
|
|
16
|
+
export declare function collectSsotEntries(files: string[], root: string): CollectSsotResult;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexical SSOT↔evidence fit (no LLM).
|
|
3
|
+
* Conservative stemming, H1/lead/body evidence, overlap, phrase-as-explanation, better-match.
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_SSOT_OVERLAP_MIN = 0.35;
|
|
6
|
+
export declare const DEFAULT_BETTER_MATCH_MARGIN = 0.15;
|
|
7
|
+
export declare function lightStem(token: string): string;
|
|
8
|
+
export declare function contentTokens(text: string): string[];
|
|
9
|
+
export declare function uniqueContentTokens(text: string): string[];
|
|
10
|
+
/** First ATX H1 text, if any. */
|
|
11
|
+
export declare function extractH1(content: string): string;
|
|
12
|
+
/** First non-empty prose paragraph after stripping SSOT/meta/code. */
|
|
13
|
+
export declare function extractLeadParagraph(content: string): string;
|
|
14
|
+
export declare function buildEvidenceText(content: string): string;
|
|
15
|
+
export declare function ssotEvidenceOverlap(summary: string, evidence: string): number;
|
|
16
|
+
/** Longest contentful n-gram from summary (prefer 3, else 2). */
|
|
17
|
+
export declare function longestSummaryPhrase(summary: string): string[] | null;
|
|
18
|
+
export declare function evidenceHasPhrase(evidence: string, phrase: string[]): boolean;
|
|
19
|
+
export interface FitOptions {
|
|
20
|
+
overlapMin?: number;
|
|
21
|
+
betterMatchMargin?: number;
|
|
22
|
+
phraseCheck?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface FitFile {
|
|
25
|
+
path: string;
|
|
26
|
+
summary: string;
|
|
27
|
+
content: string;
|
|
28
|
+
}
|
|
29
|
+
export type FitIssueKind = "short" | "weak" | "better-match";
|
|
30
|
+
export interface FitIssue {
|
|
31
|
+
kind: FitIssueKind;
|
|
32
|
+
path: string;
|
|
33
|
+
message: string;
|
|
34
|
+
otherPath?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function evaluateSsotFit(files: FitFile[], options?: FitOptions): FitIssue[];
|
|
37
|
+
/** @deprecated Prefer ssotEvidenceOverlap — kept for callers that pass raw body. */
|
|
38
|
+
export declare function ssotBodyOverlap(summary: string, body: string): number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type SsotForm = "comment" | "visible" | "legacy";
|
|
2
|
+
export interface SsotEntry {
|
|
3
|
+
summary: string;
|
|
4
|
+
form: SsotForm;
|
|
5
|
+
}
|
|
6
|
+
export type SsotParseResult = {
|
|
7
|
+
status: "ok";
|
|
8
|
+
entry: SsotEntry;
|
|
9
|
+
} | {
|
|
10
|
+
status: "none";
|
|
11
|
+
} | {
|
|
12
|
+
status: "dual";
|
|
13
|
+
forms: SsotForm[];
|
|
14
|
+
} | {
|
|
15
|
+
status: "malformed";
|
|
16
|
+
detail: string;
|
|
17
|
+
};
|
|
18
|
+
/** @deprecated Prefer parseSsot — presence-only legacy check. */
|
|
19
|
+
export declare const SOURCE_OF_TRUTH_BANNER_RE: RegExp;
|
|
20
|
+
export declare const SOURCE_OF_TRUTH_BANNER_LINE_RE: RegExp;
|
|
21
|
+
/**
|
|
22
|
+
* Parse SSOT from markdown. Opt-in: `none` is valid (file not in catalog).
|
|
23
|
+
* Dual encodings (comment + visible, etc.) or empty payloads are errors.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseSsot(content: string): SsotParseResult;
|
|
26
|
+
/** Rewrite a legacy banner to the preferred HTML comment form. */
|
|
27
|
+
export declare function rewriteLegacySsotToComment(content: string): string | null;
|
|
28
|
+
export declare function preferredSsotComment(summary: string): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AuditContext } from "../core/context.ts";
|
|
2
|
+
import type { FixEdit } from "../core/fix.ts";
|
|
3
|
+
declare function replaceExactLinkTarget(content: string, from: string, to: string): string;
|
|
4
|
+
export declare function collectAnchorFixes(ctx: AuditContext): FixEdit[];
|
|
5
|
+
/** Exported for unit tests. */
|
|
6
|
+
export { replaceExactLinkTarget };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AuditContext } from "../core/context.ts";
|
|
2
|
+
import type { FixEdit } from "../core/fix.ts";
|
|
3
|
+
export declare function bumpDocMetaLastReviewed(content: string, gitDate: string): string | null;
|
|
4
|
+
export declare function collectDocMetaFixes(ctx: AuditContext): FixEdit[];
|