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.
- package/CHANGELOG.md +10 -0
- package/README.md +58 -86
- package/lib/dashboard-automations.js +130 -0
- package/lib/dashboard-git.js +254 -0
- package/lib/dashboard-inbox.js +64 -0
- package/lib/dashboard-protocol.js +1 -0
- package/lib/dashboard-server.js +114 -924
- package/lib/dashboard-session.js +136 -0
- package/lib/dashboard-status.js +72 -0
- package/lib/dashboard-terminal.js +354 -0
- package/lib/dashboard-websocket.js +88 -0
- package/lib/drivers/codex-driver.ts +4 -4
- package/lib/feedback.js +9 -2
- package/lib/lazy-require.js +59 -0
- package/lib/logger.js +106 -0
- package/package.json +4 -2
- package/scripts/agileflow-configure.js +14 -2
- package/scripts/agileflow-welcome.js +450 -459
- package/scripts/claude-tmux.sh +113 -5
- package/scripts/context-loader.js +4 -9
- package/scripts/lib/command-prereqs.js +280 -0
- package/scripts/lib/configure-detect.js +92 -2
- package/scripts/lib/configure-features.js +411 -1
- package/scripts/lib/context-formatter.js +468 -233
- package/scripts/lib/context-loader.js +27 -15
- package/scripts/lib/damage-control-utils.js +8 -1
- package/scripts/lib/feature-catalog.js +321 -0
- package/scripts/lib/portable-tasks-cli.js +274 -0
- package/scripts/lib/portable-tasks.js +479 -0
- package/scripts/lib/signal-detectors.js +1 -1
- package/scripts/lib/team-events.js +86 -1
- package/scripts/obtain-context.js +28 -4
- package/scripts/smart-detect.js +17 -0
- package/scripts/strip-ai-attribution.js +63 -0
- package/scripts/team-manager.js +90 -0
- package/scripts/welcome-deferred.js +437 -0
- package/src/core/agents/legal-analyzer-a11y.md +110 -0
- package/src/core/agents/legal-analyzer-ai.md +117 -0
- package/src/core/agents/legal-analyzer-consumer.md +108 -0
- package/src/core/agents/legal-analyzer-content.md +113 -0
- package/src/core/agents/legal-analyzer-international.md +115 -0
- package/src/core/agents/legal-analyzer-licensing.md +115 -0
- package/src/core/agents/legal-analyzer-privacy.md +108 -0
- package/src/core/agents/legal-analyzer-security.md +112 -0
- package/src/core/agents/legal-analyzer-terms.md +111 -0
- package/src/core/agents/legal-consensus.md +242 -0
- package/src/core/agents/perf-analyzer-assets.md +174 -0
- package/src/core/agents/perf-analyzer-bundle.md +165 -0
- package/src/core/agents/perf-analyzer-caching.md +160 -0
- package/src/core/agents/perf-analyzer-compute.md +165 -0
- package/src/core/agents/perf-analyzer-memory.md +182 -0
- package/src/core/agents/perf-analyzer-network.md +157 -0
- package/src/core/agents/perf-analyzer-queries.md +155 -0
- package/src/core/agents/perf-analyzer-rendering.md +156 -0
- package/src/core/agents/perf-consensus.md +280 -0
- package/src/core/agents/security-analyzer-api.md +199 -0
- package/src/core/agents/security-analyzer-auth.md +160 -0
- package/src/core/agents/security-analyzer-authz.md +168 -0
- package/src/core/agents/security-analyzer-deps.md +147 -0
- package/src/core/agents/security-analyzer-infra.md +176 -0
- package/src/core/agents/security-analyzer-injection.md +148 -0
- package/src/core/agents/security-analyzer-input.md +191 -0
- package/src/core/agents/security-analyzer-secrets.md +175 -0
- package/src/core/agents/security-consensus.md +276 -0
- package/src/core/agents/team-lead.md +50 -13
- package/src/core/agents/test-analyzer-assertions.md +181 -0
- package/src/core/agents/test-analyzer-coverage.md +183 -0
- package/src/core/agents/test-analyzer-fragility.md +185 -0
- package/src/core/agents/test-analyzer-integration.md +155 -0
- package/src/core/agents/test-analyzer-maintenance.md +173 -0
- package/src/core/agents/test-analyzer-mocking.md +178 -0
- package/src/core/agents/test-analyzer-patterns.md +189 -0
- package/src/core/agents/test-analyzer-structure.md +177 -0
- package/src/core/agents/test-consensus.md +294 -0
- package/src/core/commands/audit/legal.md +446 -0
- package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
- package/src/core/commands/audit/performance.md +443 -0
- package/src/core/commands/audit/security.md +443 -0
- package/src/core/commands/audit/test.md +442 -0
- package/src/core/commands/babysit.md +505 -463
- package/src/core/commands/configure.md +18 -33
- package/src/core/commands/research/ask.md +42 -9
- package/src/core/commands/research/import.md +14 -8
- package/src/core/commands/research/list.md +17 -16
- package/src/core/commands/research/synthesize.md +8 -8
- package/src/core/commands/research/view.md +28 -4
- package/src/core/commands/team/start.md +36 -7
- package/src/core/commands/team/stop.md +5 -2
- package/src/core/commands/whats-new.md +2 -2
- package/src/core/experts/devops/expertise.yaml +13 -2
- package/src/core/experts/documentation/expertise.yaml +26 -4
- package/src/core/profiles/COMPARISON.md +170 -0
- package/src/core/profiles/README.md +178 -0
- package/src/core/profiles/claude-code.yaml +111 -0
- package/src/core/profiles/codex.yaml +103 -0
- package/src/core/profiles/cursor.yaml +134 -0
- package/src/core/profiles/examples.js +250 -0
- package/src/core/profiles/loader.js +235 -0
- package/src/core/profiles/windsurf.yaml +159 -0
- package/src/core/teams/logic-audit.json +6 -0
- package/src/core/teams/perf-audit.json +71 -0
- package/src/core/teams/security-audit.json +71 -0
- package/src/core/teams/test-audit.json +71 -0
- package/src/core/templates/command-prerequisites.yaml +169 -0
- package/src/core/templates/damage-control-patterns.yaml +9 -0
- package/tools/cli/installers/ide/_base-ide.js +33 -3
- package/tools/cli/installers/ide/claude-code.js +2 -67
- package/tools/cli/installers/ide/codex.js +9 -9
- package/tools/cli/installers/ide/cursor.js +165 -4
- package/tools/cli/installers/ide/windsurf.js +237 -6
- package/tools/cli/lib/content-transformer.js +234 -9
- package/tools/cli/lib/docs-setup.js +1 -1
- package/tools/cli/lib/ide-generator.js +357 -0
- package/tools/cli/lib/ide-registry.js +2 -2
- package/scripts/tmux-task-name.sh +0 -75
- 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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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 = {
|
|
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();
|