@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.
- package/.claude-plugin/plugin.json +12 -3
- package/.mcp.json +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +24 -1
- package/bin/adia-scaffold +93 -13
- package/package.json +1 -1
- package/references/shell-embed.md +10 -0
- package/references/spa-architecture.md +7 -0
- package/skills/adia-ui-kit/CHANGELOG.md +41 -0
- package/skills/adia-ui-kit/SKILL.md +442 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/Guidelines.md +87 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/components.md +142 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/styles.md +118 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/tokens.md +120 -0
- package/skills/adia-ui-kit/assets/team-scaffold/archive-README.md +30 -0
- package/skills/adia-ui-kit/assets/team-scaffold/drafts-README.md +57 -0
- package/skills/adia-ui-kit/assets/team-scaffold/inbox-README.md +76 -0
- package/skills/adia-ui-kit/assets/team-scaffold/outbox-README.md +46 -0
- package/skills/adia-ui-kit/assets/team-scaffold/team-root.README.md +51 -0
- package/skills/adia-ui-kit/assets/team-scaffold/tickets-README.md +208 -0
- package/skills/adia-ui-kit/assets/team-scaffold/tickets-REGISTRY.md +81 -0
- package/skills/adia-ui-kit/assets/templates/AGENTS.template.md +128 -0
- package/skills/adia-ui-kit/assets/templates/CLAUDE.template.md +1 -0
- package/skills/adia-ui-kit/assets/templates/FEEDBACK-OMISSIONS-DEFENSE.template.md +150 -0
- package/skills/adia-ui-kit/assets/templates/FEEDBACK.template.md +213 -0
- package/skills/adia-ui-kit/assets/templates/RESPONSE.template.md +114 -0
- package/skills/adia-ui-kit/assets/templates/figma-make-guidelines.md +90 -0
- package/skills/adia-ui-kit/evals/adversarial-content-trust.json +83 -0
- package/skills/adia-ui-kit/evals/evals.json +55 -0
- package/skills/adia-ui-kit/evals/routing-corpus.json +121 -0
- package/skills/adia-ui-kit/evals/teach-routing-cases.json +92 -0
- package/skills/adia-ui-kit/references/capabilities.md +245 -0
- package/skills/adia-ui-kit/references/catalog-lookup.md +186 -0
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v1.md +8 -0
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v2.md +3585 -0
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v3.md +1899 -0
- package/skills/adia-ui-kit/references/common-gotchas-consumer.md +522 -0
- package/skills/adia-ui-kit/references/component-literacy.md +291 -0
- package/skills/adia-ui-kit/references/composition.md +245 -0
- package/skills/adia-ui-kit/references/correction-loop.md +462 -0
- package/skills/adia-ui-kit/references/css-policy.md +343 -0
- package/skills/adia-ui-kit/references/decision-model.md +158 -0
- package/skills/adia-ui-kit/references/diagnostic-output.md +57 -0
- package/skills/adia-ui-kit/references/docs-catalog.md +239 -0
- package/skills/adia-ui-kit/references/exclusions.md +225 -0
- package/skills/adia-ui-kit/references/feedback-authoring.md +466 -0
- package/skills/adia-ui-kit/references/figma-make.md +244 -0
- package/skills/adia-ui-kit/references/flows-auth.md +307 -0
- package/skills/adia-ui-kit/references/flows-content.md +206 -0
- package/skills/adia-ui-kit/references/flows-data.md +348 -0
- package/skills/adia-ui-kit/references/flows-interaction.md +181 -0
- package/skills/adia-ui-kit/references/flows-shells.md +336 -0
- package/skills/adia-ui-kit/references/gap-detection.md +266 -0
- package/skills/adia-ui-kit/references/harness.md +284 -0
- package/skills/adia-ui-kit/references/loading-protocol.md +406 -0
- package/skills/adia-ui-kit/references/local-link.md +198 -0
- package/skills/adia-ui-kit/references/mcp.md +401 -0
- package/skills/adia-ui-kit/references/migration.md +116 -0
- package/skills/adia-ui-kit/references/mission.md +194 -0
- package/skills/adia-ui-kit/references/mode-routing.md +298 -0
- package/skills/adia-ui-kit/references/patterns-agent.md +223 -0
- package/skills/adia-ui-kit/references/patterns-data.md +1065 -0
- package/skills/adia-ui-kit/references/patterns-forms.md +223 -0
- package/skills/adia-ui-kit/references/patterns-layout.md +326 -0
- package/skills/adia-ui-kit/references/patterns-recipes.md +1416 -0
- package/skills/adia-ui-kit/references/patterns-shells.md +1097 -0
- package/skills/adia-ui-kit/references/patterns-traits.md +259 -0
- package/skills/adia-ui-kit/references/patterns-wiring.md +339 -0
- package/skills/adia-ui-kit/references/pev-rationale.md +137 -0
- package/skills/adia-ui-kit/references/reasoning-gate.md +146 -0
- package/skills/adia-ui-kit/references/reasoning-methodologies.md +112 -0
- package/skills/adia-ui-kit/references/recon.md +192 -0
- package/skills/adia-ui-kit/references/rendering-model.md +431 -0
- package/skills/adia-ui-kit/references/response-authoring.md +398 -0
- package/skills/adia-ui-kit/references/session-log-schema.md +203 -0
- package/skills/adia-ui-kit/references/session-log-template.md +118 -0
- package/skills/adia-ui-kit/references/setup.md +652 -0
- package/skills/adia-ui-kit/references/skill-hygiene.md +135 -0
- package/skills/adia-ui-kit/references/spec-to-ui-reasoning.md +394 -0
- package/skills/adia-ui-kit/references/state.md +229 -0
- package/skills/adia-ui-kit/references/teach-protocol.md +421 -0
- package/skills/adia-ui-kit/references/theme.md +142 -0
- package/skills/adia-ui-kit/references/version-check.md +69 -0
- package/skills/adia-ui-kit/scripts/audit-decision-trace.mjs +198 -0
- package/skills/adia-ui-kit/scripts/audit-kit-roster.mjs +366 -0
- package/skills/adia-ui-kit/scripts/changelog-archive.mjs +126 -0
- package/skills/adia-ui-kit/scripts/check-adia-versions.py +404 -0
- package/skills/adia-ui-kit/scripts/run-skill-evals.mjs +358 -0
- package/skills/adia-ui-kit/scripts/validate-session-log.mjs +242 -0
- package/skills/adia-ui-kit/skill.json +102 -0
- package/skills/adia-ui-migration/CHANGELOG.md +47 -0
- package/skills/adia-ui-migration/SKILL.md +381 -0
- package/skills/adia-ui-migration/skill.json +29 -0
- package/skills/adia-ui-spa/SKILL.md +1 -1
- package/skills/adia-ui-verify/SKILL.md +2 -2
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// audit-kit-roster.mjs — §SelfAudit enforcement for adia-ui-kit (factory/consumer edition).
|
|
3
|
+
// Standalone version — audit-axes inlined (no .agents/ import; safe in bare consumer repos).
|
|
4
|
+
//
|
|
5
|
+
// Axes 1–8 (universal) + Axis 9 = pattern/flow bundle-roster currency.
|
|
6
|
+
//
|
|
7
|
+
// This is the kit's structural self-audit — the mechanical check the
|
|
8
|
+
// §Plan-Execute-Verify loop and §Teach landing name as their verify target
|
|
9
|
+
// ("audit-kit-roster.mjs --strict"). It answers: does the SKILL.md itself still
|
|
10
|
+
// satisfy its structural invariants? (manifest ⊨ disk, reference graph resolves,
|
|
11
|
+
// version literals agree, every pattern/flow bundle the seed advertises exists).
|
|
12
|
+
//
|
|
13
|
+
// Usage (from the skill dir, or from anywhere with --dir):
|
|
14
|
+
// node scripts/audit-kit-roster.mjs
|
|
15
|
+
// node scripts/audit-kit-roster.mjs --json
|
|
16
|
+
// node scripts/audit-kit-roster.mjs --strict # exit 1 on any drift
|
|
17
|
+
// node scripts/audit-kit-roster.mjs --dir <path-to-skill-root>
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import process from 'node:process';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
|
|
24
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
+
const SCRIPT_DIR = path.dirname(__filename);
|
|
26
|
+
|
|
27
|
+
// Resolve skill dir: --dir flag or parent of scripts/
|
|
28
|
+
function resolveSkillDir(argv) {
|
|
29
|
+
const dirFlag = argv.find(a => a.startsWith('--dir='));
|
|
30
|
+
if (dirFlag) return path.resolve(dirFlag.slice('--dir='.length));
|
|
31
|
+
const idx = argv.indexOf('--dir');
|
|
32
|
+
if (idx >= 0 && argv[idx + 1]) return path.resolve(argv[idx + 1]);
|
|
33
|
+
return path.dirname(SCRIPT_DIR);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SKILL_DIR = resolveSkillDir(process.argv.slice(2));
|
|
37
|
+
const SKILL_MD = path.join(SKILL_DIR, 'SKILL.md');
|
|
38
|
+
const SKILL_JSON = path.join(SKILL_DIR, 'skill.json');
|
|
39
|
+
|
|
40
|
+
const read = (p) => { try { return fs.readFileSync(p, 'utf8'); } catch { return ''; } };
|
|
41
|
+
|
|
42
|
+
function parseArgs(argv) {
|
|
43
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
44
|
+
console.log(`audit-kit-roster.mjs — adia-ui-kit §SelfAudit (9 axes, factory/consumer edition).
|
|
45
|
+
|
|
46
|
+
USAGE
|
|
47
|
+
node scripts/audit-kit-roster.mjs [--json] [--strict] [--dir <skill-root>]
|
|
48
|
+
|
|
49
|
+
AXES (1-8 universal, inlined; 9 kit-specific)
|
|
50
|
+
1 Manifest enforcement — skill.json files[] matches disk (.claude/docs/ excluded)
|
|
51
|
+
2 Reference graph — every markdown link in SKILL.md resolves
|
|
52
|
+
3 Capability-menu drift — §ColdStartTriage entry refs exist (skipped if absent)
|
|
53
|
+
4 Version-literal parity — SKILL.md frontmatter version == skill.json
|
|
54
|
+
5 Phase-label absence — no stale "(Phase N)" annotations
|
|
55
|
+
6 Fence-leak detection — no headings orphaned inside code fences
|
|
56
|
+
7 Content currency — every files[] entry exists on disk
|
|
57
|
+
8 CLI helper currency — npm/node helpers cited in SKILL.md resolve
|
|
58
|
+
9 Bundle-roster currency — every references/patterns-*.md and
|
|
59
|
+
references/flows-*.md named in §Patterns/§Flows bundle maps exists on
|
|
60
|
+
disk AND is declared in skill.json files[].
|
|
61
|
+
|
|
62
|
+
EXIT CODES
|
|
63
|
+
0 All axes clean 1 At least one axis drifting (with --strict)
|
|
64
|
+
`);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
return { json: argv.includes('--json'), strict: argv.includes('--strict') };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── inlined audit-axes (universal axes 1-8) ─────────────────────────────────
|
|
71
|
+
|
|
72
|
+
function walkDir(dir, { include = null, exclude = ['node_modules', '.git', 'docs'] } = {}) {
|
|
73
|
+
const results = [];
|
|
74
|
+
if (!fs.existsSync(dir)) return results;
|
|
75
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
76
|
+
if (exclude.some(x => entry.name === x)) continue;
|
|
77
|
+
const fullPath = path.join(dir, entry.name);
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
results.push(...walkDir(fullPath, { include, exclude }));
|
|
80
|
+
} else {
|
|
81
|
+
if (!include || include.some(ext => entry.name.endsWith(ext))) {
|
|
82
|
+
results.push(fullPath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readSafe(p) {
|
|
90
|
+
try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseFrontmatter(content) {
|
|
94
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
95
|
+
if (!match) return {};
|
|
96
|
+
const result = {};
|
|
97
|
+
for (const line of match[1].split('\n')) {
|
|
98
|
+
const m = line.match(/^(\w+):\s*['"]?(.*?)['"]?\s*$/);
|
|
99
|
+
if (m) result[m[1]] = m[2];
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractMarkdownLinks(content, filePath) {
|
|
105
|
+
const links = [];
|
|
106
|
+
const lines = content.split('\n');
|
|
107
|
+
let inFence = false;
|
|
108
|
+
for (let i = 0; i < lines.length; i++) {
|
|
109
|
+
const line = lines[i];
|
|
110
|
+
if (/^```/.test(line)) { inFence = !inFence; continue; }
|
|
111
|
+
if (inFence) continue;
|
|
112
|
+
const lineStripped = line.replace(/`[^`]*`/g, '``');
|
|
113
|
+
const re = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
114
|
+
let m;
|
|
115
|
+
while ((m = re.exec(lineStripped)) !== null) {
|
|
116
|
+
const url = m[2];
|
|
117
|
+
if (url.startsWith('http') || url.startsWith('#') || url.startsWith('mailto:')) continue;
|
|
118
|
+
if (url.startsWith('~/')) continue;
|
|
119
|
+
if (/<[^>]+>/.test(url)) continue;
|
|
120
|
+
if (/^[a-z]+$/.test(url) && !url.includes('/')) continue;
|
|
121
|
+
const cleanUrl = url.split('#')[0];
|
|
122
|
+
if (!cleanUrl) continue;
|
|
123
|
+
links.push({ text: m[1], url: cleanUrl, line: i + 1, file: filePath });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return links;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function finding(type, message, extra = {}) {
|
|
130
|
+
return { type, message, ...extra };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function axis1_manifestEnforcement(ctx) {
|
|
134
|
+
const { skillDir, skillJson } = ctx;
|
|
135
|
+
const pkg = JSON.parse(readSafe(skillJson) || '{}');
|
|
136
|
+
const declared = new Set((pkg.files || []).map(f => path.resolve(skillDir, f)));
|
|
137
|
+
const onDisk = new Set();
|
|
138
|
+
|
|
139
|
+
for (const entry of fs.readdirSync(skillDir, { withFileTypes: true })) {
|
|
140
|
+
if (!entry.isFile()) continue;
|
|
141
|
+
if (!/\.(md|json|mjs|js)$/.test(entry.name)) continue;
|
|
142
|
+
onDisk.add(path.join(skillDir, entry.name));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (const subDir of ['references', 'scripts', 'evals', 'assets']) {
|
|
146
|
+
const full = path.join(skillDir, subDir);
|
|
147
|
+
for (const f of walkDir(full, { include: ['.md', '.json', '.mjs', '.js', '.ts', '.py'] })) {
|
|
148
|
+
onDisk.add(f);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const undeclared = [...onDisk].filter(f => !declared.has(f)).map(f => path.relative(skillDir, f));
|
|
153
|
+
const missing = [...declared].filter(f => !onDisk.has(f)).map(f => path.relative(skillDir, f));
|
|
154
|
+
const status = (undeclared.length + missing.length) > 0 ? 'drift' : 'ok';
|
|
155
|
+
const findings = [
|
|
156
|
+
...undeclared.map(f => finding('undeclared', `on disk but not in files[]: ${f}`, { path: f })),
|
|
157
|
+
...missing.map(f => finding('missing', `in files[] but not on disk: ${f}`, { path: f })),
|
|
158
|
+
];
|
|
159
|
+
return { axis: 'manifestEnforcement', axis_num: 1, status, findings, summary: `${undeclared.length} undeclared, ${missing.length} missing` };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function axis2_referenceGraph(ctx) {
|
|
163
|
+
const { skillDir, skillMd } = ctx;
|
|
164
|
+
const findings = [];
|
|
165
|
+
const content = readSafe(skillMd);
|
|
166
|
+
const links = extractMarkdownLinks(content, skillMd);
|
|
167
|
+
for (const { url, line, file } of links) {
|
|
168
|
+
const resolved = path.resolve(path.dirname(file), url);
|
|
169
|
+
if (!fs.existsSync(resolved)) {
|
|
170
|
+
const relFile = path.relative(skillDir, file);
|
|
171
|
+
const relResolved = path.relative(skillDir, resolved);
|
|
172
|
+
findings.push(finding('broken-link', `${relFile}:${line} → ${url} (resolves to ${relResolved}, not found)`, { file: relFile, line, url }));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { axis: 'referenceGraph', axis_num: 2, status: findings.length > 0 ? 'drift' : 'ok', findings, summary: `${findings.length} broken link(s)` };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function axis3_capabilityMenuDrift(ctx) {
|
|
179
|
+
const { skillDir, skillMd } = ctx;
|
|
180
|
+
const content = readSafe(skillMd);
|
|
181
|
+
const sectionMatch = content.match(/##\s+§ColdStartTriage[\s\S]*?(?=\n##\s+|$)/);
|
|
182
|
+
if (!sectionMatch) {
|
|
183
|
+
return { axis: 'capabilityMenuDrift', axis_num: 3, status: 'ok', findings: [], summary: 'No §ColdStartTriage section found (skip)' };
|
|
184
|
+
}
|
|
185
|
+
const section = sectionMatch[0];
|
|
186
|
+
const links = extractMarkdownLinks(section, skillMd);
|
|
187
|
+
const barePaths = [...section.matchAll(/`(references\/[^`]+\.md)`/g)].map(m => m[1]);
|
|
188
|
+
const findings = [];
|
|
189
|
+
for (const { url, text } of links) {
|
|
190
|
+
if (!url.startsWith('references/') && !url.startsWith('../')) continue;
|
|
191
|
+
const resolved = path.resolve(skillDir, url);
|
|
192
|
+
if (!fs.existsSync(resolved)) findings.push(finding('missing-entry-ref', `Mode entry ref missing: [${text}](${url})`, { url }));
|
|
193
|
+
}
|
|
194
|
+
for (const p of barePaths) {
|
|
195
|
+
const resolved = path.join(skillDir, p);
|
|
196
|
+
if (!fs.existsSync(resolved)) findings.push(finding('missing-entry-ref', `Bare path in §ColdStartTriage not found: ${p}`, { url: p }));
|
|
197
|
+
}
|
|
198
|
+
return { axis: 'capabilityMenuDrift', axis_num: 3, status: findings.length > 0 ? 'drift' : 'ok', findings, summary: `${findings.length} missing entry-reference path(s)` };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function axis4_versionLiteralParity(ctx) {
|
|
202
|
+
const { skillMd, skillJson } = ctx;
|
|
203
|
+
const content = readSafe(skillMd);
|
|
204
|
+
const pkg = JSON.parse(readSafe(skillJson) || '{}');
|
|
205
|
+
const jsonVersion = pkg.version || null;
|
|
206
|
+
const findings = [];
|
|
207
|
+
if (!jsonVersion) {
|
|
208
|
+
findings.push(finding('no-version', 'skill.json has no version field'));
|
|
209
|
+
return { axis: 'versionLiteralParity', axis_num: 4, status: 'drift', findings, summary: 'no version in skill.json' };
|
|
210
|
+
}
|
|
211
|
+
const fm = parseFrontmatter(content);
|
|
212
|
+
if (fm.version && fm.version !== jsonVersion) {
|
|
213
|
+
findings.push(finding('frontmatter-drift', `SKILL.md frontmatter version: ${fm.version} != skill.json: ${jsonVersion}`));
|
|
214
|
+
}
|
|
215
|
+
const statusMatch = content.match(/##\s+§Status([\s\S]*?)(?=\n##\s+|$)/);
|
|
216
|
+
if (statusMatch) {
|
|
217
|
+
const versionRe = /\bv?(\d+\.\d+\.\d+)\b/g;
|
|
218
|
+
let m;
|
|
219
|
+
while ((m = versionRe.exec(statusMatch[1])) !== null) {
|
|
220
|
+
if (m[1] !== jsonVersion) findings.push(finding('status-section-stale', `§Status body contains version ${m[1]} != skill.json ${jsonVersion}`));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { axis: 'versionLiteralParity', axis_num: 4, status: findings.length > 0 ? 'drift' : 'ok', findings, summary: findings.length > 0 ? `${findings.length} version-literal mismatch(es)` : `all literals match ${jsonVersion}` };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function axis5_phaseLabelAbsence(ctx) {
|
|
227
|
+
const { skillMd } = ctx;
|
|
228
|
+
const content = readSafe(skillMd);
|
|
229
|
+
const lines = content.split('\n');
|
|
230
|
+
const findings = [];
|
|
231
|
+
const phaseRe = /\(Phase\s+\d+[\w\s—.]*\)|Phase\s+\d+\s*\(v\d+\.\d+\.\d+|Phase\s+\d+\s+references\s*\(planned\)/i;
|
|
232
|
+
for (let i = 0; i < lines.length; i++) {
|
|
233
|
+
if (phaseRe.test(lines[i])) findings.push(finding('phase-label', `Line ${i + 1}: stale phase annotation — ${lines[i].trim()}`, { line: i + 1 }));
|
|
234
|
+
}
|
|
235
|
+
return { axis: 'phaseLabelAbsence', axis_num: 5, status: findings.length > 0 ? 'drift' : 'ok', findings, summary: `${findings.length} stale phase label(s)` };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function axis6_fenceLeakDetection(ctx) {
|
|
239
|
+
const { skillMd } = ctx;
|
|
240
|
+
const content = readSafe(skillMd);
|
|
241
|
+
const lines = content.split('\n');
|
|
242
|
+
const findings = [];
|
|
243
|
+
let inFence = false;
|
|
244
|
+
let fenceLang = '';
|
|
245
|
+
for (let i = 0; i < lines.length; i++) {
|
|
246
|
+
const line = lines[i];
|
|
247
|
+
const fenceOpen = line.match(/^```(\w*)/);
|
|
248
|
+
if (fenceOpen && !inFence) { inFence = true; fenceLang = fenceOpen[1].toLowerCase(); continue; }
|
|
249
|
+
if (inFence && line.startsWith('```') && !line.startsWith('````')) { inFence = false; fenceLang = ''; continue; }
|
|
250
|
+
if (inFence) {
|
|
251
|
+
if (['markdown', 'md', 'mdx'].includes(fenceLang)) continue;
|
|
252
|
+
if (/^#{2,3}\s/.test(line)) findings.push(finding('fence-leak', `Line ${i + 1}: heading inside \`\`\`${fenceLang}\`\`\` fence — ${line.trim()}`, { line: i + 1 }));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return { axis: 'fenceLeakDetection', axis_num: 6, status: findings.length > 0 ? 'drift' : 'ok', findings, summary: `${findings.length} fence-leak heading(s)` };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function axis7_contentCurrency(ctx) {
|
|
259
|
+
const { skillDir, skillJson } = ctx;
|
|
260
|
+
const pkg = JSON.parse(readSafe(skillJson) || '{}');
|
|
261
|
+
const declared = pkg.files || [];
|
|
262
|
+
const findings = [];
|
|
263
|
+
for (const relPath of declared) {
|
|
264
|
+
const full = path.resolve(skillDir, relPath);
|
|
265
|
+
if (!fs.existsSync(full)) findings.push(finding('missing-declared', `Declared in files[] but not on disk: ${relPath}`, { path: relPath }));
|
|
266
|
+
}
|
|
267
|
+
return { axis: 'contentCurrency', axis_num: 7, status: findings.length > 0 ? 'drift' : 'ok', findings, summary: `${findings.length} declared-but-missing file(s)` };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function axis8_cliHelperCurrency(ctx) {
|
|
271
|
+
const { skillDir, skillMd, repoRoot } = ctx;
|
|
272
|
+
const content = readSafe(skillMd);
|
|
273
|
+
const findings = [];
|
|
274
|
+
let pkgScripts = null;
|
|
275
|
+
if (repoRoot) {
|
|
276
|
+
try { pkgScripts = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')).scripts || {}; } catch { /* no package.json */ }
|
|
277
|
+
}
|
|
278
|
+
const lines = content.split('\n');
|
|
279
|
+
for (let i = 0; i < lines.length; i++) {
|
|
280
|
+
const line = lines[i];
|
|
281
|
+
if (/(substrate.only|chat.ui.only)/i.test(line)) continue;
|
|
282
|
+
if (/^\s*(#|\/\/)/.test(line)) continue;
|
|
283
|
+
const npmRe = /`npm run ([\w:.-]+)`/g;
|
|
284
|
+
let m;
|
|
285
|
+
while ((m = npmRe.exec(line)) !== null) {
|
|
286
|
+
if (pkgScripts && !pkgScripts[m[1]]) findings.push(finding('missing-npm-script', `Line ${i + 1}: npm run ${m[1]} not found in package.json scripts`, { line: i + 1, script: m[1] }));
|
|
287
|
+
}
|
|
288
|
+
const nodeRe = /`node\s+(\.agents\/skills\/[^\s`]+|scripts\/[^\s`]+)`/g;
|
|
289
|
+
while ((m = nodeRe.exec(line)) !== null) {
|
|
290
|
+
const scriptPath = m[1];
|
|
291
|
+
const base = repoRoot || skillDir;
|
|
292
|
+
const resolved = path.resolve(base, scriptPath);
|
|
293
|
+
if (!fs.existsSync(resolved)) findings.push(finding('missing-script', `Line ${i + 1}: node ${scriptPath} — file not found`, { line: i + 1, scriptPath }));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return { axis: 'cliHelperCurrency', axis_num: 8, status: findings.length > 0 ? 'drift' : 'ok', findings, summary: `${findings.length} missing CLI helper(s)` };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ─── Axis 9 — kit-specific bundle-roster currency ─────────────────────────────
|
|
300
|
+
|
|
301
|
+
function axis9_bundleRosterCurrency() {
|
|
302
|
+
const content = read(SKILL_MD);
|
|
303
|
+
const declared = new Set((JSON.parse(read(SKILL_JSON) || '{}').files) || []);
|
|
304
|
+
const bundles = new Set();
|
|
305
|
+
const re = /`(references\/(?:patterns|flows)-[a-z0-9-]+\.md)`/g;
|
|
306
|
+
let m;
|
|
307
|
+
while ((m = re.exec(content)) !== null) bundles.add(m[1]);
|
|
308
|
+
|
|
309
|
+
const findings = [];
|
|
310
|
+
for (const b of [...bundles].sort()) {
|
|
311
|
+
const onDisk = fs.existsSync(path.join(SKILL_DIR, b));
|
|
312
|
+
if (!onDisk) {
|
|
313
|
+
findings.push({ type: 'bundle-missing-disk', message: `${b} advertised in a bundle map but not on disk` });
|
|
314
|
+
} else if (!declared.has(b)) {
|
|
315
|
+
findings.push({ type: 'bundle-undeclared', message: `${b} on disk + advertised but not in skill.json files[]` });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
axis: 'kitBundleRosterCurrency',
|
|
320
|
+
axis_num: 9,
|
|
321
|
+
status: findings.length > 0 ? 'drift' : 'ok',
|
|
322
|
+
findings,
|
|
323
|
+
summary: `${bundles.size} pattern/flow bundle(s) checked; ${findings.length} issue(s)`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function formatResults(results) {
|
|
328
|
+
const lines = [];
|
|
329
|
+
for (const r of results) {
|
|
330
|
+
const icon = r.status === 'ok' ? '✓' : '✗';
|
|
331
|
+
lines.push(`${icon} ${r.axis}: ${r.summary}`);
|
|
332
|
+
if (r.status === 'drift' && r.findings.length > 0) {
|
|
333
|
+
for (const f of r.findings) lines.push(` ${f.type}: ${f.message}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return lines.join('\n');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function main() {
|
|
340
|
+
const args = parseArgs(process.argv.slice(2));
|
|
341
|
+
const ctx = { skillDir: SKILL_DIR, skillMd: SKILL_MD, skillJson: SKILL_JSON };
|
|
342
|
+
const universal = [
|
|
343
|
+
axis1_manifestEnforcement(ctx),
|
|
344
|
+
axis2_referenceGraph(ctx),
|
|
345
|
+
axis3_capabilityMenuDrift(ctx),
|
|
346
|
+
axis4_versionLiteralParity(ctx),
|
|
347
|
+
axis5_phaseLabelAbsence(ctx),
|
|
348
|
+
axis6_fenceLeakDetection(ctx),
|
|
349
|
+
axis7_contentCurrency(ctx),
|
|
350
|
+
axis8_cliHelperCurrency(ctx),
|
|
351
|
+
];
|
|
352
|
+
const allResults = [...universal, axis9_bundleRosterCurrency()];
|
|
353
|
+
const driftCount = allResults.filter((r) => r.status === 'drift').length;
|
|
354
|
+
|
|
355
|
+
if (args.json) {
|
|
356
|
+
console.log(JSON.stringify({ results: allResults, driftCount }, null, 2));
|
|
357
|
+
} else {
|
|
358
|
+
console.log(`[audit-kit-roster] adia-ui-kit §SelfAudit — ${driftCount} drifting axis/axes`);
|
|
359
|
+
console.log(formatResults(allResults));
|
|
360
|
+
if (driftCount === 0) console.log('\n✓ All clean.');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (args.strict && driftCount > 0) process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
main();
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// changelog-archive.mjs — keep CHANGELOG.md lean; preserve full history in a per-major archive set.
|
|
3
|
+
//
|
|
4
|
+
// THE SYSTEM: the live CHANGELOG.md holds only the most recent N entries (default 3);
|
|
5
|
+
// every older entry lives in references/changelog-archive/CHANGELOG-v<major>.md (newest-first).
|
|
6
|
+
// Idempotent + re-runnable: run it after each cut and it rolls the now-oldest live entry
|
|
7
|
+
// into the archive. Re-running with nothing to roll is a no-op (only the archive index is
|
|
8
|
+
// regenerated). Merge-by-version + semver-sort means an entry is never duplicated or lost.
|
|
9
|
+
//
|
|
10
|
+
// Usage (from the skill dir, or anywhere with --dir):
|
|
11
|
+
// node scripts/changelog-archive.mjs # keep newest 3, archive the rest
|
|
12
|
+
// node scripts/changelog-archive.mjs --keep 5 # keep newest 5
|
|
13
|
+
// node scripts/changelog-archive.mjs --check # report what WOULD change; exit 1 if live > keep
|
|
14
|
+
//
|
|
15
|
+
// Entry heading shape: `## <major>.<minor>.<patch> (date)`. Non-version `##` lines (e.g. a
|
|
16
|
+
// "[Previously-unreleased …]" note) stay attached to the entry they sit under.
|
|
17
|
+
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import process from 'node:process';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const SKILL_DIR = path.dirname(SCRIPT_DIR);
|
|
25
|
+
|
|
26
|
+
function parseArgs(argv) {
|
|
27
|
+
const a = { keep: 3, check: false, dir: SKILL_DIR };
|
|
28
|
+
for (let i = 0; i < argv.length; i++) {
|
|
29
|
+
if (argv[i] === '--keep') a.keep = parseInt(argv[++i], 10);
|
|
30
|
+
else if (argv[i] === '--check') a.check = true;
|
|
31
|
+
else if (argv[i] === '--dir') a.dir = path.resolve(argv[++i]);
|
|
32
|
+
}
|
|
33
|
+
if (!Number.isInteger(a.keep) || a.keep < 1) { console.error('--keep must be a positive integer'); process.exit(2); }
|
|
34
|
+
return a;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const HEADING = /^## v?(\d+)\.(\d+)\.(\d+)\b/;
|
|
38
|
+
const cmpDesc = (a, b) => b.M - a.M || b.m - a.m || b.p - a.p;
|
|
39
|
+
|
|
40
|
+
// Split a changelog body into { preamble, entries[] }. Strips any trailing "## Archive"
|
|
41
|
+
// index section first (it is always regenerated, never parsed as an entry).
|
|
42
|
+
function parse(body) {
|
|
43
|
+
const archiveAt = body.search(/^## Archive\b/m);
|
|
44
|
+
if (archiveAt >= 0) body = body.slice(0, archiveAt).replace(/\s+$/, '') + '\n';
|
|
45
|
+
const lines = body.split('\n');
|
|
46
|
+
const idx = [];
|
|
47
|
+
for (let i = 0; i < lines.length; i++) if (HEADING.test(lines[i])) idx.push(i);
|
|
48
|
+
if (idx.length === 0) return { preamble: body.replace(/\s+$/, ''), entries: [] };
|
|
49
|
+
const preamble = lines.slice(0, idx[0]).join('\n').replace(/\s+$/, '');
|
|
50
|
+
const entries = idx.map((start, j) => {
|
|
51
|
+
const end = j + 1 < idx.length ? idx[j + 1] : lines.length;
|
|
52
|
+
const h = lines[start].match(HEADING);
|
|
53
|
+
return {
|
|
54
|
+
version: `${h[1]}.${h[2]}.${h[3]}`, major: h[1],
|
|
55
|
+
M: +h[1], m: +h[2], p: +h[3],
|
|
56
|
+
text: lines.slice(start, end).join('\n').replace(/\s+$/, ''),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
return { preamble, entries };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readEntries(file) {
|
|
63
|
+
if (!fs.existsSync(file)) return [];
|
|
64
|
+
return parse(fs.readFileSync(file, 'utf8')).entries;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function dedupe(entries) {
|
|
68
|
+
const seen = new Set(); const out = [];
|
|
69
|
+
for (const e of entries.sort(cmpDesc)) { if (seen.has(e.version)) continue; seen.add(e.version); out.push(e); }
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function main() {
|
|
74
|
+
const args = parseArgs(process.argv.slice(2));
|
|
75
|
+
const CHANGELOG = path.join(args.dir, 'CHANGELOG.md');
|
|
76
|
+
const ARCHIVE_DIR = path.join(args.dir, 'references/changelog-archive');
|
|
77
|
+
if (!fs.existsSync(CHANGELOG)) { console.error(`not found: ${CHANGELOG}`); process.exit(2); }
|
|
78
|
+
|
|
79
|
+
const { preamble, entries } = parse(fs.readFileSync(CHANGELOG, 'utf8'));
|
|
80
|
+
const keep = entries.slice(0, args.keep);
|
|
81
|
+
const toArchive = entries.slice(args.keep);
|
|
82
|
+
|
|
83
|
+
if (args.check) {
|
|
84
|
+
console.log(`[changelog-archive --check] ${entries.length} live entries; keep ${args.keep}; ${toArchive.length} over budget`);
|
|
85
|
+
process.exit(toArchive.length > 0 ? 1 : 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Group entries-to-archive by major, merge with existing archive files, rewrite each.
|
|
89
|
+
fs.mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
90
|
+
// Entries archived FROM CHANGELOG.md drop 2 dirs into references/changelog-archive/, so
|
|
91
|
+
// their skill-root-relative markdown links need `../../` prepended (the check-links depth
|
|
92
|
+
// bug caught on the kit v3.11.0 first cut). Already-archived entries re-read below are
|
|
93
|
+
// already corrected — reroot only the NEW ones, or the prefixes double up on re-runs.
|
|
94
|
+
const reroot = (text) => text.replace(/\]\((?!\.\.?\/|\/|https?:|#)([^)]+)\)/g, '](../../$1)');
|
|
95
|
+
const byMajor = {};
|
|
96
|
+
for (const e of toArchive) (byMajor[e.major] ||= []).push({ ...e, text: reroot(e.text) });
|
|
97
|
+
|
|
98
|
+
const written = [];
|
|
99
|
+
// Touch every major that has either new entries OR an existing archive file (to keep them current).
|
|
100
|
+
const majors = new Set([...Object.keys(byMajor), ...fs.readdirSync(ARCHIVE_DIR)
|
|
101
|
+
.map((f) => (f.match(/^CHANGELOG-v(\d+)\.md$/) || [])[1]).filter(Boolean)]);
|
|
102
|
+
for (const major of majors) {
|
|
103
|
+
const file = path.join(ARCHIVE_DIR, `CHANGELOG-v${major}.md`);
|
|
104
|
+
const merged = dedupe([...(byMajor[major] || []), ...readEntries(file)]);
|
|
105
|
+
if (merged.length === 0) continue;
|
|
106
|
+
const header = `# adia-ui-kit Changelog — v${major}.x archive\n\n` +
|
|
107
|
+
`> Historical entries rolled out of the live [\`CHANGELOG.md\`](../../CHANGELOG.md), which keeps only the most recent ${args.keep}. Newest-first.\n`;
|
|
108
|
+
fs.writeFileSync(file, `${header}\n${merged.map((e) => e.text).join('\n\n')}\n`);
|
|
109
|
+
written.push({ major, file, count: merged.length, range: `${merged[merged.length - 1].version} … ${merged[0].version}` });
|
|
110
|
+
}
|
|
111
|
+
written.sort((a, b) => b.major - a.major);
|
|
112
|
+
|
|
113
|
+
// Rebuild CHANGELOG.md: preamble + kept entries + archive index.
|
|
114
|
+
let out = `${preamble}\n\n${keep.map((e) => e.text).join('\n\n')}\n`;
|
|
115
|
+
if (written.length) {
|
|
116
|
+
out += `\n## Archive\n\nOlder entries live in a per-major archive set (kept out of this file to hold its always-on cost down):\n\n`;
|
|
117
|
+
for (const w of written) out += `- [\`references/changelog-archive/CHANGELOG-v${w.major}.md\`](references/changelog-archive/CHANGELOG-v${w.major}.md) — v${w.major}.x · ${w.count} entries (${w.range})\n`;
|
|
118
|
+
}
|
|
119
|
+
fs.writeFileSync(CHANGELOG, out);
|
|
120
|
+
|
|
121
|
+
const kb = (p) => (fs.statSync(p).size / 1024).toFixed(0);
|
|
122
|
+
console.log(`[changelog-archive] kept newest ${keep.length} in CHANGELOG.md (${kb(CHANGELOG)} KB); archived ${toArchive.length} entries:`);
|
|
123
|
+
for (const w of written) console.log(` v${w.major}.x → ${path.relative(args.dir, w.file)} — ${w.count} entries (${w.range}), ${kb(w.file)} KB`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
main();
|