@mhosaic/feedback-cli 0.49.0 → 0.50.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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/bin.js +6 -7
  3. package/dist/{build-RCLWV2WL.js → build-AHXPNO76.js} +1 -2
  4. package/dist/{check-FOJPEE4Q.js → check-UZZFPZMA.js} +3 -4
  5. package/dist/{chunk-COJ75KUD.js → chunk-45QCNACT.js} +0 -1
  6. package/dist/{chunk-AZQSQJNQ.js → chunk-CFJPCJAQ.js} +0 -1
  7. package/dist/{chunk-SSLQOK2Z.js → chunk-GJEJSB2Q.js} +2 -3
  8. package/dist/{chunk-IHJPCMYF.js → chunk-KHQBWJ4A.js} +0 -1
  9. package/dist/config-75OC57EE.js +7 -0
  10. package/dist/{doctor-CTL34U4Y.js → doctor-Z4KZBFBN.js} +1 -2
  11. package/dist/{eject-VNJVOCQM.js → eject-QFPZ7Z3P.js} +1 -2
  12. package/dist/{generate-VFRQNPOI.js → generate-CLL5W7SX.js} +3 -4
  13. package/dist/{init-RS7BKALE.js → init-BK6TPKX6.js} +1 -2
  14. package/dist/{install-skill-BIBDYLKE.js → install-skill-FZ4YOIHX.js} +79 -10
  15. package/dist/{qa-YR4GCV6T.js → qa-APZ5CKN5.js} +9 -10
  16. package/dist/{sitemap-react-QTEAUKN5.js → sitemap-react-IF5PX2PM.js} +2 -3
  17. package/dist/{sitemap-vue-EJDQTCYM.js → sitemap-vue-LWK6Y4FE.js} +2 -3
  18. package/dist/{verify-LCXL3WOX.js → verify-24HTNRXV.js} +0 -1
  19. package/package.json +1 -1
  20. package/skills/integrate-feedback/SKILL.md +25 -91
  21. package/skills/integrate-feedback/references/consumer-install.md +3 -3
  22. package/skills/integrate-feedback/references/verify-install.md +21 -20
  23. package/dist/bin.js.map +0 -1
  24. package/dist/build-RCLWV2WL.js.map +0 -1
  25. package/dist/check-FOJPEE4Q.js.map +0 -1
  26. package/dist/chunk-AZQSQJNQ.js.map +0 -1
  27. package/dist/chunk-COJ75KUD.js.map +0 -1
  28. package/dist/chunk-IHJPCMYF.js.map +0 -1
  29. package/dist/chunk-SSLQOK2Z.js.map +0 -1
  30. package/dist/config-JE3QRZVH.js +0 -8
  31. package/dist/config-JE3QRZVH.js.map +0 -1
  32. package/dist/doctor-CTL34U4Y.js.map +0 -1
  33. package/dist/eject-VNJVOCQM.js.map +0 -1
  34. package/dist/generate-VFRQNPOI.js.map +0 -1
  35. package/dist/init-RS7BKALE.js.map +0 -1
  36. package/dist/install-skill-BIBDYLKE.js.map +0 -1
  37. package/dist/qa-YR4GCV6T.js.map +0 -1
  38. package/dist/sitemap-react-QTEAUKN5.js.map +0 -1
  39. package/dist/sitemap-vue-EJDQTCYM.js.map +0 -1
  40. package/dist/verify-LCXL3WOX.js.map +0 -1
  41. package/skills/chantier/SKILL.md +0 -141
  42. package/skills/feedback-close/SKILL.md +0 -66
  43. package/skills/feedback-fix/SKILL.md +0 -176
  44. package/skills/feedback-from-meeting/SKILL.md +0 -173
  45. package/skills/feedback-pull/SKILL.md +0 -74
  46. package/skills/feedback-watch-merges/SKILL.md +0 -68
  47. package/skills/integrate-feedback/references/operator-provision.md +0 -397
  48. package/skills/issue-pull/SKILL.md +0 -49
package/README.md CHANGED
@@ -91,7 +91,7 @@ pnpm remove @mhosaic/feedback # or npm/yarn equivalent
91
91
 
92
92
  ## The guided skill
93
93
 
94
- After `install-skill`, run `/integrate-feedback` inside Claude Code. The skill branches on **operator** vs **consumer** mode and walks you through provisioning a project, generating a public key, and wiring the widget into your specific framework — with explicit checkpoints, framework-aware snippets, and a smoke test that proves the end-to-end path works before declaring done.
94
+ After `install-skill`, run `/integrate-feedback` inside Claude Code. Paste the handoff payload from your Mhosaic contact (endpoint, `pk_proj_…` key, allowed origins) as your first message, and the skill walks you through wiring the widget into your specific framework — with explicit checkpoints, framework-aware snippets, and a smoke test that proves the end-to-end path works before declaring done. (Provisioning the project/key itself is a separate, Mhosaic-internal step — not part of this package.)
95
95
 
96
96
  Full details: `skills/integrate-feedback/SKILL.md` in this package, or installed at `~/.claude/skills/integrate-feedback/SKILL.md` after `install-skill`.
97
97
 
