agileflow 3.0.2 → 3.2.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 (116) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +58 -86
  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/feedback.js +9 -2
  14. package/lib/lazy-require.js +59 -0
  15. package/lib/logger.js +106 -0
  16. package/package.json +4 -2
  17. package/scripts/agileflow-configure.js +14 -2
  18. package/scripts/agileflow-welcome.js +450 -459
  19. package/scripts/claude-tmux.sh +113 -5
  20. package/scripts/context-loader.js +4 -9
  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 +411 -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 +90 -0
  36. package/scripts/welcome-deferred.js +437 -0
  37. package/src/core/agents/legal-analyzer-a11y.md +110 -0
  38. package/src/core/agents/legal-analyzer-ai.md +117 -0
  39. package/src/core/agents/legal-analyzer-consumer.md +108 -0
  40. package/src/core/agents/legal-analyzer-content.md +113 -0
  41. package/src/core/agents/legal-analyzer-international.md +115 -0
  42. package/src/core/agents/legal-analyzer-licensing.md +115 -0
  43. package/src/core/agents/legal-analyzer-privacy.md +108 -0
  44. package/src/core/agents/legal-analyzer-security.md +112 -0
  45. package/src/core/agents/legal-analyzer-terms.md +111 -0
  46. package/src/core/agents/legal-consensus.md +242 -0
  47. package/src/core/agents/perf-analyzer-assets.md +174 -0
  48. package/src/core/agents/perf-analyzer-bundle.md +165 -0
  49. package/src/core/agents/perf-analyzer-caching.md +160 -0
  50. package/src/core/agents/perf-analyzer-compute.md +165 -0
  51. package/src/core/agents/perf-analyzer-memory.md +182 -0
  52. package/src/core/agents/perf-analyzer-network.md +157 -0
  53. package/src/core/agents/perf-analyzer-queries.md +155 -0
  54. package/src/core/agents/perf-analyzer-rendering.md +156 -0
  55. package/src/core/agents/perf-consensus.md +280 -0
  56. package/src/core/agents/security-analyzer-api.md +199 -0
  57. package/src/core/agents/security-analyzer-auth.md +160 -0
  58. package/src/core/agents/security-analyzer-authz.md +168 -0
  59. package/src/core/agents/security-analyzer-deps.md +147 -0
  60. package/src/core/agents/security-analyzer-infra.md +176 -0
  61. package/src/core/agents/security-analyzer-injection.md +148 -0
  62. package/src/core/agents/security-analyzer-input.md +191 -0
  63. package/src/core/agents/security-analyzer-secrets.md +175 -0
  64. package/src/core/agents/security-consensus.md +276 -0
  65. package/src/core/agents/team-lead.md +50 -13
  66. package/src/core/agents/test-analyzer-assertions.md +181 -0
  67. package/src/core/agents/test-analyzer-coverage.md +183 -0
  68. package/src/core/agents/test-analyzer-fragility.md +185 -0
  69. package/src/core/agents/test-analyzer-integration.md +155 -0
  70. package/src/core/agents/test-analyzer-maintenance.md +173 -0
  71. package/src/core/agents/test-analyzer-mocking.md +178 -0
  72. package/src/core/agents/test-analyzer-patterns.md +189 -0
  73. package/src/core/agents/test-analyzer-structure.md +177 -0
  74. package/src/core/agents/test-consensus.md +294 -0
  75. package/src/core/commands/audit/legal.md +446 -0
  76. package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
  77. package/src/core/commands/audit/performance.md +443 -0
  78. package/src/core/commands/audit/security.md +443 -0
  79. package/src/core/commands/audit/test.md +442 -0
  80. package/src/core/commands/babysit.md +505 -463
  81. package/src/core/commands/configure.md +18 -33
  82. package/src/core/commands/research/ask.md +42 -9
  83. package/src/core/commands/research/import.md +14 -8
  84. package/src/core/commands/research/list.md +17 -16
  85. package/src/core/commands/research/synthesize.md +8 -8
  86. package/src/core/commands/research/view.md +28 -4
  87. package/src/core/commands/team/start.md +36 -7
  88. package/src/core/commands/team/stop.md +5 -2
  89. package/src/core/commands/whats-new.md +2 -2
  90. package/src/core/experts/devops/expertise.yaml +13 -2
  91. package/src/core/experts/documentation/expertise.yaml +26 -4
  92. package/src/core/profiles/COMPARISON.md +170 -0
  93. package/src/core/profiles/README.md +178 -0
  94. package/src/core/profiles/claude-code.yaml +111 -0
  95. package/src/core/profiles/codex.yaml +103 -0
  96. package/src/core/profiles/cursor.yaml +134 -0
  97. package/src/core/profiles/examples.js +250 -0
  98. package/src/core/profiles/loader.js +235 -0
  99. package/src/core/profiles/windsurf.yaml +159 -0
  100. package/src/core/teams/logic-audit.json +6 -0
  101. package/src/core/teams/perf-audit.json +71 -0
  102. package/src/core/teams/security-audit.json +71 -0
  103. package/src/core/teams/test-audit.json +71 -0
  104. package/src/core/templates/command-prerequisites.yaml +169 -0
  105. package/src/core/templates/damage-control-patterns.yaml +9 -0
  106. package/tools/cli/installers/ide/_base-ide.js +33 -3
  107. package/tools/cli/installers/ide/claude-code.js +2 -67
  108. package/tools/cli/installers/ide/codex.js +9 -9
  109. package/tools/cli/installers/ide/cursor.js +165 -4
  110. package/tools/cli/installers/ide/windsurf.js +237 -6
  111. package/tools/cli/lib/content-transformer.js +234 -9
  112. package/tools/cli/lib/docs-setup.js +1 -1
  113. package/tools/cli/lib/ide-generator.js +357 -0
  114. package/tools/cli/lib/ide-registry.js +2 -2
  115. package/scripts/tmux-task-name.sh +0 -75
  116. package/scripts/tmux-task-watcher.sh +0 -177
