@thebassclef/lite 1.0.4 → 1.1.1

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 (275) hide show
  1. package/dist/cli.cjs +298 -55
  2. package/dist/cli.js +298 -55
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/lite/.claude/agents/architect.md +70 -0
  7. package/dist/lite/.claude/agents/builder.md +114 -0
  8. package/dist/lite/.claude/agents/designer.md +156 -0
  9. package/dist/lite/.claude/agents/reviewer.md +88 -0
  10. package/dist/lite/.claude/hooks/bassclef-sync.sh +85 -27
  11. package/dist/lite/.claude/hooks/longrun-prep-compounding-axis-check.sh +492 -0
  12. package/dist/lite/.claude/hooks/longrun-prep-render-check.sh +236 -0
  13. package/dist/lite/.claude/hooks/persona-name-scrub-check.sh +105 -0
  14. package/dist/lite/.claude/hooks/skill-step-list-check.sh +171 -0
  15. package/dist/lite/.claude/luminaries/alan-cooper.md +170 -0
  16. package/dist/lite/.claude/luminaries/alistair-cockburn.md +140 -0
  17. package/dist/lite/.claude/luminaries/amazon-pr-faq.md +34 -0
  18. package/dist/lite/.claude/luminaries/andreas-zeller.md +74 -0
  19. package/dist/lite/.claude/luminaries/ash-maurya.md +121 -0
  20. package/dist/lite/.claude/luminaries/bill-buxton.md +210 -0
  21. package/dist/lite/.claude/luminaries/charles-sanders-peirce.md +150 -0
  22. package/dist/lite/.claude/luminaries/david-ogilvy.md +192 -0
  23. package/dist/lite/.claude/luminaries/don-norman.md +173 -0
  24. package/dist/lite/.claude/luminaries/edward-tufte.md +179 -0
  25. package/dist/lite/.claude/luminaries/eric-evans.md +160 -0
  26. package/dist/lite/.claude/luminaries/frederick-brooks.md +50 -0
  27. package/dist/lite/.claude/luminaries/gang-of-four.md +157 -0
  28. package/dist/lite/.claude/luminaries/glenford-myers.md +230 -0
  29. package/dist/lite/.claude/luminaries/hunt-thomas.md +115 -0
  30. package/dist/lite/.claude/luminaries/hyrum-wright.md +94 -0
  31. package/dist/lite/.claude/luminaries/jason-fried-dhh.md +46 -0
  32. package/dist/lite/.claude/luminaries/jesse-james-garrett.md +154 -0
  33. package/dist/lite/.claude/luminaries/john-ousterhout.md +94 -0
  34. package/dist/lite/.claude/luminaries/karl-popper.md +132 -0
  35. package/dist/lite/.claude/luminaries/kent-beck.md +168 -0
  36. package/dist/lite/.claude/luminaries/linus-torvalds.md +218 -0
  37. package/dist/lite/.claude/luminaries/martin-fowler.md +164 -0
  38. package/dist/lite/.claude/luminaries/michael-feathers.md +125 -0
  39. package/dist/lite/.claude/luminaries/michael-nygard.md +118 -0
  40. package/dist/lite/.claude/luminaries/robert-c-martin.md +164 -0
  41. package/dist/lite/.claude/luminaries/saltzer-schroeder.md +194 -0
  42. package/dist/lite/.claude/luminaries/sophia-prater.md +193 -0
  43. package/dist/lite/.claude/luminaries/stephen-toulmin.md +131 -0
  44. package/dist/lite/.claude/luminaries/tony-hoare.md +170 -0
  45. package/dist/lite/.claude/luminaries/vaughn-vernon.md +50 -0
  46. package/dist/lite/.claude/luminaries/w-edwards-deming.md +158 -0
  47. package/dist/lite/.claude/rules/accessor-library-discipline.md +110 -0
  48. package/dist/lite/.claude/rules/adr-discipline.md +96 -0
  49. package/dist/lite/.claude/rules/api-conventions.md +115 -0
  50. package/dist/lite/.claude/rules/artifact-ingestion.md +137 -0
  51. package/dist/lite/.claude/rules/assert-only-after-verify.md +111 -0
  52. package/dist/lite/.claude/rules/blocked-items.md +139 -0
  53. package/dist/lite/.claude/rules/bootstrap-pair-discipline.md +106 -0
  54. package/dist/lite/.claude/rules/branching.md +28 -0
  55. package/dist/lite/.claude/rules/cold-adopter-harness-discipline.md +93 -0
  56. package/dist/lite/.claude/rules/commit-conventions.md +22 -0
  57. package/dist/lite/.claude/rules/compounding-axis-fresh-analysis.md +173 -0
  58. package/dist/lite/.claude/rules/compounding-sequence-fresh-analysis.md +173 -0
  59. package/dist/lite/.claude/rules/context-engineering.md +155 -0
  60. package/dist/lite/.claude/rules/context-management.md +85 -0
  61. package/dist/lite/.claude/rules/defensive-bash.md +52 -0
  62. package/dist/lite/.claude/rules/deferred-actions.md +233 -0
  63. package/dist/lite/.claude/rules/destructive-operations.md +69 -0
  64. package/dist/lite/.claude/rules/diagnosis.md +38 -0
  65. package/dist/lite/.claude/rules/github-issue-flash-tweet.md +114 -0
  66. package/dist/lite/.claude/rules/guardrails.md +73 -0
  67. package/dist/lite/.claude/rules/hook-wire-on-author.md +77 -0
  68. package/dist/lite/.claude/rules/identifier-leak-prevention.md +73 -0
  69. package/dist/lite/.claude/rules/iteration-bet-brief-completeness.md +43 -0
  70. package/dist/lite/.claude/rules/lite-manifest-schema-change-discipline.md +79 -0
  71. package/dist/lite/.claude/rules/longrun-prep-plan-doc-compression.md +77 -0
  72. package/dist/lite/.claude/rules/loop-discipline.md +71 -0
  73. package/dist/lite/.claude/rules/manual-prod-approval.md +100 -0
  74. package/dist/lite/.claude/rules/marker-enrichment-discipline.md +72 -0
  75. package/dist/lite/.claude/rules/mobile-ephemeral-session.md +109 -0
  76. package/dist/lite/.claude/rules/new-dependency-check.md +44 -0
  77. package/dist/lite/.claude/rules/oo-ad-entry-point.md +95 -0
  78. package/dist/lite/.claude/rules/operator-facing-prose.md +177 -0
  79. package/dist/lite/.claude/rules/option-label-discipline.md +82 -0
  80. package/dist/lite/.claude/rules/pattern-annotation.md +83 -0
  81. package/dist/lite/.claude/rules/persona-name-leak-scrub.md +90 -0
  82. package/dist/lite/.claude/rules/plain-english-discipline.md +132 -0
  83. package/dist/lite/.claude/rules/plan-enumeration-needs-value-props.md +177 -0
  84. package/dist/lite/.claude/rules/pr-body-shape.md +195 -0
  85. package/dist/lite/.claude/rules/pr-strategy.md +142 -0
  86. package/dist/lite/.claude/rules/pr-title-shape.md +90 -0
  87. package/dist/lite/.claude/rules/prototype-workflow.md +53 -0
  88. package/dist/lite/.claude/rules/reserved-skill-names.md +103 -0
  89. package/dist/lite/.claude/rules/schema-management.md +42 -0
  90. package/dist/lite/.claude/rules/sdlc-gates.md +149 -0
  91. package/dist/lite/.claude/rules/security.md +37 -0
  92. package/dist/lite/.claude/rules/session-artifacts.md +236 -0
  93. package/dist/lite/.claude/rules/skill-composition-declarations.md +105 -0
  94. package/dist/lite/.claude/rules/skill-description-clarity.md +196 -0
  95. package/dist/lite/.claude/rules/skill-procedure-step-list.md +119 -0
  96. package/dist/lite/.claude/rules/sprint-prep-preset-picker.md +76 -0
  97. package/dist/lite/.claude/rules/state-schema-validation.md +156 -0
  98. package/dist/lite/.claude/rules/stuck-signal-diagnostic.md +111 -0
  99. package/dist/lite/.claude/rules/substrate-config-schema.md +85 -0
  100. package/dist/lite/.claude/rules/test-list-discipline.md +129 -0
  101. package/dist/lite/.claude/rules/test-sufficiency.md +186 -0
  102. package/dist/lite/.claude/rules/testing-tier-config.md +125 -0
  103. package/dist/lite/.claude/rules/testing.md +38 -0
  104. package/dist/lite/.claude/rules/turn-estimate-grounding.md +82 -0
  105. package/dist/lite/.claude/rules/visual-hierarchy.md +427 -0
  106. package/dist/lite/.claude/rules/we-dont-break-adopters.md +103 -0
  107. package/dist/lite/.claude/rules/whereami-load-bearing.md +184 -0
  108. package/dist/lite/.claude/rules/writing-craft-discipline.md +72 -0
  109. package/dist/lite/.claude/rules/wu-sequencing-compounds.md +119 -0
  110. package/dist/lite/.claude/skills/build/SKILL.md +640 -0
  111. package/dist/lite/.claude/skills/chronicle/SKILL.md +55 -0
  112. package/dist/lite/.claude/skills/clean-artifacts/SKILL.md +249 -0
  113. package/dist/lite/.claude/skills/decompose/SKILL.md +280 -0
  114. package/dist/lite/.claude/skills/diagnose/SKILL.md +297 -0
  115. package/dist/lite/.claude/skills/feynman/SKILL.md +90 -0
  116. package/dist/lite/.claude/skills/howdoi/SKILL.md +105 -0
  117. package/dist/lite/.claude/skills/ia-model/SKILL.md +108 -0
  118. package/dist/lite/.claude/skills/interaction-design/SKILL.md +112 -0
  119. package/dist/lite/.claude/skills/interpret-input/SKILL.md +180 -0
  120. package/dist/lite/.claude/skills/journal/SKILL.md +209 -0
  121. package/dist/lite/.claude/skills/kiss/SKILL.md +449 -0
  122. package/dist/lite/.claude/skills/launch/SKILL.md +915 -0
  123. package/dist/lite/.claude/skills/lean-canvas/SKILL.md +332 -0
  124. package/dist/lite/.claude/skills/longrun/SKILL.md +512 -0
  125. package/dist/lite/.claude/skills/luminary/SKILL.md +481 -0
  126. package/dist/lite/.claude/skills/ogilvy-writing-audit/SKILL.md +177 -0
  127. package/dist/lite/.claude/skills/onboard-repo/SKILL.md +1640 -0
  128. package/dist/lite/.claude/skills/pattern-review/SKILL.md +99 -0
  129. package/dist/lite/.claude/skills/personas/SKILL.md +207 -0
  130. package/dist/lite/.claude/skills/promote/SKILL.md +283 -0
  131. package/dist/lite/.claude/skills/requirement/SKILL.md +98 -0
  132. package/dist/lite/.claude/skills/retro/SKILL.md +117 -0
  133. package/dist/lite/.claude/skills/riff/SKILL.md +114 -0
  134. package/dist/lite/.claude/skills/roadmap-reconcile/SKILL.md +163 -0
  135. package/dist/lite/.claude/skills/session-end/SKILL.md +309 -0
  136. package/dist/lite/.claude/skills/session-log/SKILL.md +299 -0
  137. package/dist/lite/.claude/skills/skills/SKILL.md +228 -0
  138. package/dist/lite/.claude/skills/spec/SKILL.md +105 -0
  139. package/dist/lite/.claude/skills/sprint/SKILL.md +418 -0
  140. package/dist/lite/.claude/skills/stage/SKILL.md +384 -0
  141. package/dist/lite/.claude/skills/state-a-problem/SKILL.md +185 -0
  142. package/dist/lite/.claude/skills/temperance/SKILL.md +108 -0
  143. package/dist/lite/.claude/skills/use-case/SKILL.md +417 -0
  144. package/dist/lite/.claude/skills/user-stories/SKILL.md +268 -0
  145. package/dist/lite/.claude/skills/value-prop/SKILL.md +251 -0
  146. package/dist/lite/.claude/skills/verify/SKILL.md +160 -0
  147. package/dist/lite/.claude/skills/visual-review/SKILL.md +503 -0
  148. package/dist/lite/.claude/skills/whats-the-plan/SKILL.md +202 -0
  149. package/dist/lite/.claude/skills/whereami/SKILL.md +307 -0
  150. package/dist/lite/AGENTS.md +79 -0
  151. package/dist/lite/CLAUDE-lite.md +85 -0
  152. package/dist/lite/CODE_OF_CONDUCT.md +28 -0
  153. package/dist/lite/CONTRIBUTING.md +177 -0
  154. package/dist/lite/README.md +173 -0
  155. package/dist/lite/SECURITY.md +19 -0
  156. package/dist/lite/architecture/decisions/ADR-029-release-pipeline.md +79 -0
  157. package/dist/lite/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +139 -0
  158. package/dist/lite/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +192 -0
  159. package/dist/lite/architecture/decisions/ADR-039-release-tagging-scheme.md +145 -0
  160. package/dist/lite/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +155 -0
  161. package/dist/lite/architecture/decisions/ADR-044-unified-skill-body-template.md +162 -0
  162. package/dist/lite/lib/clean-artifacts-sweep.sh +112 -0
  163. package/dist/lite/lib/code-comment-discipline.sh +144 -0
  164. package/dist/lite/lib/composer-preflight.sh +459 -0
  165. package/dist/lite/lib/deferred-actions.sh +146 -0
  166. package/dist/lite/lib/hook-header-parser.sh +61 -0
  167. package/dist/lite/lib/hook-heartbeat.sh +152 -0
  168. package/dist/lite/lib/hook-inject.sh +255 -0
  169. package/dist/lite/lib/hook-installer.sh +195 -0
  170. package/dist/lite/lib/luminary-pick.sh +96 -0
  171. package/dist/lite/lib/output-discipline.sh +143 -0
  172. package/dist/lite/lib/prose-scan-boundary.sh +171 -0
  173. package/dist/lite/lib/rewrite-check.sh +214 -0
  174. package/dist/lite/lib/session-rescue.sh +151 -0
  175. package/dist/lite/lib/source-graph-walker.sh +277 -0
  176. package/dist/lite/lib/state.sh +1372 -0
  177. package/dist/lite/lib/telemetry.sh +205 -0
  178. package/dist/lite/lib/tier-check.sh +187 -0
  179. package/dist/lite/lib/tier-dependency-audit.sh +1088 -0
  180. package/dist/lite/presence/install/bassclef-hook-connect.sh +178 -0
  181. package/dist/lite/presence/install/bassclef-sync.dispatcher.template.sh +841 -0
  182. package/dist/lite/presence/install/bassclef-sync.template.sh +2278 -0
  183. package/dist/lite/presence/install/schedule-auto-save.cron.sh +88 -0
  184. package/dist/lite/presence/install/schedule-auto-save.taskscheduler.md +122 -0
  185. package/dist/lite/scripts/aggregate-telemetry.sh +217 -0
  186. package/dist/lite/scripts/analyze-tier-dependencies.sh +239 -0
  187. package/dist/lite/scripts/generate-lite-manifest.sh +505 -0
  188. package/dist/lite/scripts/generate-tier-manifest.sh +28 -0
  189. package/dist/lite/scripts/intent-drift-check.sh +456 -0
  190. package/dist/lite/scripts/lite-manifest-drift-check.sh +146 -0
  191. package/dist/lite/scripts/render-lite-manifest-doc.sh +150 -0
  192. package/dist/lite/standards/adr-template.md +86 -0
  193. package/dist/lite/standards/api-conventions/nextjs.md +84 -0
  194. package/dist/lite/standards/artifact-composition.md +209 -0
  195. package/dist/lite/standards/bash-hook-safety.md +246 -0
  196. package/dist/lite/standards/bassclef-configs-schema.md +232 -0
  197. package/dist/lite/standards/bassclef-evolution.md +143 -0
  198. package/dist/lite/standards/bassclef-internal-jargon.md +244 -0
  199. package/dist/lite/standards/bassclef-managed-sentinel.md +96 -0
  200. package/dist/lite/standards/bassclef-source-config.md +228 -0
  201. package/dist/lite/standards/branch-stacking.md +408 -0
  202. package/dist/lite/standards/code-safety-principles.md +176 -0
  203. package/dist/lite/standards/composer-prerequisites.md +155 -0
  204. package/dist/lite/standards/deferred-actions-schema.md +204 -0
  205. package/dist/lite/standards/dependency-discipline/cargo.md +39 -0
  206. package/dist/lite/standards/dependency-discipline/gem.md +43 -0
  207. package/dist/lite/standards/dependency-discipline/go-mod.md +41 -0
  208. package/dist/lite/standards/dependency-discipline/npm.md +42 -0
  209. package/dist/lite/standards/dependency-discipline/pip.md +42 -0
  210. package/dist/lite/standards/deployment-topology/ec2-tailscale.md +225 -0
  211. package/dist/lite/standards/deployment-topology.md +69 -0
  212. package/dist/lite/standards/docs-sync-allowlist.md +76 -0
  213. package/dist/lite/standards/domain-and-dns.md +145 -0
  214. package/dist/lite/standards/frontend-stack.md +67 -0
  215. package/dist/lite/standards/frontmatter-schema.md +154 -0
  216. package/dist/lite/standards/graceful-exit.md +227 -0
  217. package/dist/lite/standards/hook-idempotency.md +102 -0
  218. package/dist/lite/standards/hook-injection-discipline.md +202 -0
  219. package/dist/lite/standards/hook-install-class.md +215 -0
  220. package/dist/lite/standards/input-handler-interface.md +152 -0
  221. package/dist/lite/standards/lite-manifest-schema-changes.md +180 -0
  222. package/dist/lite/standards/lite-manifest.json +2797 -0
  223. package/dist/lite/standards/luminary-matching.md +105 -0
  224. package/dist/lite/standards/luminary-problem-patterns.md +481 -0
  225. package/dist/lite/standards/migration-discipline/active-record.md +50 -0
  226. package/dist/lite/standards/migration-discipline/alembic.md +43 -0
  227. package/dist/lite/standards/migration-discipline/gorm.md +50 -0
  228. package/dist/lite/standards/migration-discipline/prisma.md +53 -0
  229. package/dist/lite/standards/migration-discipline/sqlalchemy.md +51 -0
  230. package/dist/lite/standards/mobile-ephemeral-session.md +167 -0
  231. package/dist/lite/standards/model-routing-discipline.md +160 -0
  232. package/dist/lite/standards/ogilvy-writing-rules.md +225 -0
  233. package/dist/lite/standards/opener-discipline.md +96 -0
  234. package/dist/lite/standards/operator-facing-prose-discipline.md +201 -0
  235. package/dist/lite/standards/persona-schema.md +229 -0
  236. package/dist/lite/standards/pluggable-luminaries.md +323 -0
  237. package/dist/lite/standards/pr-body-discipline.md +115 -0
  238. package/dist/lite/standards/preview-state-schema.md +189 -0
  239. package/dist/lite/standards/project-directory-layout.md +276 -0
  240. package/dist/lite/standards/release-tagging.md +137 -0
  241. package/dist/lite/standards/reserved-skill-names.md +120 -0
  242. package/dist/lite/standards/scannable-multi-option-output.md +265 -0
  243. package/dist/lite/standards/sdlc-compliance.md +286 -0
  244. package/dist/lite/standards/sdlc-gates/typescript.md +57 -0
  245. package/dist/lite/standards/secrets-lifecycle.md +210 -0
  246. package/dist/lite/standards/security-scanner-adapter.md +145 -0
  247. package/dist/lite/standards/session-board.md +256 -0
  248. package/dist/lite/standards/skill-output-discipline.md +121 -0
  249. package/dist/lite/standards/state-spine-contract.md +255 -0
  250. package/dist/lite/standards/state-spine.md +511 -0
  251. package/dist/lite/standards/steering-hints/kiss-words.md +11 -0
  252. package/dist/lite/standards/substrate-config-schema.md +267 -0
  253. package/dist/lite/standards/tech-stack-config.md +109 -0
  254. package/dist/lite/standards/tier-dependency-analysis.md +167 -0
  255. package/dist/lite/standards/tier-runtime-deps/lite.md +57 -0
  256. package/dist/lite/standards/tier-tag-schema.md +155 -0
  257. package/dist/lite/standards/two-layer-config.md +99 -0
  258. package/dist/lite/standards/use-case-format.md +292 -0
  259. package/dist/lite/standards/user-story-invest.md +268 -0
  260. package/dist/lite/standards/velocity-and-appetite.md +229 -0
  261. package/dist/lite/standards/voice-input-pattern.md +119 -0
  262. package/dist/lite/standards/whereami-schema.md +301 -0
  263. package/dist/lite/standards/worktree-management.md +211 -0
  264. package/dist/lite/standards/writing-guide.md +213 -0
  265. package/dist/lite/templates/chronicle-template.md +75 -0
  266. package/dist/lite/templates/deferred-action-template.md +45 -0
  267. package/dist/lite/templates/memory-proposal-template.md +77 -0
  268. package/dist/lite/templates/persona-template.md +200 -0
  269. package/dist/lite/templates/pr-faq.md +45 -0
  270. package/dist/lite/templates/secret-rotation-template.md +162 -0
  271. package/dist/lite/templates/spec-template.md +131 -0
  272. package/dist/lite/templates/use-case-template.md +194 -0
  273. package/dist/lite/templates/user-story-template.md +107 -0
  274. package/dist/lite/templates/whereami-template.md +101 -0
  275. package/package.json +12 -5
