sdd-cli 0.1.22 → 0.1.24

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.
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.geminiProvider = void 0;
4
+ exports.geminiVersion = geminiVersion;
5
+ exports.geminiExec = geminiExec;
6
+ const child_process_1 = require("child_process");
7
+ function resolveCommand(input) {
8
+ if (process.platform !== "win32") {
9
+ return input;
10
+ }
11
+ const looksLikePath = input.includes("\\") || input.includes("/");
12
+ const hasExt = /\.[A-Za-z0-9]+$/.test(input);
13
+ if (!looksLikePath && !hasExt) {
14
+ return `${input}.cmd`;
15
+ }
16
+ return input;
17
+ }
18
+ function geminiVersion() {
19
+ const command = resolveCommand(process.env.SDD_GEMINI_BIN?.trim() || "gemini");
20
+ const useShell = process.platform === "win32" && command.toLowerCase().endsWith(".cmd");
21
+ const result = (0, child_process_1.spawnSync)(command, ["--version"], {
22
+ encoding: "utf-8",
23
+ shell: useShell
24
+ });
25
+ if (result.status !== 0) {
26
+ return { ok: false, output: "", error: result.stderr || "gemini not available" };
27
+ }
28
+ return { ok: true, output: result.stdout.trim() };
29
+ }
30
+ function geminiExec(prompt) {
31
+ const command = resolveCommand(process.env.SDD_GEMINI_BIN?.trim() || "gemini");
32
+ const model = process.env.SDD_GEMINI_MODEL?.trim();
33
+ const useShell = process.platform === "win32" && command.toLowerCase().endsWith(".cmd");
34
+ const normalizedPrompt = prompt.replace(/\r?\n/g, "\\n");
35
+ const env = {
36
+ ...process.env,
37
+ NO_COLOR: "1"
38
+ };
39
+ const modelArgs = model ? ["-m", model] : [];
40
+ const runPrimary = (withModel) => useShell
41
+ ? (0, child_process_1.spawnSync)(`${command} ${withModel && model ? `-m "${model.replace(/"/g, "\"\"")}" ` : ""}--prompt "${normalizedPrompt.replace(/"/g, "\"\"")}" --output-format json`, {
42
+ encoding: "utf-8",
43
+ shell: true,
44
+ env
45
+ })
46
+ : (0, child_process_1.spawnSync)(command, [...(withModel ? modelArgs : []), "--prompt", normalizedPrompt, "--output-format", "json"], {
47
+ encoding: "utf-8",
48
+ shell: false,
49
+ env
50
+ });
51
+ const runFallback = (withModel) => useShell
52
+ ? (0, child_process_1.spawnSync)(`${command} ${withModel && model ? `-m "${model.replace(/"/g, "\"\"")}" ` : ""}--prompt "${normalizedPrompt.replace(/"/g, "\"\"")}"`, {
53
+ encoding: "utf-8",
54
+ shell: true,
55
+ env
56
+ })
57
+ : (0, child_process_1.spawnSync)(command, [...(withModel ? modelArgs : []), "--prompt", normalizedPrompt], {
58
+ encoding: "utf-8",
59
+ shell: false,
60
+ env
61
+ });
62
+ let result = runPrimary(true);
63
+ if (result.status !== 0 && model) {
64
+ result = runPrimary(false);
65
+ }
66
+ if (result.status !== 0) {
67
+ result = runFallback(true);
68
+ }
69
+ if (result.status !== 0 && model) {
70
+ result = runFallback(false);
71
+ }
72
+ if (result.status !== 0) {
73
+ return { ok: false, output: result.stdout || "", error: result.stderr || "gemini exec failed" };
74
+ }
75
+ return { ok: true, output: result.stdout.trim() };
76
+ }
77
+ exports.geminiProvider = {
78
+ id: "gemini",
79
+ label: "Gemini",
80
+ version: geminiVersion,
81
+ exec: geminiExec
82
+ };
@@ -0,0 +1,16 @@
1
+ import { AIProvider, ProviderId, ProviderPreference } from "./types";
2
+ export declare function defaultProviderPreference(): ProviderPreference;
3
+ export declare function parseProviderPreference(input?: string): ProviderPreference | null;
4
+ export declare function listProviders(): AIProvider[];
5
+ export type ProviderResolution = {
6
+ ok: true;
7
+ provider: AIProvider;
8
+ selected: ProviderId;
9
+ requested: ProviderPreference;
10
+ } | {
11
+ ok: false;
12
+ requested: string;
13
+ reason: "invalid" | "unavailable";
14
+ details: string;
15
+ };
16
+ export declare function resolveProvider(requested?: string): ProviderResolution;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultProviderPreference = defaultProviderPreference;
4
+ exports.parseProviderPreference = parseProviderPreference;
5
+ exports.listProviders = listProviders;
6
+ exports.resolveProvider = resolveProvider;
7
+ const codex_1 = require("./codex");
8
+ const gemini_1 = require("./gemini");
9
+ const config_1 = require("../config");
10
+ const PROVIDERS = {
11
+ gemini: gemini_1.geminiProvider,
12
+ codex: codex_1.codexProvider
13
+ };
14
+ const AUTO_ORDER = ["gemini", "codex"];
15
+ function normalizePreference(input) {
16
+ const raw = (input ?? "").trim().toLowerCase();
17
+ if (!raw) {
18
+ return null;
19
+ }
20
+ if (raw === "auto" || raw === "gemini" || raw === "codex") {
21
+ return raw;
22
+ }
23
+ return null;
24
+ }
25
+ function defaultProviderPreference() {
26
+ const fromEnv = normalizePreference(process.env.SDD_AI_PROVIDER_DEFAULT);
27
+ if (fromEnv) {
28
+ return fromEnv;
29
+ }
30
+ const config = (0, config_1.ensureConfig)();
31
+ const fromConfig = normalizePreference(config.ai.preferred_cli);
32
+ return fromConfig ?? "gemini";
33
+ }
34
+ function parseProviderPreference(input) {
35
+ return normalizePreference(input);
36
+ }
37
+ function listProviders() {
38
+ return AUTO_ORDER.map((id) => PROVIDERS[id]);
39
+ }
40
+ function resolveProvider(requested) {
41
+ const normalized = normalizePreference(requested);
42
+ if (!normalized) {
43
+ return {
44
+ ok: false,
45
+ requested: requested ?? "",
46
+ reason: "invalid",
47
+ details: "Use one of: gemini, codex, auto."
48
+ };
49
+ }
50
+ if (normalized === "auto") {
51
+ const preferred = defaultProviderPreference();
52
+ if (preferred !== "auto") {
53
+ const provider = PROVIDERS[preferred];
54
+ const status = provider.version();
55
+ if (status.ok) {
56
+ return { ok: true, provider, selected: preferred, requested: normalized };
57
+ }
58
+ return {
59
+ ok: false,
60
+ requested: normalized,
61
+ reason: "unavailable",
62
+ details: `${provider.label} not available: ${status.error || "provider unavailable"}`
63
+ };
64
+ }
65
+ for (const id of AUTO_ORDER) {
66
+ const provider = PROVIDERS[id];
67
+ const status = provider.version();
68
+ if (status.ok) {
69
+ return { ok: true, provider, selected: id, requested: normalized };
70
+ }
71
+ }
72
+ return {
73
+ ok: false,
74
+ requested: normalized,
75
+ reason: "unavailable",
76
+ details: "No provider available. Install/configure gemini or codex."
77
+ };
78
+ }
79
+ const provider = PROVIDERS[normalized];
80
+ const status = provider.version();
81
+ if (!status.ok) {
82
+ return {
83
+ ok: false,
84
+ requested: normalized,
85
+ reason: "unavailable",
86
+ details: `${provider.label} not available: ${status.error || "provider unavailable"}`
87
+ };
88
+ }
89
+ return { ok: true, provider, selected: normalized, requested: normalized };
90
+ }
@@ -0,0 +1,13 @@
1
+ export type ProviderId = "gemini" | "codex";
2
+ export type ProviderPreference = ProviderId | "auto";
3
+ export type ProviderResult = {
4
+ ok: boolean;
5
+ output: string;
6
+ error?: string;
7
+ };
8
+ export type AIProvider = {
9
+ id: ProviderId;
10
+ label: string;
11
+ version: () => ProviderResult;
12
+ exec: (prompt: string) => ProviderResult;
13
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -3,7 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FLOW_PROMPT_PACKS = void 0;
4
4
  exports.classifyIntent = classifyIntent;
5
5
  const SIGNALS = [
6
- { intent: "bug_fix", flow: "BUG_FIX", domain: "bug_fix", keywords: ["bug", "issue", "error", "crash", "stack"] },
6
+ {
7
+ intent: "bug_fix",
8
+ flow: "BUG_FIX",
9
+ domain: "bug_fix",
10
+ keywords: ["bug", "issue", "error", "crash", "stack trace", "stacktrace"]
11
+ },
7
12
  { intent: "pr_review", flow: "PR_REVIEW", domain: "pr_review", keywords: ["pr", "pull request", "review"] },
8
13
  {
9
14
  intent: "learning",
@@ -31,7 +36,25 @@ const SIGNALS = [
31
36
  intent: "software",
32
37
  flow: "SOFTWARE_FEATURE",
33
38
  domain: "software",
34
- keywords: ["feature", "api", "backend", "frontend", "implement", "developer", "refactor", "code"]
39
+ keywords: [
40
+ "feature",
41
+ "api",
42
+ "backend",
43
+ "frontend",
44
+ "implement",
45
+ "developer",
46
+ "refactor",
47
+ "code",
48
+ "crear",
49
+ "crea",
50
+ "aplicacion",
51
+ "aplicación",
52
+ "app",
53
+ "web",
54
+ "desktop",
55
+ "movil",
56
+ "móvil"
57
+ ]
35
58
  }
36
59
  ];
37
60
  exports.FLOW_PROMPT_PACKS = {
@@ -48,14 +71,21 @@ exports.FLOW_PROMPT_PACKS = {
48
71
  };
49
72
  function classifyIntent(input) {
50
73
  const normalized = input.toLowerCase();
74
+ const containsKeyword = (keyword) => {
75
+ const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
76
+ if (escaped.length <= 3 && !escaped.includes(" ")) {
77
+ return new RegExp(`\\b${escaped}\\b`, "i").test(normalized);
78
+ }
79
+ return normalized.includes(keyword);
80
+ };
51
81
  for (const rule of SIGNALS) {
52
- if (rule.keywords.some((keyword) => normalized.includes(keyword))) {
82
+ if (rule.keywords.some((keyword) => containsKeyword(keyword))) {
53
83
  return {
54
84
  intent: rule.intent,
55
85
  confidence: 0.7,
56
86
  flow: rule.flow,
57
87
  domain: rule.domain,
58
- signals: rule.keywords.filter((keyword) => normalized.includes(keyword))
88
+ signals: rule.keywords.filter((keyword) => containsKeyword(keyword))
59
89
  };
60
90
  }
61
91
  }
@@ -17,8 +17,8 @@ exports.createProject = createProject;
17
17
  exports.updateProjectStatus = updateProjectStatus;
18
18
  const fs_1 = __importDefault(require("fs"));
19
19
  const path_1 = __importDefault(require("path"));
20
- const os_1 = __importDefault(require("os"));
21
20
  const flags_1 = require("../context/flags");
21
+ const config_1 = require("../config");
22
22
  const WORKSPACE_LOCK_RETRY_MS = 50;
23
23
  const WORKSPACE_LOCK_WAIT_MS = 5000;
24
24
  const WORKSPACE_LOCK_STALE_MS = 30000;
@@ -119,11 +119,11 @@ function getWorkspaceInfoForScope(scope) {
119
119
  }
120
120
  function getWorkspaceBaseRoot() {
121
121
  const flags = (0, flags_1.getFlags)();
122
- return flags.output
123
- ? path_1.default.resolve(flags.output)
124
- : process.env.APPDATA
125
- ? path_1.default.join(process.env.APPDATA, "sdd-cli", "workspaces")
126
- : path_1.default.join(os_1.default.homedir(), ".config", "sdd-cli", "workspaces");
122
+ if (flags.output) {
123
+ return path_1.default.resolve(flags.output);
124
+ }
125
+ const config = (0, config_1.ensureConfig)();
126
+ return path_1.default.resolve(config.workspace.default_root);
127
127
  }
128
128
  function listScopes(baseRoot) {
129
129
  const root = baseRoot ? path_1.default.resolve(baseRoot) : getWorkspaceBaseRoot();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sdd-cli",
3
- "version": "0.1.22",
4
- "description": "Use sdd-cli to turn ideas, GitHub/Jira work items, and PR feedback into actionable requirements, specs, plans, and done-ready delivery records with guided workflows.",
3
+ "version": "0.1.24",
4
+ "description": "AI-orchestrated specification-driven delivery CLI that plans, validates, and ships production-ready software projects.",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "specification-driven-development",
@@ -25,10 +25,11 @@
25
25
  "bugs": {
26
26
  "url": "https://github.com/jdsalasca/sdd-tool/issues"
27
27
  },
28
- "bin": {
29
- "sdd-cli": "dist/cli.js",
30
- "sdd": "dist/cli.js"
31
- },
28
+ "bin": {
29
+ "sdd-cli": "dist/cli.js",
30
+ "sdd": "dist/cli.js",
31
+ "sdd-tool": "dist/cli.js"
32
+ },
32
33
  "main": "dist/cli.js",
33
34
  "types": "dist/cli.d.ts",
34
35
  "files": [