@ryanstark24/sfgraph-skills 1.1.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.
Files changed (33) hide show
  1. package/dist/__tests__/cursor-format.test.d.ts +2 -0
  2. package/dist/__tests__/cursor-format.test.d.ts.map +1 -0
  3. package/dist/__tests__/cursor-format.test.js +44 -0
  4. package/dist/__tests__/cursor-format.test.js.map +1 -0
  5. package/dist/__tests__/installer.test.d.ts +2 -0
  6. package/dist/__tests__/installer.test.d.ts.map +1 -0
  7. package/dist/__tests__/installer.test.js +64 -0
  8. package/dist/__tests__/installer.test.js.map +1 -0
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +14 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/installer.d.ts +39 -0
  14. package/dist/installer.d.ts.map +1 -0
  15. package/dist/installer.js +187 -0
  16. package/dist/installer.js.map +1 -0
  17. package/package.json +51 -0
  18. package/skills/sf-cross-layer-trace/SKILL.md +97 -0
  19. package/skills/sf-cross-org-diff/SKILL.md +70 -0
  20. package/skills/sf-dead-code-audit/SKILL.md +72 -0
  21. package/skills/sf-deployment-manifest/SKILL.md +70 -0
  22. package/skills/sf-explain-code/SKILL.md +66 -0
  23. package/skills/sf-flow-impact/SKILL.md +67 -0
  24. package/skills/sf-governor-risk-fix/SKILL.md +64 -0
  25. package/skills/sf-impact-from-diff/SKILL.md +64 -0
  26. package/skills/sf-metadata-refresh/SKILL.md +71 -0
  27. package/skills/sf-omnistudio-migration-audit/SKILL.md +71 -0
  28. package/skills/sf-schema-overview/SKILL.md +66 -0
  29. package/skills/sf-security-audit/SKILL.md +66 -0
  30. package/skills/sf-snapshot-compare/SKILL.md +68 -0
  31. package/skills/sf-web-explorer/SKILL.md +56 -0
  32. package/skills/sf-what-broke/SKILL.md +64 -0
  33. package/skills/sf-wip-impact/SKILL.md +74 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cursor-format.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-format.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cursor-format.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { toClaudeFormat, toCursorFormat } from "../installer.js";
