@nklisch/pi-agile-workflow 0.15.3

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 (203) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/.codex-plugin/plugin.json +28 -0
  3. package/CHANGELOG.md +591 -0
  4. package/README.md +107 -0
  5. package/docs/ARCHITECTURE.md +716 -0
  6. package/docs/MIGRATION.md +419 -0
  7. package/docs/PRINCIPLES.md +464 -0
  8. package/docs/ROADMAP.md +95 -0
  9. package/docs/SPEC.md +855 -0
  10. package/docs/VISION.md +106 -0
  11. package/extensions/agile-workflow.test.ts +419 -0
  12. package/extensions/agile-workflow.ts +467 -0
  13. package/hooks/hooks.json +52 -0
  14. package/hooks/scripts/prompt-context.py +628 -0
  15. package/hooks/scripts/substrate-maintainer.py +359 -0
  16. package/hooks/scripts/test_prompt_context.py +757 -0
  17. package/package.json +30 -0
  18. package/scripts/install-work-view.sh +165 -0
  19. package/scripts/tests/agent-metadata.test.sh +75 -0
  20. package/scripts/tests/bump-version.test.sh +552 -0
  21. package/scripts/tests/channel-parity.test.sh +117 -0
  22. package/scripts/tests/convert-content-integrity.test.sh +246 -0
  23. package/scripts/tests/convert-install-routing.test.sh +159 -0
  24. package/scripts/tests/install-work-view.test.sh +411 -0
  25. package/scripts/tests/pi-package-metadata.test.sh +119 -0
  26. package/scripts/tests/work-board-shim.test.sh +239 -0
  27. package/scripts/tests/work-view-dist-version.test.sh +105 -0
  28. package/scripts/work-board.sh +64 -0
  29. package/scripts/work-view.sh +423 -0
  30. package/skills/autopilot/SKILL.md +330 -0
  31. package/skills/autopilot/agents/openai.yaml +6 -0
  32. package/skills/board/SKILL.md +62 -0
  33. package/skills/board/agents/openai.yaml +6 -0
  34. package/skills/bold-refactor/SKILL.md +244 -0
  35. package/skills/bold-refactor/agents/openai.yaml +6 -0
  36. package/skills/bug-scan/SKILL.md +378 -0
  37. package/skills/bug-scan/agents/openai.yaml +6 -0
  38. package/skills/bug-scan/references/async-promises.md +119 -0
  39. package/skills/bug-scan/references/concurrency-races.md +148 -0
  40. package/skills/bug-scan/references/data-layer.md +167 -0
  41. package/skills/bug-scan/references/error-handling.md +197 -0
  42. package/skills/bug-scan/references/gate-item-template.md +94 -0
  43. package/skills/bug-scan/references/language-footguns.md +219 -0
  44. package/skills/bug-scan/references/parked-item-template.md +103 -0
  45. package/skills/bug-scan/references/report-template.md +115 -0
  46. package/skills/bug-scan/references/resource-leaks.md +199 -0
  47. package/skills/bug-scan/references/state-closures.md +148 -0
  48. package/skills/bug-scan/references/time-numbers.md +158 -0
  49. package/skills/convert/SKILL.md +1463 -0
  50. package/skills/convert/agents/openai.yaml +6 -0
  51. package/skills/convert/references/legacy-overlap-migration.md +179 -0
  52. package/skills/deep-code-scan/SKILL.md +378 -0
  53. package/skills/deep-code-scan/agents/openai.yaml +6 -0
  54. package/skills/deep-code-scan/references/artifact-ledger.md +148 -0
  55. package/skills/deep-code-scan/references/consolidation.md +91 -0
  56. package/skills/deep-code-scan/references/decomposition.md +127 -0
  57. package/skills/deep-code-scan/references/item-templates.md +263 -0
  58. package/skills/deep-code-scan/references/lane-catalog.md +129 -0
  59. package/skills/deep-code-scan/references/review-gauntlet.md +108 -0
  60. package/skills/deep-code-scan/references/scanner-brief.md +152 -0
  61. package/skills/e2e-test-design/SKILL.md +517 -0
  62. package/skills/e2e-test-design/references/anti-tautology.md +261 -0
  63. package/skills/e2e-test-design/references/service-mocks.md +234 -0
  64. package/skills/e2e-test-design/references/test-taxonomy.md +211 -0
  65. package/skills/epic-design/SKILL.md +476 -0
  66. package/skills/epicize/SKILL.md +210 -0
  67. package/skills/epicize/agents/openai.yaml +6 -0
  68. package/skills/feature-design/SKILL.md +472 -0
  69. package/skills/fix/SKILL.md +173 -0
  70. package/skills/gate-cruft/SKILL.md +258 -0
  71. package/skills/gate-docs/SKILL.md +278 -0
  72. package/skills/gate-patterns/SKILL.md +389 -0
  73. package/skills/gate-refactor/SKILL.md +408 -0
  74. package/skills/gate-security/SKILL.md +275 -0
  75. package/skills/gate-tests/SKILL.md +367 -0
  76. package/skills/groom/SKILL.md +162 -0
  77. package/skills/groom/agents/openai.yaml +6 -0
  78. package/skills/ideate/SKILL.md +185 -0
  79. package/skills/ideate/agents/openai.yaml +6 -0
  80. package/skills/implement/SKILL.md +288 -0
  81. package/skills/implement-orchestrator/SKILL.md +604 -0
  82. package/skills/park/SKILL.md +111 -0
  83. package/skills/perf-design/SKILL.md +427 -0
  84. package/skills/perf-scout/SKILL.md +404 -0
  85. package/skills/perf-scout/agents/openai.yaml +6 -0
  86. package/skills/perf-scout/references/algorithmic-and-data-structures.md +126 -0
  87. package/skills/perf-scout/references/approximation-and-precomputation.md +136 -0
  88. package/skills/perf-scout/references/caching-and-memoization.md +123 -0
  89. package/skills/perf-scout/references/compiler-runtime-and-language.md +136 -0
  90. package/skills/perf-scout/references/database-and-storage-internals.md +144 -0
  91. package/skills/perf-scout/references/distributed-systems.md +150 -0
  92. package/skills/perf-scout/references/game-engine-and-realtime.md +136 -0
  93. package/skills/perf-scout/references/gpu-and-accelerators.md +130 -0
  94. package/skills/perf-scout/references/idea-ranking.md +75 -0
  95. package/skills/perf-scout/references/io-and-batching.md +135 -0
  96. package/skills/perf-scout/references/memory-and-data-locality.md +137 -0
  97. package/skills/perf-scout/references/parallelism-and-vectorization.md +126 -0
  98. package/skills/perf-scout/references/parked-item-template.md +117 -0
  99. package/skills/perf-scout/references/peer-review-pass.md +150 -0
  100. package/skills/perf-scout/references/report-template.md +148 -0
  101. package/skills/principles/SKILL.md +678 -0
  102. package/skills/principles/references/models.md +191 -0
  103. package/skills/principles/references/subagents.md +158 -0
  104. package/skills/prose-author/SKILL.md +185 -0
  105. package/skills/refactor-conventions-creator/SKILL.md +359 -0
  106. package/skills/refactor-conventions-creator/agents/openai.yaml +6 -0
  107. package/skills/refactor-conventions-creator/references/common-structures.md +91 -0
  108. package/skills/refactor-conventions-creator/references/common-styles.md +90 -0
  109. package/skills/refactor-design/SKILL.md +384 -0
  110. package/skills/release-deploy/SKILL.md +667 -0
  111. package/skills/release-deploy/agents/openai.yaml +6 -0
  112. package/skills/research/SKILL.md +166 -0
  113. package/skills/research/agents/openai.yaml +6 -0
  114. package/skills/review/SKILL.md +267 -0
  115. package/skills/review/references/deep-review.md +98 -0
  116. package/skills/review/references/review-lenses.md +68 -0
  117. package/skills/review/references/substrate-side-effects.md +161 -0
  118. package/skills/review/references/target-resolution.md +52 -0
  119. package/skills/scope/SKILL.md +486 -0
  120. package/work-view/Cargo.lock +486 -0
  121. package/work-view/Cargo.toml +9 -0
  122. package/work-view/crates/cli/.work-view-version +1 -0
  123. package/work-view/crates/cli/Cargo.toml +18 -0
  124. package/work-view/crates/cli/src/actionable.rs +500 -0
  125. package/work-view/crates/cli/src/args.rs +792 -0
  126. package/work-view/crates/cli/src/board/assets/board.css +855 -0
  127. package/work-view/crates/cli/src/board/assets/board.js +259 -0
  128. package/work-view/crates/cli/src/board/assets/card.js +139 -0
  129. package/work-view/crates/cli/src/board/assets/components.css +290 -0
  130. package/work-view/crates/cli/src/board/assets/dependency.js +1193 -0
  131. package/work-view/crates/cli/src/board/assets/detail.js +256 -0
  132. package/work-view/crates/cli/src/board/assets/filters.js +389 -0
  133. package/work-view/crates/cli/src/board/assets/index.html +77 -0
  134. package/work-view/crates/cli/src/board/assets/kanban.js +231 -0
  135. package/work-view/crates/cli/src/board/assets/markdown.js +260 -0
  136. package/work-view/crates/cli/src/board/assets/motion.css +131 -0
  137. package/work-view/crates/cli/src/board/assets/state.js +245 -0
  138. package/work-view/crates/cli/src/board/assets/table.js +304 -0
  139. package/work-view/crates/cli/src/board/assets/tokens.css +602 -0
  140. package/work-view/crates/cli/src/board/assets/views.js +41 -0
  141. package/work-view/crates/cli/src/board/assets.rs +162 -0
  142. package/work-view/crates/cli/src/board/feed.rs +204 -0
  143. package/work-view/crates/cli/src/board/mod.rs +246 -0
  144. package/work-view/crates/cli/src/board/open.rs +144 -0
  145. package/work-view/crates/cli/src/board/server.rs +407 -0
  146. package/work-view/crates/cli/src/main.rs +195 -0
  147. package/work-view/crates/cli/src/render.rs +354 -0
  148. package/work-view/crates/cli/src/scope.rs +157 -0
  149. package/work-view/crates/cli/src/stale.rs +581 -0
  150. package/work-view/crates/cli/tests/board-js/dependency-table.test.mjs +64 -0
  151. package/work-view/crates/cli/tests/board-js/expanded-browsing.test.mjs +79 -0
  152. package/work-view/crates/cli/tests/board-js/harness-smoke.test.mjs +11 -0
  153. package/work-view/crates/cli/tests/board-js/harness.mjs +437 -0
  154. package/work-view/crates/cli/tests/board-js/kanban-detail.test.mjs +75 -0
  155. package/work-view/crates/cli/tests/board-js/markdown-filter.test.mjs +60 -0
  156. package/work-view/crates/cli/tests/fixtures/golden/.work/CONVENTIONS.md +3 -0
  157. package/work-view/crates/cli/tests/fixtures/golden/.work/active/epics/epic-alpha.md +16 -0
  158. package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-a.md +16 -0
  159. package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-b.md +16 -0
  160. package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-alpha-1.md +16 -0
  161. package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-research-1.md +19 -0
  162. package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-done.md +16 -0
  163. package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-shipped.md +18 -0
  164. package/work-view/crates/cli/tests/fixtures/golden/.work/backlog/idea-backlog.md +7 -0
  165. package/work-view/crates/cli/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
  166. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/CONVENTIONS.md +6 -0
  167. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-dep-done.md +17 -0
  168. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-design-ready.md +17 -0
  169. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-impl-ready.md +17 -0
  170. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/stories/story-review-ready.md +17 -0
  171. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/CONVENTIONS.md +4 -0
  172. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/epics/malformed-no-closing-fence.md +8 -0
  173. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/features/good-item.md +16 -0
  174. package/work-view/crates/cli/tests/integration.rs +2833 -0
  175. package/work-view/crates/core/Cargo.toml +13 -0
  176. package/work-view/crates/core/src/error.rs +61 -0
  177. package/work-view/crates/core/src/filter.rs +804 -0
  178. package/work-view/crates/core/src/graph.rs +324 -0
  179. package/work-view/crates/core/src/index.rs +590 -0
  180. package/work-view/crates/core/src/lib.rs +54 -0
  181. package/work-view/crates/core/src/model.rs +202 -0
  182. package/work-view/crates/core/src/parse.rs +494 -0
  183. package/work-view/crates/core/tests/fixtures/golden/.work/CONVENTIONS.md +15 -0
  184. package/work-view/crates/core/tests/fixtures/golden/.work/active/epics/epic-core.md +16 -0
  185. package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-parser.md +16 -0
  186. package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-query.md +16 -0
  187. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/malformed-no-id.md +15 -0
  188. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-parse-unit.md +16 -0
  189. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-security-gate.md +16 -0
  190. package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-old.md +16 -0
  191. package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-parser.md +17 -0
  192. package/work-view/crates/core/tests/fixtures/golden/.work/backlog/idea-future-board.md +7 -0
  193. package/work-view/crates/core/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
  194. package/work-view/crates/core/tests/fixtures/precedence/.work/CONVENTIONS.md +10 -0
  195. package/work-view/crates/core/tests/fixtures/precedence/.work/archive/dup-item.md +18 -0
  196. package/work-view/crates/core/tests/fixtures/precedence/.work/releases/v2.0/dup-item.md +18 -0
  197. package/work-view/crates/core/tests/integration.rs +541 -0
  198. package/work-view/dist/.gitattributes +1 -0
  199. package/work-view/dist/README.md +42 -0
  200. package/work-view/dist/aarch64-apple-darwin/work-view +0 -0
  201. package/work-view/dist/aarch64-unknown-linux-musl/work-view +0 -0
  202. package/work-view/dist/x86_64-apple-darwin/work-view +0 -0
  203. package/work-view/dist/x86_64-unknown-linux-musl/work-view +0 -0
