specrails-hub 1.25.1 → 1.25.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-hub",
3
- "version": "1.25.1",
3
+ "version": "1.25.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,7 +16,8 @@
16
16
  "cli/dist",
17
17
  "server/dist",
18
18
  "client/dist",
19
- "docs"
19
+ "docs",
20
+ ".claude/commands/specrails"
20
21
  ],
21
22
  "scripts": {
22
23
  "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
@@ -186,6 +186,7 @@ class ChatManager {
186
186
  args = [
187
187
  '--model', conversation.model,
188
188
  '--dangerously-skip-permissions',
189
+ '--tools', 'default',
189
190
  '--output-format', 'stream-json',
190
191
  '--verbose',
191
192
  '--system-prompt', systemPrompt,
@@ -3,11 +3,43 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveCommand = resolveCommand;
4
4
  const fs_1 = require("fs");
5
5
  const path_1 = require("path");
6
+ /**
7
+ * Locate the hub repository root (the directory containing .claude/commands/).
8
+ * Works in both dev mode (tsx: __dirname = <hub>/server/) and
9
+ * compiled mode (tsc: __dirname = <hub>/server/dist/).
10
+ */
11
+ function findHubRoot() {
12
+ let dir = (0, path_1.resolve)(__dirname, '..');
13
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, '.claude', 'commands')))
14
+ return dir;
15
+ dir = (0, path_1.resolve)(__dirname, '../..');
16
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, '.claude', 'commands')))
17
+ return dir;
18
+ return null;
19
+ }
20
+ const HUB_ROOT = findHubRoot();
21
+ /**
22
+ * Try to find a command/skill .md file for the given command path parts
23
+ * within the given base directory. Returns the resolved path or null.
24
+ */
25
+ function findCommandFile(baseDir, parts) {
26
+ const filePath = (0, path_1.join)(baseDir, '.claude', 'commands', ...parts) + '.md';
27
+ if ((0, fs_1.existsSync)(filePath))
28
+ return filePath;
29
+ const skillPath = (0, path_1.join)(baseDir, '.claude', 'skills', ...parts) + '.md';
30
+ if ((0, fs_1.existsSync)(skillPath))
31
+ return skillPath;
32
+ return null;
33
+ }
6
34
  /**
7
35
  * Resolves a slash command string to its full prompt content.
8
36
  * Reads the command file from .claude/commands/ or .claude/skills/,
9
37
  * strips YAML frontmatter, and substitutes $ARGUMENTS.
10
38
  *
39
+ * Searches the project directory first, then falls back to the hub's
40
+ * own .claude/commands/ directory (for hub-namespaced commands like
41
+ * /specrails:implement that aren't installed in the target project).
42
+ *
11
43
  * Falls back to returning the command string as-is if the file is not found.
12
44
  */