3
+ const SAMPLE = `---
4
+ name: foo
5
+ description: bar
6
+ triggers:
7
+ - "x"
8
+ tools_used:
9
+ - tool_a
10
+ - tool_b
11
+ ---
12
+
13
+ # Body
14
+
15
+ Some content.
16
+
17
+ - bullet
18
+ `;
19
+ describe("format transforms", () => {
20
+ it("toCursorFormat rewrites frontmatter into Cursor's contract", () => {
21
+ const out = toCursorFormat(SAMPLE);
22
+ // Cursor-shaped frontmatter (the only three keys it understands)
23
+ expect(out).toMatch(/^description:.*bar/m);
24
+ // globs now contains the SF file patterns so the rule auto-attaches
25
+ // whenever the user opens any Salesforce metadata file.
26
+ expect(out).toMatch(/^globs:.*sfdx-project\.json.*\*\*\/\*\.cls/m);
27
+ expect(out).toMatch(/^alwaysApply:\s*false$/m);
28
+ // Trigger phrases folded into the description so the agent matches them
29
+ expect(out).toMatch(/Triggers:.*"?x"?/);
30
+ // The agent-requested hint mentions the skill name + sfgraph routing
31
+ expect(out).toMatch(/sfgraph MCP tools/);
32
+ // Body preserved verbatim
33
+ expect(out).toMatch(/^# Body/m);
34
+ expect(out).toMatch(/^- bullet/m);
35
+ // Old frontmatter is gone (it would confuse Cursor's parser)
36
+ expect(out).not.toMatch(/^name: foo/m);
37
+ expect(out).not.toMatch(/^triggers:/m);
38
+ expect(out).not.toMatch(/^tools_used:/m);
39
+ });
40
+ it("toClaudeFormat is identity", () => {
41
+ expect(toClaudeFormat(SAMPLE)).toBe(SAMPLE);
42
+ });
43
+ });
44
+ //# sourceMappingURL=cursor-format.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-format.test.js","sourceRoot":"","sources":["../../src/__tests__/cursor-format.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjE,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;CAed,CAAC;AAEF,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,GAAG,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QACnC,iEAAiE;QACjE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;QAC3C,oEAAoE;QACpE,wDAAwD;QACxD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC;QACnE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;QAC/C,wEAAwE;QACxE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QACxC,qEAAqE;QACrE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QACzC,0BAA0B;QAC1B,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAClC,6DAA6D;QAC7D,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=installer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/installer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { install, listSkillsBundled } from "../installer.js";
6
+ let home;
7
+ beforeEach(() => {
8
+ home = mkdtempSync(join(tmpdir(), "sfgraph-home-"));
9
+ });
10
+ afterEach(() => {
11
+ rmSync(home, { recursive: true, force: true });
12
+ });
13
+ describe("skills installer", () => {
14
+ it("installs all 16 skills to the claude target", async () => {
15
+ const results = await install("claude", { homeOverride: home });
16
+ const skillNames = await listSkillsBundled();
17
+ expect(skillNames.length).toBe(16);
18
+ expect(results.length).toBe(16);
19
+ for (const r of results) {
20
+ expect(r.target).toBe("claude");
21
+ expect(r.action).toBe("created");
22
+ expect(existsSync(r.path)).toBe(true);
23
+ expect(r.path).toContain(join(home, ".claude", "skills"));
24
+ expect(r.path.endsWith("SKILL.md")).toBe(true);
25
+ }
26
+ });
27
+ it("installs to the cursor target as .mdc files in Cursor's frontmatter shape", async () => {
28
+ const results = await install("cursor", { homeOverride: home });
29
+ expect(results.length).toBe(15);
30
+ const cursorDir = join(home, ".cursor", "rules");
31
+ const files = readdirSync(cursorDir);
32
+ expect(files.every((f) => f.endsWith(".mdc"))).toBe(true);
33
+ expect(files.length).toBe(15);
34
+ // Cursor-shaped frontmatter (the three keys Cursor's rule loader reads).
35
+ const sample = readFileSync(join(cursorDir, files[0] ?? ""), "utf8");
36
+ expect(sample).toMatch(/^description:/m);
37
+ expect(sample).toMatch(/^globs:.*\*\*\/\*\.cls/m);
38
+ expect(sample).toMatch(/^alwaysApply:\s*false$/m);
39
+ // Old frontmatter is rewritten away (would confuse Cursor's parser).
40
+ expect(sample).not.toMatch(/^name:/m);
41
+ expect(sample).not.toMatch(/^triggers:/m);
42
+ expect(sample).not.toMatch(/^tools_used:/m);
43
+ // Body content preserved.
44
+ expect(sample).toMatch(/## Playbook/);
45
+ });
46
+ it("installs to all targets in one call", async () => {
47
+ const results = await install("all", { homeOverride: home });
48
+ const claudeCount = results.filter((r) => r.target === "claude").length;
49
+ const cursorCount = results.filter((r) => r.target === "cursor").length;
50
+ const vscodeCount = results.filter((r) => r.target === "vscode").length;
51
+ expect(claudeCount).toBe(15);
52
+ expect(cursorCount).toBe(15);
53
+ expect(vscodeCount).toBe(15);
54
+ expect(existsSync(join(home, ".claude", "skills"))).toBe(true);
55
+ expect(existsSync(join(home, ".cursor", "rules"))).toBe(true);
56
+ });
57
+ it("dry-run returns would-write actions and writes nothing", async () => {
58
+ const results = await install("all", { homeOverride: home, dryRun: true });
59
+ expect(results.every((r) => r.action === "would-write")).toBe(true);
60
+ expect(existsSync(join(home, ".claude", "skills"))).toBe(false);
61
+ expect(existsSync(join(home, ".cursor", "rules"))).toBe(false);
62
+ });
63
+ });
64
+ //# sourceMappingURL=installer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.test.js","sourceRoot":"","sources":["../../src/__tests__/installer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { type InstallOptions, type InstallResult, type SkillTarget, findSkillsRoot, install, listSkillsBundled, toClaudeFormat, toCursorFormat, } from "./installer.js";
2
+ export declare const SKILLS: readonly ["sf-impact-from-diff", "sf-what-broke", "sf-cross-layer-trace", "sf-dead-code-audit", "sf-governor-risk-fix", "sf-flow-impact", "sf-security-audit", "sf-cross-org-diff", "sf-deployment-manifest", "sf-omnistudio-migration-audit"];
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,cAAc,EACd,OAAO,EACP,iBAAiB,EACjB,cAAc,EACd,cAAc,GACf,MAAM,gBAAgB,CAAC;AAExB,eAAO,MAAM,MAAM,gPAWT,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ export { findSkillsRoot, install, listSkillsBundled, toClaudeFormat, toCursorFormat, } from "./installer.js";
2
+ export const SKILLS = [
3
+ "sf-impact-from-diff",
4
+ "sf-what-broke",
5
+ "sf-cross-layer-trace",
6
+ "sf-dead-code-audit",
7
+ "sf-governor-risk-fix",
8
+ "sf-flow-impact",
9
+ "sf-security-audit",
10
+ "sf-cross-org-diff",
11
+ "sf-deployment-manifest",
12
+ "sf-omnistudio-migration-audit",
13
+ ];
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,cAAc,EACd,OAAO,EACP,iBAAiB,EACjB,cAAc,EACd,cAAc,GACf,MAAM,gBAAgB,CAAC;AAExB,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,qBAAqB;IACrB,eAAe;IACf,sBAAsB;IACtB,oBAAoB;IACpB,sBAAsB;IACtB,gBAAgB;IAChB,mBAAmB;IACnB,mBAAmB;IACnB,wBAAwB;IACxB,+BAA+B;CACvB,CAAC"}
@@ -0,0 +1,39 @@
1
+ export type SkillTarget = "claude" | "cursor" | "vscode" | "all";
2
+ export interface InstallResult {
3
+ target: "claude" | "cursor" | "vscode";
4
+ skill: string;
5
+ path: string;
6
+ action: "created" | "updated" | "would-write" | "skipped";
7
+ }
8
+ export interface InstallOptions {
9
+ dryRun?: boolean;
10
+ homeOverride?: string;
11
+ }
12
+ /**
13
+ * Walk up from the current source file (works in dist/ and src/) to locate
14
+ * the bundled `skills/` directory next to this package's root.
15
+ */
16
+ export declare function findSkillsRoot(): string;
17
+ export declare function listSkillsBundled(): Promise<string[]>;
18
+ /** Claude consumes the SKILL.md format directly. */
19
+ export declare function toClaudeFormat(md: string): string;
20
+ /**
21
+ * Cursor expects `.mdc` rule files with a specific frontmatter contract:
22
+ * description: <short description the agent matches against>
23
+ * globs: <file patterns; can be empty for non-file-scoped rules>
24
+ * alwaysApply: <true = always loaded; false = agent-requested>
25
+ *
26
+ * Our skill files use a different frontmatter (name/description/triggers/
27
+ * tools_used). Rewrite the frontmatter into Cursor's shape so rules are
28
+ * actually picked up. We use 'agent-requested' mode (alwaysApply: false +
29
+ * rich description) which lets Cursor's agent pull the skill in when the
30
+ * user's prompt matches the description — without polluting every chat in
31
+ * unrelated projects.
32
+ *
33
+ * The body of the skill markdown (playbook, response shape, visualization,
34
+ * don't list) is preserved verbatim — that's the actual instructions the
35
+ * agent reads.
36
+ */
37
+ export declare function toCursorFormat(md: string): string;
38
+ export declare function install(target: SkillTarget, opts?: InstallOptions): Promise<InstallResult[]>;
39
+ //# sourceMappingURL=installer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;CAC3D;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAYD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAevC;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAU3D;AAED,oDAAoD;AACpD,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAoEjD;AAED,wBAAsB,OAAO,CAC3B,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,aAAa,EAAE,CAAC,CAwD1B"}
@@ -0,0 +1,187 @@
1
+ import { existsSync } from "node:fs";
2
+ import { copyFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ function targetDirs(home) {
7
+ return {
8
+ claude: join(home, ".claude", "skills"),
9
+ // Cursor uses ~/.cursor/rules with .mdc extension
10
+ cursor: join(home, ".cursor", "rules"),
11
+ // VS Code Claude extension shares ~/.claude
12
+ vscode: join(home, ".claude", "skills"),
13
+ };
14
+ }
15
+ /**
16
+ * Walk up from the current source file (works in dist/ and src/) to locate
17
+ * the bundled `skills/` directory next to this package's root.
18
+ */
19
+ export function findSkillsRoot() {
20
+ // import.meta.url -> /…/packages/skills/{dist,src}/installer.{js,ts}
21
+ const here = dirname(fileURLToPath(import.meta.url));
22
+ // candidates: ../skills, ../../skills (when inside dist/ subfolder)
23
+ const candidates = [
24
+ join(here, "..", "skills"),
25
+ join(here, "..", "..", "skills"),
26
+ join(here, "..", "..", "..", "skills"),
27
+ ];
28
+ for (const c of candidates) {
29
+ if (existsSync(c))
30
+ return c;
31
+ }
32
+ throw new Error(`@ryanstark24/sfgraph-skills: bundled skills directory not found (looked in ${candidates.join(", ")})`);
33
+ }
34
+ export async function listSkillsBundled() {
35
+ const root = findSkillsRoot();
36
+ const entries = await readdir(root, { withFileTypes: true });
37
+ const names = [];
38
+ for (const e of entries) {
39
+ if (!e.isDirectory())
40
+ continue;
41
+ const skillPath = join(root, e.name, "SKILL.md");
42
+ if (existsSync(skillPath))
43
+ names.push(e.name);
44
+ }
45
+ return names.sort();
46
+ }
47
+ /** Claude consumes the SKILL.md format directly. */
48
+ export function toClaudeFormat(md) {
49
+ return md;
50
+ }
51
+ /**
52
+ * Cursor expects `.mdc` rule files with a specific frontmatter contract:
53
+ * description: <short description the agent matches against>
54
+ * globs: <file patterns; can be empty for non-file-scoped rules>
55
+ * alwaysApply: <true = always loaded; false = agent-requested>
56
+ *
57
+ * Our skill files use a different frontmatter (name/description/triggers/
58
+ * tools_used). Rewrite the frontmatter into Cursor's shape so rules are
59
+ * actually picked up. We use 'agent-requested' mode (alwaysApply: false +
60
+ * rich description) which lets Cursor's agent pull the skill in when the
61
+ * user's prompt matches the description — without polluting every chat in
62
+ * unrelated projects.
63
+ *
64
+ * The body of the skill markdown (playbook, response shape, visualization,
65
+ * don't list) is preserved verbatim — that's the actual instructions the
66
+ * agent reads.
67
+ */
68
+ export function toCursorFormat(md) {
69
+ // Parse the leading YAML frontmatter.
70
+ const fmMatch = md.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
71
+ if (!fmMatch)
72
+ return md; // no frontmatter — leave as-is
73
+ const frontmatter = fmMatch[1] ?? "";
74
+ const body = fmMatch[2] ?? "";
75
+ // Pull out the name, description, and triggers list.
76
+ const nameMatch = frontmatter.match(/^name:\s*(.+?)\s*$/m);
77
+ const descMatch = frontmatter.match(/^description:\s*(.+?)\s*$/m);
78
+ const triggersMatch = frontmatter.match(/^triggers:\s*\n((?:[ \t]+-[^\n]*\n?)+)/m);
79
+ const name = nameMatch?.[1] ?? "sfgraph-skill";
80
+ const baseDescription = descMatch?.[1] ?? "";
81
+ const triggerPhrases = [];
82
+ if (triggersMatch?.[1]) {
83
+ for (const line of triggersMatch[1].split("\n")) {
84
+ const m = line.match(/^\s*-\s*"?(.+?)"?\s*$/);
85
+ if (m?.[1])
86
+ triggerPhrases.push(m[1]);
87
+ }
88
+ }
89
+ // Compose a rich description: base + trigger phrases. Cursor's agent
90
+ // matches the conversation against this string to decide whether to
91
+ // pull the rule in. Mention sfgraph + Salesforce explicitly so the
92
+ // match fires on tool-relevant questions.
93
+ const descriptionParts = [baseDescription];
94
+ if (triggerPhrases.length > 0) {
95
+ descriptionParts.push(`Triggers: ${triggerPhrases.join("; ")}.`);
96
+ }
97
+ descriptionParts.push(`Use this skill (${name}) for Salesforce questions via the sfgraph MCP tools.`);
98
+ const description = descriptionParts.filter(Boolean).join(" ");
99
+ // Salesforce file patterns that should auto-attach the rule when the user
100
+ // opens any of them. Once any of these are in scope, Cursor's agent gets
101
+ // the rule loaded into its context automatically — no need for the user
102
+ // to mention sfgraph or invoke the rule by name.
103
+ const globs = [
104
+ "**/sfdx-project.json",
105
+ "**/force-app/**",
106
+ "**/*.cls",
107
+ "**/*.cls-meta.xml",
108
+ "**/*.trigger",
109
+ "**/*.trigger-meta.xml",
110
+ "**/lwc/**",
111
+ "**/aura/**",
112
+ "**/*.flow-meta.xml",
113
+ "**/*.object-meta.xml",
114
+ "**/*.field-meta.xml",
115
+ "**/*.permissionset-meta.xml",
116
+ "**/*.permissionsetgroup-meta.xml",
117
+ "**/*.profile-meta.xml",
118
+ "**/*.sharingRules-meta.xml",
119
+ "**/*.layout-meta.xml",
120
+ "**/*.flexipage-meta.xml",
121
+ "**/*.namedCredential-meta.xml",
122
+ "**/*.workflow-meta.xml",
123
+ ].join(",");
124
+ return `---
125
+ description: ${description.replace(/\n/g, " ").replace(/"/g, "'")}
126
+ globs: ${globs}
127
+ alwaysApply: false
128
+ ---
129
+ ${body}`;
130
+ }
131
+ export async function install(target, opts = {}) {
132
+ const home = opts.homeOverride ?? homedir();
133
+ const dirs = targetDirs(home);
134
+ const targets = target === "all" ? ["claude", "cursor", "vscode"] : [target];
135
+ const results = [];
136
+ const root = findSkillsRoot();
137
+ const skills = await listSkillsBundled();
138
+ for (const t of targets) {
139
+ const outDir = dirs[t];
140
+ if (!opts.dryRun) {
141
+ await mkdir(outDir, { recursive: true });
142
+ }
143
+ for (const skill of skills) {
144
+ const src = join(root, skill, "SKILL.md");
145
+ const body = await readFile(src, "utf8");
146
+ let outPath;
147
+ let transformed;
148
+ if (t === "cursor") {
149
+ outPath = join(outDir, `${skill}.mdc`);
150
+ transformed = toCursorFormat(body);
151
+ }
152
+ else {
153
+ // claude + vscode: install as a directory with SKILL.md inside
154
+ const skillDir = join(outDir, skill);
155
+ outPath = join(skillDir, "SKILL.md");
156
+ transformed = toClaudeFormat(body);
157
+ if (!opts.dryRun) {
158
+ await mkdir(skillDir, { recursive: true });
159
+ }
160
+ }
161
+ let action;
162
+ if (opts.dryRun) {
163
+ action = "would-write";
164
+ }
165
+ else {
166
+ const existed = existsSync(outPath);
167
+ if (existed) {
168
+ const prev = await readFile(outPath, "utf8");
169
+ if (prev === transformed) {
170
+ action = "skipped";
171
+ }
172
+ else {
173
+ await writeFile(outPath, transformed, "utf8");
174
+ action = "updated";
175
+ }
176
+ }
177
+ else {
178
+ await writeFile(outPath, transformed, "utf8");
179
+ action = "created";
180
+ }
181
+ }
182
+ results.push({ target: t, skill, path: outPath, action });
183
+ }
184
+ }
185
+ return results;
186
+ }
187
+ //# sourceMappingURL=installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.js","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAgBzC,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;QACvC,kDAAkD;QAClD,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC;QACtC,4CAA4C;QAC5C,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;KACxC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,qEAAqE;IACrE,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,oEAAoE;IACpE,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC;QAC1B,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC;QAChC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC;KACvC,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,IAAI,KAAK,CACb,8EAA8E,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACvG,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE;YAAE,SAAS;QAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACjD,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,sCAAsC;IACtC,MAAM,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAC9D,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC,CAAC,+BAA+B;IAExD,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAE9B,qDAAqD;IACrD,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAClE,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAEnF,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC;IAC/C,MAAM,eAAe,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7C,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;YAC9C,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,oEAAoE;IACpE,mEAAmE;IACnE,0CAA0C;IAC1C,MAAM,gBAAgB,GAAG,CAAC,eAAe,CAAC,CAAC;IAC3C,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,gBAAgB,CAAC,IAAI,CAAC,aAAa,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnE,CAAC;IACD,gBAAgB,CAAC,IAAI,CACnB,mBAAmB,IAAI,uDAAuD,CAC/E,CAAC;IACF,MAAM,WAAW,GAAG,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE/D,0EAA0E;IAC1E,yEAAyE;IACzE,wEAAwE;IACxE,iDAAiD;IACjD,MAAM,KAAK,GAAG;QACZ,sBAAsB;QACtB,iBAAiB;QACjB,UAAU;QACV,mBAAmB;QACnB,cAAc;QACd,uBAAuB;QACvB,WAAW;QACX,YAAY;QACZ,oBAAoB;QACpB,sBAAsB;QACtB,qBAAqB;QACrB,6BAA6B;QAC7B,kCAAkC;QAClC,uBAAuB;QACvB,4BAA4B;QAC5B,sBAAsB;QACtB,yBAAyB;QACzB,+BAA+B;QAC/B,wBAAwB;KACzB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEZ,OAAO;eACM,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;SACxD,KAAK;;;EAGZ,IAAI,EAAE,CAAC;AACT,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,MAAmB,EACnB,OAAuB,EAAE;IAEzB,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,IAAI,OAAO,EAAE,CAAC;IAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,OAAO,GACX,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAoB,EAAE,CAAC;IAEpC,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAEzC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;YAC1C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAEzC,IAAI,OAAe,CAAC;YACpB,IAAI,WAAmB,CAAC;YACxB,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACnB,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;gBACvC,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;iBAAM,CAAC;gBACN,+DAA+D;gBAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBACrC,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;gBACrC,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;gBACnC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACjB,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC7C,CAAC;YACH,CAAC;YAED,IAAI,MAA+B,CAAC;YACpC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,GAAG,aAAa,CAAC;YACzB,CAAC;iBAAM,CAAC;gBACN,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;gBACpC,IAAI,OAAO,EAAE,CAAC;oBACZ,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;oBAC7C,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;wBACzB,MAAM,GAAG,SAAS,CAAC;oBACrB,CAAC;yBAAM,CAAC;wBACN,MAAM,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;wBAC9C,MAAM,GAAG,SAAS,CAAC;oBACrB,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,MAAM,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;oBAC9C,MAAM,GAAG,SAAS,CAAC;gBACrB,CAAC;YACH,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@ryanstark24/sfgraph-skills",
3
+ "version": "1.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "devDependencies": {
14
+ "@biomejs/biome": "^1.9.0",
15
+ "@types/node": "^22.0.0",
16
+ "typescript": "^5.6.0",
17
+ "vitest": "^3.2.4"
18
+ },
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/ryanStark24/sfgraph"
23
+ },
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "description": "sfgraph SKILL.md playbooks for Cursor and Claude",
31
+ "files": [
32
+ "dist",
33
+ "skills",
34
+ "README.md"
35
+ ],
36
+ "keywords": [
37
+ "salesforce",
38
+ "sfgraph",
39
+ "mcp",
40
+ "skills",
41
+ "playbook"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsc -p tsconfig.json",
45
+ "test": "vitest run --passWithNoTests",
46
+ "lint": "biome check src",
47
+ "lint:fix": "biome check --write src",
48
+ "typecheck": "tsc -p tsconfig.json --noEmit",
49
+ "clean": "rm -rf dist"
50
+ }
51
+ }
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: sf-cross-layer-trace
3
+ description: Trace a field, component, or method end-to-end across LWC, Apex, Flow, and data layer. PROACTIVELY volunteer this skill whenever the focal artifact is an LWC bundle, Aura component, ApexPage, or a custom field — even on explain-style questions ("explain this LWC", "what does this component do", "where is X used"). After answering the surface question, offer a cross-layer trace so the user sees what the artifact ultimately touches through Apex / Flow / SObject.
4
+ triggers:
5
+ - "UI to DB"
6
+ - "trace this LWC"
7
+ - "end-to-end"
8
+ - "where is this field used"
9
+ - "where does this come from"
10
+ - "explain this LWC"
11
+ - "explain this component"
12
+ - "what does this component do"
13
+ - "what does this LWC do"
14
+ - "explain this Aura"
15
+ - "explain this VisualForce"
16
+ - "explain this ApexPage"
17
+ - "where is this component used"
18
+ - "what pages embed"
19
+ - "which pages use"
20
+ tools_used:
21
+ - cross_layer_flow_map
22
+ - analyze_field
23
+ - staleness_check
24
+ - explain_code
25
+ ---
26
+
27
+ # sf-cross-layer-trace
28
+
29
+ Use when the user needs to follow a single artifact (field, LWC, Apex method) across every layer it touches — UI rendering, Apex controller, flow automation, data model.
30
+
31
+ ## When to volunteer this skill
32
+
33
+ Source-only tools (file grep, repo search, `explain_code`) can answer "where is this used?" inside the sfdx-source repo, but they cannot see:
34
+ - Lightning App Builder page bindings (FlexiPages embedding the component)
35
+ - Quick Action / Lightning Action targets
36
+ - Flow screen components referencing the LWC
37
+ - Apex controllers the LWC `@wire`s into, and what those controllers ultimately read/write
38
+
39
+ So whenever the focal node is an **LWC bundle, Aura component, ApexPage, custom field, or Apex class/method**, after answering the surface question proactively offer:
40
+
41
+ > "I answered using the repo. Want me to run `cross_layer_flow_map` on `<node>` against the ingested org graph? That will surface page bindings, controller chains, and the SObject fields it ultimately touches — things the source files alone don't show."
42
+
43
+ Only skip the offer when the user has explicitly scoped the question to source (e.g. "just look at the repo, don't query the graph") or when the graph staleness check shows the org isn't ingested at all.
44
+
45
+ ## Playbook
46
+
47
+ 1. Resolve the starting node. If the user gave a field, call `analyze_field` first to canonicalise `SObject.Field` and gather direct readers/writers.
48
+ 2. Call `cross_layer_flow_map` with the resolved node. Receive ordered paths across layers (UI -> Controller -> Service -> Selector -> SObject and any Flow / Process Builder branches).
49
+ 3. For each distinct path, summarise the layer transitions with the edge type that bridges them (`@wire`, `@AuraEnabled`, `READS_FIELD`, `UPDATES_FIELD`, `INVOKES_FLOW`).
50
+ 4. Surface anything anomalous: cycles, missing `with sharing`, fields read by multiple LWCs but written only by Flow, etc.
51
+ 5. Render the Mermaid layered diagram from `cross_layer_flow_map`. If the graph has more than 30 nodes, ask whether to filter to a single layer pair.
52
+ 6. End with a one-line recommendation of which related skill to invoke next (e.g. `sf-security-audit` if FLS gaps appear).
53
+
54
+ ## Visualization
55
+
56
+ Render a **`flowchart TD`** layered diagram with one subgraph per layer (UI / Controller / Service / Selector / Data). Edges are labelled with the edge type that bridges layers (`@wire`, `@AuraEnabled`, `READS_FIELD`, `UPDATES_FIELD`, `INVOKES_FLOW`).
57
+
58
+ ```
59
+ flowchart TD
60
+ subgraph UI
61
+ LWC[accountList.js]
62
+ end
63
+ subgraph Apex
64
+ Ctl[AccountController.getAccounts]
65
+ Svc[AccountSelector.queryAll]
66
+ end
67
+ subgraph Data
68
+ F[(Account.Name)]
69
+ end
70
+ LWC -->|@wire| Ctl --> Svc -->|READS_FIELD| F
71
+ ```
72
+
73
+ If the resolved trace has >30 nodes, ask the user to narrow to a single layer-pair before rendering — a sprawling layered diagram is more misleading than no diagram.
74
+
75
+ ## Staleness check
76
+
77
+ Before calling `cross_layer_flow_map`, invoke `staleness_check` for the target org. If the report says stale, surface a warning to the user:
78
+
79
+ > Your ingest is N days old. Run `sfgraph ingest --org <alias>` to refresh, or proceed with the understanding that the graph may not reflect recent changes.
80
+
81
+ Then continue with the playbook.
82
+
83
+ ## Response Shape
84
+
85
+ - **Subject** — the resolved node id and type.
86
+ - **Paths** — numbered list of distinct UI->DB paths with layer transitions.
87
+ - **Anomalies** — bullets calling out anything that looks structurally suspicious.
88
+ - **Mermaid layered diagram** — embedded from tool output.
89
+ - **Suggested next skill** — one line.
90
+
91
+ ## Don't
92
+
93
+ - Do not collapse layers — the value of this skill is showing the layer boundaries explicitly.
94
+ - Do not include nodes outside the requested traversal depth; defer breadth to a follow-up call.
95
+ - Do not modify anything. Pure read.
96
+ - Do not assume the starting node exists; if `analyze_field` returns empty, ask the user to refine.
97
+ - Do not stop at a source-only answer for LWC / Aura / ApexPage / field / Apex method questions without at least *offering* the cross-layer trace. Repo grep misses FlexiPage bindings, Quick Action targets, and the Apex→SObject tail — say so and offer to fill it in.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: sf-cross-org-diff
3
+ description: Compare metadata between two orgs (sandbox vs prod, two sandboxes) and summarise drift.
4
+ triggers:
5
+ - "sandbox vs prod"
6
+ - "what changed"
7
+ - "org diff"
8
+ - "compare orgs"
9
+ - "drift"
10
+ tools_used:
11
+ - cross_org_diff
12
+ - point_in_time_diff
13
+ - list_orgs
14
+ - staleness_check
15
+ ---
16
+
17
+ # sf-cross-org-diff
18
+
19
+ Use when the user wants to understand drift between two orgs — typically pre-deploy verification or post-deploy reconciliation.
20
+
21
+ ## Playbook
22
+
23
+ 1. Call `list_orgs` first. Present the user with the authenticated orgs, each org's `ingested` flag, last sync timestamp, and `stale` flag. Ask the user to pick the two orgs to compare. Refuse to proceed if either selected org has `ingested=false` — instruct them to run `sfgraph ingest --org <alias>` first.
24
+ 2. Call `cross_org_diff(orgA, orgB)`. Capture added / removed / modified nodes grouped by metadata category.
25
+ 3. For high-churn categories, call `point_in_time_diff` inside each org to confirm the drift originated there (vs being a stale snapshot artifact).
26
+ 4. Categorise drift: **structural** (new objects/fields), **logic** (Apex, Flow body changes), **security** (profile / sharing), **config** (custom metadata / settings).
27
+ 5. Render the Mermaid drift summary from the tool. For each category, list the top-N items by impact.
28
+ 6. Recommend `sf-deployment-manifest` if the user wants to bring orgs into sync.
29
+
30
+ ## Visualization
31
+
32
+ Render a **`flowchart LR`** drift summary. Two side-by-side subgraphs (one per org) joined by edges that classify drift (added / removed / modified). Use colour to indicate severity by metadata category.
33
+
34
+ ```
35
+ flowchart LR
36
+ subgraph A[orgA]
37
+ A1[ApexClass:Foo]
38
+ end
39
+ subgraph B[orgB]
40
+ B1[ApexClass:Foo']
41
+ end
42
+ A1 -->|modified| B1
43
+ A2[Field:Account.X]:::removed -.->|absent| B
44
+ classDef removed fill:#fdd,stroke:#900
45
+ ```
46
+
47
+ For diffs with >50 nodes, switch to a bucketed table per category and skip the diagram.
48
+
49
+ ## Staleness check
50
+
51
+ Before calling `cross_org_diff`, invoke `staleness_check` for both orgs. If either is stale, surface a warning to the user:
52
+
53
+ > Your ingest for `<alias>` is N days old. Run `sfgraph ingest --org <alias>` to refresh, or proceed with the understanding that the graph may not reflect recent changes.
54
+
55
+ Then continue with the playbook.
56
+
57
+ ## Response Shape
58
+
59
+ - **Orgs compared** — `orgA -> orgB`.
60
+ - **Drift by category** — structural / logic / security / config, each with a count and top items.
61
+ - **Notable single-side items** — present in A only or B only.
62
+ - **Mermaid drift summary** — embedded.
63
+ - **Next step** — single line, typically pointing at deployment manifest skill.
64
+
65
+ ## Don't
66
+
67
+ - Do not deploy or copy metadata between orgs from this skill.
68
+ - Do not assume `orgA` is "source of truth"; report drift symmetrically.
69
+ - Do not include freshness drift (last-modified-by user) in the structural bucket — call it out separately.
70
+ - Do not run if either org has not been ingested; instruct the user instead.