|
1 | 1 | schema_version = 1 |
2 | 2 |
|
3 | 3 | [filters.hook_subagent] |
4 | | -description = "Compact subagent-context hook — extract minimal task list from JSON blob" |
| 4 | +description = "Compact subagent-context hook — extract minimal id(state) per task, drop titles and metadata" |
5 | 5 | match_command = "^flowctl\\s+hook\\s+subagent-context" |
6 | 6 | replace = [ |
7 | | - # Strip outer wrapper "Active flow-code tasks: " and the JSON structural cruft. |
8 | | - # Full line: Active flow-code tasks: {"tasks":[{...},{...}],"count":N,"success":true} |
9 | | - # First, drop the prefix |
10 | | - { pattern = "^Active flow-code tasks:\\s+", replacement = "Active: " }, |
11 | | - # Strip "success":true trailer and "count" field |
12 | | - { pattern = ",?\"success\":true", replacement = "" }, |
13 | | - { pattern = ",?\"count\":\\d+", replacement = "" }, |
14 | | - # Strip outer {"tasks":[...]} wrapper to just [...] |
15 | | - # Patterns for before/after variants since separate removal rules above left trailing , vs none |
16 | | - { pattern = "\\{\"tasks\":", replacement = "" }, |
17 | | - # Strip trailing "}" at end of line (the wrapper close) |
18 | | - { pattern = "\\}$", replacement = "" }, |
19 | | - # Strip epic prefix from task IDs inside JSON: "fn-19-slug.1" → ".1" |
20 | | - { pattern = "\"id\":\"fn-\\d+(?:-[a-zA-Z0-9-]+)?(\\.\\d+)\"", replacement = "\"id\":\"$1\"" }, |
21 | | - # Strip epic field (redundant with id) |
22 | | - { pattern = ",?\"epic\":\"[^\"]*\"", replacement = "" }, |
23 | | - # Strip priority/assignee if null |
24 | | - { pattern = ",?\"priority\":null", replacement = "" }, |
25 | | - { pattern = ",?\"assignee\":null", replacement = "" }, |
26 | | - { pattern = ",?\"claim_note\":\"\"", replacement = "" }, |
27 | | - { pattern = ",?\"claimed_at\":null", replacement = "" }, |
28 | | - # Drop empty depends_on/files arrays |
29 | | - { pattern = ",?\"depends_on\":\\[\\]", replacement = "" }, |
30 | | - { pattern = ",?\"files\":\\[\\]", replacement = "" }, |
31 | | - { pattern = ",?\"impl\":null", replacement = "" }, |
32 | | - { pattern = ",?\"review\":null", replacement = "" }, |
33 | | - { pattern = ",?\"sync\":null", replacement = "" }, |
34 | | - # Clean up any leading comma from key deletions |
35 | | - { pattern = "\\{,", replacement = "{" }, |
| 7 | + # 1. Rewrite prefix: "Active flow-code tasks: " → "ctx: " |
| 8 | + { pattern = "^Active flow-code tasks:\\s+", replacement = "ctx: " }, |
| 9 | + |
| 10 | + # 2. Shorten task IDs in any field: "fn-16-long-slug.3" → "fn-16.3" |
| 11 | + { pattern = "\"(fn-\\d+)-[a-zA-Z0-9-]+(\\.\\d+)\"", replacement = "\"$1$2\"" }, |
| 12 | + # 3. Shorten bare epic IDs (no task suffix): "fn-16-long-slug" → "fn-16" |
| 13 | + { pattern = "\"(fn-\\d+)-[a-zA-Z0-9-]+\"", replacement = "\"$1\"" }, |
| 14 | + |
| 15 | + # 4. Compress status values |
| 16 | + { pattern = "\"status\":\"in_progress\"", replacement = "\"status\":\"wip\"" }, |
| 17 | + { pattern = "\"status\":\"blocked\"", replacement = "\"status\":\"blk\"" }, |
| 18 | + |
| 19 | + # 5. Collapse each task object to "fn-N.X(state)" form by matching the full |
| 20 | + # object and extracting just id+status (field order is alphabetic: depends_on, |
| 21 | + # domain, epic, id, priority, status, title). |
| 22 | + { pattern = "\\{[^{}]*\"id\":\"(fn-\\d+\\.\\d+)\"[^{}]*\"status\":\"(\\w+)\"[^{}]*\\}", replacement = "$1($2)" }, |
| 23 | + |
| 24 | + # 6. Strip the JSON wrapper and all structural punctuation |
| 25 | + { pattern = "\\{\"count\":\\d+,\"tasks\":\\[", replacement = "" }, |
| 26 | + { pattern = "\\{\"tasks\":\\[", replacement = "" }, |
| 27 | + { pattern = "\\],\"count\":\\d+\\}", replacement = "" }, |
| 28 | + { pattern = "\\]\\}$", replacement = "" }, |
| 29 | + |
| 30 | + # 7. Replace inter-task commas with spaces |
| 31 | + { pattern = ",", replacement = " " }, |
36 | 32 | ] |
37 | | -max_lines = 10 |
| 33 | +max_lines = 5 |
38 | 34 |
|
39 | 35 | [[tests.hook_subagent]] |
40 | 36 | name = "single task" |
41 | | -input = """Active flow-code tasks: {"tasks":[{"id":"fn-21-slug.1","title":"Add filters","status":"in_progress","domain":"backend","epic":"fn-21-slug","depends_on":[],"files":[],"priority":null,"assignee":null,"claimed_at":null,"claim_note":"","impl":null,"review":null,"sync":null}],"count":1,"success":true}""" |
42 | | -expected = """Active: [{"id":".1","title":"Add filters","status":"in_progress","domain":"backend"}]""" |
| 37 | +input = """Active flow-code tasks: {"count":1,"tasks":[{"depends_on":[],"domain":"backend","epic":"fn-21-slug","id":"fn-21-slug.1","priority":null,"status":"in_progress","title":"Add filters"}]}""" |
| 38 | +expected = """ctx: fn-21.1(wip)""" |
43 | 39 |
|
44 | 40 | [[tests.hook_subagent]] |
45 | 41 | name = "empty task list" |
46 | | -input = """Active flow-code tasks: {"tasks":[],"count":0,"success":true}""" |
47 | | -expected = """Active: []""" |
| 42 | +input = """Active flow-code tasks: {"count":0,"tasks":[]}""" |
| 43 | +expected = """ctx: """ |
| 44 | + |
| 45 | +[[tests.hook_subagent]] |
| 46 | +name = "three tasks" |
| 47 | +input = """Active flow-code tasks: {"count":3,"tasks":[{"depends_on":["fn-16-web-orchestration-platform-fullstack.2"],"domain":"backend","epic":"fn-16-web-orchestration-platform-fullstack","id":"fn-16-web-orchestration-platform-fullstack.3","priority":null,"status":"in_progress","title":"WebSocket: protocol enhancement + event_bus emit on mutations"},{"depends_on":[],"domain":"backend","epic":"fn-19-migrate-flowctl-to-libsql-async-native","id":"fn-19-migrate-flowctl-to-libsql-async-native.1","priority":null,"status":"in_progress","title":"Foundation: libsql deps + async Pool + fresh schema"},{"depends_on":[],"domain":"backend","epic":"fn-21-extend-flowctl-compress-filters","id":"fn-21-extend-flowctl-compress-filters.1","priority":null,"status":"in_progress","title":"Add 7 compress filter configs"}]}""" |
| 48 | +expected = """ctx: fn-16.3(wip) fn-19.1(wip) fn-21.1(wip)""" |
48 | 49 |
|
49 | 50 | [[tests.hook_subagent]] |
50 | | -name = "two tasks" |
51 | | -input = """Active flow-code tasks: {"tasks":[{"id":"fn-5-daemon.1","title":"T1","status":"in_progress","domain":"backend","epic":"fn-5-daemon","depends_on":[],"files":[],"priority":null,"assignee":null,"claimed_at":null,"claim_note":"","impl":null,"review":null,"sync":null},{"id":"fn-5-daemon.2","title":"T2","status":"in_progress","domain":"frontend","epic":"fn-5-daemon","depends_on":[],"files":[],"priority":null,"assignee":null,"claimed_at":null,"claim_note":"","impl":null,"review":null,"sync":null}],"count":2,"success":true}""" |
52 | | -expected = """Active: [{"id":".1","title":"T1","status":"in_progress","domain":"backend"},{"id":".2","title":"T2","status":"in_progress","domain":"frontend"}]""" |
| 51 | +name = "mixed statuses" |
| 52 | +input = """Active flow-code tasks: {"count":2,"tasks":[{"depends_on":[],"domain":"backend","epic":"fn-5-daemon","id":"fn-5-daemon.1","priority":null,"status":"blocked","title":"T1"},{"depends_on":[],"domain":"frontend","epic":"fn-5-daemon","id":"fn-5-daemon.2","priority":null,"status":"todo","title":"T2"}]}""" |
| 53 | +expected = """ctx: fn-5.1(blk) fn-5.2(todo)""" |
0 commit comments