agileflow 2.91.0 → 2.92.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 (99) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +3 -3
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +31 -23
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate.js +116 -52
  22. package/package.json +1 -1
  23. package/scripts/af +34 -0
  24. package/scripts/agent-loop.js +63 -9
  25. package/scripts/agileflow-configure.js +2 -2
  26. package/scripts/agileflow-welcome.js +435 -23
  27. package/scripts/archive-completed-stories.sh +57 -11
  28. package/scripts/claude-tmux.sh +102 -0
  29. package/scripts/damage-control-bash.js +3 -70
  30. package/scripts/damage-control-edit.js +3 -20
  31. package/scripts/damage-control-write.js +3 -20
  32. package/scripts/dependency-check.js +310 -0
  33. package/scripts/get-env.js +11 -4
  34. package/scripts/lib/configure-detect.js +23 -1
  35. package/scripts/lib/configure-features.js +43 -2
  36. package/scripts/lib/context-formatter.js +771 -0
  37. package/scripts/lib/context-loader.js +699 -0
  38. package/scripts/lib/damage-control-utils.js +107 -0
  39. package/scripts/lib/json-utils.sh +162 -0
  40. package/scripts/lib/state-migrator.js +353 -0
  41. package/scripts/lib/story-state-machine.js +437 -0
  42. package/scripts/obtain-context.js +80 -1248
  43. package/scripts/pre-push-check.sh +46 -0
  44. package/scripts/precompact-context.sh +23 -10
  45. package/scripts/query-codebase.js +122 -14
  46. package/scripts/ralph-loop.js +5 -5
  47. package/scripts/session-manager.js +220 -42
  48. package/scripts/spawn-parallel.js +651 -0
  49. package/scripts/tui/blessed/data/watcher.js +20 -15
  50. package/scripts/tui/blessed/index.js +2 -2
  51. package/scripts/tui/blessed/panels/output.js +14 -8
  52. package/scripts/tui/blessed/panels/sessions.js +22 -15
  53. package/scripts/tui/blessed/panels/trace.js +14 -8
  54. package/scripts/tui/blessed/ui/help.js +3 -3
  55. package/scripts/tui/blessed/ui/screen.js +4 -4
  56. package/scripts/tui/blessed/ui/statusbar.js +5 -9
  57. package/scripts/tui/blessed/ui/tabbar.js +11 -11
  58. package/scripts/validators/component-validator.js +41 -14
  59. package/scripts/validators/json-schema-validator.js +11 -4
  60. package/scripts/validators/markdown-validator.js +1 -2
  61. package/scripts/validators/migration-validator.js +17 -5
  62. package/scripts/validators/security-validator.js +137 -33
  63. package/scripts/validators/story-format-validator.js +31 -10
  64. package/scripts/validators/test-result-validator.js +19 -4
  65. package/scripts/validators/workflow-validator.js +12 -5
  66. package/src/core/agents/codebase-query.md +24 -0
  67. package/src/core/commands/adr.md +114 -0
  68. package/src/core/commands/agent.md +120 -0
  69. package/src/core/commands/assign.md +145 -0
  70. package/src/core/commands/babysit.md +32 -5
  71. package/src/core/commands/changelog.md +118 -0
  72. package/src/core/commands/configure.md +42 -6
  73. package/src/core/commands/diagnose.md +114 -0
  74. package/src/core/commands/epic.md +113 -0
  75. package/src/core/commands/handoff.md +128 -0
  76. package/src/core/commands/help.md +75 -0
  77. package/src/core/commands/pr.md +96 -0
  78. package/src/core/commands/roadmap/analyze.md +400 -0
  79. package/src/core/commands/session/new.md +113 -6
  80. package/src/core/commands/session/spawn.md +197 -0
  81. package/src/core/commands/sprint.md +22 -0
  82. package/src/core/commands/status.md +74 -0
  83. package/src/core/commands/story.md +143 -4
  84. package/src/core/templates/agileflow-metadata.json +55 -2
  85. package/src/core/templates/plan-template.md +125 -0
  86. package/src/core/templates/story-lifecycle.md +213 -0
  87. package/src/core/templates/story-template.md +4 -0
  88. package/src/core/templates/tdd-test-template.js +241 -0
  89. package/tools/cli/commands/setup.js +86 -0
  90. package/tools/cli/installers/core/installer.js +94 -0
  91. package/tools/cli/installers/ide/_base-ide.js +20 -11
  92. package/tools/cli/installers/ide/codex.js +29 -47
  93. package/tools/cli/lib/config-manager.js +17 -2
  94. package/tools/cli/lib/content-transformer.js +271 -0
  95. package/tools/cli/lib/error-handler.js +14 -22
  96. package/tools/cli/lib/ide-error-factory.js +421 -0
  97. package/tools/cli/lib/ide-health-monitor.js +364 -0
  98. package/tools/cli/lib/ide-registry.js +114 -1
  99. package/tools/cli/lib/ui.js +14 -25
