@decantr/cli 3.0.0-next.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/bin.js +4 -4
- package/dist/{chunk-WUIFEHWM.js → chunk-4NDOHCYY.js} +313 -25
- package/dist/{chunk-MNZKOZW7.js → chunk-7Z74ETDR.js} +5 -2
- package/dist/{chunk-GXC5IJQY.js → chunk-B2PJDAMS.js} +1 -1
- package/dist/{chunk-BDY4JCFH.js → chunk-GN63PIKK.js} +268 -74
- package/dist/{heal-IRIDB7IZ.js → heal-HHVVTVIH.js} +1 -1
- package/dist/{health-ASR7RWZJ.js → health-DDT4RABT.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/{studio-R65NXPJW.js → studio-WDJ3BOCU.js} +3 -3
- package/dist/{workspace-2FFQEEQQ.js → workspace-3XNCMZ5L.js} +3 -3
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Use `decantr setup` when you are unsure which path applies. It detects whether t
|
|
|
22
22
|
Use `decantr scan` when you want a zero-commit Brownfield preview. It reads local files, detects framework/routes/styling/static-hosting/assistant-rule signals, previews typed Contract graph readiness in memory when a Decantr contract already exists, reports the contract capsule source-handle count and limit, prints a terminal report, and writes no `.decantr` files or report artifacts. Add `--json` when automation needs the `ScanReportV1` payload.
|
|
23
23
|
Use `decantr new` for a greenfield workspace in a fresh directory. With a starter-kit blueprint or archetype it uses the runnable adapter and Decantr CSS; without certified vocabulary content it creates a contract-only workspace unless you explicitly pass `--adoption=decantr-css`.
|
|
24
24
|
Use `decantr adopt` when you already have an app and want Decantr governance without adopting a blueprint. Brownfield attach is proposal-driven: Decantr inventories the app, writes an observed essence proposal, hydrates hosted execution packs when online, and only applies the contract when you explicitly accept or merge it.
|
|
25
|
-
Use `decantr doctor` when the next step is unclear, `decantr task` before asking an LLM to modify a route, `decantr verify` after the edit, and `decantr ci` in required automation. Use `decantr graph` when you want the Decantr 3 typed Contract graph, typed graph diff summary, manifest, content-addressed snapshot history, and cache-friendly contract capsule written under `.decantr/graph`; the capsule includes a bounded SourceArtifact path index so agents can discover valid file-impact handles without reading the full snapshot, and `--capsule-source-limit <count>` can tune that index for large repos. Add `--route /feed --task "improve loading" --json` when you want the exact task-ranked route-scoped subgraph an agent should inspect before editing, `--node cmp:button --impact --json` when you need the graph-shaped blast radius for a component, token, rule, finding, or source artifact, or `--file src/app/page.tsx --impact --json` when the agent knows the source file it is about to change. Use `--snapshot-id <id>` to inspect a replayable history snapshot and `--compare-to <id> --include-diff-ops --json` to compare the selected/current graph against a prior snapshot. Use `decantr codify --from-audit --style-bridge` when you want project-owned UI patterns, local rules, and token/class bridge mappings such as button/card/shell/theme standards to appear in future task context and verification. Once accepted, that local law is the first Hybrid lane: the app still owns source and styling, but Decantr treats accepted local patterns, rules, and style bridge mappings as project authority.
|
|
25
|
+
Use `decantr doctor` when the next step is unclear, `decantr task` before asking an LLM to modify a route, `decantr verify` after the edit, and `decantr ci` in required automation. Use `decantr graph` when you want the Decantr 3 typed Contract graph, typed graph diff summary, manifest, content-addressed snapshot history, and cache-friendly contract capsule written under `.decantr/graph`; the capsule includes a bounded SourceArtifact path index so agents can discover valid file-impact handles without reading the full snapshot, and `--capsule-source-limit <count>` can tune that index for large repos. Add `--route /feed --task "improve loading" --json` when you want the exact task-ranked route-scoped subgraph an agent should inspect before editing, `--node cmp:button --impact --json` when you need the graph-shaped blast radius for a component, token, rule, finding, or source artifact, or `--file src/app/page.tsx --impact --json` when the agent knows the source file it is about to change. Use `--snapshot-id <id>` to inspect a replayable history snapshot and `--compare-to <id> --include-diff-ops --json` to compare the selected/current graph against a prior snapshot. Use `decantr codify --from-audit --style-bridge` when you want project-owned UI patterns, optional `behavior_obligations`, local rules, and token/class bridge mappings such as button/card/shell/theme standards to appear in future task context and verification. Once accepted, that local law is the first Hybrid lane: the app still owns source and styling, but Decantr treats accepted local patterns, behavior obligations, rules, and style bridge mappings as project authority.
|
|
26
26
|
In monorepos, app-scoped commands accept `--project <app-path>`. `setup` shows attach guidance before adoption and the day-two loop after adoption. Hosted pack hydration also follows the essence path: `decantr registry compile-packs apps/web/decantr.essence.json --write-context` writes into `apps/web/.decantr/context`. In contract-only/offline Brownfield, deferred hosted packs are optional context unless a present manifest references missing files.
|
|
27
27
|
Use `decantr init`, `decantr analyze`, `decantr check`, and `decantr health` as advanced primitives when you need direct control over one step.
|
|
28
28
|
|
|
@@ -91,9 +91,9 @@ pnpm exec decantr codify --map-pattern hero --project apps/web
|
|
|
91
91
|
pnpm exec decantr ci init --project apps/web
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
Assistant rule integration is preview-first: `--assistant-bridge=preview` writes `.decantr/context/assistant-bridge.md`, `decantr rules preview` prints the bridge, and `--assistant-bridge=apply` or `decantr rules apply` mutates supported rule files with idempotent marked blocks. When `.decantr/graph/contract-capsule.json` exists, `decantr task` includes it in the read list and JSON payload so CLI-only agents can load the typed Contract capsule and its SourceArtifact path index before route-specific edits. When `.decantr/graph/graph.snapshot.json` exists, `decantr task --json` also includes `graph.routeContext`: the route node, scoped page/shell/pattern/component/token nodes, local law/style bridge nodes, open findings/evidence IDs, deterministic ranked nodes boosted by the task text, and the supporting edges. If git changed files resolve to SourceArtifact nodes, `decantr task --json` also includes `graph.changedFileContext` so an agent can see source-file blast radius before editing or repairing the current diff.
|
|
94
|
+
Assistant rule integration is preview-first: `--assistant-bridge=preview` writes `.decantr/context/assistant-bridge.md`, `decantr rules preview` prints the bridge, and `--assistant-bridge=apply` or `decantr rules apply` mutates supported rule files with idempotent marked blocks. When `.decantr/graph/contract-capsule.json` exists, `decantr task` includes it in the read list and JSON payload so CLI-only agents can load the typed Contract capsule and its SourceArtifact path index before route-specific edits. When `.decantr/graph/graph.snapshot.json` exists, `decantr task --json` also includes `graph.routeContext`: the route node, scoped page/shell/pattern/component/token nodes, local law/style bridge nodes, behavior-obligation LocalRule nodes, open findings/evidence IDs, deterministic ranked nodes boosted by the task text, and the supporting edges. Accepted `.decantr/local-patterns.json` `behavior_obligations` also appear in `localLaw.patterns[].behaviorObligations` and `localLaw.behaviorObligations` so agents see project-owned dialog/form obligations before editing. If git changed files resolve to SourceArtifact nodes, `decantr task --json` also includes `graph.changedFileContext` so an agent can see source-file blast radius before editing or repairing the current diff.
|
|
95
95
|
|
|
96
|
-
`decantr scan` is different from the mutating Brownfield primitives: it is look-don't-touch reconnaissance for fit, route/style evidence, GitHub Pages hints, typed Contract graph readiness, capsule source-handle bounds, and next-command guidance. For attached Essence V4 apps it derives the graph preview in memory, reports stale/missing `.decantr/graph` artifacts, and still writes nothing. `decantr analyze` writes `.decantr/doctrine-map.json`, a ranked source-precedence map across security/data, architecture, design-system, workflow/CI, feature/business, assistant-specific, stale, and unsafe-to-cite evidence. It also writes `.decantr/brownfield-intelligence.json`, `.decantr/theme-inventory.json`, and `.decantr/enrichment-backlog.md`. The proposal groups routes into observed semantic domains such as auth, RBAC, billing, reporting, facilities, settings, and public surfaces across Next App/Pages Router, React Router, Angular Router, SvelteKit, Vue Router, and Nuxt file routes. Existing styling systems such as Tailwind, Bootstrap, MUI, Chakra, plain CSS, and Decantr CSS are observed as evidence instead of replaced. Theme variants are observed in the theme inventory without changing Essence V4. `decantr adopt` writes the first typed Contract graph baseline before verification so Project Health, task context, and agents can anchor to graph artifacts immediately. `decantr codify --from-audit` proposes `.decantr/local-patterns.proposal.json` and `.decantr/rules.proposal.json` with Hybrid authority guidance, source-derived button/card/form/theme evidence, variant hints, and
|
|
96
|
+
`decantr scan` is different from the mutating Brownfield primitives: it is look-don't-touch reconnaissance for fit, route/style evidence, GitHub Pages hints, typed Contract graph readiness, capsule source-handle bounds, and next-command guidance. For attached Essence V4 apps it derives the graph preview in memory, reports stale/missing `.decantr/graph` artifacts, and still writes nothing. `decantr analyze` writes `.decantr/doctrine-map.json`, a ranked source-precedence map across security/data, architecture, design-system, workflow/CI, feature/business, assistant-specific, stale, and unsafe-to-cite evidence. It also writes `.decantr/brownfield-intelligence.json`, `.decantr/theme-inventory.json`, and `.decantr/enrichment-backlog.md`. The proposal groups routes into observed semantic domains such as auth, RBAC, billing, reporting, facilities, settings, and public surfaces across Next App/Pages Router, React Router, Angular Router, SvelteKit, Vue Router, and Nuxt file routes. Existing styling systems such as Tailwind, Bootstrap, MUI, Chakra, plain CSS, and Decantr CSS are observed as evidence instead of replaced. Theme variants are observed in the theme inventory without changing Essence V4. `decantr adopt` writes the first typed Contract graph baseline before verification so Project Health, task context, and agents can anchor to graph artifacts immediately. `decantr codify --from-audit` proposes `.decantr/local-patterns.proposal.json` and `.decantr/rules.proposal.json` with Hybrid authority guidance, source-derived button/card/form/theme evidence, variant hints, confidence tiers, and optional `behavior_obligations` for form controls and destructive confirmation dialogs when source evidence is strong; `decantr codify --style-bridge` proposes `.decantr/style-bridge.proposal.json`, mapping Decantr intent to project-owned tokens/classes without requiring `@decantr/css`; `decantr codify --map-pattern <slug>` maps a hosted or bundled registry pattern into an advisory local-law proposal without changing source. After review, `decantr codify --accept` promotes whichever proposals exist. `decantr doctor` reports whether the app is contract-only, Hybrid local law, style bridge, Decantr CSS, or Hybrid composition. `decantr task` prints that authority block and warns before mixing runtimes or adding Decantr CSS to a non-Decantr-CSS app. `decantr suggest --from-code` surfaces accepted local patterns and style bridge mappings from the app root or selected `--project`, and `decantr ci` prints accepted local-rule findings, behavior-obligation findings, plus style bridge status in text, markdown, and JSON reports. `decantr verify --brownfield --local-patterns` uses the Brownfield guard layer plus accepted local law to flag actionable missing doctrine coverage, unsafe context, missing assistant bridges, behavior-obligation drift, style drift, raw local-rule violations, and unsafe defaults without treating current database migrations as stale docs.
|
|
97
97
|
|
|
98
98
|
## What It Does
|
|
99
99
|
|
|
@@ -102,7 +102,7 @@ Assistant rule integration is preview-first: `--assistant-bridge=preview` writes
|
|
|
102
102
|
- guides users through human workflow commands: setup, adopt, doctor, task, verify, ci, and codify
|
|
103
103
|
- supports explicit workflow lanes: greenfield blueprint, greenfield contract-only, brownfield adoption, Hybrid local law, Hybrid style bridge, Hybrid Decantr CSS, and hybrid composition
|
|
104
104
|
- generates execution-pack context files for AI coding assistants
|
|
105
|
-
- generates typed Contract graph artifacts, replayable snapshot history, graph diffs, manifests, source-file impact context, style-bridge Token nodes, and `contract-capsule.json` for agent sessions
|
|
105
|
+
- generates typed Contract graph artifacts, replayable snapshot history, graph diffs, manifests, source-file impact context, style-bridge Token nodes, behavior-obligation LocalRule nodes, and `contract-capsule.json` for agent sessions
|
|
106
106
|
- audits projects against Decantr contracts
|
|
107
107
|
- produces local Project Health reports, Evidence Bundles, workspace health, and a localhost Studio dashboard for end-user drift triage
|
|
108
108
|
- audits local vocabulary repositories with Content Health reports for schema, reference, and quality coverage
|
|
@@ -181,7 +181,7 @@ decantr showcase verification --json
|
|
|
181
181
|
|
|
182
182
|
`decantr ci` is the blessed non-mutating automation gate. It runs the Project Health surface with adoption-mode-aware local law checks and emits a schema-backed CI report. `decantr ci init` writes root GitHub workflows or portable generic snippets using the detected package manager and pinned local CLI command instead of `@latest`; if the root manifest has not pinned Decantr yet, it prints the exact install command first.
|
|
183
183
|
|
|
184
|
-
`decantr health` remains the advanced project observability primitive. It composes the existing verifier audit, guard checks, brownfield route drift checks, runtime evidence, component reuse drift, accepted style bridge drift, typed Contract graph freshness, and execution-pack files into a `ProjectHealthReport` with a status, score, route summary, pack summary, findings, stable diagnostic codes, typed repair IDs, and AI-ready remediation prompts. The graph freshness slice emits `GRAPH001` / `regenerate-typed-graph` when an attached app has missing, stale, or non-derivable `.decantr/graph` artifacts. The component reuse slice emits `COMP001` / `import-existing-component` when production source locally redeclares a common primitive that already exists as an exported reusable component, and `COMP010` / `replace-raw-control-with-local-component` when production JSX renders raw controls such as `<button>` while the project already owns a reusable primitive. The style bridge slice emits `TOKEN010` / `replace-arbitrary-style-with-bridge-token` when production JSX, common class helpers, hardcoded inline color styles, or hardcoded visual values in CSS/module stylesheets bypass `.decantr/style-bridge.json` after it has been accepted as project-owned style authority. The baseline slice emits `VISUAL010` / `review-visual-baseline-drift` when `--since-baseline` detects changed screenshot hashes. When `.decantr/graph/graph.snapshot.json` exists, each finding is anchored to the most specific graph node Decantr can resolve, and JSON, markdown, text output, repair prompts, and Evidence Bundles carry that anchor. `decantr graph` also writes content-addressed history snapshots under `.decantr/graph/snapshots/` so repeated graph runs can be replayed across an AI edit sequence. When `.decantr/analysis.json` exists, `decantr graph` links observed routes/pages to implementation source artifacts and links exported reusable component declarations to their source files. When browser evidence writes `.decantr/evidence/visual-manifest.json`, `decantr graph` ingests it as local route/page Evidence nodes without uploading screenshots. When `.decantr/evidence/latest.json` exists, `decantr graph` can also materialize saved findings, evidence strings, graph anchors, repair IDs, and referenced repair/read target files as typed graph nodes and edges. When `.decantr/health-baseline-diff.json` exists, baseline changed files become file-level temporal evidence in the graph.
|
|
184
|
+
`decantr health` remains the advanced project observability primitive. It composes the existing verifier audit, guard checks, brownfield route drift checks, runtime evidence, component reuse drift, accepted style bridge drift, accepted behavior-obligation checks, typed Contract graph freshness, and execution-pack files into a `ProjectHealthReport` with a status, score, route summary, pack summary, findings, stable diagnostic codes, typed repair IDs, and AI-ready remediation prompts. The graph freshness slice emits `GRAPH001` / `regenerate-typed-graph` when an attached app has missing, stale, or non-derivable `.decantr/graph` artifacts. The component reuse slice emits `COMP001` / `import-existing-component` when production source locally redeclares a common primitive that already exists as an exported reusable component, and `COMP010` / `replace-raw-control-with-local-component` when production JSX renders raw controls such as `<button>` while the project already owns a reusable primitive. The behavior-obligation slice emits `A11Y010`, `A11Y011`, `INT010`, `INT011`, `INT012`, `INT013`, and `COMP020` for high-confidence dialog/form regressions such as missing accessible names, missing label associations, missing visible destructive consequence copy, missing cancel affordances, missing submitting guards, implicit form button types, or bypassed project-owned interaction primitives. The style bridge slice emits `TOKEN010` / `replace-arbitrary-style-with-bridge-token` when production JSX, common class helpers, hardcoded inline color styles, or hardcoded visual values in CSS/module stylesheets bypass `.decantr/style-bridge.json` after it has been accepted as project-owned style authority. The baseline slice emits `VISUAL010` / `review-visual-baseline-drift` when `--since-baseline` detects changed screenshot hashes. When `.decantr/graph/graph.snapshot.json` exists, each finding is anchored to the most specific graph node Decantr can resolve, and JSON, markdown, text output, repair prompts, and Evidence Bundles carry that anchor. `decantr graph` also writes content-addressed history snapshots under `.decantr/graph/snapshots/` so repeated graph runs can be replayed across an AI edit sequence. When `.decantr/analysis.json` exists, `decantr graph` links observed routes/pages to implementation source artifacts and links exported reusable component declarations to their source files. When browser evidence writes `.decantr/evidence/visual-manifest.json`, `decantr graph` ingests it as local route/page Evidence nodes without uploading screenshots. When `.decantr/evidence/latest.json` exists, `decantr graph` can also materialize saved findings, evidence strings, graph anchors, repair IDs, and referenced repair/read target files as typed graph nodes and edges. When `.decantr/health-baseline-diff.json` exists, baseline changed files become file-level temporal evidence in the graph.
|
|
185
185
|
|
|
186
186
|
```bash
|
|
187
187
|
decantr verify
|
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-GN63PIKK.js";
|
|
3
3
|
import "./chunk-SIDKK73N.js";
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
import "./chunk-B2PJDAMS.js";
|
|
5
|
+
import "./chunk-4NDOHCYY.js";
|
|
6
|
+
import "./chunk-7Z74ETDR.js";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
sendProjectHealthCiFailedTelemetry,
|
|
4
4
|
sendProjectHealthPromptTelemetry,
|
|
5
5
|
sendProjectHealthReportTelemetry
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7Z74ETDR.js";
|
|
7
7
|
|
|
8
8
|
// src/commands/health.ts
|
|
9
9
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -117,9 +117,9 @@ import { createHash } from "crypto";
|
|
|
117
117
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
118
118
|
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4, relative as relative2 } from "path";
|
|
119
119
|
import {
|
|
120
|
+
buildContractCapsuleFromSnapshot,
|
|
120
121
|
buildGraphImpactContext,
|
|
121
122
|
buildGraphRouteContext,
|
|
122
|
-
buildContractCapsuleFromSnapshot,
|
|
123
123
|
buildGraphSnapshotFromEssence,
|
|
124
124
|
diffGraphSnapshots,
|
|
125
125
|
GRAPH_DIFF_SCHEMA_URL,
|
|
@@ -182,6 +182,129 @@ function readLocalPatternPack(projectRoot) {
|
|
|
182
182
|
function readLocalRuleManifest(projectRoot) {
|
|
183
183
|
return readJsonFile(localRulesPath(projectRoot));
|
|
184
184
|
}
|
|
185
|
+
function stringArray(value) {
|
|
186
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
|
|
187
|
+
}
|
|
188
|
+
function localBehaviorSeverity(value) {
|
|
189
|
+
return value === "info" || value === "warn" || value === "error" ? value : null;
|
|
190
|
+
}
|
|
191
|
+
function summarizeLocalPatternBehaviorObligations(pattern) {
|
|
192
|
+
const contract = pattern.behavior_obligations;
|
|
193
|
+
if (!contract || typeof contract !== "object" || Array.isArray(contract)) return null;
|
|
194
|
+
if (!Array.isArray(contract.obligations)) return null;
|
|
195
|
+
const patternId = typeof pattern.id === "string" && pattern.id.trim() ? pattern.id : "unknown";
|
|
196
|
+
const obligations = contract.obligations.map((obligation, index) => {
|
|
197
|
+
if (!obligation || typeof obligation !== "object" || Array.isArray(obligation)) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const id = typeof obligation.id === "string" && obligation.id.trim() ? obligation.id.trim() : `obligation-${index + 1}`;
|
|
201
|
+
const label = typeof obligation.label === "string" && obligation.label.trim() ? obligation.label.trim() : id;
|
|
202
|
+
const evidence = typeof obligation.evidence === "string" && obligation.evidence.trim() ? obligation.evidence.trim() : null;
|
|
203
|
+
return {
|
|
204
|
+
id,
|
|
205
|
+
label,
|
|
206
|
+
severity: localBehaviorSeverity(obligation.severity),
|
|
207
|
+
evidence
|
|
208
|
+
};
|
|
209
|
+
}).filter(
|
|
210
|
+
(entry) => Boolean(entry)
|
|
211
|
+
);
|
|
212
|
+
if (obligations.length === 0) return null;
|
|
213
|
+
return {
|
|
214
|
+
patternId,
|
|
215
|
+
patternRole: typeof contract.pattern_role === "string" ? contract.pattern_role : typeof pattern.role === "string" ? pattern.role : null,
|
|
216
|
+
intent: typeof contract.intent === "string" && contract.intent.trim() ? contract.intent.trim() : null,
|
|
217
|
+
modalities: stringArray(contract.modalities),
|
|
218
|
+
states: stringArray(contract.states),
|
|
219
|
+
riskProfile: stringArray(contract.risk_profile),
|
|
220
|
+
obligations,
|
|
221
|
+
testHints: stringArray(contract.test_hints),
|
|
222
|
+
componentPaths: stringArray(pattern.componentPaths)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function formControlBehaviorObligations(evidence) {
|
|
226
|
+
if (evidence.formComponents.length === 0 && evidence.formSourceEvidence.length === 0) {
|
|
227
|
+
return void 0;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
intent: "Preserve accessible, explicit form behavior when AI edits inputs, labels, validation, or form actions.",
|
|
231
|
+
pattern_role: "form-control",
|
|
232
|
+
modalities: ["keyboard", "pointer", "screen-reader", "touch"],
|
|
233
|
+
states: ["empty", "focused", "valid", "invalid", "submitting", "disabled"],
|
|
234
|
+
risk_profile: ["label-loss", "accidental-submit", "validation-context-loss"],
|
|
235
|
+
obligations: [
|
|
236
|
+
{
|
|
237
|
+
id: "label-associated",
|
|
238
|
+
label: "Inputs, selects, and textareas keep an associated visible or programmatic label.",
|
|
239
|
+
severity: "warn",
|
|
240
|
+
evidence: "static"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: "explicit-form-button-type",
|
|
244
|
+
label: 'Buttons inside forms declare type="button" or type="submit" explicitly.',
|
|
245
|
+
severity: "warn",
|
|
246
|
+
evidence: "static"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "project-form-primitive",
|
|
250
|
+
label: "Use project-owned form/control primitives when they exist.",
|
|
251
|
+
severity: "info",
|
|
252
|
+
evidence: "primitive"
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
test_hints: [
|
|
256
|
+
"keyboard form path smoke test",
|
|
257
|
+
"label association assertion",
|
|
258
|
+
"non-submit action does not submit form"
|
|
259
|
+
]
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function confirmationDialogBehaviorObligations() {
|
|
263
|
+
return {
|
|
264
|
+
intent: "Confirm destructive account or data actions without accidental execution.",
|
|
265
|
+
pattern_role: "confirmation-dialog",
|
|
266
|
+
modalities: ["keyboard", "pointer", "screen-reader", "touch"],
|
|
267
|
+
states: ["closed", "opening", "open", "submitting", "error", "success"],
|
|
268
|
+
risk_profile: ["accidental-destruction", "focus-loss", "screen-reader-context-loss"],
|
|
269
|
+
obligations: [
|
|
270
|
+
{
|
|
271
|
+
id: "project-dialog-primitive",
|
|
272
|
+
label: "Use the project-owned Dialog or AlertDialog primitive instead of one-off overlays.",
|
|
273
|
+
severity: "warn",
|
|
274
|
+
evidence: "primitive"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "accessible-name",
|
|
278
|
+
label: "Dialog has an accessible name.",
|
|
279
|
+
severity: "error",
|
|
280
|
+
evidence: "static"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "visible-consequence",
|
|
284
|
+
label: "Destructive consequence is stated in visible text.",
|
|
285
|
+
severity: "warn",
|
|
286
|
+
evidence: "static"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "cancel-affordance",
|
|
290
|
+
label: "Dialog exposes a clear cancel/close affordance before the destructive action.",
|
|
291
|
+
severity: "warn",
|
|
292
|
+
evidence: "static"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: "submitting-guard",
|
|
296
|
+
label: "Submitting state prevents accidental repeated destructive execution.",
|
|
297
|
+
severity: "info",
|
|
298
|
+
evidence: "static"
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
test_hints: [
|
|
302
|
+
"keyboard interaction smoke test",
|
|
303
|
+
"focus return assertion",
|
|
304
|
+
"destructive action requires explicit confirmation"
|
|
305
|
+
]
|
|
306
|
+
};
|
|
307
|
+
}
|
|
185
308
|
function createBrownfieldCodifyProposal(input) {
|
|
186
309
|
const sourceFiles = input.fromAudit ? listSourceFiles(input.projectRoot, 800) : [];
|
|
187
310
|
const evidence = summarizeSourceEvidence(input.projectRoot, sourceFiles);
|
|
@@ -326,6 +449,7 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
326
449
|
"Start by mapping field wrappers, labels, error states, and disabled states from source."
|
|
327
450
|
]
|
|
328
451
|
},
|
|
452
|
+
behavior_obligations: formControlBehaviorObligations(evidence),
|
|
329
453
|
evidence: evidence.formComponents.length ? evidence.formComponents : ["Add form field wrapper paths and validation examples."],
|
|
330
454
|
forbiddenAlternatives: [
|
|
331
455
|
"Unlabeled one-off inputs or validation states that do not match the app standard."
|
|
@@ -356,7 +480,39 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
356
480
|
forbiddenAlternatives: [
|
|
357
481
|
"Component-local theme forks that bypass shared theme providers or tokens."
|
|
358
482
|
]
|
|
359
|
-
}
|
|
483
|
+
},
|
|
484
|
+
...evidence.dialogComponents.length > 0 || evidence.dialogSourceEvidence.length > 0 ? [
|
|
485
|
+
{
|
|
486
|
+
id: "confirmation-dialog",
|
|
487
|
+
label: "Confirmation dialogs",
|
|
488
|
+
role: "Destructive confirmations, modal decisions, and interruptive dialog actions",
|
|
489
|
+
appliesTo: [
|
|
490
|
+
"destructive confirmations",
|
|
491
|
+
"account deletion",
|
|
492
|
+
"data removal",
|
|
493
|
+
"modal decisions"
|
|
494
|
+
],
|
|
495
|
+
componentPaths: evidence.dialogComponents,
|
|
496
|
+
decide: "Define which Dialog/AlertDialog primitive, destructive Button variant, consequence copy, cancel affordance, and submitting guard the app owns.",
|
|
497
|
+
sourceEvidence: evidence.dialogSourceEvidence,
|
|
498
|
+
confidence: confidenceForEvidence({
|
|
499
|
+
componentCount: evidence.dialogComponents.length,
|
|
500
|
+
sourceEvidenceCount: evidence.dialogSourceEvidence.length
|
|
501
|
+
}),
|
|
502
|
+
enforcement: {
|
|
503
|
+
level: "warn",
|
|
504
|
+
status: "proposal",
|
|
505
|
+
notes: [
|
|
506
|
+
"Only enforce statically checkable obligations. Focus trapping and screen-reader behavior should stay in project tests unless source evidence is explicit."
|
|
507
|
+
]
|
|
508
|
+
},
|
|
509
|
+
behavior_obligations: confirmationDialogBehaviorObligations(),
|
|
510
|
+
evidence: evidence.dialogComponents.length ? evidence.dialogComponents : evidence.dialogSourceEvidence.map((entry) => entry.file),
|
|
511
|
+
forbiddenAlternatives: [
|
|
512
|
+
"One-off overlays for destructive actions without reviewed dialog primitives."
|
|
513
|
+
]
|
|
514
|
+
}
|
|
515
|
+
] : []
|
|
360
516
|
],
|
|
361
517
|
starterRules: [
|
|
362
518
|
"Prefer project-owned wrappers for repeated primitives once they exist.",
|
|
@@ -597,6 +753,29 @@ function validateLocalLaw(projectRoot) {
|
|
|
597
753
|
`Local pattern ${id} maps hosted registry guidance but has no project-owned component path yet.`
|
|
598
754
|
);
|
|
599
755
|
}
|
|
756
|
+
if (pattern.behavior_obligations && (typeof pattern.behavior_obligations !== "object" || Array.isArray(pattern.behavior_obligations))) {
|
|
757
|
+
warnings.push(
|
|
758
|
+
`Local pattern ${id || "unknown"} has behavior_obligations, but it is not an object.`
|
|
759
|
+
);
|
|
760
|
+
} else if (pattern.behavior_obligations && !Array.isArray(pattern.behavior_obligations.obligations)) {
|
|
761
|
+
warnings.push(
|
|
762
|
+
`Local pattern ${id || "unknown"} has behavior_obligations without an obligations array.`
|
|
763
|
+
);
|
|
764
|
+
} else if (pattern.behavior_obligations?.obligations) {
|
|
765
|
+
for (const [index, obligation] of pattern.behavior_obligations.obligations.entries()) {
|
|
766
|
+
if (!obligation || typeof obligation !== "object" || Array.isArray(obligation)) {
|
|
767
|
+
warnings.push(
|
|
768
|
+
`Local pattern ${id || "unknown"} has malformed behavior obligation at index ${index}.`
|
|
769
|
+
);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (typeof obligation.id !== "string" || !obligation.id.trim()) {
|
|
773
|
+
warnings.push(
|
|
774
|
+
`Local pattern ${id || "unknown"} has a behavior obligation missing an id.`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
600
779
|
}
|
|
601
780
|
}
|
|
602
781
|
if (ruleManifest && !Array.isArray(ruleManifest.rules)) {
|
|
@@ -615,14 +794,20 @@ function validateLocalLaw(projectRoot) {
|
|
|
615
794
|
function createLocalLawTaskSummary(projectRoot) {
|
|
616
795
|
const patternPack = readLocalPatternPack(projectRoot);
|
|
617
796
|
const ruleManifest = readLocalRuleManifest(projectRoot);
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
797
|
+
const behaviorObligations = [];
|
|
798
|
+
const patterns = (patternPack?.patterns ?? []).map((pattern) => {
|
|
799
|
+
const behavior = summarizeLocalPatternBehaviorObligations(pattern);
|
|
800
|
+
if (behavior) behaviorObligations.push(behavior);
|
|
801
|
+
return {
|
|
802
|
+
id: typeof pattern.id === "string" ? pattern.id : "unknown",
|
|
803
|
+
role: typeof pattern.role === "string" ? pattern.role : null,
|
|
804
|
+
componentPaths: Array.isArray(pattern.componentPaths) ? pattern.componentPaths.filter((entry) => typeof entry === "string") : [],
|
|
805
|
+
confidenceTier: typeof pattern.confidence === "object" && pattern.confidence !== null && typeof pattern.confidence.tier === "string" ? pattern.confidence.tier : null,
|
|
806
|
+
enforcementLevel: typeof pattern.enforcement === "object" && pattern.enforcement !== null && typeof pattern.enforcement.level === "string" ? pattern.enforcement.level : null,
|
|
807
|
+
hostedPatternRefs: Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs.map((ref) => typeof ref?.slug === "string" ? ref.slug : null).filter((slug) => Boolean(slug)) : [],
|
|
808
|
+
behaviorObligations: behavior
|
|
809
|
+
};
|
|
810
|
+
});
|
|
626
811
|
const rules = (ruleManifest?.rules ?? []).map((rule) => ({
|
|
627
812
|
id: rule.id,
|
|
628
813
|
severity: rule.severity,
|
|
@@ -635,6 +820,7 @@ function createLocalLawTaskSummary(projectRoot) {
|
|
|
635
820
|
patternCount: patterns.length,
|
|
636
821
|
ruleCount: rules.length,
|
|
637
822
|
patterns,
|
|
823
|
+
behaviorObligations,
|
|
638
824
|
rules
|
|
639
825
|
};
|
|
640
826
|
}
|
|
@@ -732,6 +918,7 @@ function summarizeSourceEvidence(projectRoot, files) {
|
|
|
732
918
|
const componentPaths = files.filter((file) => /(^|[/\\])components?([/\\]|$)|(^|[/\\])ui([/\\]|$)/i.test(file.relative)).map((file) => file.relative);
|
|
733
919
|
const byName = (terms) => componentPaths.filter((file) => terms.some((term) => basename(file).toLowerCase().includes(term))).slice(0, 12);
|
|
734
920
|
const themeComponents = componentPaths.filter((file) => /theme|provider|mode|appearance|tenant|brand/i.test(file)).slice(0, 12);
|
|
921
|
+
const dialogComponents = componentPaths.filter((file) => /dialog|modal|alert|confirm|sheet|drawer/i.test(file)).slice(0, 12);
|
|
735
922
|
const shellComponents = files.filter((file) => /layout|shell|frame|app|root|nav|sidebar/i.test(basename(file.relative))).map((file) => file.relative).slice(0, 12);
|
|
736
923
|
return {
|
|
737
924
|
buttonComponents: byName(["button", "action"]),
|
|
@@ -776,6 +963,19 @@ function summarizeSourceEvidence(projectRoot, files) {
|
|
|
776
963
|
"field",
|
|
777
964
|
"select"
|
|
778
965
|
]),
|
|
966
|
+
dialogComponents,
|
|
967
|
+
dialogSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
968
|
+
"dialog",
|
|
969
|
+
"alertdialog",
|
|
970
|
+
"modal",
|
|
971
|
+
"confirm",
|
|
972
|
+
"confirmation",
|
|
973
|
+
"delete",
|
|
974
|
+
"remove",
|
|
975
|
+
"destructive",
|
|
976
|
+
"irreversible",
|
|
977
|
+
"cannot be undone"
|
|
978
|
+
]),
|
|
779
979
|
themeSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
780
980
|
"theme",
|
|
781
981
|
"dark",
|
|
@@ -988,13 +1188,13 @@ function readStyleBridge(projectRoot) {
|
|
|
988
1188
|
function readStyleBridgeProposal(projectRoot) {
|
|
989
1189
|
return readJsonFile2(styleBridgeProposalPath(projectRoot));
|
|
990
1190
|
}
|
|
991
|
-
function
|
|
1191
|
+
function stringArray2(value) {
|
|
992
1192
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
993
1193
|
}
|
|
994
1194
|
function readThemeInventory(projectRoot) {
|
|
995
1195
|
const inventory = readJsonFile2(join3(projectRoot, ".decantr", "theme-inventory.json"));
|
|
996
1196
|
return {
|
|
997
|
-
modes:
|
|
1197
|
+
modes: stringArray2(inventory?.modes),
|
|
998
1198
|
variantIds: Array.isArray(inventory?.variants) ? inventory.variants.map((variant) => variant.id).filter((id) => typeof id === "string") : [],
|
|
999
1199
|
darkModeDetected: typeof inventory?.darkModeDetected === "boolean" ? inventory.darkModeDetected : null
|
|
1000
1200
|
};
|
|
@@ -1003,9 +1203,7 @@ function tokenHints(styling, terms) {
|
|
|
1003
1203
|
return styling.cssVariables.filter((name) => terms.test(name)).slice(0, 12);
|
|
1004
1204
|
}
|
|
1005
1205
|
function readProjectPatternPack(projectRoot) {
|
|
1006
|
-
return readLocalPatternPack(projectRoot) ?? readJsonFile2(
|
|
1007
|
-
join3(projectRoot, ".decantr", "local-patterns.proposal.json")
|
|
1008
|
-
);
|
|
1206
|
+
return readLocalPatternPack(projectRoot) ?? readJsonFile2(join3(projectRoot, ".decantr", "local-patterns.proposal.json"));
|
|
1009
1207
|
}
|
|
1010
1208
|
function classHintsForPattern(projectRoot, ids) {
|
|
1011
1209
|
const pack = readProjectPatternPack(projectRoot);
|
|
@@ -1029,7 +1227,9 @@ function sourceEvidence(projectRoot, ids) {
|
|
|
1029
1227
|
function colorTokenNames(styling) {
|
|
1030
1228
|
const names = new Set(Object.keys(styling.colors ?? {}));
|
|
1031
1229
|
for (const variable of styling.cssVariables) {
|
|
1032
|
-
if (/color|bg|background|surface|text|foreground|border|primary|secondary|accent|muted/i.test(
|
|
1230
|
+
if (/color|bg|background|surface|text|foreground|border|primary|secondary|accent|muted/i.test(
|
|
1231
|
+
variable
|
|
1232
|
+
)) {
|
|
1033
1233
|
names.add(variable);
|
|
1034
1234
|
}
|
|
1035
1235
|
}
|
|
@@ -1038,7 +1238,9 @@ function colorTokenNames(styling) {
|
|
|
1038
1238
|
function createStyleBridgeProposal(input) {
|
|
1039
1239
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1040
1240
|
const theme = readThemeInventory(input.projectRoot);
|
|
1041
|
-
const routeCount = input.essence && typeof input.essence === "object" && "blueprint" in input.essence ? Object.keys(
|
|
1241
|
+
const routeCount = input.essence && typeof input.essence === "object" && "blueprint" in input.essence ? Object.keys(
|
|
1242
|
+
input.essence.blueprint?.routes ?? {}
|
|
1243
|
+
).length : 0;
|
|
1042
1244
|
const target = input.essence && typeof input.essence === "object" && "meta" in input.essence ? String(input.essence.meta?.target ?? "") || null : null;
|
|
1043
1245
|
const darkModeDetected = theme.darkModeDetected ?? input.styling.darkMode;
|
|
1044
1246
|
const themeModes = theme.modes.length > 0 ? theme.modes : darkModeDetected ? ["base", "dark"] : ["base"];
|
|
@@ -1084,7 +1286,10 @@ function createStyleBridgeProposal(input) {
|
|
|
1084
1286
|
label: "Surfaces and cards",
|
|
1085
1287
|
decantrIntent: "surface background, card treatment, border, radius, depth, and hover state",
|
|
1086
1288
|
projectAuthority: "Use the app card/surface primitives, tokens, and accepted local law.",
|
|
1087
|
-
tokenHints: tokenHints(
|
|
1289
|
+
tokenHints: tokenHints(
|
|
1290
|
+
input.styling,
|
|
1291
|
+
/surface|card|panel|bg|background|border|shadow|radius/i
|
|
1292
|
+
),
|
|
1088
1293
|
classHints: classHintsForPattern(input.projectRoot, ["surface-card"]),
|
|
1089
1294
|
sourceEvidence: sourceEvidence(input.projectRoot, ["surface-card"]),
|
|
1090
1295
|
guardrails: [
|
|
@@ -1097,7 +1302,10 @@ function createStyleBridgeProposal(input) {
|
|
|
1097
1302
|
label: "Actions and buttons",
|
|
1098
1303
|
decantrIntent: "primary, secondary, tertiary, destructive, icon-only, loading, and disabled action states",
|
|
1099
1304
|
projectAuthority: "Use the app button/action primitives and local variant names.",
|
|
1100
|
-
tokenHints: tokenHints(
|
|
1305
|
+
tokenHints: tokenHints(
|
|
1306
|
+
input.styling,
|
|
1307
|
+
/primary|secondary|accent|danger|error|destructive|focus/i
|
|
1308
|
+
),
|
|
1101
1309
|
classHints: classHintsForPattern(input.projectRoot, ["button"]),
|
|
1102
1310
|
sourceEvidence: sourceEvidence(input.projectRoot, ["button"]),
|
|
1103
1311
|
guardrails: [
|
|
@@ -1209,7 +1417,9 @@ function styleBridgeMatches(projectRoot, query) {
|
|
|
1209
1417
|
if (!projectRoot) return [];
|
|
1210
1418
|
const bridge = readStyleBridge(projectRoot);
|
|
1211
1419
|
if (!bridge) return [];
|
|
1212
|
-
const terms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1).flatMap(
|
|
1420
|
+
const terms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1).flatMap(
|
|
1421
|
+
(term) => term.endsWith("s") && term.length > 3 ? [term, term.slice(0, -1)] : [term]
|
|
1422
|
+
);
|
|
1213
1423
|
if (terms.length === 0) return [];
|
|
1214
1424
|
return bridge.mappings.map((mapping) => {
|
|
1215
1425
|
const haystack = [
|
|
@@ -1594,6 +1804,15 @@ function sourceArtifacts(projectRoot, componentReuseAudit = null) {
|
|
|
1594
1804
|
hash: hashFile(rulesPath)
|
|
1595
1805
|
});
|
|
1596
1806
|
}
|
|
1807
|
+
const patternsPath = localPatternsPath(projectRoot);
|
|
1808
|
+
if (existsSync4(patternsPath)) {
|
|
1809
|
+
sources.push({
|
|
1810
|
+
id: "src:.decantr/local-patterns.json",
|
|
1811
|
+
kind: "local-pattern-manifest",
|
|
1812
|
+
path: ".decantr/local-patterns.json",
|
|
1813
|
+
hash: hashFile(patternsPath)
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1597
1816
|
const bridgePath = styleBridgePath(projectRoot);
|
|
1598
1817
|
if (existsSync4(bridgePath)) {
|
|
1599
1818
|
sources.push({
|
|
@@ -1673,7 +1892,11 @@ function sourceArtifacts(projectRoot, componentReuseAudit = null) {
|
|
|
1673
1892
|
}
|
|
1674
1893
|
});
|
|
1675
1894
|
}
|
|
1676
|
-
const importedFile = resolveImportSourcePath(
|
|
1895
|
+
const importedFile = resolveImportSourcePath(
|
|
1896
|
+
projectRoot,
|
|
1897
|
+
importingFile,
|
|
1898
|
+
importReference.source
|
|
1899
|
+
);
|
|
1677
1900
|
if (!importedFile) continue;
|
|
1678
1901
|
const importedPath = join4(projectRoot, importedFile);
|
|
1679
1902
|
if (!existsSync4(importedPath)) continue;
|
|
@@ -1865,6 +2088,63 @@ function augmentProjectGraph(snapshot, projectRoot, sources, componentReuseAudit
|
|
|
1865
2088
|
});
|
|
1866
2089
|
}
|
|
1867
2090
|
}
|
|
2091
|
+
const localPatterns = readLocalPatternPack(projectRoot);
|
|
2092
|
+
if (localPatterns) {
|
|
2093
|
+
const sourceId = "src:.decantr/local-patterns.json";
|
|
2094
|
+
for (const pattern of localPatterns.patterns ?? []) {
|
|
2095
|
+
const behavior = summarizeLocalPatternBehaviorObligations(pattern);
|
|
2096
|
+
if (!behavior) continue;
|
|
2097
|
+
const patternNodeId = `pat:${graphSlug(behavior.patternId, "pattern")}`;
|
|
2098
|
+
for (const obligation of behavior.obligations) {
|
|
2099
|
+
const ruleId = `rule:behavior:${graphSlug(behavior.patternId, "pattern")}:${graphSlug(
|
|
2100
|
+
obligation.id,
|
|
2101
|
+
"obligation"
|
|
2102
|
+
)}`;
|
|
2103
|
+
addNode(nodes, {
|
|
2104
|
+
id: ruleId,
|
|
2105
|
+
type: "LocalRule",
|
|
2106
|
+
payload: {
|
|
2107
|
+
id: ruleId.replace(/^rule:/, ""),
|
|
2108
|
+
kind: "behavior-obligation",
|
|
2109
|
+
patternId: behavior.patternId,
|
|
2110
|
+
patternRole: behavior.patternRole,
|
|
2111
|
+
intent: behavior.intent,
|
|
2112
|
+
obligationId: obligation.id,
|
|
2113
|
+
label: obligation.label,
|
|
2114
|
+
severity: obligation.severity,
|
|
2115
|
+
evidence: obligation.evidence,
|
|
2116
|
+
modalities: behavior.modalities,
|
|
2117
|
+
states: behavior.states,
|
|
2118
|
+
riskProfile: behavior.riskProfile,
|
|
2119
|
+
testHints: behavior.testHints,
|
|
2120
|
+
componentPaths: behavior.componentPaths,
|
|
2121
|
+
manifest: {
|
|
2122
|
+
status: localPatterns.status,
|
|
2123
|
+
source: localPatterns.source,
|
|
2124
|
+
acceptedAt: localPatterns.acceptedAt
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
2128
|
+
addEdge(edges, {
|
|
2129
|
+
src: ruleId,
|
|
2130
|
+
dst: snapshot.project_id,
|
|
2131
|
+
relation: "LOCAL_RULE_APPLIES_TO"
|
|
2132
|
+
});
|
|
2133
|
+
if (nodes.has(patternNodeId)) {
|
|
2134
|
+
addEdge(edges, {
|
|
2135
|
+
src: ruleId,
|
|
2136
|
+
dst: patternNodeId,
|
|
2137
|
+
relation: "LOCAL_RULE_APPLIES_TO"
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
addEdge(edges, {
|
|
2141
|
+
src: ruleId,
|
|
2142
|
+
dst: sourceId,
|
|
2143
|
+
relation: "NODE_DERIVED_FROM_SOURCE"
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
1868
2148
|
const styleBridge = readStyleBridge(projectRoot);
|
|
1869
2149
|
if (styleBridge) {
|
|
1870
2150
|
const sourceId = "src:.decantr/style-bridge.json";
|
|
@@ -1998,7 +2278,11 @@ function augmentProjectGraph(snapshot, projectRoot, sources, componentReuseAudit
|
|
|
1998
2278
|
for (const importReference of componentReuseAudit?.imports ?? []) {
|
|
1999
2279
|
const importingFile = projectRelativePath(projectRoot, importReference.file);
|
|
2000
2280
|
if (!importingFile || !nodes.has(`src:${importingFile}`)) continue;
|
|
2001
|
-
const importedFile = resolveImportSourcePath(
|
|
2281
|
+
const importedFile = resolveImportSourcePath(
|
|
2282
|
+
projectRoot,
|
|
2283
|
+
importingFile,
|
|
2284
|
+
importReference.source
|
|
2285
|
+
);
|
|
2002
2286
|
if (!importedFile || !nodes.has(`src:${importedFile}`)) continue;
|
|
2003
2287
|
addEdge(edges, {
|
|
2004
2288
|
src: `src:${importingFile}`,
|
|
@@ -2463,7 +2747,10 @@ function summaryPayload(artifacts, routeContext, impactContext, selected, compar
|
|
|
2463
2747
|
evidence: selected.snapshot.summary.evidence
|
|
2464
2748
|
} : void 0,
|
|
2465
2749
|
comparison,
|
|
2466
|
-
routeContext: routeContextPayload(
|
|
2750
|
+
routeContext: routeContextPayload(
|
|
2751
|
+
routeContext ?? null,
|
|
2752
|
+
routeContext ? optionsRoute(routeContext) : void 0
|
|
2753
|
+
),
|
|
2467
2754
|
impactContext: impactContextPayload(impactContext ?? null, impactSelection)
|
|
2468
2755
|
};
|
|
2469
2756
|
}
|
|
@@ -3008,7 +3295,7 @@ function sourceFromAuditFinding(finding) {
|
|
|
3008
3295
|
if (category.includes("pack") || category.includes("review contract")) {
|
|
3009
3296
|
return "pack";
|
|
3010
3297
|
}
|
|
3011
|
-
if (category.includes("interaction") || id.includes("interaction") || rule.includes("interaction")) {
|
|
3298
|
+
if (category.includes("interaction") || category.includes("behavior") || id.includes("interaction") || id.includes("behavior") || rule.includes("interaction")) {
|
|
3012
3299
|
return "interaction";
|
|
3013
3300
|
}
|
|
3014
3301
|
if (category.includes("style bridge") || id.includes("style-bridge") || rule.includes("style-bridge")) {
|
|
@@ -3030,6 +3317,7 @@ function normalizeHealthCategory(category, source) {
|
|
|
3030
3317
|
if (source === "design-token" || lower.includes("design-token")) return "Design Token";
|
|
3031
3318
|
if (source === "style-bridge" || lower.includes("style bridge")) return "Style Bridge";
|
|
3032
3319
|
if (source === "graph") return "Typed Contract Graph";
|
|
3320
|
+
if (lower.includes("behavior obligation")) return "Behavior Obligation";
|
|
3033
3321
|
if (lower.includes("accessibility")) return "Accessibility";
|
|
3034
3322
|
if (source === "runtime") return "Runtime";
|
|
3035
3323
|
if (source === "browser") return "Visual Evidence";
|
|
@@ -1559,7 +1559,10 @@ async function captureCliTelemetryEvent(input) {
|
|
|
1559
1559
|
}
|
|
1560
1560
|
}
|
|
1561
1561
|
async function sendCliCommandTelemetry(input) {
|
|
1562
|
-
const projectRoot = resolveCliTelemetryProjectRoot(
|
|
1562
|
+
const projectRoot = resolveCliTelemetryProjectRoot(
|
|
1563
|
+
input.projectRoot ?? process.cwd(),
|
|
1564
|
+
input.args
|
|
1565
|
+
);
|
|
1563
1566
|
const command = normalizeCommand(input.args[0]);
|
|
1564
1567
|
if (!isOptedIn(projectRoot) || !command || command === "help" || command === "version" || isHelpOrVersionProbe(input.args)) {
|
|
1565
1568
|
return;
|
|
@@ -2157,11 +2160,11 @@ ${CYAN}Telemetry enabled.${RESET} Decantr will send privacy-filtered CLI product
|
|
|
2157
2160
|
}
|
|
2158
2161
|
|
|
2159
2162
|
export {
|
|
2163
|
+
scanStyling,
|
|
2160
2164
|
loadBundledContentItem,
|
|
2161
2165
|
loadBundledContentList,
|
|
2162
2166
|
scanAmbientContext,
|
|
2163
2167
|
scanRoutes,
|
|
2164
|
-
scanStyling,
|
|
2165
2168
|
createDoctrineMap,
|
|
2166
2169
|
writeDoctrineMap,
|
|
2167
2170
|
isOptedIn,
|