agileflow 3.2.1 → 3.4.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 +6 -6
- package/lib/feature-flags.js +32 -4
- package/lib/skill-loader.js +0 -1
- package/package.json +1 -1
- package/scripts/agileflow-statusline.sh +81 -0
- package/scripts/babysit-clear-restore.js +154 -0
- package/scripts/claude-tmux.sh +120 -24
- package/scripts/claude-watchdog.sh +225 -0
- package/scripts/generators/agent-registry.js +14 -1
- package/scripts/generators/inject-babysit.js +22 -9
- package/scripts/generators/inject-help.js +19 -9
- package/scripts/lib/README-portable-tasks.md +424 -0
- package/scripts/lib/audit-cleanup.js +250 -0
- package/scripts/lib/audit-registry.js +248 -0
- package/scripts/lib/configure-detect.js +20 -0
- package/scripts/lib/feature-catalog.js +13 -2
- package/scripts/lib/gate-enforcer.js +295 -0
- package/scripts/lib/model-profiles.js +98 -0
- package/scripts/lib/signal-detectors.js +1 -1
- package/scripts/lib/skill-catalog.js +557 -0
- package/scripts/lib/skill-recommender.js +311 -0
- package/scripts/lib/tdd-phase-manager.js +455 -0
- package/scripts/lib/team-events.js +76 -8
- package/scripts/lib/tmux-group-colors.js +113 -0
- package/scripts/messaging-bridge.js +209 -1
- package/scripts/spawn-audit-sessions.js +549 -0
- package/scripts/team-manager.js +37 -16
- package/scripts/tmux-close-windows.sh +180 -0
- package/scripts/tmux-restore-window.sh +67 -0
- package/scripts/tmux-save-closed-window.sh +35 -0
- package/src/core/agents/ads-audit-budget.md +181 -0
- package/src/core/agents/ads-audit-compliance.md +169 -0
- package/src/core/agents/ads-audit-creative.md +164 -0
- package/src/core/agents/ads-audit-google.md +226 -0
- package/src/core/agents/ads-audit-meta.md +183 -0
- package/src/core/agents/ads-audit-tracking.md +197 -0
- package/src/core/agents/ads-consensus.md +322 -0
- package/src/core/agents/brainstorm-analyzer-features.md +169 -0
- package/src/core/agents/brainstorm-analyzer-growth.md +161 -0
- package/src/core/agents/brainstorm-analyzer-integration.md +172 -0
- package/src/core/agents/brainstorm-analyzer-market.md +147 -0
- package/src/core/agents/brainstorm-analyzer-ux.md +167 -0
- package/src/core/agents/brainstorm-consensus.md +237 -0
- package/src/core/agents/completeness-analyzer-api.md +190 -0
- package/src/core/agents/completeness-analyzer-conditional.md +201 -0
- package/src/core/agents/completeness-analyzer-handlers.md +159 -0
- package/src/core/agents/completeness-analyzer-imports.md +159 -0
- package/src/core/agents/completeness-analyzer-routes.md +182 -0
- package/src/core/agents/completeness-analyzer-state.md +188 -0
- package/src/core/agents/completeness-analyzer-stubs.md +198 -0
- package/src/core/agents/completeness-consensus.md +286 -0
- package/src/core/agents/perf-consensus.md +2 -2
- package/src/core/agents/security-consensus.md +2 -2
- package/src/core/agents/seo-analyzer-content.md +167 -0
- package/src/core/agents/seo-analyzer-images.md +187 -0
- package/src/core/agents/seo-analyzer-performance.md +206 -0
- package/src/core/agents/seo-analyzer-schema.md +176 -0
- package/src/core/agents/seo-analyzer-sitemap.md +172 -0
- package/src/core/agents/seo-analyzer-technical.md +144 -0
- package/src/core/agents/seo-consensus.md +289 -0
- package/src/core/agents/test-consensus.md +2 -2
- package/src/core/commands/ads/audit.md +375 -0
- package/src/core/commands/ads/budget.md +97 -0
- package/src/core/commands/ads/competitor.md +112 -0
- package/src/core/commands/ads/creative.md +85 -0
- package/src/core/commands/ads/google.md +112 -0
- package/src/core/commands/ads/landing.md +119 -0
- package/src/core/commands/ads/linkedin.md +112 -0
- package/src/core/commands/ads/meta.md +91 -0
- package/src/core/commands/ads/microsoft.md +115 -0
- package/src/core/commands/ads/plan.md +321 -0
- package/src/core/commands/ads/tiktok.md +129 -0
- package/src/core/commands/ads/youtube.md +124 -0
- package/src/core/commands/ads.md +128 -0
- package/src/core/commands/babysit.md +250 -1344
- package/src/core/commands/code/completeness.md +466 -0
- package/src/core/commands/{audit → code}/legal.md +26 -16
- package/src/core/commands/{audit → code}/logic.md +27 -16
- package/src/core/commands/{audit → code}/performance.md +30 -20
- package/src/core/commands/{audit → code}/security.md +32 -19
- package/src/core/commands/{audit → code}/test.md +30 -20
- package/src/core/commands/{discovery → ideate}/brief.md +12 -12
- package/src/core/commands/{discovery/new.md → ideate/discover.md} +13 -13
- package/src/core/commands/ideate/features.md +435 -0
- package/src/core/commands/seo/audit.md +373 -0
- package/src/core/commands/seo/competitor.md +174 -0
- package/src/core/commands/seo/content.md +107 -0
- package/src/core/commands/seo/geo.md +229 -0
- package/src/core/commands/seo/hreflang.md +140 -0
- package/src/core/commands/seo/images.md +96 -0
- package/src/core/commands/seo/page.md +198 -0
- package/src/core/commands/seo/plan.md +163 -0
- package/src/core/commands/seo/programmatic.md +131 -0
- package/src/core/commands/seo/references/cwv-thresholds.md +64 -0
- package/src/core/commands/seo/references/eeat-framework.md +110 -0
- package/src/core/commands/seo/references/quality-gates.md +91 -0
- package/src/core/commands/seo/references/schema-types.md +102 -0
- package/src/core/commands/seo/schema.md +183 -0
- package/src/core/commands/seo/sitemap.md +97 -0
- package/src/core/commands/seo/technical.md +100 -0
- package/src/core/commands/seo.md +107 -0
- package/src/core/commands/skill/list.md +68 -212
- package/src/core/commands/skill/recommend.md +216 -0
- package/src/core/commands/tdd-next.md +238 -0
- package/src/core/commands/tdd.md +210 -0
- package/src/core/experts/_core-expertise.yaml +105 -0
- package/src/core/experts/analytics/expertise.yaml +5 -99
- package/src/core/experts/codebase-query/expertise.yaml +3 -72
- package/src/core/experts/compliance/expertise.yaml +6 -72
- package/src/core/experts/database/expertise.yaml +9 -52
- package/src/core/experts/documentation/expertise.yaml +7 -140
- package/src/core/experts/integrations/expertise.yaml +7 -127
- package/src/core/experts/mentor/expertise.yaml +8 -35
- package/src/core/experts/monitoring/expertise.yaml +7 -49
- package/src/core/experts/performance/expertise.yaml +1 -26
- package/src/core/experts/security/expertise.yaml +9 -34
- package/src/core/experts/ui/expertise.yaml +6 -36
- package/src/core/knowledge/ads/ad-audit-checklist-scoring.md +424 -0
- package/src/core/knowledge/ads/ad-optimization-logic.md +590 -0
- package/src/core/knowledge/ads/ad-technical-specifications.md +385 -0
- package/src/core/knowledge/ads/definitive-advertising-reference-2026.md +506 -0
- package/src/core/knowledge/ads/paid-advertising-research-2026.md +445 -0
- package/src/core/templates/agileflow-metadata.json +15 -1
- package/tools/cli/installers/ide/_base-ide.js +42 -5
- package/tools/cli/installers/ide/claude-code.js +13 -4
- package/tools/cli/lib/content-injector.js +160 -12
- package/tools/cli/lib/docs-setup.js +1 -1
- package/src/core/commands/skill/create.md +0 -698
- package/src/core/commands/skill/delete.md +0 -316
- package/src/core/commands/skill/edit.md +0 -359
- package/src/core/commands/skill/test.md +0 -394
- package/src/core/commands/skill/upgrade.md +0 -552
- package/src/core/templates/skill-template.md +0 -117
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# Portable Task Tracking System
|
|
2
|
+
|
|
3
|
+
A file-based, IDE-agnostic task tracking system for AgileFlow projects. Works with Claude Code, Cursor, Windsurf, Codex, and any IDE that can read/write files.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Unlike Claude Code's native `TaskCreate`/`TaskUpdate` tools (which only work in Claude Code), this system stores tasks in a simple markdown file (`.agileflow/tasks.md`) that ANY IDE's AI can read and modify.
|
|
8
|
+
|
|
9
|
+
**Key Features:**
|
|
10
|
+
- Pure markdown format - human-readable, git-friendly
|
|
11
|
+
- No dependencies beyond Node.js built-ins
|
|
12
|
+
- Works across all IDEs
|
|
13
|
+
- Supports filtering, custom fields, and blocking relationships
|
|
14
|
+
- Round-trip stable (parse → modify → format → parse)
|
|
15
|
+
|
|
16
|
+
## Files
|
|
17
|
+
|
|
18
|
+
- **`portable-tasks.js`** - Core module (functions for CRUD operations)
|
|
19
|
+
- **`portable-tasks-cli.js`** - Command-line interface (callable from any IDE)
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Copy both files to your project's `.agileflow/scripts/lib/` directory during setup. They are published with the agileflow npm package.
|
|
24
|
+
|
|
25
|
+
## File Format
|
|
26
|
+
|
|
27
|
+
Tasks are stored in `.agileflow/tasks.md`:
|
|
28
|
+
|
|
29
|
+
```markdown
|
|
30
|
+
# AgileFlow Tasks
|
|
31
|
+
|
|
32
|
+
> Auto-managed task list. Edit carefully - format matters.
|
|
33
|
+
> Last updated: 2026-02-20T15:30:00Z
|
|
34
|
+
|
|
35
|
+
## Active Tasks
|
|
36
|
+
|
|
37
|
+
### T-001: Implement user authentication [in_progress]
|
|
38
|
+
- **Owner**: AG-API
|
|
39
|
+
- **Created**: 2026-02-20
|
|
40
|
+
- **Story**: US-0042
|
|
41
|
+
- **Description**: Add JWT-based auth to /api/login endpoint
|
|
42
|
+
|
|
43
|
+
### T-002: Write auth tests [pending]
|
|
44
|
+
- **Owner**: AG-CI
|
|
45
|
+
- **Created**: 2026-02-20
|
|
46
|
+
- **Story**: US-0042
|
|
47
|
+
- **Blocked by**: T-001
|
|
48
|
+
- **Description**: Unit and integration tests for auth flow
|
|
49
|
+
|
|
50
|
+
## Completed Tasks
|
|
51
|
+
|
|
52
|
+
### T-003: Setup database schema [completed]
|
|
53
|
+
- **Owner**: AG-DEVOPS
|
|
54
|
+
- **Created**: 2026-02-19
|
|
55
|
+
- **Completed**: 2026-02-20
|
|
56
|
+
- **Story**: US-0041
|
|
57
|
+
- **Description**: Create users and sessions tables
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API Reference
|
|
61
|
+
|
|
62
|
+
### Module: `portable-tasks.js`
|
|
63
|
+
|
|
64
|
+
#### `loadTasks(projectDir)`
|
|
65
|
+
Load tasks from `.agileflow/tasks.md`.
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
const { loadTasks } = require('./portable-tasks');
|
|
69
|
+
const { activeTasks, completedTasks } = loadTasks(process.cwd());
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Returns: `{ activeTasks: [], completedTasks: [] }`
|
|
73
|
+
|
|
74
|
+
#### `saveTasks(projectDir, tasksData)`
|
|
75
|
+
Save tasks back to markdown file.
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
saveTasks(process.cwd(), { activeTasks, completedTasks });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Returns: `boolean` (success/failure)
|
|
82
|
+
|
|
83
|
+
#### `addTask(projectDir, taskData)`
|
|
84
|
+
Create a new task.
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
const result = addTask(process.cwd(), {
|
|
88
|
+
subject: 'Write API tests',
|
|
89
|
+
description: 'Integration tests for new endpoints',
|
|
90
|
+
owner: 'AG-CI',
|
|
91
|
+
status: 'pending', // pending, in_progress, completed, blocked
|
|
92
|
+
story: 'US-0040',
|
|
93
|
+
blockedBy: 'T-001', // optional
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// result: { ok: true, taskId: 'T-001' } or { ok: false, error: 'message' }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### `updateTask(projectDir, taskId, updates)`
|
|
100
|
+
Update an existing task.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
updateTask(process.cwd(), 'T-001', {
|
|
104
|
+
status: 'in_progress',
|
|
105
|
+
description: 'Updated description',
|
|
106
|
+
owner: 'AG-DEVOPS',
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Returns: `{ ok: boolean, error?: string }`
|
|
111
|
+
|
|
112
|
+
Moves task between active/completed sections automatically when status changes.
|
|
113
|
+
|
|
114
|
+
#### `deleteTask(projectDir, taskId)`
|
|
115
|
+
Remove a task.
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
deleteTask(process.cwd(), 'T-001');
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Returns: `{ ok: boolean, error?: string }`
|
|
122
|
+
|
|
123
|
+
#### `getTask(projectDir, taskId)`
|
|
124
|
+
Retrieve a single task by ID.
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
const task = getTask(process.cwd(), 'T-001');
|
|
128
|
+
// Returns task object or null if not found
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### `listTasks(projectDir, filters?)`
|
|
132
|
+
List tasks with optional filtering.
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
// All active tasks (default)
|
|
136
|
+
const tasks = listTasks(process.cwd());
|
|
137
|
+
|
|
138
|
+
// Filter by status
|
|
139
|
+
listTasks(process.cwd(), { status: 'pending' });
|
|
140
|
+
listTasks(process.cwd(), { status: ['pending', 'blocked'] });
|
|
141
|
+
|
|
142
|
+
// Filter by owner
|
|
143
|
+
listTasks(process.cwd(), { owner: 'AG-API' });
|
|
144
|
+
|
|
145
|
+
// Include completed tasks
|
|
146
|
+
listTasks(process.cwd(), { includeCompleted: true });
|
|
147
|
+
|
|
148
|
+
// Combine filters
|
|
149
|
+
listTasks(process.cwd(), {
|
|
150
|
+
status: 'pending',
|
|
151
|
+
owner: 'AG-API',
|
|
152
|
+
includeCompleted: true,
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Returns: `Array` of task objects
|
|
157
|
+
|
|
158
|
+
#### `getNextId(tasks)`
|
|
159
|
+
Generate the next sequential task ID.
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
const nextId = getNextId(allTasks); // 'T-001', 'T-002', etc.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Parsing Functions
|
|
166
|
+
- `parseTasksFile(content)` - Parse markdown content into task objects
|
|
167
|
+
- `formatTasksFile(activeTasks, completedTasks)` - Generate markdown from tasks
|
|
168
|
+
|
|
169
|
+
## CLI: `portable-tasks-cli.js`
|
|
170
|
+
|
|
171
|
+
Command-line interface for task management (works in any IDE's terminal).
|
|
172
|
+
|
|
173
|
+
### Usage
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
node portable-tasks-cli.js [command] [options]
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Commands
|
|
180
|
+
|
|
181
|
+
#### List Tasks
|
|
182
|
+
```bash
|
|
183
|
+
# List active tasks
|
|
184
|
+
node portable-tasks-cli.js list
|
|
185
|
+
|
|
186
|
+
# Filter by status
|
|
187
|
+
node portable-tasks-cli.js list --status=pending
|
|
188
|
+
node portable-tasks-cli.js list --status=in_progress
|
|
189
|
+
|
|
190
|
+
# Filter by owner
|
|
191
|
+
node portable-tasks-cli.js list --owner=AG-API
|
|
192
|
+
|
|
193
|
+
# Include completed tasks
|
|
194
|
+
node portable-tasks-cli.js list --include-completed
|
|
195
|
+
|
|
196
|
+
# Output as JSON
|
|
197
|
+
node portable-tasks-cli.js list --json
|
|
198
|
+
|
|
199
|
+
# Combine filters
|
|
200
|
+
node portable-tasks-cli.js list --status=pending --owner=AG-CI
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### Add Task
|
|
204
|
+
```bash
|
|
205
|
+
# Minimal (subject required)
|
|
206
|
+
node portable-tasks-cli.js add --subject="Fix login bug"
|
|
207
|
+
|
|
208
|
+
# Full details
|
|
209
|
+
node portable-tasks-cli.js add \
|
|
210
|
+
--subject="Implement auth" \
|
|
211
|
+
--description="Add JWT tokens to API" \
|
|
212
|
+
--owner=AG-API \
|
|
213
|
+
--status=in_progress \
|
|
214
|
+
--story=US-0040 \
|
|
215
|
+
--blocked-by=T-001
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Update Task
|
|
219
|
+
```bash
|
|
220
|
+
# Change status
|
|
221
|
+
node portable-tasks-cli.js update T-001 --status=completed
|
|
222
|
+
|
|
223
|
+
# Update multiple fields
|
|
224
|
+
node portable-tasks-cli.js update T-001 \
|
|
225
|
+
--status=in_progress \
|
|
226
|
+
--owner=AG-API \
|
|
227
|
+
--description="Started implementation"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### Get Task
|
|
231
|
+
```bash
|
|
232
|
+
# Display task
|
|
233
|
+
node portable-tasks-cli.js get T-001
|
|
234
|
+
|
|
235
|
+
# Output as JSON
|
|
236
|
+
node portable-tasks-cli.js get T-001 --json
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### Delete Task
|
|
240
|
+
```bash
|
|
241
|
+
node portable-tasks-cli.js delete T-001
|
|
242
|
+
node portable-tasks-cli.js rm T-001 # alias
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Output Formats
|
|
246
|
+
|
|
247
|
+
**Human-readable (default):**
|
|
248
|
+
```
|
|
249
|
+
T-001 [in_progress] Implement user auth (AG-API) [blocked by T-002]
|
|
250
|
+
T-002 [pending] Write tests (AG-CI)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**JSON output:**
|
|
254
|
+
```json
|
|
255
|
+
[
|
|
256
|
+
{
|
|
257
|
+
"id": "T-001",
|
|
258
|
+
"title": "Implement user auth",
|
|
259
|
+
"status": "in_progress",
|
|
260
|
+
"owner": "AG-API",
|
|
261
|
+
"created": "2026-02-20",
|
|
262
|
+
"completed": null,
|
|
263
|
+
"story": "US-0040",
|
|
264
|
+
"blockedBy": "T-002",
|
|
265
|
+
"description": "Add JWT tokens to /api/login endpoint"
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Exit Codes
|
|
271
|
+
- **0**: Success
|
|
272
|
+
- **1**: Error (invalid command, missing task, etc.)
|
|
273
|
+
|
|
274
|
+
## Task Fields
|
|
275
|
+
|
|
276
|
+
Every task has these fields:
|
|
277
|
+
|
|
278
|
+
| Field | Type | Required | Description |
|
|
279
|
+
|-------|------|----------|-------------|
|
|
280
|
+
| `id` | string | Yes | Auto-generated ID (T-001, T-002, etc.) |
|
|
281
|
+
| `title` | string | Yes | Task summary |
|
|
282
|
+
| `status` | string | Yes | One of: `pending`, `in_progress`, `completed`, `blocked` |
|
|
283
|
+
| `owner` | string | No | Responsible agent/team (e.g., AG-API) |
|
|
284
|
+
| `created` | date | No | Creation date (YYYY-MM-DD) |
|
|
285
|
+
| `completed` | date | No | Completion date (auto-set when status → completed) |
|
|
286
|
+
| `story` | string | No | Link to parent story (e.g., US-0040) |
|
|
287
|
+
| `blockedBy` | string | No | Task ID blocking this task (e.g., T-001) |
|
|
288
|
+
| `description` | string | No | Detailed task description |
|
|
289
|
+
|
|
290
|
+
## Examples
|
|
291
|
+
|
|
292
|
+
### Python Script Using Module
|
|
293
|
+
```python
|
|
294
|
+
import subprocess
|
|
295
|
+
import json
|
|
296
|
+
|
|
297
|
+
# List all pending tasks
|
|
298
|
+
result = subprocess.run(
|
|
299
|
+
['node', '.agileflow/scripts/lib/portable-tasks-cli.js', 'list', '--status=pending', '--json'],
|
|
300
|
+
capture_output=True,
|
|
301
|
+
text=True
|
|
302
|
+
)
|
|
303
|
+
tasks = json.loads(result.stdout)
|
|
304
|
+
for task in tasks:
|
|
305
|
+
print(f"{task['id']}: {task['title']} ({task['owner']})")
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### JavaScript Using Module
|
|
309
|
+
```javascript
|
|
310
|
+
const { addTask, listTasks, updateTask } = require('./.agileflow/scripts/lib/portable-tasks');
|
|
311
|
+
|
|
312
|
+
// Create task
|
|
313
|
+
const result = addTask(process.cwd(), {
|
|
314
|
+
subject: 'Run integration tests',
|
|
315
|
+
owner: 'AG-CI',
|
|
316
|
+
status: 'pending'
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// List tasks for an owner
|
|
320
|
+
const tasks = listTasks(process.cwd(), { owner: 'AG-CI' });
|
|
321
|
+
|
|
322
|
+
// Update task
|
|
323
|
+
updateTask(process.cwd(), result.taskId, { status: 'in_progress' });
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Bash Script
|
|
327
|
+
```bash
|
|
328
|
+
#!/bin/bash
|
|
329
|
+
|
|
330
|
+
# Add task
|
|
331
|
+
node .agileflow/scripts/lib/portable-tasks-cli.js add \
|
|
332
|
+
--subject="Deploy to staging" \
|
|
333
|
+
--owner=AG-DEVOPS
|
|
334
|
+
|
|
335
|
+
# Get all pending tasks for AG-API
|
|
336
|
+
node .agileflow/scripts/lib/portable-tasks-cli.js list \
|
|
337
|
+
--status=pending \
|
|
338
|
+
--owner=AG-API
|
|
339
|
+
|
|
340
|
+
# Mark task complete
|
|
341
|
+
node .agileflow/scripts/lib/portable-tasks-cli.js update T-001 \
|
|
342
|
+
--status=completed
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Status Values
|
|
346
|
+
|
|
347
|
+
- **`pending`** - Task not started
|
|
348
|
+
- **`in_progress`** - Task actively being worked on
|
|
349
|
+
- **`blocked`** - Task blocked by another (see `blockedBy` field)
|
|
350
|
+
- **`completed`** - Task finished
|
|
351
|
+
|
|
352
|
+
## Markdown Format Details
|
|
353
|
+
|
|
354
|
+
The markdown format is strict for correct parsing:
|
|
355
|
+
|
|
356
|
+
**Correct:**
|
|
357
|
+
```markdown
|
|
358
|
+
### T-001: Task title [status]
|
|
359
|
+
- **Owner**: Value
|
|
360
|
+
- **Blocked by**: T-002
|
|
361
|
+
- **Description**: Description text
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Incorrect (will not parse):**
|
|
365
|
+
```markdown
|
|
366
|
+
### T-001: Task title [status]
|
|
367
|
+
- Owner: Value # Missing **bold** markers
|
|
368
|
+
- **Blocked By**: T-002 # Wrong case (must be "Blocked by")
|
|
369
|
+
- **Description**: Description text
|
|
370
|
+
# Extra blank lines between fields are ok
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Rules:**
|
|
374
|
+
- Task header: `### T-NNN: Title [status]`
|
|
375
|
+
- Field format: `- **Field name**: Value`
|
|
376
|
+
- Field names are case-insensitive (will be normalized)
|
|
377
|
+
- Extra blank lines, comments, headers are ignored
|
|
378
|
+
- Tasks can have any fields beyond the standard ones
|
|
379
|
+
|
|
380
|
+
## Thread Safety
|
|
381
|
+
|
|
382
|
+
The system uses atomic writes (write to temp file, then rename) to prevent corruption if multiple processes write simultaneously. However, reads are NOT locked, so concurrent reads during writes may see partial data. For production use with high concurrency, implement file locking via `fcntl` or `flock`.
|
|
383
|
+
|
|
384
|
+
## Testing
|
|
385
|
+
|
|
386
|
+
Run the comprehensive test suite:
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
cd packages/cli
|
|
390
|
+
npm test -- __tests__/scripts/lib/portable-tasks.test.js --no-coverage
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Coverage:
|
|
394
|
+
- 47 tests across all functions
|
|
395
|
+
- Parsing, formatting, CRUD operations
|
|
396
|
+
- Round-trip integrity (parse → modify → format → parse)
|
|
397
|
+
- Error handling and edge cases
|
|
398
|
+
- Integration workflows
|
|
399
|
+
|
|
400
|
+
## Design Decisions
|
|
401
|
+
|
|
402
|
+
1. **Markdown format** - Human-readable, diff-friendly, git-compatible (vs JSON/YAML)
|
|
403
|
+
2. **Status-driven sections** - Completed/active sections based on task status, not location
|
|
404
|
+
3. **Auto-ID generation** - Sequential IDs (T-001) simplify references
|
|
405
|
+
4. **No dependencies** - Only Node.js built-ins to minimize install footprint
|
|
406
|
+
5. **Lazy parsing** - Parse on load, format on save (vs streaming)
|
|
407
|
+
6. **Fail-safe writes** - Atomic writes prevent corruption on crashes
|
|
408
|
+
|
|
409
|
+
## Limitations
|
|
410
|
+
|
|
411
|
+
- No encryption (store sensitive data elsewhere)
|
|
412
|
+
- No versioning/history (file is current state only)
|
|
413
|
+
- No concurrent write locking (implement if needed)
|
|
414
|
+
- No real-time sync (changes require reload)
|
|
415
|
+
- Task IDs are reassigned if you manually edit the file
|
|
416
|
+
|
|
417
|
+
## Future Enhancements
|
|
418
|
+
|
|
419
|
+
- [ ] Watch mode (monitor file for external changes)
|
|
420
|
+
- [ ] Priority levels (high/medium/low)
|
|
421
|
+
- [ ] Time tracking (estimated/actual hours)
|
|
422
|
+
- [ ] Comments/notes per task
|
|
423
|
+
- [ ] Subtasks (nested hierarchy)
|
|
424
|
+
- [ ] Recurring tasks
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* audit-cleanup.js - Orphan cleanup for ULTRADEEP audit sessions
|
|
3
|
+
*
|
|
4
|
+
* Cleans up abandoned tmux sessions and incomplete sentinel directories
|
|
5
|
+
* from ULTRADEEP audit runs. Designed to be called from Stop hooks or
|
|
6
|
+
* manually for maintenance.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const { cleanupOrphanSessions } = require('./audit-cleanup');
|
|
10
|
+
* cleanupOrphanSessions(rootDir);
|
|
11
|
+
*
|
|
12
|
+
* CLI:
|
|
13
|
+
* node scripts/lib/audit-cleanup.js [--max-age=60] [--dry-run]
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { execFileSync } = require('child_process');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const MAX_AGE_MINUTES = 60;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Find all ultradeep trace directories.
|
|
24
|
+
* @param {string} rootDir - Project root
|
|
25
|
+
* @returns {Array<{ traceId: string, dir: string, status: object|null }>}
|
|
26
|
+
*/
|
|
27
|
+
function findTraceDirectories(rootDir) {
|
|
28
|
+
const ultradeepDir = path.join(rootDir, 'docs', '09-agents', 'ultradeep');
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(ultradeepDir)) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const traces = [];
|
|
35
|
+
try {
|
|
36
|
+
const entries = fs.readdirSync(ultradeepDir, { withFileTypes: true });
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (!entry.isDirectory()) continue;
|
|
39
|
+
|
|
40
|
+
const traceDir = path.join(ultradeepDir, entry.name);
|
|
41
|
+
const statusFile = path.join(traceDir, '_status.json');
|
|
42
|
+
let status = null;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
if (fs.existsSync(statusFile)) {
|
|
46
|
+
status = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
|
|
47
|
+
}
|
|
48
|
+
} catch (_) {
|
|
49
|
+
// Corrupt status file
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
traces.push({
|
|
53
|
+
traceId: entry.name,
|
|
54
|
+
dir: traceDir,
|
|
55
|
+
status,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
} catch (_) {
|
|
59
|
+
// Directory read failure
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return traces;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if a trace is stale (older than maxAge).
|
|
67
|
+
* @param {object} trace - Trace info from findTraceDirectories
|
|
68
|
+
* @param {number} maxAgeMinutes - Maximum age in minutes
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
function isStaleTrace(trace, maxAgeMinutes) {
|
|
72
|
+
if (!trace.status || !trace.status.started_at) {
|
|
73
|
+
// No status = assume stale
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const startedAt = new Date(trace.status.started_at).getTime();
|
|
78
|
+
if (isNaN(startedAt)) return true; // Invalid date = treat as stale
|
|
79
|
+
const age = Date.now() - startedAt;
|
|
80
|
+
return age > maxAgeMinutes * 60 * 1000;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if a trace is incomplete (not all analyzers have findings).
|
|
85
|
+
* @param {object} trace - Trace info from findTraceDirectories
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
function isIncompleteTrace(trace) {
|
|
89
|
+
if (!trace.status || !trace.status.analyzers) return true;
|
|
90
|
+
|
|
91
|
+
const expected = trace.status.analyzers;
|
|
92
|
+
const completed = trace.status.completed || [];
|
|
93
|
+
|
|
94
|
+
return completed.length < expected.length;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Find orphaned tmux sessions matching audit pattern.
|
|
99
|
+
* @returns {string[]} Array of session names
|
|
100
|
+
*/
|
|
101
|
+
function findOrphanedTmuxSessions() {
|
|
102
|
+
try {
|
|
103
|
+
const output = execFileSync('tmux', ['list-sessions', '-F', '#{session_name}'], {
|
|
104
|
+
encoding: 'utf8',
|
|
105
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
106
|
+
}).trim();
|
|
107
|
+
|
|
108
|
+
if (!output) return [];
|
|
109
|
+
|
|
110
|
+
return output.split('\n').filter(name => name.startsWith('audit-'));
|
|
111
|
+
} catch (_) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Kill a tmux session by name.
|
|
118
|
+
* @param {string} sessionName - Session name to kill
|
|
119
|
+
* @returns {boolean} True if killed successfully
|
|
120
|
+
*/
|
|
121
|
+
function killTmuxSession(sessionName) {
|
|
122
|
+
try {
|
|
123
|
+
execFileSync('tmux', ['kill-session', '-t', sessionName], {
|
|
124
|
+
stdio: 'pipe',
|
|
125
|
+
});
|
|
126
|
+
return true;
|
|
127
|
+
} catch (_) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Remove a sentinel directory.
|
|
134
|
+
* @param {string} dir - Directory to remove
|
|
135
|
+
* @returns {boolean} True if removed successfully
|
|
136
|
+
*/
|
|
137
|
+
function removeSentinelDir(dir) {
|
|
138
|
+
try {
|
|
139
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
140
|
+
return true;
|
|
141
|
+
} catch (_) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clean up orphaned ULTRADEEP audit sessions and stale sentinel dirs.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} rootDir - Project root directory
|
|
150
|
+
* @param {object} [options] - Options
|
|
151
|
+
* @param {number} [options.maxAgeMinutes] - Max age for stale traces (default: 60)
|
|
152
|
+
* @param {boolean} [options.dryRun] - If true, report but don't delete
|
|
153
|
+
* @returns {{ sessionsKilled: string[], tracesRemoved: string[], errors: string[] }}
|
|
154
|
+
*/
|
|
155
|
+
function cleanupOrphanSessions(rootDir, options) {
|
|
156
|
+
const maxAge = (options && options.maxAgeMinutes) || MAX_AGE_MINUTES;
|
|
157
|
+
const dryRun = (options && options.dryRun) || false;
|
|
158
|
+
|
|
159
|
+
const result = {
|
|
160
|
+
sessionsKilled: [],
|
|
161
|
+
tracesRemoved: [],
|
|
162
|
+
errors: [],
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// 1. Find and kill orphaned tmux sessions
|
|
166
|
+
const orphanedSessions = findOrphanedTmuxSessions();
|
|
167
|
+
for (const session of orphanedSessions) {
|
|
168
|
+
// Extract trace ID from session name: audit-{type}-{traceId}
|
|
169
|
+
const parts = session.split('-');
|
|
170
|
+
if (parts.length < 3) continue; // Malformed session name, skip
|
|
171
|
+
const traceId = parts.slice(2).join('-');
|
|
172
|
+
|
|
173
|
+
if (dryRun) {
|
|
174
|
+
result.sessionsKilled.push(`${session} (dry-run)`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (killTmuxSession(session)) {
|
|
179
|
+
result.sessionsKilled.push(session);
|
|
180
|
+
} else {
|
|
181
|
+
result.errors.push(`Failed to kill session: ${session}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 2. Clean up stale sentinel directories
|
|
186
|
+
const traces = findTraceDirectories(rootDir);
|
|
187
|
+
for (const trace of traces) {
|
|
188
|
+
if (isStaleTrace(trace, maxAge) && isIncompleteTrace(trace)) {
|
|
189
|
+
if (dryRun) {
|
|
190
|
+
result.tracesRemoved.push(`${trace.traceId} (dry-run)`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (removeSentinelDir(trace.dir)) {
|
|
195
|
+
result.tracesRemoved.push(trace.traceId);
|
|
196
|
+
} else {
|
|
197
|
+
result.errors.push(`Failed to remove trace dir: ${trace.traceId}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// CLI
|
|
206
|
+
if (require.main === module) {
|
|
207
|
+
const args = process.argv.slice(2);
|
|
208
|
+
let maxAge = MAX_AGE_MINUTES;
|
|
209
|
+
let dryRun = false;
|
|
210
|
+
|
|
211
|
+
for (const arg of args) {
|
|
212
|
+
if (arg.startsWith('--max-age=')) {
|
|
213
|
+
const parsed = parseInt(arg.split('=')[1], 10);
|
|
214
|
+
maxAge = isNaN(parsed) ? MAX_AGE_MINUTES : parsed;
|
|
215
|
+
}
|
|
216
|
+
if (arg === '--dry-run') dryRun = true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const rootDir = process.cwd();
|
|
220
|
+
const result = cleanupOrphanSessions(rootDir, { maxAgeMinutes: maxAge, dryRun });
|
|
221
|
+
|
|
222
|
+
if (result.sessionsKilled.length > 0) {
|
|
223
|
+
console.log(`Killed ${result.sessionsKilled.length} orphaned session(s):`);
|
|
224
|
+
result.sessionsKilled.forEach(s => console.log(` - ${s}`));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (result.tracesRemoved.length > 0) {
|
|
228
|
+
console.log(`Removed ${result.tracesRemoved.length} stale trace(s):`);
|
|
229
|
+
result.tracesRemoved.forEach(t => console.log(` - ${t}`));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (result.errors.length > 0) {
|
|
233
|
+
console.error(`${result.errors.length} error(s):`);
|
|
234
|
+
result.errors.forEach(e => console.error(` - ${e}`));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (result.sessionsKilled.length === 0 && result.tracesRemoved.length === 0) {
|
|
238
|
+
console.log('No orphaned sessions or stale traces found.');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = {
|
|
243
|
+
findTraceDirectories,
|
|
244
|
+
isStaleTrace,
|
|
245
|
+
isIncompleteTrace,
|
|
246
|
+
findOrphanedTmuxSessions,
|
|
247
|
+
killTmuxSession,
|
|
248
|
+
removeSentinelDir,
|
|
249
|
+
cleanupOrphanSessions,
|
|
250
|
+
};
|