agileflow 3.1.0 → 3.2.1

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 (106) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +57 -85
  3. package/lib/dashboard-automations.js +130 -0
  4. package/lib/dashboard-git.js +254 -0
  5. package/lib/dashboard-inbox.js +64 -0
  6. package/lib/dashboard-protocol.js +1 -0
  7. package/lib/dashboard-server.js +114 -924
  8. package/lib/dashboard-session.js +136 -0
  9. package/lib/dashboard-status.js +72 -0
  10. package/lib/dashboard-terminal.js +354 -0
  11. package/lib/dashboard-websocket.js +88 -0
  12. package/lib/drivers/codex-driver.ts +4 -4
  13. package/lib/logger.js +106 -0
  14. package/package.json +4 -2
  15. package/scripts/agileflow-configure.js +2 -2
  16. package/scripts/agileflow-welcome.js +409 -434
  17. package/scripts/claude-tmux.sh +80 -2
  18. package/scripts/context-loader.js +4 -9
  19. package/scripts/lib/browser-qa-evidence.js +409 -0
  20. package/scripts/lib/browser-qa-status.js +192 -0
  21. package/scripts/lib/command-prereqs.js +280 -0
  22. package/scripts/lib/configure-detect.js +92 -2
  23. package/scripts/lib/configure-features.js +295 -1
  24. package/scripts/lib/context-formatter.js +468 -233
  25. package/scripts/lib/context-loader.js +27 -15
  26. package/scripts/lib/damage-control-utils.js +8 -1
  27. package/scripts/lib/feature-catalog.js +321 -0
  28. package/scripts/lib/portable-tasks-cli.js +274 -0
  29. package/scripts/lib/portable-tasks.js +479 -0
  30. package/scripts/lib/signal-detectors.js +1 -1
  31. package/scripts/lib/team-events.js +86 -1
  32. package/scripts/obtain-context.js +28 -4
  33. package/scripts/smart-detect.js +17 -0
  34. package/scripts/strip-ai-attribution.js +63 -0
  35. package/scripts/team-manager.js +7 -2
  36. package/scripts/welcome-deferred.js +437 -0
  37. package/src/core/agents/browser-qa.md +328 -0
  38. package/src/core/agents/perf-analyzer-assets.md +174 -0
  39. package/src/core/agents/perf-analyzer-bundle.md +165 -0
  40. package/src/core/agents/perf-analyzer-caching.md +160 -0
  41. package/src/core/agents/perf-analyzer-compute.md +165 -0
  42. package/src/core/agents/perf-analyzer-memory.md +182 -0
  43. package/src/core/agents/perf-analyzer-network.md +157 -0
  44. package/src/core/agents/perf-analyzer-queries.md +155 -0
  45. package/src/core/agents/perf-analyzer-rendering.md +156 -0
  46. package/src/core/agents/perf-consensus.md +280 -0
  47. package/src/core/agents/security-analyzer-api.md +199 -0
  48. package/src/core/agents/security-analyzer-auth.md +160 -0
  49. package/src/core/agents/security-analyzer-authz.md +168 -0
  50. package/src/core/agents/security-analyzer-deps.md +147 -0
  51. package/src/core/agents/security-analyzer-infra.md +176 -0
  52. package/src/core/agents/security-analyzer-injection.md +148 -0
  53. package/src/core/agents/security-analyzer-input.md +191 -0
  54. package/src/core/agents/security-analyzer-secrets.md +175 -0
  55. package/src/core/agents/security-consensus.md +276 -0
  56. package/src/core/agents/test-analyzer-assertions.md +181 -0
  57. package/src/core/agents/test-analyzer-coverage.md +183 -0
  58. package/src/core/agents/test-analyzer-fragility.md +185 -0
  59. package/src/core/agents/test-analyzer-integration.md +155 -0
  60. package/src/core/agents/test-analyzer-maintenance.md +173 -0
  61. package/src/core/agents/test-analyzer-mocking.md +178 -0
  62. package/src/core/agents/test-analyzer-patterns.md +189 -0
  63. package/src/core/agents/test-analyzer-structure.md +177 -0
  64. package/src/core/agents/test-consensus.md +294 -0
  65. package/src/core/commands/{legal/audit.md → audit/legal.md} +13 -13
  66. package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
  67. package/src/core/commands/audit/performance.md +443 -0
  68. package/src/core/commands/audit/security.md +443 -0
  69. package/src/core/commands/audit/test.md +442 -0
  70. package/src/core/commands/babysit.md +505 -463
  71. package/src/core/commands/browser-qa.md +240 -0
  72. package/src/core/commands/configure.md +8 -8
  73. package/src/core/commands/research/ask.md +42 -9
  74. package/src/core/commands/research/import.md +14 -8
  75. package/src/core/commands/research/list.md +17 -16
  76. package/src/core/commands/research/synthesize.md +8 -8
  77. package/src/core/commands/research/view.md +28 -4
  78. package/src/core/commands/whats-new.md +2 -2
  79. package/src/core/experts/devops/expertise.yaml +13 -2
  80. package/src/core/experts/documentation/expertise.yaml +26 -4
  81. package/src/core/profiles/COMPARISON.md +170 -0
  82. package/src/core/profiles/README.md +178 -0
  83. package/src/core/profiles/claude-code.yaml +111 -0
  84. package/src/core/profiles/codex.yaml +103 -0
  85. package/src/core/profiles/cursor.yaml +134 -0
  86. package/src/core/profiles/examples.js +250 -0
  87. package/src/core/profiles/loader.js +235 -0
  88. package/src/core/profiles/windsurf.yaml +159 -0
  89. package/src/core/teams/logic-audit.json +6 -0
  90. package/src/core/teams/perf-audit.json +71 -0
  91. package/src/core/teams/security-audit.json +71 -0
  92. package/src/core/teams/test-audit.json +71 -0
  93. package/src/core/templates/browser-qa-spec.yaml +94 -0
  94. package/src/core/templates/command-prerequisites.yaml +169 -0
  95. package/src/core/templates/damage-control-patterns.yaml +9 -0
  96. package/tools/cli/installers/ide/_base-ide.js +33 -3
  97. package/tools/cli/installers/ide/claude-code.js +2 -69
  98. package/tools/cli/installers/ide/codex.js +9 -9
  99. package/tools/cli/installers/ide/cursor.js +165 -4
  100. package/tools/cli/installers/ide/windsurf.js +237 -6
  101. package/tools/cli/lib/content-transformer.js +234 -9
  102. package/tools/cli/lib/docs-setup.js +1 -1
  103. package/tools/cli/lib/ide-generator.js +357 -0
  104. package/tools/cli/lib/ide-registry.js +2 -2
  105. package/scripts/tmux-task-name.sh +0 -105
  106. package/scripts/tmux-task-watcher.sh +0 -344
