@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,411 @@
1
+ #!/usr/bin/env bash
2
+ # install-work-view.test.sh — unit tests for install-work-view.sh
3
+ #
4
+ # Self-contained: builds a temp PLUGIN_ROOT with stub artifacts and drives the
5
+ # helper with WORK_VIEW_UNAME_S/M overrides and a temp working directory.
6
+ #
7
+ # Runnable locally and from CI. Exits non-zero if any assertion fails.
8
+
9
+ set -uo pipefail
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ HELPER="${SCRIPT_DIR}/../install-work-view.sh"
13
+ TEST_VERSION="9.8.7"
14
+ STATUS_PREBUILT_X64="installed prebuilt x86_64-unknown-linux-musl (work-view ${TEST_VERSION})"
15
+ STATUS_PREBUILT_DARWIN_ARM64="installed prebuilt aarch64-apple-darwin (work-view ${TEST_VERSION})"
16
+ STATUS_FALLBACK="installed bash fallback (work-view ${TEST_VERSION})"
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Counters
20
+ # ---------------------------------------------------------------------------
21
+ PASS=0
22
+ FAIL=0
23
+ ERRORS=()
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # assert helpers
27
+ # ---------------------------------------------------------------------------
28
+ assert_eq() {
29
+ local label="$1" expected="$2" actual="$3"
30
+ if [ "$expected" = "$actual" ]; then
31
+ echo " PASS: $label"
32
+ ((PASS++))
33
+ else
34
+ echo " FAIL: $label"
35
+ echo " expected: $(printf '%q' "$expected")"
36
+ echo " actual: $(printf '%q' "$actual")"
37
+ ((FAIL++))
38
+ ERRORS+=("$label")
39
+ fi
40
+ }
41
+
42
+ assert_true() {
43
+ local label="$1"
44
+ if eval "$2"; then
45
+ echo " PASS: $label"
46
+ ((PASS++))
47
+ else
48
+ echo " FAIL: $label"
49
+ echo " condition false: $2"
50
+ ((FAIL++))
51
+ ERRORS+=("$label")
52
+ fi
53
+ }
54
+
55
+ assert_false() {
56
+ local label="$1"
57
+ if ! eval "$2"; then
58
+ echo " PASS: $label"
59
+ ((PASS++))
60
+ else
61
+ echo " FAIL: $label"
62
+ echo " expected false but was true: $2"
63
+ ((FAIL++))
64
+ ERRORS+=("$label")
65
+ fi
66
+ }
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Setup: build a temp PLUGIN_ROOT with stub artifacts
70
+ # ---------------------------------------------------------------------------
71
+ PLUGIN_ROOT_DIR="$(mktemp -d)"
72
+ trap 'rm -rf "$PLUGIN_ROOT_DIR"' EXIT
73
+
74
+ mkdir -p \
75
+ "${PLUGIN_ROOT_DIR}/.claude-plugin" \
76
+ "${PLUGIN_ROOT_DIR}/scripts" \
77
+ "${PLUGIN_ROOT_DIR}/work-view/dist/x86_64-unknown-linux-musl" \
78
+ "${PLUGIN_ROOT_DIR}/work-view/dist/aarch64-unknown-linux-musl" \
79
+ "${PLUGIN_ROOT_DIR}/work-view/dist/x86_64-apple-darwin" \
80
+ "${PLUGIN_ROOT_DIR}/work-view/dist/aarch64-apple-darwin"
81
+
82
+ write_plugin_json() {
83
+ local version="$1"
84
+ cat > "${PLUGIN_ROOT_DIR}/.claude-plugin/plugin.json" <<EOF
85
+ {
86
+ "name": "agile-workflow",
87
+ "version": "${version}",
88
+ "description": "fixture"
89
+ }
90
+ EOF
91
+ }
92
+
93
+ write_bash_stub() {
94
+ local version="$1"
95
+ cat > "${PLUGIN_ROOT_DIR}/scripts/work-view.sh" <<EOF
96
+ #!/usr/bin/env bash
97
+ if [[ "\${1:-}" == "--version" ]]; then
98
+ echo "work-view ${version}"
99
+ exit 0
100
+ fi
101
+ if [[ "\${1:-}" == "--help" ]]; then
102
+ echo "work-view bash stub ${version}"
103
+ exit 0
104
+ fi
105
+ echo "work-view bash stub ${version}"
106
+ EOF
107
+ chmod +x "${PLUGIN_ROOT_DIR}/scripts/work-view.sh"
108
+ }
109
+
110
+ write_prebuilt_stub() {
111
+ local triple="$1"
112
+ local version="${2:-$TEST_VERSION}"
113
+ local out="${PLUGIN_ROOT_DIR}/work-view/dist/${triple}/work-view"
114
+ cat > "$out" <<EOF
115
+ #!/usr/bin/env bash
116
+ if [[ "\${1:-}" == "--version" ]]; then
117
+ echo "work-view ${version}"
118
+ exit 0
119
+ fi
120
+ if [[ "\${1:-}" == "--help" ]]; then
121
+ echo "work-view prebuilt stub ${triple}"
122
+ exit 0
123
+ fi
124
+ if [[ "\${1:-}" == "board" ]]; then
125
+ echo "work-view board prebuilt stub ${triple}"
126
+ exit 0
127
+ fi
128
+ echo "work-view prebuilt stub ${triple}"
129
+ EOF
130
+ chmod +x "$out"
131
+ }
132
+
133
+ write_bad_prebuilt_stub() {
134
+ local triple="$1"
135
+ local out="${PLUGIN_ROOT_DIR}/work-view/dist/${triple}/work-view"
136
+ cat > "$out" <<EOF
137
+ #!/usr/bin/env bash
138
+ if [[ "\${1:-}" == "--version" ]]; then
139
+ echo "work-view ${TEST_VERSION}"
140
+ exit 0
141
+ fi
142
+ exit 1
143
+ EOF
144
+ chmod +x "$out"
145
+ }
146
+
147
+ write_no_board_prebuilt_stub() {
148
+ local triple="$1"
149
+ local out="${PLUGIN_ROOT_DIR}/work-view/dist/${triple}/work-view"
150
+ cat > "$out" <<EOF
151
+ #!/usr/bin/env bash
152
+ if [[ "\${1:-}" == "--version" ]]; then
153
+ echo "work-view ${TEST_VERSION}"
154
+ exit 0
155
+ fi
156
+ if [[ "\${1:-}" == "--help" ]]; then
157
+ echo "work-view prebuilt stub ${triple}"
158
+ exit 0
159
+ fi
160
+ if [[ "\${1:-}" == "board" ]]; then
161
+ exit 1
162
+ fi
163
+ echo "work-view prebuilt stub ${triple}"
164
+ EOF
165
+ chmod +x "$out"
166
+ }
167
+
168
+ write_plugin_json "$TEST_VERSION"
169
+ write_bash_stub "$TEST_VERSION"
170
+ write_prebuilt_stub "x86_64-unknown-linux-musl"
171
+ write_prebuilt_stub "aarch64-unknown-linux-musl"
172
+ write_prebuilt_stub "x86_64-apple-darwin"
173
+ write_prebuilt_stub "aarch64-apple-darwin"
174
+
175
+ # ---------------------------------------------------------------------------
176
+ # Helper: run install helper in a fresh temp working dir
177
+ # Returns stdout in $INSTALL_OUT, stderr in $INSTALL_ERR, rc in $INSTALL_RC.
178
+ # ---------------------------------------------------------------------------
179
+ run_install() {
180
+ local uname_s="$1" uname_m="$2"
181
+ local workdir
182
+ workdir="$(mktemp -d)"
183
+ INSTALL_ERR="${workdir}/install.err"
184
+ INSTALL_OUT=$(
185
+ cd "$workdir" &&
186
+ PLUGIN_ROOT="$PLUGIN_ROOT_DIR" \
187
+ WORK_VIEW_UNAME_S="$uname_s" \
188
+ WORK_VIEW_UNAME_M="$uname_m" \
189
+ bash "$HELPER" 2>"$INSTALL_ERR"
190
+ )
191
+ INSTALL_RC=$?
192
+ INSTALL_WORKDIR="$workdir"
193
+ }
194
+
195
+ helper_plugin_version() {
196
+ PLUGIN_ROOT="$PLUGIN_ROOT_DIR" bash -c 'source "$1"; plugin_version' _ "$HELPER"
197
+ }
198
+
199
+ helper_candidate_rc() {
200
+ local candidate="$1"
201
+ PLUGIN_ROOT="$PLUGIN_ROOT_DIR" bash -c 'source "$1"; candidate_is_current "$2" "$3"' _ "$HELPER" "$candidate" "$TEST_VERSION"
202
+ }
203
+
204
+ make_candidate() {
205
+ local body="$1"
206
+ local candidate
207
+ candidate="$(mktemp "${PLUGIN_ROOT_DIR}/candidate.XXXXXX")"
208
+ cat > "$candidate" <<EOF
209
+ #!/usr/bin/env bash
210
+ ${body}
211
+ EOF
212
+ chmod +x "$candidate"
213
+ echo "$candidate"
214
+ }
215
+
216
+ # ---------------------------------------------------------------------------
217
+ # Test 1: plugin_version reads plugin.json without jq/yq
218
+ # ---------------------------------------------------------------------------
219
+ echo ""
220
+ echo "=== Test group 1: plugin_version ==="
221
+
222
+ PV="$(helper_plugin_version)"
223
+ assert_eq "plugin_version extracts fixture version" "$TEST_VERSION" "$PV"
224
+
225
+ # ---------------------------------------------------------------------------
226
+ # Test 2: candidate_is_current version cases
227
+ # ---------------------------------------------------------------------------
228
+ echo ""
229
+ echo "=== Test group 2: candidate_is_current ==="
230
+
231
+ CURRENT_CANDIDATE="$(make_candidate 'echo "work-view 9.8.7"; exit 0')"
232
+ NONZERO_CANDIDATE="$(make_candidate 'exit 42')"
233
+ EMPTY_CANDIDATE="$(make_candidate 'exit 0')"
234
+ MISMATCH_CANDIDATE="$(make_candidate 'echo "work-view 9.8.6"; exit 0')"
235
+
236
+ helper_candidate_rc "$CURRENT_CANDIDATE"
237
+ assert_eq "candidate_is_current accepts matching last token" "0" "$?"
238
+ helper_candidate_rc "$NONZERO_CANDIDATE"
239
+ assert_eq "candidate_is_current rejects non-zero --version" "1" "$?"
240
+ helper_candidate_rc "$EMPTY_CANDIDATE"
241
+ assert_eq "candidate_is_current rejects empty --version" "1" "$?"
242
+ helper_candidate_rc "$MISMATCH_CANDIDATE"
243
+ assert_eq "candidate_is_current rejects mismatched semver" "1" "$?"
244
+
245
+ # ---------------------------------------------------------------------------
246
+ # Test 3: Default install writes prebuilt binaries on supported platforms,
247
+ # falling back to bash only for unsupported platforms.
248
+ # ---------------------------------------------------------------------------
249
+ echo ""
250
+ echo "=== Test group 3: prebuilt first, bash fallback only when unsupported ==="
251
+
252
+ run_install "Linux" "x86_64"
253
+ assert_eq "Linux x86_64 exit 0" "0" "$INSTALL_RC"
254
+ assert_eq "Linux x86_64 output" "$STATUS_PREBUILT_X64" "$INSTALL_OUT"
255
+ assert_true "Linux x86_64 work-view exists" "[ -f '${INSTALL_WORKDIR}/.work/bin/work-view' ]"
256
+ assert_true "Linux x86_64 work-view executable" "[ -x '${INSTALL_WORKDIR}/.work/bin/work-view' ]"
257
+ assert_false "Linux x86_64 no .tmp left" "[ -f '${INSTALL_WORKDIR}/.work/bin/work-view.tmp' ]"
258
+ assert_true "Linux x86_64 --version matches plugin" "[ \"\$('${INSTALL_WORKDIR}/.work/bin/work-view' --version)\" = 'work-view ${TEST_VERSION}' ]"
259
+ assert_true "Linux x86_64 installed prebuilt" "'${INSTALL_WORKDIR}/.work/bin/work-view' --help 2>/dev/null | grep -q 'prebuilt'"
260
+ assert_true "Linux x86_64 board-capable command installed" "'${INSTALL_WORKDIR}/.work/bin/work-view' board --help 2>/dev/null | grep -q 'board prebuilt'"
261
+ assert_false "Linux x86_64 did not install bash fallback" "'${INSTALL_WORKDIR}/.work/bin/work-view' --help 2>/dev/null | grep -q 'bash stub'"
262
+ rm -rf "$INSTALL_WORKDIR"
263
+
264
+ run_install "Darwin" "arm64"
265
+ assert_eq "Darwin arm64 exit 0" "0" "$INSTALL_RC"
266
+ assert_eq "Darwin arm64 output" "$STATUS_PREBUILT_DARWIN_ARM64" "$INSTALL_OUT"
267
+ assert_true "Darwin arm64 installed prebuilt" "'${INSTALL_WORKDIR}/.work/bin/work-view' --help 2>/dev/null | grep -q 'prebuilt'"
268
+ assert_false "Darwin arm64 did not install bash fallback" "'${INSTALL_WORKDIR}/.work/bin/work-view' --help 2>/dev/null | grep -q 'bash stub'"
269
+ rm -rf "$INSTALL_WORKDIR"
270
+
271
+ run_install "FreeBSD" "x86_64"
272
+ assert_eq "FreeBSD x86_64 exit 0" "0" "$INSTALL_RC"
273
+ assert_eq "FreeBSD x86_64 output" "$STATUS_FALLBACK" "$INSTALL_OUT"
274
+ assert_true "FreeBSD x86_64 installed bash fallback" "'${INSTALL_WORKDIR}/.work/bin/work-view' --help 2>/dev/null | grep -q 'bash stub'"
275
+ assert_false "FreeBSD x86_64 prebuilt not installed" "'${INSTALL_WORKDIR}/.work/bin/work-view' --help 2>/dev/null | grep -q 'prebuilt'"
276
+ rm -rf "$INSTALL_WORKDIR"
277
+
278
+ # ---------------------------------------------------------------------------
279
+ # Test 4: Fail-fast postcondition catches source-stamp drift
280
+ # ---------------------------------------------------------------------------
281
+ echo ""
282
+ echo "=== Test group 4: postcondition failure ==="
283
+
284
+ write_plugin_json "9.8.8"
285
+ write_bash_stub "$TEST_VERSION"
286
+ run_install "Linux" "x86_64"
287
+ POST_ERR="$(cat "$INSTALL_ERR" 2>/dev/null || true)"
288
+ assert_eq "postcondition drift exits 1" "1" "$INSTALL_RC"
289
+ assert_eq "postcondition drift prints no success status" "" "$INSTALL_OUT"
290
+ assert_true "postcondition drift reports version mismatch" "printf '%s' \"\$POST_ERR\" | grep -q 'does not report plugin version 9.8.8'"
291
+ assert_false "postcondition drift no .tmp left" "[ -f '${INSTALL_WORKDIR}/.work/bin/work-view.tmp' ]"
292
+ assert_false "postcondition drift no final work-view" "[ -f '${INSTALL_WORKDIR}/.work/bin/work-view' ]"
293
+ rm -rf "$INSTALL_WORKDIR"
294
+ write_plugin_json "$TEST_VERSION"
295
+ write_bash_stub "$TEST_VERSION"
296
+
297
+ # ---------------------------------------------------------------------------
298
+ # Test 5: Atomicity — failing --help leaves no partial install or tmp
299
+ # ---------------------------------------------------------------------------
300
+ echo ""
301
+ echo "=== Test group 5: atomicity ==="
302
+
303
+ write_bad_prebuilt_stub "x86_64-unknown-linux-musl"
304
+
305
+ run_install "Linux" "x86_64"
306
+ assert_eq "atomicity: failed smoke exits 1" "1" "$INSTALL_RC"
307
+ assert_false "atomicity: no .tmp after failed smoke" "[ -f '${INSTALL_WORKDIR}/.work/bin/work-view.tmp' ]"
308
+ assert_false "atomicity: no final work-view after failed smoke" "[ -f '${INSTALL_WORKDIR}/.work/bin/work-view' ]"
309
+ rm -rf "$INSTALL_WORKDIR"
310
+ write_prebuilt_stub "x86_64-unknown-linux-musl"
311
+
312
+ # ---------------------------------------------------------------------------
313
+ # Test 6: Prebuilt without board support is rejected before replacing existing
314
+ # work-view.
315
+ # ---------------------------------------------------------------------------
316
+ echo ""
317
+ echo "=== Test group 6: prebuilt board smoke ==="
318
+
319
+ write_no_board_prebuilt_stub "x86_64-unknown-linux-musl"
320
+ WORKDIR_BOARD="$(mktemp -d)"
321
+ mkdir -p "${WORKDIR_BOARD}/.work/bin"
322
+ cat > "${WORKDIR_BOARD}/.work/bin/work-view" <<'EOF'
323
+ #!/usr/bin/env bash
324
+ echo existing
325
+ EOF
326
+ chmod +x "${WORKDIR_BOARD}/.work/bin/work-view"
327
+
328
+ BOARD_OUT=$(
329
+ cd "$WORKDIR_BOARD" &&
330
+ PLUGIN_ROOT="$PLUGIN_ROOT_DIR" \
331
+ WORK_VIEW_UNAME_S=Linux \
332
+ WORK_VIEW_UNAME_M=x86_64 \
333
+ bash "$HELPER" 2>/dev/null
334
+ )
335
+ BOARD_RC=$?
336
+
337
+ assert_eq "board-smoke: installer exits 1" "1" "$BOARD_RC"
338
+ assert_eq "board-smoke: no success output" "" "$BOARD_OUT"
339
+ assert_false "board-smoke: no .tmp left" "[ -f '${WORKDIR_BOARD}/.work/bin/work-view.tmp' ]"
340
+ assert_true "board-smoke: existing work-view preserved" \
341
+ "[ \"\$('${WORKDIR_BOARD}/.work/bin/work-view')\" = 'existing' ]"
342
+ rm -rf "$WORKDIR_BOARD"
343
+ write_prebuilt_stub "x86_64-unknown-linux-musl"
344
+
345
+ # ---------------------------------------------------------------------------
346
+ # Test 7: Idempotency — second run overwrites cleanly
347
+ # ---------------------------------------------------------------------------
348
+ echo ""
349
+ echo "=== Test group 7: idempotency ==="
350
+
351
+ WORKDIR_IDEM="$(mktemp -d)"
352
+ mkdir -p "${WORKDIR_IDEM}/.work/bin"
353
+ cat > "${WORKDIR_IDEM}/.work/bin/work-view" <<'EOF'
354
+ #!/usr/bin/env bash
355
+ echo stale
356
+ EOF
357
+ chmod +x "${WORKDIR_IDEM}/.work/bin/work-view"
358
+
359
+ OUT1=$(cd "$WORKDIR_IDEM" && PLUGIN_ROOT="$PLUGIN_ROOT_DIR" WORK_VIEW_UNAME_S=Linux WORK_VIEW_UNAME_M=x86_64 bash "$HELPER" 2>/dev/null)
360
+ RC1=$?
361
+ OUT2=$(cd "$WORKDIR_IDEM" && PLUGIN_ROOT="$PLUGIN_ROOT_DIR" WORK_VIEW_UNAME_S=Linux WORK_VIEW_UNAME_M=x86_64 bash "$HELPER" 2>/dev/null)
362
+ RC2=$?
363
+
364
+ assert_eq "idempotency: first run exit 0" "0" "$RC1"
365
+ assert_eq "idempotency: first run output" "$STATUS_PREBUILT_X64" "$OUT1"
366
+ assert_eq "idempotency: second run exit 0" "0" "$RC2"
367
+ assert_eq "idempotency: second run output" "$STATUS_PREBUILT_X64" "$OUT2"
368
+ assert_true "idempotency: work-view still exists" "[ -f '${WORKDIR_IDEM}/.work/bin/work-view' ]"
369
+ assert_true "idempotency: work-view still executable" "[ -x '${WORKDIR_IDEM}/.work/bin/work-view' ]"
370
+ assert_false "idempotency: no .tmp left" "[ -f '${WORKDIR_IDEM}/.work/bin/work-view.tmp' ]"
371
+ assert_true "idempotency: overwritten with prebuilt stub" "'${WORKDIR_IDEM}/.work/bin/work-view' --help 2>/dev/null | grep -q 'prebuilt'"
372
+ assert_true "idempotency: --version works after second run" "[ \"\$('${WORKDIR_IDEM}/.work/bin/work-view' --version)\" = 'work-view ${TEST_VERSION}' ]"
373
+ rm -rf "$WORKDIR_IDEM"
374
+
375
+ # ---------------------------------------------------------------------------
376
+ # Test 8: Destination is a pre-existing directory → installer fails
377
+ # ---------------------------------------------------------------------------
378
+ echo ""
379
+ echo "=== Test group 8: dest is a pre-existing directory ==="
380
+
381
+ WORKDIR_DIR="$(mktemp -d)"
382
+ mkdir -p "${WORKDIR_DIR}/.work/bin/work-view"
383
+
384
+ DIR_OUT=$(
385
+ cd "$WORKDIR_DIR" &&
386
+ PLUGIN_ROOT="$PLUGIN_ROOT_DIR" \
387
+ bash "$HELPER" 2>/dev/null
388
+ )
389
+ DIR_RC=$?
390
+
391
+ assert_eq "dest-is-dir: installer exits 1" "1" "$DIR_RC"
392
+ assert_eq "dest-is-dir: no success output" "" "$DIR_OUT"
393
+ assert_true "dest-is-dir: destination remains directory" "[ -d '${WORKDIR_DIR}/.work/bin/work-view' ]"
394
+ assert_false "dest-is-dir: no .tmp left" "[ -f '${WORKDIR_DIR}/.work/bin/work-view.tmp' ]"
395
+ rm -rf "$WORKDIR_DIR"
396
+
397
+ # ---------------------------------------------------------------------------
398
+ # Summary
399
+ # ---------------------------------------------------------------------------
400
+ echo ""
401
+ echo "============================================================"
402
+ echo "Results: ${PASS} passed, ${FAIL} failed"
403
+ if [ ${#ERRORS[@]} -gt 0 ]; then
404
+ echo "Failed tests:"
405
+ for e in "${ERRORS[@]}"; do
406
+ echo " - $e"
407
+ done
408
+ fi
409
+ echo "============================================================"
410
+
411
+ [ "$FAIL" -eq 0 ] || exit 1
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ # pi-package-metadata.test.sh — guard every independently published Pi package.
3
+
4
+ set -uo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../.." && pwd)"
8
+
9
+ PASS=0
10
+ FAIL=0
11
+ ERRORS=()
12
+
13
+ assert_true() {
14
+ local label="$1"
15
+ if eval "$2"; then
16
+ echo " PASS: $label"
17
+ ((PASS++))
18
+ else
19
+ echo " FAIL: $label"
20
+ echo " condition false: $2"
21
+ ((FAIL++))
22
+ ERRORS+=("$label")
23
+ fi
24
+ }
25
+
26
+ assert_eq() {
27
+ local label="$1" expected="$2" actual="$3"
28
+ if [ "$expected" = "$actual" ]; then
29
+ echo " PASS: $label"
30
+ ((PASS++))
31
+ else
32
+ echo " FAIL: $label"
33
+ echo " expected: $(printf '%q' "$expected")"
34
+ echo " actual: $(printf '%q' "$actual")"
35
+ ((FAIL++))
36
+ ERRORS+=("$label")
37
+ fi
38
+ }
39
+
40
+ json_value() {
41
+ jq -r "$1" "$2"
42
+ }
43
+
44
+ assert_package() {
45
+ local plugin="$1" expected_name="$2" expect_extensions="$3" expect_channels="$4"
46
+ local plugin_dir="${REPO_ROOT}/plugins/${plugin}"
47
+ local package_json="${plugin_dir}/package.json"
48
+ local readme="${plugin_dir}/README.md"
49
+ local claude_json="${plugin_dir}/.claude-plugin/plugin.json"
50
+ local codex_json="${plugin_dir}/.codex-plugin/plugin.json"
51
+
52
+ echo ""
53
+ echo "=== Plugin: ${plugin} ==="
54
+ assert_true "${plugin} package.json exists" "[ -f '$package_json' ]"
55
+ assert_true "${plugin} README exists" "[ -f '$readme' ]"
56
+
57
+ local package_version
58
+ package_version="$(json_value '.version' "$package_json")"
59
+ assert_eq "${plugin} package name" "$expected_name" "$(json_value '.name' "$package_json")"
60
+ assert_true "${plugin} package version is non-empty semver" \
61
+ "printf '%s' '$package_version' | grep -Eq '^[0-9]+[.][0-9]+[.][0-9]+([-+][0-9A-Za-z.-]+)?\$'"
62
+ assert_true "${plugin} has pi-package keyword" \
63
+ "jq -e '.keywords | index(\"pi-package\")' '$package_json' >/dev/null"
64
+ assert_eq "${plugin} publish access is public" "public" "$(json_value '.publishConfig.access' "$package_json")"
65
+ assert_eq "${plugin} repository directory" "plugins/${plugin}" "$(json_value '.repository.directory' "$package_json")"
66
+ assert_eq "${plugin} pi.skills" "[\"./skills\"]" "$(jq -c '.pi.skills' "$package_json")"
67
+ assert_true "${plugin} skills directory exists" "[ -d '${plugin_dir}/skills' ]"
68
+
69
+ if [ "$expect_extensions" = "yes" ]; then
70
+ assert_eq "${plugin} pi.extensions" "[\"./extensions\"]" "$(jq -c '.pi.extensions' "$package_json")"
71
+ assert_true "${plugin} extensions directory exists" "[ -d '${plugin_dir}/extensions' ]"
72
+ else
73
+ assert_eq "${plugin} pi.extensions absent" "null" "$(jq -c '.pi.extensions // null' "$package_json")"
74
+ fi
75
+
76
+ if [ "$expect_channels" = "yes" ]; then
77
+ assert_true "${plugin} claude plugin.json exists" "[ -f '$claude_json' ]"
78
+ assert_true "${plugin} codex plugin.json exists" "[ -f '$codex_json' ]"
79
+ assert_eq "${plugin} claude/package version lockstep" "$package_version" "$(json_value '.version' "$claude_json")"
80
+ assert_eq "${plugin} codex/package version lockstep" "$package_version" "$(json_value '.version' "$codex_json")"
81
+ else
82
+ assert_true "${plugin} is intentionally Pi-only" "[ ! -f '$claude_json' ] && [ ! -f '$codex_json' ]"
83
+ fi
84
+
85
+ assert_eq "${plugin} pi.subagents absent" "null" "$(jq -c '.pi.subagents // null' "$package_json")"
86
+ }
87
+
88
+ echo ""
89
+ echo "=== Preflight: jq and Pi package metadata ==="
90
+ assert_true "jq is available" "command -v jq >/dev/null 2>&1"
91
+
92
+ # Runtime-extension packages.
93
+ assert_package "agile-workflow" "@nklisch/pi-agile-workflow" "yes" "yes"
94
+ assert_package "background-tasks" "@nklisch/pi-background-tasks" "yes" "no"
95
+ assert_package "nates-toolkit" "@nklisch/pi-nates-toolkit" "yes" "yes"
96
+ assert_package "zai-research" "@nklisch/pi-zai-research" "yes" "yes"
97
+
98
+ # Skills-only packages.
99
+ assert_package "agent-coordination" "@nklisch/pi-agent-coordination" "no" "yes"
100
+ assert_package "agentic-research" "@nklisch/pi-agentic-research" "no" "yes"
101
+ assert_package "code-audit" "@nklisch/pi-code-audit" "no" "yes"
102
+ assert_package "ux-ui-design" "@nklisch/pi-ux-ui-design" "no" "yes"
103
+
104
+ # The deprecated workflow plugin remains intentionally absent from Pi packages.
105
+ assert_true "deprecated workflow plugin has no Pi package manifest" \
106
+ "[ ! -f '${REPO_ROOT}/plugins/workflow/package.json' ]"
107
+
108
+ echo ""
109
+ echo "============================================================"
110
+ echo "Results: ${PASS} passed, ${FAIL} failed"
111
+ if [ ${#ERRORS[@]} -gt 0 ]; then
112
+ echo "Failed tests:"
113
+ for e in "${ERRORS[@]}"; do
114
+ echo " - $e"
115
+ done
116
+ fi
117
+ echo "============================================================"
118
+
119
+ [ "$FAIL" -eq 0 ] || exit 1