agileflow 3.0.1 → 3.1.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 (69) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +8 -8
  3. package/lib/api-server.js +3 -2
  4. package/lib/feedback.js +9 -2
  5. package/lib/flag-detection.js +4 -2
  6. package/lib/git-operations.js +4 -2
  7. package/lib/lazy-require.js +59 -0
  8. package/lib/process-executor.js +24 -9
  9. package/lib/skill-loader.js +11 -3
  10. package/package.json +1 -1
  11. package/scripts/agileflow-configure.js +12 -0
  12. package/scripts/agileflow-welcome.js +146 -90
  13. package/scripts/claude-tmux.sh +42 -6
  14. package/scripts/damage-control-multi-agent.js +14 -10
  15. package/scripts/lib/bus-utils.js +3 -1
  16. package/scripts/lib/configure-detect.js +12 -9
  17. package/scripts/lib/configure-features.js +128 -7
  18. package/scripts/lib/configure-repair.js +6 -5
  19. package/scripts/lib/context-formatter.js +13 -3
  20. package/scripts/lib/damage-control-utils.js +5 -1
  21. package/scripts/lib/lifecycle-detector.js +5 -3
  22. package/scripts/lib/process-cleanup.js +8 -4
  23. package/scripts/lib/scale-detector.js +47 -8
  24. package/scripts/lib/signal-detectors.js +117 -59
  25. package/scripts/lib/task-registry.js +5 -1
  26. package/scripts/lib/team-events.js +4 -4
  27. package/scripts/messaging-bridge.js +7 -1
  28. package/scripts/ralph-loop.js +10 -8
  29. package/scripts/smart-detect.js +32 -11
  30. package/scripts/team-manager.js +86 -1
  31. package/scripts/tmux-task-name.sh +105 -0
  32. package/scripts/tmux-task-watcher.sh +344 -0
  33. package/src/core/agents/legal-analyzer-a11y.md +110 -0
  34. package/src/core/agents/legal-analyzer-ai.md +117 -0
  35. package/src/core/agents/legal-analyzer-consumer.md +108 -0
  36. package/src/core/agents/legal-analyzer-content.md +113 -0
  37. package/src/core/agents/legal-analyzer-international.md +115 -0
  38. package/src/core/agents/legal-analyzer-licensing.md +115 -0
  39. package/src/core/agents/legal-analyzer-privacy.md +108 -0
  40. package/src/core/agents/legal-analyzer-security.md +112 -0
  41. package/src/core/agents/legal-analyzer-terms.md +111 -0
  42. package/src/core/agents/legal-consensus.md +242 -0
  43. package/src/core/agents/team-lead.md +50 -13
  44. package/src/core/commands/babysit.md +75 -42
  45. package/src/core/commands/blockers.md +7 -7
  46. package/src/core/commands/configure.md +15 -61
  47. package/src/core/commands/discovery/brief.md +363 -0
  48. package/src/core/commands/discovery/new.md +395 -0
  49. package/src/core/commands/ideate/new.md +5 -5
  50. package/src/core/commands/legal/audit.md +446 -0
  51. package/src/core/commands/logic/audit.md +5 -5
  52. package/src/core/commands/review.md +7 -1
  53. package/src/core/commands/rpi.md +61 -26
  54. package/src/core/commands/sprint.md +7 -6
  55. package/src/core/commands/team/start.md +36 -7
  56. package/src/core/commands/team/stop.md +5 -2
  57. package/src/core/templates/product-brief.md +136 -0
  58. package/tools/cli/installers/ide/claude-code.js +69 -2
  59. package/src/core/agents/configuration/archival.md +0 -350
  60. package/src/core/agents/configuration/attribution.md +0 -343
  61. package/src/core/agents/configuration/ci.md +0 -1103
  62. package/src/core/agents/configuration/damage-control.md +0 -375
  63. package/src/core/agents/configuration/git-config.md +0 -537
  64. package/src/core/agents/configuration/hooks.md +0 -623
  65. package/src/core/agents/configuration/precompact.md +0 -302
  66. package/src/core/agents/configuration/status-line.md +0 -557
  67. package/src/core/agents/configuration/verify.md +0 -618
  68. package/src/core/agents/configuration-damage-control.md +0 -259
  69. package/src/core/agents/configuration-visual-e2e.md +0 -339
