@adia-ai/adia-ui-factory 0.2.4 → 0.8.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/.claude-plugin/plugin.json +5 -7
- package/.mcp.json +1 -1
- package/CHANGELOG.md +27 -67
- package/README.md +32 -53
- package/agents/app-architect.md +28 -0
- package/agents/routing-corpus.json +202 -0
- package/agents/screen-composer.md +27 -0
- package/bin/adia-lint +17 -2
- package/commands/adia-genui.md +5 -8
- package/commands/adia-migrate.md +5 -6
- package/commands/adia-orient.md +5 -10
- package/commands/adia-scaffold.md +7 -13
- package/commands/adia-verify.md +5 -6
- package/package.json +5 -6
- package/references/a2ui-mcp-tools.md +11 -8
- package/references/authoring-components.md +25 -1
- package/references/component-model.md +52 -4
- package/references/contracts/a2ui-mcp-surface.md +35 -0
- package/references/contracts/migration-guide-format.md +34 -0
- package/references/data-and-hydration.md +8 -5
- package/references/genui-a2ui.md +12 -6
- package/references/llm.md +22 -39
- package/references/migration.md +110 -36
- package/references/project-shapes.md +11 -13
- package/references/shell-admin.md +5 -2
- package/references/shell-chat.md +5 -3
- package/references/shell-editor.md +1 -1
- package/references/shell-embed.md +40 -26
- package/references/shell-simple.md +5 -2
- package/references/spa-architecture.md +28 -73
- package/references/ssr-integration.md +1 -1
- package/references/verification.md +8 -12
- package/skills/adia-compose/SKILL.md +52 -0
- package/skills/{adia-ui-kit → adia-compose}/assets/figma-make/guidelines/Guidelines.md +3 -3
- package/skills/{adia-ui-kit → adia-compose}/assets/figma-make/guidelines/components.md +1 -1
- package/skills/{adia-ui-kit → adia-compose}/assets/figma-make/guidelines/styles.md +1 -1
- package/skills/{adia-ui-kit → adia-compose}/assets/figma-make/guidelines/tokens.md +3 -3
- package/skills/{adia-ui-kit → adia-compose}/assets/templates/FEEDBACK.template.md +7 -7
- package/skills/{adia-ui-kit → adia-compose}/assets/templates/RESPONSE.template.md +6 -7
- package/skills/adia-compose/references/composition-traps.md +58 -0
- package/skills/adia-compose/references/feedback-discipline.md +20 -0
- package/skills/adia-compose/references/spec-to-ui-reasoning.md +158 -0
- package/skills/adia-data/SKILL.md +78 -0
- package/skills/adia-genui/SKILL.md +83 -0
- package/skills/adia-host/SKILL.md +119 -0
- package/skills/adia-llm/SKILL.md +36 -0
- package/skills/adia-migrate/SKILL.md +76 -0
- package/skills/adia-orient/SKILL.md +123 -0
- package/skills/adia-project/SKILL.md +83 -0
- package/skills/adia-shells/SKILL.md +74 -0
- package/skills/adia-verify/SKILL.md +43 -0
- package/skills/adia-verify/references/verification.md +42 -0
- package/commands/adia-compose.md +0 -10
- package/commands/adia-wire.md +0 -13
- package/skills/adia-ui-compose/SKILL.md +0 -57
- package/skills/adia-ui-data/SKILL.md +0 -62
- package/skills/adia-ui-factory/SKILL.md +0 -113
- package/skills/adia-ui-genui/SKILL.md +0 -74
- package/skills/adia-ui-kit/CHANGELOG.md +0 -41
- package/skills/adia-ui-kit/SKILL.md +0 -442
- package/skills/adia-ui-kit/assets/team-scaffold/archive-README.md +0 -30
- package/skills/adia-ui-kit/assets/team-scaffold/drafts-README.md +0 -57
- package/skills/adia-ui-kit/assets/team-scaffold/inbox-README.md +0 -76
- package/skills/adia-ui-kit/assets/team-scaffold/outbox-README.md +0 -46
- package/skills/adia-ui-kit/assets/team-scaffold/team-root.README.md +0 -51
- package/skills/adia-ui-kit/assets/team-scaffold/tickets-README.md +0 -208
- package/skills/adia-ui-kit/assets/team-scaffold/tickets-REGISTRY.md +0 -81
- package/skills/adia-ui-kit/assets/templates/AGENTS.template.md +0 -128
- package/skills/adia-ui-kit/assets/templates/CLAUDE.template.md +0 -1
- package/skills/adia-ui-kit/assets/templates/figma-make-guidelines.md +0 -90
- package/skills/adia-ui-kit/evals/adversarial-content-trust.json +0 -83
- package/skills/adia-ui-kit/evals/evals.json +0 -55
- package/skills/adia-ui-kit/evals/routing-corpus.json +0 -121
- package/skills/adia-ui-kit/evals/teach-routing-cases.json +0 -92
- package/skills/adia-ui-kit/references/capabilities.md +0 -245
- package/skills/adia-ui-kit/references/catalog-lookup.md +0 -186
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v1.md +0 -8
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v2.md +0 -3585
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v3.md +0 -1899
- package/skills/adia-ui-kit/references/common-gotchas-consumer.md +0 -522
- package/skills/adia-ui-kit/references/component-literacy.md +0 -291
- package/skills/adia-ui-kit/references/composition.md +0 -245
- package/skills/adia-ui-kit/references/correction-loop.md +0 -462
- package/skills/adia-ui-kit/references/css-policy.md +0 -343
- package/skills/adia-ui-kit/references/decision-model.md +0 -158
- package/skills/adia-ui-kit/references/diagnostic-output.md +0 -57
- package/skills/adia-ui-kit/references/docs-catalog.md +0 -239
- package/skills/adia-ui-kit/references/exclusions.md +0 -225
- package/skills/adia-ui-kit/references/feedback-authoring.md +0 -466
- package/skills/adia-ui-kit/references/figma-make.md +0 -244
- package/skills/adia-ui-kit/references/flows-auth.md +0 -307
- package/skills/adia-ui-kit/references/flows-content.md +0 -206
- package/skills/adia-ui-kit/references/flows-data.md +0 -348
- package/skills/adia-ui-kit/references/flows-interaction.md +0 -181
- package/skills/adia-ui-kit/references/flows-shells.md +0 -336
- package/skills/adia-ui-kit/references/gap-detection.md +0 -266
- package/skills/adia-ui-kit/references/harness.md +0 -284
- package/skills/adia-ui-kit/references/loading-protocol.md +0 -406
- package/skills/adia-ui-kit/references/local-link.md +0 -198
- package/skills/adia-ui-kit/references/mcp.md +0 -401
- package/skills/adia-ui-kit/references/migration.md +0 -116
- package/skills/adia-ui-kit/references/mission.md +0 -194
- package/skills/adia-ui-kit/references/mode-routing.md +0 -298
- package/skills/adia-ui-kit/references/patterns-agent.md +0 -223
- package/skills/adia-ui-kit/references/patterns-data.md +0 -1065
- package/skills/adia-ui-kit/references/patterns-forms.md +0 -223
- package/skills/adia-ui-kit/references/patterns-layout.md +0 -326
- package/skills/adia-ui-kit/references/patterns-recipes.md +0 -1416
- package/skills/adia-ui-kit/references/patterns-shells.md +0 -1097
- package/skills/adia-ui-kit/references/patterns-traits.md +0 -259
- package/skills/adia-ui-kit/references/patterns-wiring.md +0 -339
- package/skills/adia-ui-kit/references/pev-rationale.md +0 -137
- package/skills/adia-ui-kit/references/reasoning-gate.md +0 -146
- package/skills/adia-ui-kit/references/reasoning-methodologies.md +0 -112
- package/skills/adia-ui-kit/references/recon.md +0 -192
- package/skills/adia-ui-kit/references/rendering-model.md +0 -431
- package/skills/adia-ui-kit/references/response-authoring.md +0 -398
- package/skills/adia-ui-kit/references/session-log-schema.md +0 -203
- package/skills/adia-ui-kit/references/session-log-template.md +0 -118
- package/skills/adia-ui-kit/references/setup.md +0 -652
- package/skills/adia-ui-kit/references/skill-hygiene.md +0 -135
- package/skills/adia-ui-kit/references/spec-to-ui-reasoning.md +0 -394
- package/skills/adia-ui-kit/references/state.md +0 -229
- package/skills/adia-ui-kit/references/teach-protocol.md +0 -421
- package/skills/adia-ui-kit/references/theme.md +0 -142
- package/skills/adia-ui-kit/references/version-check.md +0 -69
- package/skills/adia-ui-kit/scripts/audit-decision-trace.mjs +0 -198
- package/skills/adia-ui-kit/scripts/audit-kit-roster.mjs +0 -366
- package/skills/adia-ui-kit/scripts/changelog-archive.mjs +0 -126
- package/skills/adia-ui-kit/scripts/check-adia-versions.py +0 -404
- package/skills/adia-ui-kit/scripts/run-skill-evals.mjs +0 -358
- package/skills/adia-ui-kit/scripts/validate-session-log.mjs +0 -242
- package/skills/adia-ui-kit/skill.json +0 -102
- package/skills/adia-ui-llm/SKILL.md +0 -51
- package/skills/adia-ui-migrate/SKILL.md +0 -64
- package/skills/adia-ui-migration/CHANGELOG.md +0 -47
- package/skills/adia-ui-migration/SKILL.md +0 -381
- package/skills/adia-ui-migration/skill.json +0 -29
- package/skills/adia-ui-project/SKILL.md +0 -77
- package/skills/adia-ui-shells/SKILL.md +0 -62
- package/skills/adia-ui-spa/SKILL.md +0 -52
- package/skills/adia-ui-ssr/SKILL.md +0 -52
- package/skills/adia-ui-verify/SKILL.md +0 -44
- /package/skills/{adia-ui-kit → adia-compose}/assets/templates/FEEDBACK-OMISSIONS-DEFENSE.template.md +0 -0
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
check-adia-versions.py — verify your @adia-ai/* install is sane.
|
|
4
|
-
|
|
5
|
-
Run this from any consumer project (not just the monorepo). Walks three
|
|
6
|
-
sources and reports drift:
|
|
7
|
-
|
|
8
|
-
1. Installed (node_modules/@adia-ai/<pkg>/package.json)
|
|
9
|
-
2. Declared (your package.json `dependencies` + `devDependencies`)
|
|
10
|
-
3. Latest published (npm view @adia-ai/<pkg> version — needs network)
|
|
11
|
-
|
|
12
|
-
Exits 0 when everything lines up. Exits 1 on drift. Exits 2 on
|
|
13
|
-
unrecoverable error (missing node_modules, malformed package.json).
|
|
14
|
-
|
|
15
|
-
Usage
|
|
16
|
-
-----
|
|
17
|
-
./check-adia-versions.py # full check (network)
|
|
18
|
-
./check-adia-versions.py --offline # skip npm view, lockstep only
|
|
19
|
-
./check-adia-versions.py --json # machine-readable output
|
|
20
|
-
./check-adia-versions.py --root . # explicit consumer root
|
|
21
|
-
|
|
22
|
-
Why this exists
|
|
23
|
-
---------------
|
|
24
|
-
The 9 `@adia-ai/*` packages ship in lockstep — one stale install desyncs
|
|
25
|
-
the whole bundle. Plus the skill documents fixes that only apply at
|
|
26
|
-
specific versions (e.g. F-014/F-015: v0.6.7 ships broken absolute-path
|
|
27
|
-
imports in `@adia-ai/web-modules`, fix lands in v0.6.8). Without a
|
|
28
|
-
verification step, agents trust their installed snapshot blindly and
|
|
29
|
-
ship advice that's wrong for the user's actual install.
|
|
30
|
-
|
|
31
|
-
Output legend
|
|
32
|
-
-------------
|
|
33
|
-
✓ ok installed == declared, and matches latest (or --offline)
|
|
34
|
-
🔗 linked declared as `file:...`, `link:...`, or `workspace:*` —
|
|
35
|
-
local development link, not an npm version (informational)
|
|
36
|
-
⚠ stale installed < latest published — `npm update` recommended
|
|
37
|
-
⚠ split lockstep broken — packages disagree on version
|
|
38
|
-
⚠ undeclared installed in node_modules but missing from package.json
|
|
39
|
-
⚠ uninstalled declared but missing from node_modules — `npm install`
|
|
40
|
-
✗ ahead installed > latest published — local source build /
|
|
41
|
-
pre-publish state (informational, not an error in this
|
|
42
|
-
monorepo where you may dev against unpublished cuts)
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
from __future__ import annotations
|
|
46
|
-
|
|
47
|
-
import argparse
|
|
48
|
-
import json
|
|
49
|
-
import re
|
|
50
|
-
import subprocess
|
|
51
|
-
import sys
|
|
52
|
-
from dataclasses import dataclass, field
|
|
53
|
-
from pathlib import Path
|
|
54
|
-
from typing import Optional
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
PACKAGE_NAMES = [
|
|
58
|
-
"@adia-ai/web-components",
|
|
59
|
-
"@adia-ai/web-modules",
|
|
60
|
-
"@adia-ai/llm",
|
|
61
|
-
"@adia-ai/a2ui-runtime",
|
|
62
|
-
"@adia-ai/a2ui-compose",
|
|
63
|
-
"@adia-ai/a2ui-corpus",
|
|
64
|
-
"@adia-ai/a2ui-mcp",
|
|
65
|
-
"@adia-ai/a2ui-retrieval",
|
|
66
|
-
"@adia-ai/a2ui-validator",
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@dataclass
|
|
71
|
-
class PackageStatus:
|
|
72
|
-
name: str
|
|
73
|
-
installed: Optional[str] = None
|
|
74
|
-
declared: Optional[str] = None # raw range from package.json
|
|
75
|
-
declared_normalized: Optional[str] = None # version stripped of ^~
|
|
76
|
-
latest: Optional[str] = None
|
|
77
|
-
symlinked: bool = False # node_modules/<name> is a symlink
|
|
78
|
-
issues: list[str] = field(default_factory=list)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def read_json(path: Path) -> Optional[dict]:
|
|
82
|
-
try:
|
|
83
|
-
return json.loads(path.read_text())
|
|
84
|
-
except (FileNotFoundError, json.JSONDecodeError):
|
|
85
|
-
return None
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def is_link_spec(spec: Optional[str]) -> bool:
|
|
89
|
-
"""Detect local-development link specs (file:, link:, workspace:)."""
|
|
90
|
-
if not spec:
|
|
91
|
-
return False
|
|
92
|
-
return spec.startswith(("file:", "link:", "workspace:", "portal:"))
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def normalize_range(spec: str) -> str:
|
|
96
|
-
"""Strip leading ^ ~ >= etc. from a semver range to get a version."""
|
|
97
|
-
if not spec:
|
|
98
|
-
return ""
|
|
99
|
-
# Link specs aren't semver — leave them alone for display
|
|
100
|
-
if is_link_spec(spec):
|
|
101
|
-
return spec
|
|
102
|
-
m = re.match(r"^[\^~><=\s]*([\d]+\.[\d]+\.[\d]+(?:-[\w.]+)?)", spec)
|
|
103
|
-
return m.group(1) if m else spec
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def parse_semver(v: Optional[str]) -> tuple[int, int, int]:
|
|
107
|
-
if not v:
|
|
108
|
-
return (0, 0, 0)
|
|
109
|
-
m = re.match(r"^(\d+)\.(\d+)\.(\d+)", v)
|
|
110
|
-
if not m:
|
|
111
|
-
return (0, 0, 0)
|
|
112
|
-
return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def get_installed(root: Path, name: str) -> Optional[str]:
|
|
116
|
-
pkg = root / "node_modules" / name / "package.json"
|
|
117
|
-
data = read_json(pkg)
|
|
118
|
-
return data.get("version") if data else None
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def is_symlinked(root: Path, name: str) -> bool:
|
|
122
|
-
"""Check whether node_modules/<name> is a symlink (linked) vs a real dir."""
|
|
123
|
-
pkg_dir = root / "node_modules" / name
|
|
124
|
-
try:
|
|
125
|
-
return pkg_dir.is_symlink()
|
|
126
|
-
except OSError:
|
|
127
|
-
return False
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def get_declared(consumer_pkg: dict, name: str) -> Optional[str]:
|
|
131
|
-
for field_name in ("dependencies", "devDependencies", "peerDependencies"):
|
|
132
|
-
spec = (consumer_pkg.get(field_name) or {}).get(name)
|
|
133
|
-
if spec:
|
|
134
|
-
return spec
|
|
135
|
-
return None
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def get_latest(name: str, timeout: int = 10) -> Optional[str]:
|
|
139
|
-
"""Query npm registry for the dist-tag latest version. Network call."""
|
|
140
|
-
try:
|
|
141
|
-
result = subprocess.run(
|
|
142
|
-
["npm", "view", name, "version"],
|
|
143
|
-
capture_output=True, text=True, timeout=timeout,
|
|
144
|
-
)
|
|
145
|
-
if result.returncode == 0:
|
|
146
|
-
return result.stdout.strip() or None
|
|
147
|
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
148
|
-
pass
|
|
149
|
-
return None
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def check_lockstep(statuses: list[PackageStatus]) -> Optional[str]:
|
|
153
|
-
"""Return the consensus installed version, or None if split."""
|
|
154
|
-
versions = {s.installed for s in statuses if s.installed}
|
|
155
|
-
if len(versions) == 0:
|
|
156
|
-
return None
|
|
157
|
-
if len(versions) == 1:
|
|
158
|
-
return versions.pop()
|
|
159
|
-
return None # split
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def run_audit(root: Path, offline: bool) -> tuple[list[PackageStatus], dict]:
|
|
163
|
-
consumer_pkg = read_json(root / "package.json") or {}
|
|
164
|
-
|
|
165
|
-
# Monorepo-root detection: if this package.json declares "workspaces",
|
|
166
|
-
# we're inside the @adia-ai/* source monorepo, not a consumer project.
|
|
167
|
-
# In that case, "undeclared" is expected (the packages are workspaces,
|
|
168
|
-
# not external deps) — suppress the noise.
|
|
169
|
-
is_monorepo_root = bool(consumer_pkg.get("workspaces"))
|
|
170
|
-
|
|
171
|
-
# "undeclared" classification — only flag when it likely reflects a
|
|
172
|
-
# mistake. In a consumer project, sub-packages like @adia-ai/llm,
|
|
173
|
-
# a2ui-runtime, a2ui-mcp are commonly transitive deps pulled in by
|
|
174
|
-
# @adia-ai/web-components or web-modules; "undeclared" there is noise.
|
|
175
|
-
# We only flag undeclared when:
|
|
176
|
-
# (a) we are NOT in the monorepo root (workspaces detected), AND
|
|
177
|
-
# (b) the package is one of the user-facing entry points
|
|
178
|
-
# (web-components, web-modules) AND it's installed.
|
|
179
|
-
# This catches the real failure mode (someone deleted the dep but
|
|
180
|
-
# node_modules still has it = phantom install) without spamming on
|
|
181
|
-
# legitimate transitive deps.
|
|
182
|
-
USER_FACING = {"@adia-ai/web-components", "@adia-ai/web-modules"}
|
|
183
|
-
|
|
184
|
-
statuses: list[PackageStatus] = []
|
|
185
|
-
for name in PACKAGE_NAMES:
|
|
186
|
-
s = PackageStatus(name=name)
|
|
187
|
-
s.installed = get_installed(root, name)
|
|
188
|
-
s.declared = get_declared(consumer_pkg, name)
|
|
189
|
-
s.declared_normalized = normalize_range(s.declared) if s.declared else None
|
|
190
|
-
s.symlinked = is_symlinked(root, name)
|
|
191
|
-
if not offline:
|
|
192
|
-
s.latest = get_latest(name)
|
|
193
|
-
|
|
194
|
-
declared_is_link = is_link_spec(s.declared)
|
|
195
|
-
|
|
196
|
-
# Per-package classification
|
|
197
|
-
if s.declared and not s.installed:
|
|
198
|
-
s.issues.append("uninstalled")
|
|
199
|
-
if (s.installed and not s.declared
|
|
200
|
-
and not is_monorepo_root
|
|
201
|
-
and name in USER_FACING):
|
|
202
|
-
s.issues.append("undeclared")
|
|
203
|
-
# Link-spec packages get a "linked" marker; suppress version-drift
|
|
204
|
-
# warnings since semver comparison doesn't apply to file:/link:/workspace:.
|
|
205
|
-
if declared_is_link:
|
|
206
|
-
s.issues.append("linked")
|
|
207
|
-
# Pitfall warning: declared as link but installed as real dir
|
|
208
|
-
# (npm sometimes copies instead of linking)
|
|
209
|
-
if s.installed and not s.symlinked:
|
|
210
|
-
s.issues.append("link-copied-not-symlinked")
|
|
211
|
-
elif s.installed and s.latest:
|
|
212
|
-
inst = parse_semver(s.installed)
|
|
213
|
-
late = parse_semver(s.latest)
|
|
214
|
-
if inst < late:
|
|
215
|
-
s.issues.append("stale")
|
|
216
|
-
elif inst > late:
|
|
217
|
-
s.issues.append("ahead")
|
|
218
|
-
|
|
219
|
-
statuses.append(s)
|
|
220
|
-
|
|
221
|
-
consensus = check_lockstep(statuses)
|
|
222
|
-
summary = {
|
|
223
|
-
"consumer_root": str(root.resolve()),
|
|
224
|
-
"consensus_installed": consensus,
|
|
225
|
-
"split_lockstep": consensus is None and any(s.installed for s in statuses),
|
|
226
|
-
"offline": offline,
|
|
227
|
-
"is_monorepo_root": is_monorepo_root,
|
|
228
|
-
}
|
|
229
|
-
return statuses, summary
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def render_text(statuses: list[PackageStatus], summary: dict) -> int:
|
|
233
|
-
"""Human-readable output. Returns process exit code."""
|
|
234
|
-
print(f"@adia-ai install audit — {summary['consumer_root']}")
|
|
235
|
-
if summary.get("is_monorepo_root"):
|
|
236
|
-
print("(monorepo-root mode — workspaces detected, 'undeclared' suppressed)")
|
|
237
|
-
print("─" * 78)
|
|
238
|
-
|
|
239
|
-
any_issues = False
|
|
240
|
-
consensus_latest = None
|
|
241
|
-
for s in statuses:
|
|
242
|
-
if s.latest:
|
|
243
|
-
consensus_latest = s.latest
|
|
244
|
-
break # first one with a latest is enough for the header line
|
|
245
|
-
|
|
246
|
-
if consensus_latest:
|
|
247
|
-
print(f"Latest published (npm view): {consensus_latest}")
|
|
248
|
-
if summary["consensus_installed"]:
|
|
249
|
-
print(f"Consensus installed: {summary['consensus_installed']}")
|
|
250
|
-
elif summary["split_lockstep"]:
|
|
251
|
-
print("Consensus installed: ⚠ SPLIT — packages disagree")
|
|
252
|
-
any_issues = True
|
|
253
|
-
print()
|
|
254
|
-
|
|
255
|
-
# Per-package table
|
|
256
|
-
name_width = max(len(s.name) for s in statuses) + 2
|
|
257
|
-
print(f" {'package'.ljust(name_width)} {'installed':<10} {'declared':<26} {'latest':<10} status")
|
|
258
|
-
print(f" {'-' * name_width} {'-' * 10} {'-' * 26} {'-' * 10} {'-' * 24}")
|
|
259
|
-
|
|
260
|
-
for s in statuses:
|
|
261
|
-
installed = s.installed or "—"
|
|
262
|
-
declared = s.declared or "—"
|
|
263
|
-
# Truncate long file: specs for table readability
|
|
264
|
-
if declared and len(declared) > 26:
|
|
265
|
-
declared = declared[:23] + "..."
|
|
266
|
-
latest = s.latest or ("(offline)" if summary["offline"] else "—")
|
|
267
|
-
|
|
268
|
-
if not s.issues:
|
|
269
|
-
status = "✓ ok"
|
|
270
|
-
else:
|
|
271
|
-
status_parts = []
|
|
272
|
-
for i in s.issues:
|
|
273
|
-
if i == "linked":
|
|
274
|
-
status_parts.append("🔗 linked")
|
|
275
|
-
elif i == "ahead":
|
|
276
|
-
status_parts.append("ⓘ ahead")
|
|
277
|
-
elif i == "link-copied-not-symlinked":
|
|
278
|
-
status_parts.append("⚠ copied-not-linked")
|
|
279
|
-
else:
|
|
280
|
-
status_parts.append(f"⚠ {i}")
|
|
281
|
-
status = " · ".join(status_parts)
|
|
282
|
-
# Only count as a real failure if it's not purely informational
|
|
283
|
-
real_issues = [i for i in s.issues
|
|
284
|
-
if i not in ("ahead", "linked")]
|
|
285
|
-
if real_issues:
|
|
286
|
-
any_issues = True
|
|
287
|
-
|
|
288
|
-
print(f" {s.name.ljust(name_width)} {installed:<10} {declared:<26} {latest:<10} {status}")
|
|
289
|
-
|
|
290
|
-
print()
|
|
291
|
-
|
|
292
|
-
# Diagnostics
|
|
293
|
-
if summary["split_lockstep"]:
|
|
294
|
-
print("✗ Lockstep broken — the 9 @adia-ai/* packages must share a version.")
|
|
295
|
-
print(" Fix: `npm install @adia-ai/web-components@<v> @adia-ai/web-modules@<v> ...`")
|
|
296
|
-
print(" or delete node_modules + package-lock.json and `npm install`.")
|
|
297
|
-
print()
|
|
298
|
-
|
|
299
|
-
stale_pkgs = [s for s in statuses if "stale" in s.issues]
|
|
300
|
-
if stale_pkgs:
|
|
301
|
-
print(f"⚠ Stale: {len(stale_pkgs)} package(s) behind latest. Suggested:")
|
|
302
|
-
print(f" npm install {' '.join(f'{s.name}@latest' for s in stale_pkgs)}")
|
|
303
|
-
print()
|
|
304
|
-
|
|
305
|
-
ahead_pkgs = [s for s in statuses if "ahead" in s.issues]
|
|
306
|
-
if ahead_pkgs:
|
|
307
|
-
print(f"ⓘ Ahead: {len(ahead_pkgs)} package(s) installed past latest published.")
|
|
308
|
-
print(" This is normal in the monorepo (pre-publish dev cut) or if you")
|
|
309
|
-
print(" installed from a local tarball / git tag. Not an error.")
|
|
310
|
-
print()
|
|
311
|
-
|
|
312
|
-
linked_pkgs = [s for s in statuses if "linked" in s.issues]
|
|
313
|
-
if linked_pkgs:
|
|
314
|
-
all_linked = len(linked_pkgs) == sum(1 for s in statuses if s.declared)
|
|
315
|
-
print(f"🔗 Linked: {len(linked_pkgs)} package(s) declared as local "
|
|
316
|
-
f"dev link ({'all declared' if all_linked else 'subset'}).")
|
|
317
|
-
print(" Version comparison disabled for these — they resolve to source")
|
|
318
|
-
print(" on disk, not an npm-published version. The 'installed' column")
|
|
319
|
-
print(" shows the version from the linked source's package.json.")
|
|
320
|
-
if not all_linked and sum(1 for s in statuses if s.declared) > len(linked_pkgs):
|
|
321
|
-
print()
|
|
322
|
-
print(" ⚠ Mixed link + npm declarations risk split-lockstep when the")
|
|
323
|
-
print(" linked source's transitive deps disagree with what npm")
|
|
324
|
-
print(" resolved. Recommended: link ALL 9 @adia-ai/* packages, or")
|
|
325
|
-
print(" link NONE. See §LocalLink in the adia-ui-kit skill.")
|
|
326
|
-
print()
|
|
327
|
-
|
|
328
|
-
copied_pkgs = [s for s in statuses if "link-copied-not-symlinked" in s.issues]
|
|
329
|
-
if copied_pkgs:
|
|
330
|
-
print(f"⚠ Link copied, not symlinked: {len(copied_pkgs)} package(s).")
|
|
331
|
-
print(" npm sometimes copies file: deps instead of symlinking — edits")
|
|
332
|
-
print(" to source won't be reflected. Fix:")
|
|
333
|
-
print(" rm -rf node_modules/@adia-ai")
|
|
334
|
-
print(" npm install --install-links=false")
|
|
335
|
-
print(" Then re-run this script to confirm symlinks are in place.")
|
|
336
|
-
print()
|
|
337
|
-
|
|
338
|
-
uninstalled = [s for s in statuses if "uninstalled" in s.issues]
|
|
339
|
-
if uninstalled:
|
|
340
|
-
print(f"⚠ Declared but uninstalled: {len(uninstalled)} package(s).")
|
|
341
|
-
print(" Fix: `npm install`")
|
|
342
|
-
print()
|
|
343
|
-
|
|
344
|
-
if not any_issues:
|
|
345
|
-
print("✓ All clean — lockstep aligned, declarations match installs, "
|
|
346
|
-
"and (if online) installs match latest published.")
|
|
347
|
-
return 0
|
|
348
|
-
|
|
349
|
-
return 1
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
def render_json(statuses: list[PackageStatus], summary: dict) -> int:
|
|
353
|
-
payload = {
|
|
354
|
-
"summary": summary,
|
|
355
|
-
"packages": [
|
|
356
|
-
{
|
|
357
|
-
"name": s.name,
|
|
358
|
-
"installed": s.installed,
|
|
359
|
-
"declared": s.declared,
|
|
360
|
-
"declared_normalized": s.declared_normalized,
|
|
361
|
-
"latest": s.latest,
|
|
362
|
-
"symlinked": s.symlinked,
|
|
363
|
-
"issues": s.issues,
|
|
364
|
-
}
|
|
365
|
-
for s in statuses
|
|
366
|
-
],
|
|
367
|
-
}
|
|
368
|
-
print(json.dumps(payload, indent=2))
|
|
369
|
-
# 'ahead' and 'linked' are informational, not failures
|
|
370
|
-
real_issues = summary["split_lockstep"] or any(
|
|
371
|
-
i for s in statuses for i in s.issues if i not in ("ahead", "linked")
|
|
372
|
-
)
|
|
373
|
-
return 1 if real_issues else 0
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
def main(argv: Optional[list[str]] = None) -> int:
|
|
377
|
-
parser = argparse.ArgumentParser(
|
|
378
|
-
description="Verify @adia-ai/* install lockstep + freshness.",
|
|
379
|
-
)
|
|
380
|
-
parser.add_argument("--root", default=".",
|
|
381
|
-
help="Consumer project root (default: cwd).")
|
|
382
|
-
parser.add_argument("--offline", action="store_true",
|
|
383
|
-
help="Skip npm view (no network).")
|
|
384
|
-
parser.add_argument("--json", action="store_true",
|
|
385
|
-
help="Machine-readable JSON output.")
|
|
386
|
-
args = parser.parse_args(argv)
|
|
387
|
-
|
|
388
|
-
root = Path(args.root).resolve()
|
|
389
|
-
if not (root / "package.json").exists():
|
|
390
|
-
print(f"✗ No package.json at {root}", file=sys.stderr)
|
|
391
|
-
return 2
|
|
392
|
-
if not (root / "node_modules").exists():
|
|
393
|
-
print(f"⚠ No node_modules at {root} — run `npm install` first", file=sys.stderr)
|
|
394
|
-
return 2
|
|
395
|
-
|
|
396
|
-
statuses, summary = run_audit(root, args.offline)
|
|
397
|
-
|
|
398
|
-
if args.json:
|
|
399
|
-
return render_json(statuses, summary)
|
|
400
|
-
return render_text(statuses, summary)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if __name__ == "__main__":
|
|
404
|
-
sys.exit(main())
|