package/dist/bin.js CHANGED
@@ -4,27 +4,27 @@
4
4
  async function main() {
5
5
  const [, , cmd, ...args] = process.argv;
6
6
  if (!cmd || cmd === "init") {
7
- const { runInit } = await import("./init-RS7BKALE.js");
7
+ const { runInit } = await import("./init-BK6TPKX6.js");
8
8
  return runInit(args);
9
9
  }
10
10
  if (cmd === "doctor") {
11
- const { runDoctor } = await import("./doctor-CTL34U4Y.js");
11
+ const { runDoctor } = await import("./doctor-Z4KZBFBN.js");
12
12
  return runDoctor(args);
13
13
  }
14
14
  if (cmd === "eject") {
15
- const { runEject } = await import("./eject-VNJVOCQM.js");
15
+ const { runEject } = await import("./eject-QFPZ7Z3P.js");
16
16
  return runEject(args);
17
17
  }
18
18
  if (cmd === "verify") {
19
- const { runVerify } = await import("./verify-LCXL3WOX.js");
19
+ const { runVerify } = await import("./verify-24HTNRXV.js");
20
20
  return runVerify(args);
21
21
  }
22
22
  if (cmd === "install-skill") {
23
- const { runInstallSkill } = await import("./install-skill-BIBDYLKE.js");
23
+ const { runInstallSkill } = await import("./install-skill-FZ4YOIHX.js");
24
24
  return runInstallSkill(args);
25
25
  }
26
26
  if (cmd === "qa") {
27
- const { runQa } = await import("./qa-YR4GCV6T.js");
27
+ const { runQa } = await import("./qa-APZ5CKN5.js");
28
28
  return runQa(args);
29
29
  }
30
30
  if (cmd === "--version" || cmd === "-v") {
@@ -48,4 +48,3 @@ main().catch((err) => {
48
48
  `);
49
49
  process.exitCode = 1;
50
50
  });
51
- //# sourceMappingURL=bin.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadQaConfig
4
- } from "./chunk-IHJPCMYF.js";
4
+ } from "./chunk-KHQBWJ4A.js";
5
5
 
6
6
  // src/qa/build.ts
7
7
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
@@ -562,4 +562,3 @@ export {
562
562
  buildArtifact,
563
563
  runBuild
564
564
  };
565
- //# sourceMappingURL=build-RCLWV2WL.js.map
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  generateSuites,
4
4
  serializeSuite
5
- } from "./chunk-SSLQOK2Z.js";
6
- import "./chunk-AZQSQJNQ.js";
5
+ } from "./chunk-GJEJSB2Q.js";
6
+ import "./chunk-CFJPCJAQ.js";
7
7
  import {
8
8
  loadQaConfig
9
- } from "./chunk-IHJPCMYF.js";
9
+ } from "./chunk-KHQBWJ4A.js";
10
10
 
11
11
  // src/qa/check.ts
12
12
  import { execFileSync } from "child_process";
@@ -339,4 +339,3 @@ export {
339
339
  runCheck,
340
340
  runChecks
341
341
  };
342
- //# sourceMappingURL=check-FOJPEE4Q.js.map
@@ -23,4 +23,3 @@ async function detectFramework(cwd) {
23
23
  export {
24
24
  detectFramework
25
25
  };
26
- //# sourceMappingURL=chunk-COJ75KUD.js.map
@@ -80,4 +80,3 @@ export {
80
80
  normalizePattern,
81
81
  buildSitemap
82
82
  };
83
- //# sourceMappingURL=chunk-AZQSQJNQ.js.map
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  normalizePattern
4
- } from "./chunk-AZQSQJNQ.js";
4
+ } from "./chunk-CFJPCJAQ.js";
5
5
  import {
6
6
  loadQaConfig
7
- } from "./chunk-IHJPCMYF.js";
7
+ } from "./chunk-KHQBWJ4A.js";
8
8
 
9
9
  // src/qa/generate.ts
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
@@ -453,4 +453,3 @@ export {
453
453
  serializeSuite,
454
454
  runGenerate
455
455
  };
456
- //# sourceMappingURL=chunk-SSLQOK2Z.js.map
@@ -123,4 +123,3 @@ function loadQaConfig(cwd) {
123
123
  export {
124
124
  loadQaConfig
125
125
  };
126
- //# sourceMappingURL=chunk-IHJPCMYF.js.map
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadQaConfig
4
+ } from "./chunk-KHQBWJ4A.js";
5
+ export {
6
+ loadQaConfig
7
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  detectFramework
4
- } from "./chunk-COJ75KUD.js";
4
+ } from "./chunk-45QCNACT.js";
5
5
 
6
6
  // src/commands/doctor.ts
7
7
  import { existsSync, readFileSync, readdirSync } from "fs";
@@ -225,4 +225,3 @@ export {
225
225
  scanWidgetWiring,
226
226
  scriptSrcOf
227
227
  };
228
- //# sourceMappingURL=doctor-CTL34U4Y.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  detectFramework
4
- } from "./chunk-COJ75KUD.js";
4
+ } from "./chunk-45QCNACT.js";
5
5
 
6
6
  // src/commands/eject.ts
7
7
  import { existsSync, readFileSync } from "fs";
@@ -37,4 +37,3 @@ async function runEject(argv) {
37
37
  export {
38
38
  runEject
39
39
  };
40
- //# sourceMappingURL=eject-VNJVOCQM.js.map
@@ -3,12 +3,11 @@ import {
3
3
  generateSuites,
4
4
  runGenerate,
5
5
  serializeSuite
6
- } from "./chunk-SSLQOK2Z.js";
7
- import "./chunk-AZQSQJNQ.js";
8
- import "./chunk-IHJPCMYF.js";
6
+ } from "./chunk-GJEJSB2Q.js";
7
+ import "./chunk-CFJPCJAQ.js";
8
+ import "./chunk-KHQBWJ4A.js";
9
9
  export {
10
10
  generateSuites,
11
11
  runGenerate,
12
12
  serializeSuite
13
13
  };
14
- //# sourceMappingURL=generate-VFRQNPOI.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  detectFramework
4
- } from "./chunk-COJ75KUD.js";
4
+ } from "./chunk-45QCNACT.js";
5
5
 
6
6
  // src/commands/init.ts
7
7
  import { existsSync as existsSync3 } from "fs";
@@ -269,4 +269,3 @@ async function runInit(argv) {
269
269
  export {
270
270
  runInit
271
271
  };
272
- //# sourceMappingURL=init-RS7BKALE.js.map
@@ -1,21 +1,65 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands/install-skill.ts
4
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
4
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
5
5
  import { homedir } from "os";
6
- import { dirname, join } from "path";
6
+ import { dirname, join, sep } from "path";
7
7
  import { fileURLToPath } from "url";
8
8
  import kleur from "kleur";
9
9
  function parseArgs(argv) {
10
- const out = { force: false, dryRun: false, dest: join(homedir(), ".claude", "skills") };
10
+ const out = { force: false, dryRun: false, dest: join(homedir(), ".claude", "skills"), pruneRetired: false };
11
11
  for (let i = 0; i < argv.length; i++) {
12
12
  const a = argv[i];
13
13
  if (a === "--force" || a === "-f") out.force = true;
14
14
  else if (a === "--dry-run") out.dryRun = true;
15
15
  else if (a === "--dest") out.dest = argv[++i] ?? out.dest;
16
+ else if (a === "--prune-retired") out.pruneRetired = true;
16
17
  }
17
18
  return out;
18
19
  }
20
+ var RETIRED_SKILLS = [
21
+ "chantier",
22
+ "feedback-close",
23
+ "feedback-fix",
24
+ "feedback-from-meeting",
25
+ "feedback-pull",
26
+ "feedback-watch-merges",
27
+ "issue-pull"
28
+ ];
29
+ var RETIRED_MARKER = "mhosaic-feedback";
30
+ function hasSymlinkOnPath(dest, rel) {
31
+ let current = dest;
32
+ for (const part of rel.split(sep)) {
33
+ current = join(current, part);
34
+ if (!existsSync(current)) return false;
35
+ if (lstatSync(current).isSymbolicLink()) return true;
36
+ }
37
+ return false;
38
+ }
39
+ function looksLikeOurRetiredSkill(dest, name) {
40
+ const skillMd = join(dest, name, "SKILL.md");
41
+ if (!existsSync(skillMd)) return false;
42
+ const content = readFileSync(skillMd, "utf8");
43
+ const match = content.match(/^name:\s*(.+)$/m);
44
+ if (match?.[1]?.trim() !== name) return false;
45
+ return content.includes(RETIRED_MARKER);
46
+ }
47
+ function scanRetired(dest) {
48
+ const prunable = [];
49
+ const skipped = [];
50
+ for (const name of RETIRED_SKILLS) {
51
+ if (!existsSync(join(dest, name))) continue;
52
+ if (hasSymlinkOnPath(dest, name)) {
53
+ skipped.push({ name, reason: "symlink in the path" });
54
+ } else if (!looksLikeOurRetiredSkill(dest, name)) {
55
+ skipped.push({ name, reason: "not a Mhosaic skill" });
56
+ } else {
57
+ prunable.push(name);
58
+ }
59
+ }
60
+ return { prunable, skipped };
61
+ }
62
+ var STALE_OWN_FILE = join("integrate-feedback", "references", "operator-provision.md");
19
63
  function findSkillsSource() {
20
64
  const here = dirname(fileURLToPath(import.meta.url));
21
65
  const candidates = [
@@ -76,6 +120,38 @@ async function runInstallSkill(argv) {
76
120
  process.stdout.write(kleur.yellow(`\u26A0 ${skipped} file(s) skipped (already exist; use --force to overwrite)
77
121
  `));
78
122
  }
123
+ const scan = scanRetired(args.dest);
124
+ if (args.pruneRetired) {
125
+ if (scan.prunable.length > 0) {
126
+ if (!args.dryRun) {
127
+ for (const name of scan.prunable) rmSync(join(args.dest, name), { recursive: true, force: true });
128
+ }
129
+ process.stdout.write(
130
+ kleur.yellow(`\u26A0 ${scan.prunable.length} retired skill(s) ${args.dryRun ? "would be " : ""}removed: ${scan.prunable.join(", ")}
131
+ `)
132
+ );
133
+ }
134
+ for (const { name, reason } of scan.skipped) {
135
+ process.stdout.write(kleur.gray(` left alone: ${name} (${reason})
136
+ `));
137
+ }
138
+ } else if (scan.prunable.length > 0) {
139
+ process.stdout.write(
140
+ kleur.yellow(`\u26A0 These skills are no longer part of this package: ${scan.prunable.join(", ")}
141
+ `)
142
+ );
143
+ process.stdout.write(kleur.gray(" Run with --prune-retired to remove them.\n"));
144
+ }
145
+ if (hasSymlinkOnPath(args.dest, STALE_OWN_FILE)) {
146
+ process.stdout.write(kleur.gray(` left alone: ${STALE_OWN_FILE} (symlink in the path)
147
+ `));
148
+ } else if (existsSync(join(args.dest, STALE_OWN_FILE))) {
149
+ if (!args.dryRun) rmSync(join(args.dest, STALE_OWN_FILE), { force: true });
150
+ process.stdout.write(
151
+ kleur.yellow(`\u26A0 ${args.dryRun ? "would remove" : "removed"} ${STALE_OWN_FILE} (no longer part of this package)
152
+ `)
153
+ );
154
+ }
79
155
  process.stdout.write("\n");
80
156
  process.stdout.write(kleur.bold("Next:\n"));
81
157
  process.stdout.write(` 1. ${kleur.gray("Restart Claude Code if you have it open \u2014 skills are discovered at session start.")}
@@ -83,15 +159,8 @@ async function runInstallSkill(argv) {
83
159
  process.stdout.write(` 2. ${kleur.gray("Onboard a new host app: ")}${kleur.cyan("/integrate-feedback")}
84
160
  `);
85
161
  process.stdout.write(` 3. ${kleur.gray("Add the QA Meter (test-coverage FAB) to a host app: ")}${kleur.cyan("/integrate-qa-meter")}
86
- `);
87
- process.stdout.write(` 4. ${kleur.gray("Triage + fix reports: ")}${kleur.cyan("/feedback-pull")} ${kleur.gray("\u2192")} ${kleur.cyan("/feedback-fix")} ${kleur.gray("\u2192")} ${kleur.cyan("/feedback-watch-merges")} ${kleur.gray("\u2192")} ${kleur.cyan("/feedback-close")}
88
- `);
89
- process.stdout.write(` 5. ${kleur.gray("Design before development (chantiers): ")}${kleur.cyan("/chantier")}
90
- `);
91
- process.stdout.write(` 6. ${kleur.gray("Record a meeting's feedback in each person's name: ")}${kleur.cyan("/feedback-from-meeting")}
92
162
  `);
93
163
  }
94
164
  export {
95
165
  runInstallSkill
96
166
  };
97
- //# sourceMappingURL=install-skill-BIBDYLKE.js.map
@@ -5,16 +5,16 @@ async function runQa(args) {
5
5
  const [sub, ...rest] = args;
6
6
  const commands = {
7
7
  sitemap: async () => ({ run: dispatchSitemap }),
8
- generate: async () => ({ run: (await import("./generate-VFRQNPOI.js")).runGenerate }),
9
- build: async () => ({ run: (await import("./build-RCLWV2WL.js")).runBuild }),
10
- check: async () => ({ run: (await import("./check-FOJPEE4Q.js")).runCheck }),
8
+ generate: async () => ({ run: (await import("./generate-CLL5W7SX.js")).runGenerate }),
9
+ build: async () => ({ run: (await import("./build-AHXPNO76.js")).runBuild }),
10
+ check: async () => ({ run: (await import("./check-UZZFPZMA.js")).runCheck }),
11
11
  refresh: async () => ({
12
12
  run: async (a) => {
13
- const { loadQaConfig } = await import("./config-JE3QRZVH.js");
13
+ const { loadQaConfig } = await import("./config-75OC57EE.js");
14
14
  const cfg = loadQaConfig(process.cwd());
15
15
  if (cfg.sitemap?.framework) await dispatchSitemap(a);
16
- await (await import("./generate-VFRQNPOI.js")).runGenerate(a);
17
- await (await import("./build-RCLWV2WL.js")).runBuild(a);
16
+ await (await import("./generate-CLL5W7SX.js")).runGenerate(a);
17
+ await (await import("./build-AHXPNO76.js")).runBuild(a);
18
18
  }
19
19
  })
20
20
  };
@@ -27,10 +27,10 @@ async function runQa(args) {
27
27
  await (await entry()).run(rest);
28
28
  }
29
29
  async function dispatchSitemap(args) {
30
- const { loadQaConfig } = await import("./config-JE3QRZVH.js");
30
+ const { loadQaConfig } = await import("./config-75OC57EE.js");
31
31
  const framework = loadQaConfig(process.cwd()).sitemap?.framework;
32
- if (framework === "vue-router") return (await import("./sitemap-vue-EJDQTCYM.js")).runSitemap(args);
33
- if (framework === "react-router") return (await import("./sitemap-react-QTEAUKN5.js")).runReactSitemap(args);
32
+ if (framework === "vue-router") return (await import("./sitemap-vue-LWK6Y4FE.js")).runSitemap(args);
33
+ if (framework === "react-router") return (await import("./sitemap-react-IF5PX2PM.js")).runReactSitemap(args);
34
34
  process.stderr.write(
35
35
  `qa sitemap: unsupported framework ${framework ? `'${framework}'` : "(none set)"} (supported: vue-router, react-router). Set config.sitemap.framework.
36
36
  `
@@ -40,4 +40,3 @@ async function dispatchSitemap(args) {
40
40
  export {
41
41
  runQa
42
42
  };
43
- //# sourceMappingURL=qa-YR4GCV6T.js.map
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildSitemap
4
- } from "./chunk-AZQSQJNQ.js";
4
+ } from "./chunk-CFJPCJAQ.js";
5
5
  import {
6
6
  loadQaConfig
7
- } from "./chunk-IHJPCMYF.js";
7
+ } from "./chunk-KHQBWJ4A.js";
8
8
 
9
9
  // src/qa/sitemap-react.ts
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
@@ -327,4 +327,3 @@ export {
327
327
  readJsxRoutes,
328
328
  runReactSitemap
329
329
  };
330
- //# sourceMappingURL=sitemap-react-QTEAUKN5.js.map
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildSitemap
4
- } from "./chunk-AZQSQJNQ.js";
4
+ } from "./chunk-CFJPCJAQ.js";
5
5
  import {
6
6
  loadQaConfig
7
- } from "./chunk-IHJPCMYF.js";
7
+ } from "./chunk-KHQBWJ4A.js";
8
8
 
9
9
  // src/qa/sitemap-vue.ts
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
@@ -240,4 +240,3 @@ export {
240
240
  extractWithWarnings,
241
241
  runSitemap
242
242
  };
243
- //# sourceMappingURL=sitemap-vue-EJDQTCYM.js.map
@@ -185,4 +185,3 @@ ${failed} check(s) failed.
185
185
  export {
186
186
  runVerify
187
187
  };
188
- //# sourceMappingURL=verify-LCXL3WOX.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhosaic/feedback-cli",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "CLI to install @mhosaic/feedback into a host app, verify the integration, and drop a guided Claude Code skill (/integrate-feedback) into ~/.claude/skills.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,16 +1,12 @@
1
1
  ---
2
2
  name: integrate-feedback
3
- description: Full guide for integrating @mhosaic/feedback. Covers operator-side provisioning (Company/Project/pk_proj_ key creation via the admin SPA), consumer-side install (CLI run + framework-specific wiring + identify() recipes + smoke test), and every common question a teammate might ask — what permissions are needed, where to run from, how the operator→consumer handoff works, what Chrome MCP is for, external clients vs internal apps, why the FAB isn't appearing, CORS errors, etc. Use whenever the user mentions: the Mhosaic feedback widget, adding feedback to an app, onboarding a new client/app, getting a pk_proj_ key, deploying for a company, configuring identify(), CORS errors on the widget, or any troubleshooting around the integration. Equally useful as a slash-command runbook and as a reference Claude reads to answer ad-hoc questions.
3
+ description: Full guide for installing @mhosaic/feedback into a host app — CLI run, framework-specific wiring, identify() recipes, and a live smoke test — plus every common question a teammate might ask about the integration. Use whenever the user mentions: the Mhosaic feedback widget, adding feedback to an app, installing the widget in a client/app, wiring identify(), CORS errors on the widget, or any troubleshooting around the integration. Equally useful as a slash-command runbook and as a reference Claude reads to answer ad-hoc questions.
4
4
  user-invocable: true
5
5
  ---
6
6
 
7
- # /integrate-feedback — guided widget integration
7
+ # /integrate-feedback — guided widget install
8
8
 
9
- End-to-end flow for getting the **@mhosaic/feedback** widget into a host app. There are two phases, run in two separate places:
10
-
11
- 1. **Operator phase** — provisions Company / Project / `pk_proj_…` key on the Mhosaic backend (https://software-factory-3tbbu.ondigitalocean.app). Run from **inside `feedback-tool-mhosaic`**. The skill drives Chrome at the admin SPA, no filesystem changes. At the end, it outputs a Markdown handoff payload and offers to install itself globally so phase 2 is invokable in the client's repo.
12
-
13
- 2. **Consumer phase** — installs the widget in the host app. Run from **inside the client's app directory** (NOT feedback-tool-mhosaic). The skill detects the framework, runs the CLI's `init`, pastes the right entry-point snippet, wires `identify()` against the client's auth provider, and runs a live smoke test in Chrome.
9
+ End-to-end flow for getting the **@mhosaic/feedback** widget into a host app. Run this from **inside the host app's own directory** (NOT `feedback-tool-mhosaic`). You'll need a handoff payload from your Mhosaic contact first — see the Q&A below if you don't have one yet.
14
10
 
15
11
  This skill is **both** a slash-command runbook AND a reference doc. When the user invokes `/integrate-feedback`, run the procedural flow. When they just ask an integration question, answer from the Q&A below.
16
12
 
@@ -19,47 +15,28 @@ This skill is **both** a slash-command runbook AND a reference doc. When the use
19
15
  ## Quick Q&A
20
16
 
21
17
  **"What does this skill do?"**
22
- End-to-end widget integration. Operator phase: creates Company/Project/key on the backend. Consumer phase: installs the widget in a host app + runs a smoke test that confirms a report lands in admin. Framework-aware (Vite/Next/Nuxt/Astro/Remix/Vue/SvelteKit/plain HTML). Auth-aware (Auth0/Clerk/Supabase/Firebase/NextAuth/Django/JWT/anonymous).
18
+ Installs the widget in a host app + runs a smoke test that confirms a report lands in admin. Framework-aware (Vite/Next/Nuxt/Astro/Remix/Vue/SvelteKit/plain HTML). Auth-aware (Auth0/Clerk/Supabase/Firebase/NextAuth/Django/JWT/anonymous).
23
19
 
24
20
  **"Do I need to install anything first?"**
25
-
26
- - Inside `feedback-tool-mhosaic`: no. The skill is committed under `.claude/skills/integrate-feedback/`; `/integrate-feedback` works directly.
27
- - In the client's repo: optional. If you want `/integrate-feedback` invokable there too (recommended for the consumer phase), the operator phase will offer to install it globally for you (one shell command, `npx @mhosaic/feedback-cli@latest install-skill`). Skip the install and the consumer phase can still happen — you just run `npx @mhosaic/feedback-cli@latest init …` directly in the client's repo and follow the framework-specific snippet from `references/`.
21
+ No — the skill is installed globally by `npx @mhosaic/feedback-cli@latest install-skill` (or your Mhosaic contact may have installed it for you). If it's not there yet, run that command once, then `/integrate-feedback` works in any project on this machine. You can also skip the skill entirely and run `npx @mhosaic/feedback-cli@latest init …` directly, following the framework-specific snippet from `references/`.
28
22
 
29
23
  **"Where do I run `/integrate-feedback` from?"**
24
+ Inside the host app's own project root — NOT `feedback-tool-mhosaic`. The skill writes to the current directory (`.env.local`, entry-point edits); running it inside `feedback-tool-mhosaic` would install the widget into that monorepo by mistake. The skill detects this (via `package.json` name check) and refuses.
30
25
 
31
- - Operator phase: **inside `feedback-tool-mhosaic`** (any directory in the monorepo).
32
- - Consumer phase: **inside the client's app's project root**.
33
- - Don't run consumer phase inside `feedback-tool-mhosaic` — the CLI would install the widget into our own monorepo. The skill detects this (via `package.json` name check) and refuses.
34
-
35
- **"What permissions do I need on the backend?"**
36
-
37
- - Login: Feedback Django group.
38
- - Create a Project/key under an existing company: Feedback group + Membership for that company.
39
- - Create a brand-new Company (external client): `is_staff=True` + `is_superuser=True`. Held by the platform maintainers.
26
+ **"I don't have a `pk_proj_…` key yet — where do I get one?"**
27
+ Ask your Mhosaic contact. They provision a Company/Project/key on the backend and send you a Markdown handoff payload (endpoint, key, allowed origins) — paste that as your first message when you run `/integrate-feedback`.
40
28
 
41
29
  **"What's Chrome MCP and why do I need it?"**
42
- A Claude Code MCP server that lets the skill drive a real Chrome tab. Operator phase uses it to call our admin API from the authenticated browser session (no token paste). Consumer phase uses it for the smoke test. If MCP isn't configured, operator phase stops at Step 0.5 with an install hint; consumer phase degrades to "open the dev URL yourself and confirm visually."
30
+ A Claude Code MCP server that lets the skill drive a real Chrome tab, used here for the smoke test. If MCP isn't configured, the flow degrades to "open the dev URL yourself and confirm visually."
43
31
 
44
- **"How does the handoff between phases work?"**
45
- End of operator phase produces a Markdown payload (endpoint, `pk_proj_…`, slug, allowed origins). The skill prints it AND offers to install itself globally. The operator then closes this Claude session, `cd`s into the client's repo, opens a fresh `claude`, runs `/integrate-feedback`, picks "Install (consumer)", and pastes the payload as the first message.
32
+ **"How does the handoff work?"**
33
+ Your Mhosaic contact provisions the project and gives you a Markdown handoff payload (endpoint, `pk_proj_…` key, slug, allowed origins). Paste it as your first message when you run `/integrate-feedback` — see the payload format below.
46
34
 
47
35
  **"My FAB isn't appearing — what's wrong?"**
48
36
  The FAB stays hidden until `fb.identify({id: 'something'})` runs with a non-empty `id`. Check `references/identify-snippets.md` for your auth provider's recipe. For testing, hardcode `fb.identify({id: 'test'})` to force-render.
49
37
 
50
38
  **"CORS errors / 403 / origin not allowed."**
51
- The project's `allowed_origins` doesn't include the URL the request comes from. Usual causes: forgot the dev URL (e.g. `http://localhost:5173`), trailing slash, path included, wildcard (rejected by design). Operator opens admin SPA → Edit project → adds the missing origin.
52
-
53
- **"External client vs internal Mhosaic app?"**
54
-
55
- - Internal: use existing companies (`mhosaic`, `arime`, `norag`); create a new project under one. Needs Feedback group + Membership.
56
- - External: create a brand-new Company. Needs `is_staff=True`. The client can't log into software-factory (it's @mhosaic.com-only SSO); Mhosaic acts as report-viewer for v1.
57
-
58
- **"Do clients get notifications / digests?"**
59
- Notifications go to a per-project Google Chat space (new reports, incidents, ✅ closures, Friday weekly digest) — wired by pasting the space's incoming-webhook URL into the project settings ("Notifications Google Chat"). **A project with no webhook falls through to the platform's global room: the client sees nothing.** Wiring it is part of the operator phase (Step 4.5 of `references/operator-provision.md`). Email digests additionally require SMTP env vars on the backend — ask the platform operator.
60
-
61
- **"Why are the project's Métriques / Journaux tabs empty?"**
62
- `observability enabled` alone forwards nothing. Metrics need `ObservabilityTarget` rows (POST `targets` to `/projects/<id>/observability/enable/` — operator Step 4.6a) and logs need the `log_destinations` block in the client app's DO spec (`scripts/observability/forward-app-logs.sh` — Step 4.6b, triggers a redeploy). If either was skipped at provisioning time, run Step 4.6 now; both are idempotent.
39
+ The project's `allowed_origins` doesn't include the URL the request comes from. Usual causes: forgot the dev URL (e.g. `http://localhost:5173`), trailing slash, path included, wildcard (rejected by design). Ask your Mhosaic contact to open the admin SPA → Edit project → add the missing origin.
63
40
 
64
41
  **"How do I also enable the QA Meter?"**
65
42
  The QA Meter is an opt-in second FAB (half size, stacked above the feedback button) that shows test-coverage status. It's host-served and host-gated — no backend involvement. Build the artifact with `mhosaic-feedback qa refresh`, serve `qa-status.json` as a static asset (or from your own endpoint), then enable it per surface:
@@ -70,76 +47,34 @@ The QA Meter is an opt-in second FAB (half size, stacked above the feedback butt
70
47
  Only enable it where you want it (e.g. staging); omit the option in production to ship feedback-only.
71
48
 
72
49
  **"Where's the human-readable doc?"**
73
- `docs/INTEGRATING.md` in this repo. Covers the skill, the manual fallback, the CLI command reference, and a troubleshooting matrix.
50
+ `docs/INTEGRATING.md` in the `feedback-tool-mhosaic` repo. Covers the skill, the manual fallback, the CLI command reference, and a troubleshooting matrix.
74
51
 
75
52
  ---
76
53
 
77
54
  **"The badge counts feedback from other records / pages — is that a bug?"**
78
- No — that route was flipped to « Regroupé » in the project's admin **Pages** tab. On grouped routes, every record of the same screen (`/dossier/123`, `/dossier/456` → `/dossier/:id`) counts as one page: badge, hover peek, board default and dedup all follow. Exact is the default; only an operator flip changes it. To check or revert: admin SPA → project → Paramètres → Pages.
55
+ No — that route was flipped to « Regroupé » in the project's admin **Pages** tab. On grouped routes, every record of the same screen (`/dossier/123`, `/dossier/456` → `/dossier/:id`) counts as one page: badge, hover peek, board default and dedup all follow. Exact is the default; only an operator flip changes it. Ask your Mhosaic contact to check or revert it: admin SPA → project → Paramètres → Pages.
79
56
 
80
57
  **"A client says one bug on their form creates a separate report per record and nothing groups."**
81
- That's the exact-by-default behavior on a form/workflow route. Fix is operator-side, zero client code: open the project's **Pages** tab, find the route pattern (the tab flags form-looking ones with « suggestion : formulaire ? »), flip it to « Regroupé ». Reads regroup instantly across history; fingerprints group for new reports (run `recompute_fingerprints --project <slug>` to backfill deliberately). If the route uses slug ids the heuristic can't collapse (`/dossier/acme-corp`), add a custom template (`/dossier/:slug`) via « Ajouter un motif ».
58
+ That's the exact-by-default behavior on a form/workflow route. Fix is operator-side, zero client code — flag it to your Mhosaic contact: they open the project's **Pages** tab, find the route pattern (the tab flags form-looking ones with « suggestion : formulaire ? »), flip it to « Regroupé ». Reads regroup instantly across history; fingerprints group for new reports. If the route uses slug ids the heuristic can't collapse (`/dossier/acme-corp`), a custom template (`/dossier/:slug`) fixes it.
82
59
 
83
60
  **"Does page grouping need anything in the host app?"**
84
61
  No. Patterns are discovered from stored feedback and resolved server-side; the widget just sends its pathname. The only optional host hook is `getCurrentPage()` in the widget config, for apps whose notion of "current page" isn't the URL path. Widgets ≥ 0.47.0 also adapt their copy (« sur ce formulaire ») and board default to the resolved mode.
85
62
 
86
- ## Step 0 — Identify the phase
87
-
88
- `AskUserQuestion`:
89
-
90
- - question: `Which phase are you running?`
91
- - header: `Phase`
92
- - options:
93
- - label: `Provision (operator)`, description: `Create a new Company/Project/key on the Mhosaic backend. Run this from inside feedback-tool-mhosaic.`
94
- - label: `Install (consumer)`, description: `Install the widget into a host app. Run this from inside the client's app directory, with the handoff payload from the operator phase.`
95
-
96
- Branch on the answer.
63
+ ## Step 0 — Pre-flight
97
64
 
98
- ---
99
-
100
- ## Step 0.5 — Pre-flight
101
-
102
- **For operator phase:** confirm Chrome MCP is configured. Call `mcp__claude-in-chrome__tabs_context_mcp` once. If the tool isn't available, tell the user:
103
-
104
- > "Operator phase needs Chrome MCP. Two options:
105
- >
106
- > 1. Install Chrome MCP (Claude Code → MCP settings → add the chrome connector), then re-run `/integrate-feedback`.
107
- > 2. Provision manually via `docs/INTEGRATING.md` (open the admin SPA, create the project, mint the key)."
108
-
109
- Stop. Don't continue without MCP.
110
-
111
- **For consumer phase:** read `package.json` in the cwd. If its `name` is `@mhosaic/feedback-tool-mhosaic` or any of our own monorepo packages, the user is inside our own repo — refuse and redirect:
65
+ Read `package.json` in the cwd. If its `name` is `@mhosaic/feedback-tool-mhosaic` or any of our own monorepo packages, the user is inside our own repo — refuse and redirect:
112
66
 
113
- > "Looks like we're inside `feedback-tool-mhosaic`. The consumer phase writes to the current directory; running it here would install the widget into our own monorepo. Close Claude and re-open in the client app's root, then run `/integrate-feedback` again."
67
+ > "Looks like we're inside `feedback-tool-mhosaic`. This skill writes to the current directory; running it here would install the widget into our own monorepo. Close Claude and re-open in the host app's root, then run `/integrate-feedback` again."
114
68
 
115
69
  Stop. Don't continue.
116
70
 
117
71
  ---
118
72
 
119
- ## Operator phase
120
-
121
- Read **`references/operator-provision.md`** and follow it step by step. The phase is **A→Z**: a project leaves this flow with its full 360 view (widget + Chat alerts + metrics + logs + members) or with each gap recorded as an explicit operator decision — never a silent skip.
122
-
123
- 1. Confirm operator is SSO-logged-in to software-factory in Chrome.
124
- 2. Collect: company (existing or new), project name + slug, allowed origins, `share_reports_with_widget`.
125
- 3. Drive Chrome MCP to the admin SPA, refresh the JWT via `/api/auth/token/refresh/`, call `POST /companies/`, `POST /projects/`, `POST /project-keys/create/` from the in-tab `fetch()` (uses the existing cookies). Company create auto-grants the creator an owner Membership.
126
- 4. Capture the plaintext `pk_proj_…` from the keys response.
127
- 5. **Chat notifications (Step 4.5):** create or reuse the client's "«Company» Alerts" Chat space, add an incoming webhook named "Mhosaic Feedback", paste its URL into the project's settings page ("Notifications Google Chat"), fire a `[TEST]` probe and confirm the card lands. Never echo the webhook URL into the chat — it's a credential.
128
- 6. **Observability (Step 4.6, governance-gated):** POST `targets` (`environment` × `component` × DO app id) to `/projects/<id>/observability/enable/` so the Métriques page shows the deployment(s), then run `scripts/observability/forward-app-logs.sh <app-id> <index>` per backend app (triggers a redeploy — verify ACTIVE). Without this step the Métriques/Journaux tabs stay empty forever.
129
- 7. **Members (Step 4.7):** add the responsible Mhosaic owner + any client users; mint an MCP key if the fix-flow is wanted. The company must not end the flow member-less.
130
- 8. Verify the project + key show up in the admin SPA.
131
- 9. **Completeness card (Step 5):** build the "Provisioning 360" checklist; every unchecked line must carry the operator's explicit skip reason. Print it with the handoff payload.
132
- 10. Offer to install the skill globally so the operator can run `/integrate-feedback` in the client's repo for the consumer phase. The full offer logic is at the end of `references/operator-provision.md`.
133
-
134
- After this phase the operator has: (a) the Provisioning 360 card + handoff payload in the chat, (b) optionally `/integrate-feedback` available globally so the next `claude` in the client repo picks it up.
135
-
136
- ---
137
-
138
- ## Consumer phase
73
+ ## Steps
139
74
 
140
75
  Read **`references/consumer-install.md`** and follow it step by step:
141
76
 
142
- 1. Confirm cwd is the client app's root (NOT feedback-tool-mhosaic — see Step 0.5).
77
+ 1. Confirm cwd is the host app's root (NOT feedback-tool-mhosaic — see Step 0).
143
78
  2. Parse the pasted handoff payload (endpoint, key, origins).
144
79
  3. Detect the framework from `package.json` → route to `references/consumer-install-<framework>.md` (Vite, Next, Nuxt, Astro, Remix, Vue, SvelteKit, or plain/CDN).
145
80
  4. Run `npx @mhosaic/feedback-cli@latest init --api-key=… --endpoint=… --yes`.
@@ -147,15 +82,15 @@ Read **`references/consumer-install.md`** and follow it step by step:
147
82
  6. Wire `fb.identify()` per `references/identify-snippets.md` — pick the recipe matching the client's auth provider.
148
83
  7. Run `mhosaic-feedback verify --origin <dev-url> --with-test-report` — all 6 checks must be green before proceeding.
149
84
  8. Start the dev server, drive Chrome to the dev URL, screenshot the FAB, submit a `[INTEGRATE-FEEDBACK SMOKE TEST]` report.
150
- 9. Drive Chrome to software-factory `/reports`, confirm the report landed.
85
+ 9. Confirm the report landed: if you have admin access, drive Chrome to software-factory `/reports`. If you don't (most consumers won't — admin is @mhosaic.com-only SSO), the `201` from step 7's `--with-test-report` is your pass signal — ask your Mhosaic contact to confirm the report landed.
151
86
 
152
87
  Smoke test details + diagnostic table: **`references/verify-install.md`**.
153
88
 
154
89
  ---
155
90
 
156
- ## Operator → consumer handoff payload format
91
+ ## Handoff payload format (what you'll paste in)
157
92
 
158
- Always print this Markdown block at the end of operator phase:
93
+ Your Mhosaic contact sends you a block like this — paste it as your first message:
159
94
 
160
95
  ````markdown
161
96
  ## Mhosaic Feedback handoff
@@ -174,7 +109,7 @@ npx @mhosaic/feedback-cli@latest init \
174
109
  --yes
175
110
  ```
176
111
 
177
- Or for the full guided flow: `/integrate-feedback` → "Install (consumer)" → paste this block as your first message.
112
+ Or for the full guided flow: `/integrate-feedback` → paste this block as your first message.
178
113
  ````
179
114
 
180
115
  ---
@@ -193,9 +128,8 @@ Or for the full guided flow: `/integrate-feedback` → "Install (consumer)" →
193
128
 
194
129
  ```
195
130
  .claude/skills/integrate-feedback/
196
- ├── SKILL.md # this file (phase router)
131
+ ├── SKILL.md # this file
197
132
  ├── references/
198
- │ ├── operator-provision.md # Chrome MCP → admin SPA → DRF
199
133
  │ ├── consumer-install.md # framework router
200
134
  │ ├── consumer-install-vite.md # Vite + React (CLI auto-wires)
201
135
  │ ├── consumer-install-next.md # Next.js App Router + Pages