@@ -0,0 +1,308 @@
1
+ /**
2
+ * validate-commands.js - Command Validation for Shell Execution
3
+ *
4
+ * Validates and sanitizes commands before shell execution to prevent
5
+ * command injection attacks. Uses allowlist approach for safe commands
6
+ * and rejects dangerous shell metacharacters.
7
+ *
8
+ * Usage:
9
+ * const { validateCommand, ALLOWED_COMMANDS } = require('./validate-commands');
10
+ *
11
+ * const result = validateCommand('npm test');
12
+ * if (result.ok) {
13
+ * // Safe to execute
14
+ * const { command, args } = result.data;
15
+ * } else {
16
+ * console.error(result.error);
17
+ * }
18
+ */
19
+
20
+ const { debugLog, sanitizeForShell } = require('./errors');
21
+
22
+ /**
23
+ * Allowed command prefixes for agent-loop execution
24
+ * These are the only commands that can be executed
25
+ */
26
+ const ALLOWED_COMMANDS = {
27
+ // Package managers
28
+ npm: ['test', 'run', 'run-script'],
29
+ npx: ['jest', 'tsc', 'eslint', 'prettier', 'playwright', 'vitest'],
30
+ yarn: ['test', 'run'],
31
+ pnpm: ['test', 'run'],
32
+ bun: ['test', 'run'],
33
+
34
+ // Direct test runners (for projects not using npm scripts)
35
+ jest: true, // Allow all jest args
36
+ vitest: true,
37
+ mocha: true,
38
+ playwright: ['test'],
39
+
40
+ // Build tools
41
+ tsc: ['--noEmit', '--build', '-b'],
42
+
43
+ // Linters
44
+ eslint: true,
45
+ prettier: ['--check', '--write'],
46
+ };
47
+
48
+ /**
49
+ * Dangerous shell metacharacters that could enable injection
50
+ */
51
+ const DANGEROUS_PATTERNS = [
52
+ /[;&|`$(){}]/g, // Shell operators, substitution
53
+ /\$\{/g, // Variable expansion
54
+ /\$\(/g, // Command substitution
55
+ /`[^`]*`/g, // Backtick substitution
56
+ />\s/g, // Redirection
57
+ /<\s/g, // Input redirection
58
+ /\n/g, // Newlines (could chain commands)
59
+ /\r/g, // Carriage return
60
+ /\\$/g, // Line continuation
61
+ ];
62
+
63
+ /**
64
+ * Parse a command string into executable and arguments
65
+ * @param {string} cmdString - Full command string
66
+ * @returns {{ executable: string, args: string[] }}
67
+ */
68
+ function parseCommand(cmdString) {
69
+ // Handle quoted arguments properly
70
+ const parts = [];
71
+ let current = '';
72
+ let inQuote = null;
73
+
74
+ for (let i = 0; i < cmdString.length; i++) {
75
+ const char = cmdString[i];
76
+
77
+ if (inQuote) {
78
+ if (char === inQuote) {
79
+ inQuote = null;
80
+ } else {
81
+ current += char;
82
+ }
83
+ } else if (char === '"' || char === "'") {
84
+ inQuote = char;
85
+ } else if (char === ' ' || char === '\t') {
86
+ if (current) {
87
+ parts.push(current);
88
+ current = '';
89
+ }
90
+ } else {
91
+ current += char;
92
+ }
93
+ }
94
+
95
+ if (current) {
96
+ parts.push(current);
97
+ }
98
+
99
+ return {
100
+ executable: parts[0] || '',
101
+ args: parts.slice(1),
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Check if an argument contains dangerous patterns
107
+ * @param {string} arg - Argument to check
108
+ * @returns {{ safe: boolean, pattern?: string }}
109
+ */
110
+ function checkArgSafety(arg) {
111
+ for (const pattern of DANGEROUS_PATTERNS) {
112
+ if (pattern.test(arg)) {
113
+ return { safe: false, pattern: pattern.source };
114
+ }
115
+ }
116
+ return { safe: true };
117
+ }
118
+
119
+ /**
120
+ * Validate a command against the allowlist
121
+ * @param {string} cmdString - Full command string to validate
122
+ * @param {Object} [options={}] - Validation options
123
+ * @param {boolean} [options.strict=true] - Require command in allowlist
124
+ * @param {boolean} [options.logBlocked=true] - Log blocked commands
125
+ * @returns {{ ok: boolean, data?: { command: string, args: string[] }, error?: string, severity?: string }}
126
+ */
127
+ function validateCommand(cmdString, options = {}) {
128
+ const { strict = true, logBlocked = true } = options;
129
+
130
+ // Must be a string
131
+ if (typeof cmdString !== 'string') {
132
+ return {
133
+ ok: false,
134
+ error: `Command must be a string, got ${typeof cmdString}`,
135
+ severity: 'high',
136
+ };
137
+ }
138
+
139
+ // Trim and check for empty
140
+ const trimmed = cmdString.trim();
141
+ if (!trimmed) {
142
+ return {
143
+ ok: false,
144
+ error: 'Command cannot be empty',
145
+ severity: 'medium',
146
+ };
147
+ }
148
+
149
+ // Parse the command
150
+ const { executable, args } = parseCommand(trimmed);
151
+
152
+ if (!executable) {
153
+ return {
154
+ ok: false,
155
+ error: 'No executable found in command',
156
+ severity: 'medium',
157
+ };
158
+ }
159
+
160
+ // Check executable against allowlist
161
+ const allowedSubcommands = ALLOWED_COMMANDS[executable];
162
+
163
+ if (strict && !allowedSubcommands) {
164
+ const error = `Command '${executable}' not in allowlist. Allowed: ${Object.keys(ALLOWED_COMMANDS).join(', ')}`;
165
+ if (logBlocked) {
166
+ debugLog('validateCommand', {
167
+ blocked: true,
168
+ command: executable,
169
+ reason: 'not_in_allowlist',
170
+ });
171
+ }
172
+ return {
173
+ ok: false,
174
+ error,
175
+ severity: 'high',
176
+ };
177
+ }
178
+
179
+ // If allowedSubcommands is an array, check the first argument
180
+ if (Array.isArray(allowedSubcommands) && args.length > 0) {
181
+ const subcommand = args[0];
182
+ if (!allowedSubcommands.includes(subcommand)) {
183
+ const error = `Subcommand '${subcommand}' not allowed for '${executable}'. Allowed: ${allowedSubcommands.join(', ')}`;
184
+ if (logBlocked) {
185
+ debugLog('validateCommand', {
186
+ blocked: true,
187
+ command: `${executable} ${subcommand}`,
188
+ reason: 'subcommand_not_allowed',
189
+ });
190
+ }
191
+ return {
192
+ ok: false,
193
+ error,
194
+ severity: 'high',
195
+ };
196
+ }
197
+ }
198
+
199
+ // Check all arguments for dangerous patterns
200
+ for (const arg of args) {
201
+ const argCheck = checkArgSafety(arg);
202
+ if (!argCheck.safe) {
203
+ const error = `Dangerous pattern in argument: '${arg}' matches ${argCheck.pattern}`;
204
+ if (logBlocked) {
205
+ debugLog('validateCommand', {
206
+ blocked: true,
207
+ command: executable,
208
+ arg,
209
+ pattern: argCheck.pattern,
210
+ reason: 'dangerous_pattern',
211
+ });
212
+ }
213
+ return {
214
+ ok: false,
215
+ error,
216
+ severity: 'critical',
217
+ };
218
+ }
219
+ }
220
+
221
+ // Additional check with sanitizeForShell for the full command
222
+ const fullCmdCheck = sanitizeForShell(trimmed, { context: 'command' });
223
+ if (!fullCmdCheck.ok) {
224
+ if (logBlocked) {
225
+ debugLog('validateCommand', {
226
+ blocked: true,
227
+ command: trimmed.slice(0, 50),
228
+ reason: 'shell_unsafe',
229
+ detected: fullCmdCheck.detected,
230
+ });
231
+ }
232
+ return {
233
+ ok: false,
234
+ error: fullCmdCheck.error,
235
+ severity: 'critical',
236
+ };
237
+ }
238
+
239
+ return {
240
+ ok: true,
241
+ data: {
242
+ command: executable,
243
+ args,
244
+ },
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Build a safe command array for spawn()
250
+ * @param {string} cmdString - Command string
251
+ * @param {Object} [options={}] - Options
252
+ * @returns {{ ok: boolean, data?: { file: string, args: string[] }, error?: string }}
253
+ */
254
+ function buildSpawnArgs(cmdString, options = {}) {
255
+ const validation = validateCommand(cmdString, options);
256
+
257
+ if (!validation.ok) {
258
+ return validation;
259
+ }
260
+
261
+ return {
262
+ ok: true,
263
+ data: {
264
+ file: validation.data.command,
265
+ args: validation.data.args,
266
+ },
267
+ };
268
+ }
269
+
270
+ /**
271
+ * Check if a command would be allowed (without modifying)
272
+ * @param {string} cmdString - Command to check
273
+ * @returns {boolean}
274
+ */
275
+ function isAllowedCommand(cmdString) {
276
+ return validateCommand(cmdString, { logBlocked: false }).ok;
277
+ }
278
+
279
+ /**
280
+ * Get a list of all allowed command patterns
281
+ * @returns {string[]}
282
+ */
283
+ function getAllowedCommandList() {
284
+ const list = [];
285
+
286
+ for (const [exe, subcommands] of Object.entries(ALLOWED_COMMANDS)) {
287
+ if (subcommands === true) {
288
+ list.push(`${exe} *`);
289
+ } else if (Array.isArray(subcommands)) {
290
+ for (const sub of subcommands) {
291
+ list.push(`${exe} ${sub}`);
292
+ }
293
+ }
294
+ }
295
+
296
+ return list;
297
+ }
298
+
299
+ module.exports = {
300
+ ALLOWED_COMMANDS,
301
+ DANGEROUS_PATTERNS,
302
+ validateCommand,
303
+ buildSpawnArgs,
304
+ isAllowedCommand,
305
+ getAllowedCommandList,
306
+ parseCommand,
307
+ checkArgSafety,
308
+ };
package/lib/validate.js CHANGED
@@ -4,67 +4,131 @@
4
4
  * Centralized validation patterns and helpers to prevent
5
5
  * command injection, path traversal, and invalid input handling.
6
6
  *
7
- * This module re-exports from split validation modules for backward compatibility.
8
- * For better performance, import directly from:
9
- * - validate-names.js - Name/ID validation patterns
10
- * - validate-args.js - CLI argument validation
11
- * - validate-paths.js - Path traversal protection
7
+ * Usage patterns:
8
+ *
9
+ * 1. Namespace import (recommended - clear, discoverable):
10
+ * const { names, args, paths, commands } = require('./validate');
11
+ * names.isValidStoryId('US-0001')
12
+ * paths.validatePath('/some/path')
13
+ * args.validateArgs(schema, input)
14
+ *
15
+ * 2. Flat import (backwards compatible):
16
+ * const { isValidStoryId, validatePath } = require('./validate');
17
+ *
18
+ * 3. Direct import (best performance):
19
+ * const { isValidStoryId } = require('./validate-names');
20
+ * const { validatePath } = require('./validate-paths');
12
21
  */
13
22
 
14
- // Re-export name validators
15
- const {
16
- PATTERNS,
17
- isValidBranchName,
18
- isValidStoryId,
19
- isValidEpicId,
20
- isValidFeatureName,
21
- isValidProfileName,
22
- isValidCommandName,
23
- isValidSessionNickname,
24
- isValidMergeStrategy,
25
- } = require('./validate-names');
23
+ // Import all validators
24
+ const validateNames = require('./validate-names');
25
+ const validateArgs = require('./validate-args');
26
+ const validatePaths = require('./validate-paths');
27
+ const validateCommands = require('./validate-commands');
26
28
 
27
- // Re-export argument validators
28
- const {
29
- isPositiveInteger,
30
- parseIntBounded,
31
- isValidOption,
32
- validateArgs,
33
- } = require('./validate-args');
29
+ // ============================================================================
30
+ // Namespace exports (recommended for new code)
31
+ // ============================================================================
34
32
 
35
- // Re-export path validators
36
- const {
37
- PathValidationError,
38
- checkSymlinkChainDepth,
39
- validatePath,
40
- validatePathSync,
41
- hasUnsafePathPatterns,
42
- sanitizeFilename,
43
- } = require('./validate-paths');
33
+ /**
34
+ * Name/ID validation namespace
35
+ * @namespace names
36
+ */
37
+ const names = {
38
+ PATTERNS: validateNames.PATTERNS,
39
+ isValidBranchName: validateNames.isValidBranchName,
40
+ isValidStoryId: validateNames.isValidStoryId,
41
+ isValidEpicId: validateNames.isValidEpicId,
42
+ isValidFeatureName: validateNames.isValidFeatureName,
43
+ isValidProfileName: validateNames.isValidProfileName,
44
+ isValidCommandName: validateNames.isValidCommandName,
45
+ isValidSessionNickname: validateNames.isValidSessionNickname,
46
+ isValidMergeStrategy: validateNames.isValidMergeStrategy,
47
+ };
48
+
49
+ /**
50
+ * CLI argument validation namespace
51
+ * @namespace args
52
+ */
53
+ const args = {
54
+ isPositiveInteger: validateArgs.isPositiveInteger,
55
+ parseIntBounded: validateArgs.parseIntBounded,
56
+ isValidOption: validateArgs.isValidOption,
57
+ validateArgs: validateArgs.validateArgs,
58
+ };
59
+
60
+ /**
61
+ * Path traversal protection namespace
62
+ * @namespace paths
63
+ */
64
+ const paths = {
65
+ PathValidationError: validatePaths.PathValidationError,
66
+ checkSymlinkChainDepth: validatePaths.checkSymlinkChainDepth,
67
+ validatePath: validatePaths.validatePath,
68
+ validatePathSync: validatePaths.validatePathSync,
69
+ hasUnsafePathPatterns: validatePaths.hasUnsafePathPatterns,
70
+ sanitizeFilename: validatePaths.sanitizeFilename,
71
+ };
72
+
73
+ /**
74
+ * Command validation namespace
75
+ * @namespace commands
76
+ */
77
+ const commands = {
78
+ ALLOWED_COMMANDS: validateCommands.ALLOWED_COMMANDS,
79
+ DANGEROUS_PATTERNS: validateCommands.DANGEROUS_PATTERNS,
80
+ validateCommand: validateCommands.validateCommand,
81
+ buildSpawnArgs: validateCommands.buildSpawnArgs,
82
+ isAllowedCommand: validateCommands.isAllowedCommand,
83
+ getAllowedCommandList: validateCommands.getAllowedCommandList,
84
+ parseCommand: validateCommands.parseCommand,
85
+ checkArgSafety: validateCommands.checkArgSafety,
86
+ };
87
+
88
+ // ============================================================================
89
+ // Flat exports (backwards compatible)
90
+ // ============================================================================
44
91
 
45
92
  module.exports = {
93
+ // Namespaces (recommended for new code)
94
+ names,
95
+ args,
96
+ paths,
97
+ commands,
98
+
99
+ // Flat exports (backwards compatible)
46
100
  // Patterns and basic validators (from validate-names.js)
47
- PATTERNS,
48
- isValidBranchName,
49
- isValidStoryId,
50
- isValidEpicId,
51
- isValidFeatureName,
52
- isValidProfileName,
53
- isValidCommandName,
54
- isValidSessionNickname,
55
- isValidMergeStrategy,
101
+ PATTERNS: validateNames.PATTERNS,
102
+ isValidBranchName: validateNames.isValidBranchName,
103
+ isValidStoryId: validateNames.isValidStoryId,
104
+ isValidEpicId: validateNames.isValidEpicId,
105
+ isValidFeatureName: validateNames.isValidFeatureName,
106
+ isValidProfileName: validateNames.isValidProfileName,
107
+ isValidCommandName: validateNames.isValidCommandName,
108
+ isValidSessionNickname: validateNames.isValidSessionNickname,
109
+ isValidMergeStrategy: validateNames.isValidMergeStrategy,
56
110
 
57
111
  // Argument validators (from validate-args.js)
58
- isPositiveInteger,
59
- parseIntBounded,
60
- isValidOption,
61
- validateArgs,
112
+ isPositiveInteger: validateArgs.isPositiveInteger,
113
+ parseIntBounded: validateArgs.parseIntBounded,
114
+ isValidOption: validateArgs.isValidOption,
115
+ validateArgs: validateArgs.validateArgs,
62
116
 
63
117
  // Path traversal protection (from validate-paths.js)
64
- PathValidationError,
65
- validatePath,
66
- validatePathSync,
67
- hasUnsafePathPatterns,
68
- sanitizeFilename,
69
- checkSymlinkChainDepth,
118
+ PathValidationError: validatePaths.PathValidationError,
119
+ validatePath: validatePaths.validatePath,
120
+ validatePathSync: validatePaths.validatePathSync,
121
+ hasUnsafePathPatterns: validatePaths.hasUnsafePathPatterns,
122
+ sanitizeFilename: validatePaths.sanitizeFilename,
123
+ checkSymlinkChainDepth: validatePaths.checkSymlinkChainDepth,
124
+
125
+ // Command validation (from validate-commands.js)
126
+ ALLOWED_COMMANDS: validateCommands.ALLOWED_COMMANDS,
127
+ DANGEROUS_PATTERNS: validateCommands.DANGEROUS_PATTERNS,
128
+ validateCommand: validateCommands.validateCommand,
129
+ buildSpawnArgs: validateCommands.buildSpawnArgs,
130
+ isAllowedCommand: validateCommands.isAllowedCommand,
131
+ getAllowedCommandList: validateCommands.getAllowedCommandList,
132
+ parseCommand: validateCommands.parseCommand,
133
+ checkArgSafety: validateCommands.checkArgSafety,
70
134
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.91.0",
3
+ "version": "2.92.0",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
package/scripts/af ADDED
@@ -0,0 +1,34 @@
1
+ #!/bin/bash
2
+ # af - Short alias for Claude Code with tmux integration
3
+ #
4
+ # This is a convenience wrapper that:
5
+ # 1. Auto-starts Claude in a tmux session (for parallel sessions support)
6
+ # 2. Attaches to existing session if one exists for this directory
7
+ # 3. Falls back to regular claude if tmux isn't available
8
+ #
9
+ # Usage:
10
+ # af # Start Claude in tmux
11
+ # af --no-tmux # Start without tmux
12
+ # af -h # Show help
13
+ #
14
+ # To install globally:
15
+ # sudo ln -s $(pwd)/.agileflow/scripts/af /usr/local/bin/af
16
+ # # Or add to your PATH
17
+
18
+ set -e
19
+
20
+ # Get the directory where this script is located
21
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+
23
+ # Check if claude-tmux.sh exists in the same directory
24
+ if [ -f "$SCRIPT_DIR/claude-tmux.sh" ]; then
25
+ exec "$SCRIPT_DIR/claude-tmux.sh" "$@"
26
+ fi
27
+
28
+ # Fallback: try to find claude-tmux.sh in .agileflow/scripts
29
+ if [ -f ".agileflow/scripts/claude-tmux.sh" ]; then
30
+ exec ".agileflow/scripts/claude-tmux.sh" "$@"
31
+ fi
32
+
33
+ # Last resort: just run claude directly
34
+ exec claude "$@"
@@ -25,7 +25,14 @@ const crypto = require('crypto');
25
25
  // Shared utilities
26
26
  const { c } = require('../lib/colors');
27
27
  const { getProjectRoot } = require('../lib/paths');
28
- const { safeReadJSON, safeWriteJSON } = require('../lib/errors');
28
+ const { safeReadJSON, safeWriteJSON, debugLog } = require('../lib/errors');
29
+ const { validateCommand, buildSpawnArgs } = require('../lib/validate-commands');
30
+ const {
31
+ initializeForProject,
32
+ injectCorrelation,
33
+ startSpan,
34
+ getContext,
35
+ } = require('../lib/correlation');
29
36
 
30
37
  const ROOT = getProjectRoot();
31
38
  const LOOPS_DIR = path.join(ROOT, '.agileflow', 'sessions', 'agent-loops');
@@ -85,11 +92,13 @@ function emitEvent(event) {
85
92
  fs.mkdirSync(busDir, { recursive: true });
86
93
  }
87
94
 
88
- const line =
89
- JSON.stringify({
90
- ...event,
91
- timestamp: new Date().toISOString(),
92
- }) + '\n';
95
+ // Inject correlation IDs (trace_id, session_id, span_id)
96
+ const correlatedEvent = injectCorrelation({
97
+ ...event,
98
+ timestamp: new Date().toISOString(),
99
+ });
100
+
101
+ const line = JSON.stringify(correlatedEvent) + '\n';
93
102
 
94
103
  fs.appendFileSync(BUS_PATH, line);
95
104
  }
@@ -128,12 +137,51 @@ function getCoverageReportPath() {
128
137
  return 'coverage/coverage-summary.json';
129
138
  }
130
139
 
140
+ /**
141
+ * Run a command safely using spawn with validated arguments
142
+ * @param {string} cmd - Command string to run
143
+ * @returns {{ passed: boolean, exitCode: number, error?: string, blocked?: boolean }}
144
+ */
131
145
  function runCommand(cmd) {
146
+ // Validate command against allowlist
147
+ const validation = buildSpawnArgs(cmd, { strict: true, logBlocked: true });
148
+
149
+ if (!validation.ok) {
150
+ console.error(
151
+ `${c.red}Command blocked: ${validation.error}${c.reset}` +
152
+ (validation.severity ? ` [${validation.severity}]` : '')
153
+ );
154
+ debugLog('runCommand', {
155
+ blocked: true,
156
+ command: cmd.slice(0, 50),
157
+ error: validation.error,
158
+ severity: validation.severity,
159
+ });
160
+ return { passed: false, exitCode: 1, error: validation.error, blocked: true };
161
+ }
162
+
163
+ const { file, args } = validation.data;
164
+
132
165
  try {
133
- execSync(cmd, { cwd: ROOT, stdio: 'inherit' });
134
- return { passed: true, exitCode: 0 };
166
+ // Use spawnSync with array arguments (no shell injection possible)
167
+ const result = spawnSync(file, args, {
168
+ cwd: ROOT,
169
+ stdio: 'inherit',
170
+ shell: false, // CRITICAL: Do not use shell to prevent injection
171
+ });
172
+
173
+ if (result.error) {
174
+ // spawn itself failed (e.g., command not found)
175
+ console.error(`${c.red}Spawn error: ${result.error.message}${c.reset}`);
176
+ return { passed: false, exitCode: 1, error: result.error.message };
177
+ }
178
+
179
+ return {
180
+ passed: result.status === 0,
181
+ exitCode: result.status || 0,
182
+ };
135
183
  } catch (error) {
136
- return { passed: false, exitCode: error.status || 1 };
184
+ return { passed: false, exitCode: error.status || 1, error: error.message };
137
185
  }
138
186
  }
139
187
 
@@ -265,6 +313,9 @@ function initLoop(options) {
265
313
  parentId = null,
266
314
  } = options;
267
315
 
316
+ // Initialize correlation context (trace_id, session_id)
317
+ const { traceId, sessionId } = initializeForProject(ROOT);
318
+
268
319
  // Validate gate
269
320
  if (!GATES[gate]) {
270
321
  console.error(`${c.red}Invalid gate: ${gate}${c.reset}`);
@@ -295,6 +346,8 @@ function initLoop(options) {
295
346
 
296
347
  const state = {
297
348
  loop_id: loopId,
349
+ trace_id: traceId,
350
+ session_id: sessionId,
298
351
  agent_type: agentType,
299
352
  parent_orchestration: parentId,
300
353
  quality_gate: gate,
@@ -324,6 +377,7 @@ function initLoop(options) {
324
377
  console.log(`${c.green}${c.bold}Agent Loop Initialized${c.reset}`);
325
378
  console.log(`${c.dim}${'─'.repeat(40)}${c.reset}`);
326
379
  console.log(` Loop ID: ${c.cyan}${loopId}${c.reset}`);
380
+ console.log(` Trace ID: ${c.dim}${traceId}${c.reset}`);
327
381
  console.log(` Gate: ${c.magenta}${GATES[gate].name}${c.reset}`);
328
382
  console.log(` Threshold: ${threshold > 0 ? threshold + '%' : 'pass/fail'}`);
329
383
  console.log(` Max Iterations: ${maxIter}`);
@@ -23,7 +23,7 @@
23
23
  * --detect Show current status
24
24
  * --help Show help
25
25
  *
26
- * Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, autoupdate, damagecontrol, askuserquestion
26
+ * Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, autoupdate, damagecontrol, askuserquestion, tmuxautospawn
27
27
  */
28
28
 
29
29
  const fs = require('fs');
@@ -130,7 +130,7 @@ ${c.cyan}Feature Control:${c.reset}
130
130
  --enable=<list> Enable features (comma-separated)
131
131
  --disable=<list> Disable features (comma-separated)
132
132
 
133
- Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol, askuserquestion
133
+ Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol, askuserquestion, tmuxautospawn
134
134
 
135
135
  ${c.cyan}Statusline Components:${c.reset}
136
136
  --show=<list> Show statusline components (comma-separated)