sdd-jc-methodology 0.2.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 (148) hide show
  1. package/.claude/commands/sdd-archive.md +122 -0
  2. package/.claude/commands/sdd-constitution.md +240 -0
  3. package/.claude/commands/sdd-execute.md +132 -0
  4. package/.claude/commands/sdd-propose.md +149 -0
  5. package/.claude/commands/sdd-seo.md +251 -0
  6. package/.claude/commands/sdd-specify.md +264 -0
  7. package/.claude/commands/sdd-test.md +128 -0
  8. package/.claude/commands/sdd-validate.md +165 -0
  9. package/.claude/skills/api-design-principles/SKILL.md +528 -0
  10. package/.claude/skills/api-design-principles/assets/api-design-checklist.md +155 -0
  11. package/.claude/skills/api-design-principles/assets/rest-api-template.py +182 -0
  12. package/.claude/skills/api-design-principles/references/graphql-schema-design.md +583 -0
  13. package/.claude/skills/api-design-principles/references/rest-best-practices.md +408 -0
  14. package/.claude/skills/aws-serverless/SKILL.md +323 -0
  15. package/.claude/skills/brainstorming/SKILL.md +96 -0
  16. package/.claude/skills/error-handling-patterns/SKILL.md +641 -0
  17. package/.claude/skills/frontend-design/LICENSE.txt +177 -0
  18. package/.claude/skills/frontend-design/SKILL.md +272 -0
  19. package/.claude/skills/nestjs-expert/SKILL.md +552 -0
  20. package/.claude/skills/product-manager-toolkit/SKILL.md +351 -0
  21. package/.claude/skills/product-manager-toolkit/references/prd_templates.md +317 -0
  22. package/.claude/skills/product-manager-toolkit/scripts/customer_interview_analyzer.py +441 -0
  23. package/.claude/skills/product-manager-toolkit/scripts/rice_prioritizer.py +296 -0
  24. package/.claude/skills/react-doctor/AGENTS.md +15 -0
  25. package/.claude/skills/react-doctor/SKILL.md +19 -0
  26. package/.claude/skills/shadcn-ui/SKILL.md +1677 -0
  27. package/.claude/skills/shadcn-ui/references/learn.md +145 -0
  28. package/.claude/skills/shadcn-ui/references/official-ui-reference.md +1725 -0
  29. package/.claude/skills/shadcn-ui/references/reference.md +586 -0
  30. package/.claude/skills/shadcn-ui/references/ui-reference.md +1578 -0
  31. package/.claude/skills/stitch-design/README.md +50 -0
  32. package/.claude/skills/stitch-design/SKILL.md +84 -0
  33. package/.claude/skills/stitch-design/examples/DESIGN.md +22 -0
  34. package/.claude/skills/stitch-design/examples/enhanced-prompt.md +28 -0
  35. package/.claude/skills/stitch-design/references/design-mappings.md +45 -0
  36. package/.claude/skills/stitch-design/references/prompt-keywords.md +114 -0
  37. package/.claude/skills/stitch-design/references/tool-schemas.md +76 -0
  38. package/.claude/skills/stitch-design/workflows/edit-design.md +44 -0
  39. package/.claude/skills/stitch-design/workflows/generate-design-md.md +63 -0
  40. package/.claude/skills/stitch-design/workflows/text-to-design.md +47 -0
  41. package/.claude/skills/systematic-debugging/CREATION-LOG.md +119 -0
  42. package/.claude/skills/systematic-debugging/SKILL.md +296 -0
  43. package/.claude/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  44. package/.claude/skills/systematic-debugging/condition-based-waiting.md +115 -0
  45. package/.claude/skills/systematic-debugging/defense-in-depth.md +122 -0
  46. package/.claude/skills/systematic-debugging/find-polluter.sh +63 -0
  47. package/.claude/skills/systematic-debugging/root-cause-tracing.md +169 -0
  48. package/.claude/skills/systematic-debugging/test-academic.md +14 -0
  49. package/.claude/skills/systematic-debugging/test-pressure-1.md +58 -0
  50. package/.claude/skills/systematic-debugging/test-pressure-2.md +68 -0
  51. package/.claude/skills/systematic-debugging/test-pressure-3.md +69 -0
  52. package/.claude/skills/tailwind-design-system/SKILL.md +874 -0
  53. package/.claude/skills/ui-ux-pro-max/SKILL.md +377 -0
  54. package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  55. package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  56. package/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
  57. package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  58. package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  59. package/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  60. package/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  61. package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  62. package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  63. package/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  64. package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  65. package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  66. package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  67. package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  68. package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  69. package/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  70. package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  71. package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  72. package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  73. package/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
  74. package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  75. package/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  76. package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  77. package/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  78. package/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
  79. package/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  80. package/.claude/skills/ui-ux-pro-max/scripts/search.py +114 -0
  81. package/.claude/skills/vercel-react-best-practices/AGENTS.md +2934 -0
  82. package/.claude/skills/vercel-react-best-practices/SKILL.md +136 -0
  83. package/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  84. package/.claude/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
  85. package/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  86. package/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  87. package/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  88. package/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  89. package/.claude/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  90. package/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  91. package/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  92. package/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  93. package/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  94. package/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  95. package/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  96. package/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  97. package/.claude/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  98. package/.claude/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  99. package/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  100. package/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  101. package/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  102. package/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  103. package/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  104. package/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  105. package/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  106. package/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  107. package/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  108. package/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  109. package/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  110. package/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  111. package/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  112. package/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  113. package/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  114. package/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  115. package/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  116. package/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  117. package/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  118. package/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  119. package/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  120. package/.claude/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  121. package/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  122. package/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  123. package/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  124. package/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  125. package/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  126. package/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  127. package/.claude/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  128. package/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  129. package/.claude/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  130. package/.claude/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  131. package/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  132. package/.claude/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  133. package/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  134. package/.claude/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  135. package/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  136. package/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  137. package/.claude/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  138. package/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  139. package/.claude/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  140. package/.mcp.json.example +12 -0
  141. package/CHANGELOG.md +61 -0
  142. package/LICENSE +21 -0
  143. package/README.md +571 -0
  144. package/assets/jc-fox-mark.svg +10 -0
  145. package/assets/jc-methodology-badge.png +0 -0
  146. package/bin/sdd-jc.js +379 -0
  147. package/package.json +43 -0
  148. package/scripts/gsc_verify.py +162 -0