@@ -0,0 +1,423 @@
1
+ #!/usr/bin/env bash
2
+ # work-view — query items in the agile-workflow substrate.
3
+ #
4
+ # FROZEN DEGRADED FALLBACK. The canonical work-view is the Rust binary
5
+ # (crates/, installed to .work/bin/work-view). This script is only the
6
+ # install fallback for platforms without a prebuilt binary. It deliberately
7
+ # LACKS newer features: no `--scope` tier filtering (it always queries all
8
+ # tiers) and no `work-view board`. bash<->Rust byte-parity is no longer
9
+ # enforced. Full retirement is tracked as a parked epic; until then this stays
10
+ # as a best-effort degraded fallback.
11
+ #
12
+ # Pure bash + a single awk pass. Frontmatter for every item is parsed exactly
13
+ # once, in one awk process for the whole tree, instead of spawning an awk per
14
+ # field per file. Optional yq enhancement detected at runtime but not required.
15
+ #
16
+ # Exit codes:
17
+ # 0 success
18
+ # 1 usage error (bad flag, conflicting flags)
19
+ # 2 no substrate found (no .work/CONVENTIONS.md in CWD or ancestor)
20
+ # 3 internal error (corrupted item file)
21
+
22
+ set -euo pipefail
23
+
24
+ # Kept in lockstep with plugin.json by scripts/bump-version.sh. Do not hand-edit.
25
+ WORK_VIEW_VERSION="0.15.3"
26
+
27
+ # ============================================================================
28
+ # Version prelude (POSIX / bash 3.2 safe — runs BEFORE the Bash-4 guard)
29
+ # ============================================================================
30
+ #
31
+ # `--version` must answer correctly even on a host whose only bash is the
32
+ # macOS system 3.2 with no modern bash to re-exec into. If it fell through to
33
+ # the Bash-4 guard below it would print "requires bash 4" and exit 1, and a
34
+ # self-heal staleness probe would read that failure as a broken/stale tool
35
+ # instead of a version. So short-circuit here using only constructs that work
36
+ # in bash 3.2 (printf, simple parameter expansion, case). Output is
37
+ # byte-identical to the Rust binary: `work-view <semver>\n`, exit 0.
38
+ case "${1:-}" in
39
+ --version|-V) printf 'work-view %s\n' "$WORK_VIEW_VERSION"; exit 0 ;;
40
+ esac
41
+
42
+ # ============================================================================
43
+ # Bash 4+ required (associative arrays). macOS ships bash 3.2 at /bin/bash,
44
+ # so re-exec under a modern bash if one is available.
45
+ # ============================================================================
46
+
47
+ if [[ "${BASH_VERSINFO[0]:-0}" -lt 4 ]]; then
48
+ for candidate in \
49
+ /opt/homebrew/bin/bash \
50
+ /usr/local/bin/bash \
51
+ /home/linuxbrew/.linuxbrew/bin/bash \
52
+ /usr/bin/bash \
53
+ "$(command -v bash 2>/dev/null || true)"
54
+ do
55
+ [[ -n "$candidate" && -x "$candidate" ]] || continue
56
+ ver="$("$candidate" -c 'echo "${BASH_VERSINFO[0]}"' 2>/dev/null || echo 0)"
57
+ if [[ "${ver:-0}" -ge 4 ]]; then
58
+ exec "$candidate" "$0" "$@"
59
+ fi
60
+ done
61
+ {
62
+ echo "work-view: requires bash 4 or newer (current: ${BASH_VERSION:-unknown})"
63
+ case "$(uname -s 2>/dev/null)" in
64
+ Darwin) echo " macOS ships bash 3.2. Install a modern bash: brew install bash" ;;
65
+ *) echo " Install bash 4+ via your package manager." ;;
66
+ esac
67
+ } >&2
68
+ exit 1
69
+ fi
70
+
71
+ # ============================================================================
72
+ # Usage
73
+ # ============================================================================
74
+
75
+ usage() {
76
+ cat <<'USAGE'
77
+ work-view — query items in the agile-workflow substrate
78
+
79
+ Usage: work-view [FILTERS...] [OUTPUT]
80
+
81
+ Filters (compose with AND semantics):
82
+ --stage <stage> Items at the given stage
83
+ --tag <tag> Items with the given tag (repeatable, AND)
84
+ --kind <kind> Items of the given kind (epic|feature|story|release)
85
+ --parent <id> Direct children of the given item
86
+ --release <version> Items with release_binding: <version>
87
+ --gate <name> Items with gate_origin: <name>
88
+ --ready Active items at drafting/implementing/review with all depends_on done
89
+ --blocked Active items at drafting/implementing/review with unmet dependencies
90
+ --blocking <id> Items that depend on <id>
91
+
92
+ Output (default tabular):
93
+ --paths One file path per line
94
+ --cat Full item bodies (separated by ---)
95
+ --count Match count only
96
+
97
+ Other:
98
+ --version Print the work-view version and exit
99
+ --help Show this help and exit
100
+ USAGE
101
+ }
102
+
103
+ # ============================================================================
104
+ # Substrate root detection
105
+ # ============================================================================
106
+
107
+ find_substrate_root() {
108
+ local dir
109
+ dir="$(pwd)"
110
+ while [[ "$dir" != "/" && -n "$dir" ]]; do
111
+ if [[ -f "$dir/.work/CONVENTIONS.md" ]]; then
112
+ printf '%s\n' "$dir"
113
+ return 0
114
+ fi
115
+ dir="$(dirname "$dir")"
116
+ done
117
+ return 1
118
+ }
119
+
120
+ # ============================================================================
121
+ # Frontmatter parser (single awk pass for the whole tree)
122
+ # ============================================================================
123
+ #
124
+ # Reads every item file once and emits one Unit-Separator-delimited record per
125
+ # file:
126
+ # path \037 id \037 kind \037 stage \037 parent \037 release_binding \037
127
+ # gate_origin \037 tags \037 depends_on
128
+ #
129
+ # Scalars are whitespace- and one-layer-quote-stripped exactly like the old
130
+ # fm_field. tags/depends_on are normalized from flow style ([a, b, c]) to
131
+ # space-joined tokens exactly like the old fm_array (only flow arrays are
132
+ # supported, matching prior behavior). The Unit Separator (0x1F) never appears
133
+ # in YAML frontmatter, so empty fields survive the round trip.
134
+
135
+ read -r -d '' AWK_INDEX <<'AWK' || true
136
+ function strip(v) {
137
+ sub(/^[[:space:]]+/, "", v)
138
+ sub(/[[:space:]]+$/, "", v)
139
+ sub(/^"/, "", v); sub(/"$/, "", v)
140
+ sub(/^'/, "", v); sub(/'$/, "", v)
141
+ return v
142
+ }
143
+ # Normalize a flow-style scalar ("[a, b]") to space-joined tokens ("a b").
144
+ function narr(v) {
145
+ if (v == "" || v == "[]") return ""
146
+ sub(/^\[/, "", v); sub(/\]$/, "", v)
147
+ gsub(/ /, "", v)
148
+ gsub(/,/, " ", v)
149
+ return v
150
+ }
151
+ function field(line, key, p) {
152
+ p = "^" key ":"
153
+ if (match(line, p)) return strip(substr(line, RLENGTH + 1))
154
+ return "\001NOMATCH\001"
155
+ }
156
+ function emit() {
157
+ if (cf == "") return
158
+ printf "%s\037%s\037%s\037%s\037%s\037%s\037%s\037%s\037%s\n", \
159
+ cf, id, kind, stage, parent, rel, gate, narr(tags), narr(deps)
160
+ }
161
+ function reset(fname) {
162
+ cf = fname; in_fm = 0; fm_done = 0
163
+ id = ""; kind = ""; stage = ""; parent = ""
164
+ rel = ""; gate = ""; tags = ""; deps = ""
165
+ delete have
166
+ }
167
+ BEGIN { v = "" }
168
+ FNR == 1 { emit(); reset(FILENAME) }
169
+ {
170
+ if (fm_done) next
171
+ if ($0 ~ /^---[[:space:]]*$/) {
172
+ if (in_fm == 0) { in_fm = 1 } else { fm_done = 1 }
173
+ next
174
+ }
175
+ if (in_fm != 1) next
176
+ if (!("id" in have)) { v = field($0, "id"); if (v != "\001NOMATCH\001") { id = v; have["id"] = 1; next } }
177
+ if (!("kind" in have)) { v = field($0, "kind"); if (v != "\001NOMATCH\001") { kind = v; have["kind"] = 1; next } }
178
+ if (!("stage" in have)) { v = field($0, "stage"); if (v != "\001NOMATCH\001") { stage = v; have["stage"] = 1; next } }
179
+ if (!("parent" in have)) { v = field($0, "parent"); if (v != "\001NOMATCH\001") { parent = v; have["parent"] = 1; next } }
180
+ if (!("rel" in have)) { v = field($0, "release_binding"); if (v != "\001NOMATCH\001") { rel = v; have["rel"] = 1; next } }
181
+ if (!("gate" in have)) { v = field($0, "gate_origin"); if (v != "\001NOMATCH\001") { gate = v; have["gate"] = 1; next } }
182
+ if (!("tags" in have)) { v = field($0, "tags"); if (v != "\001NOMATCH\001") { tags = v; have["tags"] = 1; next } }
183
+ if (!("deps" in have)) { v = field($0, "depends_on"); if (v != "\001NOMATCH\001") { deps = v; have["deps"] = 1; next } }
184
+ }
185
+ END { emit() }
186
+ AWK
187
+
188
+ # ============================================================================
189
+ # Item index
190
+ # ============================================================================
191
+ #
192
+ # Globals populated by build_index (all keyed by file path unless noted):
193
+ # ALL_FILES — array of item file paths, in find traversal order
194
+ # IDX_ID/KIND/STAGE/PARENT/REL/GATE — scalar fields
195
+ # IDX_TAGS/IDX_DEPS — space-joined tokens
196
+ # FILE_BY_ID/STAGE_BY_ID — keyed by item id (for the dependency graph)
197
+
198
+ declare -a ALL_FILES=()
199
+ declare -A IDX_ID IDX_KIND IDX_STAGE IDX_PARENT IDX_REL IDX_GATE IDX_TAGS IDX_DEPS
200
+ declare -A STAGE_BY_ID FILE_BY_ID
201
+
202
+ build_index() {
203
+ local root="$1"
204
+ local path id kind stage parent rel gate tags deps
205
+ ALL_FILES=()
206
+ while IFS=$'\037' read -r path id kind stage parent rel gate tags deps; do
207
+ [[ -n "$path" ]] || continue
208
+ ALL_FILES+=("$path")
209
+ IDX_ID["$path"]="$id"
210
+ IDX_KIND["$path"]="$kind"
211
+ IDX_STAGE["$path"]="$stage"
212
+ IDX_PARENT["$path"]="$parent"
213
+ IDX_REL["$path"]="$rel"
214
+ IDX_GATE["$path"]="$gate"
215
+ IDX_TAGS["$path"]="$tags"
216
+ IDX_DEPS["$path"]="$deps"
217
+ if [[ -n "$id" ]]; then
218
+ FILE_BY_ID["$id"]="$path"
219
+ STAGE_BY_ID["$id"]="$stage"
220
+ fi
221
+ done < <(find "$root/.work/active" "$root/.work/backlog" "$root/.work/releases" "$root/.work/archive" \
222
+ -type f -name '*.md' -print0 2>/dev/null \
223
+ | LC_ALL=C sort -z \
224
+ | xargs -0 awk "$AWK_INDEX" 2>/dev/null || true)
225
+ }
226
+
227
+ # True if item id is at stage:done OR lives in releases/ or archive/ (terminal).
228
+ is_done() {
229
+ local id="$1"
230
+ local stage="${STAGE_BY_ID[$id]:-}"
231
+ if [[ "$stage" == "done" || "$stage" == "released" ]]; then
232
+ return 0
233
+ fi
234
+ local file="${FILE_BY_ID[$id]:-}"
235
+ case "$file" in
236
+ */.work/releases/*|*/.work/archive/*) return 0 ;;
237
+ esac
238
+ return 1
239
+ }
240
+
241
+ # True if item file's depends_on are all done.
242
+ deps_satisfied() {
243
+ local file="$1"
244
+ local dep
245
+ for dep in ${IDX_DEPS[$file]:-}; do
246
+ if ! is_done "$dep"; then
247
+ return 1
248
+ fi
249
+ done
250
+ return 0
251
+ }
252
+
253
+ # ============================================================================
254
+ # Argument parsing
255
+ # ============================================================================
256
+
257
+ want_stage=""
258
+ want_kind=""
259
+ want_parent=""
260
+ want_release=""
261
+ want_gate=""
262
+ want_blocking=""
263
+ want_ready=0
264
+ want_blocked=0
265
+ declare -a want_tags=()
266
+ output_mode="table"
267
+
268
+ while [[ $# -gt 0 ]]; do
269
+ case "$1" in
270
+ --help|-h) usage; exit 0 ;;
271
+ # Short-circuits before substrate detection (like --help) so --version
272
+ # works outside a substrate. -V is the short form; lowercase -v is reserved
273
+ # for a possible future --verbose. The POSIX prelude at the top already
274
+ # handles --version as the first arg (for bash 3.2); this arm covers it in
275
+ # any position once running under bash 4+.
276
+ --version|-V) printf 'work-view %s\n' "$WORK_VIEW_VERSION"; exit 0 ;;
277
+ --stage) want_stage="$2"; shift 2 ;;
278
+ --kind) want_kind="$2"; shift 2 ;;
279
+ --parent) want_parent="$2"; shift 2 ;;
280
+ --release) want_release="$2"; shift 2 ;;
281
+ --gate) want_gate="$2"; shift 2 ;;
282
+ --blocking) want_blocking="$2"; shift 2 ;;
283
+ --tag) want_tags+=("$2"); shift 2 ;;
284
+ --ready) want_ready=1; shift ;;
285
+ --blocked) want_blocked=1; shift ;;
286
+ --paths) output_mode="paths"; shift ;;
287
+ --cat) output_mode="cat"; shift ;;
288
+ --count) output_mode="count"; shift ;;
289
+ --) shift; break ;;
290
+ -*) echo "work-view: unknown flag: $1" >&2; exit 1 ;;
291
+ *) echo "work-view: unexpected argument: $1" >&2; exit 1 ;;
292
+ esac
293
+ done
294
+
295
+ # --ready and --blocked are mutually exclusive
296
+ if [[ $want_ready -eq 1 && $want_blocked -eq 1 ]]; then
297
+ echo "work-view: --ready and --blocked are mutually exclusive" >&2
298
+ exit 1
299
+ fi
300
+
301
+ # ============================================================================
302
+ # Main
303
+ # ============================================================================
304
+
305
+ ROOT="$(find_substrate_root || true)"
306
+ if [[ -z "$ROOT" ]]; then
307
+ echo "work-view: no substrate found (no .work/CONVENTIONS.md in CWD or ancestor)" >&2
308
+ exit 2
309
+ fi
310
+
311
+ build_index "$ROOT"
312
+
313
+ declare -a matches=()
314
+
315
+ for f in "${ALL_FILES[@]}"; do
316
+ # --kind
317
+ if [[ -n "$want_kind" ]]; then
318
+ [[ "${IDX_KIND[$f]}" == "$want_kind" ]] || continue
319
+ fi
320
+
321
+ # --stage
322
+ if [[ -n "$want_stage" ]]; then
323
+ [[ "${IDX_STAGE[$f]}" == "$want_stage" ]] || continue
324
+ fi
325
+
326
+ # --parent
327
+ if [[ -n "$want_parent" ]]; then
328
+ [[ "${IDX_PARENT[$f]}" == "$want_parent" ]] || continue
329
+ fi
330
+
331
+ # --release
332
+ if [[ -n "$want_release" ]]; then
333
+ [[ "${IDX_REL[$f]}" == "$want_release" ]] || continue
334
+ fi
335
+
336
+ # --gate
337
+ if [[ -n "$want_gate" ]]; then
338
+ [[ "${IDX_GATE[$f]}" == "$want_gate" ]] || continue
339
+ fi
340
+
341
+ # --tag (AND semantics, repeatable)
342
+ if (( ${#want_tags[@]} > 0 )); then
343
+ file_tags="${IDX_TAGS[$f]}"
344
+ skip=0
345
+ for t in "${want_tags[@]}"; do
346
+ if [[ " $file_tags " != *" $t "* ]]; then
347
+ skip=1
348
+ break
349
+ fi
350
+ done
351
+ [[ $skip -eq 0 ]] || continue
352
+ fi
353
+
354
+ # --blocking <id>
355
+ if [[ -n "$want_blocking" ]]; then
356
+ if [[ " ${IDX_DEPS[$f]} " != *" $want_blocking "* ]]; then
357
+ continue
358
+ fi
359
+ fi
360
+
361
+ # --ready / --blocked: active-tier items at a movable stage
362
+ if [[ $want_ready -eq 1 || $want_blocked -eq 1 ]]; then
363
+ # only active-tier items are actionable candidates
364
+ case "$f" in */.work/active/*) ;; *) continue ;; esac
365
+ # stage must be one of: drafting, implementing, review
366
+ case "${IDX_STAGE[$f]}" in drafting|implementing|review) ;; *) continue ;; esac
367
+ if [[ $want_ready -eq 1 ]]; then
368
+ deps_satisfied "$f" || continue
369
+ else
370
+ if deps_satisfied "$f"; then
371
+ continue
372
+ fi
373
+ fi
374
+ fi
375
+
376
+ matches+=("$f")
377
+ done
378
+
379
+ # ============================================================================
380
+ # Output
381
+ # ============================================================================
382
+
383
+ case "$output_mode" in
384
+ count)
385
+ echo "${#matches[@]}"
386
+ ;;
387
+ paths)
388
+ for f in "${matches[@]}"; do
389
+ printf '%s\n' "$f"
390
+ done
391
+ ;;
392
+ cat)
393
+ first=1
394
+ for f in "${matches[@]}"; do
395
+ if [[ $first -eq 0 ]]; then
396
+ echo ""
397
+ echo "---"
398
+ echo ""
399
+ fi
400
+ cat "$f"
401
+ first=0
402
+ done
403
+ ;;
404
+ table|*)
405
+ if (( ${#matches[@]} == 0 )); then
406
+ exit 0
407
+ fi
408
+ # Header
409
+ printf '%-40s %-8s %-14s %-30s %s\n' "ID" "KIND" "STAGE" "TAGS" "PARENT"
410
+ printf '%-40s %-8s %-14s %-30s %s\n' "----------------------------------------" "--------" "--------------" "------------------------------" "----------------"
411
+ for f in "${matches[@]}"; do
412
+ id="${IDX_ID[$f]}"
413
+ kind="${IDX_KIND[$f]}"
414
+ stage="${IDX_STAGE[$f]}"
415
+ tags_csv="${IDX_TAGS[$f]// /,}"
416
+ parent="${IDX_PARENT[$f]}"
417
+ [[ "$parent" == "null" ]] && parent="-"
418
+ printf '%-40s %-8s %-14s %-30s %s\n' "$id" "$kind" "$stage" "$tags_csv" "$parent"
419
+ done
420
+ ;;
421
+ esac
422
+
423
+ exit 0