@adia-ai/adia-ui-factory 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.claude-plugin/plugin.json +12 -3
  2. package/.mcp.json +1 -1
  3. package/CHANGELOG.md +12 -0
  4. package/README.md +24 -1
  5. package/bin/adia-scaffold +93 -13
  6. package/package.json +1 -1
  7. package/references/shell-embed.md +10 -0
  8. package/references/spa-architecture.md +7 -0
  9. package/skills/adia-ui-kit/CHANGELOG.md +41 -0
  10. package/skills/adia-ui-kit/SKILL.md +442 -0
  11. package/skills/adia-ui-kit/assets/figma-make/guidelines/Guidelines.md +87 -0
  12. package/skills/adia-ui-kit/assets/figma-make/guidelines/components.md +142 -0
  13. package/skills/adia-ui-kit/assets/figma-make/guidelines/styles.md +118 -0
  14. package/skills/adia-ui-kit/assets/figma-make/guidelines/tokens.md +120 -0
  15. package/skills/adia-ui-kit/assets/team-scaffold/archive-README.md +30 -0
  16. package/skills/adia-ui-kit/assets/team-scaffold/drafts-README.md +57 -0
  17. package/skills/adia-ui-kit/assets/team-scaffold/inbox-README.md +76 -0
  18. package/skills/adia-ui-kit/assets/team-scaffold/outbox-README.md +46 -0
  19. package/skills/adia-ui-kit/assets/team-scaffold/team-root.README.md +51 -0
  20. package/skills/adia-ui-kit/assets/team-scaffold/tickets-README.md +208 -0
  21. package/skills/adia-ui-kit/assets/team-scaffold/tickets-REGISTRY.md +81 -0
  22. package/skills/adia-ui-kit/assets/templates/AGENTS.template.md +128 -0
  23. package/skills/adia-ui-kit/assets/templates/CLAUDE.template.md +1 -0
  24. package/skills/adia-ui-kit/assets/templates/FEEDBACK-OMISSIONS-DEFENSE.template.md +150 -0
  25. package/skills/adia-ui-kit/assets/templates/FEEDBACK.template.md +213 -0
  26. package/skills/adia-ui-kit/assets/templates/RESPONSE.template.md +114 -0
  27. package/skills/adia-ui-kit/assets/templates/figma-make-guidelines.md +90 -0
  28. package/skills/adia-ui-kit/evals/adversarial-content-trust.json +83 -0
  29. package/skills/adia-ui-kit/evals/evals.json +55 -0
  30. package/skills/adia-ui-kit/evals/routing-corpus.json +121 -0
  31. package/skills/adia-ui-kit/evals/teach-routing-cases.json +92 -0
  32. package/skills/adia-ui-kit/references/capabilities.md +245 -0
  33. package/skills/adia-ui-kit/references/catalog-lookup.md +186 -0
  34. package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v1.md +8 -0
  35. package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v2.md +3585 -0
  36. package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v3.md +1899 -0
  37. package/skills/adia-ui-kit/references/common-gotchas-consumer.md +522 -0
  38. package/skills/adia-ui-kit/references/component-literacy.md +291 -0
  39. package/skills/adia-ui-kit/references/composition.md +245 -0
  40. package/skills/adia-ui-kit/references/correction-loop.md +462 -0
  41. package/skills/adia-ui-kit/references/css-policy.md +343 -0
  42. package/skills/adia-ui-kit/references/decision-model.md +158 -0
  43. package/skills/adia-ui-kit/references/diagnostic-output.md +57 -0
  44. package/skills/adia-ui-kit/references/docs-catalog.md +239 -0
  45. package/skills/adia-ui-kit/references/exclusions.md +225 -0
  46. package/skills/adia-ui-kit/references/feedback-authoring.md +466 -0
  47. package/skills/adia-ui-kit/references/figma-make.md +244 -0
  48. package/skills/adia-ui-kit/references/flows-auth.md +307 -0
  49. package/skills/adia-ui-kit/references/flows-content.md +206 -0
  50. package/skills/adia-ui-kit/references/flows-data.md +348 -0
  51. package/skills/adia-ui-kit/references/flows-interaction.md +181 -0
  52. package/skills/adia-ui-kit/references/flows-shells.md +336 -0
  53. package/skills/adia-ui-kit/references/gap-detection.md +266 -0
  54. package/skills/adia-ui-kit/references/harness.md +284 -0
  55. package/skills/adia-ui-kit/references/loading-protocol.md +406 -0
  56. package/skills/adia-ui-kit/references/local-link.md +198 -0
  57. package/skills/adia-ui-kit/references/mcp.md +401 -0
  58. package/skills/adia-ui-kit/references/migration.md +116 -0
  59. package/skills/adia-ui-kit/references/mission.md +194 -0
  60. package/skills/adia-ui-kit/references/mode-routing.md +298 -0
  61. package/skills/adia-ui-kit/references/patterns-agent.md +223 -0
  62. package/skills/adia-ui-kit/references/patterns-data.md +1065 -0
  63. package/skills/adia-ui-kit/references/patterns-forms.md +223 -0
  64. package/skills/adia-ui-kit/references/patterns-layout.md +326 -0
  65. package/skills/adia-ui-kit/references/patterns-recipes.md +1416 -0
  66. package/skills/adia-ui-kit/references/patterns-shells.md +1097 -0
  67. package/skills/adia-ui-kit/references/patterns-traits.md +259 -0
  68. package/skills/adia-ui-kit/references/patterns-wiring.md +339 -0
  69. package/skills/adia-ui-kit/references/pev-rationale.md +137 -0
  70. package/skills/adia-ui-kit/references/reasoning-gate.md +146 -0
  71. package/skills/adia-ui-kit/references/reasoning-methodologies.md +112 -0
  72. package/skills/adia-ui-kit/references/recon.md +192 -0
  73. package/skills/adia-ui-kit/references/rendering-model.md +431 -0
  74. package/skills/adia-ui-kit/references/response-authoring.md +398 -0
  75. package/skills/adia-ui-kit/references/session-log-schema.md +203 -0
  76. package/skills/adia-ui-kit/references/session-log-template.md +118 -0
  77. package/skills/adia-ui-kit/references/setup.md +652 -0
  78. package/skills/adia-ui-kit/references/skill-hygiene.md +135 -0
  79. package/skills/adia-ui-kit/references/spec-to-ui-reasoning.md +394 -0
  80. package/skills/adia-ui-kit/references/state.md +229 -0
  81. package/skills/adia-ui-kit/references/teach-protocol.md +421 -0
  82. package/skills/adia-ui-kit/references/theme.md +142 -0
  83. package/skills/adia-ui-kit/references/version-check.md +69 -0
  84. package/skills/adia-ui-kit/scripts/audit-decision-trace.mjs +198 -0
  85. package/skills/adia-ui-kit/scripts/audit-kit-roster.mjs +366 -0
  86. package/skills/adia-ui-kit/scripts/changelog-archive.mjs +126 -0
  87. package/skills/adia-ui-kit/scripts/check-adia-versions.py +404 -0
  88. package/skills/adia-ui-kit/scripts/run-skill-evals.mjs +358 -0
  89. package/skills/adia-ui-kit/scripts/validate-session-log.mjs +242 -0
  90. package/skills/adia-ui-kit/skill.json +102 -0
  91. package/skills/adia-ui-migration/CHANGELOG.md +47 -0
  92. package/skills/adia-ui-migration/SKILL.md +381 -0
  93. package/skills/adia-ui-migration/skill.json +29 -0
  94. package/skills/adia-ui-spa/SKILL.md +1 -1
  95. package/skills/adia-ui-verify/SKILL.md +2 -2
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: theme
3
+ load-when: When customizing theme — color schemes, density, brand colors, custom themes, toggle-scheme, Cmd+K command palette
4
+ load-size-tokens: ~1,200
5
+ required-for:
6
+ - theme customization
7
+ - brand integration
8
+ ---
9
+
10
+ # §Theme — Color scheme, density, custom themes
11
+
12
+ AdiaUI ships a three-knob theming contract. All themes are CSS-only,
13
+ no runtime JS required.
14
+
15
+ ### Three knobs
16
+
17
+ | Knob | What it controls | How to set |
18
+ |---|---|---|
19
+ | **Named theme** | Token palette (`[theme="ocean"]` swaps `--a-*` values) | `<html theme="ocean">` |
20
+ | **Color scheme** | Light / dark within a theme | `<html style="color-scheme: light">` or use `<toggle-scheme-ui>` |
21
+ | **Density / radius** | `--a-density` (0–1), `--a-radius-k` (multiplier) | inline `style` or `[data-density="cozy"]` |
22
+
23
+ Eight named themes ship by default (`default`, `ocean`, `forest`, `sunset`,
24
+ `graphite`, `paper`, `ink`, `neon`). Add `theme="<slug>"` to `<html>` or
25
+ any ancestor element to apply.
26
+
27
+ ### Add a scheme toggle (light/dark)
28
+
29
+ ```html
30
+ <!-- One-line scheme toggle, persists to localStorage -->
31
+ <toggle-scheme-ui scheme="auto"></toggle-scheme-ui>
32
+ ```
33
+
34
+ > **Placement.** Put `<toggle-scheme-ui>` in the shell topbar's trailing
35
+ > `slot="action"` cluster — inside `<admin-topbar slot="header">` of
36
+ > `<admin-content>`. That is the persistent top-right home for app-wide
37
+ > preference controls. Do **not** put it in the sidebar footer: a global
38
+ > preference buried there reads as navigation and vanishes when the
39
+ > sidebar collapses.
40
+
41
+ The component:
42
+ - Reads `prefers-color-scheme` for `auto`
43
+ - Writes the resolved scheme to `<html style="color-scheme: ...">`
44
+ - Persists to `localStorage` under the `adia-theme-` namespace
45
+ - Swaps moon/sun icon automatically
46
+
47
+ Attributes: `scheme="auto|light|dark"`, `target="<selector>"` (default `:root`).
48
+
49
+ ```js
50
+ // Listen for scheme changes
51
+ document.querySelector('toggle-scheme-ui').addEventListener('scheme-change', e => {
52
+ console.log('new scheme:', e.detail.scheme); // 'light' | 'dark'
53
+ });
54
+ ```
55
+
56
+ ### Full theme panel (settings page)
57
+
58
+ `<theme-panel>` exposes all three knobs in one composition. Drop into a
59
+ settings section or popover.
60
+
61
+ ```html
62
+ <popover-ui>
63
+ <button-ui slot="trigger" icon="palette" variant="ghost"></button-ui>
64
+ <theme-panel slot="content"
65
+ parametric
66
+ presets
67
+ scheme-toggle>
68
+ </theme-panel>
69
+ </popover-ui>
70
+ ```
71
+
72
+ Imports (not in barrel — composes 5 primitives):
73
+
74
+ ```js
75
+ import '@adia-ai/web-components/components/toggle-scheme/toggle-scheme.js';
76
+ import '@adia-ai/web-modules/theme/theme-panel/theme-panel.js';
77
+ ```
78
+
79
+ ### Brand-color overrides
80
+
81
+ To override the brand palette without a full custom theme, write a small
82
+ CSS block that targets the semantic tokens:
83
+
84
+ ```css
85
+ /* main.css — after `@adia-ai/web-components/css` */
86
+ :root {
87
+ --a-primary-bg: #3b82f6; /* your brand blue */
88
+ --a-primary-fg: white;
89
+ --a-primary-strong: #1d4ed8;
90
+ --a-accent-bg: #ec4899; /* your accent pink */
91
+ --a-accent-fg: white;
92
+ }
93
+
94
+ /* Dark scheme override */
95
+ :root[style*="color-scheme: dark"] {
96
+ --a-primary-bg: #60a5fa;
97
+ --a-primary-strong: #93c5fd;
98
+ }
99
+ ```
100
+
101
+ Only override semantic tokens (`--a-*-bg`, `--a-*-fg`, `--a-*-strong`).
102
+ Never override component-internal tokens (`--button-ui-padding`, etc.) —
103
+ those are implementation details and will break with version bumps.
104
+
105
+ ### Custom theme (`[theme="my-brand"]`)
106
+
107
+ For a full custom palette across many tokens, define a theme block:
108
+
109
+ ```css
110
+ /* themes/my-brand.css */
111
+ [theme="my-brand"] {
112
+ /* Surface */
113
+ --a-bg: hsl(220 20% 99%);
114
+ --a-bg-muted: hsl(220 20% 96%);
115
+ --a-bg-strong: hsl(220 20% 92%);
116
+ --a-fg: hsl(220 30% 12%);
117
+ --a-fg-subtle: hsl(220 15% 45%);
118
+
119
+ /* Chrome / borders */
120
+ --a-chrome-strong: hsl(220 15% 80%);
121
+ --a-chrome-bg: hsl(220 15% 90%);
122
+
123
+ /* Primary / accent / danger / success / warning / info */
124
+ --a-primary-bg: hsl(252 100% 60%);
125
+ --a-primary-fg: white;
126
+ --a-accent-bg: hsl(340 80% 55%);
127
+ /* ... */
128
+ }
129
+
130
+ [theme="my-brand"][style*="color-scheme: dark"] {
131
+ --a-bg: hsl(220 30% 8%);
132
+ /* ... */
133
+ }
134
+ ```
135
+
136
+ Apply with `<html theme="my-brand">`.
137
+
138
+ > **Token reference:** see `node_modules/@adia-ai/web-components/styles/tokens.css`
139
+ > for the full semantic-token surface. Roughly 80 tokens across surfaces,
140
+ > chrome, semantic colors, spacing, radius, and motion.
141
+
142
+ ---
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: version-check
3
+ load-when: Before any authoring — verifies that the installed `@adia-ai/*` version matches the kit's targeted shape (current lockstep is v0.6.33)
4
+ load-size-tokens: ~750
5
+ required-for:
6
+ - cold-start version sanity
7
+ - pre-author bundle gate companion
8
+ ---
9
+
10
+ # §VersionCheck — Verify the install matches what the skill describes
11
+
12
+ **Why this gate exists.** The 9 `@adia-ai/*` packages ship in lockstep,
13
+ and skill guidance is version-sensitive. Before trusting a stale install,
14
+ run:
15
+
16
+ ```bash
17
+ scripts/check-adia-versions.py
18
+ ```
19
+
20
+ (Path relative to the consumer project that has this skill installed.
21
+ If running from another consumer, pass `--root /path/to/consumer`.)
22
+
23
+ The script reports three sources side-by-side: **installed**
24
+ (node_modules), **declared** (package.json), **latest** (npm registry).
25
+ It classifies drift and exits non-zero when something needs attention.
26
+
27
+ **Exit codes**
28
+
29
+ - `0` — install is clean: lockstep aligned, declarations match installs,
30
+ and installed ≥ latest published (or you used `--offline`).
31
+ - `1` — drift detected. The text output names every package + suggests
32
+ the fix (`npm install @adia-ai/X@latest`, `npm install`, or wipe +
33
+ reinstall to repair split lockstep).
34
+ - `2` — unrecoverable (no `package.json` or no `node_modules` at root).
35
+
36
+ **Status legend**
37
+
38
+ | Marker | Meaning | Fix |
39
+ |---|---|---|
40
+ | ✓ ok | installed == declared, matches latest | — |
41
+ | ⚠ stale | installed < latest published | `npm install @adia-ai/<pkg>@latest` |
42
+ | ⚠ split | lockstep broken — packages disagree on version | wipe `node_modules` + lockfile, reinstall |
43
+ | ⚠ undeclared | user-facing package installed but not in package.json | add to `dependencies` |
44
+ | ⚠ uninstalled | declared but missing from node_modules | `npm install` |
45
+ | ⓘ ahead | installed > latest published (dev cut / local tarball) | informational, not an error |
46
+
47
+ **Flags**
48
+
49
+ - `--offline` — skip `npm view` (no network). Still verifies lockstep
50
+ + declared vs. installed; just can't tell you about staleness.
51
+ - `--json` — machine-readable for agent harnesses.
52
+ - `--root <path>` — explicit consumer root (default: cwd).
53
+
54
+ **When to run it**
55
+
56
+ - Once at the start of any new project work (cold-start gate).
57
+ - After any `npm install` or `npm update` involving `@adia-ai/*`.
58
+ - Before filing a feedback ticket — version coherence is required
59
+ evidence (see §FeedbackDiscipline principle 4 "verify before
60
+ claiming").
61
+ - When a piece of skill guidance contradicts your install (the script
62
+ tells you which side is stale).
63
+
64
+ **Monorepo-root behavior.** The script auto-detects the `@adia-ai/*`
65
+ source monorepo via the `workspaces` field and suppresses "undeclared"
66
+ warnings there. The "ⓘ ahead" status is expected in the monorepo when
67
+ sources are bumped but not yet published.
68
+
69
+ ---
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * audit-decision-trace.mjs (adia-ui-kit v3.1.0+)
4
+ *
5
+ * Walks a JSONL session log (per references/session-log-schema.md) and
6
+ * audits component-trace coverage: every component_named event should
7
+ * trace to ≥1 decision_made event whose possible_actions array contains
8
+ * (or whose chosen_action equals) the named component.
9
+ *
10
+ * Per REFACTOR-SPEC P5(b). See references/decision-model.md for the
11
+ * contract.
12
+ *
13
+ * Default mode: --advisory — prints findings + summary; exit 0 even if
14
+ * orphans found (advisory rollout — collecting baseline for v3.2.0
15
+ * promotion to hard-fail).
16
+ *
17
+ * Strict mode: --strict — exits 1 on any orphan finding.
18
+ *
19
+ * Usage:
20
+ * node audit-decision-trace.mjs <session-log.jsonl>
21
+ * node audit-decision-trace.mjs <session-log.jsonl> --strict
22
+ * node audit-decision-trace.mjs <session-log.jsonl> --json
23
+ * node audit-decision-trace.mjs <session-log.jsonl> --allowlist=path/to/.decision-trace-allowlist.json
24
+ */
25
+
26
+ import fs from 'node:fs';
27
+ import path from 'node:path';
28
+ import process from 'node:process';
29
+ import { fileURLToPath } from 'node:url';
30
+
31
+ const __filename = fileURLToPath(import.meta.url);
32
+ const SCRIPT_DIR = path.dirname(__filename);
33
+ const KIT_DIR = path.dirname(SCRIPT_DIR);
34
+
35
+ function loadAllowlist(allowlistPath) {
36
+ if (!allowlistPath) return new Set();
37
+ if (!fs.existsSync(allowlistPath)) return new Set();
38
+ try {
39
+ const obj = JSON.parse(fs.readFileSync(allowlistPath, 'utf8'));
40
+ return new Set((obj.allowed_orphans || []).map(e => e.tag));
41
+ } catch (err) {
42
+ console.error(`⚠ Could not parse allowlist ${allowlistPath}: ${err.message}`);
43
+ return new Set();
44
+ }
45
+ }
46
+
47
+ function readJsonlEvents(logPath) {
48
+ const content = fs.readFileSync(logPath, 'utf8');
49
+ const lines = content.split('\n').filter(l => l.trim().length > 0);
50
+ const events = [];
51
+ lines.forEach((line, idx) => {
52
+ try {
53
+ events.push({ ...JSON.parse(line), _line: idx + 1 });
54
+ } catch {
55
+ // skip malformed lines (validate-session-log surfaces them separately)
56
+ }
57
+ });
58
+ return events;
59
+ }
60
+
61
+ function extractDecisionsAndComponents(events) {
62
+ const decisions = events.filter(e => e.event_type === 'decision_made');
63
+ const components = events.filter(e => e.event_type === 'component_named');
64
+ return { decisions, components };
65
+ }
66
+
67
+ /**
68
+ * Normalize an action string to a canonical component tag.
69
+ * Examples:
70
+ * "button-ui variant=primary [type=submit] [loading]" → "button-ui"
71
+ * "input-ui type=email" → "input-ui"
72
+ * "form-field with input-ui" → "form-field" (and also matches "input-ui")
73
+ * "none (raw)" → "none" (sentinel)
74
+ */
75
+ function extractTagsFromAction(action) {
76
+ if (!action || typeof action !== 'string') return [];
77
+ // Match anything that looks like a component tag (lowercase-with-hyphens, ending in -ui or known suffix or containing -)
78
+ const tagPattern = /\b([a-z][a-z0-9]*(?:-[a-z0-9]+)+)\b/g;
79
+ const tags = new Set();
80
+ let m;
81
+ while ((m = tagPattern.exec(action)) !== null) {
82
+ tags.add(m[1]);
83
+ }
84
+ return [...tags];
85
+ }
86
+
87
+ function auditTrace(events, allowlist) {
88
+ const { decisions, components } = extractDecisionsAndComponents(events);
89
+
90
+ // Build the universe of tags traced to any decision (possible_actions ∪ chosen_action)
91
+ const tracedTags = new Set();
92
+ for (const d of decisions) {
93
+ const p = d.payload || {};
94
+ const actions = Array.isArray(p.possible_actions) ? p.possible_actions : [];
95
+ for (const action of actions) {
96
+ for (const tag of extractTagsFromAction(action)) {
97
+ tracedTags.add(tag);
98
+ }
99
+ }
100
+ if (p.chosen_action) {
101
+ for (const tag of extractTagsFromAction(p.chosen_action)) {
102
+ tracedTags.add(tag);
103
+ }
104
+ }
105
+ }
106
+
107
+ // Walk component_named events; flag any tag not in tracedTags ∪ allowlist
108
+ const findings = [];
109
+ const allTagsObserved = new Set();
110
+ for (const c of components) {
111
+ const tag = c.payload?.tag;
112
+ if (!tag) continue;
113
+ allTagsObserved.add(tag);
114
+ if (tracedTags.has(tag)) continue;
115
+ if (allowlist.has(tag)) continue;
116
+ findings.push({
117
+ line: c._line,
118
+ severity: 'orphan',
119
+ tag,
120
+ session_id: c.session_id,
121
+ context: c.payload?.context || '(no context)',
122
+ msg: `Component "${tag}" named at line ${c._line} but no decision_made event lists it in possible_actions or chosen_action, and it's not in the allowlist.`,
123
+ });
124
+ }
125
+
126
+ return {
127
+ decision_count: decisions.length,
128
+ component_named_count: components.length,
129
+ unique_tags_observed: [...allTagsObserved].sort(),
130
+ unique_tags_traced: [...tracedTags].sort(),
131
+ allowlist_size: allowlist.size,
132
+ orphan_count: findings.length,
133
+ findings,
134
+ };
135
+ }
136
+
137
+ function main() {
138
+ const args = process.argv.slice(2);
139
+ const json = args.includes('--json');
140
+ const strict = args.includes('--strict');
141
+ // Default: advisory
142
+ const advisory = args.includes('--advisory') || !strict;
143
+ const allowlistArg = args.find(a => a.startsWith('--allowlist='));
144
+ const logPath = args.find(a => !a.startsWith('--'));
145
+
146
+ if (!logPath) {
147
+ console.error('Usage: audit-decision-trace.mjs <session-log.jsonl> [--strict] [--json] [--allowlist=path]');
148
+ process.exit(2);
149
+ }
150
+ if (!fs.existsSync(logPath)) {
151
+ console.error(`✗ Session log not found: ${logPath}`);
152
+ process.exit(2);
153
+ }
154
+
155
+ // Try kit-default allowlist if not specified
156
+ const defaultAllowlist = path.join(KIT_DIR, '.decision-trace-allowlist.json');
157
+ const allowlistPath = allowlistArg
158
+ ? allowlistArg.slice('--allowlist='.length)
159
+ : (fs.existsSync(defaultAllowlist) ? defaultAllowlist : null);
160
+ const allowlist = loadAllowlist(allowlistPath);
161
+
162
+ const events = readJsonlEvents(logPath);
163
+ const result = auditTrace(events, allowlist);
164
+
165
+ const mode = strict ? 'STRICT' : 'ADVISORY';
166
+
167
+ if (json) {
168
+ console.log(JSON.stringify({ mode, allowlist_path: allowlistPath, ...result }, null, 2));
169
+ } else {
170
+ console.log(`\n══ audit-decision-trace — ${logPath} ══`);
171
+ console.log(`Mode: ${mode}${advisory && !strict ? ' (default: never fails; v3.2.0 may promote to hard-fail — see references/decision-model.md)' : ''}`);
172
+ console.log(`Allowlist: ${allowlistPath || '(none)'} (${result.allowlist_size} entries)`);
173
+ console.log(`Decisions: ${result.decision_count}`);
174
+ console.log(`Components named: ${result.component_named_count}`);
175
+ console.log(`Unique tags: ${result.unique_tags_observed.length} observed; ${result.unique_tags_traced.length} traced`);
176
+ console.log(`Orphan findings: ${result.orphan_count}`);
177
+
178
+ if (result.findings.length > 0) {
179
+ console.log(`\nOrphan-component findings (named without traced decision):`);
180
+ for (const f of result.findings) {
181
+ console.log(` ⚠ line ${f.line}: tag="${f.tag}" context="${f.context}"`);
182
+ }
183
+ console.log(`\nResolution paths:`);
184
+ console.log(` 1. Retroactively add a decision_made event with this tag in possible_actions.`);
185
+ console.log(` 2. Add the tag to .decision-trace-allowlist.json with a rationale.`);
186
+ console.log(` 3. Remove the orphan component from the composition.`);
187
+ } else {
188
+ console.log(`\n✓ No orphan-component findings.`);
189
+ }
190
+ }
191
+
192
+ if (strict && result.orphan_count > 0) {
193
+ process.exit(1);
194
+ }
195
+ process.exit(0);
196
+ }
197
+
198
+ main();