@spikedpunch/align-core 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.
Files changed (203) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +27 -0
  3. package/dist/baseline/fingerprint.d.ts +19 -0
  4. package/dist/baseline/fingerprint.d.ts.map +1 -0
  5. package/dist/baseline/fingerprint.js +0 -0
  6. package/dist/baseline/fingerprint.js.map +1 -0
  7. package/dist/baseline/store.d.ts +81 -0
  8. package/dist/baseline/store.d.ts.map +1 -0
  9. package/dist/baseline/store.js +111 -0
  10. package/dist/baseline/store.js.map +1 -0
  11. package/dist/build/diff.d.ts +22 -0
  12. package/dist/build/diff.d.ts.map +1 -0
  13. package/dist/build/diff.js +61 -0
  14. package/dist/build/diff.js.map +1 -0
  15. package/dist/build/export-ir.d.ts +11 -0
  16. package/dist/build/export-ir.d.ts.map +1 -0
  17. package/dist/build/export-ir.js +4 -0
  18. package/dist/build/export-ir.js.map +1 -0
  19. package/dist/build/ground.d.ts +32 -0
  20. package/dist/build/ground.d.ts.map +1 -0
  21. package/dist/build/ground.js +162 -0
  22. package/dist/build/ground.js.map +1 -0
  23. package/dist/build/hash.d.ts +10 -0
  24. package/dist/build/hash.d.ts.map +1 -0
  25. package/dist/build/hash.js +13 -0
  26. package/dist/build/hash.js.map +1 -0
  27. package/dist/build/impact.d.ts +20 -0
  28. package/dist/build/impact.d.ts.map +1 -0
  29. package/dist/build/impact.js +15 -0
  30. package/dist/build/impact.js.map +1 -0
  31. package/dist/build/index.d.ts +14 -0
  32. package/dist/build/index.d.ts.map +1 -0
  33. package/dist/build/index.js +14 -0
  34. package/dist/build/index.js.map +1 -0
  35. package/dist/build/merge.d.ts +27 -0
  36. package/dist/build/merge.d.ts.map +1 -0
  37. package/dist/build/merge.js +85 -0
  38. package/dist/build/merge.js.map +1 -0
  39. package/dist/build/propose.d.ts +16 -0
  40. package/dist/build/propose.d.ts.map +1 -0
  41. package/dist/build/propose.js +126 -0
  42. package/dist/build/propose.js.map +1 -0
  43. package/dist/build/provenance.d.ts +19 -0
  44. package/dist/build/provenance.d.ts.map +1 -0
  45. package/dist/build/provenance.js +23 -0
  46. package/dist/build/provenance.js.map +1 -0
  47. package/dist/build/schema.d.ts +1825 -0
  48. package/dist/build/schema.d.ts.map +1 -0
  49. package/dist/build/schema.js +128 -0
  50. package/dist/build/schema.js.map +1 -0
  51. package/dist/build/sections.d.ts +30 -0
  52. package/dist/build/sections.d.ts.map +1 -0
  53. package/dist/build/sections.js +58 -0
  54. package/dist/build/sections.js.map +1 -0
  55. package/dist/build/tier1.d.ts +24 -0
  56. package/dist/build/tier1.d.ts.map +1 -0
  57. package/dist/build/tier1.js +68 -0
  58. package/dist/build/tier1.js.map +1 -0
  59. package/dist/build/tier2.d.ts +42 -0
  60. package/dist/build/tier2.d.ts.map +1 -0
  61. package/dist/build/tier2.js +187 -0
  62. package/dist/build/tier2.js.map +1 -0
  63. package/dist/build/types.d.ts +42 -0
  64. package/dist/build/types.d.ts.map +1 -0
  65. package/dist/build/types.js +2 -0
  66. package/dist/build/types.js.map +1 -0
  67. package/dist/components/glob.d.ts +10 -0
  68. package/dist/components/glob.d.ts.map +1 -0
  69. package/dist/components/glob.js +52 -0
  70. package/dist/components/glob.js.map +1 -0
  71. package/dist/components/registry.d.ts +51 -0
  72. package/dist/components/registry.d.ts.map +1 -0
  73. package/dist/components/registry.js +121 -0
  74. package/dist/components/registry.js.map +1 -0
  75. package/dist/dsl/factories.d.ts +86 -0
  76. package/dist/dsl/factories.d.ts.map +1 -0
  77. package/dist/dsl/factories.js +145 -0
  78. package/dist/dsl/factories.js.map +1 -0
  79. package/dist/dsl/index.d.ts +46 -0
  80. package/dist/dsl/index.d.ts.map +1 -0
  81. package/dist/dsl/index.js +67 -0
  82. package/dist/dsl/index.js.map +1 -0
  83. package/dist/dsl/reserved-names.typecheck.d.ts +2 -0
  84. package/dist/dsl/reserved-names.typecheck.d.ts.map +1 -0
  85. package/dist/dsl/reserved-names.typecheck.js +16 -0
  86. package/dist/dsl/reserved-names.typecheck.js.map +1 -0
  87. package/dist/dsl/verb-manifest.d.ts +12 -0
  88. package/dist/dsl/verb-manifest.d.ts.map +1 -0
  89. package/dist/dsl/verb-manifest.js +101 -0
  90. package/dist/dsl/verb-manifest.js.map +1 -0
  91. package/dist/fix/apply.d.ts +67 -0
  92. package/dist/fix/apply.d.ts.map +1 -0
  93. package/dist/fix/apply.js +163 -0
  94. package/dist/fix/apply.js.map +1 -0
  95. package/dist/fix/index.d.ts +3 -0
  96. package/dist/fix/index.d.ts.map +1 -0
  97. package/dist/fix/index.js +3 -0
  98. package/dist/fix/index.js.map +1 -0
  99. package/dist/fix/schema.d.ts +180 -0
  100. package/dist/fix/schema.d.ts.map +1 -0
  101. package/dist/fix/schema.js +41 -0
  102. package/dist/fix/schema.js.map +1 -0
  103. package/dist/gates/advisories.d.ts +11 -0
  104. package/dist/gates/advisories.d.ts.map +1 -0
  105. package/dist/gates/advisories.js +27 -0
  106. package/dist/gates/advisories.js.map +1 -0
  107. package/dist/gates/types.d.ts +36 -0
  108. package/dist/gates/types.d.ts.map +1 -0
  109. package/dist/gates/types.js +8 -0
  110. package/dist/gates/types.js.map +1 -0
  111. package/dist/index.d.ts +17 -0
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.js +17 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/orchestrator.d.ts +51 -0
  116. package/dist/orchestrator.d.ts.map +1 -0
  117. package/dist/orchestrator.js +304 -0
  118. package/dist/orchestrator.js.map +1 -0
  119. package/dist/payload/builder.d.ts +49 -0
  120. package/dist/payload/builder.d.ts.map +1 -0
  121. package/dist/payload/builder.js +61 -0
  122. package/dist/payload/builder.js.map +1 -0
  123. package/dist/payload/mermaid.d.ts +22 -0
  124. package/dist/payload/mermaid.d.ts.map +1 -0
  125. package/dist/payload/mermaid.js +89 -0
  126. package/dist/payload/mermaid.js.map +1 -0
  127. package/dist/plugin/registry.d.ts +24 -0
  128. package/dist/plugin/registry.d.ts.map +1 -0
  129. package/dist/plugin/registry.js +24 -0
  130. package/dist/plugin/registry.js.map +1 -0
  131. package/dist/rules/component-refs.d.ts +26 -0
  132. package/dist/rules/component-refs.d.ts.map +1 -0
  133. package/dist/rules/component-refs.js +81 -0
  134. package/dist/rules/component-refs.js.map +1 -0
  135. package/dist/rules/evaluators.d.ts +50 -0
  136. package/dist/rules/evaluators.d.ts.map +1 -0
  137. package/dist/rules/evaluators.js +236 -0
  138. package/dist/rules/evaluators.js.map +1 -0
  139. package/dist/rules/host-rules.d.ts +109 -0
  140. package/dist/rules/host-rules.d.ts.map +1 -0
  141. package/dist/rules/host-rules.js +152 -0
  142. package/dist/rules/host-rules.js.map +1 -0
  143. package/dist/rules/index.d.ts +7 -0
  144. package/dist/rules/index.d.ts.map +1 -0
  145. package/dist/rules/index.js +7 -0
  146. package/dist/rules/index.js.map +1 -0
  147. package/dist/rules/manifest-evaluators.d.ts +32 -0
  148. package/dist/rules/manifest-evaluators.d.ts.map +1 -0
  149. package/dist/rules/manifest-evaluators.js +124 -0
  150. package/dist/rules/manifest-evaluators.js.map +1 -0
  151. package/dist/rules/rule-category.d.ts +13 -0
  152. package/dist/rules/rule-category.d.ts.map +1 -0
  153. package/dist/rules/rule-category.js +27 -0
  154. package/dist/rules/rule-category.js.map +1 -0
  155. package/dist/rules/tarjan.d.ts +9 -0
  156. package/dist/rules/tarjan.d.ts.map +1 -0
  157. package/dist/rules/tarjan.js +102 -0
  158. package/dist/rules/tarjan.js.map +1 -0
  159. package/dist/scanner.d.ts +12 -0
  160. package/dist/scanner.d.ts.map +1 -0
  161. package/dist/scanner.js +2 -0
  162. package/dist/scanner.js.map +1 -0
  163. package/dist/telemetry/diff.d.ts +27 -0
  164. package/dist/telemetry/diff.d.ts.map +1 -0
  165. package/dist/telemetry/diff.js +54 -0
  166. package/dist/telemetry/diff.js.map +1 -0
  167. package/dist/telemetry/index.d.ts +4 -0
  168. package/dist/telemetry/index.d.ts.map +1 -0
  169. package/dist/telemetry/index.js +4 -0
  170. package/dist/telemetry/index.js.map +1 -0
  171. package/dist/telemetry/serialize.d.ts +10 -0
  172. package/dist/telemetry/serialize.d.ts.map +1 -0
  173. package/dist/telemetry/serialize.js +11 -0
  174. package/dist/telemetry/serialize.js.map +1 -0
  175. package/dist/telemetry/types.d.ts +138 -0
  176. package/dist/telemetry/types.d.ts.map +1 -0
  177. package/dist/telemetry/types.js +3 -0
  178. package/dist/telemetry/types.js.map +1 -0
  179. package/dist/types/branded.d.ts +26 -0
  180. package/dist/types/branded.d.ts.map +1 -0
  181. package/dist/types/branded.js +24 -0
  182. package/dist/types/branded.js.map +1 -0
  183. package/dist/types/graph.d.ts +46 -0
  184. package/dist/types/graph.d.ts.map +1 -0
  185. package/dist/types/graph.js +2 -0
  186. package/dist/types/graph.js.map +1 -0
  187. package/dist/types/index.d.ts +6 -0
  188. package/dist/types/index.d.ts.map +1 -0
  189. package/dist/types/index.js +6 -0
  190. package/dist/types/index.js.map +1 -0
  191. package/dist/types/ir.d.ts +1747 -0
  192. package/dist/types/ir.d.ts.map +1 -0
  193. package/dist/types/ir.js +120 -0
  194. package/dist/types/ir.js.map +1 -0
  195. package/dist/types/manifest.d.ts +58 -0
  196. package/dist/types/manifest.d.ts.map +1 -0
  197. package/dist/types/manifest.js +2 -0
  198. package/dist/types/manifest.js.map +1 -0
  199. package/dist/types/violation.d.ts +94 -0
  200. package/dist/types/violation.d.ts.map +1 -0
  201. package/dist/types/violation.js +56 -0
  202. package/dist/types/violation.js.map +1 -0
  203. package/package.json +58 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/build/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD;6FAC6F;AAC7F,MAAM,MAAM,aAAa,GACrB,uBAAuB,GACvB,iBAAiB,GACjB,kBAAkB,GAClB,qBAAqB,GACrB,wBAAwB,CAAC;AAE7B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAEpE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;wBAEwB;AACxB,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACzD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/build/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Minimal dependency-free glob matcher for component file selectors (ADR 003). align/core's