@@ -0,0 +1,459 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # lib/composer-preflight.sh — shared pre-flight check for composer skills.
4
+ #
5
+ # Composer skills (/launch, /build, /shape full) source this helper
6
+ # in Phase -1 (before Phase 0). Surfaces 6 prerequisites at once:
7
+ # 1. feature-branch — repo on a feature branch (refuses on main)
8
+ # 2. state-spine — lib/state.sh present (AUTO-PREP from bassclef)
9
+ # 3. personas — at least 1 persona scaffolded (signal-and-offer)
10
+ # 4. budget — iteration_ceiling sufficient for estimate
11
+ # 5. skill-registry — dispatching skill resolves (covers bassclef#271)
12
+ # 6. deploy-host — hosting_platform set + adapter shipped (bassclef#449/#450)
13
+ #
14
+ # Usage (from a composer SKILL.md procedure):
15
+ # source $BASSCLEF_DIR/lib/composer-preflight.sh
16
+ # composer_preflight_check <skill_name> <estimated_turns> [--no-launch]
17
+ # case $? in
18
+ # 0) ;; # all pass — proceed to Phase 0
19
+ # 1) ;; # warnings — composer prompts operator proceed/abort
20
+ # 2) exit 1 ;; # failures — composer refuses dispatch
21
+ # esac
22
+ #
23
+ # --no-launch: variants-only run; Check 6 short-circuits to pass.
24
+ # Use when /launch is invoked for variant generation only,
25
+ # no deploy-host chain. /stage (light) is the typical caller.
26
+ #
27
+ # Override: SKIP_COMPOSER_PREFLIGHT=1 (logged via trace-helper).
28
+ #
29
+ # Standard: standards/composer-prerequisites.md
30
+ # Refs bassclef#408 (preflight scaffold) + bassclef#449/#450 (deploy-host check).
31
+
32
+ # Don't use `set -e` — preflight checks must complete even when one fails.
33
+
34
+ # Output format (key=value lines on stdout):
35
+ # check_<n>_name=<check name>
36
+ # check_<n>_status=<pass|warn|fail>
37
+ # check_<n>_message=<single-line message>
38
+ # check_<n>_recommendation=<single-line recommendation>
39
+ # summary_status=<pass|warn|fail>
40
+ # summary_count_pass=<n>
41
+ # summary_count_warn=<n>
42
+ # summary_count_fail=<n>
43
+
44
+ composer_preflight_check() {
45
+ local skill_name="${1:-}"
46
+ local estimated_turns="${2:-50}"
47
+ local mode_flag="${3:-}" # optional: --no-launch for variants-only runs
48
+
49
+ # Override hatch
50
+ if [ "${SKIP_COMPOSER_PREFLIGHT:-0}" = "1" ]; then
51
+ if command -v trace_log >/dev/null 2>&1; then
52
+ trace_log "composer-preflight" "OVERRIDE skill=$skill_name" 2>/dev/null || true
53
+ fi
54
+ echo "summary_status=skip"
55
+ return 0
56
+ fi
57
+
58
+ if [ -z "$skill_name" ]; then
59
+ echo "summary_status=fail" >&2
60
+ echo "ERROR: composer_preflight_check requires skill_name" >&2
61
+ return 2
62
+ fi
63
+
64
+ local pass=0 warn=0 fail=0
65
+
66
+ # === Check 1: feature-branch ===
67
+ local current_branch
68
+ current_branch=$(git branch --show-current 2>/dev/null || echo "")
69
+ echo "check_1_name=feature-branch"
70
+ if [ -z "$current_branch" ]; then
71
+ echo "check_1_status=fail"
72
+ echo "check_1_message=Not in a git repository OR branch detection failed"
73
+ echo "check_1_recommendation=Run from inside a git repository"
74
+ fail=$((fail + 1))
75
+ elif [ "$current_branch" = "main" ] || [ "$current_branch" = "master" ]; then
76
+ echo "check_1_status=fail"
77
+ echo "check_1_message=On $current_branch — composer skills refuse to run from default branch"
78
+ echo "check_1_recommendation=git checkout -b feature/<slug>"
79
+ fail=$((fail + 1))
80
+ else
81
+ echo "check_1_status=pass"
82
+ echo "check_1_message=On feature branch: $current_branch"
83
+ echo "check_1_recommendation="
84
+ pass=$((pass + 1))
85
+ fi
86
+
87
+ # === Check 2: state-spine ===
88
+ local repo_root
89
+ repo_root=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
90
+ echo "check_2_name=state-spine"
91
+ if [ -f "$repo_root/lib/state.sh" ]; then
92
+ echo "check_2_status=pass"
93
+ echo "check_2_message=lib/state.sh present"
94
+ echo "check_2_recommendation="
95
+ pass=$((pass + 1))
96
+ else
97
+ # Auto-prep available: bootstrap from BASSCLEF_DIR if reachable
98
+ local bassclef_state="${BASSCLEF_DIR:-}/lib/state.sh"
99
+ if [ -n "${BASSCLEF_DIR:-}" ] && [ -f "$bassclef_state" ]; then
100
+ echo "check_2_status=warn"
101
+ echo "check_2_message=lib/state.sh missing; AUTO-PREP available from $bassclef_state"
102
+ echo "check_2_recommendation=cp $bassclef_state $repo_root/lib/state.sh # safe to auto-execute"
103
+ warn=$((warn + 1))
104
+ else
105
+ echo "check_2_status=fail"
106
+ echo "check_2_message=lib/state.sh missing AND BASSCLEF_DIR not reachable"
107
+ echo "check_2_recommendation=Run /onboard-repo to bootstrap bassclef substrate"
108
+ fail=$((fail + 1))
109
+ fi
110
+ fi
111
+
112
+ # === Check 3: personas ===
113
+ echo "check_3_name=personas"
114
+ local persona_count=0
115
+ if [ -d "$repo_root/docs/personas" ]; then
116
+ persona_count=$(find "$repo_root/docs/personas" -maxdepth 1 -name "*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
117
+ fi
118
+ if [ "$persona_count" -ge 1 ]; then
119
+ echo "check_3_status=pass"
120
+ echo "check_3_message=$persona_count persona(s) scaffolded"
121
+ echo "check_3_recommendation="
122
+ pass=$((pass + 1))
123
+ else
124
+ echo "check_3_status=warn"
125
+ echo "check_3_message=No personas scaffolded; composers default to operator-as-persona"
126
+ echo "check_3_recommendation=Run /personas to seed primary persona OR proceed with operator-only"
127
+ warn=$((warn + 1))
128
+ fi
129
+
130
+ # === Check 4: budget ===
131
+ echo "check_4_name=budget"
132
+ local budget_estimate=$(( estimated_turns * 10 / 100 )) # rough $0.10/turn proxy
133
+ local ceiling=""
134
+ if [ -f "$repo_root/substrate.config.md" ]; then
135
+ # Use [[:space:]] (POSIX) instead of \s (GNU extension) for BSD sed compat
136
+ ceiling=$(grep -E '^[[:space:]]*iteration_ceiling:' "$repo_root/substrate.config.md" 2>/dev/null \
137
+ | head -1 | sed 's/.*:[[:space:]]*//;s/[[:space:]].*//;s/^\$//' || echo "")
138
+ fi
139
+ if [ -z "$ceiling" ] || [ "$ceiling" = "0" ]; then
140
+ # Sequential mode (no burst cost) — pass
141
+ echo "check_4_status=pass"
142
+ echo "check_4_message=Sequential mode (\$0 burst); estimated $estimated_turns turns"
143
+ echo "check_4_recommendation="
144
+ pass=$((pass + 1))
145
+ elif ! [[ "$ceiling" =~ ^[0-9]+$ ]]; then
146
+ # Non-numeric ceiling (e.g., free-form description) — pass with note
147
+ echo "check_4_status=pass"
148
+ echo "check_4_message=iteration_ceiling: $ceiling (free-form); estimated $estimated_turns turns"
149
+ echo "check_4_recommendation="
150
+ pass=$((pass + 1))
151
+ elif [ "$ceiling" -lt "$budget_estimate" ]; then
152
+ echo "check_4_status=warn"
153
+ echo "check_4_message=iteration_ceiling \$$ceiling < estimated \$$budget_estimate ($estimated_turns turns)"
154
+ echo "check_4_recommendation=Bump iteration_ceiling in substrate.config.md OR reduce composer tier"
155
+ warn=$((warn + 1))
156
+ else
157
+ echo "check_4_status=pass"
158
+ echo "check_4_message=iteration_ceiling \$$ceiling >= estimated \$$budget_estimate"
159
+ echo "check_4_recommendation="
160
+ pass=$((pass + 1))
161
+ fi
162
+
163
+ # === Check 5: skill-registry ===
164
+ # Detects bassclef#271 race — skill not picked up by harness registry.
165
+ # Read-and-check approach: the skill MUST exist as SKILL.md at a discoverable
166
+ # path. Three discovery paths:
167
+ # a) ~/.claude/skills/<skill>/SKILL.md (user-level symlink — harness primary)
168
+ # b) <repo>/.claude/skills/<skill>/SKILL.md (repo-level — fallback)
169
+ # c) <BASSCLEF_DIR>/.claude/skills/<skill>/SKILL.md (bassclef authoritative)
170
+ echo "check_5_name=skill-registry"
171
+ local user_skill="$HOME/.claude/skills/$skill_name/SKILL.md"
172
+ local repo_skill="$repo_root/.claude/skills/$skill_name/SKILL.md"
173
+ local bassclef_skill="${BASSCLEF_DIR:-}/.claude/skills/$skill_name/SKILL.md"
174
+
175
+ if [ -f "$user_skill" ] || [ -L "$user_skill" ]; then
176
+ echo "check_5_status=pass"
177
+ echo "check_5_message=Skill resolves at user-level: $user_skill"
178
+ echo "check_5_recommendation="
179
+ pass=$((pass + 1))
180
+ elif [ -f "$repo_skill" ] || [ -L "$repo_skill" ]; then
181
+ echo "check_5_status=warn"
182
+ echo "check_5_message=Skill at repo-level only ($repo_skill); user-level symlink missing — harness may not pick up (bassclef#271)"
183
+ echo "check_5_recommendation=Run bassclef-sync.sh to create user-level symlink OR restart Claude session"
184
+ warn=$((warn + 1))
185
+ elif [ -n "${BASSCLEF_DIR:-}" ] && [ -f "$bassclef_skill" ]; then
186
+ echo "check_5_status=warn"
187
+ echo "check_5_message=Skill at bassclef only; consumer-side links missing — substrate sync incomplete"
188
+ echo "check_5_recommendation=Run bassclef-sync.sh in this repo OR /onboard-repo"
189
+ warn=$((warn + 1))
190
+ else
191
+ echo "check_5_status=fail"
192
+ echo "check_5_message=Skill /$skill_name not found at any discovery path"
193
+ echo "check_5_recommendation=Verify skill name OR check bassclef substrate sync"
194
+ fail=$((fail + 1))
195
+ fi
196
+
197
+ # === Check 6: deploy-host (bassclef#449/#450 + bassclef#523) ===
198
+ #
199
+ # Reads hosting_platform from substrate.config.md and verifies (a) a deploy
200
+ # adapter is shipped AND (b) provisioning has actually happened (the
201
+ # per-platform provisioning-indicator field is populated with a real value,
202
+ # not the schema's bracketed placeholder). Catches the preview→production
203
+ # gap before /launch burns 200t AND the silent-defer-bind class
204
+ # where Phase 12 silently no-ops because the consumer never ran
205
+ # /provision-deploy-host (bassclef#523 — today's POA mobile session).
206
+ #
207
+ # Logic:
208
+ # missing hosting_platform → fail (set hosting_platform: amplify)
209
+ # hosting_platform=none + --no-launch → pass (variants-only run)
210
+ # hosting_platform=none + no --no-launch → fail (refuse dispatch)
211
+ # adapter shipped + indicator populated → pass
212
+ # adapter shipped + indicator MISSING → fail with three-path remediation:
213
+ # run /provision-deploy-host inline / re-dispatch with --no-launch /
214
+ # explicit defer
215
+ # adapter not shipped (vercel | netlify | cf-pages) → warn
216
+ # any other value → fail (not in enum)
217
+ #
218
+ # Per-platform provisioning indicators (set by /provision-deploy-host on success):
219
+ # amplify → amplify_app_id (e.g., d154l5usyc80nk)
220
+ # ec2-tailscale → deploy_host (e.g., poa-prod.tailnet.ts.net)
221
+ # mock → no indicator required (test scaffold)
222
+ #
223
+ # The override --no-launch lets /launch run in variants-only mode
224
+ # (e.g., /stage light callers) without requiring deploy-host provisioning.
225
+ echo "check_6_name=deploy-host"
226
+ local hosting=""
227
+ if [ -f "$repo_root/substrate.config.md" ]; then
228
+ hosting=$(grep -E '^[[:space:]]*hosting_platform:' "$repo_root/substrate.config.md" 2>/dev/null \
229
+ | head -1 | sed 's/.*:[[:space:]]*//;s/[[:space:]].*//;s/^["'\'']//;s/["'\'']$//')
230
+ fi
231
+ local bassclef_adapter_dir="${BASSCLEF_DIR:-}/scripts/adapters/deploy"
232
+ local adapter_check=""
233
+ if [ -n "$hosting" ] && [ -n "${BASSCLEF_DIR:-}" ]; then
234
+ adapter_check="${bassclef_adapter_dir}/${hosting}.sh"
235
+ fi
236
+
237
+ # Helper: read the per-platform provisioning-indicator field from
238
+ # substrate.config.md. Returns the field value if populated and not the
239
+ # bracketed placeholder shape (e.g., `[e.g., d154l...]`); empty otherwise.
240
+ _check6_provisioning_indicator_value() {
241
+ local field="$1"
242
+ [ -f "$repo_root/substrate.config.md" ] || return 0
243
+ local val
244
+ val=$(grep -E "^[[:space:]]*${field}:" "$repo_root/substrate.config.md" 2>/dev/null \
245
+ | head -1 | sed 's/.*:[[:space:]]*//;s/[[:space:]]*#.*$//;s/^["'\'']//;s/["'\'']$//')
246
+ # Reject placeholders (bracketed values are schema templates, not real values)
247
+ case "$val" in
248
+ "" | \[*\] | "[]" )
249
+ echo ""
250
+ ;;
251
+ *)
252
+ echo "$val"
253
+ ;;
254
+ esac
255
+ }
256
+
257
+ if [ -z "$hosting" ]; then
258
+ echo "check_6_status=fail"
259
+ echo "check_6_message=hosting_platform missing from substrate.config.md"
260
+ echo "check_6_recommendation=Add 'hosting_platform: amplify' (bassclef default per ADR-008) OR 'hosting_platform: none' if no UI ships"
261
+ fail=$((fail + 1))
262
+ elif [ "$hosting" = "none" ]; then
263
+ if [ "$mode_flag" = "--no-launch" ]; then
264
+ echo "check_6_status=pass"
265
+ echo "check_6_message=hosting_platform=none + --no-launch (variants-only run)"
266
+ echo "check_6_recommendation="
267
+ pass=$((pass + 1))
268
+ else
269
+ echo "check_6_status=fail"
270
+ echo "check_6_message=hosting_platform=none — composer would generate variants that can't launch"
271
+ echo "check_6_recommendation=Set hosting_platform to a deploy provider (amplify) OR re-run with --no-launch for variants-only"
272
+ fail=$((fail + 1))
273
+ fi
274
+ elif [ -n "$adapter_check" ] && [ -f "$adapter_check" ]; then
275
+ # Adapter shipped — now verify provisioning indicator is populated.
276
+ # --no-launch override skips this gate (variants-only run).
277
+ if [ "$mode_flag" = "--no-launch" ]; then
278
+ echo "check_6_status=pass"
279
+ echo "check_6_message=hosting_platform=$hosting + --no-launch (variants-only; provisioning indicator gate bypassed)"
280
+ echo "check_6_recommendation="
281
+ pass=$((pass + 1))
282
+ else
283
+ local indicator_field=""
284
+ case "$hosting" in
285
+ amplify) indicator_field="amplify_app_id" ;;
286
+ ec2-tailscale) indicator_field="deploy_host" ;;
287
+ mock) indicator_field="" ;; # test scaffold, no indicator required
288
+ esac
289
+ local indicator_value=""
290
+ if [ -n "$indicator_field" ]; then
291
+ indicator_value=$(_check6_provisioning_indicator_value "$indicator_field")
292
+ fi
293
+ if [ -z "$indicator_field" ] || [ -n "$indicator_value" ]; then
294
+ echo "check_6_status=pass"
295
+ if [ -n "$indicator_field" ]; then
296
+ echo "check_6_message=hosting_platform=$hosting; adapter shipped; ${indicator_field}=${indicator_value}"
297
+ else
298
+ echo "check_6_message=hosting_platform=$hosting; adapter shipped (no indicator required for $hosting)"
299
+ fi
300
+ echo "check_6_recommendation="
301
+ pass=$((pass + 1))
302
+ else
303
+ echo "check_6_status=fail"
304
+ echo "check_6_message=hosting_platform=$hosting; adapter shipped but ${indicator_field} not populated in substrate.config.md — /provision-deploy-host has not run for this consumer"
305
+ echo "check_6_recommendation=Three paths: (a) run /provision-deploy-host (creates ${hosting} app + populates ${indicator_field}; ~1-2h, mostly waiting); (b) re-dispatch with --no-launch for variants-only (no deploy host needed); (c) explicit defer — tell the agent 'skip launch deploy-host check this session' (per blocked-items.md deferral syntax)"
306
+ fail=$((fail + 1))
307
+ fi
308
+ fi
309
+ elif [ "$hosting" = "vercel" ] || [ "$hosting" = "netlify" ] || [ "$hosting" = "cloudflare-pages" ]; then
310
+ echo "check_6_status=warn"
311
+ echo "check_6_message=hosting_platform=$hosting in schema enum but adapter not yet shipped"
312
+ echo "check_6_recommendation=Switch to amplify (bassclef default) OR contribute the adapter at scripts/adapters/deploy/${hosting}.sh"
313
+ warn=$((warn + 1))
314
+ elif [ "$hosting" = "amplify" ] || [ "$hosting" = "ec2-tailscale" ] || [ "$hosting" = "mock" ]; then
315
+ # Known-good enum value but adapter not found at expected path — could be
316
+ # BASSCLEF_DIR pointing at the wrong place, or substrate sync incomplete
317
+ if [ -z "${BASSCLEF_DIR:-}" ]; then
318
+ echo "check_6_status=warn"
319
+ echo "check_6_message=hosting_platform=$hosting (BASSCLEF_DIR not reachable; can't verify adapter)"
320
+ echo "check_6_recommendation=Set BASSCLEF_DIR OR run /onboard-repo to sync bassclef substrate"
321
+ warn=$((warn + 1))
322
+ else
323
+ echo "check_6_status=warn"
324
+ echo "check_6_message=hosting_platform=$hosting in enum but adapter missing at $adapter_check"
325
+ echo "check_6_recommendation=Run bassclef-sync.sh to refresh adapter symlinks OR check BASSCLEF_DIR points at bassclef repo root"
326
+ warn=$((warn + 1))
327
+ fi
328
+ else
329
+ echo "check_6_status=fail"
330
+ echo "check_6_message=hosting_platform=$hosting not in schema enum"
331
+ echo "check_6_recommendation=Use one of: amplify | ec2-tailscale | vercel | netlify | cloudflare-pages | none"
332
+ fail=$((fail + 1))
333
+ fi
334
+
335
+ # === Check 7: spec-and-use-case (bet 24a Step 2c per Cockburn ceremony matrix) ===
336
+ # Fires when staged diff includes code paths that merit spec + use-case coverage.
337
+ # Warns if no artifact at docs/use-cases/UC-*.md OR docs/specs/*.md OR
338
+ # docs/decompositions/*.md when substrate + adopter code staged.
339
+ # Per .claude/rules/oo-ad-entry-point.md (extended to include hook/lib/script paths).
340
+ # Cockburn tiering — new hook + lib merit brief use case; app code merits fully-dressed.
341
+ echo "check_7_name=spec-and-use-case"
342
+ local staged_code
343
+ staged_code=$(git diff --cached --name-only --diff-filter=AM 2>/dev/null | grep -E \
344
+ -e '^src/.*\.(ts|tsx|js|py)$' \
345
+ -e '^\.claude/hooks/[^/]+\.sh$' \
346
+ -e '^lib/[^/]+\.sh$' \
347
+ -e '^scripts/[^/]+\.sh$' \
348
+ || true)
349
+ if [ -z "$staged_code" ]; then
350
+ echo "check_7_status=pass"
351
+ echo "check_7_message=No code paths staged (docs-only or non-code commit)"
352
+ echo "check_7_recommendation="
353
+ pass=$((pass + 1))
354
+ else
355
+ local uc_count spec_count decomp_count total_artifacts
356
+ uc_count=$(ls "$repo_root/docs/use-cases/"UC-*.md 2>/dev/null | wc -l | tr -d ' ')
357
+ spec_count=$(ls "$repo_root/docs/specs/"*.md 2>/dev/null | wc -l | tr -d ' ')
358
+ decomp_count=$(ls "$repo_root/docs/decompositions/"*.md 2>/dev/null | wc -l | tr -d ' ')
359
+ total_artifacts=$((uc_count + spec_count + decomp_count))
360
+ if [ "$total_artifacts" -gt 0 ]; then
361
+ echo "check_7_status=pass"
362
+ echo "check_7_message=Code staged + $total_artifacts design artifact(s) present (uc=$uc_count spec=$spec_count decomp=$decomp_count)"
363
+ echo "check_7_recommendation="
364
+ pass=$((pass + 1))
365
+ else
366
+ echo "check_7_status=warn"
367
+ echo "check_7_message=Code paths staged but no use-case / spec / decomposition present"
368
+ echo "check_7_recommendation=Per Cockburn ceremony matrix — new hook + lib merit brief use case at docs/use-cases/UC-<slug>.md; app code merits fully-dressed. Skip only for typo/trivial extensions."
369
+ warn=$((warn + 1))
370
+ fi
371
+ fi
372
+
373
+ # === Summary ===
374
+ echo "summary_count_pass=$pass"
375
+ echo "summary_count_warn=$warn"
376
+ echo "summary_count_fail=$fail"
377
+ if [ "$fail" -gt 0 ]; then
378
+ echo "summary_status=fail"
379
+ return 2
380
+ elif [ "$warn" -gt 0 ]; then
381
+ echo "summary_status=warn"
382
+ return 1
383
+ else
384
+ echo "summary_status=pass"
385
+ return 0
386
+ fi
387
+ }
388
+
389
+ # Helper: render the structured output as a human-readable block.
390
+ # Composers can pipe `composer_preflight_check ... | composer_preflight_render`
391
+ # for a polished operator-facing surface.
392
+ composer_preflight_render() {
393
+ local skill_name="${1:-the skill}"
394
+ local input
395
+ input=$(cat)
396
+
397
+ echo ""
398
+ echo "============================================"
399
+ echo "PRE-FLIGHT CHECK — /$skill_name"
400
+ echo "============================================"
401
+
402
+ local i status name message rec
403
+ for i in 1 2 3 4 5 6 7; do
404
+ status=$(echo "$input" | grep -E "^check_${i}_status=" | head -1 | sed 's/^check_[0-9]_status=//')
405
+ name=$(echo "$input" | grep -E "^check_${i}_name=" | head -1 | sed 's/^check_[0-9]_name=//')
406
+ message=$(echo "$input" | grep -E "^check_${i}_message=" | head -1 | sed 's/^check_[0-9]_message=//')
407
+ rec=$(echo "$input" | grep -E "^check_${i}_recommendation=" | head -1 | sed 's/^check_[0-9]_recommendation=//')
408
+
409
+ # Skip render slot if check didn't fire (e.g., pre-Check-6 fixtures)
410
+ [ -z "$name" ] && continue
411
+
412
+ local glyph
413
+ case "$status" in
414
+ pass) glyph="✓" ;;
415
+ warn) glyph="⚠️ " ;;
416
+ fail) glyph="✗" ;;
417
+ *) glyph="?" ;;
418
+ esac
419
+
420
+ printf " %s %-15s %s\n" "$glyph" "$name" "$message"
421
+ if [ -n "$rec" ]; then
422
+ printf " └─ recommendation: %s\n" "$rec"
423
+ fi
424
+ done
425
+
426
+ local summary_status pass warn fail
427
+ summary_status=$(echo "$input" | grep "^summary_status=" | head -1 | sed 's/^summary_status=//')
428
+ pass=$(echo "$input" | grep "^summary_count_pass=" | head -1 | sed 's/^summary_count_pass=//')
429
+ warn=$(echo "$input" | grep "^summary_count_warn=" | head -1 | sed 's/^summary_count_warn=//')
430
+ fail=$(echo "$input" | grep "^summary_count_fail=" | head -1 | sed 's/^summary_count_fail=//')
431
+
432
+ echo ""
433
+ case "$summary_status" in
434
+ pass)
435
+ echo "DISPATCH READY: all $pass checks pass."
436
+ ;;
437
+ warn)
438
+ echo "DISPATCH WITH WARNINGS: $pass pass, $warn warn, $fail fail."
439
+ echo "Composer should ask operator to proceed/abort."
440
+ ;;
441
+ fail)
442
+ echo "DISPATCH REFUSED: $pass pass, $warn warn, $fail fail."
443
+ echo "Resolve fail-tier checks before proceeding."
444
+ ;;
445
+ skip)
446
+ echo "PRE-FLIGHT SKIPPED (SKIP_COMPOSER_PREFLIGHT=1)."
447
+ ;;
448
+ esac
449
+ echo "============================================"
450
+ echo ""
451
+ }
452
+
453
+ # If sourced, the functions are available. If executed directly, run a
454
+ # self-test against the current repo + report.
455
+ if [ "${BASH_SOURCE[0]:-$0}" = "${0}" ]; then
456
+ echo "Self-test mode: running composer_preflight_check for /launch with 60-turn estimate"
457
+ echo ""
458
+ composer_preflight_check "launch" 60 | composer_preflight_render "launch"
459
+ fi
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # lib/deferred-actions.sh — typed accessor for docs/deferred-actions/
4
+ #
5
+ # Single reader/writer for deferred-action entries. Replaces ad-hoc ls +
6
+ # grep patterns in 60-deferred-actions.sh and similar consumers.
7
+ #
8
+ # Public API:
9
+ # da_dir → echo path to docs/deferred-actions/
10
+ # da_completed_dir → echo path to docs/deferred-actions/completed/
11
+ # da_list_open → list open *.md (one path per line)
12
+ # da_list_completed → list completed/*.md
13
+ # da_get_field <id> <field> → frontmatter field reader
14
+ # da_resolve <id> → git mv id.md → completed/id.md
15
+ # da_filter_by_capability <cap> → list open whose required-capability matches
16
+ # da_filter_by_priority <high|medium|low> → list open by priority field
17
+ #
18
+ # `<id>` may be a bare filename (`2026-04-26T01-00-00Z-foo.md`) or full path.
19
+ # `<cap>` matches against `requires_capability:` frontmatter field.
20
+
21
+ set -uo pipefail
22
+
23
+ da_dir() {
24
+ if [[ -n "${DEFERRED_ACTIONS_DIR:-}" ]]; then
25
+ echo "$DEFERRED_ACTIONS_DIR"
26
+ elif [[ -n "${BASSCLEF_REPO_ROOT:-}" ]]; then
27
+ echo "$BASSCLEF_REPO_ROOT/docs/deferred-actions"
28
+ else
29
+ echo "docs/deferred-actions"
30
+ fi
31
+ }
32
+
33
+ da_completed_dir() {
34
+ echo "$(da_dir)/completed"
35
+ }
36
+
37
+ da_list_open() {
38
+ local dir
39
+ dir="$(da_dir)"
40
+ [[ -d "$dir" ]] || return 0
41
+ # Open entries = .md files at top-level (not in completed/), excluding README/template
42
+ find "$dir" -maxdepth 1 -type f -name '*.md' \
43
+ ! -name 'README.md' \
44
+ ! -name 'template.md' \
45
+ 2>/dev/null | sort
46
+ }
47
+
48
+ da_list_completed() {
49
+ local dir
50
+ dir="$(da_completed_dir)"
51
+ [[ -d "$dir" ]] || return 0
52
+ find "$dir" -maxdepth 1 -type f -name '*.md' 2>/dev/null | sort
53
+ }
54
+
55
+ # Resolve <id> → full path. Accepts bare filename or full path.
56
+ # Full path forms: absolute (/foo), explicit relative (./foo), or any other
57
+ # string that already resolves to a file. Bare filename: prepended with da_dir.
58
+ # Non-existent bare-relative: also prepended with da_dir (backward compat).
59
+ _da_resolve_path() {
60
+ local id="$1"
61
+ if [[ "$id" == /* || "$id" == ./* ]]; then
62
+ echo "$id"
63
+ elif [[ -f "$id" ]]; then
64
+ # Bare-relative path that already resolves — pass through.
65
+ # Closes bassclef-upstream#1016 (da_list_open output was being doubled).
66
+ echo "$id"
67
+ else
68
+ echo "$(da_dir)/$id"
69
+ fi
70
+ }
71
+
72
+ da_get_field() {
73
+ local id="$1"
74
+ local field="$2"
75
+ local path
76
+ path="$(_da_resolve_path "$id")"
77
+ [[ -f "$path" ]] || { echo "not-found: $path" >&2; return 1; }
78
+ # Read frontmatter only (between two `---` lines)
79
+ awk -v f="$field" '
80
+ BEGIN { in_fm = 0; seen_open = 0 }
81
+ /^---[[:space:]]*$/ {
82
+ if (!seen_open) { seen_open = 1; in_fm = 1; next }
83
+ else { in_fm = 0; exit }
84
+ }
85
+ in_fm && $0 ~ "^" f ":" {
86
+ sub("^" f ":[[:space:]]*", "")
87
+ # Strip surrounding quotes if any
88
+ gsub(/^"|"$/, "")
89
+ print
90
+ exit
91
+ }
92
+ ' "$path"
93
+ }
94
+
95
+ da_resolve() {
96
+ local id="$1"
97
+ local src dst dst_dir
98
+ src="$(_da_resolve_path "$id")"
99
+ [[ -f "$src" ]] || { echo "da_resolve: not-found: $src" >&2; return 1; }
100
+ dst_dir="$(da_completed_dir)"
101
+ mkdir -p "$dst_dir"
102
+ dst="$dst_dir/$(basename "$src")"
103
+ git mv "$src" "$dst" 2>/dev/null || mv "$src" "$dst"
104
+ }
105
+
106
+ da_filter_by_capability() {
107
+ local cap="$1"
108
+ local entry value
109
+ while IFS= read -r entry; do
110
+ [[ -z "$entry" ]] && continue
111
+ value="$(da_get_field "$entry" requires_capability 2>/dev/null || true)"
112
+ # Match if exact OR comma-separated list contains cap
113
+ if [[ "$value" == "$cap" || ",$value," == *",$cap,"* ]]; then
114
+ echo "$entry"
115
+ fi
116
+ done < <(da_list_open)
117
+ }
118
+
119
+ da_filter_by_priority() {
120
+ local prio="$1"
121
+ local entry value
122
+ while IFS= read -r entry; do
123
+ [[ -z "$entry" ]] && continue
124
+ value="$(da_get_field "$entry" priority 2>/dev/null || true)"
125
+ [[ "$value" == "$prio" ]] && echo "$entry"
126
+ done < <(da_list_open)
127
+ }
128
+
129
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
130
+ cmd="${1:-}"
131
+ shift || true
132
+ case "$cmd" in
133
+ dir) da_dir ;;
134
+ completed_dir) da_completed_dir ;;
135
+ list_open) da_list_open ;;
136
+ list_completed) da_list_completed ;;
137
+ get_field) da_get_field "$@" ;;
138
+ resolve) da_resolve "$@" ;;
139
+ filter_by_capability) da_filter_by_capability "$@" ;;
140
+ filter_by_priority) da_filter_by_priority "$@" ;;
141
+ *)
142
+ echo "usage: $0 {dir|completed_dir|list_open|list_completed|get_field|resolve|filter_by_capability|filter_by_priority} [args]" >&2
143
+ exit 2
144
+ ;;
145
+ esac
146
+ fi
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ #
5
+ # lib/hook-header-parser.sh — reads the # install-class: header from a hook file
6
+ #
7
+ # @pattern patterns/code/gof/adapter.md
8
+ # Adapts raw header text to typed enum values.
9
+ #
10
+ # Contract (Hoare):
11
+ # - Precondition: hook_path exists and is readable
12
+ # - Postcondition (success): stdout is one of operator|project|dual + exit 0
13
+ # - Postcondition (failure): stdout empty + exit non-zero
14
+ #
15
+ # Per docs/decompositions/2026-09-15-install-class-dispatch.md
16
+ # Per docs/use-cases/UC-lib-hook-installer.md
17
+
18
+ set -euo pipefail
19
+
20
+ # Valid install-class values per standards/hook-install-class.md
21
+ _HOOK_INSTALL_CLASS_VALID_VALUES=(operator project dual)
22
+
23
+ # parse_install_class(hook_path) → prints "operator" | "project" | "dual" | ""
24
+ # Returns 0 on success, non-zero on failure.
25
+ parse_install_class() {
26
+ local hook_path="$1"
27
+
28
+ # Precondition: file exists and is readable (not a directory)
29
+ if [ ! -f "$hook_path" ] || [ ! -r "$hook_path" ]; then
30
+ return 1
31
+ fi
32
+
33
+ # Read first 10 lines only — strict header window
34
+ # Match: optional leading whitespace + "# install-class: <value>"
35
+ # Case-sensitive on value
36
+ local raw_class
37
+ raw_class=$(head -n 10 "$hook_path" | grep -E '^[[:space:]]*# install-class:[[:space:]]*' | head -n 1 || true)
38
+
39
+ if [ -z "$raw_class" ]; then
40
+ return 1
41
+ fi
42
+
43
+ # Extract value — strip leading whitespace, hash, "install-class:", surrounding whitespace
44
+ local value
45
+ value=$(echo "$raw_class" | sed -E 's/^[[:space:]]*#[[:space:]]*install-class:[[:space:]]*//; s/[[:space:]]*$//')
46
+
47
+ # Validate against enum
48
+ local v
49
+ for v in "${_HOOK_INSTALL_CLASS_VALID_VALUES[@]}"; do
50
+ if [ "$value" = "$v" ]; then
51
+ echo "$value"
52
+ return 0
53
+ fi
54
+ done
55
+
56
+ # Unknown or malformed value
57
+ return 1
58
+ }
59
+
60
+ # Export for callers that source this file
61
+ export -f parse_install_class 2>/dev/null || true