@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.
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/baseline/fingerprint.d.ts +19 -0
- package/dist/baseline/fingerprint.d.ts.map +1 -0
- package/dist/baseline/fingerprint.js +0 -0
- package/dist/baseline/fingerprint.js.map +1 -0
- package/dist/baseline/store.d.ts +81 -0
- package/dist/baseline/store.d.ts.map +1 -0
- package/dist/baseline/store.js +111 -0
- package/dist/baseline/store.js.map +1 -0
- package/dist/build/diff.d.ts +22 -0
- package/dist/build/diff.d.ts.map +1 -0
- package/dist/build/diff.js +61 -0
- package/dist/build/diff.js.map +1 -0
- package/dist/build/export-ir.d.ts +11 -0
- package/dist/build/export-ir.d.ts.map +1 -0
- package/dist/build/export-ir.js +4 -0
- package/dist/build/export-ir.js.map +1 -0
- package/dist/build/ground.d.ts +32 -0
- package/dist/build/ground.d.ts.map +1 -0
- package/dist/build/ground.js +162 -0
- package/dist/build/ground.js.map +1 -0
- package/dist/build/hash.d.ts +10 -0
- package/dist/build/hash.d.ts.map +1 -0
- package/dist/build/hash.js +13 -0
- package/dist/build/hash.js.map +1 -0
- package/dist/build/impact.d.ts +20 -0
- package/dist/build/impact.d.ts.map +1 -0
- package/dist/build/impact.js +15 -0
- package/dist/build/impact.js.map +1 -0
- package/dist/build/index.d.ts +14 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +14 -0
- package/dist/build/index.js.map +1 -0
- package/dist/build/merge.d.ts +27 -0
- package/dist/build/merge.d.ts.map +1 -0
- package/dist/build/merge.js +85 -0
- package/dist/build/merge.js.map +1 -0
- package/dist/build/propose.d.ts +16 -0
- package/dist/build/propose.d.ts.map +1 -0
- package/dist/build/propose.js +126 -0
- package/dist/build/propose.js.map +1 -0
- package/dist/build/provenance.d.ts +19 -0
- package/dist/build/provenance.d.ts.map +1 -0
- package/dist/build/provenance.js +23 -0
- package/dist/build/provenance.js.map +1 -0
- package/dist/build/schema.d.ts +1825 -0
- package/dist/build/schema.d.ts.map +1 -0
- package/dist/build/schema.js +128 -0
- package/dist/build/schema.js.map +1 -0
- package/dist/build/sections.d.ts +30 -0
- package/dist/build/sections.d.ts.map +1 -0
- package/dist/build/sections.js +58 -0
- package/dist/build/sections.js.map +1 -0
- package/dist/build/tier1.d.ts +24 -0
- package/dist/build/tier1.d.ts.map +1 -0
- package/dist/build/tier1.js +68 -0
- package/dist/build/tier1.js.map +1 -0
- package/dist/build/tier2.d.ts +42 -0
- package/dist/build/tier2.d.ts.map +1 -0
- package/dist/build/tier2.js +187 -0
- package/dist/build/tier2.js.map +1 -0
- package/dist/build/types.d.ts +42 -0
- package/dist/build/types.d.ts.map +1 -0
- package/dist/build/types.js +2 -0
- package/dist/build/types.js.map +1 -0
- package/dist/components/glob.d.ts +10 -0
- package/dist/components/glob.d.ts.map +1 -0
- package/dist/components/glob.js +52 -0
- package/dist/components/glob.js.map +1 -0
- package/dist/components/registry.d.ts +51 -0
- package/dist/components/registry.d.ts.map +1 -0
- package/dist/components/registry.js +121 -0
- package/dist/components/registry.js.map +1 -0
- package/dist/dsl/factories.d.ts +86 -0
- package/dist/dsl/factories.d.ts.map +1 -0
- package/dist/dsl/factories.js +145 -0
- package/dist/dsl/factories.js.map +1 -0
- package/dist/dsl/index.d.ts +46 -0
- package/dist/dsl/index.d.ts.map +1 -0
- package/dist/dsl/index.js +67 -0
- package/dist/dsl/index.js.map +1 -0
- package/dist/dsl/reserved-names.typecheck.d.ts +2 -0
- package/dist/dsl/reserved-names.typecheck.d.ts.map +1 -0
- package/dist/dsl/reserved-names.typecheck.js +16 -0
- package/dist/dsl/reserved-names.typecheck.js.map +1 -0
- package/dist/dsl/verb-manifest.d.ts +12 -0
- package/dist/dsl/verb-manifest.d.ts.map +1 -0
- package/dist/dsl/verb-manifest.js +101 -0
- package/dist/dsl/verb-manifest.js.map +1 -0
- package/dist/fix/apply.d.ts +67 -0
- package/dist/fix/apply.d.ts.map +1 -0
- package/dist/fix/apply.js +163 -0
- package/dist/fix/apply.js.map +1 -0
- package/dist/fix/index.d.ts +3 -0
- package/dist/fix/index.d.ts.map +1 -0
- package/dist/fix/index.js +3 -0
- package/dist/fix/index.js.map +1 -0
- package/dist/fix/schema.d.ts +180 -0
- package/dist/fix/schema.d.ts.map +1 -0
- package/dist/fix/schema.js +41 -0
- package/dist/fix/schema.js.map +1 -0
- package/dist/gates/advisories.d.ts +11 -0
- package/dist/gates/advisories.d.ts.map +1 -0
- package/dist/gates/advisories.js +27 -0
- package/dist/gates/advisories.js.map +1 -0
- package/dist/gates/types.d.ts +36 -0
- package/dist/gates/types.d.ts.map +1 -0
- package/dist/gates/types.js +8 -0
- package/dist/gates/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +51 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +304 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/payload/builder.d.ts +49 -0
- package/dist/payload/builder.d.ts.map +1 -0
- package/dist/payload/builder.js +61 -0
- package/dist/payload/builder.js.map +1 -0
- package/dist/payload/mermaid.d.ts +22 -0
- package/dist/payload/mermaid.d.ts.map +1 -0
- package/dist/payload/mermaid.js +89 -0
- package/dist/payload/mermaid.js.map +1 -0
- package/dist/plugin/registry.d.ts +24 -0
- package/dist/plugin/registry.d.ts.map +1 -0
- package/dist/plugin/registry.js +24 -0
- package/dist/plugin/registry.js.map +1 -0
- package/dist/rules/component-refs.d.ts +26 -0
- package/dist/rules/component-refs.d.ts.map +1 -0
- package/dist/rules/component-refs.js +81 -0
- package/dist/rules/component-refs.js.map +1 -0
- package/dist/rules/evaluators.d.ts +50 -0
- package/dist/rules/evaluators.d.ts.map +1 -0
- package/dist/rules/evaluators.js +236 -0
- package/dist/rules/evaluators.js.map +1 -0
- package/dist/rules/host-rules.d.ts +109 -0
- package/dist/rules/host-rules.d.ts.map +1 -0
- package/dist/rules/host-rules.js +152 -0
- package/dist/rules/host-rules.js.map +1 -0
- package/dist/rules/index.d.ts +7 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +7 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/manifest-evaluators.d.ts +32 -0
- package/dist/rules/manifest-evaluators.d.ts.map +1 -0
- package/dist/rules/manifest-evaluators.js +124 -0
- package/dist/rules/manifest-evaluators.js.map +1 -0
- package/dist/rules/rule-category.d.ts +13 -0
- package/dist/rules/rule-category.d.ts.map +1 -0
- package/dist/rules/rule-category.js +27 -0
- package/dist/rules/rule-category.js.map +1 -0
- package/dist/rules/tarjan.d.ts +9 -0
- package/dist/rules/tarjan.d.ts.map +1 -0
- package/dist/rules/tarjan.js +102 -0
- package/dist/rules/tarjan.js.map +1 -0
- package/dist/scanner.d.ts +12 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +2 -0
- package/dist/scanner.js.map +1 -0
- package/dist/telemetry/diff.d.ts +27 -0
- package/dist/telemetry/diff.d.ts.map +1 -0
- package/dist/telemetry/diff.js +54 -0
- package/dist/telemetry/diff.js.map +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +4 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/serialize.d.ts +10 -0
- package/dist/telemetry/serialize.d.ts.map +1 -0
- package/dist/telemetry/serialize.js +11 -0
- package/dist/telemetry/serialize.js.map +1 -0
- package/dist/telemetry/types.d.ts +138 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +3 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/types/branded.d.ts +26 -0
- package/dist/types/branded.d.ts.map +1 -0
- package/dist/types/branded.js +24 -0
- package/dist/types/branded.js.map +1 -0
- package/dist/types/graph.d.ts +46 -0
- package/dist/types/graph.d.ts.map +1 -0
- package/dist/types/graph.js +2 -0
- package/dist/types/graph.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/ir.d.ts +1747 -0
- package/dist/types/ir.d.ts.map +1 -0
- package/dist/types/ir.js +120 -0
- package/dist/types/ir.js.map +1 -0
- package/dist/types/manifest.d.ts +58 -0
- package/dist/types/manifest.d.ts.map +1 -0
- package/dist/types/manifest.js +2 -0
- package/dist/types/manifest.js.map +1 -0
- package/dist/types/violation.d.ts +94 -0
- package/dist/types/violation.d.ts.map +1 -0
- package/dist/types/violation.js +56 -0
- package/dist/types/violation.js.map +1 -0
- 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 @@
|
|
|
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
|