package/bin/sdd-jc.js ADDED
@@ -0,0 +1,379 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+
7
+ const PACKAGE_ROOT = path.resolve(__dirname, "..");
8
+ const SOURCE_CLAUDE = path.join(PACKAGE_ROOT, ".claude");
9
+ const SOURCE_COMMANDS = path.join(SOURCE_CLAUDE, "commands");
10
+ const SOURCE_SKILLS = path.join(SOURCE_CLAUDE, "skills");
11
+ const SOURCE_SCRIPTS = path.join(PACKAGE_ROOT, "scripts");
12
+ const SOURCE_MCP_EXAMPLE = path.join(PACKAGE_ROOT, ".mcp.json.example");
13
+
14
+ const TOOL_DEFAULTS = {
15
+ claude: path.join(os.homedir(), ".claude"),
16
+ opencode: path.join(os.homedir(), ".config", "opencode"),
17
+ };
18
+
19
+ function printHelp() {
20
+ console.log(`SDD JC Methodology CLI
21
+
22
+ Usage:
23
+ sdd-jc <command> [options]
24
+
25
+ Commands:
26
+ install Install commands, skills, and helper resources
27
+ update Reinstall commands, skills, and helper resources
28
+ doctor Check whether expected files are installed
29
+ list List packaged commands, skills, and helper resources
30
+ help Show this help
31
+
32
+ Options:
33
+ --tool <name> Install target: claude, opencode, or both. Default: claude
34
+ --target <path> Target config directory for selected single tool
35
+ --claude-target Claude config directory. Default: ~/.claude
36
+ --opencode-target OpenCode config directory. Default: ~/.config/opencode
37
+ --force Overwrite existing files
38
+ --dry-run Show what would happen without writing files
39
+ --commands-only Install or check only commands
40
+ --skills-only Install or check only skills
41
+
42
+ Examples:
43
+ sdd-jc install
44
+ sdd-jc install --tool opencode
45
+ sdd-jc install --tool both --dry-run
46
+ sdd-jc install --tool claude --target ./.claude
47
+ sdd-jc update --tool both --force
48
+ sdd-jc doctor --tool opencode
49
+ sdd-jc list
50
+ `);
51
+ }
52
+
53
+ function parseArgs(argv) {
54
+ const args = {
55
+ command: argv[2] || "help",
56
+ tool: "claude",
57
+ target: undefined,
58
+ claudeTarget: TOOL_DEFAULTS.claude,
59
+ opencodeTarget: TOOL_DEFAULTS.opencode,
60
+ force: false,
61
+ dryRun: false,
62
+ commandsOnly: false,
63
+ skillsOnly: false,
64
+ };
65
+
66
+ for (let i = 3; i < argv.length; i += 1) {
67
+ const arg = argv[i];
68
+
69
+ if (arg === "--tool") {
70
+ const value = argv[i + 1];
71
+ if (!value) fail("Missing value for --tool");
72
+ if (!["claude", "opencode", "both"].includes(value)) {
73
+ fail("--tool must be one of: claude, opencode, both");
74
+ }
75
+ args.tool = value;
76
+ i += 1;
77
+ } else if (arg === "--target") {
78
+ const value = argv[i + 1];
79
+ if (!value) fail("Missing value for --target");
80
+ args.target = resolveUserPath(value);
81
+ i += 1;
82
+ } else if (arg === "--claude-target") {
83
+ const value = argv[i + 1];
84
+ if (!value) fail("Missing value for --claude-target");
85
+ args.claudeTarget = resolveUserPath(value);
86
+ i += 1;
87
+ } else if (arg === "--opencode-target") {
88
+ const value = argv[i + 1];
89
+ if (!value) fail("Missing value for --opencode-target");
90
+ args.opencodeTarget = resolveUserPath(value);
91
+ i += 1;
92
+ } else if (arg === "--force") {
93
+ args.force = true;
94
+ } else if (arg === "--dry-run") {
95
+ args.dryRun = true;
96
+ } else if (arg === "--commands-only") {
97
+ args.commandsOnly = true;
98
+ } else if (arg === "--skills-only") {
99
+ args.skillsOnly = true;
100
+ } else if (arg === "--help" || arg === "-h") {
101
+ args.command = "help";
102
+ } else {
103
+ fail(`Unknown option: ${arg}`);
104
+ }
105
+ }
106
+
107
+ if (args.commandsOnly && args.skillsOnly) {
108
+ fail("Use only one of --commands-only or --skills-only");
109
+ }
110
+
111
+ if (args.target && args.tool === "both") {
112
+ fail("--target can only be used with --tool claude or --tool opencode. Use --claude-target and --opencode-target with --tool both.");
113
+ }
114
+
115
+ if (args.target && args.tool === "claude") args.claudeTarget = args.target;
116
+ if (args.target && args.tool === "opencode") args.opencodeTarget = args.target;
117
+
118
+ return args;
119
+ }
120
+
121
+ function resolveUserPath(input) {
122
+ return path.resolve(input.replace(/^~(?=$|\/|\\)/, os.homedir()));
123
+ }
124
+
125
+ function fail(message) {
126
+ console.error(`ERROR: ${message}`);
127
+ process.exit(1);
128
+ }
129
+
130
+ function ensureDirectory(dir, dryRun) {
131
+ if (fs.existsSync(dir)) return;
132
+ if (dryRun) {
133
+ console.log(`create directory ${dir}`);
134
+ return;
135
+ }
136
+ fs.mkdirSync(dir, { recursive: true });
137
+ }
138
+
139
+ function listEntries(dir, filter) {
140
+ if (!fs.existsSync(dir)) return [];
141
+ return fs
142
+ .readdirSync(dir, { withFileTypes: true })
143
+ .filter(filter)
144
+ .map((entry) => entry.name)
145
+ .sort((a, b) => a.localeCompare(b));
146
+ }
147
+
148
+ function listCommands() {
149
+ return listEntries(SOURCE_COMMANDS, (entry) => entry.isFile() && entry.name.endsWith(".md"));
150
+ }
151
+
152
+ function listSkills() {
153
+ return listEntries(SOURCE_SKILLS, (entry) => entry.isDirectory());
154
+ }
155
+
156
+ function selectedTools(args) {
157
+ if (args.tool === "both") return ["claude", "opencode"];
158
+ return [args.tool];
159
+ }
160
+
161
+ function shouldInclude(type, args) {
162
+ if (args.commandsOnly) return type === "commands";
163
+ if (args.skillsOnly) return type === "skills";
164
+ return true;
165
+ }
166
+
167
+ function copySingleFile(sourcePath, targetPath, args) {
168
+ ensureDirectory(path.dirname(targetPath), args.dryRun);
169
+
170
+ if (fs.existsSync(targetPath) && !args.force) {
171
+ console.log(`skip existing ${targetPath}`);
172
+ return { installed: 0, skipped: 1 };
173
+ }
174
+
175
+ const action = fs.existsSync(targetPath) ? "overwrite" : "install";
176
+ console.log(`${args.dryRun ? "would " : ""}${action} ${targetPath}`);
177
+
178
+ if (!args.dryRun) {
179
+ fs.copyFileSync(sourcePath, targetPath);
180
+ }
181
+
182
+ return { installed: 1, skipped: 0 };
183
+ }
184
+
185
+ function copyDirectoryContents(sourceDir, targetDir, args) {
186
+ ensureDirectory(targetDir, args.dryRun);
187
+
188
+ const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
189
+ let installed = 0;
190
+ let skipped = 0;
191
+
192
+ for (const entry of entries) {
193
+ const sourcePath = path.join(sourceDir, entry.name);
194
+ const targetPath = path.join(targetDir, entry.name);
195
+
196
+ if (fs.existsSync(targetPath) && !args.force) {
197
+ console.log(`skip existing ${targetPath}`);
198
+ skipped += 1;
199
+ continue;
200
+ }
201
+
202
+ const action = fs.existsSync(targetPath) ? "overwrite" : "install";
203
+ console.log(`${args.dryRun ? "would " : ""}${action} ${targetPath}`);
204
+
205
+ if (!args.dryRun) {
206
+ fs.cpSync(sourcePath, targetPath, {
207
+ recursive: true,
208
+ force: true,
209
+ errorOnExist: false,
210
+ });
211
+ }
212
+ installed += 1;
213
+ }
214
+
215
+ return { installed, skipped };
216
+ }
217
+
218
+ function installTool(tool, args) {
219
+ const targetRoot = tool === "claude" ? args.claudeTarget : args.opencodeTarget;
220
+ const targetCommands = path.join(targetRoot, "commands");
221
+ const targetSkills = path.join(targetRoot, "skills");
222
+ const targetResources = path.join(targetRoot, "sdd-jc");
223
+ let installed = 0;
224
+ let skipped = 0;
225
+
226
+ console.log(`\n${tool} target: ${targetRoot}`);
227
+
228
+ if (shouldInclude("commands", args)) {
229
+ const result = copyDirectoryContents(SOURCE_COMMANDS, targetCommands, args);
230
+ installed += result.installed;
231
+ skipped += result.skipped;
232
+ }
233
+
234
+ if (shouldInclude("skills", args)) {
235
+ const result = copyDirectoryContents(SOURCE_SKILLS, targetSkills, args);
236
+ installed += result.installed;
237
+ skipped += result.skipped;
238
+ }
239
+
240
+ if (shouldInclude("resources", args)) {
241
+ const scriptsResult = copyDirectoryContents(
242
+ SOURCE_SCRIPTS,
243
+ path.join(targetResources, "scripts"),
244
+ args
245
+ );
246
+ installed += scriptsResult.installed;
247
+ skipped += scriptsResult.skipped;
248
+
249
+ const mcpResult = copySingleFile(
250
+ SOURCE_MCP_EXAMPLE,
251
+ path.join(targetResources, ".mcp.json.example"),
252
+ args
253
+ );
254
+ installed += mcpResult.installed;
255
+ skipped += mcpResult.skipped;
256
+ }
257
+
258
+ return { installed, skipped };
259
+ }
260
+
261
+ function runInstall(args) {
262
+ let installed = 0;
263
+ let skipped = 0;
264
+
265
+ for (const tool of selectedTools(args)) {
266
+ const result = installTool(tool, args);
267
+ installed += result.installed;
268
+ skipped += result.skipped;
269
+ }
270
+
271
+ console.log(`\nDone. Installed: ${installed} | Skipped: ${skipped}`);
272
+ if (skipped > 0 && !args.force) {
273
+ console.log("Use --force to overwrite existing files.");
274
+ }
275
+
276
+ if (selectedTools(args).includes("opencode") && !args.dryRun) {
277
+ console.log("Restart OpenCode for installed commands and skills to be loaded.");
278
+ }
279
+ }
280
+
281
+ function runList() {
282
+ const commands = listCommands();
283
+ const skills = listSkills();
284
+ const scripts = listEntries(SOURCE_SCRIPTS, (entry) => entry.isFile());
285
+
286
+ console.log("Commands:");
287
+ commands.forEach((name) => console.log(` ${name.replace(/\.md$/, "")}`));
288
+
289
+ console.log("\nSkills:");
290
+ skills.forEach((name) => console.log(` ${name}`));
291
+
292
+ console.log("\nResources:");
293
+ scripts.forEach((name) => console.log(` scripts/${name}`));
294
+ console.log(" .mcp.json.example");
295
+ }
296
+
297
+ function hasInstalledCommand(targetRoot, name) {
298
+ return fs.existsSync(path.join(targetRoot, "commands", name));
299
+ }
300
+
301
+ function hasInstalledSkill(targetRoot, name) {
302
+ return fs.existsSync(path.join(targetRoot, "skills", name, "SKILL.md"));
303
+ }
304
+
305
+ function doctorTool(tool, args) {
306
+ const targetRoot = tool === "claude" ? args.claudeTarget : args.opencodeTarget;
307
+ const targetResources = path.join(targetRoot, "sdd-jc");
308
+ let missing = 0;
309
+
310
+ console.log(`\nChecking ${tool}: ${targetRoot}`);
311
+
312
+ if (shouldInclude("commands", args)) {
313
+ console.log("\nCommands:");
314
+ for (const command of listCommands()) {
315
+ const ok = hasInstalledCommand(targetRoot, command);
316
+ console.log(` ${ok ? "OK" : "MISSING"} ${command}`);
317
+ if (!ok) missing += 1;
318
+ }
319
+ }
320
+
321
+ if (shouldInclude("skills", args)) {
322
+ console.log("\nSkills:");
323
+ for (const skill of listSkills()) {
324
+ const ok = hasInstalledSkill(targetRoot, skill);
325
+ console.log(` ${ok ? "OK" : "MISSING"} ${skill}`);
326
+ if (!ok) missing += 1;
327
+ }
328
+ }
329
+
330
+ if (shouldInclude("resources", args)) {
331
+ console.log("\nResources:");
332
+ const resourceChecks = [
333
+ path.join(targetResources, "scripts", "gsc_verify.py"),
334
+ path.join(targetResources, ".mcp.json.example"),
335
+ ];
336
+
337
+ for (const resourcePath of resourceChecks) {
338
+ const ok = fs.existsSync(resourcePath);
339
+ console.log(` ${ok ? "OK" : "MISSING"} ${resourcePath}`);
340
+ if (!ok) missing += 1;
341
+ }
342
+ }
343
+
344
+ return missing;
345
+ }
346
+
347
+ function runDoctor(args) {
348
+ let missing = 0;
349
+ for (const tool of selectedTools(args)) {
350
+ missing += doctorTool(tool, args);
351
+ }
352
+
353
+ console.log(`\nDone. Missing: ${missing}`);
354
+ if (missing > 0) process.exitCode = 1;
355
+ }
356
+
357
+ function main() {
358
+ const args = parseArgs(process.argv);
359
+
360
+ switch (args.command) {
361
+ case "install":
362
+ case "update":
363
+ runInstall(args);
364
+ break;
365
+ case "doctor":
366
+ runDoctor(args);
367
+ break;
368
+ case "list":
369
+ runList();
370
+ break;
371
+ case "help":
372
+ printHelp();
373
+ break;
374
+ default:
375
+ fail(`Unknown command: ${args.command}`);
376
+ }
377
+ }
378
+
379
+ main();
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "sdd-jc-methodology",
3
+ "version": "0.2.0",
4
+ "description": "Portable SDD JC methodology commands and skills for AI-assisted development.",
5
+ "homepage": "https://github.com/JuankCadavid/sdd-jc-methodology#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/JuankCadavid/sdd-jc-methodology.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/JuankCadavid/sdd-jc-methodology/issues"
12
+ },
13
+ "bin": {
14
+ "sdd-jc": "./bin/sdd-jc.js"
15
+ },
16
+ "files": [
17
+ ".claude/commands",
18
+ ".claude/skills",
19
+ ".mcp.json.example",
20
+ "assets",
21
+ "bin",
22
+ "CHANGELOG.md",
23
+ "LICENSE",
24
+ "README.md",
25
+ "scripts"
26
+ ],
27
+ "scripts": {
28
+ "pack:dry-run": "npm pack --dry-run",
29
+ "verify:cli": "node bin/sdd-jc.js list"
30
+ },
31
+ "keywords": [
32
+ "sdd",
33
+ "spec-driven-development",
34
+ "claude",
35
+ "opencode",
36
+ "ai-coding",
37
+ "methodology"
38
+ ],
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "license": "MIT"
43
+ }
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Verify a Google service account as an owner on Google Search Console
4
+ Domain properties via DNS TXT records.
5
+
6
+ The `gsc` MCP (@mikusnuz/gsc-mcp) does NOT expose Site Verification
7
+ endpoints — only Search Console. This helper covers the verification
8
+ half: token generation + verify call.
9
+
10
+ After verification succeeds, use the `gsc` MCP `sites_add` tool to add
11
+ the property to Search Console for the service account.
12
+
13
+ Setup:
14
+ pip install google-auth google-api-python-client
15
+
16
+ Usage:
17
+ GSC_KEY_PATH=/abs/path/to/service-account.json \
18
+ python3 gsc_verify.py get <domain> [<domain> ...]
19
+
20
+ GSC_KEY_PATH=/abs/path/to/service-account.json \
21
+ python3 gsc_verify.py verify <domain> [<domain> ...]
22
+
23
+ Or pass the key path explicitly:
24
+ python3 gsc_verify.py --key /abs/path/to/key.json get example.com
25
+
26
+ Workflow:
27
+ 1. Run with `get` to obtain a DNS TXT verification token per domain.
28
+ 2. Add the TXT record to each domain's DNS (host: @, value: the token).
29
+ 3. Wait for DNS propagation (a few minutes to an hour).
30
+ 4. Run with `verify` to trigger the verification on Google's side.
31
+ """
32
+
33
+ import argparse
34
+ import os
35
+ import subprocess
36
+ import sys
37
+ from pathlib import Path
38
+
39
+ try:
40
+ from google.oauth2 import service_account
41
+ from googleapiclient.discovery import build
42
+ from googleapiclient.errors import HttpError
43
+ except ImportError:
44
+ print("ERROR: missing dependencies. Install with:", file=sys.stderr)
45
+ print(" pip install google-auth google-api-python-client", file=sys.stderr)
46
+ sys.exit(1)
47
+
48
+
49
+ SCOPES = ["https://www.googleapis.com/auth/siteverification"]
50
+
51
+
52
+ def make_client(key_path: str):
53
+ if not Path(key_path).exists():
54
+ print(f"ERROR: key file not found at: {key_path}", file=sys.stderr)
55
+ sys.exit(1)
56
+ creds = service_account.Credentials.from_service_account_file(
57
+ key_path, scopes=SCOPES
58
+ )
59
+ return build("siteVerification", "v1", credentials=creds, cache_discovery=False)
60
+
61
+
62
+ def get_token(sv, domain: str) -> str:
63
+ body = {
64
+ "site": {"type": "INET_DOMAIN", "identifier": domain},
65
+ "verificationMethod": "DNS_TXT",
66
+ }
67
+ return sv.webResource().getToken(body=body).execute()["token"]
68
+
69
+
70
+ def check_dns(domain: str, token: str):
71
+ """Best-effort check that the TXT record is visible via dig."""
72
+ try:
73
+ out = subprocess.check_output(
74
+ ["dig", "+short", "TXT", domain], stderr=subprocess.DEVNULL, text=True
75
+ )
76
+ return token in out
77
+ except Exception:
78
+ return None
79
+
80
+
81
+ def verify_domain(sv, domain: str):
82
+ body = {"site": {"type": "INET_DOMAIN", "identifier": domain}}
83
+ return sv.webResource().insert(verificationMethod="DNS_TXT", body=body).execute()
84
+
85
+
86
+ def cmd_get(sv, domains):
87
+ print("\n=== DNS TXT records to add ===\n")
88
+ print("For each domain, log into your DNS provider and add a TXT record")
89
+ print("at the root (host = '@' or blank) with the value shown.\n")
90
+ for d in domains:
91
+ try:
92
+ token = get_token(sv, d)
93
+ print(f"--- {d} ---")
94
+ print(f" Type: TXT")
95
+ print(f" Host: @ (root of {d})")
96
+ print(f" Value: {token}\n")
97
+ except HttpError as e:
98
+ print(f"--- {d} ---")
99
+ print(f" ERROR getting token: {e}\n")
100
+
101
+
102
+ def cmd_verify(sv, domains):
103
+ print("\n=== Verifying domains ===\n")
104
+ successes, failures = [], []
105
+ for d in domains:
106
+ try:
107
+ token = get_token(sv, d)
108
+ except HttpError as e:
109
+ print(f"[{d}] ERROR fetching token: {e}")
110
+ failures.append(d)
111
+ continue
112
+
113
+ dns_ok = check_dns(d, token)
114
+ if dns_ok is False:
115
+ print(f"[{d}] WARNING: TXT record not visible yet via dig. Trying anyway...")
116
+ elif dns_ok is True:
117
+ print(f"[{d}] TXT record visible in DNS")
118
+
119
+ try:
120
+ verify_domain(sv, d)
121
+ print(f"[{d}] VERIFIED — service account is now an owner.")
122
+ successes.append(d)
123
+ except HttpError as e:
124
+ print(f"[{d}] FAILED to verify: {e}")
125
+ failures.append(d)
126
+ print()
127
+
128
+ print(f"\nDone. Success: {len(successes)} | Failed: {len(failures)}")
129
+ if failures:
130
+ print("Failed domains:", ", ".join(failures))
131
+ print("Tip: DNS can take a few minutes to propagate. Try again later.")
132
+ return 0 if not failures else 1
133
+
134
+
135
+ def main():
136
+ parser = argparse.ArgumentParser(
137
+ description="Verify a service account as owner on GSC domain properties."
138
+ )
139
+ parser.add_argument(
140
+ "--key",
141
+ default=os.environ.get("GSC_KEY_PATH"),
142
+ help="Path to the Google service account JSON key. "
143
+ "Defaults to $GSC_KEY_PATH.",
144
+ )
145
+ parser.add_argument("action", choices=["get", "verify"])
146
+ parser.add_argument("domains", nargs="+", help="One or more apex domains.")
147
+ args = parser.parse_args()
148
+
149
+ if not args.key:
150
+ parser.error(
151
+ "missing service account key: pass --key /abs/path.json or set $GSC_KEY_PATH"
152
+ )
153
+
154
+ sv = make_client(args.key)
155
+ if args.action == "get":
156
+ cmd_get(sv, args.domains)
157
+ return 0
158
+ return cmd_verify(sv, args.domains)
159
+
160
+
161
+ if __name__ == "__main__":
162
+ sys.exit(main())