@@ -0,0 +1,357 @@
1
+ /**
2
+ * ide-generator.js - Build-time IDE-specific prompt/skill/agent generator
3
+ *
4
+ * Integrates IDE capability profiles with content transformer to produce
5
+ * IDE-native commands and agents during the install process.
6
+ *
7
+ * Main entry points:
8
+ * - generateForIde(content, targetIde, options) - Base transformation
9
+ * - generateCommandForIde(content, commandName, targetIde, options) - Command-specific
10
+ * - generateAgentForIde(content, agentName, targetIde, options) - Agent-specific
11
+ */
12
+
13
+ const {
14
+ transformForIde,
15
+ transformToolReferences,
16
+ replaceReferences,
17
+ stripFrontmatter,
18
+ getFrontmatter,
19
+ convertFrontmatter,
20
+ } = require('./content-transformer');
21
+
22
+ // Lazy-load profile loader - only if profiles are needed
23
+ let profileLoader = null;
24
+
25
+ function getProfileLoader() {
26
+ if (!profileLoader) {
27
+ try {
28
+ profileLoader = require('../../../src/core/profiles/loader');
29
+ } catch (e) {
30
+ // Profiles not available - return null to indicate fallback mode
31
+ return null;
32
+ }
33
+ }
34
+ return profileLoader;
35
+ }
36
+
37
+ /**
38
+ * Get the IDE prefix style for commands
39
+ * Claude Code uses "/agileflow:", others use "/" or "$" as prefix
40
+ * @param {string} targetIde - Target IDE: 'claude-code', 'cursor', 'windsurf', 'codex'
41
+ * @returns {string} Command prefix pattern
42
+ * @private
43
+ */
44
+ function getCommandPrefix(targetIde) {
45
+ const prefixes = {
46
+ 'claude-code': '/agileflow:',
47
+ cursor: '/',
48
+ windsurf: '/',
49
+ codex: '$agileflow-',
50
+ };
51
+ return prefixes[targetIde] || '/agileflow:';
52
+ }
53
+
54
+ /**
55
+ * Convert /agileflow:style:commands to IDE-specific format
56
+ * - Claude Code: /agileflow:foo:bar → /agileflow:foo:bar (unchanged)
57
+ * - Cursor: /agileflow:foo:bar → /foo-bar
58
+ * - Windsurf: /agileflow:foo:bar → /agileflow-foo-bar
59
+ * - Codex: /agileflow:foo:bar → $agileflow-foo-bar
60
+ *
61
+ * @param {string} content - Content with command references
62
+ * @param {string} targetIde - Target IDE
63
+ * @returns {string} Content with converted command prefixes
64
+ * @private
65
+ */
66
+ function convertCommandPrefixes(content, targetIde) {
67
+ if (targetIde === 'claude-code') {
68
+ return content; // No conversion needed
69
+ }
70
+
71
+ let result = content;
72
+
73
+ if (targetIde === 'cursor') {
74
+ // /agileflow:foo:bar → /foo-bar
75
+ result = result.replace(/\/agileflow:([a-zA-Z0-9:_-]+)/g, (_match, rest) => {
76
+ return '/' + rest.replace(/:/g, '-');
77
+ });
78
+ } else if (targetIde === 'windsurf') {
79
+ // /agileflow:foo:bar → /agileflow-foo-bar
80
+ result = result.replace(/\/agileflow:([a-zA-Z0-9:_-]+)/g, (_match, rest) => {
81
+ return '/agileflow-' + rest.replace(/:/g, '-');
82
+ });
83
+ } else if (targetIde === 'codex') {
84
+ // /agileflow:foo:bar → $agileflow-foo-bar
85
+ result = result.replace(/\/agileflow:([a-zA-Z0-9:_-]+)/g, (_match, rest) => {
86
+ return '$agileflow-' + rest.replace(/:/g, '-');
87
+ });
88
+ }
89
+
90
+ return result;
91
+ }
92
+
93
+ /**
94
+ * Main entry point for IDE-specific content generation
95
+ * Takes canonical Claude Code markdown content and transforms it for a target IDE
96
+ *
97
+ * @param {string} content - Source content (Claude Code format)
98
+ * @param {string} targetIde - Target IDE: 'claude-code', 'cursor', 'windsurf', 'codex'
99
+ * @param {Object} [options] - Generation options
100
+ * @param {string} [options.docsFolder] - Custom docs folder name
101
+ * @param {boolean} [options.transformTools] - Apply tool reference transformations (default: true)
102
+ * @param {boolean} [options.transformPrefixes] - Convert command prefixes (default: true)
103
+ * @param {Object} [options.additionalReplacements] - Extra IDE-specific replacements
104
+ * @returns {string} Transformed content for target IDE
105
+ *
106
+ * @example
107
+ * const content = await fs.readFile('command.md', 'utf8');
108
+ * const cursorVersion = generateForIde(content, 'cursor', { transformTools: true });
109
+ */
110
+ function generateForIde(content, targetIde, options = {}) {
111
+ if (!content || typeof content !== 'string') {
112
+ return content || '';
113
+ }
114
+
115
+ // Claude Code is canonical format - return as-is
116
+ if (targetIde === 'claude-code') {
117
+ return content;
118
+ }
119
+
120
+ const {
121
+ docsFolder,
122
+ transformTools = true,
123
+ transformPrefixes = true,
124
+ additionalReplacements = {},
125
+ } = options;
126
+
127
+ // Step 1: Apply IDE-specific replacements and docs folder updates
128
+ let result = transformForIde(content, targetIde, {
129
+ docsFolder,
130
+ additionalReplacements,
131
+ transformTools: false, // Will apply separately
132
+ });
133
+
134
+ // Step 2: Apply tool reference transformations if requested
135
+ if (transformTools) {
136
+ result = transformToolReferences(result, targetIde);
137
+ }
138
+
139
+ // Step 3: Convert command prefixes if requested
140
+ if (transformPrefixes) {
141
+ result = convertCommandPrefixes(result, targetIde);
142
+ }
143
+
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Generate IDE-specific command content with IDE-native wrapping
149
+ *
150
+ * For Codex, adds {{input}} placeholder at end for context injection.
151
+ * For other IDEs, uses base generateForIde() transformation.
152
+ *
153
+ * @param {string} content - Source command content (Claude Code format)
154
+ * @param {string} commandName - Command name for display
155
+ * @param {string} targetIde - Target IDE
156
+ * @param {Object} [options] - Generation options (same as generateForIde)
157
+ * @returns {string} IDE-native command content
158
+ *
159
+ * @example
160
+ * const cmd = await fs.readFile('commands/deploy.md', 'utf8');
161
+ * const codexPrompt = generateCommandForIde(cmd, 'deploy', 'codex');
162
+ */
163
+ function generateCommandForIde(content, commandName, targetIde, options = {}) {
164
+ // Handle null/undefined content
165
+ if (content === null || content === undefined || typeof content !== 'string') {
166
+ return '';
167
+ }
168
+
169
+ // Start with base transformation
170
+ let result = generateForIde(content, targetIde, options);
171
+
172
+ // Codex-specific wrapping: Add {{input}} placeholder
173
+ if (targetIde === 'codex' && result !== '') {
174
+ // Strip frontmatter for Codex format
175
+ const bodyContent = stripFrontmatter(result);
176
+ const frontmatter = getFrontmatter(content);
177
+ const description = frontmatter.description || `AgileFlow ${commandName} command`;
178
+
179
+ const header = `# AgileFlow: ${commandName}
180
+
181
+ > ${description}
182
+
183
+ ## Instructions
184
+
185
+ `;
186
+
187
+ const footer = `
188
+
189
+ ## Context
190
+
191
+ {{input}}
192
+ `;
193
+
194
+ result = header + bodyContent + footer;
195
+ }
196
+
197
+ return result;
198
+ }
199
+
200
+ /**
201
+ * Generate IDE-specific agent/skill content
202
+ *
203
+ * Performs IDE-specific transformations including:
204
+ * - For Codex: Converts to SKILL.md format with skill-specific frontmatter
205
+ * - For Windsurf: Converts to agentskills.io format with skill-specific frontmatter
206
+ * - For Cursor: Adds agent-specific frontmatter for spawnable agents
207
+ * - For Claude Code: Returns unchanged (canonical format)
208
+ *
209
+ * @param {string} content - Source agent content (Claude Code format)
210
+ * @param {string} agentName - Agent name (e.g., 'database', 'security')
211
+ * @param {string} targetIde - Target IDE: 'claude-code', 'cursor', 'windsurf', 'codex'
212
+ * @param {Object} [options] - Generation options
213
+ * @param {string} [options.docsFolder] - Custom docs folder name
214
+ * @param {boolean} [options.transformTools] - Apply tool reference transformations (default: true)
215
+ * @returns {string} IDE-native agent/skill content
216
+ *
217
+ * @example
218
+ * const agent = await fs.readFile('agents/security.md', 'utf8');
219
+ * const windsurfSkill = generateAgentForIde(agent, 'security', 'windsurf');
220
+ */
221
+ function generateAgentForIde(content, agentName, targetIde, options = {}) {
222
+ if (!content || typeof content !== 'string') {
223
+ return content || '';
224
+ }
225
+
226
+ // Claude Code is canonical format
227
+ if (targetIde === 'claude-code') {
228
+ return content;
229
+ }
230
+
231
+ const { docsFolder, transformTools = true } = options;
232
+
233
+ // Get base transformations
234
+ const baseContent = generateForIde(content, targetIde, {
235
+ docsFolder,
236
+ transformTools,
237
+ transformPrefixes: false, // Don't convert prefixes in agent bodies
238
+ });
239
+
240
+ // Extract frontmatter and body
241
+ const frontmatter = getFrontmatter(content);
242
+ const description = frontmatter.description || `AgileFlow ${agentName} agent`;
243
+
244
+ // Apply IDE-specific formatting
245
+ if (targetIde === 'codex') {
246
+ return formatAgentForCodex(baseContent, agentName, description);
247
+ } else if (targetIde === 'windsurf') {
248
+ return formatAgentForWindsurf(baseContent, agentName, description);
249
+ } else if (targetIde === 'cursor') {
250
+ return formatAgentForCursor(baseContent, agentName, description);
251
+ }
252
+
253
+ return baseContent;
254
+ }
255
+
256
+ /**
257
+ * Format agent content for Codex SKILL.md format
258
+ * @private
259
+ */
260
+ function formatAgentForCodex(content, agentName, description) {
261
+ const yaml = require('../../../lib/yaml-utils').yaml;
262
+
263
+ const bodyContent = stripFrontmatter(content);
264
+
265
+ // Create SKILL.md with YAML frontmatter
266
+ const skillFrontmatter = yaml
267
+ .dump({
268
+ name: `agileflow-${agentName}`,
269
+ description: description,
270
+ version: '1.0.0',
271
+ })
272
+ .trim();
273
+
274
+ const codexHeader = `# AgileFlow: ${agentName.charAt(0).toUpperCase() + agentName.slice(1)} Agent
275
+
276
+ > Invoke with \`$agileflow-${agentName}\` or via \`/skills\`
277
+
278
+ `;
279
+
280
+ return `---
281
+ ${skillFrontmatter}
282
+ ---
283
+
284
+ ${codexHeader}${bodyContent}`;
285
+ }
286
+
287
+ /**
288
+ * Format agent content for Windsurf SKILL.md (agentskills.io spec)
289
+ * @private
290
+ */
291
+ function formatAgentForWindsurf(content, agentName, description) {
292
+ const yaml = require('../../../lib/yaml-utils').yaml;
293
+
294
+ const bodyContent = stripFrontmatter(content);
295
+
296
+ // Create SKILL.md with YAML frontmatter (agentskills.io spec)
297
+ const skillFrontmatter = yaml
298
+ .dump({
299
+ name: `agileflow-${agentName}`,
300
+ description: description,
301
+ })
302
+ .trim();
303
+
304
+ const windsurfHeader = `# AgileFlow: ${agentName.charAt(0).toUpperCase() + agentName.slice(1)} Skill
305
+
306
+ > Use this skill via \`@agileflow-${agentName}\` or /cascade
307
+
308
+ `;
309
+
310
+ return `---
311
+ ${skillFrontmatter}
312
+ ---
313
+
314
+ ${windsurfHeader}${bodyContent}`;
315
+ }
316
+
317
+ /**
318
+ * Format agent content for Cursor spawnable agent
319
+ * Cursor agents use YAML frontmatter with name, description, and model fields
320
+ * @private
321
+ */
322
+ function formatAgentForCursor(content, agentName, description) {
323
+ const yaml = require('../../../lib/yaml-utils').yaml;
324
+
325
+ // Extract frontmatter from source
326
+ const sourceFrontmatter = getFrontmatter(content);
327
+ const bodyContent = stripFrontmatter(content);
328
+
329
+ // Create agent frontmatter for Cursor
330
+ const agentFrontmatter = yaml
331
+ .dump({
332
+ name: `agileflow-${agentName}`,
333
+ description: description,
334
+ model: sourceFrontmatter.model || 'claude-3-5-sonnet',
335
+ readonly: false,
336
+ })
337
+ .trim();
338
+
339
+ return `---
340
+ ${agentFrontmatter}
341
+ ---
342
+
343
+ ${bodyContent}`;
344
+ }
345
+
346
+ module.exports = {
347
+ generateForIde,
348
+ generateCommandForIde,
349
+ generateAgentForIde,
350
+ getCommandPrefix,
351
+ // Export private functions for testing
352
+ _convertCommandPrefixes: convertCommandPrefixes,
353
+ _getProfileLoader: getProfileLoader,
354
+ _formatAgentForCodex: formatAgentForCodex,
355
+ _formatAgentForWindsurf: formatAgentForWindsurf,
356
+ _formatAgentForCursor: formatAgentForCursor,
357
+ };
@@ -98,13 +98,13 @@ const IDE_REGISTRY = {
98
98
  },
99
99
  codex: {
100
100
  name: 'codex',
101
- displayName: 'OpenAI Codex CLI',
101
+ displayName: 'OpenAI Codex',
102
102
  configDir: '.codex',
103
103
  commandsSubdir: 'skills',
104
104
  agileflowFolder: 'agileflow',
105
105
  targetSubdir: 'skills', // Codex uses skills directory
106
106
  preferred: false,
107
- description: "OpenAI's Codex CLI",
107
+ description: "OpenAI's Codex",
108
108
  handler: 'CodexSetup',
109
109
  labels: {
110
110
  commands: 'prompts',
@@ -1,105 +0,0 @@
1
- #!/usr/bin/env bash
2
- # tmux-task-name.sh - Rename current tmux window based on task/work description
3
- #
4
- # Called by Claude Code when starting work on a task (via TaskCreate/TaskUpdate).
5
- # Reads the task subject from ~/.claude/tasks/ or accepts it as an argument.
6
- #
7
- # Usage:
8
- # tmux-task-name.sh "Fix auth middleware" # Rename to task subject
9
- # tmux-task-name.sh --scan # Auto-detect from task files
10
- # tmux-task-name.sh --scan --session <UUID> # Scan only one session's tasks
11
- # tmux-task-name.sh --reset # Reset to default "claude-N"
12
- #
13
- # The script is best-effort: silently exits if not inside tmux.
14
-
15
- set -euo pipefail
16
-
17
- # Exit silently if not in tmux
18
- [ -n "${TMUX:-}" ] || exit 0
19
-
20
- MAX_LEN=30
21
-
22
- truncate_name() {
23
- local name="$1"
24
- if [ ${#name} -gt $MAX_LEN ]; then
25
- echo "${name:0:$((MAX_LEN - 1))}…"
26
- else
27
- echo "$name"
28
- fi
29
- }
30
-
31
- # Mode: reset to default sequential name
32
- if [ "${1:-}" = "--reset" ]; then
33
- N=$(( $(tmux list-windows -F '#{window_name}' 2>/dev/null | grep -c '^claude') ))
34
- [ "$N" -eq 0 ] && N=1
35
- tmux rename-window "claude-$N" 2>/dev/null || true
36
- exit 0
37
- fi
38
-
39
- # Mode: scan ~/.claude/tasks/ for most recently modified in-progress task
40
- if [ "${1:-}" = "--scan" ]; then
41
- TASKS_BASE="${HOME}/.claude/tasks"
42
- [ -d "$TASKS_BASE" ] || exit 0
43
-
44
- # Determine session scope: --session param, pane option, or global scan
45
- SESSION_ID=""
46
- if [ "${2:-}" = "--session" ] && [ -n "${3:-}" ]; then
47
- # Validate session ID is alphanumeric + hyphens only (prevent path traversal)
48
- if [[ "$3" =~ ^[a-zA-Z0-9_-]+$ ]]; then
49
- SESSION_ID="$3"
50
- fi
51
- elif [ -n "${TMUX:-}" ]; then
52
- SESSION_ID=$(tmux show-options -pqv @claude_session_id 2>/dev/null || true)
53
- fi
54
-
55
- BEST_SUBJECT=""
56
- BEST_MTIME=0
57
-
58
- if [ -n "$SESSION_ID" ]; then
59
- # Scoped scan: only look at this session's tasks
60
- SCAN_DIR="$TASKS_BASE/$SESSION_ID"
61
- if [ -d "$SCAN_DIR" ]; then
62
- for f in "$SCAN_DIR"/*.json; do
63
- [ -f "$f" ] || continue
64
- status=$(python3 -c "import json,sys; d=json.load(open('$f')); print(d.get('status',''))" 2>/dev/null || echo "")
65
- if [ "$status" = "in_progress" ]; then
66
- mtime=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null || echo "0")
67
- if [ "$mtime" -gt "$BEST_MTIME" ]; then
68
- BEST_MTIME=$mtime
69
- BEST_SUBJECT=$(python3 -c "import json; d=json.load(open('$f')); print(d.get('subject',''))" 2>/dev/null || echo "")
70
- fi
71
- fi
72
- done
73
- fi
74
- else
75
- # Global scan: all sessions (fallback for no session context)
76
- for dir in "$TASKS_BASE"/*/; do
77
- [ -d "$dir" ] || continue
78
- for f in "$dir"*.json; do
79
- [ -f "$f" ] || continue
80
- status=$(python3 -c "import json,sys; d=json.load(open('$f')); print(d.get('status',''))" 2>/dev/null || echo "")
81
- if [ "$status" = "in_progress" ]; then
82
- mtime=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null || echo "0")
83
- if [ "$mtime" -gt "$BEST_MTIME" ]; then
84
- BEST_MTIME=$mtime
85
- BEST_SUBJECT=$(python3 -c "import json; d=json.load(open('$f')); print(d.get('subject',''))" 2>/dev/null || echo "")
86
- fi
87
- fi
88
- done
89
- done
90
- fi
91
-
92
- if [ -n "$BEST_SUBJECT" ]; then
93
- tmux rename-window "$(truncate_name "$BEST_SUBJECT")" 2>/dev/null || true
94
- fi
95
- exit 0
96
- fi
97
-
98
- # Mode: direct - rename to provided argument
99
- if [ -n "${1:-}" ]; then
100
- tmux rename-window "$(truncate_name "$1")" 2>/dev/null || true
101
- exit 0
102
- fi
103
-
104
- echo "Usage: tmux-task-name.sh <task-subject> | --scan | --reset" >&2
105
- exit 1