@descryy/adapter-common 0.2.0 → 0.3.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.
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The guard between "extraction gave up" and "this adapter is broken".
3
+ *
4
+ * Several adapters already refuse to return an empty graph over a non-empty
5
+ * repository: they build a filesystem-level graph — one node per file, nothing
6
+ * claimed about contents — plus one loud row saying the analysis failed rather
7
+ * than the repository being empty. That rule was earned, not designed: two real
8
+ * repositories came back with 0 nodes over ~18,000 files and a successful R0
9
+ * status, because a transport failure and an empty repository were
10
+ * indistinguishable downstream.
11
+ *
12
+ * Every one of those guards covered extraction **returning** nothing, and none
13
+ * covered it **throwing**. The two outcomes differ only in how the extractor
14
+ * gave up, and the thrown one is strictly worse for the reader: an exception
15
+ * out of `emit()` cannot be told apart from the adapter being broken for every
16
+ * repository, so the caller reports the whole language as not analysable. It
17
+ * fires exactly when someone is analysing code they just wrote — the worst
18
+ * possible moment for a tool trying to earn trust.
19
+ *
20
+ * ## Degrading is not swallowing
21
+ *
22
+ * The `degrade` callback an adapter supplies still says loudly that no graph was
23
+ * produced and that findings over the repository are unsupported. The only thing
24
+ * that changes is that the caller receives that sentence instead of a stack
25
+ * trace it has to interpret. A silent `catch` returning an empty extraction
26
+ * would be the exact failure these guards exist to prevent, arriving by a
27
+ * shorter route — so this helper cannot express that: it has no empty-result
28
+ * path, and an adapter with no degraded graph to fall back to must not call it.
29
+ *
30
+ * ## Language-blind, and it has to be
31
+ *
32
+ * Nothing here knows what a node is. `T` is the adapter's own extraction shape,
33
+ * and the disclosure text lives in the adapter's `degrade` callback, where the
34
+ * vocabulary for "module" or "compilation unit" belongs. This file only decides
35
+ * that a throw becomes a disclosure, and formats the cause.
36
+ */
37
+ /**
38
+ * Run `extract`; on a throw, return `degrade(cause)` instead of propagating.
39
+ *
40
+ * `cause` names the thrown value's own type and message, because they are the
41
+ * cheapest real diagnosis available and the reader's alternative is reading the
42
+ * adapter's source. A non-`Error` throw is stringified rather than having
43
+ * `.message` read off it — doing that yields the literal text "undefined",
44
+ * which is a disclosure that names nothing.
45
+ */
46
+ export declare function extractOrDegrade<T>(extract: () => T, degrade: (cause: string) => T): T;
47
+ /** `Name: message` for an `Error`; an explicitly-labelled stringification otherwise. */
48
+ export declare function describeThrown(error: unknown): string;
49
+ //# sourceMappingURL=degrade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"degrade.d.ts","sourceRoot":"","sources":["../src/degrade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAMtF;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The guard between "extraction gave up" and "this adapter is broken".
3
+ *
4
+ * Several adapters already refuse to return an empty graph over a non-empty
5
+ * repository: they build a filesystem-level graph — one node per file, nothing
6
+ * claimed about contents — plus one loud row saying the analysis failed rather
7
+ * than the repository being empty. That rule was earned, not designed: two real
8
+ * repositories came back with 0 nodes over ~18,000 files and a successful R0
9
+ * status, because a transport failure and an empty repository were
10
+ * indistinguishable downstream.
11
+ *
12
+ * Every one of those guards covered extraction **returning** nothing, and none
13
+ * covered it **throwing**. The two outcomes differ only in how the extractor
14
+ * gave up, and the thrown one is strictly worse for the reader: an exception
15
+ * out of `emit()` cannot be told apart from the adapter being broken for every
16
+ * repository, so the caller reports the whole language as not analysable. It
17
+ * fires exactly when someone is analysing code they just wrote — the worst
18
+ * possible moment for a tool trying to earn trust.
19
+ *
20
+ * ## Degrading is not swallowing
21
+ *
22
+ * The `degrade` callback an adapter supplies still says loudly that no graph was
23
+ * produced and that findings over the repository are unsupported. The only thing
24
+ * that changes is that the caller receives that sentence instead of a stack
25
+ * trace it has to interpret. A silent `catch` returning an empty extraction
26
+ * would be the exact failure these guards exist to prevent, arriving by a
27
+ * shorter route — so this helper cannot express that: it has no empty-result
28
+ * path, and an adapter with no degraded graph to fall back to must not call it.
29
+ *
30
+ * ## Language-blind, and it has to be
31
+ *
32
+ * Nothing here knows what a node is. `T` is the adapter's own extraction shape,
33
+ * and the disclosure text lives in the adapter's `degrade` callback, where the
34
+ * vocabulary for "module" or "compilation unit" belongs. This file only decides
35
+ * that a throw becomes a disclosure, and formats the cause.
36
+ */
37
+ /**
38
+ * Run `extract`; on a throw, return `degrade(cause)` instead of propagating.
39
+ *
40
+ * `cause` names the thrown value's own type and message, because they are the
41
+ * cheapest real diagnosis available and the reader's alternative is reading the
42
+ * adapter's source. A non-`Error` throw is stringified rather than having
43
+ * `.message` read off it — doing that yields the literal text "undefined",
44
+ * which is a disclosure that names nothing.
45
+ */
46
+ export function extractOrDegrade(extract, degrade) {
47
+ try {
48
+ return extract();
49
+ }
50
+ catch (error) {
51
+ return degrade(`extraction failed (${describeThrown(error)})`);
52
+ }
53
+ }
54
+ /** `Name: message` for an `Error`; an explicitly-labelled stringification otherwise. */
55
+ export function describeThrown(error) {
56
+ if (error instanceof Error)
57
+ return `${error.name}: ${error.message}`;
58
+ return `a non-Error value was thrown: ${String(error)}`;
59
+ }
60
+ //# sourceMappingURL=degrade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"degrade.js","sourceRoot":"","sources":["../src/degrade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAI,OAAgB,EAAE,OAA6B;IACjF,IAAI,CAAC;QACH,OAAO,OAAO,EAAE,CAAC;IACnB,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC,sBAAsB,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;IACrE,OAAO,iCAAiC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The shared, language-blind `FILE`-node minter.
3
+ *
4
+ * `FILE` is one of the fifteen canonical node types and was already real and
5
+ * populated before this file existed — `packages/core/src/sources/git/
6
+ * source.ts` in `descry-core` mints one per touched file, for `CHANGES_WITH`
7
+ * blast-radius edges, via `nodeId(scope, "FILE", fileQsp(path), null)`. No
8
+ * *adapter* had ever minted one; every adapter's own filesystem-level
9
+ * fallback used `MODULE` instead (see `adapter-python`'s `degradedGraph`).
10
+ *
11
+ * This helper exists so an adapter's degraded-extraction path can assert the
12
+ * one fact that survives a parse failure — the file exists — using the exact
13
+ * id computation the git-history producer already uses. Two `FILE` nodes for
14
+ * the same repo-relative path, one minted here and one minted by git
15
+ * history, hash to the same id and merge into one node rather than
16
+ * duplicate. `producedBy` is deliberately excluded from identity (it is not
17
+ * part of `nodeId`'s hash inputs) for the same reason: two adapters
18
+ * degrading on the same file must also merge, not fork.
19
+ *
20
+ * Filesystem-level only, per the product owner's ruling on
21
+ * `DEC-NEXT-degraded-graph-shape-for-adapters-without-one` (2026-09-15): a
22
+ * fallback "may assert filesystem-level facts... and nothing more." No
23
+ * language is guessed from the extension (DEC-026's reasoning, restated here
24
+ * for `FILE` the way `source.ts` already states it for its own).
25
+ */
26
+ import { type IdentityScope, type IRNode } from "@descryy/ir";
27
+ /**
28
+ * Mint a `FILE` node for one repo-relative path.
29
+ *
30
+ * `producedBy` names the caller for provenance only — it plays no part in
31
+ * the node's identity, so callers must not rely on it to keep two callers'
32
+ * nodes apart.
33
+ */
34
+ export declare function fileNode(scope: IdentityScope, repoRelativePath: string, producedBy: string): IRNode;
35
+ //# sourceMappingURL=file-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-node.d.ts","sourceRoot":"","sources":["../src/file-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAmB,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAE/E;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAenG"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The shared, language-blind `FILE`-node minter.
3
+ *
4
+ * `FILE` is one of the fifteen canonical node types and was already real and
5
+ * populated before this file existed — `packages/core/src/sources/git/
6
+ * source.ts` in `descry-core` mints one per touched file, for `CHANGES_WITH`
7
+ * blast-radius edges, via `nodeId(scope, "FILE", fileQsp(path), null)`. No
8
+ * *adapter* had ever minted one; every adapter's own filesystem-level
9
+ * fallback used `MODULE` instead (see `adapter-python`'s `degradedGraph`).
10
+ *
11
+ * This helper exists so an adapter's degraded-extraction path can assert the
12
+ * one fact that survives a parse failure — the file exists — using the exact
13
+ * id computation the git-history producer already uses. Two `FILE` nodes for
14
+ * the same repo-relative path, one minted here and one minted by git
15
+ * history, hash to the same id and merge into one node rather than
16
+ * duplicate. `producedBy` is deliberately excluded from identity (it is not
17
+ * part of `nodeId`'s hash inputs) for the same reason: two adapters
18
+ * degrading on the same file must also merge, not fork.
19
+ *
20
+ * Filesystem-level only, per the product owner's ruling on
21
+ * `DEC-NEXT-degraded-graph-shape-for-adapters-without-one` (2026-09-15): a
22
+ * fallback "may assert filesystem-level facts... and nothing more." No
23
+ * language is guessed from the extension (DEC-026's reasoning, restated here
24
+ * for `FILE` the way `source.ts` already states it for its own).
25
+ */
26
+ import { fileQsp, nodeId } from "@descryy/ir";
27
+ /**
28
+ * Mint a `FILE` node for one repo-relative path.
29
+ *
30
+ * `producedBy` names the caller for provenance only — it plays no part in
31
+ * the node's identity, so callers must not rely on it to keep two callers'
32
+ * nodes apart.
33
+ */
34
+ export function fileNode(scope, repoRelativePath, producedBy) {
35
+ return {
36
+ id: nodeId(scope, "FILE", fileQsp(repoRelativePath), null),
37
+ type: "FILE",
38
+ name: repoRelativePath.split("/").pop() ?? repoRelativePath,
39
+ file: repoRelativePath,
40
+ range: null,
41
+ // Guessing language from the extension would be a language decision made
42
+ // by a helper with no business making one — the same reasoning
43
+ // `source.ts` states for its own FILE nodes.
44
+ language: null,
45
+ producedBy,
46
+ resolution: 0,
47
+ attrs: { parsed: false },
48
+ };
49
+ }
50
+ //# sourceMappingURL=file-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-node.js","sourceRoot":"","sources":["../src/file-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAmC,MAAM,aAAa,CAAC;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAoB,EAAE,gBAAwB,EAAE,UAAkB;IACzF,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC;QAC1D,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,gBAAgB;QAC3D,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,IAAI;QACX,yEAAyE;QACzE,+DAA+D;QAC/D,6CAA6C;QAC7C,QAAQ,EAAE,IAAI;QACd,UAAU;QACV,UAAU,EAAE,CAAC;QACb,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;KACzB,CAAC;AACJ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -10,4 +10,6 @@ export { discover, globToRegExp, ignoredDirectories, matchesAny, toRepoRelative
10
10
  export type { DiscoverOptions } from "./discover.ts";
11
11
  export { isolateFile } from "./isolate.ts";
12
12
  export type { Isolated, IsolatedFailure } from "./isolate.ts";
13
+ export { extractOrDegrade, describeThrown } from "./degrade.ts";
14
+ export { fileNode } from "./file-node.ts";
13
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACvG,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACvG,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -8,4 +8,6 @@
8
8
  */
9
9
  export { discover, globToRegExp, ignoredDirectories, matchesAny, toRepoRelative } from "./discover.js";
10
10
  export { isolateFile } from "./isolate.js";
11
+ export { extractOrDegrade, describeThrown } from "./degrade.js";
12
+ export { fileNode } from "./file-node.js";
11
13
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEvG,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEvG,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descryy/adapter-common",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "description": "Shared adapter infrastructure: file discovery, glob matching, path normalisation. No language knowledge.",
6
6
  "license": "UNLICENSED",
@@ -32,5 +32,7 @@
32
32
  "scripts": {
33
33
  "build": "tsc -b"
34
34
  },
35
- "dependencies": {}
35
+ "dependencies": {
36
+ "@descryy/ir": "0.10.0"
37
+ }
36
38
  }