3
+ * only runtime dependency is zod, so this is a small hand-rolled glob-to-regex compiler rather
4
+ * than pulling in micromatch/minimatch — it only needs to support the pattern vocabulary
5
+ * component selectors actually use: `*` (one path segment), `**` (zero or more segments), `?`
6
+ * (one character), and literal path segments.
7
+ */
8
+ export declare function globToRegExp(pattern: string): RegExp;
9
+ export declare function globMatch(pattern: string, filePath: string): boolean;
10
+ //# sourceMappingURL=glob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/components/glob.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiCpD;AAID,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOpE"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Minimal dependency-free glob matcher for component file selectors (ADR 003). align/core's
3
+ * only runtime dependency is zod, so this is a small hand-rolled glob-to-regex compiler rather
4
+ * than pulling in micromatch/minimatch — it only needs to support the pattern vocabulary
5
+ * component selectors actually use: `*` (one path segment), `**` (zero or more segments), `?`
6
+ * (one character), and literal path segments.
7
+ */
8
+ export function globToRegExp(pattern) {
9
+ const normalized = pattern.split('\\').join('/');
10
+ let out = '';
11
+ let i = 0;
12
+ while (i < normalized.length) {
13
+ const c = normalized[i];
14
+ if (c === '*') {
15
+ if (normalized[i + 1] === '*') {
16
+ // `**` — consume any following `/` so `a/**/b` and `a/**` both behave sanely.
17
+ let j = i + 2;
18
+ if (normalized[j] === '/')
19
+ j += 1;
20
+ out += '.*';
21
+ i = j;
22
+ continue;
23
+ }
24
+ out += '[^/]*';
25
+ i += 1;
26
+ continue;
27
+ }
28
+ if (c === '?') {
29
+ out += '[^/]';
30
+ i += 1;
31
+ continue;
32
+ }
33
+ if (c !== undefined && '.+^${}()|[]\\'.includes(c)) {
34
+ out += `\\${c}`;
35
+ i += 1;
36
+ continue;
37
+ }
38
+ out += c;
39
+ i += 1;
40
+ }
41
+ return new RegExp(`^${out}$`);
42
+ }
43
+ const regexCache = new Map();
44
+ export function globMatch(pattern, filePath) {
45
+ let re = regexCache.get(pattern);
46
+ if (re === undefined) {
47
+ re = globToRegExp(pattern);
48
+ regexCache.set(pattern, re);
49
+ }
50
+ return re.test(filePath);
51
+ }
52
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["../../src/components/glob.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC9B,8EAA8E;gBAC9E,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACd,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,GAAG;oBAAE,CAAC,IAAI,CAAC,CAAC;gBAClC,GAAG,IAAI,IAAI,CAAC;gBACZ,CAAC,GAAG,CAAC,CAAC;gBACN,SAAS;YACX,CAAC;YACD,GAAG,IAAI,OAAO,CAAC;YACf,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,GAAG,IAAI,MAAM,CAAC;YACd,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,SAAS,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC;YAChB,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,GAAG,IAAI,CAAC,CAAC;QACT,CAAC,IAAI,CAAC,CAAC;IACT,CAAC;IACD,OAAO,IAAI,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE7C,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,QAAgB;IACzD,IAAI,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACrB,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QAC3B,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { ComponentName, RepoRelativePath } from '../types/branded.js';
2
+ import type { ComponentDefinitionIR, EmptyPolicy } from '../types/ir.js';
3
+ /** Package name -> repo-relative directory (with trailing slash), e.g. from pnpm-workspace.yaml. */
4
+ export type WorkspacePackageIndex = ReadonlyMap<string, RepoRelativePath>;
5
+ export declare class ComponentValidationError extends Error {
6
+ readonly componentName: ComponentName;
7
+ constructor(message: string, componentName: ComponentName);
8
+ }
9
+ /** First-component-match-wins classification, in the order components are declared. */
10
+ export declare function classifyFile(file: RepoRelativePath, components: Readonly<Record<ComponentName, ComponentDefinitionIR>>, workspacePackages: WorkspacePackageIndex): ComponentName | undefined;
11
+ /**
12
+ * Load-time validation (ADR 003): a `package:` selector naming a package absent from the
13
+ * resolved workspace inventory is an error, and a component whose selector resolves to zero
14
+ * files is a load-time error pointing at the component definition — unless its empty policy
15
+ * (`empty: 'allow' | 'until-populated'`, greenfield mode, ADR 003 amendment) opts out. Both
16
+ * checks run against the current scan's file list, since v1 has no separate config-build step;
17
+ * the closest analog to "load time" is "the first scan after config load."
18
+ */
19
+ export declare function validateComponents(components: Readonly<Record<ComponentName, ComponentDefinitionIR>>, allFiles: readonly RepoRelativePath[], workspacePackages: WorkspacePackageIndex): void;
20
+ /** R1 (greenfield mode, IMPLEMENTATION_PLAN.md Design Reserve "Greenfield mode"): every component
21
+ * that is green ONLY because it matched zero files this scan (`empty: 'allow'` or
22
+ * `'until-populated'`, and currently zero classified files) — the data `align explain`/`doctor`
23
+ * already had, now surfaced in the loop a check-agent actually runs (`align check`'s human output,
24
+ * `--json`, and the MCP `align_check` payload; `orchestrator.ts` calls this once per check and
25
+ * threads the result onto `CheckRun.ungroundedComponents`). A component with `empty: 'fail'` never
26
+ * reaches here empty (it would have thrown in `validateComponents`/`validateClassifiedComponents`
27
+ * first) — this function only ever returns entries for the two policies that tolerate emptiness,
28
+ * closing the exact visibility hole this file's `validateClassifiedComponents` doc comment names:
29
+ * "the same false-green class as an unknown ComponentRef," now distinguishable in the verdict
30
+ * itself rather than requiring a per-rule `align explain` to notice `exampleFiles: []`.
31
+ */
32
+ export declare function findUngroundedComponents(components: Readonly<Record<ComponentName, ComponentDefinitionIR>>, classifiedComponents: ReadonlySet<string>): UngroundedComponent[];
33
+ export interface UngroundedComponent {
34
+ readonly name: ComponentName;
35
+ readonly selector: string;
36
+ readonly policy: Exclude<EmptyPolicy, 'fail'>;
37
+ }
38
+ /**
39
+ * Classification-based companion to `validateComponents`, for callers that only have the
40
+ * *classified* scan result rather than the raw file list + workspace inventory (the
41
+ * `GateOrchestrator` — see its check-time validation step). Enforces the same ADR 003
42
+ * empty-selector-fails-by-default doctrine one step later in the pipeline, where it also catches
43
+ * a case selector-based validation structurally cannot: a component whose selector DOES match
44
+ * files but loses every one of them to an earlier component under first-match-wins
45
+ * classification (`classifyFile` above) — zero classified files means every rule referencing the
46
+ * component evaluates vacuously green, the same false-green class as an unknown ComponentRef
47
+ * (`rules/component-refs.ts`). Fail-fast on the first offender, same convention as
48
+ * `validateComponents`.
49
+ */
50
+ export declare function validateClassifiedComponents(components: Readonly<Record<ComponentName, ComponentDefinitionIR>>, classifiedComponents: ReadonlySet<string>): void;
51
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/components/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGzE,oGAAoG;AACpG,MAAM,MAAM,qBAAqB,GAAG,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAE1E,qBAAa,wBAAyB,SAAQ,KAAK;aAG/B,aAAa,EAAE,aAAa;gBAD5C,OAAO,EAAE,MAAM,EACC,aAAa,EAAE,aAAa;CAK/C;AAiBD,uFAAuF;AACvF,wBAAgB,YAAY,CAC1B,IAAI,EAAE,gBAAgB,EACtB,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC,EAClE,iBAAiB,EAAE,qBAAqB,GACvC,aAAa,GAAG,SAAS,CAM3B;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC,EAClE,QAAQ,EAAE,SAAS,gBAAgB,EAAE,EACrC,iBAAiB,EAAE,qBAAqB,GACvC,IAAI,CAgCN;AAQD;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC,EAClE,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,GACxC,mBAAmB,EAAE,CASvB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;CAC/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC,EAClE,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,GACxC,IAAI,CAgBN"}
@@ -0,0 +1,121 @@
1
+ import { globMatch } from './glob.js';
2
+ export class ComponentValidationError extends Error {
3
+ componentName;
4
+ constructor(message, componentName) {
5
+ super(message);
6
+ this.componentName = componentName;
7
+ this.name = 'ComponentValidationError';
8
+ }
9
+ }
10
+ function matchesSelector(file, def, workspacePackages) {
11
+ if (def.selector.kind === 'glob') {
12
+ return def.selector.patterns.some((pattern) => globMatch(pattern, file));
13
+ }
14
+ // 'package' selector: file belongs if it falls under any named package's directory.
15
+ return def.selector.packageNames.some((name) => {
16
+ const dir = workspacePackages.get(name);
17
+ return dir !== undefined && (file === dir.replace(/\/$/, '') || file.startsWith(dir));
18
+ });
19
+ }
20
+ /** First-component-match-wins classification, in the order components are declared. */
21
+ export function classifyFile(file, components, workspacePackages) {
22
+ for (const name of Object.keys(components)) {
23
+ const def = components[name];
24
+ if (def !== undefined && matchesSelector(file, def, workspacePackages))
25
+ return name;
26
+ }
27
+ return undefined;
28
+ }
29
+ /**
30
+ * Load-time validation (ADR 003): a `package:` selector naming a package absent from the
31
+ * resolved workspace inventory is an error, and a component whose selector resolves to zero
32
+ * files is a load-time error pointing at the component definition — unless its empty policy
33
+ * (`empty: 'allow' | 'until-populated'`, greenfield mode, ADR 003 amendment) opts out. Both
34
+ * checks run against the current scan's file list, since v1 has no separate config-build step;
35
+ * the closest analog to "load time" is "the first scan after config load."
36
+ */
37
+ export function validateComponents(components, allFiles, workspacePackages) {
38
+ for (const name of Object.keys(components)) {
39
+ const def = components[name];
40
+ if (def === undefined)
41
+ continue;
42
+ if (def.selector.kind === 'package') {
43
+ for (const pkgName of def.selector.packageNames) {
44
+ if (!workspacePackages.has(pkgName)) {
45
+ throw new ComponentValidationError(`Component '${name}' references package '${pkgName}', which is not in the resolved ` +
46
+ `workspace inventory. Check pnpm-workspace.yaml / package.json names.`, name);
47
+ }
48
+ }
49
+ }
50
+ if (def.empty !== 'fail')
51
+ continue;
52
+ const matched = allFiles.some((file) => matchesSelector(file, def, workspacePackages));
53
+ if (!matched) {
54
+ throw new ComponentValidationError(`Component '${name}' (selector: ${describeSelector(def)}) matches zero files. Likely ` +
55
+ `cause: its directory was renamed/moved or the selector is stale. If this is expected, ` +
56
+ `set empty: 'until-populated' on the component definition (greenfield — auto-arms once ` +
57
+ `files land, and shows as a distinct 'ungrounded, provisionally green' line in \`align ` +
58
+ `check\` rather than plain green) or empty: 'allow' for a component that stays ` +
59
+ `permanently optional. (\`allowEmpty: true\` is still supported as a deprecated alias ` +
60
+ `for \`empty: 'allow'\`.)`, name);
61
+ }
62
+ }
63
+ }
64
+ function describeSelector(def) {
65
+ return def.selector.kind === 'glob'
66
+ ? def.selector.patterns.join(', ')
67
+ : `package: ${def.selector.packageNames.join(', ')}`;
68
+ }
69
+ /** R1 (greenfield mode, IMPLEMENTATION_PLAN.md Design Reserve "Greenfield mode"): every component
70
+ * that is green ONLY because it matched zero files this scan (`empty: 'allow'` or
71
+ * `'until-populated'`, and currently zero classified files) — the data `align explain`/`doctor`
72
+ * already had, now surfaced in the loop a check-agent actually runs (`align check`'s human output,
73
+ * `--json`, and the MCP `align_check` payload; `orchestrator.ts` calls this once per check and
74
+ * threads the result onto `CheckRun.ungroundedComponents`). A component with `empty: 'fail'` never
75
+ * reaches here empty (it would have thrown in `validateComponents`/`validateClassifiedComponents`
76
+ * first) — this function only ever returns entries for the two policies that tolerate emptiness,
77
+ * closing the exact visibility hole this file's `validateClassifiedComponents` doc comment names:
78
+ * "the same false-green class as an unknown ComponentRef," now distinguishable in the verdict
79
+ * itself rather than requiring a per-rule `align explain` to notice `exampleFiles: []`.
80
+ */
81
+ export function findUngroundedComponents(components, classifiedComponents) {
82
+ const out = [];
83
+ for (const name of Object.keys(components)) {
84
+ const def = components[name];
85
+ if (def === undefined || def.empty === 'fail')
86
+ continue;
87
+ if (classifiedComponents.has(name))
88
+ continue;
89
+ out.push({ name, selector: describeSelector(def), policy: def.empty });
90
+ }
91
+ return out;
92
+ }
93
+ /**
94
+ * Classification-based companion to `validateComponents`, for callers that only have the
95
+ * *classified* scan result rather than the raw file list + workspace inventory (the
96
+ * `GateOrchestrator` — see its check-time validation step). Enforces the same ADR 003
97
+ * empty-selector-fails-by-default doctrine one step later in the pipeline, where it also catches
98
+ * a case selector-based validation structurally cannot: a component whose selector DOES match
99
+ * files but loses every one of them to an earlier component under first-match-wins
100
+ * classification (`classifyFile` above) — zero classified files means every rule referencing the
101
+ * component evaluates vacuously green, the same false-green class as an unknown ComponentRef
102
+ * (`rules/component-refs.ts`). Fail-fast on the first offender, same convention as
103
+ * `validateComponents`.
104
+ */
105
+ export function validateClassifiedComponents(components, classifiedComponents) {
106
+ for (const name of Object.keys(components)) {
107
+ const def = components[name];
108
+ if (def === undefined || def.empty !== 'fail')
109
+ continue;
110
+ if (classifiedComponents.has(name))
111
+ continue;
112
+ throw new ComponentValidationError(`Component '${name}' (selector: ${describeSelector(def)}) has zero files classified to it ` +
113
+ `in this scan — every rule referencing '${name}' would silently pass. Likely cause: its ` +
114
+ `directory was renamed/moved, the selector is stale, or an earlier component's selector ` +
115
+ `claims all of its files (components classify first-match-wins, in declaration order). ` +
116
+ `If '${name}' is legitimately empty, set empty: 'until-populated' (greenfield — auto-arms ` +
117
+ `once files land) or empty: 'allow' (permanent) on the component definition. ` +
118
+ `(\`allowEmpty: true\` is still supported as a deprecated alias for \`empty: 'allow'\`.)`, name);
119
+ }
120
+ }
121
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/components/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * DSL builder factories (ADR 002/013) — split out of `dsl/index.ts` so this module can be the
3
+ * single source of truth for the verb surface: `dsl/index.ts`'s `defineProject` imports these
4
+ * factory constructors to build `ComponentContext`, and `dsl/verb-manifest.ts` imports the same
5
+ * constructors to runtime-introspect the verb table `align skill` renders (Stage 5,
6
+ * IMPLEMENTATION_PLAN.md). Neither consumer hand-duplicates the verb list — both read this file.
7
+ */
8
+ import { type ComponentName } from '../types/branded.js';
9
+ import type { RuleIR, RuleProvenance } from '../types/ir.js';
10
+ export interface ComponentToken {
11
+ readonly name: ComponentName;
12
+ }
13
+ export interface RuleBuilder {
14
+ /** Hoists into `provenance.because` (ADR 002) — the single field feeding terminal output, IDE
15
+ * hover, and future fix-prompt explanations. */
16
+ because(text: string): RuleBuilder;
17
+ /** Internal: finalizes into one or more concrete RuleIR nodes. Some verbs (`isIsolated()`,
18
+ * `cannotDependOn()`) expand into multiple no-dependency rules. `id` is pre-filled with a
19
+ * semantic, human-readable slug (e.g. `arch.no-dependency:api->ui`); `defineProject` only
20
+ * appends a numeric suffix if two rules would otherwise collide, so ids stay stable across
21
+ * config edits that don't touch the colliding rules themselves. */
22
+ build(): readonly RuleIR[];
23
+ }
24
+ export declare function ruleBuilder(makeBase: (provenance: RuleProvenance) => readonly RuleIR[]): RuleBuilder;
25
+ export interface LayerRuleBuilder {
26
+ /** dependencies outside this allowlist are violations */
27
+ canOnlyDependOn(...refs: readonly ComponentToken[]): RuleBuilder;
28
+ /** dependencies on this denylist are violations; everything else is permitted */
29
+ cannotDependOn(...refs: readonly ComponentToken[]): RuleBuilder;
30
+ }
31
+ export interface ComponentRuleBuilder {
32
+ /** no other component may depend on this one, and it depends on none */
33
+ isIsolated(): RuleBuilder;
34
+ /** every file classified to this component must stay at or under `max` lines — `arch.metric`
35
+ * (max-LOC), promoted 2026-07-12 on kluster ruleset evidence (IMPLEMENTATION_PLAN.md's Promotion
36
+ * log: two 2,100+-line files were structurally invisible to every dependency/cycle rule). Only
37
+ * the `loc` metric is promoted — `fan-in`/`fan-out`/`instability` verbs stay reserved pending
38
+ * their own evidence. */
39
+ maxLinesPerFile(max: number): RuleBuilder;
40
+ }
41
+ export interface NoCyclesOptions {
42
+ readonly includeTypeOnly?: boolean;
43
+ }
44
+ export interface ArchFactory {
45
+ layer(token: ComponentToken): LayerRuleBuilder;
46
+ component(token: ComponentToken): ComponentRuleBuilder;
47
+ /** Not in ADR 002's illustrative vocabulary table, but `arch.no-cycles` is a v1 IR rule kind
48
+ * with no other documented authoring path — added following the same negation-free, positive
49
+ * verb convention (deviation noted in the Stage 1 final report). */
50
+ noCycles(scope?: ComponentToken | 'repo', options?: NoCyclesOptions): RuleBuilder;
51
+ }
52
+ export interface CustomFactory {
53
+ /** Produces a `custom.host` IR rule (ADR 002's escape hatch, docs/proposals/rule-expansion-
54
+ * evaluation.md §B.0) referencing a predicate registered by the same name in align.config.ts's
55
+ * sibling `hostRules` export (`{ [hostRuleName]: HostPredicate }`, `@spikedpunch/align-core`'s
56
+ * `HostPredicateRegistry`) — not passed through `defineProject` itself, since `RulesetIR` is
57
+ * portable JSON (ADR 002) and predicates are functions. `defineProject` only builds the
58
+ * reference (`hostRuleName`, `portable: false`); `validateHostRules` (the orchestrator's guard
59
+ * step) is what confirms the name actually resolves to a registered predicate at check time —
60
+ * the same "reference must resolve or the gate errors" doctrine every other selector in this
61
+ * file already follows (ADR 008 amendment). */
62
+ host(hostRuleName: string): RuleBuilder;
63
+ }
64
+ export type ComponentContext<T extends Record<string, unknown>> = {
65
+ readonly [K in keyof T]: ComponentToken;
66
+ } & {
67
+ readonly arch: ArchFactory;
68
+ readonly custom: CustomFactory;
69
+ readonly security: SecurityFactory;
70
+ };
71
+ export declare function makeCustomFactory(): CustomFactory;
72
+ export interface SecurityManifestFactory {
73
+ /** Any dependency specifier resolving to a git/http(s)/file/link source (not registry, not
74
+ * `workspace:`) is a violation — `security.manifest.source-hygiene`. */
75
+ sourceHygiene(): RuleBuilder;
76
+ /** Every current runtime/dev dependency is fingerprinted (name + declaring manifest); baseline
77
+ * consent (`align init` / `baseline accept`) seeds what's there today, so only a genuinely new
78
+ * dependency added later shows red — `security.manifest.new-dependency`. */
79
+ newDependencyGate(): RuleBuilder;
80
+ }
81
+ export interface SecurityFactory {
82
+ readonly manifest: SecurityManifestFactory;
83
+ }
84
+ export declare function makeSecurityFactory(): SecurityFactory;
85
+ export declare function makeArchFactory(allComponents: readonly ComponentToken[]): ArchFactory;
86
+ //# sourceMappingURL=factories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factories.d.ts","sourceRoot":"","sources":["../../src/dsl/factories.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,KAAK,EAMV,MAAM,EACN,cAAc,EAGf,MAAM,gBAAgB,CAAC;AAMxB,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B;oDACgD;IAChD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC;;;;uEAImE;IACnE,KAAK,IAAI,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,cAAc,KAAK,SAAS,MAAM,EAAE,GAAG,WAAW,CAYpG;AAED,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,eAAe,CAAC,GAAG,IAAI,EAAE,SAAS,cAAc,EAAE,GAAG,WAAW,CAAC;IACjE,iFAAiF;IACjF,cAAc,CAAC,GAAG,IAAI,EAAE,SAAS,cAAc,EAAE,GAAG,WAAW,CAAC;CACjE;AAED,MAAM,WAAW,oBAAoB;IACnC,wEAAwE;IACxE,UAAU,IAAI,WAAW,CAAC;IAC1B;;;;6BAIyB;IACzB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;CAC3C;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB,CAAC;IAC/C,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,CAAC;IACvD;;wEAEoE;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,WAAW,CAAC;CACnF;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;;mDAQ+C;IAC/C,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,CAAC;CACzC;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IAChE,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,cAAc;CACxC,GAAG;IACF,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC,CAAC;AAEF,wBAAgB,iBAAiB,IAAI,aAAa,CAejD;AAYD,MAAM,WAAW,uBAAuB;IACtC;4EACwE;IACxE,aAAa,IAAI,WAAW,CAAC;IAC7B;;gFAE4E;IAC5E,iBAAiB,IAAI,WAAW,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;CAC5C;AAED,wBAAgB,mBAAmB,IAAI,eAAe,CAyBrD;AAED,wBAAgB,eAAe,CAAC,aAAa,EAAE,SAAS,cAAc,EAAE,GAAG,WAAW,CAoFrF"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * DSL builder factories (ADR 002/013) — split out of `dsl/index.ts` so this module can be the
3
+ * single source of truth for the verb surface: `dsl/index.ts`'s `defineProject` imports these
4
+ * factory constructors to build `ComponentContext`, and `dsl/verb-manifest.ts` imports the same
5
+ * constructors to runtime-introspect the verb table `align skill` renders (Stage 5,
6
+ * IMPLEMENTATION_PLAN.md). Neither consumer hand-duplicates the verb list — both read this file.
7
+ */
8
+ import { toRuleId } from '../types/branded.js';
9
+ export function ruleBuilder(makeBase) {
10
+ let provenance = {};
11
+ const self = {
12
+ because(text) {
13
+ provenance = { ...provenance, because: text };
14
+ return self;
15
+ },
16
+ build() {
17
+ return makeBase(provenance);
18
+ },
19
+ };
20
+ return self;
21
+ }
22
+ export function makeCustomFactory() {
23
+ return {
24
+ host(hostRuleName) {
25
+ return ruleBuilder((provenance) => {
26
+ const rule = {
27
+ kind: 'custom.host',
28
+ id: toRuleId(`custom.host:${hostRuleName}`),
29
+ hostRuleName,
30
+ portable: false,
31
+ provenance,
32
+ };
33
+ return [rule];
34
+ });
35
+ },
36
+ };
37
+ }
38
+ export function makeSecurityFactory() {
39
+ return {
40
+ manifest: {
41
+ sourceHygiene() {
42
+ return ruleBuilder((provenance) => {
43
+ const rule = {
44
+ kind: 'security.manifest.source-hygiene',
45
+ id: toRuleId('security.manifest.source-hygiene'),
46
+ provenance,
47
+ };
48
+ return [rule];
49
+ });
50
+ },
51
+ newDependencyGate() {
52
+ return ruleBuilder((provenance) => {
53
+ const rule = {
54
+ kind: 'security.manifest.new-dependency',
55
+ id: toRuleId('security.manifest.new-dependency'),
56
+ provenance,
57
+ };
58
+ return [rule];
59
+ });
60
+ },
61
+ },
62
+ };
63
+ }
64
+ export function makeArchFactory(allComponents) {
65
+ return {
66
+ layer(token) {
67
+ return {
68
+ canOnlyDependOn(...refs) {
69
+ return ruleBuilder((provenance) => {
70
+ const rule = {
71
+ kind: 'arch.layers',
72
+ id: toRuleId(`arch.layers:${token.name}`),
73
+ layers: [{ layer: token.name, canDependOn: refs.map((r) => r.name) }],
74
+ provenance,
75
+ };
76
+ return [rule];
77
+ });
78
+ },
79
+ cannotDependOn(...refs) {
80
+ return ruleBuilder((provenance) => refs.map((ref) => ({
81
+ kind: 'arch.no-dependency',
82
+ id: toRuleId(`arch.no-dependency:${token.name}->${ref.name}`),
83
+ from: token.name,
84
+ to: ref.name,
85
+ provenance,
86
+ })));
87
+ },
88
+ };
89
+ },
90
+ component(token) {
91
+ return {
92
+ isIsolated() {
93
+ return ruleBuilder((provenance) => {
94
+ const others = allComponents.filter((c) => c.name !== token.name);
95
+ const rules = [];
96
+ for (const other of others) {
97
+ rules.push({
98
+ kind: 'arch.no-dependency',
99
+ id: toRuleId(`arch.no-dependency:${other.name}->${token.name}`),
100
+ from: other.name,
101
+ to: token.name,
102
+ provenance,
103
+ });
104
+ rules.push({
105
+ kind: 'arch.no-dependency',
106
+ id: toRuleId(`arch.no-dependency:${token.name}->${other.name}`),
107
+ from: token.name,
108
+ to: other.name,
109
+ provenance,
110
+ });
111
+ }
112
+ return rules;
113
+ });
114
+ },
115
+ maxLinesPerFile(max) {
116
+ return ruleBuilder((provenance) => {
117
+ const rule = {
118
+ kind: 'arch.metric',
119
+ id: toRuleId(`arch.metric:loc:${token.name}`),
120
+ target: token.name,
121
+ metric: 'loc',
122
+ max,
123
+ provenance,
124
+ };
125
+ return [rule];
126
+ });
127
+ },
128
+ };
129
+ },
130
+ noCycles(scope, options) {
131
+ return ruleBuilder((provenance) => {
132
+ const scopeValue = scope === undefined || scope === 'repo' ? 'repo' : scope.name;
133
+ const rule = {
134
+ kind: 'arch.no-cycles',
135
+ id: toRuleId(`arch.no-cycles:${scopeValue}`),
136
+ scope: scopeValue,
137
+ includeTypeOnly: options?.includeTypeOnly ?? false,
138
+ provenance,
139
+ };
140
+ return [rule];
141
+ });
142
+ },
143
+ };
144
+ }
145
+ //# sourceMappingURL=factories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factories.js","sourceRoot":"","sources":["../../src/dsl/factories.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { EmptyPolicy, RulesetIR } from '../types/ir.js';
2
+ import { type ComponentContext as ComponentContextBase, type RuleBuilder } from './factories.js';
3
+ export * from './factories.js';
4
+ export * from './verb-manifest.js';
5
+ export type ComponentDeclaration = string | {
6
+ readonly pattern: string;
7
+ /**
8
+ * @deprecated Use `empty: 'allow'` instead — kept for back-compat (align's own dogfood
9
+ * config history and external adopters, e.g. kluster's `sdd` component, already authored
10
+ * `allowEmpty: true`; it keeps working, unchanged, as an alias). `allowEmpty: true` resolves
11
+ * to `empty: 'allow'` when `empty` itself is not also set; `empty` wins if both are present.
12
+ */
13
+ readonly allowEmpty?: boolean;
14
+ /**
15
+ * Greenfield mode (IMPLEMENTATION_PLAN.md Design Reserve "Greenfield mode", ADR 003
16
+ * amendment) — the empty-selector policy, as a 3-state discriminant:
17
+ * - `'fail'` (default): a component matching zero files is a load-time error (unchanged
18
+ * ADR 003 empty-selector-fails-by-default safety).
19
+ * - `'allow'`: empty tolerated permanently — same as `allowEmpty: true`, now additionally
20
+ * surfaced as an `ungrounded-component` advisory in `align check` (R1) instead of an
21
+ * indistinguishable plain `green`.
22
+ * - `'until-populated'`: empty tolerated + surfaced the same way, but self-heals — once the
23
+ * component has >=1 classified file, the empty-check simply stops firing and its rules
24
+ * evaluate normally (no manual flag flip). Use this for architecture-first authoring:
25
+ * components declared before any code exists.
26
+ */
27
+ readonly empty?: EmptyPolicy;
28
+ };
29
+ export type ComponentsInput = Record<string, ComponentDeclaration>;
30
+ type ReservedFactoryName = 'arch' | 'metrics' | 'gates' | 'security' | 'custom';
31
+ /** Forces a type error at the call site of `defineProject` when a component key shadows a
32
+ * reserved factory name — the colliding key's required value type collapses to `never`. */
33
+ type NoReservedComponentKeys<T> = {
34
+ readonly [K in Extract<keyof T, ReservedFactoryName>]?: never;
35
+ };
36
+ export type ComponentContext<T extends ComponentsInput> = ComponentContextBase<T>;
37
+ export interface DefineProjectConfig<T extends ComponentsInput> {
38
+ readonly components: T & NoReservedComponentKeys<T>;
39
+ readonly rules?: (c: ComponentContext<T>) => readonly RuleBuilder[];
40
+ }
41
+ /**
42
+ * `rules` is optional — `defineProject({ components })` alone is valid; zero-DSL day-one value
43
+ * is unaffected by whether an architecture ruleset exists yet (ADR 002/009).
44
+ */
45
+ export declare function defineProject<T extends ComponentsInput>(config: DefineProjectConfig<T>): RulesetIR;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dsl/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAyB,WAAW,EAAwB,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE1G,OAAO,EAIL,KAAK,gBAAgB,IAAI,oBAAoB,EAE7C,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAC;AAExB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AAMnC,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN;IACE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;CAC9B,CAAC;AAEN,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;AAwBnE,KAAK,mBAAmB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEhF;2FAC2F;AAC3F,KAAK,uBAAuB,CAAC,CAAC,IAAI;IAChC,QAAQ,EAAE,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,KAAK;CAC9D,CAAC;AASF,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,eAAe,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC;AAMlF,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,eAAe;IAC5D,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,SAAS,WAAW,EAAE,CAAC;CACrE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,eAAe,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,SAAS,CA2BlG"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Fluent authoring surface -> IR serializer (ADR 002). Exported as the `@spikedpunch/align-core/dsl` subpath
3
+ * — folded into `@spikedpunch/align-core` for v1 (ARCHITECTURE.md §5: single consumer, extraction to a
4
+ * standalone `@spikedpunch/align-dsl` package is cheap and deferred until a second consumer needs it).
5
+ */
6
+ import { toComponentName, toRuleId } from '../types/branded.js';
7
+ import { rulesetIRSchema } from '../types/ir.js';
8
+ import { makeArchFactory, makeCustomFactory, makeSecurityFactory, } from './factories.js';
9
+ export * from './factories.js';
10
+ export * from './verb-manifest.js';
11
+ function parseSelector(pattern) {
12
+ if (pattern.startsWith('package:')) {
13
+ return { kind: 'package', packageNames: [pattern.slice('package:'.length)] };
14
+ }
15
+ return { kind: 'glob', patterns: [pattern] };
16
+ }
17
+ function resolveComponentDefinition(name, decl) {
18
+ if (typeof decl === 'string') {
19
+ return { name, selector: parseSelector(decl), empty: 'fail' };
20
+ }
21
+ // `empty` wins if both are authored; `allowEmpty: true` alone resolves to `empty: 'allow'`
22
+ // (deprecated-alias back-compat — see the `ComponentDeclaration` doc comment above).
23
+ const empty = decl.empty ?? (decl.allowEmpty === true ? 'allow' : 'fail');
24
+ return { name, selector: parseSelector(decl.pattern), empty };
25
+ }
26
+ /**
27
+ * `rules` is optional — `defineProject({ components })` alone is valid; zero-DSL day-one value
28
+ * is unaffected by whether an architecture ruleset exists yet (ADR 002/009).
29
+ */
30
+ export function defineProject(config) {
31
+ const componentEntries = Object.entries(config.components);
32
+ const components = {};
33
+ const tokens = {};
34
+ for (const [name, decl] of componentEntries) {
35
+ components[name] = resolveComponentDefinition(name, decl);
36
+ tokens[name] = { name: toComponentName(name) };
37
+ }
38
+ const context = {
39
+ ...tokens,
40
+ arch: makeArchFactory(Object.values(tokens)),
41
+ custom: makeCustomFactory(),
42
+ security: makeSecurityFactory(),
43
+ };
44
+ const builders = config.rules?.(context) ?? [];
45
+ const usedIds = new Set();
46
+ const rules = [];
47
+ for (const builder of builders) {
48
+ for (const rule of builder.build()) {
49
+ rules.push({ ...rule, id: dedupeRuleId(rule.id, usedIds) });
50
+ }
51
+ }
52
+ return rulesetIRSchema.parse({ irVersion: '1', components, rules });
53
+ }
54
+ /** Semantic ids (e.g. `arch.no-dependency:api->ui`) are stable across unrelated config edits;
55
+ * a numeric suffix is appended only on an actual collision (e.g. two `.cannotDependOn()` calls
56
+ * naming the same pair). */
57
+ function dedupeRuleId(base, usedIds) {
58
+ let candidate = base;
59
+ let suffix = 2;
60
+ while (usedIds.has(candidate)) {
61
+ candidate = `${base}-${suffix}`;
62
+ suffix += 1;
63
+ }
64
+ usedIds.add(candidate);
65
+ return toRuleId(candidate);
66
+ }
67
+ //# sourceMappingURL=index.js.map