@intentset/architecture 0.6.0 → 0.7.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 ADDED
@@ -0,0 +1,48 @@
1
+ # @intentset/architecture
2
+
3
+ The Traceable Vertical Slice Architecture checker (`spec/vsa-0.2.md`) with the TypeScript and AWS Amplify Gen 2
4
+ profile's boundaries: path claims and ownership, the import graph read with TypeScript 7's scanner, regions, layers,
5
+ areas, exceptions and a baseline of known violations. It reads files it is given and runs nothing.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @intentset/architecture
11
+ pnpm add @intentset/architecture
12
+ ```
13
+
14
+ ## Example
15
+
16
+ Most repositories run it through the CLI, `@intentset/cli`, at L2:
17
+
18
+ ```sh
19
+ npx -p @intentset/cli intentset architecture check
20
+ npx -p @intentset/cli intentset architecture check --mode strict # no baseline: every violation fails
21
+ ```
22
+
23
+ With pnpm, `pnpm dlx --package=@intentset/cli intentset …`, or `pnpm exec intentset …` once `@intentset/cli` is
24
+ installed. The command is always `@intentset/cli`'s: this package has no `intentset` binary, and npx asked
25
+ for `intentset` alone would fetch an unrelated unscoped package from the registry.
26
+
27
+ From code, after core's validation:
28
+
29
+ ```ts
30
+ import { checkArchitecture } from "@intentset/architecture";
31
+
32
+ // result from validate(), registries from readRegistries(), files: a Map of repository-relative path -> text
33
+ const check = checkArchitecture(result.graph, registries, { files });
34
+ for (const d of check.diagnostics) console.log(d.path, d.code, d.message);
35
+ ```
36
+
37
+ It needs TypeScript 7 as a peer. On an earlier TypeScript, run the CLI without installing it:
38
+ `npx -p @intentset/cli -p typescript@7 intentset architecture check`.
39
+
40
+ ## Learn more
41
+
42
+ Part of [Intentset](https://intentset.org/), which keeps product intent connected to the code that delivers it and the
43
+ checks that verify it, as readable Markdown files in a repository. The specifications are at
44
+ https://intentset.org/specifications/ and the source at https://github.com/intentset/intentset.
45
+
46
+ ## Licence
47
+
48
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * @intentset/architecture: the Traceable VSA checker (spec/vsa-0.1.md) with
2
+ * @intentset/architecture: the Traceable VSA checker (spec/vsa-0.2.md) with
3
3
  * the TypeScript + Amplify Gen 2 profile's boundaries
4
- * (spec/profile-typescript-amplify-gen2-0.1.md). `checkArchitecture` is the
4
+ * (spec/profile-typescript-amplify-gen2-0.2.md). `checkArchitecture` is the
5
5
  * whole check; the rest is exported for reports, tools and tests.
6
6
  */
7
7
  export { AREA_REVIEW_REQUIRED, areaOf, backendAreaOf, checkAreas, isAreaSchema } from "./areas.ts";
@@ -17,6 +17,7 @@ export type { Finding, SubjectEdge } from "./finding.ts";
17
17
  export { buildImportGraph, type ImportEdge, type ImportGraph, type UnresolvedImport } from "./imports.ts";
18
18
  export { inSeam, isPure, layerAllows, layerOf, purePatterns, seamPatterns } from "./layers.ts";
19
19
  export { checkOwnership } from "./ownership.ts";
20
+ export { type ArchitectureSubjectSource, type SubjectSourceOptions, subjectSource } from "./subject-source.ts";
20
21
  export { expandClaims, matchAny, matchPattern, validatePattern } from "./patterns.ts";
21
22
  export { activeSlices, buildModel, type Model, type Region, type RegionKind, regionLabel, type SliceInfo, } from "./regions.ts";
22
23
  export { type Resolution, Resolver, type Via } from "./resolve.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EACL,aAAa,EACb,KAAK,aAAa,EAClB,WAAW,EACX,KAAK,IAAI,EACT,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,2BAA2B,EAC3B,SAAS,EACT,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EACL,QAAQ,EACR,eAAe,EACf,MAAM,EACN,mBAAmB,EACnB,KAAK,eAAe,EACpB,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,UAAU,EACf,cAAc,EACd,aAAa,EACb,SAAS,EACT,KAAK,SAAS,EACd,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,UAAU,EACV,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,UAAU,EACf,WAAW,EACX,KAAK,SAAS,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,UAAU,EAAE,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EACL,aAAa,EACb,KAAK,aAAa,EAClB,WAAW,EACX,KAAK,IAAI,EACT,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,2BAA2B,EAC3B,SAAS,EACT,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EACL,QAAQ,EACR,eAAe,EACf,MAAM,EACN,mBAAmB,EACnB,KAAK,eAAe,EACpB,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,UAAU,EACf,cAAc,EACd,aAAa,EACb,SAAS,EACT,KAAK,SAAS,EACd,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,KAAK,yBAAyB,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,UAAU,EACV,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,UAAU,EACf,WAAW,EACX,KAAK,SAAS,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,UAAU,EAAE,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * @intentset/architecture: the Traceable VSA checker (spec/vsa-0.1.md) with
2
+ * @intentset/architecture: the Traceable VSA checker (spec/vsa-0.2.md) with
3
3
  * the TypeScript + Amplify Gen 2 profile's boundaries
4
- * (spec/profile-typescript-amplify-gen2-0.1.md). `checkArchitecture` is the
4
+ * (spec/profile-typescript-amplify-gen2-0.2.md). `checkArchitecture` is the
5
5
  * whole check; the rest is exported for reports, tools and tests.
6
6
  */
7
7
  export { AREA_REVIEW_REQUIRED, areaOf, backendAreaOf, checkAreas, isAreaSchema } from "./areas.js";
@@ -16,6 +16,7 @@ export { extractImports, extractSchema, findCalls, SCHEMA_KINDS, } from "./extra
16
16
  export { buildImportGraph } from "./imports.js";
17
17
  export { inSeam, isPure, layerAllows, layerOf, purePatterns, seamPatterns } from "./layers.js";
18
18
  export { checkOwnership } from "./ownership.js";
19
+ export { subjectSource } from "./subject-source.js";
19
20
  export { expandClaims, matchAny, matchPattern, validatePattern } from "./patterns.js";
20
21
  export { activeSlices, buildModel, regionLabel, } from "./regions.js";
21
22
  export { Resolver } from "./resolve.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EACL,aAAa,EAEb,WAAW,EAEX,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAuB,eAAe,EAAE,iBAAiB,EAAkB,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAIL,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,wBAAwB,EAGxB,2BAA2B,EAC3B,SAAS,EACT,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EACL,QAAQ,EACR,eAAe,EACf,MAAM,EACN,mBAAmB,EAEnB,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,cAAc,EACd,aAAa,EACb,SAAS,EAET,YAAY,GAKb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,gBAAgB,EAA4D,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,UAAU,EAIV,WAAW,GAEZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAmB,QAAQ,EAAY,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAiB,SAAS,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EACL,aAAa,EAEb,WAAW,EAEX,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAuB,eAAe,EAAE,iBAAiB,EAAkB,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EAIL,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,wBAAwB,EAGxB,2BAA2B,EAC3B,SAAS,EACT,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EACL,QAAQ,EACR,eAAe,EACf,MAAM,EACN,mBAAmB,EAEnB,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,cAAc,EACd,aAAa,EACb,SAAS,EAET,YAAY,GAKb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,gBAAgB,EAA4D,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAA6D,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,UAAU,EAIV,WAAW,GAEZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAmB,QAAQ,EAAY,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAiB,SAAS,EAAE,MAAM,eAAe,CAAC"}
@@ -6,7 +6,7 @@
6
6
  * only way to write a star that spans segments is `**` mixed with other
7
7
  * characters, which validatePattern rejects.
8
8
  */
9
- /** Why a pattern is not a v0.1 pattern, or null when it is one. */
9
+ /** Why a pattern is not a v0.2 pattern, or null when it is one. */
10
10
  export declare function validatePattern(pattern: string): string | null;
11
11
  /** Does a repository-relative path match a VSA §3 pattern? Invalid patterns match nothing. */
12
12
  export declare function matchPattern(pattern: string, path: string): boolean;
package/dist/patterns.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * only way to write a star that spans segments is `**` mixed with other
7
7
  * characters, which validatePattern rejects.
8
8
  */
9
- /** Why a pattern is not a v0.1 pattern, or null when it is one. */
9
+ /** Why a pattern is not a v0.2 pattern, or null when it is one. */
10
10
  export function validatePattern(pattern) {
11
11
  if (pattern === "")
12
12
  return "is empty";
@@ -0,0 +1,35 @@
1
+ /**
2
+ * What the evidence package's subject hash needs from the architecture
3
+ * (Core §8, ADR 0013): the files each slice's claims resolve to, the files of
4
+ * each registry resource, the local files a test imports (resolved with the
5
+ * same TypeScript-aware resolver as the import graph, and stopping at a
6
+ * package boundary), the global inputs expanded to paths, and which files can
7
+ * move any subject at all. Pure: it reads the tree it is given and the file
8
+ * contents through the function it is given.
9
+ */
10
+ import type { Graph, Registries } from "@intentset/core";
11
+ import { type ArchitectureConfig } from "./config.ts";
12
+ export interface SubjectSourceOptions {
13
+ /** Laid over the defaults and `.intentset/architecture.yaml`, as for `checkArchitecture`. */
14
+ config?: Partial<ArchitectureConfig>;
15
+ /** `evidence.globalInputs`: paths, or patterns expanded over the tree. */
16
+ globalInputs: readonly string[];
17
+ /** The bytes of a repository-relative file, or undefined when it does not exist. */
18
+ readFile: (path: string) => Uint8Array | undefined;
19
+ }
20
+ /** Shaped as the evidence package's `SubjectSource`, which this package does not import. */
21
+ export interface ArchitectureSubjectSource {
22
+ readFile: (path: string) => Uint8Array | undefined;
23
+ sliceFiles: Map<string, string[]>;
24
+ resources: Map<string, {
25
+ record: unknown;
26
+ files: string[];
27
+ }>;
28
+ localImports: (path: string) => string[];
29
+ globalInputs: string[];
30
+ covers: (path: string) => boolean;
31
+ }
32
+ export declare function subjectSource(graph: Graph, registries: Registries, tree: {
33
+ files: ReadonlyMap<string, string>;
34
+ }, options: SubjectSourceOptions): ArchitectureSubjectSource;
35
+ //# sourceMappingURL=subject-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject-source.d.ts","sourceRoot":"","sources":["../src/subject-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAA4B,KAAK,kBAAkB,EAAyC,MAAM,aAAa,CAAC;AAOvH,MAAM,WAAW,oBAAoB;IACnC,6FAA6F;IAC7F,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrC,0EAA0E;IAC1E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,oFAAoF;IACpF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;CACpD;AAED,4FAA4F;AAC5F,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IACnD,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC7D,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IACzC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AAaD,wBAAgB,aAAa,CAC3B,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE;IAAE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,EAC5C,OAAO,EAAE,oBAAoB,GAC5B,yBAAyB,CAuE3B"}
@@ -0,0 +1,100 @@
1
+ import { ARCHITECTURE_CONFIG_PATH, readArchitectureConfig, resolveConfig } from "./config.js";
2
+ import { extractImports } from "./extract.js";
3
+ import { isCode } from "./paths.js";
4
+ import { compareStrings, matchPattern } from "./patterns.js";
5
+ import { buildModel } from "./regions.js";
6
+ import { Resolver } from "./resolve.js";
7
+ /** A global input with no `*` is a path, present or not (a missing one moves the subject when it appears); the rest are patterns. */
8
+ function expandGlobalInputs(inputs, paths) {
9
+ const all = [...paths];
10
+ const out = new Set();
11
+ for (const input of inputs) {
12
+ if (!input.includes("*"))
13
+ out.add(input);
14
+ else
15
+ for (const path of all)
16
+ if (matchPattern(input, path))
17
+ out.add(path);
18
+ }
19
+ return [...out].sort(compareStrings);
20
+ }
21
+ export function subjectSource(graph, registries, tree, options) {
22
+ const configText = tree.files.get(ARCHITECTURE_CONFIG_PATH);
23
+ const fileConfig = configText === undefined ? undefined : readArchitectureConfig(configText, ARCHITECTURE_CONFIG_PATH).config;
24
+ const config = resolveConfig(fileConfig, options.config);
25
+ const model = buildModel(graph, registries, tree.files, config);
26
+ const sliceFiles = new Map();
27
+ for (const slice of model.slices) {
28
+ sliceFiles.set(slice.id, [...new Set(slice.claimFiles.flat())].sort(compareStrings));
29
+ }
30
+ const resources = new Map();
31
+ for (const info of model.resources)
32
+ resources.set(info.resource.id, { record: info.resource, files: [...info.files] });
33
+ let resolver = null;
34
+ const closures = new Map();
35
+ const importsOf = (path) => {
36
+ resolver ??= new Resolver(model.files, config);
37
+ const text = model.files.get(path);
38
+ if (text === undefined || !isCode(path))
39
+ return [];
40
+ const targets = [];
41
+ for (const raw of extractImports(path, text).imports) {
42
+ const resolution = resolver.resolve(path, raw.specifier);
43
+ if (resolution.problem === undefined && resolution.to !== null)
44
+ targets.push(resolution.to);
45
+ }
46
+ return targets;
47
+ };
48
+ const localImports = (start) => {
49
+ const cached = closures.get(start);
50
+ if (cached !== undefined)
51
+ return cached;
52
+ const boundary = model.packageOf(start);
53
+ const seen = new Set([start]);
54
+ const queue = [start];
55
+ while (queue.length > 0) {
56
+ const file = queue.pop();
57
+ for (const target of importsOf(file)) {
58
+ if (seen.has(target) || model.packageOf(target) !== boundary)
59
+ continue;
60
+ seen.add(target);
61
+ queue.push(target);
62
+ }
63
+ }
64
+ seen.delete(start);
65
+ const list = [...seen].sort(compareStrings);
66
+ closures.set(start, list);
67
+ return list;
68
+ };
69
+ const globalInputs = expandGlobalInputs(options.globalInputs, tree.files.keys());
70
+ let covered = null;
71
+ const covers = (path) => {
72
+ if (covered === null) {
73
+ covered = new Set(globalInputs);
74
+ for (const files of sliceFiles.values())
75
+ for (const file of files)
76
+ covered.add(file);
77
+ const used = new Set();
78
+ for (const slice of model.slices)
79
+ for (const id of slice.meta.usesResources)
80
+ used.add(id);
81
+ for (const artifact of graph.artifacts.values()) {
82
+ covered.add(artifact.path);
83
+ const verification = artifact.meta.verification;
84
+ if (artifact.meta.type !== "verification" || verification === undefined)
85
+ continue;
86
+ covered.add(verification.locator);
87
+ for (const file of localImports(verification.locator))
88
+ covered.add(file);
89
+ for (const id of verification.exercises ?? [])
90
+ used.add(id);
91
+ }
92
+ for (const id of used)
93
+ for (const file of resources.get(id)?.files ?? [])
94
+ covered.add(file);
95
+ }
96
+ return covered.has(path);
97
+ };
98
+ return { readFile: options.readFile, sliceFiles, resources, localImports, globalInputs, covers };
99
+ }
100
+ //# sourceMappingURL=subject-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject-source.js","sourceRoot":"","sources":["../src/subject-source.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@intentset/architecture",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Traceable VSA checks: path claims, import graph, layer matrix, resources, exceptions, baseline.",
5
+ "keywords": [
6
+ "intentset",
7
+ "architecture",
8
+ "vertical-slice",
9
+ "import-graph",
10
+ "typescript",
11
+ "amplify"
12
+ ],
5
13
  "license": "MIT",
6
14
  "type": "module",
7
15
  "engines": {
@@ -28,7 +36,7 @@
28
36
  }
29
37
  },
30
38
  "dependencies": {
31
- "@intentset/core": "^0.6.0"
39
+ "@intentset/core": "^0.7.0"
32
40
  },
33
41
  "peerDependencies": {
34
42
  "typescript": "^7.0.2"
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * @intentset/architecture: the Traceable VSA checker (spec/vsa-0.1.md) with
2
+ * @intentset/architecture: the Traceable VSA checker (spec/vsa-0.2.md) with
3
3
  * the TypeScript + Amplify Gen 2 profile's boundaries
4
- * (spec/profile-typescript-amplify-gen2-0.1.md). `checkArchitecture` is the
4
+ * (spec/profile-typescript-amplify-gen2-0.2.md). `checkArchitecture` is the
5
5
  * whole check; the rest is exported for reports, tools and tests.
6
6
  */
7
7
  export { AREA_REVIEW_REQUIRED, areaOf, backendAreaOf, checkAreas, isAreaSchema } from "./areas.ts";
@@ -59,6 +59,7 @@ export type { Finding, SubjectEdge } from "./finding.ts";
59
59
  export { buildImportGraph, type ImportEdge, type ImportGraph, type UnresolvedImport } from "./imports.ts";
60
60
  export { inSeam, isPure, layerAllows, layerOf, purePatterns, seamPatterns } from "./layers.ts";
61
61
  export { checkOwnership } from "./ownership.ts";
62
+ export { type ArchitectureSubjectSource, type SubjectSourceOptions, subjectSource } from "./subject-source.ts";
62
63
  export { expandClaims, matchAny, matchPattern, validatePattern } from "./patterns.ts";
63
64
  export {
64
65
  activeSlices,
package/src/patterns.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * characters, which validatePattern rejects.
8
8
  */
9
9
 
10
- /** Why a pattern is not a v0.1 pattern, or null when it is one. */
10
+ /** Why a pattern is not a v0.2 pattern, or null when it is one. */
11
11
  export function validatePattern(pattern: string): string | null {
12
12
  if (pattern === "") return "is empty";
13
13
  if (pattern.startsWith("/")) return "is absolute";
@@ -0,0 +1,124 @@
1
+ /**
2
+ * What the evidence package's subject hash needs from the architecture
3
+ * (Core §8, ADR 0013): the files each slice's claims resolve to, the files of
4
+ * each registry resource, the local files a test imports (resolved with the
5
+ * same TypeScript-aware resolver as the import graph, and stopping at a
6
+ * package boundary), the global inputs expanded to paths, and which files can
7
+ * move any subject at all. Pure: it reads the tree it is given and the file
8
+ * contents through the function it is given.
9
+ */
10
+ import type { Graph, Registries } from "@intentset/core";
11
+ import { ARCHITECTURE_CONFIG_PATH, type ArchitectureConfig, readArchitectureConfig, resolveConfig } from "./config.ts";
12
+ import { extractImports } from "./extract.ts";
13
+ import { isCode } from "./paths.ts";
14
+ import { compareStrings, matchPattern } from "./patterns.ts";
15
+ import { buildModel } from "./regions.ts";
16
+ import { Resolver } from "./resolve.ts";
17
+
18
+ export interface SubjectSourceOptions {
19
+ /** Laid over the defaults and `.intentset/architecture.yaml`, as for `checkArchitecture`. */
20
+ config?: Partial<ArchitectureConfig>;
21
+ /** `evidence.globalInputs`: paths, or patterns expanded over the tree. */
22
+ globalInputs: readonly string[];
23
+ /** The bytes of a repository-relative file, or undefined when it does not exist. */
24
+ readFile: (path: string) => Uint8Array | undefined;
25
+ }
26
+
27
+ /** Shaped as the evidence package's `SubjectSource`, which this package does not import. */
28
+ export interface ArchitectureSubjectSource {
29
+ readFile: (path: string) => Uint8Array | undefined;
30
+ sliceFiles: Map<string, string[]>;
31
+ resources: Map<string, { record: unknown; files: string[] }>;
32
+ localImports: (path: string) => string[];
33
+ globalInputs: string[];
34
+ covers: (path: string) => boolean;
35
+ }
36
+
37
+ /** A global input with no `*` is a path, present or not (a missing one moves the subject when it appears); the rest are patterns. */
38
+ function expandGlobalInputs(inputs: readonly string[], paths: Iterable<string>): string[] {
39
+ const all = [...paths];
40
+ const out = new Set<string>();
41
+ for (const input of inputs) {
42
+ if (!input.includes("*")) out.add(input);
43
+ else for (const path of all) if (matchPattern(input, path)) out.add(path);
44
+ }
45
+ return [...out].sort(compareStrings);
46
+ }
47
+
48
+ export function subjectSource(
49
+ graph: Graph,
50
+ registries: Registries,
51
+ tree: { files: ReadonlyMap<string, string> },
52
+ options: SubjectSourceOptions,
53
+ ): ArchitectureSubjectSource {
54
+ const configText = tree.files.get(ARCHITECTURE_CONFIG_PATH);
55
+ const fileConfig =
56
+ configText === undefined ? undefined : readArchitectureConfig(configText, ARCHITECTURE_CONFIG_PATH).config;
57
+ const config = resolveConfig(fileConfig, options.config);
58
+ const model = buildModel(graph, registries, tree.files, config);
59
+
60
+ const sliceFiles = new Map<string, string[]>();
61
+ for (const slice of model.slices) {
62
+ sliceFiles.set(slice.id, [...new Set(slice.claimFiles.flat())].sort(compareStrings));
63
+ }
64
+ const resources = new Map<string, { record: unknown; files: string[] }>();
65
+ for (const info of model.resources)
66
+ resources.set(info.resource.id, { record: info.resource, files: [...info.files] });
67
+
68
+ let resolver: Resolver | null = null;
69
+ const closures = new Map<string, string[]>();
70
+ const importsOf = (path: string): string[] => {
71
+ resolver ??= new Resolver(model.files, config);
72
+ const text = model.files.get(path);
73
+ if (text === undefined || !isCode(path)) return [];
74
+ const targets: string[] = [];
75
+ for (const raw of extractImports(path, text).imports) {
76
+ const resolution = resolver.resolve(path, raw.specifier);
77
+ if (resolution.problem === undefined && resolution.to !== null) targets.push(resolution.to);
78
+ }
79
+ return targets;
80
+ };
81
+ const localImports = (start: string): string[] => {
82
+ const cached = closures.get(start);
83
+ if (cached !== undefined) return cached;
84
+ const boundary = model.packageOf(start);
85
+ const seen = new Set<string>([start]);
86
+ const queue = [start];
87
+ while (queue.length > 0) {
88
+ const file = queue.pop() as string;
89
+ for (const target of importsOf(file)) {
90
+ if (seen.has(target) || model.packageOf(target) !== boundary) continue;
91
+ seen.add(target);
92
+ queue.push(target);
93
+ }
94
+ }
95
+ seen.delete(start);
96
+ const list = [...seen].sort(compareStrings);
97
+ closures.set(start, list);
98
+ return list;
99
+ };
100
+
101
+ const globalInputs = expandGlobalInputs(options.globalInputs, tree.files.keys());
102
+
103
+ let covered: Set<string> | null = null;
104
+ const covers = (path: string): boolean => {
105
+ if (covered === null) {
106
+ covered = new Set<string>(globalInputs);
107
+ for (const files of sliceFiles.values()) for (const file of files) covered.add(file);
108
+ const used = new Set<string>();
109
+ for (const slice of model.slices) for (const id of slice.meta.usesResources) used.add(id);
110
+ for (const artifact of graph.artifacts.values()) {
111
+ covered.add(artifact.path);
112
+ const verification = artifact.meta.verification;
113
+ if (artifact.meta.type !== "verification" || verification === undefined) continue;
114
+ covered.add(verification.locator);
115
+ for (const file of localImports(verification.locator)) covered.add(file);
116
+ for (const id of verification.exercises ?? []) used.add(id);
117
+ }
118
+ for (const id of used) for (const file of resources.get(id)?.files ?? []) covered.add(file);
119
+ }
120
+ return covered.has(path);
121
+ };
122
+
123
+ return { readFile: options.readFile, sliceFiles, resources, localImports, globalInputs, covers };
124
+ }