@@ -682,6 +682,7 @@ async function prefetchAllData(options = {}) {
682
682
  sessionClaims: true,
683
683
  fileOverlaps: true,
684
684
  };
685
+ const verbosityMode = options.verbosityMode || 'full';
685
686
 
686
687
  // Define all files to read
687
688
  const jsonFiles = {
@@ -690,20 +691,31 @@ async function prefetchAllData(options = {}) {
690
691
  sessionState: 'docs/09-agents/session-state.json',
691
692
  };
692
693
 
693
- const textFiles = {
694
- busLog: 'docs/09-agents/bus/log.jsonl',
695
- claudeMd: 'CLAUDE.md',
696
- readmeMd: 'README.md',
697
- archReadme: 'docs/04-architecture/README.md',
698
- practicesReadme: 'docs/02-practices/README.md',
699
- roadmap: 'docs/08-project/roadmap.md',
700
- };
701
-
702
- const directories = {
703
- docs: 'docs',
704
- research: 'docs/10-research',
705
- epics: 'docs/05-epics',
706
- };
694
+ // Text files: skip heavy content in lite/minimal modes
695
+ const textFiles = {};
696
+ if (verbosityMode === 'full') {
697
+ textFiles.busLog = 'docs/09-agents/bus/log.jsonl';
698
+ textFiles.claudeMd = 'CLAUDE.md';
699
+ textFiles.readmeMd = 'README.md';
700
+ textFiles.archReadme = 'docs/04-architecture/README.md';
701
+ textFiles.practicesReadme = 'docs/02-practices/README.md';
702
+ textFiles.roadmap = 'docs/08-project/roadmap.md';
703
+ } else if (verbosityMode === 'lite') {
704
+ textFiles.busLog = 'docs/09-agents/bus/log.jsonl';
705
+ // Skip: claudeMd, readmeMd, archReadme, practicesReadme, roadmap
706
+ }
707
+ // minimal: skip all text files
708
+
709
+ // Directories: skip in minimal mode
710
+ const directories = {};
711
+ if (verbosityMode !== 'minimal') {
712
+ directories.docs = 'docs';
713
+ directories.research = 'docs/10-research';
714
+ directories.epics = 'docs/05-epics';
715
+ } else {
716
+ // minimal still needs epics dir for count
717
+ directories.epics = 'docs/05-epics';
718
+ }
707
719
 
708
720
  // Git commands to run in parallel
709
721
  const gitCommands = {
@@ -750,7 +762,7 @@ async function prefetchAllData(options = {}) {
750
762
  const git = Object.fromEntries(gitResults);
751
763
 
752
764
  // Determine most recent research file
753
- const researchFiles = dirs.research
765
+ const researchFiles = (dirs.research || [])
754
766
  .filter(f => f.endsWith('.md') && f !== 'README.md')
755
767
  .sort()
756
768
  .reverse();
@@ -393,6 +393,7 @@ function parseBashPatterns(content) {
393
393
  bashToolPatterns: 'patterns',
394
394
  askPatterns: 'patterns',
395
395
  agileflowProtections: 'patterns',
396
+ releaseProtections: 'patterns',
396
397
  });
397
398
  }
398
399
 
@@ -483,7 +484,12 @@ function createPathHook(operation) {
483
484
  function createBashHook() {
484
485
  return function runHook() {
485
486
  const projectRoot = findProjectRoot();
486
- const defaultConfig = { bashToolPatterns: [], askPatterns: [], agileflowProtections: [] };
487
+ const defaultConfig = {
488
+ bashToolPatterns: [],
489
+ askPatterns: [],
490
+ agileflowProtections: [],
491
+ releaseProtections: [],
492
+ };
487
493
 
488
494
  /**
489
495
  * Test command against a single pattern rule
@@ -514,6 +520,7 @@ function createBashHook() {
514
520
  const allPatterns = [
515
521
  ...(config.bashToolPatterns || []),
516
522
  ...(config.agileflowProtections || []),
523
+ ...(config.releaseProtections || []),
517
524
  ];
518
525
 
519
526
  for (const rule of allPatterns) {
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * feature-catalog.js
4
+ *
5
+ * Static catalog of all major AgileFlow features with descriptions,
6
+ * usage hints, and dynamic status computation.
7
+ *
8
+ * Solves the "invisible features" problem: when smart-detect detectors
9
+ * don't trigger, features vanish from the AI's context. This catalog
10
+ * ensures the AI always knows what's available.
11
+ *
12
+ * Usage:
13
+ * const { FEATURE_CATALOG, buildCatalogWithStatus } = require('./feature-catalog');
14
+ * const catalog = buildCatalogWithStatus(signals, recommendations, autoEnabled, metadata);
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ // =============================================================================
20
+ // Static Feature Catalog
21
+ // =============================================================================
22
+
23
+ /**
24
+ * All major AgileFlow features organized by category.
25
+ *
26
+ * Fields:
27
+ * feature - Unique key (matches detector/command names)
28
+ * name - Human-readable display name
29
+ * description - What it does (1 sentence)
30
+ * how_to_use - Command or trigger hint
31
+ * category - Grouping: modes | collaboration | workflow | analysis | automation
32
+ * detector - Name of signal detector that triggers this (null if none)
33
+ * auto_mode - Key in auto_enabled that maps to this feature (null if none)
34
+ * prerequisites - Lightweight prereq checks as { signal_path, description } or null
35
+ */
36
+ const FEATURE_CATALOG = [
37
+ // --- modes ---
38
+ {
39
+ feature: 'loop-mode',
40
+ name: 'Loop Mode',
41
+ description: 'Auto-claim and implement multiple stories in sequence without pausing',
42
+ how_to_use: '3+ ready stories in same epic + test setup',
43
+ category: 'modes',
44
+ detector: null,
45
+ auto_mode: 'loop_mode',
46
+ prerequisites: [
47
+ { signal_path: 'story.epic', description: 'Active story with epic' },
48
+ { signal_path: 'tests.hasTestSetup', description: 'Test setup configured' },
49
+ ],
50
+ },
51
+ {
52
+ feature: 'coverage-mode',
53
+ name: 'Coverage Mode',
54
+ description: 'Track and enforce code coverage thresholds during implementation',
55
+ how_to_use: 'Coverage data in coverage/coverage-summary.json',
56
+ category: 'modes',
57
+ detector: null,
58
+ auto_mode: 'coverage_mode',
59
+ prerequisites: [{ signal_path: 'files.coverage', description: 'Coverage summary exists' }],
60
+ },
61
+
62
+ // --- collaboration ---
63
+ {
64
+ feature: 'agent-teams',
65
+ name: 'Agent Teams',
66
+ description: 'Coordinate multiple specialized agents working in parallel on complex tasks',
67
+ how_to_use: '/agileflow:team:start <template>',
68
+ category: 'collaboration',
69
+ detector: null,
70
+ auto_mode: null,
71
+ prerequisites: null,
72
+ },
73
+ {
74
+ feature: 'council',
75
+ name: 'AI Council',
76
+ description: 'Three-perspective decision making: Optimist, Advocate, and Analyst',
77
+ how_to_use: '/agileflow:council "<decision>"',
78
+ category: 'collaboration',
79
+ detector: null,
80
+ auto_mode: null,
81
+ prerequisites: null,
82
+ },
83
+ {
84
+ feature: 'multi-expert',
85
+ name: 'Multi-Expert',
86
+ description: 'Deploy 3-5 domain experts on the same problem and synthesize results',
87
+ how_to_use: '/agileflow:multi-expert "<question>"',
88
+ category: 'collaboration',
89
+ detector: null,
90
+ auto_mode: null,
91
+ prerequisites: null,
92
+ },
93
+ {
94
+ feature: 'sessions',
95
+ name: 'Parallel Sessions',
96
+ description: 'Run multiple Claude instances in git worktrees for parallel development',
97
+ how_to_use: '/agileflow:session:new <name>',
98
+ category: 'collaboration',
99
+ detector: null,
100
+ auto_mode: null,
101
+ prerequisites: null,
102
+ },
103
+
104
+ // --- workflow ---
105
+ {
106
+ feature: 'rpi',
107
+ name: 'RPI Workflow',
108
+ description: 'Structured Research-Plan-Implement cycle with phase gates',
109
+ how_to_use: '/agileflow:rpi "<feature>"',
110
+ category: 'workflow',
111
+ detector: null,
112
+ auto_mode: null,
113
+ prerequisites: null,
114
+ },
115
+ {
116
+ feature: 'discovery',
117
+ name: 'Discovery',
118
+ description: 'Brainstorm, research, and synthesize findings into a Product Brief',
119
+ how_to_use: '/agileflow:discovery:new "<topic>"',
120
+ category: 'workflow',
121
+ detector: null,
122
+ auto_mode: null,
123
+ prerequisites: null,
124
+ },
125
+ {
126
+ feature: 'batch',
127
+ name: 'Batch Processing',
128
+ description: 'Process multiple items with map/pmap/filter patterns',
129
+ how_to_use: '/agileflow:batch <pattern> <items>',
130
+ category: 'workflow',
131
+ detector: null,
132
+ auto_mode: null,
133
+ prerequisites: null,
134
+ },
135
+ {
136
+ feature: 'sprint',
137
+ name: 'Sprint Planning',
138
+ description: 'Data-driven sprint planning with velocity forecasting',
139
+ how_to_use: '/agileflow:sprint',
140
+ category: 'workflow',
141
+ detector: null,
142
+ auto_mode: null,
143
+ prerequisites: [{ signal_path: 'storyCount', description: 'Stories exist in status.json' }],
144
+ },
145
+
146
+ // --- analysis ---
147
+ {
148
+ feature: 'research',
149
+ name: 'Research',
150
+ description: 'Generate research prompts, save notes, and maintain a research index',
151
+ how_to_use: '/agileflow:research:ask "<question>"',
152
+ category: 'analysis',
153
+ detector: null,
154
+ auto_mode: null,
155
+ prerequisites: null,
156
+ },
157
+ {
158
+ feature: 'impact-analysis',
159
+ name: 'Impact Analysis',
160
+ description: 'Analyze change impact across the codebase before making modifications',
161
+ how_to_use: '/agileflow:impact "<change>"',
162
+ category: 'analysis',
163
+ detector: 'impact',
164
+ auto_mode: null,
165
+ prerequisites: null,
166
+ },
167
+ {
168
+ feature: 'logic-audit',
169
+ name: 'Logic Audit',
170
+ description: 'Multi-agent analysis for edge cases, race conditions, type bugs, and dead code',
171
+ how_to_use: '/agileflow:audit:logic',
172
+ category: 'analysis',
173
+ detector: null,
174
+ auto_mode: null,
175
+ prerequisites: null,
176
+ },
177
+ {
178
+ feature: 'diagnose',
179
+ name: 'Diagnose',
180
+ description: 'System health diagnostics for hooks, config, and runtime issues',
181
+ how_to_use: '/agileflow:diagnose',
182
+ category: 'analysis',
183
+ detector: null,
184
+ auto_mode: null,
185
+ prerequisites: null,
186
+ },
187
+
188
+ // --- testing ---
189
+ {
190
+ feature: 'browser-qa',
191
+ name: 'UI Testing (Bowser)',
192
+ description:
193
+ 'Agentic browser testing + visual verification during development. Playwright-based screenshot evidence and workflow validation.',
194
+ how_to_use: '/agileflow:browser-qa SCENARIO=<spec.yaml> or VISUAL=true on babysit',
195
+ category: 'testing',
196
+ detector: null,
197
+ auto_mode: 'browser_qa_mode',
198
+ prerequisites: [{ signal_path: 'files.playwright', description: 'Playwright config exists' }],
199
+ },
200
+
201
+ // --- automation ---
202
+ {
203
+ feature: 'ideation',
204
+ name: 'Ideation',
205
+ description: 'Generate categorized improvement ideas using multi-expert analysis',
206
+ how_to_use: '/agileflow:ideate:new',
207
+ category: 'automation',
208
+ detector: null,
209
+ auto_mode: null,
210
+ prerequisites: null,
211
+ },
212
+ ];
213
+
214
+ // Valid categories for validation
215
+ const VALID_CATEGORIES = [
216
+ 'modes',
217
+ 'collaboration',
218
+ 'workflow',
219
+ 'analysis',
220
+ 'testing',
221
+ 'automation',
222
+ ];
223
+
224
+ // Valid statuses
225
+ const VALID_STATUSES = ['triggered', 'available', 'unavailable', 'disabled'];
226
+
227
+ // =============================================================================
228
+ // Status Computation
229
+ // =============================================================================
230
+
231
+ /**
232
+ * Resolve a dot-path signal (e.g. 'story.epic') against the signals object.
233
+ * Returns the value at that path, or undefined if any segment is missing.
234
+ */
235
+ function resolveSignalPath(signals, dotPath) {
236
+ const parts = dotPath.split('.');
237
+ let current = signals;
238
+ for (const part of parts) {
239
+ if (current == null || typeof current !== 'object') return undefined;
240
+ current = current[part];
241
+ }
242
+ return current;
243
+ }
244
+
245
+ /**
246
+ * Check if all prerequisites are met for a feature.
247
+ * Returns true if prerequisites is null (no requirements) or all paths are truthy.
248
+ */
249
+ function checkPrerequisites(signals, prerequisites) {
250
+ if (!prerequisites || prerequisites.length === 0) return true;
251
+ return prerequisites.every(prereq => {
252
+ const value = resolveSignalPath(signals, prereq.signal_path);
253
+ return !!value;
254
+ });
255
+ }
256
+
257
+ /**
258
+ * Build the feature catalog with dynamic status for each entry.
259
+ *
260
+ * Status logic:
261
+ * 1. If feature is in metadata.smart_detect.disabled_features -> 'disabled'
262
+ * 2. If feature's auto_mode key is truthy in autoEnabled -> 'triggered'
263
+ * 3. If feature's detector triggered (in recommendations) -> 'triggered'
264
+ * 4. If prerequisites all met -> 'available'
265
+ * 5. Otherwise -> 'unavailable'
266
+ *
267
+ * @param {Object} signals - Extracted signals from smart-detect
268
+ * @param {{ immediate: Object[], available: Object[] }} recommendations - Filtered recommendations
269
+ * @param {Object} autoEnabled - Auto-enabled mode flags (loop_mode, visual_mode, etc.)
270
+ * @param {Object} metadata - AgileFlow metadata (for disabled_features)
271
+ * @returns {Object[]} Catalog entries with 'status' field added
272
+ */
273
+ function buildCatalogWithStatus(signals, recommendations, autoEnabled, metadata) {
274
+ const disabledFeatures = new Set(metadata?.smart_detect?.disabled_features || []);
275
+
276
+ // Collect all triggered feature names from recommendations
277
+ const triggeredFeatures = new Set();
278
+ const allRecs = [...(recommendations?.immediate || []), ...(recommendations?.available || [])];
279
+ for (const rec of allRecs) {
280
+ triggeredFeatures.add(rec.feature);
281
+ }
282
+
283
+ // Collect auto-enabled mode keys that are truthy
284
+ const autoEnabledKeys = new Set();
285
+ if (autoEnabled) {
286
+ for (const [key, value] of Object.entries(autoEnabled)) {
287
+ if (value) autoEnabledKeys.add(key);
288
+ }
289
+ }
290
+
291
+ return FEATURE_CATALOG.map(entry => {
292
+ let status;
293
+
294
+ if (disabledFeatures.has(entry.feature)) {
295
+ status = 'disabled';
296
+ } else if (entry.auto_mode && autoEnabledKeys.has(entry.auto_mode)) {
297
+ status = 'triggered';
298
+ } else if (
299
+ triggeredFeatures.has(entry.feature) ||
300
+ (entry.detector && triggeredFeatures.has(entry.detector))
301
+ ) {
302
+ status = 'triggered';
303
+ } else if (checkPrerequisites(signals, entry.prerequisites)) {
304
+ status = 'available';
305
+ } else {
306
+ status = 'unavailable';
307
+ }
308
+
309
+ return { ...entry, status };
310
+ });
311
+ }
312
+
313
+ module.exports = {
314
+ FEATURE_CATALOG,
315
+ VALID_CATEGORIES,
316
+ VALID_STATUSES,
317
+ buildCatalogWithStatus,
318
+ // Exported for testing
319
+ resolveSignalPath,
320
+ checkPrerequisites,
321
+ };
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * portable-tasks-cli.js - CLI wrapper for portable task tracking
5
+ *
6
+ * Usage:
7
+ * node portable-tasks-cli.js list [--status=pending] [--owner=AG-API] [--json]
8
+ * node portable-tasks-cli.js add --subject="..." [--description="..."] [--owner=...] [--status=pending]
9
+ * node portable-tasks-cli.js update T-001 --status=completed
10
+ * node portable-tasks-cli.js get T-001
11
+ * node portable-tasks-cli.js delete T-001
12
+ *
13
+ * Exit codes:
14
+ * 0 = Success
15
+ * 1 = Error
16
+ */
17
+
18
+ const path = require('path');
19
+ const {
20
+ loadTasks,
21
+ addTask,
22
+ updateTask,
23
+ deleteTask,
24
+ getTask,
25
+ listTasks,
26
+ } = require('./portable-tasks');
27
+
28
+ // Get project directory (current working directory)
29
+ const projectDir = process.cwd();
30
+
31
+ /**
32
+ * Parse command-line arguments into command and options
33
+ * @returns {Object} { command, taskId, options }
34
+ */
35
+ function parseArgs() {
36
+ const args = process.argv.slice(2);
37
+ if (args.length === 0) {
38
+ return { command: 'help' };
39
+ }
40
+
41
+ const command = args[0];
42
+ const taskId = args[1] && !args[1].startsWith('--') ? args[1] : null;
43
+
44
+ const options = {};
45
+ for (let i = taskId ? 2 : 1; i < args.length; i++) {
46
+ const arg = args[i];
47
+ if (arg.startsWith('--')) {
48
+ const eqIndex = arg.indexOf('=');
49
+ const key = eqIndex === -1 ? arg.substring(2) : arg.substring(2, eqIndex);
50
+ const value = eqIndex === -1 ? undefined : arg.substring(eqIndex + 1);
51
+ options[key] = value === undefined ? true : value;
52
+ }
53
+ }
54
+
55
+ return { command, taskId, options };
56
+ }
57
+
58
+ /**
59
+ * Format task for human-readable output
60
+ */
61
+ function formatTaskHuman(task) {
62
+ let output = `${task.id} [${task.status}] ${task.title}`;
63
+ if (task.owner) output += ` (${task.owner})`;
64
+ if (task.blockedBy) output += ` [blocked by ${task.blockedBy}]`;
65
+ return output;
66
+ }
67
+
68
+ /**
69
+ * Run a command
70
+ */
71
+ function run() {
72
+ const { command, taskId, options } = parseArgs();
73
+
74
+ switch (command) {
75
+ case 'list':
76
+ case 'ls': {
77
+ const tasks = listTasks(projectDir, {
78
+ status: options.status,
79
+ owner: options.owner,
80
+ includeCompleted:
81
+ options['include-completed'] === true || options['include-completed'] === 'true',
82
+ });
83
+
84
+ if (options.json) {
85
+ console.log(JSON.stringify(tasks, null, 2));
86
+ } else {
87
+ if (tasks.length === 0) {
88
+ console.log('No tasks found');
89
+ } else {
90
+ tasks.forEach(task => console.log(formatTaskHuman(task)));
91
+ }
92
+ }
93
+ process.exit(0);
94
+ break;
95
+ }
96
+
97
+ case 'add': {
98
+ if (!options.subject) {
99
+ console.error('[ERROR] --subject is required');
100
+ process.exit(1);
101
+ }
102
+
103
+ const result = addTask(projectDir, {
104
+ subject: options.subject,
105
+ description: options.description,
106
+ owner: options.owner,
107
+ status: options.status || 'pending',
108
+ story: options.story,
109
+ blockedBy: options['blocked-by'],
110
+ });
111
+
112
+ if (result.ok) {
113
+ console.log(`Created: ${result.taskId}`);
114
+ process.exit(0);
115
+ } else {
116
+ console.error(`[ERROR] ${result.error}`);
117
+ process.exit(1);
118
+ }
119
+ break;
120
+ }
121
+
122
+ case 'update': {
123
+ if (!taskId) {
124
+ console.error('[ERROR] Task ID is required (e.g., T-001)');
125
+ process.exit(1);
126
+ }
127
+
128
+ const task = getTask(projectDir, taskId);
129
+ if (!task) {
130
+ console.error(`[ERROR] Task ${taskId} not found`);
131
+ process.exit(1);
132
+ }
133
+
134
+ const updates = {};
135
+ if (options.status && typeof options.status === 'string') updates.status = options.status;
136
+ if (options.description !== undefined && typeof options.description === 'string')
137
+ updates.description = options.description;
138
+ if (options.owner !== undefined && typeof options.owner === 'string')
139
+ updates.owner = options.owner;
140
+ if (options.title !== undefined && typeof options.title === 'string')
141
+ updates.title = options.title;
142
+ if (options.story !== undefined && typeof options.story === 'string')
143
+ updates.story = options.story;
144
+ if (options['blocked-by'] !== undefined && typeof options['blocked-by'] === 'string')
145
+ updates.blockedBy = options['blocked-by'];
146
+
147
+ if (Object.keys(updates).length === 0) {
148
+ console.error('[ERROR] No updates specified');
149
+ process.exit(1);
150
+ }
151
+
152
+ const result = updateTask(projectDir, taskId, updates);
153
+ if (result.ok) {
154
+ console.log(`Updated: ${taskId}`);
155
+ process.exit(0);
156
+ } else {
157
+ console.error(`[ERROR] ${result.error}`);
158
+ process.exit(1);
159
+ }
160
+ break;
161
+ }
162
+
163
+ case 'get': {
164
+ if (!taskId) {
165
+ console.error('[ERROR] Task ID is required (e.g., T-001)');
166
+ process.exit(1);
167
+ }
168
+
169
+ const task = getTask(projectDir, taskId);
170
+ if (!task) {
171
+ console.error(`[ERROR] Task ${taskId} not found`);
172
+ process.exit(1);
173
+ }
174
+
175
+ if (options.json) {
176
+ console.log(JSON.stringify(task, null, 2));
177
+ } else {
178
+ console.log(formatTaskHuman(task));
179
+ if (task.description) console.log(` Description: ${task.description}`);
180
+ if (task.story) console.log(` Story: ${task.story}`);
181
+ if (task.created) console.log(` Created: ${task.created}`);
182
+ if (task.completed) console.log(` Completed: ${task.completed}`);
183
+ }
184
+ process.exit(0);
185
+ break;
186
+ }
187
+
188
+ case 'delete':
189
+ case 'rm': {
190
+ if (!taskId) {
191
+ console.error('[ERROR] Task ID is required (e.g., T-001)');
192
+ process.exit(1);
193
+ }
194
+
195
+ const task = getTask(projectDir, taskId);
196
+ if (!task) {
197
+ console.error(`[ERROR] Task ${taskId} not found`);
198
+ process.exit(1);
199
+ }
200
+
201
+ const result = deleteTask(projectDir, taskId);
202
+ if (result.ok) {
203
+ console.log(`Deleted: ${taskId}`);
204
+ process.exit(0);
205
+ } else {
206
+ console.error(`[ERROR] ${result.error}`);
207
+ process.exit(1);
208
+ }
209
+ break;
210
+ }
211
+
212
+ case 'help':
213
+ case '-h':
214
+ case '--help': {
215
+ console.log(`
216
+ Portable Task Tracking - File-based task management for all IDEs
217
+
218
+ Usage:
219
+ portable-tasks-cli.js list [OPTIONS]
220
+ portable-tasks-cli.js add --subject="..." [OPTIONS]
221
+ portable-tasks-cli.js update <TASK_ID> --status=... [OPTIONS]
222
+ portable-tasks-cli.js get <TASK_ID>
223
+ portable-tasks-cli.js delete <TASK_ID>
224
+
225
+ Commands:
226
+ list (ls) List tasks (default: active only)
227
+ add Create a new task
228
+ update Update an existing task
229
+ get Show task details
230
+ delete (rm) Delete a task
231
+ help Show this message
232
+
233
+ List Options:
234
+ --status=<status> Filter by status (pending, in_progress, completed, blocked)
235
+ --owner=<owner> Filter by owner (e.g., AG-API, AG-UI)
236
+ --include-completed Include completed tasks (default: false)
237
+ --json Output as JSON
238
+
239
+ Add Options:
240
+ --subject=<text> Task title (required)
241
+ --description=<text> Task description
242
+ --owner=<owner> Task owner (e.g., AG-API)
243
+ --status=<status> Initial status (default: pending)
244
+ --story=<story-id> Link to story (e.g., US-0040)
245
+ --blocked-by=<task-id> Block this task (e.g., T-001)
246
+
247
+ Update Options:
248
+ --status=<status> New status
249
+ --description=<text> New description
250
+ --owner=<owner> New owner
251
+ --title=<text> New title
252
+ --story=<story-id> Update story link
253
+ --blocked-by=<task-id> Update blocker
254
+
255
+ Examples:
256
+ node portable-tasks-cli.js list
257
+ node portable-tasks-cli.js list --status=pending --owner=AG-API
258
+ node portable-tasks-cli.js add --subject="Write tests" --owner=AG-CI
259
+ node portable-tasks-cli.js update T-001 --status=completed
260
+ node portable-tasks-cli.js get T-001 --json
261
+ `);
262
+ process.exit(0);
263
+ break;
264
+ }
265
+
266
+ default: {
267
+ console.error(`[ERROR] Unknown command: ${command}`);
268
+ console.log('Run with --help for usage');
269
+ process.exit(1);
270
+ }
271
+ }
272
+ }
273
+
274
+ run();