@thebassclef/lite 1.0.4 → 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 +65 -0
  2. package/dist/cli.js +65 -0
  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,456 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # intent-drift-check.sh — measure intent drift after an artifact body rewrite.
4
+ #
5
+ # Reads a saved embedding from .claude/embeddings/<type>/<slug>.json where
6
+ # <type> is one of skills/rules/standards/hooks. Re-embeds the current source
7
+ # body via Voyage. Computes cosine similarity. Compares against a threshold.
8
+ # Reports PASS/FAIL.
9
+ #
10
+ # The threshold varies per change class. The class captures author intent —
11
+ # preserve, simplify, extend, restructure, or material. Different classes
12
+ # expect different natural cosine drops per Mikolov 2013 embedding-space
13
+ # behavior. See bet 24a Path 1 fold-in for the threshold table.
14
+ #
15
+ # Closes bassclef-upstream#783 — makes /loop step 5 (INTENT CHECK) measurable
16
+ # rather than subjective. Per Feathers characterization + Beck TDD rhythm.
17
+ # Bet 24a Path 1 extends coverage from skills to rules + standards + hooks
18
+ # with change-class-aware thresholds.
19
+ #
20
+ # Usage:
21
+ # scripts/intent-drift-check.sh --skill <slug> [--change-class <c>] [--threshold <f>] [--json]
22
+ # scripts/intent-drift-check.sh --rule <slug> [--change-class <c>] [--threshold <f>] [--json]
23
+ # scripts/intent-drift-check.sh --standard <slug> [--change-class <c>] [--threshold <f>] [--json]
24
+ # scripts/intent-drift-check.sh --hook <slug> [--change-class <c>] [--threshold <f>] [--json]
25
+ # scripts/intent-drift-check.sh --luminary <slug> [--change-class <c>] [--threshold <f>] [--json]
26
+ # scripts/intent-drift-check.sh --adr <slug> [--change-class <c>] [--threshold <f>] [--json]
27
+ # scripts/intent-drift-check.sh --persona <slug> [--change-class <c>] [--threshold <f>] [--json]
28
+ # scripts/intent-drift-check.sh --template <slug> [--change-class <c>] [--threshold <f>] [--json]
29
+ # scripts/intent-drift-check.sh --help
30
+ #
31
+ # Change classes (drives default threshold):
32
+ # preserve — typo, style, formatting; threshold 0.95
33
+ # simplify — /kiss rewrite, grade lift; threshold 0.90 (default)
34
+ # extend — new subsection, added content; threshold 0.70
35
+ # restructure — reorder, section merge; threshold 0.65
36
+ # material — rescope, breaking change; no threshold (always PASS-LOGGED)
37
+ #
38
+ # Exits:
39
+ # 0 — cosine at or above threshold (intent preserved) OR material class
40
+ # 1 — cosine below threshold (intent drift detected; rewrite failed)
41
+ # 2 — precondition failure (missing artifact, missing vector, missing keys,
42
+ # or mutually exclusive artifact flags supplied)
43
+ #
44
+ # Env:
45
+ # VOYAGE_API_KEY — required for live mode
46
+ # ANTHROPIC_API_KEY — checked via intent-matcher.sh (required for live mode)
47
+ # INTENT_DRIFT_STUB=1 — skip Voyage call; return cosine 1.0 (for tests)
48
+ #
49
+ # Per @luminary michael-feathers + @luminary kent-beck + @luminary tomas-mikolov + ADR-042
50
+
51
+ set -o pipefail
52
+
53
+ usage() {
54
+ cat <<'EOF'
55
+ Usage:
56
+ intent-drift-check.sh --skill <slug> [--change-class <c>] [--threshold <f>] [--json]
57
+ intent-drift-check.sh --rule <slug> [--change-class <c>] [--threshold <f>] [--json]
58
+ intent-drift-check.sh --standard <slug> [--change-class <c>] [--threshold <f>] [--json]
59
+ intent-drift-check.sh --hook <slug> [--change-class <c>] [--threshold <f>] [--json]
60
+ intent-drift-check.sh --luminary <slug> [--change-class <c>] [--threshold <f>] [--json]
61
+ intent-drift-check.sh --adr <slug> [--change-class <c>] [--threshold <f>] [--json]
62
+ intent-drift-check.sh --persona <slug> [--change-class <c>] [--threshold <f>] [--json]
63
+ intent-drift-check.sh --template <slug> [--change-class <c>] [--threshold <f>] [--json]
64
+
65
+ Only one artifact flag at a time. Combining them fails with exit 2.
66
+
67
+ Measures intent drift after an artifact body rewrite. Reads saved vector from
68
+ .claude/embeddings/<type>/<slug>.json, re-embeds the current source body, and
69
+ computes cosine similarity against a change-class-aware threshold.
70
+
71
+ Change classes (drives default threshold when --threshold is omitted):
72
+ preserve Threshold 0.95 Typo/style/formatting; meaning identical
73
+ simplify Threshold 0.90 Grade lift, /kiss rewrite; meaning held (default)
74
+ extend Threshold 0.70 New subsection/content added
75
+ restructure Threshold 0.65 Reorder/merge; same meaning, new shape
76
+ material Log only Rescope/breaking; always PASS-LOGGED
77
+
78
+ Options:
79
+ --skill <slug> Skill slug (e.g., state-a-problem)
80
+ --rule <slug> Rule slug (e.g., loop-discipline)
81
+ --standard <slug> Standard slug (e.g., loop-discipline)
82
+ --hook <slug> Hook slug without .sh extension (e.g., pre-commit-gate)
83
+ --luminary <slug> Luminary slug (e.g., alan-cooper); source at .claude/luminaries/<slug>.md
84
+ --adr <slug> ADR slug (e.g., ADR-044-unified-skill-body-template); source at architecture/decisions/<slug>.md
85
+ --persona <slug> Persona slug (e.g., sam); source at docs/personas/<slug>.md
86
+ --template <slug> Template slug (e.g., chronicle-template); source at templates/<slug>.md
87
+ --change-class <c> One of: preserve, simplify, extend, restructure, material
88
+ --threshold <f> Override the class-derived threshold
89
+ --json Emit JSON output with cosine + verdict + change_class fields
90
+ --help Show this usage
91
+
92
+ Exit codes:
93
+ 0 cosine >= threshold (intent preserved) OR material class
94
+ 1 cosine < threshold (intent drift detected)
95
+ 2 precondition failure (missing artifact, missing vector, mutually exclusive flags)
96
+
97
+ Examples:
98
+ scripts/intent-drift-check.sh --skill state-a-problem
99
+ scripts/intent-drift-check.sh --rule loop-discipline --change-class extend
100
+ scripts/intent-drift-check.sh --standard loop-discipline --change-class extend --json
101
+ EOF
102
+ }
103
+
104
+ # === Arg parse ===
105
+ SKILL=""
106
+ RULE=""
107
+ STANDARD=""
108
+ HOOK=""
109
+ LUMINARY=""
110
+ ADR=""
111
+ PERSONA=""
112
+ TEMPLATE=""
113
+ CHANGE_CLASS=""
114
+ THRESHOLD=""
115
+ JSON_OUT=0
116
+
117
+ while [ $# -gt 0 ]; do
118
+ case "$1" in
119
+ --skill) SKILL="$2"; shift 2 ;;
120
+ --rule) RULE="$2"; shift 2 ;;
121
+ --standard) STANDARD="$2"; shift 2 ;;
122
+ --hook) HOOK="$2"; shift 2 ;;
123
+ --luminary) LUMINARY="$2"; shift 2 ;;
124
+ --adr) ADR="$2"; shift 2 ;;
125
+ --persona) PERSONA="$2"; shift 2 ;;
126
+ --template) TEMPLATE="$2"; shift 2 ;;
127
+ --change-class) CHANGE_CLASS="$2"; shift 2 ;;
128
+ --threshold) THRESHOLD="$2"; shift 2 ;;
129
+ --json) JSON_OUT=1; shift ;;
130
+ --help|-h) usage; exit 0 ;;
131
+ *) echo "Unknown arg: $1" >&2; usage >&2; exit 2 ;;
132
+ esac
133
+ done
134
+
135
+ # === Mutual exclusion — only one artifact flag at a time ===
136
+ FLAG_COUNT=0
137
+ [ -n "$SKILL" ] && FLAG_COUNT=$((FLAG_COUNT + 1))
138
+ [ -n "$RULE" ] && FLAG_COUNT=$((FLAG_COUNT + 1))
139
+ [ -n "$STANDARD" ] && FLAG_COUNT=$((FLAG_COUNT + 1))
140
+ [ -n "$HOOK" ] && FLAG_COUNT=$((FLAG_COUNT + 1))
141
+ [ -n "$LUMINARY" ] && FLAG_COUNT=$((FLAG_COUNT + 1))
142
+ [ -n "$ADR" ] && FLAG_COUNT=$((FLAG_COUNT + 1))
143
+ [ -n "$PERSONA" ] && FLAG_COUNT=$((FLAG_COUNT + 1))
144
+ [ -n "$TEMPLATE" ] && FLAG_COUNT=$((FLAG_COUNT + 1))
145
+
146
+ if [ "$FLAG_COUNT" -eq 0 ]; then
147
+ usage >&2
148
+ exit 2
149
+ fi
150
+
151
+ if [ "$FLAG_COUNT" -gt 1 ]; then
152
+ echo "ERROR: --skill, --rule, --standard, --hook, --luminary, --adr, --persona, --template are mutually exclusive; only one at a time" >&2
153
+ usage >&2
154
+ exit 2
155
+ fi
156
+
157
+ # === Change-class routing ===
158
+ # Default class is simplify (matches pre-fold-in behavior for skill body rewrites).
159
+ # Explicit --threshold overrides the class-derived value.
160
+ if [ -z "$CHANGE_CLASS" ]; then
161
+ CHANGE_CLASS="simplify"
162
+ fi
163
+
164
+ # Validate class + set default threshold per bet 24a Path 1 table.
165
+ CLASS_DEFAULT_THRESHOLD=""
166
+ IS_MATERIAL=0
167
+ case "$CHANGE_CLASS" in
168
+ preserve) CLASS_DEFAULT_THRESHOLD="0.95" ;;
169
+ simplify) CLASS_DEFAULT_THRESHOLD="0.90" ;;
170
+ extend) CLASS_DEFAULT_THRESHOLD="0.70" ;;
171
+ restructure) CLASS_DEFAULT_THRESHOLD="0.65" ;;
172
+ material) IS_MATERIAL=1; CLASS_DEFAULT_THRESHOLD="0.00" ;;
173
+ *) echo "ERROR: unknown --change-class: '$CHANGE_CLASS'" >&2
174
+ echo " Valid values: preserve, simplify, extend, restructure, material" >&2
175
+ exit 2 ;;
176
+ esac
177
+
178
+ # Explicit --threshold overrides the class default.
179
+ if [ -z "$THRESHOLD" ]; then
180
+ THRESHOLD="$CLASS_DEFAULT_THRESHOLD"
181
+ fi
182
+
183
+ # === Locate repo root ===
184
+ if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "$CLAUDE_PROJECT_DIR/.claude" ]; then
185
+ REPO_ROOT="$CLAUDE_PROJECT_DIR"
186
+ else
187
+ REPO_ROOT="$(pwd)"
188
+ fi
189
+
190
+ # === Route to artifact type ===
191
+ ARTIFACT_TYPE=""
192
+ ARTIFACT_SLUG=""
193
+ VECTOR_PATH=""
194
+ SOURCE_PATH=""
195
+
196
+ if [ -n "$SKILL" ]; then
197
+ ARTIFACT_TYPE="skill"
198
+ ARTIFACT_SLUG="$SKILL"
199
+ VECTOR_PATH="$REPO_ROOT/.claude/embeddings/skills/${SKILL}.json"
200
+ SOURCE_PATH="$REPO_ROOT/.claude/skills/${SKILL}/SKILL.md"
201
+ elif [ -n "$RULE" ]; then
202
+ ARTIFACT_TYPE="rule"
203
+ ARTIFACT_SLUG="$RULE"
204
+ VECTOR_PATH="$REPO_ROOT/.claude/embeddings/rules/${RULE}.json"
205
+ SOURCE_PATH="$REPO_ROOT/.claude/rules/${RULE}.md"
206
+ elif [ -n "$STANDARD" ]; then
207
+ ARTIFACT_TYPE="standard"
208
+ ARTIFACT_SLUG="$STANDARD"
209
+ VECTOR_PATH="$REPO_ROOT/.claude/embeddings/standards/${STANDARD}.json"
210
+ SOURCE_PATH="$REPO_ROOT/standards/${STANDARD}.md"
211
+ elif [ -n "$HOOK" ]; then
212
+ ARTIFACT_TYPE="hook"
213
+ ARTIFACT_SLUG="$HOOK"
214
+ VECTOR_PATH="$REPO_ROOT/.claude/embeddings/hooks/${HOOK}.json"
215
+ SOURCE_PATH="$REPO_ROOT/.claude/hooks/${HOOK}.sh"
216
+ elif [ -n "$LUMINARY" ]; then
217
+ ARTIFACT_TYPE="luminary"
218
+ ARTIFACT_SLUG="$LUMINARY"
219
+ VECTOR_PATH="$REPO_ROOT/.claude/embeddings/luminaries/${LUMINARY}.json"
220
+ SOURCE_PATH="$REPO_ROOT/.claude/luminaries/${LUMINARY}.md"
221
+ elif [ -n "$ADR" ]; then
222
+ ARTIFACT_TYPE="adr"
223
+ ARTIFACT_SLUG="$ADR"
224
+ VECTOR_PATH="$REPO_ROOT/.claude/embeddings/adrs/${ADR}.json"
225
+ SOURCE_PATH="$REPO_ROOT/architecture/decisions/${ADR}.md"
226
+ elif [ -n "$PERSONA" ]; then
227
+ ARTIFACT_TYPE="persona"
228
+ ARTIFACT_SLUG="$PERSONA"
229
+ VECTOR_PATH="$REPO_ROOT/.claude/embeddings/personas/${PERSONA}.json"
230
+ SOURCE_PATH="$REPO_ROOT/docs/personas/${PERSONA}.md"
231
+ elif [ -n "$TEMPLATE" ]; then
232
+ ARTIFACT_TYPE="template"
233
+ ARTIFACT_SLUG="$TEMPLATE"
234
+ VECTOR_PATH="$REPO_ROOT/.claude/embeddings/templates/${TEMPLATE}.json"
235
+ SOURCE_PATH="$REPO_ROOT/templates/${TEMPLATE}.md"
236
+ fi
237
+
238
+ # === Preconditions ===
239
+ if [ ! -f "$VECTOR_PATH" ]; then
240
+ echo "ERROR: saved vector not found at $VECTOR_PATH" >&2
241
+ echo " Run scripts/embed-lite-corpus.sh to generate baseline vectors first." >&2
242
+ exit 2
243
+ fi
244
+
245
+ if [ ! -f "$SOURCE_PATH" ]; then
246
+ echo "ERROR: source file not found at $SOURCE_PATH" >&2
247
+ exit 2
248
+ fi
249
+
250
+ # === Extract current body — one function per artifact shape ===
251
+ #
252
+ # EXTRACT_SAMPLE_BYTES is the byte window sampled from each artifact body.
253
+ # Voyage-3-lite context window is 32K tokens (~128KB in ASCII). 30000 chars
254
+ # ≈ 7500 tokens fits well under limit and covers 99% of SKILL bodies.
255
+ # MUST stay in sync with embed-lite-corpus.sh + embed-substrate-corpus.sh —
256
+ # cosine comparison only means anything when the drift-check samples the
257
+ # same text shape the baseline was embedded on. Per goal 2026-08-16g cure
258
+ # of chunk 2 coverage gap where L321 edit was invisible to 1500-char window.
259
+ EXTRACT_SAMPLE_BYTES="${EXTRACT_SAMPLE_BYTES:-30000}"
260
+
261
+ # === Extractors aligned with baseline generators per #1244 cure ===
262
+ # The functions below MUST emit byte-for-byte identical text to the baseline
263
+ # generator for the same file. Sources of truth:
264
+ # skill → scripts/embed-lite-corpus.sh:_extract_skill_text
265
+ # persona → scripts/embed-lite-corpus.sh:_extract_persona_text
266
+ # hook → scripts/embed-substrate-corpus.sh:_extract_hook_text
267
+ # rule → scripts/embed-substrate-corpus.sh:_extract_rule_text
268
+ # standard → scripts/embed-substrate-corpus.sh:_extract_standard_text (= rule shape)
269
+ # adr → scripts/embed-substrate-corpus.sh:_extract_adr_text
270
+ # template → scripts/embed-substrate-corpus.sh:_extract_template_text
271
+ # luminary → scripts/intent-matcher.sh:_intent_matcher_extract_luminary_text (deferred; falls back to rule shape)
272
+ # Tier 0 test at .claude/hooks/tests/intent-drift-extractor-alignment.test.sh
273
+ # pins baseline_extractor(fixture) == checker_extractor(fixture) per type.
274
+
275
+ extract_skill_text() {
276
+ # ALIGNED with embed-lite-corpus.sh:_extract_skill_text
277
+ local file="$1"
278
+ local desc body
279
+ desc=$(awk '/^description:/ { sub("^description:[[:space:]]*",""); gsub(/^"/,""); gsub(/"$/,""); print; exit }' "$file")
280
+ body=$(awk 'BEGIN { count=0 } /^---$/ { count++; next } count >= 2 { print }' "$file" | head -c "$EXTRACT_SAMPLE_BYTES")
281
+ printf 'Description: %s\n\n%s\n' "$desc" "$body" | LC_ALL=C tr -d '\000-\010\013\014\016-\037'
282
+ }
283
+
284
+ extract_hook_text() {
285
+ # ALIGNED with embed-substrate-corpus.sh:_extract_hook_text (per #1244)
286
+ # Baseline emits: Docstring: <stripped-#-header-first-20>\n\n<body-without-#-lines>
287
+ local file="$1"
288
+ local docstring body
289
+ docstring=$(awk '/^#/ { sub("^#[[:space:]]*",""); print } !/^#/ { exit }' "$file" | head -20)
290
+ body=$(awk '!/^#/ { print }' "$file" | head -c "$EXTRACT_SAMPLE_BYTES")
291
+ printf 'Docstring: %s\n\n%s\n' "$docstring" "$body" | LC_ALL=C tr -d '\000-\010\013\014\016-\037'
292
+ }
293
+
294
+ extract_rule_text() {
295
+ # ALIGNED with embed-substrate-corpus.sh:_extract_rule_text (per #1244)
296
+ # Baseline emits: Title: <first-H1>\n\n<body-after-2nd----->
297
+ local file="$1"
298
+ local title body
299
+ title=$(grep -m1 '^# ' "$file" | sed 's/^# //')
300
+ body=$(awk 'BEGIN { count=0 } /^---$/ { count++; next } count >= 2 { print }' "$file" | head -c "$EXTRACT_SAMPLE_BYTES")
301
+ # Rules may not have frontmatter — fall back to whole file
302
+ if [ -z "$body" ]; then
303
+ body=$(head -c "$EXTRACT_SAMPLE_BYTES" "$file")
304
+ fi
305
+ printf 'Title: %s\n\n%s\n' "$title" "$body" | LC_ALL=C tr -d '\000-\010\013\014\016-\037'
306
+ }
307
+
308
+ extract_standard_text() {
309
+ # ALIGNED with embed-substrate-corpus.sh:_extract_standard_text (= rule shape)
310
+ extract_rule_text "$1"
311
+ }
312
+
313
+ extract_adr_text() {
314
+ # ALIGNED with embed-substrate-corpus.sh:_extract_adr_text (per #1244)
315
+ # Baseline emits: ADR Title: <from-frontmatter>\n\n<body-after-2nd----->
316
+ local file="$1"
317
+ local title body
318
+ title=$(awk '/^title:/ { sub("^title:[[:space:]]*",""); gsub(/^"/,""); gsub(/"$/,""); print; exit }' "$file")
319
+ body=$(awk 'BEGIN { count=0 } /^---$/ { count++; next } count >= 2 { print }' "$file" | head -c "$EXTRACT_SAMPLE_BYTES")
320
+ printf 'ADR Title: %s\n\n%s\n' "$title" "$body" | LC_ALL=C tr -d '\000-\010\013\014\016-\037'
321
+ }
322
+
323
+ extract_persona_text() {
324
+ # ALIGNED with embed-lite-corpus.sh:_extract_persona_text (per #1244)
325
+ # Baseline emits: Role: <from-frontmatter>\n\n<body-after-2nd----->
326
+ local file="$1"
327
+ local role body
328
+ role=$(awk '/^role:/ { sub("^role:[[:space:]]*",""); print; exit }' "$file")
329
+ body=$(awk 'BEGIN { count=0 } /^---$/ { count++; next } count >= 2 { print }' "$file" | head -c "$EXTRACT_SAMPLE_BYTES")
330
+ printf 'Role: %s\n\n%s\n' "$role" "$body" | LC_ALL=C tr -d '\000-\010\013\014\016-\037'
331
+ }
332
+
333
+ extract_template_text() {
334
+ # ALIGNED with embed-substrate-corpus.sh:_extract_template_text (per #1244)
335
+ # Baseline emits: Template: <basename>\n\n<body-raw>
336
+ local file="$1"
337
+ local name body
338
+ name=$(basename "$file")
339
+ body=$(head -c "$EXTRACT_SAMPLE_BYTES" "$file")
340
+ printf 'Template: %s\n\n%s\n' "$name" "$body" | LC_ALL=C tr -d '\000-\010\013\014\016-\037'
341
+ }
342
+
343
+ extract_luminary_text() {
344
+ # NOT YET ALIGNED — deferred per #1244 out-of-scope note. Luminary baseline
345
+ # generator is intent-matcher.sh:_intent_matcher_extract_luminary_text which
346
+ # uses a complex frontmatter extractor (name + primary_domain + signature +
347
+ # solves_problems + anti_patterns). No luminaries in chunks 1a/1b/2/3 swept
348
+ # scope so false-positives here have not surfaced. Follow-on ticket owed if
349
+ # luminary intent-drift becomes load-bearing.
350
+ # For now, keep prior rule-shape fallback so calls do not fail.
351
+ local file="$1"
352
+ local body
353
+ if head -n 1 "$file" | grep -q '^---$'; then
354
+ body=$(awk 'BEGIN { count=0 } /^---$/ { count++; next } count >= 2 { print }' "$file" | head -c "$EXTRACT_SAMPLE_BYTES")
355
+ else
356
+ body=$(head -c "$EXTRACT_SAMPLE_BYTES" "$file")
357
+ fi
358
+ printf '%s\n' "$body" | LC_ALL=C tr -d '\000-\010\013\014\016-\037'
359
+ }
360
+
361
+ case "$ARTIFACT_TYPE" in
362
+ skill) CURRENT_TEXT=$(extract_skill_text "$SOURCE_PATH") ;;
363
+ hook) CURRENT_TEXT=$(extract_hook_text "$SOURCE_PATH") ;;
364
+ rule) CURRENT_TEXT=$(extract_rule_text "$SOURCE_PATH") ;;
365
+ standard) CURRENT_TEXT=$(extract_standard_text "$SOURCE_PATH") ;;
366
+ adr) CURRENT_TEXT=$(extract_adr_text "$SOURCE_PATH") ;;
367
+ persona) CURRENT_TEXT=$(extract_persona_text "$SOURCE_PATH") ;;
368
+ template) CURRENT_TEXT=$(extract_template_text "$SOURCE_PATH") ;;
369
+ luminary) CURRENT_TEXT=$(extract_luminary_text "$SOURCE_PATH") ;;
370
+ esac
371
+
372
+ # === Read saved vector ===
373
+ SAVED_EMB=$(jq -c '.embedding' "$VECTOR_PATH" 2>/dev/null)
374
+ if [ -z "$SAVED_EMB" ] || [ "$SAVED_EMB" = "null" ]; then
375
+ echo "ERROR: saved vector at $VECTOR_PATH has no .embedding field" >&2
376
+ exit 2
377
+ fi
378
+
379
+ # === STUB mode — skip Voyage, return cosine 1.0 ===
380
+ if [ "${INTENT_DRIFT_STUB:-0}" = "1" ]; then
381
+ COSINE="1.0"
382
+ MODE="STUB"
383
+ else
384
+ # === Live mode — re-embed via intent-matcher.sh ===
385
+ # shellcheck source=scripts/intent-matcher.sh
386
+ source "$REPO_ROOT/scripts/intent-matcher.sh"
387
+
388
+ if ! intent_matcher_is_live; then
389
+ echo "ERROR: intent-matcher.sh reports STUB (missing VOYAGE_API_KEY or ANTHROPIC_API_KEY)" >&2
390
+ echo " Set INTENT_DRIFT_STUB=1 to skip Voyage call for testing." >&2
391
+ exit 2
392
+ fi
393
+
394
+ TMP_EMB=$(mktemp)
395
+ trap "rm -f '$TMP_EMB'" EXIT
396
+
397
+ if ! _intent_matcher_live_embed "$CURRENT_TEXT" "$TMP_EMB"; then
398
+ echo "ERROR: Voyage embed call failed" >&2
399
+ exit 2
400
+ fi
401
+
402
+ CURRENT_EMB=$(jq -c '.embedding' "$TMP_EMB")
403
+ COSINE=$(intent_matcher_cosine "$SAVED_EMB" "$CURRENT_EMB")
404
+ MODE="LIVE"
405
+ fi
406
+
407
+ # === Compare against threshold ===
408
+ # Material class always passes (log only). Other classes compare cosine.
409
+ if [ "$IS_MATERIAL" -eq 1 ]; then
410
+ VERDICT="PASS-LOGGED"
411
+ else
412
+ # jq for numeric compare (bash arithmetic is int-only)
413
+ VERDICT=$(jq -n --argjson cos "$COSINE" --argjson thr "$THRESHOLD" \
414
+ 'if $cos >= $thr then "PASS" else "FAIL" end')
415
+ VERDICT=$(echo "$VERDICT" | tr -d '"')
416
+ fi
417
+
418
+ # === Output ===
419
+ if [ "$JSON_OUT" -eq 1 ]; then
420
+ jq -n \
421
+ --arg artifact_type "$ARTIFACT_TYPE" \
422
+ --arg artifact_slug "$ARTIFACT_SLUG" \
423
+ --arg change_class "$CHANGE_CLASS" \
424
+ --argjson cosine "$COSINE" \
425
+ --argjson threshold "$THRESHOLD" \
426
+ --arg verdict "$VERDICT" \
427
+ --arg mode "$MODE" \
428
+ '{
429
+ artifact_type: $artifact_type,
430
+ artifact_slug: $artifact_slug,
431
+ change_class: $change_class,
432
+ cosine: $cosine,
433
+ threshold: $threshold,
434
+ verdict: $verdict,
435
+ mode: $mode
436
+ }'
437
+ else
438
+ echo "Artifact: $ARTIFACT_TYPE/$ARTIFACT_SLUG"
439
+ echo "Change class: $CHANGE_CLASS"
440
+ if [ "$IS_MATERIAL" -eq 1 ]; then
441
+ echo "Cosine: $COSINE (material class — log only, no threshold)"
442
+ else
443
+ echo "Cosine: $COSINE (threshold $THRESHOLD)"
444
+ fi
445
+ echo "Verdict: $VERDICT ($MODE)"
446
+ fi
447
+
448
+ # === Exit code ===
449
+ # Material class always exits 0 regardless of cosine.
450
+ if [ "$IS_MATERIAL" -eq 1 ]; then
451
+ exit 0
452
+ elif [ "$VERDICT" = "PASS" ]; then
453
+ exit 0
454
+ else
455
+ exit 1
456
+ fi
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # Detect drift between committed lite-manifest.json and current substrate state.
4
+ #
5
+ # Runs the generator against current source; compares entries[] against
6
+ # the committed manifest. If they differ, exit 1 with a diff and cure
7
+ # instructions. Called by CI workflow .github/workflows/lite-manifest-drift-check.yml
8
+ # on every PR touching lite frontmatter or the manifest itself.
9
+ #
10
+ # Per bassclef-upstream#1020 Step 5. Session 2026-08-01d option b.
11
+ #
12
+ # Cures per pre-mortem risk ledger docs/risk-ledgers/2026-08-01c-lite-manifest.md:
13
+ # F2.4 + F2.5 — catches tier: lite edit without manifest bump
14
+ # F-b.N1 — generator + drift-check bump together (same PR); no false-fail
15
+ # F-b.N2 — 60s workflow timeout enforced at CI layer (this script itself is fast)
16
+ #
17
+ # Compares .entries[] only. Does NOT compare .generated_at or .upstream_commit
18
+ # — those always differ between runs; comparing them would fire on every PR.
19
+ #
20
+ # Usage:
21
+ # scripts/lite-manifest-drift-check.sh # check against ./lite-manifest.json
22
+ # scripts/lite-manifest-drift-check.sh --root PATH # override repo root (test harness)
23
+ # scripts/lite-manifest-drift-check.sh --manifest P # check against alternative manifest
24
+ #
25
+ # Exit codes:
26
+ # 0 — no drift; manifest matches current substrate
27
+ # 1 — drift detected OR missing dependency
28
+ # 2 — manifest file missing
29
+
30
+ set -euo pipefail
31
+
32
+ REPO_ROOT_OVERRIDE=""
33
+ MANIFEST_OVERRIDE=""
34
+ TARGET_TIER="lite" # per #1295 — extended to check lite/standard/ultra manifests
35
+
36
+ while [[ $# -gt 0 ]]; do
37
+ case "$1" in
38
+ --root) REPO_ROOT_OVERRIDE="${2:-}"; shift 2 ;;
39
+ --manifest) MANIFEST_OVERRIDE="${2:-}"; shift 2 ;;
40
+ --tier) TARGET_TIER="${2:-}"; shift 2 ;;
41
+ -h|--help)
42
+ grep -E '^# ' "$0" | sed 's/^# \?//'
43
+ exit 0
44
+ ;;
45
+ *) echo "Unknown arg: $1 (use --help)" >&2; exit 1 ;;
46
+ esac
47
+ done
48
+
49
+ case "$TARGET_TIER" in
50
+ lite|standard|ultra|standard-pro) ;;
51
+ *) echo "tier-manifest-drift-check: invalid --tier '$TARGET_TIER' (allowed: lite, standard, ultra, standard-pro)" >&2; exit 1 ;;
52
+ esac
53
+
54
+ if [[ -n "$REPO_ROOT_OVERRIDE" ]]; then
55
+ REPO_ROOT="$REPO_ROOT_OVERRIDE"
56
+ else
57
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
58
+ fi
59
+
60
+ if [[ -z "$REPO_ROOT" || ! -d "$REPO_ROOT" ]]; then
61
+ echo "lite-manifest-drift-check: cannot resolve repo root" >&2
62
+ exit 1
63
+ fi
64
+
65
+ MANIFEST_FILE="${MANIFEST_OVERRIDE:-${REPO_ROOT}/${TARGET_TIER}-manifest.json}"
66
+
67
+ if [[ ! -f "$MANIFEST_FILE" ]]; then
68
+ echo "lite-manifest-drift-check: manifest file not found at $MANIFEST_FILE" >&2
69
+ echo "Run scripts/generate-lite-manifest.sh to create it." >&2
70
+ exit 2
71
+ fi
72
+
73
+ for cmd in jq bash; do
74
+ if ! command -v "$cmd" >/dev/null 2>&1; then
75
+ echo "lite-manifest-drift-check: missing dependency: $cmd" >&2
76
+ exit 1
77
+ fi
78
+ done
79
+
80
+ TMP_CANDIDATE=$(mktemp)
81
+ trap 'rm -f "$TMP_CANDIDATE"' EXIT
82
+
83
+ bash "${REPO_ROOT}/scripts/generate-lite-manifest.sh" --tier "$TARGET_TIER" --root "$REPO_ROOT" --output "$TMP_CANDIDATE" 2>/dev/null
84
+
85
+ # Compare entries[] arrays (sorted; ignoring generated_at + upstream_commit)
86
+ COMMITTED_ENTRIES=$(jq -S '.entries' "$MANIFEST_FILE")
87
+ CANDIDATE_ENTRIES=$(jq -S '.entries' "$TMP_CANDIDATE")
88
+
89
+ if [[ "$COMMITTED_ENTRIES" == "$CANDIDATE_ENTRIES" ]]; then
90
+ echo "${TARGET_TIER}-manifest is up to date ($(jq '.entries | length' "$MANIFEST_FILE") entries)"
91
+ exit 0
92
+ fi
93
+
94
+ # Drift detected — surface concrete diff
95
+ echo "" >&2
96
+ echo "============================================================" >&2
97
+ echo "DRIFT DETECTED: lite-manifest.json does not match substrate" >&2
98
+ echo "============================================================" >&2
99
+ echo "" >&2
100
+
101
+ COMMITTED_COUNT=$(jq '.entries | length' "$MANIFEST_FILE")
102
+ CANDIDATE_COUNT=$(jq '.entries | length' "$TMP_CANDIDATE")
103
+ echo "Committed manifest: $COMMITTED_COUNT entries" >&2
104
+ echo "Current substrate: $CANDIDATE_COUNT entries" >&2
105
+ echo "" >&2
106
+
107
+ # Show added / removed slugs by type for readability
108
+ COMMITTED_KEYS=$(echo "$COMMITTED_ENTRIES" | jq -r '.[] | .type + "/" + .slug' | sort)
109
+ CANDIDATE_KEYS=$(echo "$CANDIDATE_ENTRIES" | jq -r '.[] | .type + "/" + .slug' | sort)
110
+
111
+ ADDED=$(comm -23 <(echo "$CANDIDATE_KEYS") <(echo "$COMMITTED_KEYS"))
112
+ REMOVED=$(comm -13 <(echo "$CANDIDATE_KEYS") <(echo "$COMMITTED_KEYS"))
113
+
114
+ if [[ -n "$ADDED" ]]; then
115
+ echo "Entries ADDED to substrate but missing from manifest:" >&2
116
+ echo "$ADDED" | sed 's/^/ + /' >&2
117
+ echo "" >&2
118
+ fi
119
+
120
+ if [[ -n "$REMOVED" ]]; then
121
+ echo "Entries in manifest but REMOVED from substrate:" >&2
122
+ echo "$REMOVED" | sed 's/^/ - /' >&2
123
+ echo "" >&2
124
+ fi
125
+
126
+ # Content hash changes (same slug, different content)
127
+ CONTENT_CHANGES=$(diff <(echo "$COMMITTED_ENTRIES" | jq -c '.[] | {type,slug,content_hash}' | sort) \
128
+ <(echo "$CANDIDATE_ENTRIES" | jq -c '.[] | {type,slug,content_hash}' | sort) \
129
+ | grep -E '^[<>]' | head -20 || true)
130
+ if [[ -n "$CONTENT_CHANGES" ]]; then
131
+ echo "Content-hash changes (same slug, different content):" >&2
132
+ echo "$CONTENT_CHANGES" | sed 's/^/ /' >&2
133
+ echo "" >&2
134
+ fi
135
+
136
+ echo "----- Cure -----" >&2
137
+ echo " bash scripts/generate-lite-manifest.sh" >&2
138
+ echo " # then edit lite-manifest.json manifest_version per semver bump policy:" >&2
139
+ echo " # entry added → minor bump (e.g., 1.0.0 → 1.1.0)" >&2
140
+ echo " # entry removed → major bump (e.g., 1.1.0 → 2.0.0; breaks partners)" >&2
141
+ echo " # content_hash change only → patch bump (e.g., 1.1.0 → 1.1.1)" >&2
142
+ echo " bash scripts/render-lite-manifest-doc.sh" >&2
143
+ echo " git add lite-manifest.json docs/lite-manifest.md" >&2
144
+ echo "" >&2
145
+
146
+ exit 1