@thebassclef/lite 1.0.3 → 1.1.0

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 +92 -6
  2. package/dist/cli.js +92 -6
  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 +1 -1
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # description: schedule-auto-save.cron.sh — install a cron entry that fires auto-save-idle.sh
4
+ # schedule-auto-save.cron.sh — install a cron entry that fires auto-save-idle.sh
5
+ # every 15 minutes on macOS + Linux + WSL.
6
+ #
7
+ # Per ADR-046 (cross-OS auto-save scheduling). Replaces the macOS-only
8
+ # launchd path for adopters on Linux or WSL. Native Windows adopters
9
+ # should read presence/install/schedule-auto-save.taskscheduler.md instead.
10
+ #
11
+ # Usage:
12
+ # bash presence/install/schedule-auto-save.cron.sh [path-to-hook]
13
+ #
14
+ # Default hook path: $HOME/src/sunj-labs/bassclef/.claude/hooks/auto-save-idle.sh
15
+ #
16
+ # The installer is idempotent — running twice does not add duplicate entries.
17
+ # The installer writes to the current user's crontab (crontab -l | grep -v ...).
18
+ #
19
+ # Override paths:
20
+ # SKIP_CRON_INSTALL=1 — bypass the install (logged; no crontab change)
21
+ # BASSCLEF_HOOK_PATH — override the default hook path
22
+ #
23
+ # Exit codes:
24
+ # 0 = success (installed, updated, or already present)
25
+ # 1 = cron not available or crontab command missing
26
+ # 2 = hook file not found at target path
27
+ # 3 = user cancelled or explicit skip
28
+
29
+ set -euo pipefail
30
+
31
+ BASSCLEF_HOOK_DEFAULT="$HOME/src/sunj-labs/bassclef/.claude/hooks/auto-save-idle.sh"
32
+ HOOK_PATH="${1:-${BASSCLEF_HOOK_PATH:-$BASSCLEF_HOOK_DEFAULT}}"
33
+ CRON_MARKER="# bassclef auto-save (ADR-046)"
34
+ CRON_ENTRY="*/15 * * * * bash ${HOOK_PATH} >/dev/null 2>&1"
35
+
36
+ echo "=== bassclef auto-save cron installer ==="
37
+ echo "Hook path: ${HOOK_PATH}"
38
+
39
+ # Override check
40
+ if [ "${SKIP_CRON_INSTALL:-0}" = "1" ]; then
41
+ echo "SKIP_CRON_INSTALL=1 — skipping install (logged)"
42
+ exit 3
43
+ fi
44
+
45
+ # Check cron availability
46
+ if ! command -v crontab >/dev/null 2>&1; then
47
+ echo "ERROR: crontab command not found. This installer needs cron."
48
+ echo " - macOS: cron ships by default; check /usr/bin/crontab"
49
+ echo " - Linux: install via 'apt install cron' or 'yum install cronie'"
50
+ echo " - Native Windows: use presence/install/schedule-auto-save.taskscheduler.md instead"
51
+ exit 1
52
+ fi
53
+
54
+ # Check hook file exists
55
+ if [ ! -f "$HOOK_PATH" ]; then
56
+ echo "ERROR: hook file not found at ${HOOK_PATH}"
57
+ echo " Pass the path as an argument OR set BASSCLEF_HOOK_PATH env var."
58
+ echo " Example: bash $0 /path/to/bassclef/.claude/hooks/auto-save-idle.sh"
59
+ exit 2
60
+ fi
61
+
62
+ # Read current crontab (empty if none)
63
+ CURRENT_CRONTAB=$(crontab -l 2>/dev/null || echo "")
64
+
65
+ # Idempotency check — skip if bassclef entry already present
66
+ if echo "$CURRENT_CRONTAB" | grep -qF "$CRON_MARKER"; then
67
+ echo "Bassclef auto-save entry already present in crontab — no change."
68
+ echo ""
69
+ echo "Current entry:"
70
+ echo "$CURRENT_CRONTAB" | grep -A1 -F "$CRON_MARKER" | head -2
71
+ exit 0
72
+ fi
73
+
74
+ # Install: append the marker + entry to the existing crontab
75
+ NEW_CRONTAB=$(printf '%s\n%s\n%s\n' "$CURRENT_CRONTAB" "$CRON_MARKER" "$CRON_ENTRY")
76
+
77
+ # Write via crontab -
78
+ echo "$NEW_CRONTAB" | crontab -
79
+
80
+ echo "Installed cron entry:"
81
+ echo " ${CRON_MARKER}"
82
+ echo " ${CRON_ENTRY}"
83
+ echo ""
84
+ echo "Verify with: crontab -l"
85
+ echo "Remove with: crontab -l | grep -v 'bassclef auto-save' | crontab -"
86
+ echo ""
87
+ echo "Auto-save fires every 15 minutes. Feature branches are skipped by save-state.sh"
88
+ echo "guards. SESSION_LOCK held by an active /longrun session suppresses fires."
@@ -0,0 +1,122 @@
1
+ ---
2
+ tier: lite
3
+ title: Native Windows setup — auto-save via Task Scheduler
4
+ adr_references: [ADR-046]
5
+ audience: bassclef adopters on native Windows without WSL
6
+ ---
7
+
8
+ # Auto-save on native Windows via Task Scheduler
9
+
10
+ Bassclef's auto-save fires every 15 minutes on macOS + Linux + WSL via cron. See `presence/install/schedule-auto-save.cron.sh` for that path.
11
+
12
+ On native Windows without WSL, cron is not available. This doc gives the manual steps to register a scheduled task that runs the same hook via Git Bash (or MSYS bash).
13
+
14
+ Per ADR-046.
15
+
16
+ ## Prerequisites
17
+
18
+ - Windows 10 or 11
19
+ - Git for Windows installed (`git --version` works from PowerShell)
20
+ - Bassclef checked out at `%USERPROFILE%\src\sunj-labs\bassclef` (or note your path)
21
+ - Task Scheduler access (built into Windows; requires admin for some operations)
22
+
23
+ ## Step 1 — Confirm the hook path
24
+
25
+ Open PowerShell. Run:
26
+
27
+ ```powershell
28
+ Test-Path "$env:USERPROFILE\src\sunj-labs\bassclef\.claude\hooks\auto-save-idle.sh"
29
+ ```
30
+
31
+ Expected: `True`. If `False`, adjust the path to where you checked out bassclef.
32
+
33
+ ## Step 2 — Find Git Bash
34
+
35
+ Task Scheduler needs to invoke bash directly. Git for Windows ships `bash.exe` at:
36
+
37
+ ```
38
+ C:\Program Files\Git\bin\bash.exe
39
+ ```
40
+
41
+ Confirm with:
42
+
43
+ ```powershell
44
+ Test-Path "C:\Program Files\Git\bin\bash.exe"
45
+ ```
46
+
47
+ If the path differs (32-bit install, custom install), note the actual location for Step 3.
48
+
49
+ ## Step 3 — Register the scheduled task
50
+
51
+ Run in an elevated PowerShell (Run as Administrator):
52
+
53
+ ```powershell
54
+ $hookPath = "$env:USERPROFILE\src\sunj-labs\bassclef\.claude\hooks\auto-save-idle.sh"
55
+ $bashExe = "C:\Program Files\Git\bin\bash.exe"
56
+
57
+ $action = New-ScheduledTaskAction `
58
+ -Execute $bashExe `
59
+ -Argument "-lc `"bash '$hookPath'`""
60
+
61
+ $trigger = New-ScheduledTaskTrigger `
62
+ -Once -At (Get-Date) `
63
+ -RepetitionInterval (New-TimeSpan -Minutes 15) `
64
+ -RepetitionDuration (New-TimeSpan -Days 3650)
65
+
66
+ $settings = New-ScheduledTaskSettingsSet `
67
+ -AllowStartIfOnBatteries `
68
+ -DontStopIfGoingOnBatteries `
69
+ -StartWhenAvailable
70
+
71
+ Register-ScheduledTask `
72
+ -TaskName "BassclefAutoSave" `
73
+ -Description "Bassclef auto-save fires every 15 minutes (ADR-046)" `
74
+ -Action $action `
75
+ -Trigger $trigger `
76
+ -Settings $settings `
77
+ -User $env:USERNAME
78
+ ```
79
+
80
+ The task fires every 15 minutes for 10 years. Idempotent — running the register command twice fails the second time with a name collision (safe to re-run after `Unregister-ScheduledTask`).
81
+
82
+ ## Step 4 — Verify
83
+
84
+ ```powershell
85
+ Get-ScheduledTask -TaskName "BassclefAutoSave" | Get-ScheduledTaskInfo
86
+ ```
87
+
88
+ Shows `LastRunTime` after the first fire (~15 minutes after registration). Errors surface in `LastTaskResult` (0 = success).
89
+
90
+ Manual fire for immediate check:
91
+
92
+ ```powershell
93
+ Start-ScheduledTask -TaskName "BassclefAutoSave"
94
+ ```
95
+
96
+ Then wait ~10 seconds and check the git log in your bassclef checkout for a `chore: auto-save checkpoint` commit.
97
+
98
+ ## Step 5 — Remove (when needed)
99
+
100
+ ```powershell
101
+ Unregister-ScheduledTask -TaskName "BassclefAutoSave" -Confirm:$false
102
+ ```
103
+
104
+ ## Notes
105
+
106
+ - The task runs as the current user, not SYSTEM. Files created by the task inherit the user's permissions.
107
+ - Windows Task Scheduler wakes the machine only if the "Wake the computer to run this task" setting is enabled (not set by default here). Adopters who sleep their machine will miss fires during sleep.
108
+ - When the machine reboots, the task resumes on schedule.
109
+ - Session-end.sh Guard 0 (per bet 27b Step 2) still fires — the SESSION_LOCK precedence check works cross-OS because it reads the file, not the OS scheduler.
110
+
111
+ ## Alternatives
112
+
113
+ - **WSL2 available?** Skip this doc; use `presence/install/schedule-auto-save.cron.sh` inside WSL. Cron there watches your Windows filesystem via `/mnt/c/Users/...`.
114
+ - **Prefer a .ps1 installer?** File a ticket. The current ship stays doc-only to avoid installer complexity for a population not yet observed. Path B (per-OS installers) upgrade is a follow-on.
115
+
116
+ ## References
117
+
118
+ - ADR-046 — Cross-OS auto-save scheduling
119
+ - Bet 27b Step 4 — Native Windows path (this doc)
120
+ - `presence/install/schedule-auto-save.cron.sh` — sister installer for macOS + Linux + WSL
121
+ - `.claude/hooks/auto-save-idle.sh` — the hook this schedule invokes
122
+ - `.claude/hooks/session-end.sh` — SESSION_LOCK precedence cure applies cross-OS
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/aggregate-telemetry.sh — read local telemetry store; emit uptake report
3
+ # tier: lite
4
+ # install-class: dual
5
+ #
6
+ # Reads state/telemetry/events-*.jsonl files, counts events by type + skill +
7
+ # install source, writes docs/telemetry-reports/YYYY-MM-DD.md.
8
+ #
9
+ # CLI:
10
+ # bash scripts/aggregate-telemetry.sh # full store, today's report
11
+ # bash scripts/aggregate-telemetry.sh --from 2026-08-01 --to 2026-08-31
12
+ # bash scripts/aggregate-telemetry.sh --output /tmp/rep.md
13
+ #
14
+ # Deep module per @luminary john-ousterhout — one CLI hides read + parse +
15
+ # aggregate + write.
16
+ # Local-first per ADR-049 — reads adopter machine only.
17
+ #
18
+ # Bash 3.2 compatible (macOS default) — uses temp files rather than
19
+ # associative arrays.
20
+
21
+ set -u
22
+
23
+ REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
24
+ EVENTS_DIR="${REPO_ROOT}/state/telemetry"
25
+ TODAY="$(date -u +%Y-%m-%d)"
26
+ OUTPUT_DIR="${REPO_ROOT}/docs/telemetry-reports"
27
+ OUTPUT_FILE=""
28
+ DATE_FROM=""
29
+ DATE_TO=""
30
+
31
+ # --- Arg parsing -----------------------------------------------------------
32
+ while [ $# -gt 0 ]; do
33
+ case "$1" in
34
+ --from) DATE_FROM="$2"; shift 2 ;;
35
+ --to) DATE_TO="$2"; shift 2 ;;
36
+ --output) OUTPUT_FILE="$2"; shift 2 ;;
37
+ --help|-h)
38
+ grep '^# ' "$0" | head -20 | sed 's/^# //'
39
+ exit 0
40
+ ;;
41
+ *) echo "unknown arg: $1" >&2; exit 2 ;;
42
+ esac
43
+ done
44
+
45
+ [ -z "$OUTPUT_FILE" ] && OUTPUT_FILE="${OUTPUT_DIR}/${TODAY}.md"
46
+
47
+ # --- Scratch counts dir (bash-3-compatible replacement for declare -A) ----
48
+ COUNTS_DIR="$(mktemp -d -t telem-counts.XXXXXX)"
49
+ trap 'rm -rf "$COUNTS_DIR"' EXIT
50
+ mkdir -p "$COUNTS_DIR/skills" "$COUNTS_DIR/installs" "$COUNTS_DIR/sessions"
51
+
52
+ _incr_dir() {
53
+ local dir="$1"
54
+ local key="$2"
55
+ # Sanitize key for filesystem (allow /, alnum, dash, underscore)
56
+ local safe_key
57
+ safe_key=$(echo "$key" | tr '/' '_' | tr -c 'a-zA-Z0-9_-' '_')
58
+ local file="$COUNTS_DIR/$dir/$safe_key"
59
+ local n
60
+ n=$(cat "$file" 2>/dev/null || echo 0)
61
+ echo $((n + 1)) > "$file"
62
+ # Also store original key for report output
63
+ echo "$key" > "$COUNTS_DIR/$dir/$safe_key.name"
64
+ }
65
+
66
+ _mark_session() {
67
+ local sid="$1"
68
+ # Sanitize
69
+ local safe_sid
70
+ safe_sid=$(echo "$sid" | tr -c 'a-zA-Z0-9-' '_')
71
+ touch "$COUNTS_DIR/sessions/$safe_sid"
72
+ }
73
+
74
+ # --- Enumerate matching event files ----------------------------------------
75
+ EVENT_FILES=()
76
+ if [ -d "$EVENTS_DIR" ]; then
77
+ shopt -s nullglob
78
+ for f in "$EVENTS_DIR"/events-*.jsonl; do
79
+ base=$(basename "$f" .jsonl)
80
+ date_stamp="${base#events-}"
81
+ d="${date_stamp:0:4}-${date_stamp:4:2}-${date_stamp:6:2}"
82
+ [ -n "$DATE_FROM" ] && [ "$d" \< "$DATE_FROM" ] && continue
83
+ [ -n "$DATE_TO" ] && [ "$d" \> "$DATE_TO" ] && continue
84
+ EVENT_FILES+=("$f")
85
+ done
86
+ shopt -u nullglob
87
+ fi
88
+
89
+ # --- Counters --------------------------------------------------------------
90
+ total_events=0
91
+ count_session_start=0
92
+ count_session_end=0
93
+ count_skill_invoke=0
94
+ count_tier_boundary=0
95
+ count_install_probe=0
96
+
97
+ for f in ${EVENT_FILES[@]+"${EVENT_FILES[@]}"}; do
98
+ while IFS= read -r line; do
99
+ [ -z "$line" ] && continue
100
+ total_events=$((total_events + 1))
101
+
102
+ if command -v jq >/dev/null 2>&1; then
103
+ et=$(echo "$line" | jq -r '.event_type // empty' 2>/dev/null)
104
+ sid=$(echo "$line" | jq -r '.session_id // empty' 2>/dev/null)
105
+ else
106
+ et=$(echo "$line" | grep -oE '"event_type"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
107
+ sid=$(echo "$line" | grep -oE '"session_id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
108
+ fi
109
+
110
+ [ -n "$sid" ] && _mark_session "$sid"
111
+
112
+ case "$et" in
113
+ session_start)
114
+ count_session_start=$((count_session_start + 1))
115
+ ;;
116
+ session_end)
117
+ count_session_end=$((count_session_end + 1))
118
+ ;;
119
+ skill_invoke)
120
+ count_skill_invoke=$((count_skill_invoke + 1))
121
+ if command -v jq >/dev/null 2>&1; then
122
+ skill=$(echo "$line" | jq -r '.payload.skill_name // empty' 2>/dev/null)
123
+ else
124
+ skill=$(echo "$line" | grep -oE '"skill_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
125
+ fi
126
+ [ -n "$skill" ] && _incr_dir skills "$skill"
127
+ ;;
128
+ tier_boundary_attempt)
129
+ count_tier_boundary=$((count_tier_boundary + 1))
130
+ ;;
131
+ install_probe)
132
+ count_install_probe=$((count_install_probe + 1))
133
+ if command -v jq >/dev/null 2>&1; then
134
+ src=$(echo "$line" | jq -r '.payload.install_source // empty' 2>/dev/null)
135
+ else
136
+ src=$(echo "$line" | grep -oE '"install_source"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
137
+ fi
138
+ [ -n "$src" ] && _incr_dir installs "$src"
139
+ ;;
140
+ esac
141
+ done < "$f"
142
+ done
143
+
144
+ # Session count = number of session marker files
145
+ session_count=$(ls -1 "$COUNTS_DIR/sessions" 2>/dev/null | wc -l | tr -d ' ')
146
+
147
+ # --- Write report ----------------------------------------------------------
148
+ mkdir -p "$(dirname "$OUTPUT_FILE")"
149
+
150
+ {
151
+ echo "---"
152
+ echo "report_type: telemetry-uptake"
153
+ echo "generated_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
154
+ echo "date_range_from: ${DATE_FROM:-<all>}"
155
+ echo "date_range_to: ${DATE_TO:-<all>}"
156
+ echo "source: local"
157
+ echo "---"
158
+ echo ""
159
+ echo "# Telemetry uptake report — $TODAY"
160
+ echo ""
161
+ echo "## Summary"
162
+ echo ""
163
+ echo "- **Total events:** $total_events"
164
+ echo "- **Distinct sessions:** $session_count"
165
+ echo "- **Files scanned:** ${#EVENT_FILES[@]}"
166
+ echo ""
167
+ echo "## Event counts by type"
168
+ echo ""
169
+ echo "| Type | Count |"
170
+ echo "|---|---|"
171
+ echo "| session_start | $count_session_start |"
172
+ echo "| session_end | $count_session_end |"
173
+ echo "| skill_invoke | $count_skill_invoke |"
174
+ echo "| tier_boundary_attempt | $count_tier_boundary |"
175
+ echo "| install_probe | $count_install_probe |"
176
+ echo ""
177
+ echo "## Skill invocations"
178
+ echo ""
179
+ skill_files=$(ls -1 "$COUNTS_DIR/skills"/*.name 2>/dev/null || true)
180
+ if [ -z "$skill_files" ]; then
181
+ echo "_No skill invocations recorded._"
182
+ else
183
+ echo "| Skill | Count |"
184
+ echo "|---|---|"
185
+ for name_file in "$COUNTS_DIR/skills"/*.name; do
186
+ count_file="${name_file%.name}"
187
+ skill=$(cat "$name_file")
188
+ n=$(cat "$count_file")
189
+ echo "| \`$skill\` | $n |"
190
+ done | sort
191
+ fi
192
+ echo ""
193
+ echo "## Install source distribution"
194
+ echo ""
195
+ install_files=$(ls -1 "$COUNTS_DIR/installs"/*.name 2>/dev/null || true)
196
+ if [ -z "$install_files" ]; then
197
+ echo "_No install probes recorded._"
198
+ else
199
+ echo "| Source | Count |"
200
+ echo "|---|---|"
201
+ for name_file in "$COUNTS_DIR/installs"/*.name; do
202
+ count_file="${name_file%.name}"
203
+ src=$(cat "$name_file")
204
+ n=$(cat "$count_file")
205
+ echo "| $src | $n |"
206
+ done | sort
207
+ fi
208
+ echo ""
209
+ echo "## Notes"
210
+ echo ""
211
+ echo "- Report is local-only per ADR-049 (opt-in default OFF; no remote endpoint)"
212
+ echo "- Adopters share reports at their discretion; raw events stay on adopter machine"
213
+ echo "- Iteration 3 pricing decision reads aggregated shared reports"
214
+ } > "$OUTPUT_FILE"
215
+
216
+ echo "telemetry report written: $OUTPUT_FILE"
217
+ echo " total_events=$total_events sessions=$session_count files=${#EVENT_FILES[@]}"
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # analyze-tier-dependencies.sh — orchestrator for the tier-dependency-audit subsystem
4
+ #
5
+ # Per standards/tier-dependency-analysis.md — walks the dependency graph
6
+ # for a given tier and classifies every file into one of 4 buckets.
7
+ #
8
+ # Per docs/decompositions/2026-08-17a-tier-dependency-audit.md — this script
9
+ # is the orchestrator (main + dispatch). Actual work lives in
10
+ # lib/tier-dependency-audit.sh (8 named functions per Larman GRASP).
11
+ #
12
+ # Usage:
13
+ # scripts/analyze-tier-dependencies.sh <tier>
14
+ # tier: one of lite | standard | standard-pro | ultra | upstream
15
+ # OR 'all' (combined-tier — all 5 tiers)
16
+ # OR 'tier,tier,..' (combined-tier — named list; e.g., lite,standard)
17
+ #
18
+ # Combined-tier mode (ADR-052 D5): INTENDED = union of every named tier's
19
+ # INTENDED set. Buckets 1+3+4 report against the union. Bucket 3 becomes
20
+ # TRUE accidental leaks (files no tier's graph claims), not
21
+ # tagged-for-other-tier files that show up as false leaks in single-tier mode.
22
+ #
23
+ # Overrides (used by Tier 0 tests + advanced consumers):
24
+ # OUTPUT_DIR — where to write manifest (default: docs/dependency-audits)
25
+ # INTENDED_FIXTURE — file listing INTENDED set (skips Sources 1+2 walk)
26
+ # ACTUAL_FIXTURE — file listing ACTUAL set (skips release-script call)
27
+ # TAGS_FIXTURE — file listing "<path>\t<tier>" for tag data
28
+ # RUNTIME_DEPS_FILE — override the tier-runtime-deps file location (single-tier only)
29
+ # SKIP_TIER_DEPENDENCY_AUDIT=1 — bypass path (logged); exits 0 without writing
30
+
31
+ set -uo pipefail
32
+
33
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
34
+
35
+ # Source the lib with named functions (single-tier + combined-tier)
36
+ # shellcheck disable=SC1091
37
+ source "$REPO_ROOT/lib/tier-dependency-audit.sh"
38
+
39
+ # Bypass path
40
+ if [ "${SKIP_TIER_DEPENDENCY_AUDIT:-0}" = "1" ]; then
41
+ echo "analyze-tier-dependencies: SKIP bypass (SKIP_TIER_DEPENDENCY_AUDIT=1 logged)" >&2
42
+ exit 0
43
+ fi
44
+
45
+ # --- Combined-tier mode detection ------------------------------------------
46
+ # If arg is 'all' or contains comma, dispatch to combined path and exit
47
+ # before the single-tier code runs.
48
+ ARG_RAW="${1:-}"
49
+ if [ "$ARG_RAW" = "all" ] || [[ "$ARG_RAW" == *,* ]]; then
50
+ TIER_LIST="$(parse_tier_list_arg "$ARG_RAW")" || exit $?
51
+ OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/docs/dependency-audits}"
52
+ INTENDED_TMP="$(mktemp)"
53
+ ACTUAL_TMP="$(mktemp)"
54
+ TAGS_TMP="$(mktemp)"
55
+ BUCKET_DIR="$(mktemp -d)"
56
+ trap 'rm -rf "$INTENDED_TMP" "$ACTUAL_TMP" "$TAGS_TMP" "$BUCKET_DIR"' EXIT
57
+
58
+ # Loop per tier: build INTENDED_i (Source 1 seeds + Source 2 BFS + Source 3 runtime deps)
59
+ cd "$REPO_ROOT"
60
+ while IFS= read -r t; do
61
+ [ -z "$t" ] && continue
62
+ echo "combined-tier: building INTENDED for $t" >&2
63
+ seeds_tmp="$(mktemp)"
64
+ read_intended_source_1 "$t" > "$seeds_tmp"
65
+ walk_graph_bfs "$seeds_tmp" extract_references >> "$INTENDED_TMP"
66
+ rm -f "$seeds_tmp"
67
+ # Source 3 — runtime deps if present for this tier
68
+ rt_file="$REPO_ROOT/standards/tier-runtime-deps/${t}.md"
69
+ if [ -f "$rt_file" ]; then
70
+ read_runtime_deps "$rt_file" >> "$INTENDED_TMP"
71
+ else
72
+ echo "combined-tier: no runtime-deps file for $t at $rt_file (skipping)" >&2
73
+ fi
74
+ done <<< "$TIER_LIST"
75
+ sort -u -o "$INTENDED_TMP" "$INTENDED_TMP"
76
+
77
+ # ACTUAL — release script include list (same regardless of tier; it is the full public release)
78
+ if [ -n "${ACTUAL_FIXTURE:-}" ]; then
79
+ cp "$ACTUAL_FIXTURE" "$ACTUAL_TMP"
80
+ else
81
+ # Pass 'lite' arg to keep read_actual_source signature; the release
82
+ # script output is the full ACTUAL set regardless.
83
+ read_actual_source "lite" > "$ACTUAL_TMP" || {
84
+ echo "analyze-tier-dependencies: Source 4 (release script) failed" >&2
85
+ exit 4
86
+ }
87
+ fi
88
+ sort -u -o "$ACTUAL_TMP" "$ACTUAL_TMP"
89
+
90
+ # TAGS — derive by grepping each file in INTENDED_TMP for its tier tag
91
+ if [ -n "${TAGS_FIXTURE:-}" ]; then
92
+ cp "$TAGS_FIXTURE" "$TAGS_TMP"
93
+ else
94
+ : > "$TAGS_TMP"
95
+ while IFS= read -r path; do
96
+ [ -z "$path" ] && continue
97
+ tag=""
98
+ if [ -f "$path" ]; then
99
+ case "$path" in
100
+ *.md)
101
+ tag="$(awk '
102
+ BEGIN{infm=0}
103
+ NR==1 && /^---/ {infm=1; next}
104
+ infm && /^---/ {exit}
105
+ infm && /^tier:[[:space:]]/ {sub(/^tier:[[:space:]]*/,""); gsub(/[[:space:]]/,""); print; exit}
106
+ ' "$path")"
107
+ ;;
108
+ *.sh)
109
+ tag="$(head -5 "$path" | awk '/^# tier:[[:space:]]/ {sub(/^# tier:[[:space:]]*/,""); gsub(/[[:space:]]/,""); print; exit}')"
110
+ ;;
111
+ esac
112
+ fi
113
+ if [ -n "$tag" ]; then
114
+ printf "%s\t%s\n" "$path" "$tag" >> "$TAGS_TMP"
115
+ else
116
+ printf "%s\n" "$path" >> "$TAGS_TMP"
117
+ fi
118
+ done < "$INTENDED_TMP"
119
+ fi
120
+
121
+ classify_files_combined "$INTENDED_TMP" "$ACTUAL_TMP" "$TAGS_TMP" "$BUCKET_DIR"
122
+
123
+ TODAY="$(date -u +%Y-%m-%d)"
124
+ # Slug the tier list for the filename (commas → dashes; keeps 'all' as-is)
125
+ TIER_SLUG="$(echo "$ARG_RAW" | tr ',' '-')"
126
+ OUTFILE="$OUTPUT_DIR/${TODAY}-combined-${TIER_SLUG}.md"
127
+ write_manifest_combined "$BUCKET_DIR" "$TODAY" "$ARG_RAW" "$OUTFILE" || {
128
+ echo "analyze-tier-dependencies: write_manifest_combined failed" >&2
129
+ exit 5
130
+ }
131
+ echo "analyze-tier-dependencies: wrote $OUTFILE" >&2
132
+ exit 0
133
+ fi
134
+ # --- End combined-tier mode ------------------------------------------------
135
+
136
+ # Validate tier arg (parse_tier_arg from lib)
137
+ TIER="$(parse_tier_arg "${1:-}")" || exit $?
138
+
139
+ # Resolve inputs (fixture overrides for tests; real sources otherwise)
140
+ OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/docs/dependency-audits}"
141
+ RUNTIME_DEPS_FILE="${RUNTIME_DEPS_FILE:-$REPO_ROOT/standards/tier-runtime-deps/${TIER}.md}"
142
+
143
+ # Check runtime-deps file exists (unless fixture mode short-circuits later)
144
+ if [ ! -f "$RUNTIME_DEPS_FILE" ]; then
145
+ echo "analyze-tier-dependencies: runtime-deps file missing at $RUNTIME_DEPS_FILE" >&2
146
+ echo " create per standards/tier-dependency-analysis.md Source 3" >&2
147
+ exit 3
148
+ fi
149
+
150
+ INTENDED_FIXTURE="${INTENDED_FIXTURE:-}"
151
+ ACTUAL_FIXTURE="${ACTUAL_FIXTURE:-}"
152
+ TAGS_FIXTURE="${TAGS_FIXTURE:-}"
153
+
154
+ # Check intended fixture exists when in fixture mode
155
+ if [ -n "$INTENDED_FIXTURE" ] && [ ! -f "$INTENDED_FIXTURE" ]; then
156
+ echo "analyze-tier-dependencies: intended fixture not found at $INTENDED_FIXTURE" >&2
157
+ exit 3
158
+ fi
159
+
160
+ # Temp files for the pipeline
161
+ INTENDED_TMP="$(mktemp)"
162
+ ACTUAL_TMP="$(mktemp)"
163
+ TAGS_TMP="$(mktemp)"
164
+ BUCKET_DIR="$(mktemp -d)"
165
+ trap 'rm -rf "$INTENDED_TMP" "$ACTUAL_TMP" "$TAGS_TMP" "$BUCKET_DIR"' EXIT
166
+
167
+ # Build INTENDED set — Sources 1 + 2 (BFS from tagged root set) + Source 3 (runtime deps)
168
+ if [ -n "$INTENDED_FIXTURE" ]; then
169
+ cat "$INTENDED_FIXTURE" > "$INTENDED_TMP"
170
+ else
171
+ # Real mode — Source 1 seed + Source 2 BFS
172
+ cd "$REPO_ROOT"
173
+ seeds_tmp="$(mktemp)"
174
+ read_intended_source_1 "$TIER" > "$seeds_tmp"
175
+ walk_graph_bfs "$seeds_tmp" extract_references > "$INTENDED_TMP"
176
+ rm -f "$seeds_tmp"
177
+ fi
178
+
179
+ # Source 3 — runtime deps
180
+ read_runtime_deps "$RUNTIME_DEPS_FILE" >> "$INTENDED_TMP"
181
+
182
+ sort -u -o "$INTENDED_TMP" "$INTENDED_TMP"
183
+
184
+ # Build ACTUAL set — Source 4 (release script dry-run)
185
+ if [ -n "$ACTUAL_FIXTURE" ]; then
186
+ cp "$ACTUAL_FIXTURE" "$ACTUAL_TMP"
187
+ else
188
+ read_actual_source "$TIER" > "$ACTUAL_TMP" || {
189
+ echo "analyze-tier-dependencies: Source 4 (release script) failed" >&2
190
+ exit 4
191
+ }
192
+ fi
193
+ sort -u -o "$ACTUAL_TMP" "$ACTUAL_TMP"
194
+
195
+ # Build TAGS map
196
+ if [ -n "$TAGS_FIXTURE" ]; then
197
+ cp "$TAGS_FIXTURE" "$TAGS_TMP"
198
+ else
199
+ # Real mode — derive tags via grep against INTENDED set
200
+ cd "$REPO_ROOT"
201
+ : > "$TAGS_TMP"
202
+ while IFS= read -r path; do
203
+ [ -z "$path" ] && continue
204
+ tag=""
205
+ if [ -f "$path" ]; then
206
+ case "$path" in
207
+ *.md)
208
+ tag="$(awk '
209
+ BEGIN{infm=0}
210
+ NR==1 && /^---/ {infm=1; next}
211
+ infm && /^---/ {exit}
212
+ infm && /^tier:[[:space:]]/ {sub(/^tier:[[:space:]]*/,""); gsub(/[[:space:]]/,""); print; exit}
213
+ ' "$path")"
214
+ ;;
215
+ *.sh)
216
+ tag="$(head -5 "$path" | awk '/^# tier:[[:space:]]/ {sub(/^# tier:[[:space:]]*/,""); gsub(/[[:space:]]/,""); print; exit}')"
217
+ ;;
218
+ esac
219
+ fi
220
+ if [ -n "$tag" ]; then
221
+ printf "%s\t%s\n" "$path" "$tag" >> "$TAGS_TMP"
222
+ else
223
+ printf "%s\n" "$path" >> "$TAGS_TMP"
224
+ fi
225
+ done < "$INTENDED_TMP"
226
+ fi
227
+
228
+ # Classify + write manifest
229
+ classify_files "$INTENDED_TMP" "$ACTUAL_TMP" "$TAGS_TMP" "$BUCKET_DIR" "$TIER"
230
+
231
+ TODAY="$(date -u +%Y-%m-%d)"
232
+ OUTFILE="$OUTPUT_DIR/${TODAY}-${TIER}.md"
233
+ write_manifest "$BUCKET_DIR" "$TODAY" "$TIER" "$OUTFILE" || {
234
+ echo "analyze-tier-dependencies: write_manifest failed" >&2
235
+ exit 5
236
+ }
237
+
238
+ echo "analyze-tier-dependencies: wrote $OUTFILE" >&2
239
+ exit 0