Преглед изворни кода

feat(content): seed canonical profiles from the legacy registry

content/profiles/{context,system} become the single authored source:
5 context bundles (core, essentials, business, engineering,
meta-authoring) compose into the 5 system profiles. Generated from
registry.json, not hand-transcribed.

Context wildcards (core/*, ui/*, ...) are authored verbatim: they are
directory subscriptions that also install future files, so expansion
happens at emission/install, never in the source.

The equivalence test reconstructs every legacy registry.profiles list
from the canonical profiles exactly - the drift alarm for the window
before the registry emitter generates .profiles itself.
darrenhinde пре 2 недеља
родитељ
комит
4c96328a32

+ 7 - 0
content/profiles/context/business.json

@@ -0,0 +1,7 @@
+{
+  "id": "business",
+  "contexts": [
+    "core/*",
+    "project-intelligence/*"
+  ]
+}

+ 9 - 0
content/profiles/context/core.json

@@ -0,0 +1,9 @@
+{
+  "id": "core",
+  "contexts": [
+    "adding-skill-basics",
+    "essential-patterns",
+    "project-context",
+    "quick-start"
+  ]
+}

+ 18 - 0
content/profiles/context/engineering.json

@@ -0,0 +1,18 @@
+{
+  "id": "engineering",
+  "contexts": [
+    "animation-advanced",
+    "animation-basics",
+    "animation-components",
+    "api-design",
+    "clean-code",
+    "core/*",
+    "design-systems",
+    "development/*",
+    "openagents-repo/*",
+    "project-intelligence/*",
+    "react-patterns",
+    "ui-styling-standards",
+    "ui/*"
+  ]
+}

+ 15 - 0
content/profiles/context/essentials.json

@@ -0,0 +1,15 @@
+{
+  "id": "essentials",
+  "contexts": [
+    "feature-breakdown",
+    "session-management",
+    "standards-analysis",
+    "standards-code",
+    "standards-docs",
+    "standards-patterns",
+    "standards-tests",
+    "system-context-guide",
+    "task-delegation-basics",
+    "workflows-review"
+  ]
+}

+ 14 - 0
content/profiles/context/meta-authoring.json

@@ -0,0 +1,14 @@
+{
+  "id": "meta-authoring",
+  "contexts": [
+    "codebase-references",
+    "context-system",
+    "core/context-system/*",
+    "frontmatter",
+    "orchestrator-template",
+    "registry-dependencies",
+    "subagent-template",
+    "system-builder-guide",
+    "templates"
+  ]
+}

+ 62 - 0
content/profiles/system/advanced.json

@@ -0,0 +1,62 @@
+{
+  "id": "advanced",
+  "agents": [
+    "agent-generator",
+    "build-agent",
+    "coder-agent",
+    "command-creator",
+    "context-organizer",
+    "context-retriever",
+    "contextscout",
+    "copywriter",
+    "data-analyst",
+    "devops-specialist",
+    "documentation",
+    "domain-analyzer",
+    "eval-runner",
+    "frontend-specialist",
+    "image-specialist",
+    "openagent",
+    "opencoder",
+    "repo-manager",
+    "reviewer",
+    "system-builder",
+    "task-manager",
+    "technical-writer",
+    "tester",
+    "workflow-designer"
+  ],
+  "contextProfiles": [
+    "core",
+    "engineering",
+    "meta-authoring"
+  ],
+  "commands": [
+    "add-context",
+    "analyze-patterns",
+    "build-context-system",
+    "clean",
+    "commit",
+    "context",
+    "optimize",
+    "prompt-enhancer",
+    "test",
+    "validate-repo",
+    "worktrees"
+  ],
+  "tools": [
+    "env",
+    "gemini"
+  ],
+  "skills": [
+    "task-management"
+  ],
+  "plugins": [
+    "notify"
+  ],
+  "config": [
+    "agent-metadata",
+    "env-example",
+    "readme"
+  ]
+}

+ 38 - 0
content/profiles/system/business.json

@@ -0,0 +1,38 @@
+{
+  "id": "business",
+  "agents": [
+    "contextscout",
+    "copywriter",
+    "data-analyst",
+    "documentation",
+    "image-specialist",
+    "openagent",
+    "task-manager",
+    "technical-writer"
+  ],
+  "contextProfiles": [
+    "core",
+    "business"
+  ],
+  "commands": [
+    "add-context",
+    "clean",
+    "context",
+    "prompt-enhancer"
+  ],
+  "tools": [
+    "env",
+    "gemini"
+  ],
+  "skills": [
+    "task-management"
+  ],
+  "plugins": [
+    "notify"
+  ],
+  "config": [
+    "agent-metadata",
+    "env-example",
+    "readme"
+  ]
+}

+ 42 - 0
content/profiles/system/developer.json

@@ -0,0 +1,42 @@
+{
+  "id": "developer",
+  "agents": [
+    "build-agent",
+    "coder-agent",
+    "contextscout",
+    "devops-specialist",
+    "documentation",
+    "frontend-specialist",
+    "openagent",
+    "opencoder",
+    "reviewer",
+    "task-manager",
+    "tester"
+  ],
+  "contextProfiles": [
+    "core",
+    "engineering"
+  ],
+  "commands": [
+    "add-context",
+    "analyze-patterns",
+    "clean",
+    "commit",
+    "context",
+    "optimize",
+    "test",
+    "validate-repo"
+  ],
+  "tools": [
+    "env"
+  ],
+  "skills": [
+    "task-management"
+  ],
+  "plugins": [],
+  "config": [
+    "agent-metadata",
+    "env-example",
+    "readme"
+  ]
+}

+ 29 - 0
content/profiles/system/essential.json

@@ -0,0 +1,29 @@
+{
+  "id": "essential",
+  "agents": [
+    "contextscout",
+    "documentation",
+    "openagent",
+    "task-manager"
+  ],
+  "contextProfiles": [
+    "core",
+    "essentials"
+  ],
+  "commands": [
+    "add-context",
+    "clean",
+    "context"
+  ],
+  "tools": [
+    "env"
+  ],
+  "skills": [
+    "task-management"
+  ],
+  "plugins": [],
+  "config": [
+    "agent-metadata",
+    "env-example"
+  ]
+}

+ 52 - 0
content/profiles/system/full.json

@@ -0,0 +1,52 @@
+{
+  "id": "full",
+  "agents": [
+    "build-agent",
+    "coder-agent",
+    "contextscout",
+    "copywriter",
+    "data-analyst",
+    "devops-specialist",
+    "documentation",
+    "eval-runner",
+    "frontend-specialist",
+    "image-specialist",
+    "openagent",
+    "opencoder",
+    "reviewer",
+    "task-manager",
+    "technical-writer",
+    "tester"
+  ],
+  "contextProfiles": [
+    "core",
+    "engineering"
+  ],
+  "commands": [
+    "add-context",
+    "analyze-patterns",
+    "clean",
+    "commit",
+    "context",
+    "optimize",
+    "prompt-enhancer",
+    "test",
+    "validate-repo",
+    "worktrees"
+  ],
+  "tools": [
+    "env",
+    "gemini"
+  ],
+  "skills": [
+    "task-management"
+  ],
+  "plugins": [
+    "notify"
+  ],
+  "config": [
+    "agent-metadata",
+    "env-example",
+    "readme"
+  ]
+}

+ 84 - 0
packages/compatibility-layer/tests/unit/build/system-profile-equivalence.test.ts

@@ -0,0 +1,84 @@
+/**
+ * Equivalence gate: the canonical profiles in `content/profiles/**` must reconstruct the
+ * legacy `registry.json` `.profiles.*.components` lists EXACTLY.
+ *
+ * ─── Why this test exists ───────────────────────────────────────────────────────────────
+ *
+ * Seeding `content/profiles/**` was a translation of the legacy hand-maintained lists, and
+ * any translation can silently drop or invent a component. Until the registry emitter
+ * generates `.profiles` from the canonical source (subtask 08's pending emission), BOTH
+ * lists are live: `install.sh` reads the registry, the canonical build reads `content/`.
+ * If they ever disagree, the two install paths ship two different products.
+ *
+ * This test is the drift alarm for that window. It fails the moment someone edits one side
+ * without the other — which is precisely the failure mode that put 51-vs-68-component drift
+ * into every legacy profile.
+ *
+ * ─── The two sanctioned translations ────────────────────────────────────────────────────
+ *
+ * 1. `subagent:` refs are authored under `agents` (the canonical tree collapses them), and
+ *    are mapped back to their registry category here.
+ *
+ * Context wildcards (`context:core/*` and friends) are NOT a translation: they are
+ * load-bearing legacy semantics — a directory subscription that also installs future
+ * files — so they are authored verbatim in the canonical bundles and compared literally
+ * here. Expansion happens at emission/install time, never in the authored source.
+ */
+
+import { describe, expect, it } from "vitest";
+import { ProfileLoader } from "../../../src/core/ProfileLoader.js";
+import { ReferenceResolver } from "../../../src/core/ReferenceResolver.js";
+import { repoPath } from "../../support/pending.js";
+
+const PROFILES = ["advanced", "business", "developer", "essential", "full"] as const;
+
+/** Registry category for an id under the collapsed `agents` field, or null. */
+function agentCategory(id: string, registry: ReturnType<ReferenceResolver["registry"]>): string | null {
+  if (registry.components.agents.some((a) => a.id === id)) return "agent";
+  if (registry.components.subagents.some((s) => s.id === id)) return "subagent";
+  return null;
+}
+
+describe("canonical profile equivalence", () => {
+  const root = repoPath();
+  const loader = new ProfileLoader(root);
+  const resolver = new ReferenceResolver(root);
+  const registry = resolver.registry();
+
+  it.each(PROFILES)(
+    "content/profiles/system/%s.json reconstructs registry.profiles.%s.components exactly",
+    async (name) => {
+      // Arrange
+      const resolved = await loader.resolveSystemProfile(name);
+      const legacy = registry.profiles[name]?.components ?? [];
+
+      // Act — map the canonical resolution back to typed refs
+      const canonical: string[] = [];
+      for (const id of resolved.agents) {
+        const category = agentCategory(id, registry);
+        expect(category, `agent id "${id}" exists in neither registry category`).not.toBeNull();
+        canonical.push(`${category}:${id}`);
+      }
+      for (const id of resolved.contexts) canonical.push(`context:${id}`);
+      for (const id of resolved.commands) canonical.push(`command:${id}`);
+      for (const id of resolved.tools) canonical.push(`tool:${id}`);
+      for (const id of resolved.skills) canonical.push(`skill:${id}`);
+      for (const id of resolved.plugins) canonical.push(`plugin:${id}`);
+      for (const id of resolved.config) canonical.push(`config:${id}`);
+
+      // Assert — set equality, order-insensitive on both sides
+      expect([...new Set(canonical)].sort()).toEqual([...new Set(legacy)].sort());
+    }
+  );
+
+  it("covers every legacy profile — no profile exists only on one side", () => {
+    // Arrange
+    const canonical = ["advanced", "business", "developer", "essential", "full"];
+
+    // Act
+    const legacy = Object.keys(registry.profiles).sort();
+
+    // Assert
+    expect(legacy).toEqual(canonical);
+  });
+});