@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,246 @@
1
+ #!/usr/bin/env bash
2
+ # convert-content-integrity.test.sh — structural regression guards for two
3
+ # markdown-skill invariants that are cheaply machine-checkable:
4
+ #
5
+ # (a) convert/SKILL.md DEFINES the content-integrity gate once, the gate
6
+ # enumerates the destructive ops it governs, and the document's destructive
7
+ # verb sites (git rm / git mv / shim / symlink / managed-section overwrite /
8
+ # mirror replacement) each sit near a content-integrity reference. A future
9
+ # edit could silently strip the gate or perform a destructive op without
10
+ # routing through it.
11
+ #
12
+ # (b) Each enumerated design/implement/review-family SKILL.md grounds on
13
+ # `.agents/rules/*.md` (the project's force-loaded agent rules) in its
14
+ # grounding phase. A future edit could silently drop one.
15
+ #
16
+ # These are structural tests on SKILL.md prose, mirroring
17
+ # convert-install-routing.test.sh. Runnable locally and from CI. Exits non-zero
18
+ # if any assertion fails.
19
+
20
+ set -uo pipefail
21
+
22
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ SKILLS_DIR="${SCRIPT_DIR}/../../skills"
24
+ CONVERT_MD="${SKILLS_DIR}/convert/SKILL.md"
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Counters
28
+ # ---------------------------------------------------------------------------
29
+ PASS=0
30
+ FAIL=0
31
+ ERRORS=()
32
+
33
+ assert_eq() {
34
+ local label="$1" expected="$2" actual="$3"
35
+ if [ "$expected" = "$actual" ]; then
36
+ echo " PASS: $label"
37
+ ((PASS++))
38
+ else
39
+ echo " FAIL: $label"
40
+ echo " expected: $(printf '%q' "$expected")"
41
+ echo " actual: $(printf '%q' "$actual")"
42
+ ((FAIL++))
43
+ ERRORS+=("$label")
44
+ fi
45
+ }
46
+
47
+ assert_true() {
48
+ local label="$1"
49
+ if eval "$2"; then
50
+ echo " PASS: $label"
51
+ ((PASS++))
52
+ else
53
+ echo " FAIL: $label"
54
+ echo " condition false: $2"
55
+ ((FAIL++))
56
+ ERRORS+=("$label")
57
+ fi
58
+ }
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # Preflight: convert/SKILL.md present
62
+ # ---------------------------------------------------------------------------
63
+ echo ""
64
+ echo "=== Preflight: convert/SKILL.md present ==="
65
+ assert_true "convert/SKILL.md exists" "[ -f '$CONVERT_MD' ]"
66
+ if [ ! -f "$CONVERT_MD" ]; then
67
+ echo "ERROR: cannot locate convert/SKILL.md at $CONVERT_MD" >&2
68
+ exit 1
69
+ fi
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Test group 1: content-integrity gate is defined exactly once
73
+ # ---------------------------------------------------------------------------
74
+ echo ""
75
+ echo "=== Test group 1: content-integrity gate defined ==="
76
+
77
+ # The canonical gate-definition line (a blockquote bold lead-in). Use a
78
+ # fixed-string match so the literal parentheses are not interpreted as regex.
79
+ GATE_DEF_LITERAL='Content-integrity gate (before any destructive op)'
80
+ GATE_DEF_COUNT="$(grep -cF "$GATE_DEF_LITERAL" "$CONVERT_MD")"
81
+ assert_eq "content-integrity gate defined exactly once" "1" "$GATE_DEF_COUNT"
82
+
83
+ # The gate must describe itself as a hard precondition, not advice.
84
+ assert_true "gate is stated as a hard precondition (not advice)" \
85
+ "grep -q 'hard precondition, not advice' '$CONVERT_MD'"
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Test group 2: the gate enumerates each destructive op kind it governs
89
+ # ---------------------------------------------------------------------------
90
+ echo ""
91
+ echo "=== Test group 2: gate enumerates the destructive op kinds ==="
92
+
93
+ # Extract the gate's definition block: from the gate-definition line up to the
94
+ # blank-quote separator line (`>` alone) that closes the enumeration.
95
+ GATE_BLOCK="$(awk -v needle="$GATE_DEF_LITERAL" '
96
+ index($0, needle) { collecting = 1 }
97
+ collecting && $0 == ">" { print; exit }
98
+ collecting { print }
99
+ ' "$CONVERT_MD")"
100
+
101
+ assert_true "gate block is non-empty" "[ -n \"\$GATE_BLOCK\" ]"
102
+ assert_true "gate enumerates git rm / delete" \
103
+ "printf '%s' \"\$GATE_BLOCK\" | grep -qE 'git rm.*delete|delete'"
104
+ assert_true "gate enumerates git mv / move" \
105
+ "printf '%s' \"\$GATE_BLOCK\" | grep -qE 'git mv.*move|move'"
106
+ assert_true "gate enumerates replace-with-symlink" \
107
+ "printf '%s' \"\$GATE_BLOCK\" | grep -q 'replace-with-symlink'"
108
+ assert_true "gate enumerates replace-with-shim" \
109
+ "printf '%s' \"\$GATE_BLOCK\" | grep -q 'replace-with-shim'"
110
+ assert_true "gate enumerates managed-section overwrite" \
111
+ "printf '%s' \"\$GATE_BLOCK\" | grep -q 'managed-section'"
112
+ assert_true "gate enumerates mirror replacement" \
113
+ "printf '%s' \"\$GATE_BLOCK\" | grep -q 'mirror replacement'"
114
+
115
+ # ---------------------------------------------------------------------------
116
+ # Test group 3: the Hard-rules summary restates the precondition
117
+ # ---------------------------------------------------------------------------
118
+ echo ""
119
+ echo "=== Test group 3: Hard-rules summary restates content integrity first ==="
120
+
121
+ assert_true "summary: content integrity mandatory before every destructive op" \
122
+ "grep -q 'Content integrity is mandatory before every destructive op' '$CONVERT_MD'"
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Test group 4: destructive verb sites each sit near a content-integrity ref
126
+ #
127
+ # Invariant: every line that performs/names a destructive op must be authored in
128
+ # proximity to the gate. A site is satisfied if EITHER
129
+ # * a 'content-integrity' reference appears within +/- WINDOW lines, OR
130
+ # * the site lies inside the gate's own defining section (Phase 1.8 through its
131
+ # manifest sub-sections) — those lines describe the ops the gate governs and
132
+ # ARE the gate, so they need no separate nearby reference.
133
+ # This catches a future destructive instruction added in isolation from the gate
134
+ # (e.g. a new shim/removal step in a migration path with no gate routing).
135
+ # ---------------------------------------------------------------------------
136
+ echo ""
137
+ echo "=== Test group 4: destructive verb sites reference content-integrity nearby ==="
138
+
139
+ WINDOW=8
140
+ TOTAL_LINES="$(wc -l < "$CONVERT_MD")"
141
+
142
+ # Bounds of the content-integrity gate's defining section: from the "### Phase
143
+ # 1.8" heading to the line before the next top-level "### Phase 2" heading.
144
+ GATE_SECTION_START="$(grep -nE '^### Phase 1\.8' "$CONVERT_MD" | head -n1 | cut -d: -f1)"
145
+ GATE_SECTION_END="$(grep -nE '^### Phase 2' "$CONVERT_MD" | head -n1 | cut -d: -f1)"
146
+ assert_true "Phase 1.8 gate section start located" "[ -n '$GATE_SECTION_START' ]"
147
+ assert_true "Phase 2 boundary (gate section end) located" "[ -n '$GATE_SECTION_END' ]"
148
+
149
+ # Lines naming a destructive op via this skill's operative phrasings.
150
+ mapfile -t DESTRUCTIVE_LINES < <(
151
+ grep -nE 'shim/removal|managed-section overwrite|mirror replacement|source-eliminating op|replace the file with the .* shim|replace-with-symlink / replace-with-shim' "$CONVERT_MD" \
152
+ | cut -d: -f1
153
+ )
154
+
155
+ assert_true "found destructive-op sites to check" "[ \"\${#DESTRUCTIVE_LINES[@]}\" -gt 0 ]"
156
+
157
+ in_gate_section() {
158
+ local line="$1"
159
+ [ "$line" -ge "$GATE_SECTION_START" ] && [ "$line" -lt "$GATE_SECTION_END" ]
160
+ }
161
+
162
+ near_content_integrity() {
163
+ local line="$1"
164
+ local lo=$(( line - WINDOW )); [ "$lo" -lt 1 ] && lo=1
165
+ local hi=$(( line + WINDOW )); [ "$hi" -gt "$TOTAL_LINES" ] && hi="$TOTAL_LINES"
166
+ sed -n "${lo},${hi}p" "$CONVERT_MD" | grep -qiE 'content[- ]integrity'
167
+ }
168
+
169
+ ALL_GUARDED=1
170
+ for ln in "${DESTRUCTIVE_LINES[@]}"; do
171
+ if in_gate_section "$ln" || near_content_integrity "$ln"; then
172
+ continue
173
+ fi
174
+ ALL_GUARDED=0
175
+ echo " destructive-op site at line $ln is neither inside the gate section nor within +/- ${WINDOW} lines of a content-integrity reference" >&2
176
+ done
177
+ assert_true "every destructive-op site is gate-guarded (in gate section or near a content-integrity ref)" \
178
+ "[ '$ALL_GUARDED' -eq 1 ]"
179
+
180
+ # ---------------------------------------------------------------------------
181
+ # Test group 5: family skills ground on .agents/rules/*.md
182
+ # ---------------------------------------------------------------------------
183
+ echo ""
184
+ echo "=== Test group 5: design/implement/review-family skills ground on .agents/rules ==="
185
+
186
+ # The enumerated family skills whose grounding phase must read the project's
187
+ # force-loaded agent rules.
188
+ FAMILY_SKILLS=(
189
+ feature-design
190
+ epic-design
191
+ refactor-design
192
+ perf-design
193
+ e2e-test-design
194
+ implement
195
+ implement-orchestrator
196
+ review
197
+ fix
198
+ )
199
+
200
+ # grounds_on_rules <skill-md> — true iff the file references the
201
+ # `.agents/rules/*.md` path AND a "force-loaded" grounding descriptor appears
202
+ # within +/- GROUND_WINDOW lines of that path. Window-based so it tolerates the
203
+ # line-wrapping some skills use ("...the project's force-loaded" / "agent rules"
204
+ # on the next line) without weakening to a bare path match.
205
+ GROUND_WINDOW=2
206
+ grounds_on_rules() {
207
+ local md="$1"
208
+ local total; total="$(wc -l < "$md")"
209
+ local ln lo hi
210
+ while IFS=: read -r ln _; do
211
+ [ -z "$ln" ] && continue
212
+ lo=$(( ln - GROUND_WINDOW )); [ "$lo" -lt 1 ] && lo=1
213
+ hi=$(( ln + GROUND_WINDOW )); [ "$hi" -gt "$total" ] && hi="$total"
214
+ if sed -n "${lo},${hi}p" "$md" | grep -qi 'force-loaded'; then
215
+ return 0
216
+ fi
217
+ done < <(grep -n '\.agents/rules/\*\.md' "$md")
218
+ return 1
219
+ }
220
+
221
+ for skill in "${FAMILY_SKILLS[@]}"; do
222
+ md="${SKILLS_DIR}/${skill}/SKILL.md"
223
+ assert_true "${skill}/SKILL.md exists" "[ -f '$md' ]"
224
+ # Grounding context: the .agents/rules/*.md path paired with the recurring
225
+ # "force-loaded [agent rules]" grounding descriptor near that path.
226
+ assert_true "${skill} references .agents/rules/*.md path" \
227
+ "grep -q '\.agents/rules/\*\.md' '$md'"
228
+ assert_true "${skill} grounds .agents/rules as force-loaded agent rules" \
229
+ "grounds_on_rules '$md'"
230
+ done
231
+
232
+ # ---------------------------------------------------------------------------
233
+ # Summary
234
+ # ---------------------------------------------------------------------------
235
+ echo ""
236
+ echo "============================================================"
237
+ echo "Results: ${PASS} passed, ${FAIL} failed"
238
+ if [ ${#ERRORS[@]} -gt 0 ]; then
239
+ echo "Failed tests:"
240
+ for e in "${ERRORS[@]}"; do
241
+ echo " - $e"
242
+ done
243
+ fi
244
+ echo "============================================================"
245
+
246
+ [ "$FAIL" -eq 0 ] || exit 1
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env bash
2
+ # convert-install-routing.test.sh — structural regression guard for convert/SKILL.md
3
+ #
4
+ # The convert skill's install path MUST route through install-work-view.sh in
5
+ # BOTH the bootstrap branch (Phase 4: Create substrate skeleton) and the sync
6
+ # branch (Phase S3: Apply refreshes). A future edit that reverts either block to
7
+ # a raw `cp .../work-view.sh .work/bin/work-view` would silently bypass the
8
+ # platform-aware prebuilt selector the installer provides, and pass the direct
9
+ # installer tests while breaking the convert seam.
10
+ #
11
+ # This is a structural test on the SKILL.md prose (Recommendation A from the
12
+ # cross-model consult): for a markdown skill, guard the instructions as written.
13
+ # It extracts the bounded Phase 4 and Phase S3 blocks and asserts:
14
+ # 1. each block references install-work-view.sh
15
+ # 2. neither block contains a raw `cp ... work-view.sh ... .work/bin/work-view`
16
+ # 3. the work_view doctor marker references --version and drift_plugin
17
+ #
18
+ # Runnable locally and from CI. Exits non-zero if any assertion fails.
19
+
20
+ set -uo pipefail
21
+
22
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ SKILL_MD="${SCRIPT_DIR}/../../skills/convert/SKILL.md"
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Counters
27
+ # ---------------------------------------------------------------------------
28
+ PASS=0
29
+ FAIL=0
30
+ ERRORS=()
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # assert helpers
34
+ # ---------------------------------------------------------------------------
35
+ assert_true() {
36
+ local label="$1"
37
+ if eval "$2"; then
38
+ echo " PASS: $label"
39
+ ((PASS++))
40
+ else
41
+ echo " FAIL: $label"
42
+ echo " condition false: $2"
43
+ ((FAIL++))
44
+ ERRORS+=("$label")
45
+ fi
46
+ }
47
+
48
+ assert_false() {
49
+ local label="$1"
50
+ if ! eval "$2"; then
51
+ echo " PASS: $label"
52
+ ((PASS++))
53
+ else
54
+ echo " FAIL: $label"
55
+ echo " expected false but was true: $2"
56
+ ((FAIL++))
57
+ ERRORS+=("$label")
58
+ fi
59
+ }
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # extract_block <start-heading> <end-heading>
63
+ # Prints the lines of SKILL.md from the start heading (inclusive) up to but not
64
+ # including the end heading. Anchors on exact "### <Phase ...>" headings.
65
+ # ---------------------------------------------------------------------------
66
+ extract_block() {
67
+ local start="$1" end="$2"
68
+ awk -v start="$start" -v end="$end" '
69
+ $0 == start { collecting = 1 }
70
+ collecting && $0 == end { collecting = 0 }
71
+ collecting { print }
72
+ ' "$SKILL_MD"
73
+ }
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # Preflight: SKILL.md exists
77
+ # ---------------------------------------------------------------------------
78
+ echo ""
79
+ echo "=== Preflight: convert/SKILL.md present ==="
80
+ assert_true "convert/SKILL.md exists" "[ -f '$SKILL_MD' ]"
81
+ if [ ! -f "$SKILL_MD" ]; then
82
+ echo "ERROR: cannot locate convert/SKILL.md at $SKILL_MD" >&2
83
+ exit 1
84
+ fi
85
+
86
+ # ---------------------------------------------------------------------------
87
+ # Extract the two bounded blocks
88
+ # ---------------------------------------------------------------------------
89
+ PHASE4_BLOCK="$(extract_block "### Phase 4: Create substrate skeleton" "### Phase 5: Write CONVENTIONS.md")"
90
+ PHASES3_BLOCK="$(extract_block "### Phase S3: Apply refreshes" "### Phase S4: Preserve user state")"
91
+ DOCTOR_BLOCK="$(awk '
92
+ /\.work\/bin\/work-view` —/ { collecting = 1 }
93
+ collecting && /^ - `CLAUDE\.md` compatibility state/ { collecting = 0 }
94
+ collecting { print }
95
+ ' "$SKILL_MD")"
96
+
97
+ # A raw fallback copy regression looks like:
98
+ # cp "${PLUGIN_ROOT}/scripts/work-view.sh" .work/bin/work-view
99
+ # i.e. a single line containing all three of: cp, work-view.sh, .work/bin/work-view
100
+ RAW_CP_RE='cp.*work-view\.sh.*\.work/bin/work-view'
101
+
102
+ # ---------------------------------------------------------------------------
103
+ # Test group 1: blocks were located and are non-empty
104
+ # ---------------------------------------------------------------------------
105
+ echo ""
106
+ echo "=== Test group 1: phase blocks located ==="
107
+ assert_true "Phase 4 block is non-empty" "[ -n \"\$PHASE4_BLOCK\" ]"
108
+ assert_true "Phase 4 block starts at its heading" \
109
+ "printf '%s' \"\$PHASE4_BLOCK\" | head -n1 | grep -q '^### Phase 4: Create substrate skeleton$'"
110
+ assert_true "Phase S3 block is non-empty" "[ -n \"\$PHASES3_BLOCK\" ]"
111
+ assert_true "Phase S3 block starts at its heading" \
112
+ "printf '%s' \"\$PHASES3_BLOCK\" | head -n1 | grep -q '^### Phase S3: Apply refreshes$'"
113
+ assert_true "work_view doctor marker block is non-empty" "[ -n \"\$DOCTOR_BLOCK\" ]"
114
+
115
+ # ---------------------------------------------------------------------------
116
+ # Test group 2: both blocks route through install-work-view.sh
117
+ # ---------------------------------------------------------------------------
118
+ echo ""
119
+ echo "=== Test group 2: install routing present ==="
120
+ assert_true "Phase 4 references install-work-view.sh" \
121
+ "printf '%s' \"\$PHASE4_BLOCK\" | grep -q 'install-work-view\.sh'"
122
+ assert_true "Phase S3 references install-work-view.sh" \
123
+ "printf '%s' \"\$PHASES3_BLOCK\" | grep -q 'install-work-view\.sh'"
124
+
125
+ # ---------------------------------------------------------------------------
126
+ # Test group 3: neither block contains a raw fallback cp regression
127
+ # ---------------------------------------------------------------------------
128
+ echo ""
129
+ echo "=== Test group 3: no raw 'cp ... work-view.sh ... .work/bin/work-view' regression ==="
130
+ assert_false "Phase 4 has no raw cp fallback" \
131
+ "printf '%s' \"\$PHASE4_BLOCK\" | grep -Eq '$RAW_CP_RE'"
132
+ assert_false "Phase S3 has no raw cp fallback" \
133
+ "printf '%s' \"\$PHASES3_BLOCK\" | grep -Eq '$RAW_CP_RE'"
134
+
135
+ # ---------------------------------------------------------------------------
136
+ # Test group 4: doctor marker stays version-aware
137
+ # ---------------------------------------------------------------------------
138
+ echo ""
139
+ echo "=== Test group 4: work_view doctor marker is version-aware ==="
140
+ assert_true "work_view doctor marker references --version" \
141
+ "printf '%s' \"\$DOCTOR_BLOCK\" | grep -q -- '--version'"
142
+ assert_true "work_view doctor marker classifies drift_plugin" \
143
+ "printf '%s' \"\$DOCTOR_BLOCK\" | grep -q 'drift_plugin'"
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Summary
147
+ # ---------------------------------------------------------------------------
148
+ echo ""
149
+ echo "============================================================"
150
+ echo "Results: ${PASS} passed, ${FAIL} failed"
151
+ if [ ${#ERRORS[@]} -gt 0 ]; then
152
+ echo "Failed tests:"
153
+ for e in "${ERRORS[@]}"; do
154
+ echo " - $e"
155
+ done
156
+ fi
157
+ echo "============================================================"
158
+
159
+ [ "$FAIL" -eq 0 ] || exit 1