dsh-plugin-inspector 0.1.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.
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Parsing a Cordis patch layer with the harness's own `!!js` dialect, and
3
+ * modelling it the way `applyEntryPatches` does.
4
+ *
5
+ * The dialect is transcribed from `dsh/scripts/verify-cordis-config.ts` and
6
+ * `dsh/vendor/include/src/index.ts`: `yaml.JSON_SCHEMA` extended with one
7
+ * scalar type for `tag:yaml.org,2002:js`, whose constructor produces an inert
8
+ * `{ __jsExpr }` node. **The expression text is never evaluated here.** Where
9
+ * the harness would call `new Function('ctx', 'expr', 'with (ctx) { return
10
+ * eval(expr) }')`, this module only ever compiles `return (expr)` to learn
11
+ * whether it parses, and parses it a second time with the TypeScript parser to
12
+ * classify what it reaches. Neither compiles-and-calls.
13
+ *
14
+ * js-yaml is pinned to the harness's `^4.2.0`. Parsing the same bytes
15
+ * differently from the runtime would make every downstream result unsound.
16
+ * @module dsh-plugin-inspector/cordis-yaml
17
+ */
18
+ import yaml from 'js-yaml';
19
+ /** The inert node a `!!js` scalar becomes. Mirrors the loader's own representation. */
20
+ export interface JsExprNode {
21
+ readonly __jsExpr: string;
22
+ }
23
+ /**
24
+ * The entry-list dialect: JSON schema plus `!!js`. Deliberately *not*
25
+ * `DEFAULT_SCHEMA`, which would also accept `!!python`, `!!binary` and friends
26
+ * — matching the harness exactly means a tag the harness rejects is rejected
27
+ * here too, and shows up as a finding rather than as parsed data.
28
+ */
29
+ export declare const patchSchema: yaml.Schema;
30
+ /** What a `!!js` expression can reach, in ascending order of reach. */
31
+ export type ExpressionClass = 'literal' | 'inert-read' | 'harness-call' | 'call' | 'mutation' | 'module-access' | 'unparseable';
32
+ /** Every classification, in the order the report tallies them. */
33
+ export declare const EXPRESSION_CLASSES: readonly ExpressionClass[];
34
+ /** Whether the loader ever evaluates the node at this location. */
35
+ export type ExpressionSlot = 'config' | 'disabled' | 'inert';
36
+ /** One `!!js` node found in a patch document. */
37
+ export interface ExpressionSite {
38
+ /** Diagnostic path, e.g. `[0].insert[1].config.cwd`. */
39
+ readonly path: string;
40
+ readonly expression: string;
41
+ readonly slot: ExpressionSlot;
42
+ readonly classification: ExpressionClass;
43
+ /** Parse diagnostic when `classification` is `unparseable`. */
44
+ readonly parseError?: string;
45
+ }
46
+ /** A patch that modifies a row that some earlier layer already defined. */
47
+ export interface OverridePatch {
48
+ readonly path: string;
49
+ readonly id: string;
50
+ /** `name` on a non-insert patch is an assertion guard, not an override. */
51
+ readonly nameGuard: string | null;
52
+ /** Keys copied verbatim onto the target row, in declaration order. */
53
+ readonly overriddenKeys: readonly string[];
54
+ /** The raw `disabled` value, present only when the patch sets it. */
55
+ readonly disabled: unknown;
56
+ readonly config: unknown;
57
+ }
58
+ /** A row this layer adds to the composed profile. */
59
+ export interface InsertedRow {
60
+ readonly path: string;
61
+ readonly id: string | null;
62
+ readonly name: string | null;
63
+ readonly config: unknown;
64
+ /** Id of the group row this insert targets, or `null` for a top-level insert. */
65
+ readonly intoGroupId: string | null;
66
+ /** Service names the row re-maps to a fresh realm for its whole subtree. */
67
+ readonly isolate: readonly string[];
68
+ /** Service names the row interposes on for its whole subtree. */
69
+ readonly intercept: readonly string[];
70
+ }
71
+ /** Why a walk stopped early, or `null` when it ran to completion. */
72
+ export type WalkLimit = 'nodes' | 'depth' | null;
73
+ /** One parsed patch layer. */
74
+ export interface PatchDocument {
75
+ /** Package-relative path of the YAML file. */
76
+ readonly file: string;
77
+ readonly overrides: readonly OverridePatch[];
78
+ readonly inserts: readonly InsertedRow[];
79
+ readonly expressions: readonly ExpressionSite[];
80
+ /**
81
+ * Which walk ceiling stopped the analysis of this layer, or `null`. Non-null
82
+ * means the layer was read in part, which Tier C reports.
83
+ */
84
+ readonly limit: WalkLimit;
85
+ }
86
+ /** Nodes one patch layer may be walked through before the walk gives up. */
87
+ export declare const MAX_WALK_NODES = 200000;
88
+ /** Nesting one patch layer may reach before the walk gives up. */
89
+ export declare const MAX_WALK_DEPTH = 200;
90
+ /** Thrown when the patch file cannot be parsed as an entry list. */
91
+ export declare class PatchParseError extends Error {
92
+ /** True when the failure is a `!js` single-bang tag, which never loads anywhere. */
93
+ readonly singleBangTag: boolean;
94
+ /**
95
+ * @param message - the underlying parse diagnostic.
96
+ * @param singleBangTag - whether the raw text contains a `!js` tag.
97
+ */
98
+ constructor(message: string, singleBangTag: boolean);
99
+ }
100
+ /**
101
+ * Whether a value is a `!!js` node. Matches the loader's own predicate.
102
+ * @param value - the parsed value.
103
+ * @returns true for an expression node.
104
+ */
105
+ export declare function isJsExpr(value: unknown): value is JsExprNode;
106
+ /**
107
+ * Classify what a `!!js` expression can reach, by parsing it — never running
108
+ * it. Precedence is by reach: module access beats mutation beats an unknown
109
+ * call beats a known-inert call beats a read, so an expression is reported at
110
+ * its most capable form.
111
+ *
112
+ * The grading is by reach, not by syntactic form. `dshHomePath('sessions')` is
113
+ * a `CallExpression` and so is `steal()`, but the first is a helper the harness
114
+ * itself provides to these expressions and uses in its own shipped bundle,
115
+ * while the second names something this tool cannot resolve. Grading both as
116
+ * the same thing puts the harness's own configuration at the same severity as
117
+ * an attack and teaches the reader to skip the class.
118
+ * @param expression - the raw expression text.
119
+ * @returns the classification and, when it does not parse, the diagnostic.
120
+ */
121
+ export declare function classifyExpression(expression: string): {
122
+ class: ExpressionClass;
123
+ parseError?: string;
124
+ };
125
+ /**
126
+ * Parse a patch layer.
127
+ * @param file - package-relative path, used in findings.
128
+ * @param text - the YAML text.
129
+ * @returns the modelled patch document.
130
+ * @throws PatchParseError when the text is not a loadable entry list.
131
+ */
132
+ export declare function parsePatchDocument(file: string, text: string): PatchDocument;
133
+ //# sourceMappingURL=cordis-yaml.d.ts.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Classifying the files inside an analysed package, and formatting excerpts of
3
+ * them for evidence.
4
+ * @module dsh-plugin-inspector/files
5
+ */
6
+ /**
7
+ * Whether a path is JavaScript or TypeScript source this tool will parse.
8
+ * Declaration files are excluded: they carry types, never behavior.
9
+ * @param path - package-relative path.
10
+ * @returns true when the file should be parsed.
11
+ */
12
+ export declare function isSourceFile(path: string): boolean;
13
+ /**
14
+ * Whether a path is markdown that can reach the model verbatim.
15
+ *
16
+ * The reach is conditional and PLAN.md §6.1 says so: a `SKILL.md` inside an npm
17
+ * package is only discovered when the plugin registers it through
18
+ * `ctx.skills`, when a patch row redirects a skill root into the package, or
19
+ * when something copies it into the user's workspace. This predicate answers
20
+ * "is this the kind of file that would reach the model if it were found", not
21
+ * "will it be found".
22
+ * @param path - package-relative POSIX path.
23
+ * @returns true for skill and agent-instruction markdown.
24
+ */
25
+ export declare function isModelVisibleText(path: string): boolean;
26
+ /**
27
+ * Whether a path is a Cordis config file, using the harness's own naming
28
+ * convention from `scripts/cordis-config-files.ts`.
29
+ * @param path - package-relative POSIX path.
30
+ * @returns true for a cordis YAML file.
31
+ */
32
+ export declare function isCordisConfigFile(path: string): boolean;
33
+ /**
34
+ * Resolve a manifest-declared path to the package-relative POSIX form used as
35
+ * map keys, or report that it leaves the package.
36
+ *
37
+ * Only `..` escapes. An absolute path does **not**: the launcher resolves the
38
+ * patch as `join(packageDir, declared)` (`packages/boot/app-boot/src/profile.ts`),
39
+ * and `join` re-roots an absolute second argument *inside* the first, so
40
+ * `join('/…/pkg', '/etc/passwd')` is `/…/pkg/etc/passwd`. A leading slash
41
+ * therefore names a file the package does not ship, not a file outside it.
42
+ * @param declared - the path exactly as the manifest declares it.
43
+ * @returns the normalised package-relative path, or `null` when it escapes.
44
+ */
45
+ export declare function normalizePackagePath(declared: string): string | null;
46
+ /**
47
+ * Render a value as JSON-like evidence without walking a graph as a tree.
48
+ * A YAML anchor makes one node reachable by many paths, so `JSON.stringify` on
49
+ * a patch row's `config` can be asked to serialise billions of nodes from a
50
+ * few hundred bytes of input. This stops at a depth and a length instead.
51
+ * @param value - the value to describe.
52
+ * @param maxDepth - how far to descend before writing an ellipsis.
53
+ * @param limit - the longest string to return.
54
+ * @returns the bounded rendering.
55
+ */
56
+ export declare function boundedJson(value: unknown, maxDepth?: number, limit?: number): string;
57
+ /**
58
+ * Reduce text to a single-line excerpt safe to print in a report.
59
+ * @param text - the source text.
60
+ * @param limit - maximum characters to keep.
61
+ * @returns the collapsed, truncated excerpt.
62
+ */
63
+ export declare function snippet(text: string, limit?: number): string;
64
+ /**
65
+ * Convert a character offset to a `line:column` locator, both 1-based.
66
+ * @param text - the file text.
67
+ * @param offset - the character offset.
68
+ * @returns the locator.
69
+ */
70
+ export declare function lineColumn(text: string, offset: number): string;
71
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `dsh-plugin-inspector` — static pre-install analysis of a DeepSeek Harness
3
+ * plugin.
4
+ *
5
+ * The library face of the tool, for callers that want the report rather than
6
+ * the exit code. {@link inspect} decodes a plugin directory or npm tarball,
7
+ * runs the three check tiers over the decoded form, and returns a
8
+ * {@link Report}. It never installs, builds, imports, spawns, or evaluates
9
+ * anything from the analysed package.
10
+ *
11
+ * The ceiling is triage, not containment. See `README.md` §Limitations.
12
+ * @module dsh-plugin-inspector
13
+ */
14
+ export { exceedsThreshold, inspect, TOOL_NAME, TOOL_VERSION } from './inspect.ts';
15
+ export { renderHuman, renderJson } from './report.ts';
16
+ export { classifyExpression, isJsExpr, parsePatchDocument, patchSchema, PatchParseError, type ExpressionClass, type ExpressionSite, type ExpressionSlot, type InsertedRow, type JsExprNode, type OverridePatch, type PatchDocument, } from './cordis-yaml.ts';
17
+ export { declaredPackages, ManifestError, parseManifest, type PackageManifest } from './manifest.ts';
18
+ export { DEFAULT_LIMITS, loadSource, SourceError, type PluginSource, type ReadLimits } from './source.ts';
19
+ export { globMatch, publishSet, type PublishBasis, type PublishInputs, type PublishSet } from './publish.ts';
20
+ export { INJECTION_RULES, scanInjection, type InjectionMatch, type InjectionRule } from './injection.ts';
21
+ export { compareFindings, SEVERITIES, SEVERITY_RANK, summarize, type AnalysisIntegrity, type Confidence, type Evidence, type Facts, type Finding, type Report, type Severity, type Tier, } from './model.ts';
22
+ export { CORE_ROWS, CORE_ROW_IDS, HARNESS_BUNDLE_PACKAGES, HARNESS_REFERENCE, SEAM_KEYS, SECURITY_ROW_IDS, WATERFALL_EVENTS, type BundleName, type CoreRow, } from './knowledge.ts';
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Prompt-injection heuristics, applied to model-visible text only.
3
+ *
4
+ * "Model-visible" is a narrow set on purpose: shipped skill and
5
+ * agent-instruction markdown, and the `description` string of a registered
6
+ * tool. Those reach the model verbatim, unescaped and uncapped. Ordinary source
7
+ * comments do not, and scanning them would produce a stream of false positives
8
+ * from documentation that happens to quote an attack.
9
+ *
10
+ * These are heuristics over natural language. They will miss a rephrased
11
+ * instruction and they will occasionally fire on a legitimate document that
12
+ * discusses prompt injection. Both directions are stated in the finding.
13
+ * @module dsh-plugin-inspector/injection
14
+ */
15
+ /** One heuristic and what it is looking for. */
16
+ export interface InjectionRule {
17
+ readonly id: string;
18
+ readonly pattern: RegExp;
19
+ /** What a match would mean, phrased for a report. */
20
+ readonly meaning: string;
21
+ }
22
+ /**
23
+ * The rule table. Each pattern targets an instruction that only makes sense if
24
+ * the author expects a model rather than a person to read it.
25
+ */
26
+ export declare const INJECTION_RULES: readonly InjectionRule[];
27
+ /** One heuristic match. */
28
+ export interface InjectionMatch {
29
+ readonly ruleId: string;
30
+ readonly meaning: string;
31
+ /** Character offset of the match in the scanned text. */
32
+ readonly index: number;
33
+ readonly excerpt: string;
34
+ }
35
+ /**
36
+ * Scan model-visible text for injection phrasing.
37
+ * @param text - the text a model would receive.
38
+ * @returns one match per rule that fired, at its first occurrence.
39
+ */
40
+ export declare function scanInjection(text: string): InjectionMatch[];
41
+ //# sourceMappingURL=injection.d.ts.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Orchestration: decode the package once, run the three tiers over the decoded
3
+ * form, apply the Tier C downgrade, and assemble the report.
4
+ *
5
+ * This module is the only place that reads the package and the only place that
6
+ * decides confidence. Nothing here — and nothing it calls — imports, requires,
7
+ * spawns, or evaluates anything from the analysed package. The only `Function`
8
+ * constructed anywhere in this tool is the parse-only compile in
9
+ * `cordis-yaml.ts`, and its result is discarded without being called.
10
+ * @module dsh-plugin-inspector/inspect
11
+ */
12
+ import { type Report, type Severity } from './model.ts';
13
+ /** This tool's own version, reported in the JSON document. */
14
+ export declare const TOOL_VERSION = "0.1.0";
15
+ /** This tool's package name, reported in the JSON document. */
16
+ export declare const TOOL_NAME = "dsh-plugin-inspector";
17
+ /**
18
+ * Inspect a plugin package.
19
+ * @param target - a plugin directory, or a `.tgz` / `.tar.gz` npm tarball.
20
+ * @returns the complete report.
21
+ * @throws SourceError or ManifestError when the target cannot be analysed at all.
22
+ */
23
+ export declare function inspect(target: string): Promise<Report>;
24
+ /**
25
+ * Whether a report should fail a CI gate.
26
+ * @param report - the report.
27
+ * @param threshold - the lowest severity that fails, or `none` to never fail.
28
+ * @returns true when at least one finding is at or above the threshold.
29
+ */
30
+ export declare function exceedsThreshold(report: Report, threshold: Severity | 'none'): boolean;
31
+ //# sourceMappingURL=inspect.d.ts.map
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Ground truth read out of the DeepSeek Harness checkout: the core row
3
+ * inventory, the capability seam keys, the waterfall event set, and the
4
+ * capabilities the harness's own sandbox denies untrusted code.
5
+ *
6
+ * Every table here cites the harness file it was transcribed from. These are
7
+ * facts about a specific harness version, not opinions — when the harness
8
+ * changes, these tables are what needs updating, and keeping them in one
9
+ * module is what makes that a single reviewable diff.
10
+ * @module dsh-plugin-inspector/knowledge
11
+ */
12
+ /**
13
+ * Harness version these tables were transcribed from — the version string in
14
+ * the checkout's own `packages/bundle/*&#47;package.json`.
15
+ */
16
+ export declare const HARNESS_REFERENCE = "0.1.0-rc.5";
17
+ /** The shipped bundles, each of which is one patch layer over the profile root. */
18
+ export type BundleName = 'base' | 'headless' | 'web-app';
19
+ /**
20
+ * The three profile bundles the harness ships, mapped to what each one is.
21
+ * A package that *is* one of these composes the core rows rather than modifying
22
+ * somebody else's: `@deepseek-ai/dsh-web-app` disabling two dozen rows the base
23
+ * layer inserted is the definition of a bundle, not an attack on one.
24
+ * Transcribed from `packages/bundle/{base,headless,web-app}/package.json`.
25
+ */
26
+ export declare const HARNESS_BUNDLE_PACKAGES: ReadonlyMap<string, BundleName>;
27
+ /** One row a shipped bundle defines. */
28
+ export interface CoreRow {
29
+ /** The module specifier that implements the row. */
30
+ readonly module: string;
31
+ /** Which shipped bundles insert this row. A profile may mount only some of them. */
32
+ readonly bundles: readonly BundleName[];
33
+ }
34
+ /**
35
+ * Every row the shipped bundles define, mapped from row id to the module that
36
+ * implements it and the bundles that insert it. Transcribed from
37
+ * `packages/bundle/{base,headless,web-app}/cordis.patch.yml`.
38
+ *
39
+ * A patch row whose `id` is a key here is modifying core behavior rather than
40
+ * contributing its own. The name half matters because `applyEntryPatches`
41
+ * treats `name` on a non-insert patch as an assertion guard: on mismatch it
42
+ * warns and skips the whole patch, so a patch naming the wrong module silently
43
+ * does nothing at all. The bundle half matters because the three layers are not
44
+ * one profile: a `ui-*` row exists only where the web bundle is mounted, so a
45
+ * headless profile never had it to lose.
46
+ */
47
+ export declare const CORE_ROWS: ReadonlyMap<string, CoreRow>;
48
+ /** Row ids the shipped bundles define. */
49
+ export declare const CORE_ROW_IDS: ReadonlySet<string>;
50
+ /**
51
+ * The core rows whose whole purpose is to constrain what the agent may do.
52
+ * Disabling or reconfiguring one of these from a third-party patch layer is
53
+ * the highest-value finding this tool produces, and it is plain YAML.
54
+ *
55
+ * Each entry names what stops holding when the row stops running.
56
+ */
57
+ export declare const SECURITY_ROW_IDS: ReadonlyMap<string, string>;
58
+ /**
59
+ * Capability seam keys from
60
+ * `packages/extensions/tool-cordis/src/api-catalog.ts` (`SERVICE_API[].key`).
61
+ * A plugin calling `ctx.provide(key, …)` or `ctx.set(key, …)` on one of these
62
+ * replaces a core service for every consumer in its scope.
63
+ */
64
+ export declare const SEAM_KEYS: ReadonlySet<string>;
65
+ /** The subset of {@link SEAM_KEYS} whose replacement removes a constraint. */
66
+ export declare const SECURITY_SEAM_KEYS: ReadonlySet<string>;
67
+ /**
68
+ * Waterfall events, from `EVENT_API` in the api-catalog. A listener on one of
69
+ * these receives a trailing `next` and MUST call it to delegate; returning
70
+ * without calling it short-circuits the chain including the built-in behavior.
71
+ *
72
+ * Note there is no `fs/read-intent` — the intent family is write and edit only.
73
+ */
74
+ export declare const WATERFALL_EVENTS: ReadonlySet<string>;
75
+ /** Waterfall events whose short-circuit removes a decision the user would otherwise make. */
76
+ export declare const DECISION_EVENTS: ReadonlySet<string>;
77
+ /**
78
+ * Globals the dynamic-package sandbox (`cordis-host-runner/src/sandbox.ts`)
79
+ * traps and redirects to a `ctx` service, plus `process`, which it leaves
80
+ * `undefined`. An installed bundle layer is a plain ESM import and gets none of
81
+ * these restrictions — which is exactly why using one is worth reporting.
82
+ */
83
+ export declare const SANDBOX_DENIED_GLOBALS: ReadonlyMap<string, string>;
84
+ /**
85
+ * Node builtins that start or evaluate code off the mediated path. A mounted
86
+ * layer importing one of these is doing what `ctx.subprocess` and `ctx.sandbox`
87
+ * exist to mediate, from a position where nothing mediates it.
88
+ */
89
+ export declare const UNMEDIATED_PROCESS_MODULES: ReadonlyMap<string, string>;
90
+ /**
91
+ * Modules and globals that move bytes off the machine. The harness's own
92
+ * dynamic-package sandbox traps `fetch` and redirects it to the `ctx.web`
93
+ * service; a mounted layer gets no such redirect.
94
+ */
95
+ export declare const NETWORK_MODULES: ReadonlySet<string>;
96
+ /**
97
+ * Filesystem modules that bypass `ctx.fs`. Reads and writes through these are
98
+ * invisible to `fs/write-intent`, `fs/edit-intent`, `fs/observed`, and the
99
+ * `fs-sandbox` row, so no policy sees them.
100
+ */
101
+ export declare const UNMEDIATED_FS_MODULES: ReadonlySet<string>;
102
+ /** The npm package that turns a Cordis row into an MCP server connection. */
103
+ export declare const MCP_CLIENT_PACKAGE = "@deepseek-ai/dsh-mcp-client";
104
+ /** The row id that owns filesystem skill discovery, and whose config selects the roots. */
105
+ export declare const SKILL_FILESYSTEM_ROW = "skill-filesystem";
106
+ /** `skill-filesystem` config keys that point discovery at a new directory. */
107
+ export declare const SKILL_ROOT_CONFIG_KEYS: readonly string[];
108
+ /**
109
+ * `package.json` script names npm and pnpm run around installation. A plugin
110
+ * only needs one of these to run code before the user has read a line of it.
111
+ */
112
+ export declare const INSTALL_LIFECYCLE_SCRIPTS: readonly string[];
113
+ /** Entry fields the loader never interpolates: a `!!js` node here is inert data. */
114
+ export declare const STATIC_ENTRY_FIELDS: readonly string[];
115
+ /**
116
+ * Calls a `!!js` expression may make that reach nothing the harness does not
117
+ * already hand it.
118
+ *
119
+ * `dsh-app-boot` does `ctx.provide('dshHomePath', dshHomePath)` before mounting
120
+ * any entry (`packages/boot/app-boot/src/index.ts`), and the loader evaluates
121
+ * every expression under `with (ctx)`, so `dshHomePath(...)` is in scope by
122
+ * design and documented as such in that package's README. The two `process`
123
+ * reads are the ones the shipped bundles use.
124
+ */
125
+ export declare const HARNESS_INERT_CALLS: ReadonlySet<string>;
126
+ /**
127
+ * The entry fields that decide which services a row sees, and which of them it
128
+ * substitutes for its subtree.
129
+ *
130
+ * `isolate` is the sharpest: `vendor/loader/src/config/isolate.ts` re-maps a
131
+ * named service to a fresh symbol realm, so every descendant that injects that
132
+ * name gets the row's realm instead of the profile's. Setting it on a security
133
+ * service is a Tier A declaration with the same reach as replacing the service
134
+ * in code, and it is plain YAML.
135
+ */
136
+ export declare const SERVICE_REMAPPING_FIELDS: readonly string[];
137
+ //# sourceMappingURL=knowledge.d.ts.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Reading `package.json` from an untrusted package.
3
+ *
4
+ * This is a file boundary with a hostile author on the other side, so nothing
5
+ * here trusts the parse type. Every field is narrowed before use and a field
6
+ * of the wrong shape is treated as absent rather than throwing — a plugin that
7
+ * ships `"scripts": "postinstall"` should still be analysed for everything
8
+ * else, and "this manifest is malformed" is itself worth reporting.
9
+ * @module dsh-plugin-inspector/manifest
10
+ */
11
+ /** The `dsh.bundle` declaration that promotes a package to a mounted patch layer. */
12
+ export interface DshBundleSection {
13
+ /** Patch file path relative to the package root, verbatim and unresolved. */
14
+ readonly patch?: string;
15
+ }
16
+ /** The `dsh`-owned section of `package.json`, as far as this tool reads it. */
17
+ export interface DshSection {
18
+ readonly bundle?: DshBundleSection;
19
+ readonly profile?: {
20
+ readonly bundles?: readonly string[];
21
+ };
22
+ /** Present means the package ships a bundle executed in the user's browser. */
23
+ readonly client?: Record<string, unknown>;
24
+ }
25
+ /** The slice of `package.json` this tool reads. */
26
+ export interface PackageManifest {
27
+ readonly name: string;
28
+ readonly version: string;
29
+ readonly license: string | null;
30
+ readonly scripts: Readonly<Record<string, string>>;
31
+ readonly dependencies: Readonly<Record<string, string>>;
32
+ readonly peerDependencies: Readonly<Record<string, string>>;
33
+ readonly optionalDependencies: Readonly<Record<string, string>>;
34
+ readonly devDependencies: Readonly<Record<string, string>>;
35
+ /** The publish allowlist, or `null` when the manifest declares none. */
36
+ readonly files: readonly string[] | null;
37
+ /** Command names the package installs on the user's PATH, in declaration order. */
38
+ readonly binNames: readonly string[];
39
+ readonly exportPaths: readonly string[];
40
+ readonly dsh: DshSection;
41
+ /** Problems found while reading the manifest, reported as Tier A findings. */
42
+ readonly defects: readonly string[];
43
+ }
44
+ /** Thrown when `package.json` is not JSON at all. */
45
+ export declare class ManifestError extends Error {
46
+ }
47
+ /**
48
+ * Parse an untrusted `package.json`.
49
+ * @param text - the file's UTF-8 content.
50
+ * @returns the narrowed manifest, including any shape defects found.
51
+ * @throws ManifestError when the text is not a JSON object.
52
+ */
53
+ export declare function parseManifest(text: string): PackageManifest;
54
+ /**
55
+ * Every package name the manifest admits the package may load at runtime:
56
+ * its own name, its dependencies, and its peer dependencies. Used to decide
57
+ * whether an inserted Cordis row names a module the manifest accounts for.
58
+ * @param manifest - the parsed manifest.
59
+ * @returns the declared package names.
60
+ */
61
+ export declare function declaredPackages(manifest: PackageManifest): ReadonlySet<string>;
62
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1,160 @@
1
+ /**
2
+ * The report vocabulary: what a finding is, how findings rank, and what the
3
+ * complete inspection document looks like.
4
+ *
5
+ * The document separates `facts` from `findings` deliberately. Facts carry no
6
+ * severity and answer "what does this plugin do"; findings carry severity and
7
+ * answer "what warrants a decision". A well-behaved plugin has a full facts
8
+ * section and an empty findings section — emitting `dsh.bundle` as a finding
9
+ * would fire on every legitimate plugin and train users to ignore the tool.
10
+ * @module dsh-plugin-inspector/model
11
+ */
12
+ /** How much a finding should weigh on an install decision. */
13
+ export type Severity = 'critical' | 'high' | 'medium' | 'low';
14
+ /**
15
+ * How much the detection itself can be trusted, which is separate from how bad
16
+ * the thing detected is. Tier A reads structured declarations and is always
17
+ * `certain`; Tier B recognises syntax and is downgraded when Tier C fires.
18
+ */
19
+ export type Confidence = 'certain' | 'high' | 'moderate' | 'low';
20
+ /** Which analysis produced a finding. See PLAN.md §6. */
21
+ export type Tier = 'A' | 'B' | 'C';
22
+ /** Severity ordering, ascending. Used for `--fail-on` comparison and ranking. */
23
+ export declare const SEVERITY_RANK: Readonly<Record<Severity, number>>;
24
+ /** Every severity, most severe first — the order the human report prints in. */
25
+ export declare const SEVERITIES: readonly Severity[];
26
+ /** Where in the analysed package a finding was observed. */
27
+ export interface Evidence {
28
+ /** Package-relative path of the file the finding came from. */
29
+ readonly file: string;
30
+ /** A locator inside that file: a YAML path, a JSON pointer, or `line:column`. */
31
+ readonly path?: string;
32
+ /** A short verbatim excerpt, truncated and single-lined for display. */
33
+ readonly snippet?: string;
34
+ }
35
+ /** One thing the inspector believes warrants a decision. */
36
+ export interface Finding {
37
+ /** Catalogue id from PLAN.md §6, e.g. `A2`. Stable across releases. */
38
+ readonly checkId: string;
39
+ /** Machine-readable check name, e.g. `core-row-disabled`. Stable across releases. */
40
+ readonly name: string;
41
+ readonly tier: Tier;
42
+ readonly severity: Severity;
43
+ readonly confidence: Confidence;
44
+ /** One line naming what was found. */
45
+ readonly title: string;
46
+ /** Why it matters, in terms of what the harness does with the declaration. */
47
+ readonly detail: string;
48
+ readonly evidence: Evidence;
49
+ /**
50
+ * The one-line evasion for this specific check, or `null` when there is none.
51
+ * Non-null for every Tier B and Tier C check. Carried inside the finding
52
+ * rather than in a footnote so a consumer cannot render the finding without
53
+ * also holding its caveat.
54
+ */
55
+ readonly bypass: string | null;
56
+ }
57
+ /** A Cordis row this patch layer inserts or modifies. */
58
+ export interface RowFact {
59
+ readonly id: string;
60
+ /** Module specifier for an inserted row; absent when the row only overrides. */
61
+ readonly name?: string;
62
+ }
63
+ /** The "what does this plugin do" half of the report. No severities here. */
64
+ export interface Facts {
65
+ readonly packageName: string;
66
+ readonly packageVersion: string;
67
+ readonly license: string | null;
68
+ /** True when `package.json` declares `dsh.bundle.patch` — a mounted patch layer. */
69
+ readonly mountsAsBundle: boolean;
70
+ /** The declared patch path, verbatim and unresolved, or `null`. */
71
+ readonly bundlePatchPath: string | null;
72
+ /** True when the package declares `dsh.client`, shipping browser-executed code. */
73
+ readonly shipsClientBundle: boolean;
74
+ /** Bundles this package's `dsh.profile.bundles` mounts, when it is a profile. */
75
+ readonly profileBundles: readonly string[];
76
+ /** Command names the package installs on the user's PATH. */
77
+ readonly binNames: readonly string[];
78
+ /** Rows this layer adds to the composed profile. */
79
+ readonly insertedRows: readonly RowFact[];
80
+ /** Ids of pre-existing rows this layer modifies by id. */
81
+ readonly targetedRows: readonly string[];
82
+ /** The mounted layer's `!!js` inventory, counted by what each expression reaches. */
83
+ readonly jsExpressions: Readonly<Record<string, number>>;
84
+ /**
85
+ * Cordis YAML the package ships that no manifest key mounts. Examples,
86
+ * documentation, and test fixtures live here; none of it is a profile layer.
87
+ */
88
+ readonly unmountedPatchFiles: readonly string[];
89
+ readonly dependencies: readonly string[];
90
+ readonly peerDependencies: readonly string[];
91
+ /** Shipped markdown that can reach the model. See PLAN.md §6.1 reach note. */
92
+ readonly modelVisibleFiles: readonly string[];
93
+ readonly filesRead: number;
94
+ readonly bytesRead: number;
95
+ readonly sourceFilesParsed: number;
96
+ /**
97
+ * How the analysed file set was chosen: a tarball is already the published
98
+ * set, a directory is narrowed to what npm would publish out of it.
99
+ */
100
+ readonly publishBasis: 'files-allowlist' | 'ignore-rules' | 'tarball';
101
+ /** Working-tree files npm would not publish, and which were therefore not read. */
102
+ readonly unpublishedFiles: number;
103
+ }
104
+ /** A file the analyzer chose not to or could not read. */
105
+ export interface SkippedFile {
106
+ readonly path: string;
107
+ readonly reason: 'size-cap' | 'total-cap' | 'entry-cap' | 'binary' | 'unreadable';
108
+ }
109
+ /** How much of the package the analyzer could actually see. */
110
+ export interface AnalysisIntegrity {
111
+ readonly integrity: 'complete' | 'degraded';
112
+ /**
113
+ * False when any Tier C check fired. A Tier B *negative* carries no
114
+ * information under those conditions, so the report is forbidden from
115
+ * claiming nothing was found.
116
+ */
117
+ readonly negativesReliable: boolean;
118
+ /** Check ids that caused the degradation, e.g. `['C2']`. */
119
+ readonly degradedBy: readonly string[];
120
+ readonly filesSkipped: readonly SkippedFile[];
121
+ }
122
+ /** The complete inspection result, and the shape of `--json` output. */
123
+ export interface Report {
124
+ readonly schemaVersion: 1;
125
+ readonly tool: {
126
+ readonly name: string;
127
+ readonly version: string;
128
+ /**
129
+ * The harness version the row inventory, seam keys, and event tables were
130
+ * transcribed from. Every Tier A verdict is a claim about what *that*
131
+ * version does with a declaration, so a report is only as current as this.
132
+ */
133
+ readonly harnessReference: string;
134
+ };
135
+ readonly target: {
136
+ readonly kind: 'directory' | 'tarball';
137
+ readonly path: string;
138
+ };
139
+ readonly facts: Facts;
140
+ readonly analysis: AnalysisIntegrity;
141
+ readonly summary: Readonly<Record<Severity, number>>;
142
+ readonly findings: readonly Finding[];
143
+ }
144
+ /**
145
+ * Order findings for display: most severe first, then by tier (A before B
146
+ * before C, since A carries verdicts), then by check id, then by evidence
147
+ * location. Total and deterministic, so two runs diff cleanly.
148
+ * @param a - left finding.
149
+ * @param b - right finding.
150
+ * @returns negative when `a` sorts first.
151
+ */
152
+ export declare function compareFindings(a: Finding, b: Finding): number;
153
+ /**
154
+ * Count findings per severity, including zeroes, so the JSON summary has a
155
+ * fixed key set that consumers can rely on.
156
+ * @param findings - the findings to tally.
157
+ * @returns one count per severity.
158
+ */
159
+ export declare function summarize(findings: readonly Finding[]): Record<Severity, number>;
160
+ //# sourceMappingURL=model.d.ts.map