@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":"schema.d.ts","sourceRoot":"","sources":["../../src/build/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire shapes for `align build` (ADR 011): the tier-1/MCP "rule fragment" input format, the
|
|
3
|
+
* `.align/generated-rules.json` file, and the `.align/rules.lock.json` file. Zod-validated per
|
|
4
|
+
* ADR 002's parse-don't-validate discipline — every fragment and every on-disk build artifact is
|
|
5
|
+
* `.parse()`d once, at its boundary.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { ruleIRSchema, rulesetIRSchema } from '../types/ir.js';
|
|
9
|
+
const componentRefText = z.string().min(1);
|
|
10
|
+
// ---------------------------------------------------------------------------------------------
|
|
11
|
+
// Rule fragment: the tier-1 ```align block JSON shape (and the MCP `align_propose_rules` proposal
|
|
12
|
+
// input shape) — a RuleIR variant's structural fields, minus `id` and `provenance`. Both of those
|
|
13
|
+
// are always assigned by align's build pipeline, never authored: `id` is a deterministic slug
|
|
14
|
+
// derived from the fragment's own content (ADR 011's rule-level diff minimization depends on ids
|
|
15
|
+
// being content-addressed, not author-assigned) and `provenance` is populated from where the
|
|
16
|
+
// fragment was found in the doc (or, for MCP, from what the client agent reports). A fragment MAY
|
|
17
|
+
// carry an optional `because` override — sourceQuote is auto-populated into the final rule's
|
|
18
|
+
// `because` field either way (ADR 011), but an author can prepend their own rationale text.
|
|
19
|
+
// ---------------------------------------------------------------------------------------------
|
|
20
|
+
const noDependencyFragmentSchema = z.object({
|
|
21
|
+
kind: z.literal('arch.no-dependency'),
|
|
22
|
+
from: componentRefText,
|
|
23
|
+
to: componentRefText,
|
|
24
|
+
because: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
const noCyclesFragmentSchema = z.object({
|
|
27
|
+
kind: z.literal('arch.no-cycles'),
|
|
28
|
+
scope: z.union([z.literal('repo'), componentRefText]).optional(),
|
|
29
|
+
includeTypeOnly: z.boolean().optional(),
|
|
30
|
+
because: z.string().optional(),
|
|
31
|
+
});
|
|
32
|
+
const layersFragmentSchema = z.object({
|
|
33
|
+
kind: z.literal('arch.layers'),
|
|
34
|
+
// Exactly one layer entry per fragment (mirrors the DSL's `.layer(x).canOnlyDependOn(...)`,
|
|
35
|
+
// which always produces a single-entry `layers` array) — keeps rule ids content-addressed
|
|
36
|
+
// one-layer-per-id (`arch.layers:<layer>`), the same scheme `dsl/index.ts` uses. A doc
|
|
37
|
+
// expressing constraints for two layers writes two bullets/blocks, not one fragment with two
|
|
38
|
+
// entries.
|
|
39
|
+
layers: z.array(z.object({ layer: componentRefText, canDependOn: z.array(componentRefText) })).length(1),
|
|
40
|
+
because: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
const customHostFragmentSchema = z.object({
|
|
43
|
+
kind: z.literal('custom.host'),
|
|
44
|
+
hostRuleName: z.string(),
|
|
45
|
+
because: z.string().optional(),
|
|
46
|
+
});
|
|
47
|
+
// `arch.metric` (max-LOC only, promoted 2026-07-12 on kluster ruleset evidence,
|
|
48
|
+
// IMPLEMENTATION_PLAN.md's Promotion log) — mirrors the DSL's `.maxLinesPerFile(max)` (dsl/index.ts).
|
|
49
|
+
const metricFragmentSchema = z.object({
|
|
50
|
+
kind: z.literal('arch.metric'),
|
|
51
|
+
target: componentRefText,
|
|
52
|
+
metric: z.literal('loc'),
|
|
53
|
+
max: z.number().int().positive(),
|
|
54
|
+
because: z.string().optional(),
|
|
55
|
+
});
|
|
56
|
+
// `security.manifest.*` (ADR 013, promoted 2026-07-12 on spike/MANIFEST_PROBE_REPORT.md probe
|
|
57
|
+
// evidence) — mirrors the DSL's `.sourceHygiene()`/`.newDependencyGate()` (dsl/index.ts). Neither
|
|
58
|
+
// verb takes a target/selector, so the fragment carries only `because`, same shape as
|
|
59
|
+
// `customHostFragmentSchema` minus `hostRuleName`.
|
|
60
|
+
const securityManifestSourceHygieneFragmentSchema = z.object({
|
|
61
|
+
kind: z.literal('security.manifest.source-hygiene'),
|
|
62
|
+
because: z.string().optional(),
|
|
63
|
+
});
|
|
64
|
+
const securityManifestNewDependencyFragmentSchema = z.object({
|
|
65
|
+
kind: z.literal('security.manifest.new-dependency'),
|
|
66
|
+
because: z.string().optional(),
|
|
67
|
+
});
|
|
68
|
+
export const ruleFragmentSchema = z.discriminatedUnion('kind', [
|
|
69
|
+
noDependencyFragmentSchema,
|
|
70
|
+
noCyclesFragmentSchema,
|
|
71
|
+
layersFragmentSchema,
|
|
72
|
+
customHostFragmentSchema,
|
|
73
|
+
metricFragmentSchema,
|
|
74
|
+
securityManifestSourceHygieneFragmentSchema,
|
|
75
|
+
securityManifestNewDependencyFragmentSchema,
|
|
76
|
+
]);
|
|
77
|
+
// ---------------------------------------------------------------------------------------------
|
|
78
|
+
// `.align/generated-rules.json` — a RuleIR array with full provenance, imported by the config
|
|
79
|
+
// loader (packages/cli/src/config.ts). Deliberately NOT a full RulesetIR: `components` stays
|
|
80
|
+
// authored once in align.config.ts, never duplicated into the generated artifact.
|
|
81
|
+
// ---------------------------------------------------------------------------------------------
|
|
82
|
+
export const generatedRulesFileSchema = z.object({
|
|
83
|
+
irVersion: z.literal('1'),
|
|
84
|
+
docPath: z.string(),
|
|
85
|
+
generatedAt: z.number(),
|
|
86
|
+
rules: z.array(ruleIRSchema),
|
|
87
|
+
});
|
|
88
|
+
// ---------------------------------------------------------------------------------------------
|
|
89
|
+
// `.align/rules.lock.json` — section hashes <-> rule ids, plus the generated-rules.json content
|
|
90
|
+
// hash (divergence detection: a hand-edit to the generated artifact changes this hash without a
|
|
91
|
+
// matching lockfile update).
|
|
92
|
+
// ---------------------------------------------------------------------------------------------
|
|
93
|
+
const lockSectionSchema = z.object({
|
|
94
|
+
anchor: z.string(),
|
|
95
|
+
headingText: z.string(),
|
|
96
|
+
startLine: z.number().int(),
|
|
97
|
+
endLine: z.number().int(),
|
|
98
|
+
contentHash: z.string(),
|
|
99
|
+
tier: z.enum(['verbatim', 'bullet', 'prose', 'empty']),
|
|
100
|
+
ruleIds: z.array(z.string()),
|
|
101
|
+
});
|
|
102
|
+
export const rulesLockSchema = z.object({
|
|
103
|
+
irVersion: z.literal('1'),
|
|
104
|
+
docPath: z.string(),
|
|
105
|
+
docContentHash: z.string(),
|
|
106
|
+
builtAt: z.number(),
|
|
107
|
+
sections: z.array(lockSectionSchema),
|
|
108
|
+
generatedRulesContentHash: z.string(),
|
|
109
|
+
});
|
|
110
|
+
// ---------------------------------------------------------------------------------------------
|
|
111
|
+
// `.align/ruleset-ir.json` (ADR 014) — the untrusted-mode data source. Written once in a trusted
|
|
112
|
+
// context by `align export-ir` and read by `align check --untrusted`/`--ir-only`, which never
|
|
113
|
+
// imports align.config.ts. `ruleset` is exactly a `RulesetIR` (ADR 002: components + rules, both
|
|
114
|
+
// already portable JSON with zero function-valued fields — `defineProject` zod-parses it before
|
|
115
|
+
// this schema ever sees it) — this wrapper adds only what an untrusted scan additionally needs
|
|
116
|
+
// beyond the ruleset itself: the scan-time `excludes` list (plain string data, config.ts's
|
|
117
|
+
// documented `excludes`-is-not-part-of-RulesetIR deviation, ADR 002) and export provenance.
|
|
118
|
+
// Deliberately does NOT carry `hostRules` — predicate functions cannot survive a JSON boundary
|
|
119
|
+
// (ADR 002 amendment) and are categorically unavailable under --untrusted regardless
|
|
120
|
+
// (`assertNoCustomHostRules`, `rules/host-rules.ts`).
|
|
121
|
+
// ---------------------------------------------------------------------------------------------
|
|
122
|
+
export const exportedRulesetSchema = z.object({
|
|
123
|
+
irVersion: z.literal('1'),
|
|
124
|
+
exportedAt: z.number(),
|
|
125
|
+
excludes: z.array(z.string()),
|
|
126
|
+
ruleset: rulesetIRSchema,
|
|
127
|
+
});
|
|
128
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/build/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown section model (ADR 011): a doc is parsed into heading-anchored sections with stable
|
|
3
|
+
* content hashes. Every markdown heading (any level, `#`-`######`) starts a new section that runs
|
|
4
|
+
* until the next heading or end of file — a flat model, not a nested tree; `align build`'s
|
|
5
|
+
* change-detection unit is "the section under one heading," which is what ADR 011's acceptance
|
|
6
|
+
* criteria ("one-section reword re-proposes only that section") operates on.
|
|
7
|
+
*/
|
|
8
|
+
export interface DocSection {
|
|
9
|
+
/** Stable slug derived from the heading text (github-style), deduped against earlier headings
|
|
10
|
+
* in the same doc so two identically-worded headings don't collide. */
|
|
11
|
+
readonly anchor: string;
|
|
12
|
+
readonly headingText: string;
|
|
13
|
+
readonly level: number;
|
|
14
|
+
/** 1-based, inclusive — the heading line itself. */
|
|
15
|
+
readonly startLine: number;
|
|
16
|
+
/** 1-based, inclusive — the last line of the section's body (before the next heading, or EOF). */
|
|
17
|
+
readonly endLine: number;
|
|
18
|
+
/** Raw body text (excludes the heading line) — the classification/hashing input. */
|
|
19
|
+
readonly bodyText: string;
|
|
20
|
+
/** Content hash of `headingText` + `bodyText` — changes iff the section's own text changes,
|
|
21
|
+
* independent of line-number shifts caused by edits elsewhere in the doc. */
|
|
22
|
+
readonly contentHash: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ParsedDoc {
|
|
25
|
+
readonly lines: readonly string[];
|
|
26
|
+
readonly sections: readonly DocSection[];
|
|
27
|
+
}
|
|
28
|
+
/** Parses a markdown document into heading-anchored sections (ADR 011). Pure — no I/O. */
|
|
29
|
+
export declare function parseMarkdownDoc(doc: string): ParsedDoc;
|
|
30
|
+
//# sourceMappingURL=sections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../src/build/sections.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB;2EACuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kGAAkG;IAClG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;iFAC6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;CAC1C;AAqBD,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CA0CvD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { sha256Hex } from './hash.js';
|
|
2
|
+
const HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
3
|
+
function slugify(headingText) {
|
|
4
|
+
const base = headingText
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.trim()
|
|
7
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
8
|
+
.replace(/\s+/g, '-')
|
|
9
|
+
.replace(/-+/g, '-')
|
|
10
|
+
.replace(/^-|-$/g, '');
|
|
11
|
+
return base.length > 0 ? base : 'section';
|
|
12
|
+
}
|
|
13
|
+
function dedupeAnchor(base, seen) {
|
|
14
|
+
const count = seen.get(base) ?? 0;
|
|
15
|
+
seen.set(base, count + 1);
|
|
16
|
+
return count === 0 ? base : `${base}-${count + 1}`;
|
|
17
|
+
}
|
|
18
|
+
/** Parses a markdown document into heading-anchored sections (ADR 011). Pure — no I/O. */
|
|
19
|
+
export function parseMarkdownDoc(doc) {
|
|
20
|
+
const lines = doc.split('\n');
|
|
21
|
+
const seenAnchors = new Map();
|
|
22
|
+
const headings = [];
|
|
23
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
24
|
+
const line = lines[i];
|
|
25
|
+
if (line === undefined)
|
|
26
|
+
continue;
|
|
27
|
+
const match = HEADING_RE.exec(line);
|
|
28
|
+
if (match === null)
|
|
29
|
+
continue;
|
|
30
|
+
const hashes = match[1];
|
|
31
|
+
const headingText = match[2];
|
|
32
|
+
if (hashes === undefined || headingText === undefined)
|
|
33
|
+
continue;
|
|
34
|
+
headings.push({ level: hashes.length, headingText, startLine: i + 1 });
|
|
35
|
+
}
|
|
36
|
+
const sections = [];
|
|
37
|
+
for (let i = 0; i < headings.length; i += 1) {
|
|
38
|
+
const h = headings[i];
|
|
39
|
+
if (h === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
const next = headings[i + 1];
|
|
42
|
+
const endLine = next === undefined ? lines.length : next.startLine - 1;
|
|
43
|
+
const bodyLines = lines.slice(h.startLine, endLine); // excludes the heading line itself
|
|
44
|
+
const bodyText = bodyLines.join('\n');
|
|
45
|
+
const anchor = dedupeAnchor(slugify(h.headingText), seenAnchors);
|
|
46
|
+
sections.push({
|
|
47
|
+
anchor,
|
|
48
|
+
headingText: h.headingText,
|
|
49
|
+
level: h.level,
|
|
50
|
+
startLine: h.startLine,
|
|
51
|
+
endLine,
|
|
52
|
+
bodyText,
|
|
53
|
+
contentHash: sha256Hex(`${h.headingText}\n${bodyText}`),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { lines, sections };
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=sections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/build/sections.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RepoRelativePath } from '../types/branded.js';
|
|
2
|
+
import type { SourceRange } from '../types/violation.js';
|
|
3
|
+
import type { DocSection } from './sections.js';
|
|
4
|
+
import { type RuleFragment } from './schema.js';
|
|
5
|
+
import type { FlaggedProposal } from './types.js';
|
|
6
|
+
export interface ExtractedFragment {
|
|
7
|
+
readonly fragment: RuleFragment;
|
|
8
|
+
readonly sourceLineRange: SourceRange;
|
|
9
|
+
/** Tier-1 sourceQuote is the fenced block's own JSON content (trimmed) — there is no separate
|
|
10
|
+
* prose to quote for a verbatim block; the block itself IS the doc's declared intent (an
|
|
11
|
+
* explicit ADR 011 resolution — see the Stage 3 final report's "ambiguities resolved" list). */
|
|
12
|
+
readonly sourceQuote: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Tier 1 of the precision ladder (ADR 011): fenced ` ```align ` code blocks compile verbatim,
|
|
16
|
+
* zero LLM involved. Each block's content is a **JSON RuleFragment** (`schema.ts`) — the
|
|
17
|
+
* structural fields of one `RuleIR` variant, minus `id`/`provenance` (both are always assigned by
|
|
18
|
+
* the build pipeline, never authored — see `ground.ts`). Pure — no I/O.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractFencedAlignBlocks(lines: readonly string[], section: DocSection, docPath: RepoRelativePath): {
|
|
21
|
+
readonly fragments: readonly ExtractedFragment[];
|
|
22
|
+
readonly errors: readonly FlaggedProposal[];
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=tier1.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tier1.d.ts","sourceRoot":"","sources":["../../src/build/tier1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC;IACtC;;oGAEgG;IAChG,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAKD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,gBAAgB,GACxB;IAAE,QAAQ,CAAC,SAAS,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;CAAE,CA+DnG"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ruleFragmentSchema } from './schema.js';
|
|
2
|
+
const FENCE_OPEN_RE = /^```align\s*$/;
|
|
3
|
+
const FENCE_CLOSE_RE = /^```\s*$/;
|
|
4
|
+
/**
|
|
5
|
+
* Tier 1 of the precision ladder (ADR 011): fenced ` ```align ` code blocks compile verbatim,
|
|
6
|
+
* zero LLM involved. Each block's content is a **JSON RuleFragment** (`schema.ts`) — the
|
|
7
|
+
* structural fields of one `RuleIR` variant, minus `id`/`provenance` (both are always assigned by
|
|
8
|
+
* the build pipeline, never authored — see `ground.ts`). Pure — no I/O.
|
|
9
|
+
*/
|
|
10
|
+
export function extractFencedAlignBlocks(lines, section, docPath) {
|
|
11
|
+
const fragments = [];
|
|
12
|
+
const errors = [];
|
|
13
|
+
const bodyStart = section.startLine; // 0-indexed first body line (section.startLine is the
|
|
14
|
+
// 1-based heading line; the body's first line is index === that 1-based number)
|
|
15
|
+
const bodyEndExclusive = section.endLine; // 0-indexed exclusive end (section.endLine is 1-based inclusive)
|
|
16
|
+
let i = bodyStart;
|
|
17
|
+
while (i < bodyEndExclusive) {
|
|
18
|
+
const line = lines[i];
|
|
19
|
+
if (line === undefined || !FENCE_OPEN_RE.test(line.trim())) {
|
|
20
|
+
i += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const contentStart = i + 1; // 0-indexed
|
|
24
|
+
let j = contentStart;
|
|
25
|
+
while (j < bodyEndExclusive && !FENCE_CLOSE_RE.test((lines[j] ?? '').trim()))
|
|
26
|
+
j += 1;
|
|
27
|
+
const contentEndExclusive = j;
|
|
28
|
+
const contentLines = lines.slice(contentStart, contentEndExclusive);
|
|
29
|
+
const raw = contentLines.join('\n').trim();
|
|
30
|
+
const range = {
|
|
31
|
+
startLine: contentStart + 1,
|
|
32
|
+
endLine: Math.max(contentStart + 1, contentEndExclusive),
|
|
33
|
+
};
|
|
34
|
+
let parsedJson;
|
|
35
|
+
try {
|
|
36
|
+
parsedJson = JSON.parse(raw);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
errors.push({
|
|
40
|
+
section: section.anchor,
|
|
41
|
+
sourceFile: docPath,
|
|
42
|
+
sourceLineRange: range,
|
|
43
|
+
sourceQuote: raw,
|
|
44
|
+
reason: 'invalid-fragment',
|
|
45
|
+
detail: `JSON parse error in \`\`\`align block: ${err instanceof Error ? err.message : String(err)}`,
|
|
46
|
+
});
|
|
47
|
+
i = j + 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const result = ruleFragmentSchema.safeParse(parsedJson);
|
|
51
|
+
if (!result.success) {
|
|
52
|
+
errors.push({
|
|
53
|
+
section: section.anchor,
|
|
54
|
+
sourceFile: docPath,
|
|
55
|
+
sourceLineRange: range,
|
|
56
|
+
sourceQuote: raw,
|
|
57
|
+
reason: 'invalid-fragment',
|
|
58
|
+
detail: `Schema validation error: ${result.error.issues.map((iss) => iss.message).join('; ')}`,
|
|
59
|
+
});
|
|
60
|
+
i = j + 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
fragments.push({ fragment: result.data, sourceLineRange: range, sourceQuote: raw });
|
|
64
|
+
i = j + 1;
|
|
65
|
+
}
|
|
66
|
+
return { fragments, errors };
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=tier1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tier1.js","sourceRoot":"","sources":["../../src/build/tier1.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RepoRelativePath } from '../types/branded.js';
|
|
2
|
+
import type { SourceRange } from '../types/violation.js';
|
|
3
|
+
import type { DocSection } from './sections.js';
|
|
4
|
+
import type { RuleFragment } from './schema.js';
|
|
5
|
+
import type { FlaggedProposal } from './types.js';
|
|
6
|
+
export interface ExtractedBullet {
|
|
7
|
+
readonly fragment: RuleFragment;
|
|
8
|
+
readonly sourceLineRange: SourceRange;
|
|
9
|
+
readonly sourceQuote: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parses one bullet's sentence into a `RuleFragment` per ADR 011's constrained grammar: component
|
|
13
|
+
* names, "must not depend on", "no cycles", "layers," "must stay under N lines" (`arch.metric`,
|
|
14
|
+
* max-LOC only). Returns `undefined` for a sentence that
|
|
15
|
+
* doesn't match any known pattern — the caller flags it as `unparsed-bullet` rather than guessing.
|
|
16
|
+
* Deterministic regex matching only — no LLM, no fuzzy matching (this stage's CLI build path is
|
|
17
|
+
* zero-LLM by design; see the Stage 3 report's ADR-011-ambiguity resolutions).
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseBulletSentence(sentence: string): RuleFragment | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Human-readable grammar-form catalog (Stage 5, IMPLEMENTATION_PLAN.md) — the single source
|
|
22
|
+
* `align skill`'s generated bullet-grammar section reads (`packages/cli/src/skill/bullet-
|
|
23
|
+
* grammar.ts`). Each `example` is asserted against the real `parseBulletSentence` in
|
|
24
|
+
* `test/build-tier2-grammar-catalog.test.ts`: if a regex above changes shape and an example stops
|
|
25
|
+
* matching (or starts producing a different rule kind), that test fails — the catalog cannot
|
|
26
|
+
* silently drift from the parser it describes.
|
|
27
|
+
*/
|
|
28
|
+
export interface BulletGrammarForm {
|
|
29
|
+
readonly ruleKind: RuleFragment['kind'];
|
|
30
|
+
readonly pattern: string;
|
|
31
|
+
readonly example: string;
|
|
32
|
+
}
|
|
33
|
+
export declare const BULLET_GRAMMAR_FORMS: readonly BulletGrammarForm[];
|
|
34
|
+
/**
|
|
35
|
+
* Tier 2 of the precision ladder (ADR 011): structured `- **Rule**: ...` bullets parse
|
|
36
|
+
* deterministically. Pure — no I/O.
|
|
37
|
+
*/
|
|
38
|
+
export declare function extractStructuredBullets(lines: readonly string[], section: DocSection, docPath: RepoRelativePath): {
|
|
39
|
+
readonly bullets: readonly ExtractedBullet[];
|
|
40
|
+
readonly errors: readonly FlaggedProposal[];
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=tier2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tier2.d.ts","sourceRoot":"","sources":["../../src/build/tier2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AA0DD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAsD9E;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAOD,eAAO,MAAM,oBAAoB,EAAE,SAAS,iBAAiB,EA+B5D,CAAC;AAEF;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,gBAAgB,GACxB;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;CAAE,CAmC/F"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const BULLET_RE = /^\s*-\s+\*\*Rule\*\*:\s*(.+?)\s*$/i;
|
|
2
|
+
const NO_CYCLES_SCOPED_RE = /^no\s+cycles?(?:\s+(?:allowed|permitted))?\s+(?:within|in|for)\s+(.+?)\.?$/i;
|
|
3
|
+
const NO_CYCLES_BARE_RE = /^no\s+cycles?\.?$/i;
|
|
4
|
+
const NO_CYCLES_SUBJECT_RE = /^(.+?)\s+must\s+have\s+no\s+cycles?\.?$/i;
|
|
5
|
+
const NO_DEPENDENCY_RE = /^(.+?)\s+must\s+not\s+depend\s+on\s+(.+?)\.?$/i;
|
|
6
|
+
const LAYERS_RE = /^(.+?)\s+(?:may|can)\s+only\s+depend\s+on\s+(.+?)\.?$/i;
|
|
7
|
+
// `arch.metric` (max-LOC only, promoted 2026-07-12 on kluster ruleset evidence,
|
|
8
|
+
// IMPLEMENTATION_PLAN.md's Promotion log). Mirrors the DSL's `.maxLinesPerFile(max)`
|
|
9
|
+
// (dsl/index.ts) — only the `loc` metric has a tier-2 grammar; fan-in/fan-out/instability stay
|
|
10
|
+
// reserved pending their own evidence and gain their own bullet grammar when promoted.
|
|
11
|
+
const MAX_LOC_RE = /^files\s+in\s+(.+?)\s+must\s+stay\s+under\s+(\d+)\s+lines?\.?$/i;
|
|
12
|
+
// `security.manifest.*` (ADR 013, promoted 2026-07-12 on probe evidence). Bare-sentence forms,
|
|
13
|
+
// same shape as `NO_CYCLES_BARE_RE` — neither rule takes a component target, so there is nothing
|
|
14
|
+
// to capture.
|
|
15
|
+
const SOURCE_HYGIENE_RE = /^dependenc(?:y|ies)\s+(?:sources?\s+)?must\s+be\s+(?:sourced\s+from\s+)?(?:the\s+)?registry(?:[- ]only)?\.?$/i;
|
|
16
|
+
const NEW_DEPENDENCY_GATE_RE = /^new\s+dependenc(?:y|ies)\s+(?:requires?|needs?)\s+baseline\s+(?:approval|acceptance)\.?$/i;
|
|
17
|
+
/** Splits a target-list clause ("`core`, `cli` and `pluginTypescript`") into raw tokens.
|
|
18
|
+
* Backtick/quote stripping happens at grounding time (`ground.ts`), not here — tier 2 stays a
|
|
19
|
+
* pure sentence splitter. */
|
|
20
|
+
function splitTokens(clause) {
|
|
21
|
+
return clause
|
|
22
|
+
.split(/\s*,\s*|\s+and\s+|\s+or\s+/i)
|
|
23
|
+
.map((t) => t.trim())
|
|
24
|
+
.filter((t) => t.length > 0);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* R5 (precision-ladder gap, GREENFIELD_TRIAD_REPORT.md §1): tier-2 bullets had no field for an
|
|
28
|
+
* authored rationale — only fenced-JSON blocks (tier 1) could carry `.because()` text, capping the
|
|
29
|
+
* greenfield demo's `.because()` coverage at 75% for no reason other than which tier a rule was
|
|
30
|
+
* authored in. Splits an optional trailing "Because <rationale>." clause off a bullet's rule
|
|
31
|
+
* sentence: "api may only depend on core. Because the API layer must stay headless." -> sentence
|
|
32
|
+
* "api may only depend on core", because "the API layer must stay headless". The result flows
|
|
33
|
+
* straight into `RuleFragment.because` (every fragment variant already has this optional field,
|
|
34
|
+
* `schema.ts` — the same field a tier-1 fenced block's own `"because"` JSON key populates), so
|
|
35
|
+
* `groundFragment`/`buildProvenance` (`ground.ts`/`provenance.ts`) need no changes at all: an
|
|
36
|
+
* authored tier-2 rationale is prepended to the auto-generated "Enforced by <doc>:<line>: '<quote>'"
|
|
37
|
+
* phrase exactly like a tier-1 fragment's `because` is.
|
|
38
|
+
*
|
|
39
|
+
* The literal period-then-"Because" boundary is the deterministic separator — no NLP, and no
|
|
40
|
+
* ambiguity with a rule sentence that happens to use the word "because" without that boundary
|
|
41
|
+
* (which simply isn't matched, and flows through unsplit, same as today).
|
|
42
|
+
*/
|
|
43
|
+
const TRAILING_BECAUSE_RE = /^(.*?)\.\s+Because\s+(.+?)\s*\.?$/i;
|
|
44
|
+
function splitBecauseClause(raw) {
|
|
45
|
+
const trimmed = raw.trim();
|
|
46
|
+
const match = TRAILING_BECAUSE_RE.exec(trimmed);
|
|
47
|
+
if (match?.[1] !== undefined && match[2] !== undefined && match[2].length > 0) {
|
|
48
|
+
return { sentence: match[1].trim(), because: match[2].trim() };
|
|
49
|
+
}
|
|
50
|
+
return { sentence: trimmed.replace(/\.$/, '').trim(), because: undefined };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parses one bullet's sentence into a `RuleFragment` per ADR 011's constrained grammar: component
|
|
54
|
+
* names, "must not depend on", "no cycles", "layers," "must stay under N lines" (`arch.metric`,
|
|
55
|
+
* max-LOC only). Returns `undefined` for a sentence that
|
|
56
|
+
* doesn't match any known pattern — the caller flags it as `unparsed-bullet` rather than guessing.
|
|
57
|
+
* Deterministic regex matching only — no LLM, no fuzzy matching (this stage's CLI build path is
|
|
58
|
+
* zero-LLM by design; see the Stage 3 report's ADR-011-ambiguity resolutions).
|
|
59
|
+
*/
|
|
60
|
+
export function parseBulletSentence(sentence) {
|
|
61
|
+
const scopedCycles = NO_CYCLES_SCOPED_RE.exec(sentence);
|
|
62
|
+
if (scopedCycles?.[1] !== undefined) {
|
|
63
|
+
return { kind: 'arch.no-cycles', scope: scopedCycles[1].trim() };
|
|
64
|
+
}
|
|
65
|
+
if (NO_CYCLES_BARE_RE.test(sentence)) {
|
|
66
|
+
return { kind: 'arch.no-cycles', scope: 'repo' };
|
|
67
|
+
}
|
|
68
|
+
const subjectCycles = NO_CYCLES_SUBJECT_RE.exec(sentence);
|
|
69
|
+
if (subjectCycles?.[1] !== undefined) {
|
|
70
|
+
return { kind: 'arch.no-cycles', scope: subjectCycles[1].trim() };
|
|
71
|
+
}
|
|
72
|
+
const noDependency = NO_DEPENDENCY_RE.exec(sentence);
|
|
73
|
+
if (noDependency?.[1] !== undefined && noDependency[2] !== undefined) {
|
|
74
|
+
const from = splitTokens(noDependency[1])[0];
|
|
75
|
+
const targets = splitTokens(noDependency[2]);
|
|
76
|
+
const to = targets[0];
|
|
77
|
+
// Single from -> single to is the only shape a RuleFragment can express (a "from must not
|
|
78
|
+
// depend on A, B" bullet with multiple targets isn't representable as one fragment — the
|
|
79
|
+
// caller sees `undefined` here and flags it as unparsed rather than silently dropping targets;
|
|
80
|
+
// multi-target denylists are `align.layers`'s job, or split into multiple bullets).
|
|
81
|
+
if (from === undefined || to === undefined || targets.length > 1)
|
|
82
|
+
return undefined;
|
|
83
|
+
return { kind: 'arch.no-dependency', from, to };
|
|
84
|
+
}
|
|
85
|
+
const layers = LAYERS_RE.exec(sentence);
|
|
86
|
+
if (layers?.[1] !== undefined && layers[2] !== undefined) {
|
|
87
|
+
const layerTokens = splitTokens(layers[1]);
|
|
88
|
+
const layer = layerTokens[0];
|
|
89
|
+
const canDependOn = splitTokens(layers[2]);
|
|
90
|
+
if (layer === undefined || layerTokens.length > 1 || canDependOn.length === 0)
|
|
91
|
+
return undefined;
|
|
92
|
+
return { kind: 'arch.layers', layers: [{ layer, canDependOn }] };
|
|
93
|
+
}
|
|
94
|
+
const maxLoc = MAX_LOC_RE.exec(sentence);
|
|
95
|
+
if (maxLoc?.[1] !== undefined && maxLoc[2] !== undefined) {
|
|
96
|
+
const targetTokens = splitTokens(maxLoc[1]);
|
|
97
|
+
const target = targetTokens[0];
|
|
98
|
+
const max = Number.parseInt(maxLoc[2], 10);
|
|
99
|
+
// Single target only — same "a RuleFragment can express one grounded target" discipline as
|
|
100
|
+
// `NO_DEPENDENCY_RE`/`LAYERS_RE` above.
|
|
101
|
+
if (target === undefined || targetTokens.length > 1 || !Number.isFinite(max))
|
|
102
|
+
return undefined;
|
|
103
|
+
return { kind: 'arch.metric', target, metric: 'loc', max };
|
|
104
|
+
}
|
|
105
|
+
if (SOURCE_HYGIENE_RE.test(sentence)) {
|
|
106
|
+
return { kind: 'security.manifest.source-hygiene' };
|
|
107
|
+
}
|
|
108
|
+
if (NEW_DEPENDENCY_GATE_RE.test(sentence)) {
|
|
109
|
+
return { kind: 'security.manifest.new-dependency' };
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/** Appended to every pattern below (R5): any bullet may carry an optional trailing rationale
|
|
114
|
+
* clause, parsed into the rule's `.because()` provenance — a period then the literal word
|
|
115
|
+
* "Because" is the deterministic boundary between the rule sentence and the rationale. */
|
|
116
|
+
const BECAUSE_SUFFIX = ' Optionally followed by " Because <rationale>." to record why (e.g. "... Because the API layer must stay headless.") — parsed into the rule\'s .because() provenance.';
|
|
117
|
+
export const BULLET_GRAMMAR_FORMS = [
|
|
118
|
+
{
|
|
119
|
+
ruleKind: 'arch.no-dependency',
|
|
120
|
+
pattern: `<component> must not depend on <component>.${BECAUSE_SUFFIX}`,
|
|
121
|
+
example: 'api must not depend on ui.',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
ruleKind: 'arch.layers',
|
|
125
|
+
pattern: `<component> may|can only depend on <component>[, <component> and/or <component>...].${BECAUSE_SUFFIX}`,
|
|
126
|
+
example: 'api may only depend on core.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
ruleKind: 'arch.no-cycles',
|
|
130
|
+
pattern: `no cycles. | no cycles within|in|for <scope>. | <scope> must have no cycles.${BECAUSE_SUFFIX}`,
|
|
131
|
+
example: 'no cycles.',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
ruleKind: 'arch.metric',
|
|
135
|
+
pattern: `files in <component> must stay under <N> lines.${BECAUSE_SUFFIX}`,
|
|
136
|
+
example: 'files in core must stay under 500 lines.',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
ruleKind: 'security.manifest.source-hygiene',
|
|
140
|
+
pattern: `dependency|dependencies (sources) must be (sourced from the) registry(-only).${BECAUSE_SUFFIX}`,
|
|
141
|
+
example: 'dependency sources must be registry-only.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
ruleKind: 'security.manifest.new-dependency',
|
|
145
|
+
pattern: `new dependency|dependencies requires|needs baseline approval|acceptance.${BECAUSE_SUFFIX}`,
|
|
146
|
+
example: 'new dependency requires baseline approval.',
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
/**
|
|
150
|
+
* Tier 2 of the precision ladder (ADR 011): structured `- **Rule**: ...` bullets parse
|
|
151
|
+
* deterministically. Pure — no I/O.
|
|
152
|
+
*/
|
|
153
|
+
export function extractStructuredBullets(lines, section, docPath) {
|
|
154
|
+
const bullets = [];
|
|
155
|
+
const errors = [];
|
|
156
|
+
const bodyStart = section.startLine;
|
|
157
|
+
const bodyEndExclusive = section.endLine;
|
|
158
|
+
for (let i = bodyStart; i < bodyEndExclusive; i += 1) {
|
|
159
|
+
const line = lines[i];
|
|
160
|
+
if (line === undefined)
|
|
161
|
+
continue;
|
|
162
|
+
const match = BULLET_RE.exec(line);
|
|
163
|
+
if (match?.[1] === undefined)
|
|
164
|
+
continue;
|
|
165
|
+
const { sentence, because } = splitBecauseClause(match[1]);
|
|
166
|
+
const range = { startLine: i + 1, endLine: i + 1 };
|
|
167
|
+
const fragment = parseBulletSentence(sentence);
|
|
168
|
+
if (fragment === undefined) {
|
|
169
|
+
errors.push({
|
|
170
|
+
section: section.anchor,
|
|
171
|
+
sourceFile: docPath,
|
|
172
|
+
sourceLineRange: range,
|
|
173
|
+
sourceQuote: line.trim(),
|
|
174
|
+
reason: 'unparsed-bullet',
|
|
175
|
+
detail: `Bullet did not match the tier-2 grammar (component names + "must not depend on" / "no cycles" / "may only depend on" / "must stay under N lines"): "${sentence}"`,
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
bullets.push({
|
|
180
|
+
fragment: because === undefined ? fragment : { ...fragment, because },
|
|
181
|
+
sourceLineRange: range,
|
|
182
|
+
sourceQuote: line.trim(),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return { bullets, errors };
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=tier2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tier2.js","sourceRoot":"","sources":["../../src/build/tier2.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RepoRelativePath, RuleId } from '../types/branded.js';
|
|
2
|
+
import type { RuleIR } from '../types/ir.js';
|
|
3
|
+
import type { SourceRange } from '../types/violation.js';
|
|
4
|
+
/** Why a candidate rule was NOT written — ADR 011: "ungroundable -> flagged proposal, never
|
|
5
|
+
* silently written." Every reason is surfaced to the human/agent, never silently dropped. */
|
|
6
|
+
export type FlaggedReason = 'ungroundable-selector' | 'unparsed-bullet' | 'invalid-fragment' | 'conflicting-rule-id' | 'unregistered-host-rule';
|
|
7
|
+
export interface FlaggedProposal {
|
|
8
|
+
readonly section: string;
|
|
9
|
+
readonly sourceFile: RepoRelativePath;
|
|
10
|
+
readonly sourceLineRange: SourceRange;
|
|
11
|
+
readonly sourceQuote: string;
|
|
12
|
+
readonly reason: FlaggedReason;
|
|
13
|
+
readonly detail: string;
|
|
14
|
+
}
|
|
15
|
+
export type SectionTier = 'verbatim' | 'bullet' | 'prose' | 'empty';
|
|
16
|
+
export interface SectionClassification {
|
|
17
|
+
readonly anchor: string;
|
|
18
|
+
readonly headingText: string;
|
|
19
|
+
readonly startLine: number;
|
|
20
|
+
readonly endLine: number;
|
|
21
|
+
readonly contentHash: string;
|
|
22
|
+
readonly tier: SectionTier;
|
|
23
|
+
/** Rule ids produced from this section, post-dedup — empty for 'prose'/'empty' sections. */
|
|
24
|
+
readonly ruleIds: readonly RuleId[];
|
|
25
|
+
}
|
|
26
|
+
/** Pass-1 scaffold for a prose section (ADR 011 two-pass clarification, Stage 3 MCP spec): align
|
|
27
|
+
* never invents concerns — `concerns` is deliberately empty here, for the connected client agent
|
|
28
|
+
* to fill in pass 2. */
|
|
29
|
+
export interface ProseSectionScaffold {
|
|
30
|
+
readonly anchor: string;
|
|
31
|
+
readonly headingText: string;
|
|
32
|
+
readonly startLine: number;
|
|
33
|
+
readonly endLine: number;
|
|
34
|
+
readonly concerns: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
export interface BuildProposal {
|
|
37
|
+
readonly sections: readonly SectionClassification[];
|
|
38
|
+
readonly rules: readonly RuleIR[];
|
|
39
|
+
readonly flagged: readonly FlaggedProposal[];
|
|
40
|
+
readonly proseSections: readonly ProseSectionScaffold[];
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=types.d.ts.map
|