compound-workflow 1.7.0 → 1.7.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compound-workflow",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "Clarify -> plan -> execute -> verify -> capture workflow for Cursor",
5
5
  "author": {
6
6
  "name": "Compound Workflow"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compound-workflow",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "Clarify → plan → execute → verify → capture. One Install action for Cursor, Claude, and OpenCode.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,13 +42,14 @@ When Cursor is detected (~/.cursor), registers the plugin so skills/commands app
42
42
  }
43
43
 
44
44
  function parseArgs(argv) {
45
- const out = { root: process.cwd(), dryRun: false, noConfig: false, noRegisterCursor: false, registerCursor: false };
45
+ const out = { root: process.cwd(), dryRun: false, noConfig: false, noRegisterCursor: false, registerCursor: false, verify: false };
46
46
  for (let i = 2; i < argv.length; i++) {
47
47
  const arg = argv[i];
48
48
  if (arg === "--dry-run") out.dryRun = true;
49
49
  else if (arg === "--no-config") out.noConfig = true;
50
50
  else if (arg === "--no-register-cursor") out.noRegisterCursor = true;
51
51
  else if (arg === "--register-cursor") out.registerCursor = true;
52
+ else if (arg === "--verify") out.verify = true;
52
53
  else if (arg === "--root") {
53
54
  const value = argv[i + 1];
54
55
  if (!value) usage(1);
@@ -342,10 +343,12 @@ function writePluginManifests(targetRoot, dryRun, isSelfInstall) {
342
343
  if (!cursorManifest || !claudeManifest) return;
343
344
 
344
345
  // Cursor supports full manifest with commands/agents/skills path overrides.
346
+ // For commands/agents, use .cursor/ directories where symlinks are created
347
+ // so Cursor can discover them properly. Skills still use source path.
345
348
  const cursorOut = {
346
349
  ...cursorManifest,
347
- commands: `${pathsBase}/commands`,
348
- agents: `${pathsBase}/agents`,
350
+ commands: "./.cursor/commands",
351
+ agents: "./.cursor/agents",
349
352
  skills: `${pathsBase}/skills`,
350
353
  };
351
354
  // Claude Code only accepts name, description, author in plugin.json.
@@ -504,10 +507,307 @@ function syncCursorSkills(targetRoot, dryRun, isSelfInstall) {
504
507
  console.log("Synced", skillDirs.length, "skills to .cursor/skills/");
505
508
  }
506
509
 
510
+ /**
511
+ * Cursor discovers commands from .cursor/commands.
512
+ * Populate .cursor/commands/ with symlinks to the package commands so Cursor finds them.
513
+ */
514
+ function syncCursorCommands(targetRoot, dryRun, isSelfInstall) {
515
+ const packageCommandsAbs = isSelfInstall
516
+ ? path.join(PACKAGE_ROOT, "src", ".agents", "commands")
517
+ : path.join(targetRoot, "node_modules", "compound-workflow", "src", ".agents", "commands");
518
+ if (!fs.existsSync(packageCommandsAbs)) return;
519
+
520
+ const cursorCommandsDir = path.join(targetRoot, ".cursor", "commands");
521
+ let entries;
522
+ try {
523
+ entries = fs.readdirSync(packageCommandsAbs, { withFileTypes: true });
524
+ } catch {
525
+ return;
526
+ }
527
+
528
+ // Filter .md files that are commands
529
+ const commandFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name);
530
+ if (commandFiles.length === 0) return;
531
+
532
+ if (dryRun) {
533
+ console.log("[dry-run] Would symlink", commandFiles.length, "commands into .cursor/commands/");
534
+ return;
535
+ }
536
+
537
+ fs.mkdirSync(cursorCommandsDir, { recursive: true });
538
+ const packageCommandsReal = realpathSafe(packageCommandsAbs);
539
+ const commandSet = new Set(commandFiles);
540
+
541
+ // Prune: remove symlinks that point at our package but are no longer in the package
542
+ try {
543
+ for (const entry of fs.readdirSync(cursorCommandsDir, { withFileTypes: true })) {
544
+ if (!entry.isSymbolicLink()) continue;
545
+ const linkPath = path.join(cursorCommandsDir, entry.name);
546
+ try {
547
+ const resolved = realpathSafe(linkPath);
548
+ if (!resolved.startsWith(packageCommandsReal + path.sep) && resolved !== packageCommandsReal) continue;
549
+ const base = path.basename(resolved);
550
+ if (commandSet.has(base)) continue;
551
+ fs.rmSync(linkPath);
552
+ } catch {
553
+ /* ignore broken symlinks or permission errors */
554
+ }
555
+ }
556
+ } catch {
557
+ /* .cursor/commands not readable */
558
+ }
559
+
560
+ for (const name of commandFiles) {
561
+ const linkPath = path.join(cursorCommandsDir, name);
562
+ const targetPath = path.join(packageCommandsAbs, name);
563
+ try {
564
+ if (fs.existsSync(linkPath)) {
565
+ const stat = fs.lstatSync(linkPath);
566
+ if (!stat.isSymbolicLink()) continue;
567
+ try {
568
+ if (realpathSafe(linkPath) !== realpathSafe(targetPath)) continue;
569
+ } catch {
570
+ continue;
571
+ }
572
+ fs.rmSync(linkPath);
573
+ }
574
+ fs.symlinkSync(targetPath, linkPath, "file");
575
+ } catch (err) {
576
+ console.warn("[cursor] Could not symlink command", name, err.message);
577
+ }
578
+ }
579
+ console.log("Synced", commandFiles.length, "commands to .cursor/commands/");
580
+ }
581
+
582
+ /**
583
+ * Cursor discovers agents from .cursor/agents.
584
+ * Populate .cursor/agents/ with symlinks to the package agents so Cursor finds them.
585
+ * Preserves subdirectory structure (research/, workflow/, review/).
586
+ */
587
+ function syncCursorAgents(targetRoot, dryRun, isSelfInstall) {
588
+ const packageAgentsAbs = isSelfInstall
589
+ ? path.join(PACKAGE_ROOT, "src", ".agents", "agents")
590
+ : path.join(targetRoot, "node_modules", "compound-workflow", "src", ".agents", "agents");
591
+ if (!fs.existsSync(packageAgentsAbs)) return;
592
+
593
+ const cursorAgentsDir = path.join(targetRoot, ".cursor", "agents");
594
+
595
+ // Get all agent files from manifest (these include subdir paths like "research/repo-research-analyst.md")
596
+ const agentRels = GENERATED_MANIFEST.agents.map((a) => a.rel);
597
+ if (agentRels.length === 0) return;
598
+
599
+ if (dryRun) {
600
+ console.log("[dry-run] Would symlink", agentRels.length, "agents into .cursor/agents/");
601
+ return;
602
+ }
603
+
604
+ fs.mkdirSync(cursorAgentsDir, { recursive: true });
605
+ const packageAgentsReal = realpathSafe(packageAgentsAbs);
606
+ const agentSet = new Set(agentRels);
607
+
608
+ // Build set of valid subdirectories to preserve structure
609
+ const validSubdirs = new Set();
610
+ for (const rel of agentRels) {
611
+ const subdir = path.dirname(rel);
612
+ if (subdir !== ".") validSubdirs.add(subdir);
613
+ }
614
+
615
+ // Prune: remove symlinks that point at our package but are no longer in the manifest
616
+ try {
617
+ for (const entry of fs.readdirSync(cursorAgentsDir, { withFileTypes: true })) {
618
+ if (entry.isDirectory()) {
619
+ // Check if this subdir is still valid
620
+ if (!validSubdirs.has(entry.name)) {
621
+ // Remove the entire stale subdirectory
622
+ fs.rmSync(path.join(cursorAgentsDir, entry.name), { recursive: true, force: true });
623
+ continue;
624
+ }
625
+ // Prune stale symlinks within valid subdirectories
626
+ const subdirPath = path.join(cursorAgentsDir, entry.name);
627
+ for (const subEntry of fs.readdirSync(subdirPath, { withFileTypes: true })) {
628
+ if (!subEntry.isSymbolicLink()) continue;
629
+ const linkPath = path.join(subdirPath, subEntry.name);
630
+ try {
631
+ const resolved = realpathSafe(linkPath);
632
+ if (!resolved.startsWith(packageAgentsReal + path.sep) && resolved !== packageAgentsReal) continue;
633
+ const relFromPackage = path.relative(packageAgentsAbs, resolved);
634
+ if (agentSet.has(relFromPackage)) continue;
635
+ fs.rmSync(linkPath);
636
+ } catch {
637
+ /* ignore broken symlinks */
638
+ }
639
+ }
640
+ } else if (entry.isSymbolicLink()) {
641
+ // Handle flat symlinks (if any were created at root level)
642
+ const linkPath = path.join(cursorAgentsDir, entry.name);
643
+ try {
644
+ const resolved = realpathSafe(linkPath);
645
+ if (!resolved.startsWith(packageAgentsReal + path.sep) && resolved !== packageAgentsReal) continue;
646
+ const relFromPackage = path.relative(packageAgentsAbs, resolved);
647
+ if (agentSet.has(relFromPackage)) continue;
648
+ fs.rmSync(linkPath);
649
+ } catch {
650
+ /* ignore broken symlinks */
651
+ }
652
+ }
653
+ }
654
+ } catch {
655
+ /* .cursor/agents not readable */
656
+ }
657
+
658
+ // Create symlinks preserving subdirectory structure
659
+ for (const rel of agentRels) {
660
+ const targetPath = path.join(packageAgentsAbs, rel);
661
+ const linkPath = path.join(cursorAgentsDir, rel);
662
+
663
+ // Ensure subdirectory exists
664
+ const subdir = path.dirname(rel);
665
+ if (subdir !== ".") {
666
+ fs.mkdirSync(path.join(cursorAgentsDir, subdir), { recursive: true });
667
+ }
668
+
669
+ try {
670
+ if (fs.existsSync(linkPath)) {
671
+ const stat = fs.lstatSync(linkPath);
672
+ if (!stat.isSymbolicLink()) continue;
673
+ try {
674
+ if (realpathSafe(linkPath) !== realpathSafe(targetPath)) continue;
675
+ } catch {
676
+ continue;
677
+ }
678
+ fs.rmSync(linkPath);
679
+ }
680
+ fs.symlinkSync(targetPath, linkPath, "file");
681
+ } catch (err) {
682
+ console.warn("[cursor] Could not symlink agent", rel, err.message);
683
+ }
684
+ }
685
+ console.log("Synced", agentRels.length, "agents to .cursor/agents/");
686
+ }
687
+
507
688
  function cursorDetected() {
508
689
  return fs.existsSync(path.join(os.homedir(), ".cursor"));
509
690
  }
510
691
 
692
+ /**
693
+ * Verifies plugin integrity by checking all symlinks in .cursor/ directories
694
+ * match the expected state from GENERATED_MANIFEST.
695
+ * Returns { ok: boolean, issues: string[] }
696
+ */
697
+ function verifyPluginIntegrity(targetRoot, isSelfInstall) {
698
+ const issues = [];
699
+ const packageRoot = isSelfInstall
700
+ ? PACKAGE_ROOT
701
+ : path.join(targetRoot, "node_modules", "compound-workflow");
702
+
703
+ // Verify skills
704
+ const cursorSkillsDir = path.join(targetRoot, ".cursor", "skills");
705
+ const packageSkillsDir = path.join(packageRoot, "src", ".agents", "skills");
706
+ if (fs.existsSync(packageSkillsDir)) {
707
+ const expectedSkills = new Set(
708
+ fs.readdirSync(packageSkillsDir, { withFileTypes: true })
709
+ .filter((e) => e.isDirectory() && fs.existsSync(path.join(packageSkillsDir, e.name, "SKILL.md")))
710
+ .map((e) => e.name)
711
+ );
712
+ if (fs.existsSync(cursorSkillsDir)) {
713
+ const actualSkills = fs.readdirSync(cursorSkillsDir, { withFileTypes: true })
714
+ .filter((e) => e.isSymbolicLink())
715
+ .map((e) => e.name);
716
+ for (const skill of expectedSkills) {
717
+ if (!actualSkills.includes(skill)) {
718
+ issues.push(`Missing skill symlink: .cursor/skills/${skill}`);
719
+ }
720
+ }
721
+ for (const skill of actualSkills) {
722
+ if (!expectedSkills.has(skill)) {
723
+ issues.push(`Stale skill symlink: .cursor/skills/${skill}`);
724
+ }
725
+ }
726
+ } else if (expectedSkills.size > 0) {
727
+ issues.push(`Missing .cursor/skills/ directory (${expectedSkills.size} expected)`);
728
+ }
729
+ }
730
+
731
+ // Verify commands
732
+ const cursorCommandsDir = path.join(targetRoot, ".cursor", "commands");
733
+ const packageCommandsDir = path.join(packageRoot, "src", ".agents", "commands");
734
+ if (fs.existsSync(packageCommandsDir)) {
735
+ const expectedCommands = new Set(
736
+ fs.readdirSync(packageCommandsDir, { withFileTypes: true })
737
+ .filter((e) => e.isFile() && e.name.endsWith(".md"))
738
+ .map((e) => e.name)
739
+ );
740
+ if (fs.existsSync(cursorCommandsDir)) {
741
+ const actualCommands = fs.readdirSync(cursorCommandsDir, { withFileTypes: true })
742
+ .filter((e) => e.isSymbolicLink())
743
+ .map((e) => e.name);
744
+ for (const cmd of expectedCommands) {
745
+ if (!actualCommands.includes(cmd)) {
746
+ issues.push(`Missing command symlink: .cursor/commands/${cmd}`);
747
+ }
748
+ }
749
+ for (const cmd of actualCommands) {
750
+ if (!expectedCommands.has(cmd)) {
751
+ issues.push(`Stale command symlink: .cursor/commands/${cmd}`);
752
+ }
753
+ }
754
+ } else if (expectedCommands.size > 0) {
755
+ issues.push(`Missing .cursor/commands/ directory (${expectedCommands.size} expected)`);
756
+ }
757
+ }
758
+
759
+ // Verify agents
760
+ const cursorAgentsDir = path.join(targetRoot, ".cursor", "agents");
761
+ const packageAgentsDir = path.join(packageRoot, "src", ".agents", "agents");
762
+ if (fs.existsSync(packageAgentsDir)) {
763
+ // Recursively get all .md files from package agents dir
764
+ const expectedAgents = [];
765
+ function collectAgents(dir, prefix = "") {
766
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
767
+ const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
768
+ if (entry.isDirectory()) {
769
+ collectAgents(path.join(dir, entry.name), relPath);
770
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
771
+ expectedAgents.push(relPath);
772
+ }
773
+ }
774
+ }
775
+ collectAgents(packageAgentsDir);
776
+ const expectedSet = new Set(expectedAgents);
777
+
778
+ if (fs.existsSync(cursorAgentsDir)) {
779
+ const actualAgents = [];
780
+ function collectActualAgents(dir, prefix = "") {
781
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
782
+ const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
783
+ if (entry.isDirectory()) {
784
+ collectActualAgents(path.join(dir, entry.name), relPath);
785
+ } else if (entry.isSymbolicLink()) {
786
+ actualAgents.push(relPath);
787
+ }
788
+ }
789
+ }
790
+ collectActualAgents(cursorAgentsDir);
791
+ const actualSet = new Set(actualAgents);
792
+
793
+ for (const agent of expectedAgents) {
794
+ if (!actualSet.has(agent)) {
795
+ issues.push(`Missing agent symlink: .cursor/agents/${agent}`);
796
+ }
797
+ }
798
+ for (const agent of actualAgents) {
799
+ if (!expectedSet.has(agent)) {
800
+ issues.push(`Stale agent symlink: .cursor/agents/${agent}`);
801
+ }
802
+ }
803
+ } else if (expectedAgents.length > 0) {
804
+ issues.push(`Missing .cursor/agents/ directory (${expectedAgents.length} expected)`);
805
+ }
806
+ }
807
+
808
+ return { ok: issues.length === 0, issues };
809
+ }
810
+
511
811
  function applyCursorRegistration(targetRoot, dryRun, noRegisterCursor, forceRegister, isSelfInstall) {
512
812
  const claudePluginsDir = path.join(os.homedir(), ".claude", "plugins");
513
813
  const installedPath = path.join(claudePluginsDir, "installed_plugins.json");
@@ -610,6 +910,31 @@ function main() {
610
910
  const args = parseArgs(process.argv);
611
911
  const targetRoot = realpathSafe(args.root);
612
912
 
913
+ // Handle verification mode early (no manifest needed)
914
+ if (args.verify) {
915
+ console.log("Verifying plugin integrity...");
916
+ const isSelfInstall = realpathSafe(targetRoot) === realpathSafe(PACKAGE_ROOT);
917
+ // Try to read manifest for accurate verification, but continue without it
918
+ try {
919
+ GENERATED_MANIFEST = readGeneratedManifest();
920
+ } catch {
921
+ console.warn("Warning: Could not read generated manifest, using filesystem scan only");
922
+ GENERATED_MANIFEST = { commands: [], agents: [] };
923
+ }
924
+ const result = verifyPluginIntegrity(targetRoot, isSelfInstall);
925
+ if (result.ok) {
926
+ console.log("Plugin integrity: OK (all symlinks present and valid)");
927
+ process.exit(0);
928
+ } else {
929
+ console.error("Plugin integrity issues found:");
930
+ for (const issue of result.issues) {
931
+ console.error(` - ${issue}`);
932
+ }
933
+ console.error("\nRun 'npx compound-workflow install' to fix.");
934
+ process.exit(1);
935
+ }
936
+ }
937
+
613
938
  const genScript = path.join(PACKAGE_ROOT, "scripts", "generate-platform-artifacts.mjs");
614
939
  if (fs.existsSync(genScript)) {
615
940
  console.log("[compound-workflow] Regenerating manifest from package source...");
@@ -653,6 +978,8 @@ function main() {
653
978
  writeOpenCodeJson(targetRoot, args.dryRun, isSelfInstall);
654
979
  writePluginManifests(targetRoot, args.dryRun, isSelfInstall);
655
980
  syncCursorSkills(targetRoot, args.dryRun, isSelfInstall);
981
+ syncCursorCommands(targetRoot, args.dryRun, isSelfInstall);
982
+ syncCursorAgents(targetRoot, args.dryRun, isSelfInstall);
656
983
  applyCursorRegistration(targetRoot, args.dryRun, args.noRegisterCursor, args.registerCursor, isSelfInstall);
657
984
  reportOpenCodeIntegration(targetRoot, args.dryRun);
658
985
  writeAgentsMd(targetRoot, args.dryRun);
@@ -6,7 +6,7 @@ argument-hint: "[PR number, branch name, or 'current' for current branch]"
6
6
 
7
7
  # Browser Test Command
8
8
 
9
- <command_purpose>Run end-to-end browser tests on pages affected by a PR or branch changes using agent-browser CLI.</command_purpose>
9
+ Run end-to-end browser tests on pages affected by a PR or branch changes using agent-browser CLI.
10
10
 
11
11
  ## CRITICAL: Use agent-browser CLI Only
12
12
 
@@ -7,8 +7,9 @@ argument-hint: "[feature idea or problem to explore]"
7
7
 
8
8
  # Brainstorm a Feature or Improvement
9
9
 
10
- **Note: The current year is 2026.** Use this when dating brainstorm
11
- documents.
10
+ Explore requirements and approaches through collaborative dialogue before planning implementation.
11
+
12
+ **Note: The current year is 2026.** Use this when dating brainstorm documents.
12
13
 
13
14
  Brainstorming helps answer **WHAT** to build through collaborative
14
15
  dialogue. It precedes `/workflow:plan`, which answers **HOW** to build
@@ -7,6 +7,8 @@ argument-hint: "[feature description, bug report, improvement idea, or brainstor
7
7
 
8
8
  # Create a plan for a new feature or bug fix
9
9
 
10
+ Transform feature descriptions into well-structured project plans using an explicit fidelity and confidence model.
11
+
10
12
  ## Introduction
11
13
 
12
14
  **Note: The current year is 2026.** Use this when dating plans and searching for recent documentation.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: presentation-composability
3
+ description: Enforce folder-per-component structure for tapesv3 presentation modules. Use when creating or refactoring components under src/features/tapesv3/presentation/.
4
+ ---
5
+
6
+ # Presentation Composability
7
+
8
+ Enforce folder-per-component structure for tapesv3 presentation modules.
9
+
10
+ ## When to Use
11
+
12
+ - Adding new presentation components to tapesv3
13
+ - Refactoring existing tapesv3 presentation into composable pieces
14
+ - Aligning new UI with established tapesv3 patterns
15
+
16
+ ## Structure Rules
17
+
18
+ ### One folder per composable
19
+
20
+ Each logical UI element gets its own subfolder (PascalCase):
21
+
22
+ ```
23
+ presentation/FeatureName/
24
+ ├── index.ts # barrel
25
+ ├── SubComponentA/
26
+ │ ├── index.tsx
27
+ │ └── styles.ts
28
+ ├── SubComponentB/
29
+ │ ├── index.tsx
30
+ │ └── styles.ts
31
+ └── SubComponentC/
32
+ └── index.tsx # styles.ts optional if no styled components
33
+ ```
34
+
35
+ ### Barrel (`index.ts`)
36
+
37
+ Re-export from subfolders. Two patterns:
38
+
39
+ **Flat exports** (simple feature, independent components):
40
+
41
+ ```typescript
42
+ export { SubComponentA } from "./SubComponentA";
43
+ export { SubComponentB } from "./SubComponentB";
44
+ ```
45
+
46
+ **Compound component** (Root + subcomponents used together):
47
+
48
+ ```typescript
49
+ export const FeatureName = Object.assign(RootComponent, {
50
+ SubA: SubComponentA,
51
+ SubB: SubComponentB,
52
+ });
53
+ ```
54
+
55
+ ### Component + styles
56
+
57
+ - `index.tsx`: component logic and JSX
58
+ - `styles.ts`: styled-components (or emotion) only
59
+ - Import: `import * as S from "./styles"` or `import { Root, Item } from "./styles"`
60
+
61
+ ### Reference implementations
62
+
63
+ - `SharedTabsHeader` – compound pattern (Root.Action, Root.BackgroundPicker)
64
+ - `PanelClipsArtist`, `FolderSelector` – compound pattern
65
+ - `MultiSourceThreads` – flat exports (FilterBar, Message, Input, List)
66
+ - `TapesTabBarTabOptions` – flat exports
67
+
68
+ ## Anti-patterns
69
+
70
+ - Single file with multiple components and inline styles
71
+ - `ComponentName.styles.ts` – use `styles.ts` in the component folder
72
+ - Barrel re-exporting from sibling files instead of subfolders
package/src/AGENTS.md CHANGED
@@ -172,7 +172,7 @@ worktree_bootstrap_notes:
172
172
  ## Implemented Components (Current Scope)
173
173
 
174
174
  - Commands: `workflow:brainstorm`, `workflow:plan`, `workflow:triage`, `workflow:work`, `workflow:review`, `workflow:tech-review`, `workflow:compound` (under `.agents/commands/` as `workflow-*.md`), plus `test-browser`, `metrics`, `assess`, `install` (root commands)
175
- - Skills: `brainstorming`, `document-review`, `technical-review`, `compound-docs` (alias: `compound_doc`), `capture-skill`, `file-todos`, `agent-browser`, `git-worktree`, `process-metrics`, `react-ddd-mvc-frontend`, `xstate-actor-orchestration`, `standards`, `pii-protection-prisma`, `financial-workflow-integrity`, `audit-traceability`, `data-foundations`
175
+ - Skills: `brainstorming`, `document-review`, `technical-review`, `compound-docs` (alias: `compound_doc`), `capture-skill`, `file-todos`, `agent-browser`, `git-worktree`, `process-metrics`, `react-ddd-mvc-frontend`, `xstate-actor-orchestration`, `standards`, `pii-protection-prisma`, `financial-workflow-integrity`, `audit-traceability`, `data-foundations`, `presentation-composability`
176
176
  - Agents:
177
177
  - `repo-research-analyst`
178
178
  - `learnings-researcher`
@@ -237,6 +237,7 @@ Maintenance:
237
237
  | `react-ddd-mvc-frontend` | You need React frontend architecture guidance (DDD + MVC hybrid) during planning or review to enforce feature structure, layer boundaries, composable pure components, container/controller responsibilities, and maintainable patterns. |
238
238
  | `xstate-actor-orchestration` | You are evaluating complexity and need explicit state orchestration: React container-as-orchestrator for UI flows, or actor/state-machine orchestration for backend/internal workflows (especially multi-step async branching, retries/timeouts/cancellation, receptionist/child-actor coordination, or boolean-flag sprawl). |
239
239
  | `standards` | You need Altai coding standards for implementation and refactoring, including domain entity patterns, XState conventions, type usage, and feature code organization. |
240
+ | `presentation-composability` | You are creating or refactoring presentation components in tapesv3 and need folder-per-component structure with barrel exports and styles separation. |
240
241
 
241
242
  ### Reference standards (guardrails)
242
243