@@ -55,7 +55,8 @@ const FEATURES = {
55
55
  },
56
56
  claudeflags: {
57
57
  metadataOnly: false,
58
- description: 'Default flags for Claude CLI (sets permissions.defaultMode in .claude/settings.json)',
58
+ description:
59
+ 'Default flags for Claude CLI (sets permissions.defaultMode in .claude/settings.json)',
59
60
  },
60
61
  agentteams: {
61
62
  metadataOnly: false,
@@ -459,12 +460,16 @@ function enableFeature(feature, options = {}, version) {
459
460
  : null;
460
461
  writeJSON('.claude/settings.json', settings);
461
462
  updateMetadata(
462
- { features: { [feature]: {
463
- enabled: true,
464
- version,
465
- ...(contentHash ? { contentHash } : {}),
466
- at: new Date().toISOString(),
467
- } } },
463
+ {
464
+ features: {
465
+ [feature]: {
466
+ enabled: true,
467
+ version,
468
+ ...(contentHash ? { contentHash } : {}),
469
+ at: new Date().toISOString(),
470
+ },
471
+ },
472
+ },
468
473
  version
469
474
  );
470
475
  updateGitignore();
@@ -1210,6 +1215,119 @@ function upgradeFeatures(status, version) {
1210
1215
  return upgraded > 0;
1211
1216
  }
1212
1217
 
1218
+ // ============================================================================
1219
+ // STARTUP MODE (atomic command)
1220
+ // ============================================================================
1221
+
1222
+ /**
1223
+ * Valid startup modes and their mappings
1224
+ */
1225
+ const STARTUP_MODES = {
1226
+ 'skip-permissions': {
1227
+ flags: '--dangerously-skip-permissions',
1228
+ defaultMode: 'bypassPermissions',
1229
+ description: 'Skip all permission prompts (trusted mode)',
1230
+ },
1231
+ 'accept-edits': {
1232
+ flags: '--permission-mode acceptEdits',
1233
+ defaultMode: 'acceptEdits',
1234
+ description: 'Auto-accept file edits, prompt for other actions',
1235
+ },
1236
+ normal: {
1237
+ flags: null,
1238
+ defaultMode: null,
1239
+ description: 'Standard Claude with permission prompts',
1240
+ },
1241
+ 'no-claude': {
1242
+ flags: null,
1243
+ defaultMode: null,
1244
+ description: 'Create worktree only, start Claude manually',
1245
+ },
1246
+ };
1247
+
1248
+ /**
1249
+ * Set startup mode atomically - updates BOTH metadata AND .claude/settings.json
1250
+ * This replaces the fragile two-step process of updating metadata + running --enable=claudeflags
1251
+ *
1252
+ * @param {string} mode - One of: skip-permissions, accept-edits, normal, no-claude
1253
+ * @param {string} version - Current version string
1254
+ * @returns {boolean} Success
1255
+ */
1256
+ function enableStartupMode(mode, version) {
1257
+ const modeConfig = STARTUP_MODES[mode];
1258
+ if (!modeConfig) {
1259
+ error(`Unknown startup mode: ${mode}`);
1260
+ log(` Valid modes: ${Object.keys(STARTUP_MODES).join(', ')}`, c.dim);
1261
+ return false;
1262
+ }
1263
+
1264
+ ensureDir('.claude');
1265
+ const settings = readJSON('.claude/settings.json') || {};
1266
+ settings.permissions = settings.permissions || { allow: [], deny: [], ask: [] };
1267
+
1268
+ if (mode === 'normal' || mode === 'no-claude') {
1269
+ // Remove defaultMode from settings
1270
+ if (settings.permissions.defaultMode) {
1271
+ delete settings.permissions.defaultMode;
1272
+ }
1273
+ writeJSON('.claude/settings.json', settings);
1274
+
1275
+ // Disable claudeflags + set defaultStartupMode in metadata (single write)
1276
+ updateMetadata(
1277
+ {
1278
+ features: {
1279
+ claudeFlags: {
1280
+ enabled: false,
1281
+ defaultFlags: '',
1282
+ version,
1283
+ at: new Date().toISOString(),
1284
+ },
1285
+ },
1286
+ },
1287
+ version
1288
+ );
1289
+ } else {
1290
+ // Set defaultMode in settings.json
1291
+ settings.permissions.defaultMode = modeConfig.defaultMode;
1292
+ writeJSON('.claude/settings.json', settings);
1293
+
1294
+ // Enable claudeflags + set defaultStartupMode in metadata (single write)
1295
+ updateMetadata(
1296
+ {
1297
+ features: {
1298
+ claudeFlags: {
1299
+ enabled: true,
1300
+ defaultFlags: modeConfig.flags,
1301
+ version,
1302
+ at: new Date().toISOString(),
1303
+ },
1304
+ },
1305
+ },
1306
+ version
1307
+ );
1308
+ }
1309
+
1310
+ // Set defaultStartupMode in metadata (updateMetadata already created file if missing)
1311
+ const metaPath = 'docs/00-meta/agileflow-metadata.json';
1312
+ const meta = readJSON(metaPath) || {};
1313
+ meta.defaultStartupMode = mode;
1314
+ meta.updated = new Date().toISOString();
1315
+ writeJSON(metaPath, meta);
1316
+
1317
+ success(`Default startup mode set to: ${mode}`);
1318
+ if (modeConfig.defaultMode) {
1319
+ info(`Set permissions.defaultMode = "${modeConfig.defaultMode}" in .claude/settings.json`);
1320
+ } else {
1321
+ info('Removed permissions.defaultMode from .claude/settings.json');
1322
+ }
1323
+ info(`Metadata: defaultStartupMode = "${mode}"`);
1324
+ if (mode !== 'normal') {
1325
+ info('Restart Claude Code for the new mode to take effect');
1326
+ }
1327
+
1328
+ return true;
1329
+ }
1330
+
1213
1331
  // ============================================================================
1214
1332
  // SHELL ALIASES
1215
1333
  // ============================================================================
@@ -1468,6 +1586,9 @@ module.exports = {
1468
1586
  // Helpers
1469
1587
  scriptExists,
1470
1588
  getScriptPath,
1589
+ // Startup mode
1590
+ enableStartupMode,
1591
+ STARTUP_MODES,
1471
1592
  // Shell aliases
1472
1593
  enableShellAliases,
1473
1594
  disableShellAliases,
@@ -116,11 +116,12 @@ function listScripts() {
116
116
  // Check if modified
117
117
  let isModified = false;
118
118
  if (exists && fileIndex?.files?.[`scripts/${script}`]) {
119
- isModified = tryOptional(() => {
120
- const currentHash = sha256(fs.readFileSync(scriptPath));
121
- const indexHash = fileIndex.files[`scripts/${script}`].sha256;
122
- return currentHash !== indexHash;
123
- }, 'hash check') ?? false;
119
+ isModified =
120
+ tryOptional(() => {
121
+ const currentHash = sha256(fs.readFileSync(scriptPath));
122
+ const indexHash = fileIndex.files[`scripts/${script}`].sha256;
123
+ return currentHash !== indexHash;
124
+ }, 'hash check') ?? false;
124
125
  }
125
126
 
126
127
  // Print status
@@ -323,7 +323,11 @@ function generateSummary(prefetched = null, options = {}) {
323
323
 
324
324
  // Scale indicator (EP-0033)
325
325
  let scaleDetectorSummary;
326
- try { scaleDetectorSummary = require('./scale-detector'); } catch { /* not available */ }
326
+ try {
327
+ scaleDetectorSummary = require('./scale-detector');
328
+ } catch {
329
+ /* not available */
330
+ }
327
331
  if (scaleDetectorSummary) {
328
332
  try {
329
333
  const scaleResult = scaleDetectorSummary.detectScale({
@@ -503,7 +507,11 @@ function generateFullContent(prefetched = null, options = {}) {
503
507
 
504
508
  // SCALE DETECTION (EP-0033)
505
509
  let scaleDetector;
506
- try { scaleDetector = require('./scale-detector'); } catch { /* not available */ }
510
+ try {
511
+ scaleDetector = require('./scale-detector');
512
+ } catch {
513
+ /* not available */
514
+ }
507
515
  if (scaleDetector) {
508
516
  try {
509
517
  const scaleResult = scaleDetector.detectScale({
@@ -886,7 +894,9 @@ function generateRemainingContent(prefetched, options = {}) {
886
894
 
887
895
  // Show auto-enabled modes
888
896
  const modes = auto_enabled || {};
889
- const enabledModes = Object.entries(modes).filter(([, v]) => v).map(([k]) => k.replace('_', ' '));
897
+ const enabledModes = Object.entries(modes)
898
+ .filter(([, v]) => v)
899
+ .map(([k]) => k.replace('_', ' '));
890
900
  if (enabledModes.length > 0) {
891
901
  content += `\n${C.mintGreen}Auto-enabled:${C.reset} ${enabledModes.join(', ')}\n`;
892
902
  }
@@ -96,7 +96,11 @@ function loadPatterns(projectRoot, parseYAML, defaultConfig = {}) {
96
96
  const stat = fs.statSync(fullPath);
97
97
  const mtime = stat.mtimeMs;
98
98
 
99
- if (_patternCache.filePath === fullPath && _patternCache.mtime === mtime && _patternCache.config) {
99
+ if (
100
+ _patternCache.filePath === fullPath &&
101
+ _patternCache.mtime === mtime &&
102
+ _patternCache.config
103
+ ) {
100
104
  return _patternCache.config;
101
105
  }
102
106
 
@@ -92,7 +92,9 @@ function detectLifecyclePhase(signals = {}) {
92
92
  return {
93
93
  phase: 'pre-story',
94
94
  confidence: story.id ? 0.6 : 0.9,
95
- reason: story.id ? `Story ${story.id} not yet started (status: ${story.status || 'unknown'})` : 'No active story',
95
+ reason: story.id
96
+ ? `Story ${story.id} not yet started (status: ${story.status || 'unknown'})`
97
+ : 'No active story',
96
98
  };
97
99
  }
98
100
 
@@ -107,8 +109,8 @@ function getRelevantPhases(phase) {
107
109
  // Each phase also includes adjacent phase features (for smooth transitions)
108
110
  const phaseMap = {
109
111
  'pre-story': ['pre-story'],
110
- 'planning': ['pre-story', 'planning'],
111
- 'implementation': ['planning', 'implementation'],
112
+ planning: ['pre-story', 'planning'],
113
+ implementation: ['planning', 'implementation'],
112
114
  'post-impl': ['implementation', 'post-impl'],
113
115
  'pre-pr': ['post-impl', 'pre-pr'],
114
116
  };
@@ -104,7 +104,8 @@ function getProcessStartTime(pid) {
104
104
 
105
105
  if (process.platform === 'darwin') {
106
106
  const result = executeCommandSync('ps', ['-o', 'lstart=', '-p', String(pid)], {
107
- timeout: 2000, fallback: null,
107
+ timeout: 2000,
108
+ fallback: null,
108
109
  });
109
110
  if (result.data === null) return null;
110
111
  const ts = new Date(result.data).getTime();
@@ -142,7 +143,8 @@ function getParentPid(pid) {
142
143
 
143
144
  if (process.platform === 'darwin') {
144
145
  const result = executeCommandSync('ps', ['-o', 'ppid=', '-p', String(pid)], {
145
- timeout: 2000, fallback: null,
146
+ timeout: 2000,
147
+ fallback: null,
146
148
  });
147
149
  if (result.data === null) return null;
148
150
  const ppid = parseInt(result.data, 10);
@@ -172,7 +174,8 @@ function getArgsForPid(pid) {
172
174
 
173
175
  if (process.platform === 'darwin') {
174
176
  const result = executeCommandSync('ps', ['-o', 'command=', '-p', String(pid)], {
175
- timeout: 2000, fallback: null,
177
+ timeout: 2000,
178
+ fallback: null,
176
179
  });
177
180
  if (result.data === null) return [];
178
181
  return result.data ? [result.data] : [];
@@ -292,7 +295,8 @@ function findClaudeProcesses() {
292
295
  // Get cwd via lsof (slower but works on macOS)
293
296
  let cwd = null;
294
297
  const lsofResult = executeCommandSync('lsof', ['-p', String(pid)], {
295
- timeout: 1000, fallback: null,
298
+ timeout: 1000,
299
+ fallback: null,
296
300
  });
297
301
  if (lsofResult.data) {
298
302
  const cwdLine = lsofResult.data.split('\n').find(l => l.includes('cwd'));
@@ -38,17 +38,54 @@ const SCALE_THRESHOLDS = {
38
38
 
39
39
  // Directories to exclude from file counting
40
40
  const EXCLUDE_DIRS = new Set([
41
- 'node_modules', '.git', 'dist', 'build', '.next', '.nuxt',
42
- 'coverage', '.agileflow', '.claude', '__pycache__', '.venv',
43
- 'vendor', 'target', 'out', '.cache', '.turbo', '.vercel',
41
+ 'node_modules',
42
+ '.git',
43
+ 'dist',
44
+ 'build',
45
+ '.next',
46
+ '.nuxt',
47
+ 'coverage',
48
+ '.agileflow',
49
+ '.claude',
50
+ '__pycache__',
51
+ '.venv',
52
+ 'vendor',
53
+ 'target',
54
+ 'out',
55
+ '.cache',
56
+ '.turbo',
57
+ '.vercel',
44
58
  ]);
45
59
 
46
60
  // Source file extensions to count
47
61
  const SOURCE_EXTENSIONS = new Set([
48
- '.js', '.jsx', '.ts', '.tsx', '.py', '.rb', '.go', '.rs',
49
- '.java', '.kt', '.swift', '.c', '.cpp', '.h', '.cs',
50
- '.vue', '.svelte', '.astro', '.php', '.sh', '.bash',
51
- '.css', '.scss', '.less', '.html', '.sql', '.graphql',
62
+ '.js',
63
+ '.jsx',
64
+ '.ts',
65
+ '.tsx',
66
+ '.py',
67
+ '.rb',
68
+ '.go',
69
+ '.rs',
70
+ '.java',
71
+ '.kt',
72
+ '.swift',
73
+ '.c',
74
+ '.cpp',
75
+ '.h',
76
+ '.cs',
77
+ '.vue',
78
+ '.svelte',
79
+ '.astro',
80
+ '.php',
81
+ '.sh',
82
+ '.bash',
83
+ '.css',
84
+ '.scss',
85
+ '.less',
86
+ '.html',
87
+ '.sql',
88
+ '.graphql',
52
89
  ]);
53
90
 
54
91
  /**
@@ -123,7 +160,9 @@ function countStories(statusJson, rootDir) {
123
160
  */
124
161
  function countGitCommits(rootDir) {
125
162
  const result = git(['rev-list', '--count', '--since=6 months ago', 'HEAD'], {
126
- cwd: rootDir, timeout: 5000, fallback: '0',
163
+ cwd: rootDir,
164
+ timeout: 5000,
165
+ fallback: '0',
127
166
  });
128
167
  const count = parseInt(result.data, 10);
129
168
  return isNaN(count) ? 0 : count;