ai-design-context 0.4.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/AGENTS.md +156 -0
- package/CHANGELOG.md +71 -0
- package/CODE_OF_CONDUCT.md +29 -0
- package/CONTRIBUTING.md +128 -0
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/RELEASE_NOTES_v0.1.0.md +45 -0
- package/RELEASE_NOTES_v0.3.0.md +47 -0
- package/RELEASE_NOTES_v0.4.0.md +29 -0
- package/RELEASE_READINESS.md +107 -0
- package/ROADMAP.md +60 -0
- package/SECURITY.md +40 -0
- package/assets/logo.svg +9 -0
- package/benchmarks/README.md +19 -0
- package/benchmarks/chat.md +37 -0
- package/benchmarks/mobile-home-screen.md +38 -0
- package/benchmarks/notes-app.md +37 -0
- package/benchmarks/settings.md +37 -0
- package/benchmarks/shopping-list.md +37 -0
- package/benchmarks/todo-app.md +144 -0
- package/checklists/DESIGN_QA.md +102 -0
- package/checklists/GENERATED_INDEX.md +7 -0
- package/docs/AGENT_CONTEXT.md +111 -0
- package/docs/BENCHMARK.md +170 -0
- package/docs/DESIGNLINT_READINESS.md +110 -0
- package/docs/DESIGN_SYSTEM.md +23 -0
- package/docs/EVALUATION_RUBRIC.md +154 -0
- package/docs/GITHUB_SETUP.md +28 -0
- package/docs/GLOSSARY.md +45 -0
- package/docs/INDEX.md +109 -0
- package/docs/KNOWLEDGE_ENGINE.md +594 -0
- package/docs/MOBILE_FIRST.md +18 -0
- package/docs/NPM_RELEASE.md +43 -0
- package/docs/PATTERN_SPEC.md +113 -0
- package/docs/PHILOSOPHY.md +25 -0
- package/docs/PRODUCT_THINKING.md +26 -0
- package/docs/STYLE_GUIDE.md +49 -0
- package/docs/TRACEABILITY.md +45 -0
- package/evidence/README.md +64 -0
- package/evidence/TEMPLATE.md +46 -0
- package/evidence/chat/.gitkeep +1 -0
- package/evidence/mobile-home/.gitkeep +1 -0
- package/evidence/notes/.gitkeep +1 -0
- package/evidence/settings/.gitkeep +1 -0
- package/evidence/shopping/.gitkeep +1 -0
- package/evidence/todo/.gitkeep +1 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/EVALUATION.md +66 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/README.md +40 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/generated-output.md +163 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/metadata.json +38 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/notes.md +20 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/prompt.md +16 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/ai-design-rules/scores.md +18 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/baseline/generated-output.md +98 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/baseline/metadata.json +19 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/baseline/notes.md +19 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/baseline/prompt.md +9 -0
- package/evidence/todo/2026-06-25-codex-gpt-5/baseline/scores.md +18 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/EVALUATION.md +70 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/README.md +52 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/GENERATION_NOTES.md +133 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/app.js +289 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/context/context-preserving-preview.json +443 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/context/daily-home-surface.json +373 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/context/quick-capture.json +301 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/context/sources-read.txt +36 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/generated-output.md +11 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/index.html +64 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/launch-prompt.md +1 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/metadata.json +55 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/notes.md +15 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/parent-status-message.md +1 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/prompt.md +49 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/runtime-checks.json +776 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/scores.md +20 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-completed.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-default.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-detail-reduced-motion.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-detail.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-empty.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-keyboard-focus.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-save-error.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-saved.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-saving.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/desktop-validation.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-completed.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-default.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-detail-reduced-motion.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-detail.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-empty.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-keyboard-focus.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-save-error.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-saved.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-saving.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/screenshots/mobile-validation.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/source-hashes.json +5 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/ai-design-rules/styles.css +122 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/GENERATION_NOTES.md +86 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/app.js +303 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/generated-output.md +11 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/index.html +71 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/launch-prompt.md +1 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/metadata.json +55 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/notes.md +15 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/parent-status-message.md +1 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/prompt.md +49 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/runtime-checks.json +772 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/scores.md +20 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-completed.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-default.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-detail-reduced-motion.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-detail.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-empty.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-keyboard-focus.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-save-error.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-saved.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-saving.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/desktop-validation.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-completed.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-default.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-detail-reduced-motion.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-detail.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-empty.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-keyboard-focus.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-save-error.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-saved.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-saving.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/screenshots/mobile-validation.png +0 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/source-hashes.json +5 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/baseline/styles.css +9 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/capture-ai-design-rules.js +60 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/capture-baseline.js +60 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/common/brief.md +35 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/common/knowledge-files.json +127 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/common/knowledge.patch +890 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/common/review-context.json +550 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/common/seed.json +86 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/common/setup.json +47 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/common/technical-envelope.md +11 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/layout-metrics.json +133 -0
- package/evidence/todo/2026-09-28-codex-paired-todo/layout-probe.js +15 -0
- package/examples/GENERATED_INDEX.md +8 -0
- package/examples/INDEX.md +16 -0
- package/examples/README.md +15 -0
- package/examples/consequential-action-confirmation-reference.md +83 -0
- package/examples/todo-app-benchmark.md +117 -0
- package/examples/todo-reference/README.md +29 -0
- package/examples/todo-reference/app.js +154 -0
- package/examples/todo-reference/favicon.svg +4 -0
- package/examples/todo-reference/index.html +67 -0
- package/examples/todo-reference/review-evidence/2026-07-15/desktop-default.png +0 -0
- package/examples/todo-reference/review-evidence/2026-07-15/desktop-detail.png +0 -0
- package/examples/todo-reference/review-evidence/2026-07-15/mobile-detail.png +0 -0
- package/examples/todo-reference/review-evidence/2026-07-15/mobile-error.png +0 -0
- package/examples/todo-reference/styles.css +241 -0
- package/graph/GENERATED_GRAPH.md +71 -0
- package/observations/README.md +23 -0
- package/observations/accessibility/reduced-interaction-motion.md +49 -0
- package/observations/accessibility/textual-error-recovery.md +50 -0
- package/observations/accessibility/visible-keyboard-focus.md +49 -0
- package/observations/ai/agent-consequential-action-controls.md +56 -0
- package/observations/ai/agent-work-visibility-and-intervention.md +51 -0
- package/observations/ai/figma-agent-context-and-skills.md +52 -0
- package/observations/finance/high-risk-wallet-recovery-flows.md +55 -0
- package/observations/finance/market-interface-adaptive-trading-surfaces.md +55 -0
- package/observations/interaction/chrome-scoped-view-transitions.md +52 -0
- package/observations/interaction/figma-motion-as-system-capability.md +53 -0
- package/observations/mobile/mobile-bottom-sheet-choice-contracts.md +55 -0
- package/observations/performance/reserved-loading-space.md +51 -0
- package/observations/product-repos/pinned-ai-design-rules-integration.md +49 -0
- package/observations/visual/apple-liquid-glass-functional-layer.md +52 -0
- package/observations/visual/chrome-content-first-adaptive-ui.md +51 -0
- package/observations/visual/material-3-expressive-system.md +52 -0
- package/package.json +65 -0
- package/patterns/GENERATED_INDEX.md +13 -0
- package/patterns/INDEX.md +41 -0
- package/patterns/README.md +5 -0
- package/patterns/consequential-action-confirmation.md +127 -0
- package/patterns/context-preserving-preview.md +145 -0
- package/patterns/daily-home-surface.md +134 -0
- package/patterns/mobile-primary-action.md +110 -0
- package/patterns/object-status-list.md +114 -0
- package/patterns/progressive-detail.md +113 -0
- package/patterns/quick-capture.md +132 -0
- package/prompts/CONSEQUENTIAL_ACTION_REVIEW.md +100 -0
- package/prompts/DESIGN_EVIDENCE_REVIEW.md +49 -0
- package/prompts/GENERATED_INDEX.md +11 -0
- package/prompts/INDEX.md +24 -0
- package/prompts/PROTOTYPE_REVIEW.md +189 -0
- package/prompts/QUICK_CAPTURE_STATE_REVIEW.md +74 -0
- package/prompts/TODO_APP_BENCHMARK.md +88 -0
- package/registry/objects.json +568 -0
- package/registry/relationships.json +972 -0
- package/research/GENERATED_INDEX.md +18 -0
- package/research/INDEX.md +40 -0
- package/research/accessibility/keyboard-focus-and-interaction-motion.md +62 -0
- package/research/accessibility/textual-error-recovery.md +57 -0
- package/research/performance/reserved-loading-space.md +56 -0
- package/research/products/apple-reminders.md +59 -0
- package/research/products/arc.md +61 -0
- package/research/products/linear.md +62 -0
- package/research/products/telegram.md +61 -0
- package/research/products/things-3.md +60 -0
- package/research/ux/contextual-agentic-interface.md +68 -0
- package/research/ux/high-risk-operational-flows.md +89 -0
- package/research/ux/motion-as-state-continuity.md +71 -0
- package/research/visual/expressive-system-ui-2026.md +75 -0
- package/reviews/GENERATED_INDEX.md +9 -0
- package/reviews/README.md +25 -0
- package/reviews/consequential-action-confirmation-review.md +92 -0
- package/reviews/todo-app-benchmark-reference-review.md +86 -0
- package/reviews/todo-reference-fixture-review.md +77 -0
- package/rules/GENERATED_INDEX.md +23 -0
- package/rules/INDEX.md +37 -0
- package/rules/accessibility/A11Y-001.md +58 -0
- package/rules/accessibility/A11Y-002.md +55 -0
- package/rules/accessibility/A11Y-003.md +54 -0
- package/rules/accessibility/A11Y-004.md +54 -0
- package/rules/ia/IA-001.md +60 -0
- package/rules/ia/IA-002.md +61 -0
- package/rules/performance/PERF-001.md +55 -0
- package/rules/product/PRD-001.md +61 -0
- package/rules/product/PRD-002.md +61 -0
- package/rules/ux/UX-001.md +58 -0
- package/rules/ux/UX-002.md +61 -0
- package/rules/ux/UX-003.md +60 -0
- package/rules/ux/UX-004.md +61 -0
- package/rules/ux/UX-005.md +62 -0
- package/rules/ux/UX-006.md +69 -0
- package/rules/visual/VIS-001.md +59 -0
- package/rules/visual/VIS-002.md +59 -0
- package/schema/checklist.schema.json +21 -0
- package/schema/common.schema.json +178 -0
- package/schema/observation.schema.json +21 -0
- package/schema/pattern.schema.json +21 -0
- package/schema/prompt.schema.json +21 -0
- package/schema/reference-project.schema.json +21 -0
- package/schema/research.schema.json +21 -0
- package/schema/review.schema.json +21 -0
- package/schema/rule.schema.json +21 -0
- package/skills/README.md +48 -0
- package/skills/accessibility-reviewer/SKILL.md +101 -0
- package/skills/agent-context/SKILL.md +34 -0
- package/skills/agent-context/agents/openai.yaml +4 -0
- package/skills/design-evidence-researcher/SKILL.md +47 -0
- package/skills/design-evidence-researcher/agents/openai.yaml +4 -0
- package/skills/design-reviewer/SKILL.md +113 -0
- package/skills/design-system-architect/SKILL.md +101 -0
- package/skills/information-architect/SKILL.md +97 -0
- package/skills/interaction-designer/SKILL.md +104 -0
- package/skills/knowledge-graph-architect/SKILL.md +96 -0
- package/skills/mobile-ux-expert/SKILL.md +105 -0
- package/skills/motion-designer/SKILL.md +100 -0
- package/skills/performance-reviewer/SKILL.md +97 -0
- package/skills/product-designer/SKILL.md +104 -0
- package/skills/prompt-architect/SKILL.md +105 -0
- package/skills/reference-driven-design/SKILL.md +119 -0
- package/skills/ux-reviewer/SKILL.md +99 -0
- package/skills/visual-designer/SKILL.md +107 -0
- package/starter-kit/AGENTS.md +37 -0
- package/starter-kit/BOOTSTRAP.md +13 -0
- package/starter-kit/INSTALL_WITH_AGENT.md +133 -0
- package/starter-kit/PROJECT_INTEGRATION.md +60 -0
- package/starter-kit/README.md +41 -0
- package/starter-kit/benchmarks/BENCHMARK_CHECKLIST.md +10 -0
- package/starter-kit/docs/DESIGN_DECISIONS.md +22 -0
- package/starter-kit/docs/INFORMATION_ARCHITECTURE.md +25 -0
- package/starter-kit/docs/PERSONAS.md +14 -0
- package/starter-kit/docs/PRD.md +32 -0
- package/starter-kit/docs/USER_FLOWS.md +19 -0
- package/starter-kit/reviews/DESIGN_REVIEW.md +49 -0
- package/starter-kit/templates/FEATURE_TEMPLATE.md +36 -0
- package/starter-kit/templates/TASK_TEMPLATE.md +22 -0
- package/templates/CHECKLIST_TEMPLATE.md +43 -0
- package/templates/OBSERVATION_TEMPLATE.md +41 -0
- package/templates/PATTERN_TEMPLATE.md +100 -0
- package/templates/PROMPT_TEMPLATE.md +46 -0
- package/templates/PROTOTYPE_REVIEW.md +50 -0
- package/templates/REFERENCE_PROJECT_TEMPLATE.md +49 -0
- package/templates/REVIEW_TEMPLATE.md +55 -0
- package/templates/RULE_TEMPLATE.md +57 -0
- package/tools/cli.mjs +46 -0
- package/tools/context.mjs +342 -0
- package/tools/designlint.mjs +77 -0
- package/tools/generate-indexes.mjs +621 -0
- package/tools/init.mjs +99 -0
- package/tools/validate-benchmark-evidence.mjs +248 -0
- package/tools/validate-knowledge.mjs +481 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { generateIndexFiles } from './generate-indexes.mjs';
|
|
4
|
+
|
|
5
|
+
const root = process.cwd();
|
|
6
|
+
const requiredFields = [
|
|
7
|
+
'id',
|
|
8
|
+
'alias',
|
|
9
|
+
'slug',
|
|
10
|
+
'title',
|
|
11
|
+
'object_type',
|
|
12
|
+
'status',
|
|
13
|
+
'version',
|
|
14
|
+
'category',
|
|
15
|
+
'tags',
|
|
16
|
+
'maturity',
|
|
17
|
+
'risk_level',
|
|
18
|
+
'relationships',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const idPrefixes = {
|
|
22
|
+
observation: 'OBS-',
|
|
23
|
+
research: 'RESEARCH-',
|
|
24
|
+
rule: 'RULE-',
|
|
25
|
+
pattern: 'PAT-',
|
|
26
|
+
prompt: 'PROMPT-',
|
|
27
|
+
checklist: 'CHECK-',
|
|
28
|
+
review: 'REVIEW-',
|
|
29
|
+
reference_project: 'REF-',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const registryBackedObjectTypes = new Set([
|
|
33
|
+
'research',
|
|
34
|
+
'rule',
|
|
35
|
+
'pattern',
|
|
36
|
+
'prompt',
|
|
37
|
+
'checklist',
|
|
38
|
+
'review',
|
|
39
|
+
'reference_project',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const allowedRelationshipTypes = new Set([
|
|
43
|
+
'derived_from',
|
|
44
|
+
'cites',
|
|
45
|
+
'inspired_by',
|
|
46
|
+
'requires',
|
|
47
|
+
'validates',
|
|
48
|
+
'implements',
|
|
49
|
+
'related_to',
|
|
50
|
+
'supersedes',
|
|
51
|
+
'deprecated_by',
|
|
52
|
+
'replaced_by',
|
|
53
|
+
]);
|
|
54
|
+
const maturityLevels = new Set(['seed', 'reviewed', 'validated', 'canonical']);
|
|
55
|
+
|
|
56
|
+
function readJson(filePath) {
|
|
57
|
+
return JSON.parse(fs.readFileSync(path.join(root, filePath), 'utf8'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function findDuplicateJsonKeyErrors(filePath) {
|
|
61
|
+
const raw = fs.readFileSync(path.join(root, filePath), 'utf8');
|
|
62
|
+
const errors = [];
|
|
63
|
+
|
|
64
|
+
for (const objectMatch of raw.matchAll(/\{([^{}]*)\}/g)) {
|
|
65
|
+
const keys = new Set();
|
|
66
|
+
|
|
67
|
+
for (const keyMatch of objectMatch[1].matchAll(/"([^"\\]+)"\s*:/g)) {
|
|
68
|
+
const key = keyMatch[1];
|
|
69
|
+
if (keys.has(key)) {
|
|
70
|
+
errors.push(`${filePath}: duplicate JSON key ${key}`);
|
|
71
|
+
}
|
|
72
|
+
keys.add(key);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return errors;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function collectMarkdownFiles(dir) {
|
|
80
|
+
const entries = fs.readdirSync(path.join(root, dir), { withFileTypes: true });
|
|
81
|
+
const files = [];
|
|
82
|
+
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const fullPath = path.join(dir, entry.name);
|
|
85
|
+
|
|
86
|
+
if (entry.isDirectory()) {
|
|
87
|
+
files.push(...collectMarkdownFiles(fullPath));
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!entry.name.endsWith('.md')) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (entry.name === 'INDEX.md' || entry.name === 'README.md' || entry.name.startsWith('GENERATED_')) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
files.push(fullPath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return files;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseScalar(value) {
|
|
106
|
+
const trimmed = value.trim();
|
|
107
|
+
if (trimmed === '[]') {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
112
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
113
|
+
) {
|
|
114
|
+
return trimmed.slice(1, -1);
|
|
115
|
+
}
|
|
116
|
+
return trimmed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function validateSkillMetadata(filePath, metadata, errors) {
|
|
120
|
+
const skillName = path.basename(path.dirname(filePath));
|
|
121
|
+
|
|
122
|
+
if (metadata.name !== skillName) {
|
|
123
|
+
errors.push(`${filePath}: skill name must match directory ${skillName}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(metadata.name ?? '')) {
|
|
127
|
+
errors.push(`${filePath}: skill name must use lowercase kebab-case`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (typeof metadata.description !== 'string' || metadata.description.trim() === '') {
|
|
131
|
+
errors.push(`${filePath}: skill description must be a non-empty string`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function relationshipKey(relationship) {
|
|
136
|
+
return `${relationship.type}:${relationship.source}:${relationship.target}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function parseFrontMatter(markdown, filePath) {
|
|
140
|
+
if (!markdown.startsWith('---\n')) {
|
|
141
|
+
throw new Error(`${filePath}: missing YAML front matter`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const end = markdown.indexOf('\n---', 4);
|
|
145
|
+
if (end === -1) {
|
|
146
|
+
throw new Error(`${filePath}: unterminated YAML front matter`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const yaml = markdown.slice(4, end).split('\n');
|
|
150
|
+
const metadata = {};
|
|
151
|
+
let currentKey = null;
|
|
152
|
+
let currentRelationship = null;
|
|
153
|
+
|
|
154
|
+
for (const rawLine of yaml) {
|
|
155
|
+
const line = rawLine.replace(/\s+$/, '');
|
|
156
|
+
if (!line.trim()) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const topLevel = line.match(/^([a-z_]+):(?:\s*(.*))?$/);
|
|
161
|
+
if (topLevel) {
|
|
162
|
+
currentKey = topLevel[1];
|
|
163
|
+
const value = topLevel[2] ?? '';
|
|
164
|
+
|
|
165
|
+
if (value === '') {
|
|
166
|
+
metadata[currentKey] = [];
|
|
167
|
+
} else {
|
|
168
|
+
metadata[currentKey] = parseScalar(value);
|
|
169
|
+
}
|
|
170
|
+
currentRelationship = null;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const listItem = line.match(/^ -\s*(.*)$/);
|
|
175
|
+
if (listItem && currentKey) {
|
|
176
|
+
if (!Array.isArray(metadata[currentKey])) {
|
|
177
|
+
throw new Error(`${filePath}: ${currentKey} must be an array`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const item = listItem[1];
|
|
181
|
+
const inlinePair = item.match(/^([a-z_]+):\s*(.*)$/);
|
|
182
|
+
|
|
183
|
+
if (inlinePair) {
|
|
184
|
+
currentRelationship = {
|
|
185
|
+
[inlinePair[1]]: parseScalar(inlinePair[2]),
|
|
186
|
+
};
|
|
187
|
+
metadata[currentKey].push(currentRelationship);
|
|
188
|
+
} else {
|
|
189
|
+
metadata[currentKey].push(parseScalar(item));
|
|
190
|
+
currentRelationship = null;
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const nestedPair = line.match(/^ ([a-z_]+):\s*(.*)$/);
|
|
196
|
+
if (nestedPair && currentRelationship) {
|
|
197
|
+
currentRelationship[nestedPair[1]] = parseScalar(nestedPair[2]);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
throw new Error(`${filePath}: unsupported front matter line "${line}"`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return metadata;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function validateMetadata(filePath, metadata, registryIds, errors) {
|
|
208
|
+
for (const field of requiredFields) {
|
|
209
|
+
if (!(field in metadata)) {
|
|
210
|
+
errors.push(`${filePath}: missing required field ${field}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const prefix = idPrefixes[metadata.object_type];
|
|
215
|
+
if (!prefix) {
|
|
216
|
+
errors.push(`${filePath}: unsupported object_type ${metadata.object_type}`);
|
|
217
|
+
} else if (!String(metadata.id || '').startsWith(prefix)) {
|
|
218
|
+
errors.push(`${filePath}: id ${metadata.id} does not match ${metadata.object_type}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!Array.isArray(metadata.tags) || metadata.tags.length === 0) {
|
|
222
|
+
errors.push(`${filePath}: tags must be a non-empty array`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!maturityLevels.has(metadata.maturity)) {
|
|
226
|
+
errors.push(`${filePath}: unsupported maturity ${metadata.maturity}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (metadata.maturity === 'seed' && metadata.status !== 'draft') {
|
|
230
|
+
errors.push(`${filePath}: seed maturity requires draft status`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (['reviewed', 'validated', 'canonical'].includes(metadata.maturity)) {
|
|
234
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(metadata.last_reviewed_at ?? '')) {
|
|
235
|
+
errors.push(`${filePath}: ${metadata.maturity} maturity requires last_reviewed_at in YYYY-MM-DD format`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (['validated', 'canonical'].includes(metadata.maturity) && metadata.status !== 'active') {
|
|
240
|
+
errors.push(`${filePath}: ${metadata.maturity} maturity requires active status`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (metadata.maturity === 'canonical' && !/^[1-9]\d*\.\d+\.\d+$/.test(metadata.version ?? '')) {
|
|
244
|
+
errors.push(`${filePath}: canonical maturity requires a stable major version`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!Array.isArray(metadata.relationships)) {
|
|
248
|
+
errors.push(`${filePath}: relationships must be an array`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const relationship of metadata.relationships) {
|
|
253
|
+
if (!relationship.type || !relationship.target) {
|
|
254
|
+
errors.push(`${filePath}: relationship must include type and target`);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!allowedRelationshipTypes.has(relationship.type)) {
|
|
259
|
+
errors.push(`${filePath}: unsupported relationship type ${relationship.type}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (/^[A-Z]+-\d{5}$/.test(relationship.target)) {
|
|
263
|
+
if (!registryIds.has(relationship.target)) {
|
|
264
|
+
errors.push(`${filePath}: relationship target ${relationship.target} is not in registry`);
|
|
265
|
+
}
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const targetPath = path.join(root, relationship.target);
|
|
270
|
+
if (!fs.existsSync(targetPath)) {
|
|
271
|
+
errors.push(`${filePath}: relationship target ${relationship.target} does not exist`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const errors = [
|
|
277
|
+
...findDuplicateJsonKeyErrors('registry/objects.json'),
|
|
278
|
+
...findDuplicateJsonKeyErrors('registry/relationships.json'),
|
|
279
|
+
];
|
|
280
|
+
const registryObjects = readJson('registry/objects.json').objects;
|
|
281
|
+
const registryRelationships = readJson('registry/relationships.json').relationships;
|
|
282
|
+
const registryIds = new Set(registryObjects.map((object) => object.id));
|
|
283
|
+
const registryByPath = new Map(registryObjects.map((object) => [object.path, object]));
|
|
284
|
+
const files = [
|
|
285
|
+
...collectMarkdownFiles('observations'),
|
|
286
|
+
...collectMarkdownFiles('research'),
|
|
287
|
+
...collectMarkdownFiles('rules'),
|
|
288
|
+
...collectMarkdownFiles('patterns'),
|
|
289
|
+
...collectMarkdownFiles('prompts'),
|
|
290
|
+
...collectMarkdownFiles('checklists'),
|
|
291
|
+
...collectMarkdownFiles('reviews'),
|
|
292
|
+
...collectMarkdownFiles('examples'),
|
|
293
|
+
].sort();
|
|
294
|
+
const skillFiles = collectMarkdownFiles('skills').filter((file) => path.basename(file) === 'SKILL.md').sort();
|
|
295
|
+
const aliases = new Map();
|
|
296
|
+
const slugs = new Map();
|
|
297
|
+
const ids = new Map();
|
|
298
|
+
const metadataById = new Map();
|
|
299
|
+
const frontMatterRelationshipKeys = new Set();
|
|
300
|
+
|
|
301
|
+
for (const filePath of files) {
|
|
302
|
+
const markdown = fs.readFileSync(path.join(root, filePath), 'utf8');
|
|
303
|
+
let metadata;
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
metadata = parseFrontMatter(markdown, filePath);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
errors.push(error.message);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
validateMetadata(filePath, metadata, registryIds, errors);
|
|
313
|
+
if (metadata.id) {
|
|
314
|
+
metadataById.set(metadata.id, { filePath, metadata });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (Array.isArray(metadata.relationships)) {
|
|
318
|
+
for (const relationship of metadata.relationships) {
|
|
319
|
+
if (/^[A-Z]+-\d{5}$/.test(relationship.target)) {
|
|
320
|
+
frontMatterRelationshipKeys.add(
|
|
321
|
+
relationshipKey({ ...relationship, source: metadata.id }),
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
for (const [map, keyName] of [
|
|
328
|
+
[ids, 'id'],
|
|
329
|
+
[aliases, 'alias'],
|
|
330
|
+
[slugs, 'slug'],
|
|
331
|
+
]) {
|
|
332
|
+
const key = metadata[keyName];
|
|
333
|
+
if (!key) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (map.has(key)) {
|
|
337
|
+
errors.push(`${filePath}: duplicate ${keyName} ${key}; first seen in ${map.get(key)}`);
|
|
338
|
+
} else {
|
|
339
|
+
map.set(key, filePath);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!registryBackedObjectTypes.has(metadata.object_type)) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const registryObject = registryByPath.get(filePath);
|
|
348
|
+
if (!registryObject) {
|
|
349
|
+
errors.push(`${filePath}: missing registry object`);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
for (const field of ['id', 'alias', 'slug', 'title', 'object_type', 'status', 'version', 'category', 'maturity']) {
|
|
354
|
+
if (registryObject[field] !== metadata[field]) {
|
|
355
|
+
errors.push(
|
|
356
|
+
`${filePath}: registry ${field} is ${registryObject[field] ?? '<missing>'}, expected ${metadata[field]}`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const filePath of skillFiles) {
|
|
363
|
+
const markdown = fs.readFileSync(path.join(root, filePath), 'utf8');
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
validateSkillMetadata(filePath, parseFrontMatter(markdown, filePath), errors);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
errors.push(error.message);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
for (const [field, label] of [
|
|
373
|
+
['id', 'id'],
|
|
374
|
+
['alias', 'alias'],
|
|
375
|
+
['slug', 'slug'],
|
|
376
|
+
]) {
|
|
377
|
+
const values = new Map();
|
|
378
|
+
|
|
379
|
+
for (const object of registryObjects) {
|
|
380
|
+
const value = object[field];
|
|
381
|
+
if (values.has(value)) {
|
|
382
|
+
errors.push(
|
|
383
|
+
`registry/objects.json: duplicate ${label} ${value}; first seen in ${values.get(value)}`,
|
|
384
|
+
);
|
|
385
|
+
} else {
|
|
386
|
+
values.set(value, object.id);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (const object of registryObjects) {
|
|
392
|
+
if (!registryBackedObjectTypes.has(object.object_type)) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const filePath = ids.get(object.id);
|
|
397
|
+
if (!filePath) {
|
|
398
|
+
errors.push(`registry/objects.json: ${object.id} has no matching knowledge file`);
|
|
399
|
+
} else if (filePath !== object.path) {
|
|
400
|
+
errors.push(
|
|
401
|
+
`registry/objects.json: ${object.id} path is ${object.path}, expected ${filePath}`,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (const [id, { filePath, metadata }] of metadataById) {
|
|
407
|
+
if (!registryBackedObjectTypes.has(metadata.object_type) || !['validated', 'canonical'].includes(metadata.maturity)) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const hasValidationRelationship = registryRelationships.some(
|
|
412
|
+
(relationship) => relationship.type === 'validates' && (relationship.source === id || relationship.target === id),
|
|
413
|
+
);
|
|
414
|
+
if (!hasValidationRelationship) {
|
|
415
|
+
errors.push(`${filePath}: ${metadata.maturity} maturity requires a validates relationship`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
for (const relationship of registryRelationships) {
|
|
420
|
+
if (!allowedRelationshipTypes.has(relationship.type)) {
|
|
421
|
+
errors.push(`registry/relationships.json: unsupported relationship type ${relationship.type}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (!registryIds.has(relationship.source)) {
|
|
425
|
+
errors.push(`registry/relationships.json: source ${relationship.source} is not in registry`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (!registryIds.has(relationship.target)) {
|
|
429
|
+
errors.push(`registry/relationships.json: target ${relationship.target} is not in registry`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!frontMatterRelationshipKeys.has(relationshipKey(relationship))) {
|
|
433
|
+
errors.push(
|
|
434
|
+
`registry/relationships.json: ${relationship.type} ${relationship.source} -> ${relationship.target} is missing from front matter`,
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
for (const relationshipKeyFromFrontMatter of frontMatterRelationshipKeys) {
|
|
440
|
+
if (!registryRelationships.some((relationship) => relationshipKey(relationship) === relationshipKeyFromFrontMatter)) {
|
|
441
|
+
errors.push(
|
|
442
|
+
`knowledge front matter: ${relationshipKeyFromFrontMatter} is missing from registry/relationships.json`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const generatedFiles = generateIndexFiles(root);
|
|
448
|
+
for (const [filePath, expectedContent] of Object.entries(generatedFiles)) {
|
|
449
|
+
const fullPath = path.join(root, filePath);
|
|
450
|
+
|
|
451
|
+
if (!fs.existsSync(fullPath)) {
|
|
452
|
+
errors.push(`${filePath}: generated index is missing; run npm run generate:indexes`);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const actualContent = fs.readFileSync(fullPath, 'utf8');
|
|
457
|
+
if (actualContent !== expectedContent) {
|
|
458
|
+
errors.push(`${filePath}: generated index is out of sync; run npm run generate:indexes`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (errors.length > 0) {
|
|
463
|
+
console.error(`Knowledge validation failed with ${errors.length} error(s):`);
|
|
464
|
+
for (const error of errors) {
|
|
465
|
+
console.error(`- ${error}`);
|
|
466
|
+
}
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const ruleCount = files.filter((file) => file.startsWith('rules/')).length;
|
|
471
|
+
const patternCount = files.filter((file) => file.startsWith('patterns/')).length;
|
|
472
|
+
const researchCount = files.filter((file) => file.startsWith('research/')).length;
|
|
473
|
+
const promptCount = files.filter((file) => file.startsWith('prompts/')).length;
|
|
474
|
+
const checklistCount = files.filter((file) => file.startsWith('checklists/')).length;
|
|
475
|
+
const reviewCount = files.filter((file) => file.startsWith('reviews/')).length;
|
|
476
|
+
const referenceProjectCount = files.filter((file) => file.startsWith('examples/')).length;
|
|
477
|
+
const observationCount = files.filter((file) => file.startsWith('observations/')).length;
|
|
478
|
+
|
|
479
|
+
console.log(
|
|
480
|
+
`Knowledge validation passed: ${observationCount} observations, ${researchCount} research, ${ruleCount} rules, ${patternCount} patterns, ${promptCount} prompts, ${checklistCount} checklists, ${reviewCount} reviews, ${referenceProjectCount} reference projects, ${skillFiles.length} skills.`,
|
|
481
|
+
);
|