13
45
  function resolveCommand(command, cwd) {
@@ -16,9 +48,13 @@ function resolveCommand(command, cwd) {
16
48
  return command;
17
49
  const commandPath = match[1];
18
50
  const commandArgs = match[2].trim();
19
- const filePath = (0, path_1.join)(cwd, '.claude', 'commands', ...commandPath.split(':')) + '.md';
20
- const skillPath = (0, path_1.join)(cwd, '.claude', 'skills', ...commandPath.split(':')) + '.md';
21
- const resolvedPath = (0, fs_1.existsSync)(filePath) ? filePath : (0, fs_1.existsSync)(skillPath) ? skillPath : null;
51
+ const parts = commandPath.split(':');
52
+ // 1. Check the project directory
53
+ let resolvedPath = findCommandFile(cwd, parts);
54
+ // 2. Fallback: check the hub's own directory
55
+ if (!resolvedPath && HUB_ROOT && (0, path_1.resolve)(cwd) !== (0, path_1.resolve)(HUB_ROOT)) {
56
+ resolvedPath = findCommandFile(HUB_ROOT, parts);
57
+ }
22
58
  if (!resolvedPath)
23
59
  return command;
24
60
  let content = (0, fs_1.readFileSync)(resolvedPath, 'utf-8');
@@ -1172,6 +1172,7 @@ function createProjectRouter(registry) {
1172
1172
  binary = 'claude';
1173
1173
  args = [
1174
1174
  '--dangerously-skip-permissions',
1175
+ '--tools', 'default',
1175
1176
  '--output-format', 'stream-json',
1176
1177
  '--verbose',
1177
1178
  '--max-turns', '1',
@@ -1443,6 +1444,7 @@ function createProjectRouter(registry) {
1443
1444
  binary = 'claude';
1444
1445
  args = [
1445
1446
  '--dangerously-skip-permissions',
1447
+ '--tools', 'default',
1446
1448
  '--output-format', 'stream-json',
1447
1449
  '--verbose',
1448
1450
  '--max-turns', '4',
@@ -43,6 +43,7 @@ class ProposalManager {
43
43
  (0, db_1.updateProposal)(this._db, proposalId, { status: 'exploring' });
44
44
  const args = [
45
45
  '--dangerously-skip-permissions',
46
+ '--tools', 'default',
46
47
  '--output-format', 'stream-json',
47
48
  '--verbose',
48
49
  '-p', prompt,
@@ -78,6 +79,7 @@ class ProposalManager {
78
79
  (0, db_1.updateProposal)(this._db, proposalId, { status: 'refining' });
79
80
  const args = [
80
81
  '--dangerously-skip-permissions',
82
+ '--tools', 'default',
81
83
  '--output-format', 'stream-json',
82
84
  '--verbose',
83
85
  '--resume', proposal.session_id,
@@ -116,6 +118,7 @@ class ProposalManager {
116
118
  "Output only the URL of the created issue on the last line of your response.";
117
119
  const args = [
118
120
  '--dangerously-skip-permissions',
121
+ '--tools', 'default',
119
122
  '--output-format', 'stream-json',
120
123
  '--verbose',
121
124
  '--resume', proposal.session_id,
@@ -337,7 +337,10 @@ class QueueManager {
337
337
  else {
338
338
  (0, hooks_1.resetPhases)(this._broadcast);
339
339
  }
340
- let commandToRun = job.command.trim();
340
+ const commandToRun = job.command.trim();
341
+ // Build supplementary context (output chaining + headless mode) that goes
342
+ // into --append-system-prompt, keeping the user prompt clean.
343
+ let systemAppend = '';
341
344
  // Output chaining: inject previous step's output as context for dependent jobs
342
345
  if (job.dependsOnJobId) {
343
346
  const parentJob = this._jobs.get(job.dependsOnJobId);
@@ -346,30 +349,38 @@ class QueueManager {
346
349
  const truncated = prevOutput.length > 10000
347
350
  ? prevOutput.slice(0, 10000) + '\n\n[output truncated]'
348
351
  : prevOutput;
349
- commandToRun = `Previous step output:\n\n${truncated}\n\n---\n\nNow execute the following:\n${commandToRun}`;
352
+ systemAppend += `Previous step output:\n\n${truncated}\n\n---\n\nNow execute the following command.\n\n`;
350
353
  }
351
354
  }
352
- let resolvedCmd = this._resolveCommand(commandToRun);
353
- // Headless mode: when --yes is in the command, append auto-proceed instructions
354
- // so Claude doesn't wait for user confirmation (stdin is ignored in spawned processes)
355
+ // Headless mode: when --yes is in the command, instruct Claude to auto-proceed
356
+ // (stdin is ignored in spawned processes, so no user confirmation is possible)
355
357
  if (job.command.includes('--yes')) {
356
- resolvedCmd += '\n\nIMPORTANT: This command is running in headless/unattended mode (--yes flag). Do NOT wait for user confirmation at any step. Auto-proceed with "yes" for all confirmation prompts. Skip any "Wait for user confirmation" instructions.';
358
+ systemAppend += '\n\nIMPORTANT: This command is running in headless/unattended mode (--yes flag). Do NOT wait for user confirmation at any step. Auto-proceed with "yes" for all confirmation prompts. Skip any "Wait for user confirmation" instructions.';
357
359
  }
358
360
  let binary;
359
361
  let args;
360
362
  if (this._provider === 'codex') {
361
363
  binary = 'codex';
362
- args = ['exec', resolvedCmd];
364
+ // Codex doesn't support slash commands — resolve the prompt
365
+ const resolved = this._resolveCommand(commandToRun);
366
+ args = ['exec', resolved];
363
367
  }
364
368
  else {
365
369
  binary = 'claude';
366
370
  args = [
367
371
  '--dangerously-skip-permissions',
372
+ '--tools', 'default',
368
373
  '--output-format', 'stream-json',
369
374
  '--verbose',
370
- '-p',
371
- resolvedCmd,
372
375
  ];
376
+ if (systemAppend) {
377
+ args.push('--append-system-prompt', systemAppend);
378
+ }
379
+ // Pass the raw command to Claude CLI so it resolves skills natively.
380
+ // This ensures skills get proper execution priority over CLAUDE.md
381
+ // instructions — pre-resolving to plain text caused the project's
382
+ // CLAUDE.md to override the pipeline prompt.
383
+ args.push('-p', commandToRun);
373
384
  }
374
385
  const child = (0, child_process_1.spawn)(binary, args, {
375
386
  env: process.env,
@@ -522,6 +522,7 @@ class SetupManager {
522
522
  const args = [
523
523
  '-p', enrichCmd,
524
524
  '--dangerously-skip-permissions',
525
+ '--tools', 'default',
525
526
  '--output-format', 'stream-json',
526
527
  '--verbose',
527
528
  ];
@@ -571,6 +572,7 @@ class SetupManager {
571
572
  const args = [
572
573
  '--resume', sessionId,
573
574
  '--dangerously-skip-permissions',
575
+ '--tools', 'default',
574
576
  '--output-format', 'stream-json',
575
577
  '--verbose',
576
578
  '-p', userMessage,
@@ -32,6 +32,7 @@ class SpecLauncherManager {
32
32
  }
33
33
  const args = [
34
34
  '--dangerously-skip-permissions',
35
+ '--tools', 'default',
35
36
  '--output-format', 'stream-json',
36
37
  '--verbose',
37
38
  '-p', prompt,