@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,143 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # output-discipline.sh — classifier library for substrate output discipline audit.
4
+ #
5
+ # Answers per artifact: does this substrate file dispatch /kiss words on its
6
+ # operator-facing output? Returns one of four labels:
7
+ #
8
+ # PRESENT — body has /kiss dispatch pattern (dispatches /kiss words on output)
9
+ # ABSENT — body emits operator-facing prose but no /kiss dispatch found
10
+ # OPT_OUT — SKILL frontmatter declares no_operator_output: true
11
+ # NOT_APPLICABLE — artifact type does not emit operator-facing prose (rare;
12
+ # reserved for future opt-outs at type layer)
13
+ #
14
+ # The classifier reads the artifact body and checks for one of these patterns:
15
+ # - /kiss words --grade10 (canonical form per bassclef-upstream#858)
16
+ # - Dispatch .*kiss words (per audit-substrate-output-discipline SKILL Test 33)
17
+ # - /kiss dispatch (loose form)
18
+ #
19
+ # Per canvas docs/canvases/2026-07-25-substrate-output-discipline.md Stage 2.
20
+ # Ships as bet 2026-07-25a WU-A3 (bassclef-upstream#859 Iteration A).
21
+ #
22
+ # Usage — sourced by /audit-substrate-output-discipline skill + scripts/sweep-kiss-dispatch.sh.
23
+ # source lib/output-discipline.sh
24
+ # classify_artifact <artifact-type> <path> # prints label to stdout; exit 0 always
25
+ # has_kiss_dispatch <path> # exit 0 if present; exit 1 if absent
26
+ # has_opt_out <path> # exit 0 if opt-out declared; exit 1 if not
27
+ #
28
+ # Sister modules:
29
+ # - lib/mechanism-fidelity.sh (bet 27c WU-3) — sister audit classifier for rule-mechanism claims
30
+ # - scripts/intent-drift-check.sh — used by sweep script for cosine gate per edit
31
+
32
+ set -o pipefail
33
+
34
+ # === has_kiss_dispatch — bool ===
35
+ # Exit 0 if body contains a /kiss dispatch pattern; exit 1 otherwise.
36
+ #
37
+ # Per bassclef-upstream#1246 — the first alternative `/kiss words( --[a-z0-9]+)+`
38
+ # recognizes both single-flag (`/kiss words --grade10`) AND two-flag
39
+ # (`/kiss words --rewrite`, `/kiss words --grade8`, `/kiss words --rewrite --grade8`)
40
+ # forms explicitly. The Dispatch/dispatch alternatives remain as loose
41
+ # fallbacks for prose that mentions the dispatch verb without a bare invocation.
42
+ # Grace window per ADR-031 — the old single-flag form keeps passing while
43
+ # 91 skills sweep to the two-flag pointer.
44
+ has_kiss_dispatch() {
45
+ local file="$1"
46
+ [ -f "$file" ] || return 1
47
+ # Match any of the four canonical /kiss dispatch patterns
48
+ grep -qE '/kiss words( --[a-z0-9]+)+|Dispatch .{0,30}kiss words|dispatches /kiss on|dispatch .{0,30}/kiss' "$file" 2>/dev/null
49
+ }
50
+
51
+ # === has_opt_out — bool ===
52
+ # Exit 0 if SKILL frontmatter declares no_operator_output: true; exit 1 otherwise.
53
+ has_opt_out() {
54
+ local file="$1"
55
+ [ -f "$file" ] || return 1
56
+ # Extract frontmatter (between leading --- ... ---) and check for opt-out field
57
+ awk '/^---$/ { c++; if(c==2) exit; next } c==1' "$file" 2>/dev/null | grep -qE '^no_operator_output:[[:space:]]*true'
58
+ }
59
+
60
+ # === classify_artifact — return label ===
61
+ # Prints one of: PRESENT | ABSENT | OPT_OUT | NOT_APPLICABLE
62
+ # Exit 0 always (informational; not a gate).
63
+ classify_artifact() {
64
+ local artifact_type="$1"
65
+ local file="$2"
66
+
67
+ if [ ! -f "$file" ]; then
68
+ echo "NOT_APPLICABLE"
69
+ return 0
70
+ fi
71
+
72
+ # Opt-out declared in frontmatter (skills only today; extensible)
73
+ if has_opt_out "$file"; then
74
+ echo "OPT_OUT"
75
+ return 0
76
+ fi
77
+
78
+ # Check for /kiss dispatch pattern in body
79
+ if has_kiss_dispatch "$file"; then
80
+ echo "PRESENT"
81
+ else
82
+ echo "ABSENT"
83
+ fi
84
+ return 0
85
+ }
86
+
87
+ # === walk_type — enumerate all files of a given artifact type ===
88
+ # Prints one path per line. Used by audit skill to iterate.
89
+ # Usage: walk_type <artifact-type> [repo-root]
90
+ walk_type() {
91
+ local artifact_type="$1"
92
+ local repo_root="${2:-$(pwd)}"
93
+
94
+ case "$artifact_type" in
95
+ skill|skills)
96
+ find "$repo_root/.claude/skills" -maxdepth 2 -name "SKILL.md" -type f 2>/dev/null | sort
97
+ ;;
98
+ rule|rules)
99
+ find "$repo_root/.claude/rules" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort
100
+ ;;
101
+ standard|standards)
102
+ find "$repo_root/standards" -maxdepth 2 -name "*.md" -type f 2>/dev/null | sort
103
+ ;;
104
+ hook|hooks)
105
+ find "$repo_root/.claude/hooks" -maxdepth 1 -name "*.sh" -type f 2>/dev/null | sort
106
+ ;;
107
+ luminary|luminaries)
108
+ find "$repo_root/.claude/luminaries" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort
109
+ ;;
110
+ adr|adrs)
111
+ find "$repo_root/architecture/decisions" -maxdepth 1 -name "ADR-*.md" -type f 2>/dev/null | sort
112
+ ;;
113
+ persona|personas)
114
+ find "$repo_root/docs/personas" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort
115
+ ;;
116
+ template|templates)
117
+ find "$repo_root/templates" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort
118
+ ;;
119
+ *)
120
+ return 1
121
+ ;;
122
+ esac
123
+ return 0
124
+ }
125
+
126
+ # === slug_from_path — extract slug from a file path ===
127
+ # Usage: slug_from_path <artifact-type> <path>
128
+ slug_from_path() {
129
+ local artifact_type="$1"
130
+ local file="$2"
131
+
132
+ case "$artifact_type" in
133
+ skill|skills)
134
+ basename "$(dirname "$file")"
135
+ ;;
136
+ hook|hooks)
137
+ basename "$file" .sh
138
+ ;;
139
+ *)
140
+ basename "$file" .md
141
+ ;;
142
+ esac
143
+ }
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ # lib/prose-scan-boundary.sh
5
+ #
6
+ # Deep module per @luminary john-ousterhout — narrow interface hides
7
+ # the boundary detection and counter file work behind 3 functions.
8
+ #
9
+ # Purpose — every prose scan hook (kiss, appositive, citation)
10
+ # strips fenced or echoed content BEFORE it scans, and writes counter
11
+ # state that plain-english-steering.sh injects into the next prompt.
12
+ #
13
+ # This closes #1443 canonical case — Stop side prose hooks that fire
14
+ # on verbatim BLOCKED banner echoes at session start plus operator
15
+ # picked shorthand labels. 5 rewrite cycles in 20 turns documented
16
+ # the cost during 2026-09-09b prep.
17
+ #
18
+ # Anchor luminaries:
19
+ # @luminary john-ousterhout — deep module hiding the boundary work
20
+ # @luminary donald-norman — feedback loop through the counter markers
21
+ # @luminary tony-hoare — pre and postcondition on each function
22
+ # @luminary michael-feathers — characterization tests pin behavior
23
+ #
24
+ # Consumers source this lib:
25
+ # source lib/prose-scan-boundary.sh
26
+ #
27
+ # Tests: lib/tests/prose-scan-boundary.test.sh (Tier 0 strict TDD)
28
+
29
+ # =========================================================================
30
+ # strip_scan_boundaries — remove content the scan should not read
31
+ # =========================================================================
32
+ # Interface: IProseScanBoundary (Larman)
33
+ # Inputs: $1 = message text
34
+ # Outputs: stdout — message with fenced or echoed content removed
35
+ # Exit: 0 always
36
+ #
37
+ # Removes:
38
+ # 1. BLOCKED banner blocks (session-start hook echoes; agent cannot rewrite)
39
+ # 2. <!--SCAN_SKIP-->...<!--/SCAN_SKIP--> explicit fence markers
40
+ # 3. Fenced code blocks (``` ... ```)
41
+ # 4. Inline code (`...`)
42
+ strip_scan_boundaries() {
43
+ local message_text="$1"
44
+ [ -z "$message_text" ] && return 0
45
+ # Python does the heavy lifting for regex-safe multiline strip.
46
+ # Fall back to raw text when python3 missing (fail-soft per Nygard).
47
+ if command -v python3 >/dev/null 2>&1; then
48
+ printf '%s' "$message_text" | python3 -c '
49
+ import sys
50
+ import re
51
+
52
+ text = sys.stdin.read()
53
+
54
+ # 1. Strip BLOCKED banner blocks. Banner shape:
55
+ # 🛑🛑🛑 BLOCKED 🛑🛑🛑
56
+ # ────────────
57
+ # content
58
+ # ────────────
59
+ # ACTION: resolve OR ...
60
+ # Silence is not deferral. ...
61
+ #
62
+ # Match the banner line and content until one of:
63
+ # - "Silence is not deferral" line followed by newline (canonical end)
64
+ # - Two consecutive newlines (paragraph break)
65
+ # - Next banner line
66
+ # - End of string
67
+ text = re.sub(
68
+ r"\U0001F6D1\U0001F6D1\U0001F6D1 BLOCKED \U0001F6D1\U0001F6D1\U0001F6D1"
69
+ r".*?"
70
+ r"(?:Silence is not deferral\.[^\n]*\n"
71
+ r"|\n\n(?=\S|\n|$)"
72
+ r"|(?=\U0001F6D1\U0001F6D1\U0001F6D1)"
73
+ r"|$)",
74
+ "",
75
+ text,
76
+ flags=re.DOTALL,
77
+ )
78
+
79
+ # 2. Strip explicit SCAN_SKIP fences
80
+ text = re.sub(
81
+ r"<!--SCAN_SKIP-->.*?<!--/SCAN_SKIP-->",
82
+ "",
83
+ text,
84
+ flags=re.DOTALL,
85
+ )
86
+
87
+ # 3. Strip fenced code blocks
88
+ text = re.sub(r"```.*?```", "", text, flags=re.DOTALL)
89
+
90
+ # 4. Strip inline code
91
+ text = re.sub(r"`[^`]+`", "", text)
92
+
93
+ sys.stdout.write(text)
94
+ '
95
+ else
96
+ # No python — return raw text so consumers still work
97
+ printf '%s' "$message_text"
98
+ fi
99
+ return 0
100
+ }
101
+
102
+ # =========================================================================
103
+ # write_prose_counter — persist a per-turn counter for the steering hook
104
+ # =========================================================================
105
+ # Interface: IProseCounterWriter (Larman)
106
+ # Inputs: $1 = counter name (e.g. "kiss_hits", "appositive_hits")
107
+ # $2 = numeric count
108
+ # $3 = optional JSON payload (findings, offenders, etc.)
109
+ # Outputs: writes state/markers/turn-prose-counters/<name>.json atomically
110
+ # Exit: 0 always (fail-soft on write error per Nygard)
111
+ write_prose_counter() {
112
+ local name="$1"
113
+ local count="$2"
114
+ local payload="${3:-{\}}"
115
+ [ -z "$name" ] && return 0
116
+ [ -z "$count" ] && count=0
117
+ local repo_root
118
+ repo_root="$(_prose_scan_repo_root)"
119
+ local dir="${repo_root}/state/markers/turn-prose-counters"
120
+ local out_file="${dir}/${name}.json"
121
+ mkdir -p "$dir" 2>/dev/null || return 0
122
+ local ts
123
+ ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "unknown")"
124
+ # Atomic write via tmp + mv
125
+ local tmp
126
+ tmp="$(mktemp "${dir}/.${name}.XXXXXX")" || return 0
127
+ cat > "$tmp" <<JSON
128
+ {
129
+ "counter": "${name}",
130
+ "count": ${count},
131
+ "updated_at": "${ts}",
132
+ "payload": ${payload}
133
+ }
134
+ JSON
135
+ mv "$tmp" "$out_file" 2>/dev/null || rm -f "$tmp"
136
+ return 0
137
+ }
138
+
139
+ # =========================================================================
140
+ # read_prose_counter — read a per-turn counter for prompt injection
141
+ # =========================================================================
142
+ # Interface: IProseCounterReader (Larman)
143
+ # Inputs: $1 = counter name
144
+ # Outputs: stdout — JSON content, or empty when marker missing
145
+ # Exit: 0 always (fail-soft per Nygard)
146
+ read_prose_counter() {
147
+ local name="$1"
148
+ [ -z "$name" ] && return 0
149
+ local repo_root
150
+ repo_root="$(_prose_scan_repo_root)"
151
+ local marker="${repo_root}/state/markers/turn-prose-counters/${name}.json"
152
+ [ -f "$marker" ] || return 0
153
+ cat "$marker" 2>/dev/null || true
154
+ return 0
155
+ }
156
+
157
+ # =========================================================================
158
+ # _prose_scan_repo_root — resolve repo root under both install classes
159
+ # =========================================================================
160
+ # Internal helper. Prefer $CLAUDE_PROJECT_DIR (project install), fall
161
+ # back to lib file's parent (operator install). Per install-class: dual
162
+ # discipline from ADR-035 substrate-as-system tenet.
163
+ _prose_scan_repo_root() {
164
+ if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then
165
+ echo "${CLAUDE_PROJECT_DIR}"
166
+ return 0
167
+ fi
168
+ local script_dir
169
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
170
+ cd "${script_dir}/.." && pwd
171
+ }
@@ -0,0 +1,214 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # rewrite-check.sh — six heuristic checks for /kiss REWRITE mode (goal 25d Step 5).
4
+ #
5
+ # Grounded in four public sources:
6
+ # - UK ONS Style Guide — Plain English section
7
+ # - Nielsen Norman Group — Plain Language for Experts (2018)
8
+ # - US Federal Plain Language Guidelines (plainlanguage.gov)
9
+ # - Hemingway Editor heuristics (public rules — passive, adverbs, long sentences)
10
+ #
11
+ # The six checks are HEURISTIC not perfect NLP. They surface CANDIDATES for
12
+ # human review. Not a gate. Not a blocking check. Exit 0 always.
13
+ #
14
+ # Sister to lib/output-discipline.sh (classifier for /kiss dispatch presence)
15
+ # and lib/code-comment-discipline.sh (classifier for header comment jargon).
16
+ # Same Ousterhout deep-module shape — small interface, shared implementation.
17
+ #
18
+ # Per bassclef-upstream#859 Iteration D + goal 25d Step 5.
19
+ # Bash 3.2+ compatible.
20
+ #
21
+ # Usage — sourced by /kiss words --rewrite mode:
22
+ # source lib/rewrite-check.sh
23
+ # check_passive_voice <file> # prints lines with passive-voice patterns
24
+ # check_hyphen_words <file> # prints hyphenated compound words flagged
25
+ # check_sentence_length <file> [N] # prints sentences over N words (default 25)
26
+ # check_svo <file> # prints sentences NOT starting with subject
27
+ # check_noun_stack <file> # prints 3+ noun runs without verb
28
+ # check_adverb_density <file> # prints density note if >5% -ly adverbs
29
+ # run_rewrite_checks <file> # runs all six; prints findings table
30
+
31
+ set -o pipefail
32
+
33
+ # === Standard tech hyphens allow-list ===
34
+ # Common tech compound words that should NOT be flagged as jargon.
35
+ # Grep-alternation shape (pipe-separated).
36
+ REWRITE_TECH_HYPHENS='pre-commit|pre-push|post-commit|post-push|real-time|data-driven|end-to-end|non-blocking|read-only|copy-on-write|test-driven|type-safe|open-source|self-hosted|cross-platform|multi-tenant|single-tenant|full-stack|front-end|back-end|no-op|no-code|low-code|check-in|check-out|log-in|log-out|sign-in|sign-out|drop-down|pop-up|pull-request|merge-request|feature-branch|main-branch|no-fast-forward|off-the-shelf|out-of-the-box'
37
+
38
+ # === Passive voice check ===
39
+ # Pattern per Hemingway Editor: "to be" + past participle + optional "by".
40
+ # Common shapes:
41
+ # was <verb-ed> by
42
+ # were <verb-ed> by
43
+ # has been <verb-ed>
44
+ # have been <verb-ed>
45
+ # had been <verb-ed>
46
+ # is being <verb-ed>
47
+ # was <verb-ed>
48
+ # Returns matching lines. Exit 0 always.
49
+ check_passive_voice() {
50
+ local file="$1"
51
+ [ -f "$file" ] || return 0
52
+ grep -niE '\b(was|were|has been|have been|had been|is being|are being|will be) [a-z]+(ed|en) (by|to)\b|\b(was|were|has been|have been|had been) [a-z]+(ed|en)\b' "$file" 2>/dev/null
53
+ return 0
54
+ }
55
+
56
+ # === Hyphen-word check ===
57
+ # Flag hyphenated compounds that are NOT in the standard tech allow-list.
58
+ # Per operator-facing-prose.md — hyphen words like X-side, N-layer X, X-as-Y
59
+ # should be rewritten to plain description.
60
+ check_hyphen_words() {
61
+ local file="$1"
62
+ [ -f "$file" ] || return 0
63
+ # Extract hyphenated words (letters-letters); filter to those NOT in the allow-list
64
+ # AND that look like adjective compounds (letters-letters where both parts are 2+ chars).
65
+ grep -oE '\b[a-z]{2,}-[a-z]{2,}\b' "$file" 2>/dev/null \
66
+ | sort -u \
67
+ | grep -vwE "$REWRITE_TECH_HYPHENS" \
68
+ | head -20
69
+ return 0
70
+ }
71
+
72
+ # === Sentence length check ===
73
+ # Flag sentences over N words (default 25 per Hemingway + Federal Plain
74
+ # Language recommendation). Very-long sentences (>30) get the strong flag.
75
+ check_sentence_length() {
76
+ local file="$1"
77
+ local ceiling="${2:-25}"
78
+ [ -f "$file" ] || return 0
79
+ # Split on sentence-ending punctuation, count words per sentence.
80
+ # awk splits, counts words, flags any over ceiling.
81
+ awk -v CEIL="$ceiling" '
82
+ BEGIN { RS = "[.!?][[:space:]]+" }
83
+ {
84
+ # Count words (whitespace-separated tokens)
85
+ n = split($0, words, /[[:space:]]+/)
86
+ if (n > CEIL) {
87
+ # Trim + print first 60 chars for context
88
+ sample = substr($0, 1, 60)
89
+ gsub(/\n/, " ", sample)
90
+ printf "SENTENCE: %d words — %s...\n", n, sample
91
+ }
92
+ }
93
+ ' "$file" 2>/dev/null
94
+ return 0
95
+ }
96
+
97
+ # === SVO check ===
98
+ # Heuristic: flag sentences that START with a preposition or subordinating
99
+ # conjunction rather than a subject. Common non-SVO openers:
100
+ # Because, Although, While, When, If, Since, Unless, Whereas, After, Before
101
+ # In X, On X, At X, With X, Under X, Without X, Despite X, Given X
102
+ # This catches the most common non-SVO shapes. False positives happen on
103
+ # legitimate variety; the check surfaces candidates for review.
104
+ check_svo() {
105
+ local file="$1"
106
+ [ -f "$file" ] || return 0
107
+ # Grep for lines starting with (after optional whitespace/quote) a non-SVO opener.
108
+ grep -niE '^[[:space:]]*(Because|Although|While|When|If|Since|Unless|Whereas|After|Before|In [A-Z]|On [A-Z]|At [A-Z]|With [A-Z]|Under [A-Z]|Without [A-Z]|Despite|Given)\b' "$file" 2>/dev/null
109
+ return 0
110
+ }
111
+
112
+ # === Noun-stack check ===
113
+ # Per operator-facing-prose.md: no more than 2 nouns in a row without a verb.
114
+ # Heuristic: look for 3+ consecutive words that end in common noun suffixes
115
+ # OR that are capitalized (proper nouns). Approximate.
116
+ #
117
+ # The heuristic flags runs of 3+ words that look like nouns (no verb-form
118
+ # suffix like -ed, -ing, -es, -s at verb pattern). Bash regex is limited;
119
+ # we use a simple approximation:
120
+ # - 3+ consecutive words each 4+ chars long
121
+ # - none ending in -ed, -ing, -ly, -s (verb / adverb / plural marker)
122
+ # - separated by single space (not by "the", "a", "an", "and", "or", "of")
123
+ # Approximation: words separated only by spaces (no articles/verbs between).
124
+ check_noun_stack() {
125
+ local file="$1"
126
+ [ -f "$file" ] || return 0
127
+ # Simple heuristic: match "word word word word" with no small connector.
128
+ # Look for runs of 4+ non-tiny words in a row.
129
+ awk '
130
+ {
131
+ # Split into words, find runs of 3+ nouny-looking tokens
132
+ n = split($0, w, /[[:space:]]+/)
133
+ run = 0
134
+ run_words = ""
135
+ for (i = 1; i <= n; i++) {
136
+ word = w[i]
137
+ # Strip punctuation for the check
138
+ clean = word
139
+ gsub(/[^A-Za-z]/, "", clean)
140
+ # Nouny heuristic: 4+ chars, not a verb/adverb marker, not a small word
141
+ if (length(clean) >= 4 \
142
+ && clean !~ /^(the|and|for|with|from|that|this|when|where|which|been|were|have|will|would|could|should|about|because|through|these|those)$/ \
143
+ && clean !~ /(ing|ed|ly)$/) {
144
+ run++
145
+ run_words = run_words " " clean
146
+ } else {
147
+ if (run >= 3) {
148
+ printf "NOUN-STACK: %d nouns —%s\n", run, run_words
149
+ }
150
+ run = 0
151
+ run_words = ""
152
+ }
153
+ }
154
+ if (run >= 3) {
155
+ printf "NOUN-STACK: %d nouns —%s\n", run, run_words
156
+ }
157
+ }
158
+ ' "$file" 2>/dev/null
159
+ return 0
160
+ }
161
+
162
+ # === Adverb density check ===
163
+ # Per Hemingway Editor: -ly adverbs above 1 per 20 words signal weak verbs.
164
+ # Federal Plain Language: prefer strong verbs over adverbs.
165
+ # Threshold: >5% (equivalent to 1 per 20 words).
166
+ check_adverb_density() {
167
+ local file="$1"
168
+ [ -f "$file" ] || return 0
169
+ local total_words adverb_count percent
170
+ # Count total words
171
+ total_words=$(tr -s '[:space:]' '\n' < "$file" | grep -cE '^[A-Za-z]+' 2>/dev/null || echo 0)
172
+ # Count -ly adverbs (word ending -ly, excluding common -ly words that aren't adverbs)
173
+ adverb_count=$(grep -oiE '\b[a-z]{3,}ly\b' "$file" 2>/dev/null \
174
+ | grep -viwE 'only|early|ally|belly|silly|fully|jolly|holy|really|apply|comply|imply|reply|supply|family|reply' \
175
+ | wc -l | tr -d ' ')
176
+ if [ "$total_words" -eq 0 ]; then
177
+ return 0
178
+ fi
179
+ # Compute percent (integer math * 1000 for one decimal)
180
+ percent=$((adverb_count * 1000 / total_words))
181
+ # Threshold: 50 = 5.0%
182
+ if [ "$percent" -gt 50 ]; then
183
+ printf "ADVERB density: %d/%d words = %d.%d%%\n" \
184
+ "$adverb_count" "$total_words" "$((percent / 10))" "$((percent % 10))"
185
+ fi
186
+ return 0
187
+ }
188
+
189
+ # === Composite check ===
190
+ # Runs all six checks against a file. Prints a findings table.
191
+ # Exit 0 always (informational, not a gate).
192
+ run_rewrite_checks() {
193
+ local file="$1"
194
+ [ -f "$file" ] || return 0
195
+ local passive hyphens length svo nouns adverbs
196
+ passive=$(check_passive_voice "$file")
197
+ hyphens=$(check_hyphen_words "$file")
198
+ length=$(check_sentence_length "$file")
199
+ svo=$(check_svo "$file")
200
+ nouns=$(check_noun_stack "$file")
201
+ adverbs=$(check_adverb_density "$file")
202
+
203
+ echo "=== REWRITE check findings: $file ==="
204
+ [ -n "$passive" ] && { echo "-- passive voice --"; echo "$passive"; }
205
+ [ -n "$hyphens" ] && { echo "-- hyphen compounds --"; echo "$hyphens"; }
206
+ [ -n "$length" ] && { echo "-- long sentences --"; echo "$length"; }
207
+ [ -n "$svo" ] && { echo "-- non-SVO openers --"; echo "$svo"; }
208
+ [ -n "$nouns" ] && { echo "-- noun stacks --"; echo "$nouns"; }
209
+ [ -n "$adverbs" ] && { echo "-- adverb density --"; echo "$adverbs"; }
210
+ if [ -z "$passive$hyphens$length$svo$nouns$adverbs" ]; then
211
+ echo "CLEAN — no rewrite findings"
212
+ fi
213
+ return 0
214
+ }
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # lib/session-rescue.sh — sweep resolved session-rescue entries to completed/.
4
+ #
5
+ # Deep module per @luminary john-ousterhout. Single-function interface hides
6
+ # the tracked-only check, soft cap, git-mv preference, banner emission, and
7
+ # silent-fail behavior.
8
+ #
9
+ # tier: upstream
10
+ # install-class: dual
11
+ #
12
+ # Interface (what callers know):
13
+ # sweep_resolved_rescue_entries <repo-root>
14
+ # — call from session-end.sh after MUST-tier checks pass
15
+ # — moves every docs/deferred-actions/*-session-rescue.md file to completed/
16
+ # — returns 0 on success (including no-op)
17
+ # — silent-fails on I/O errors (never crashes the caller)
18
+ #
19
+ # Hidden (what the module encapsulates):
20
+ # - Soft cap: 20 entries per call (Nygard fuse against runaway sweep)
21
+ # - Move mechanism per source state:
22
+ # tracked source → git mv (audit trail preserved)
23
+ # untracked source → plain mv (rescue class is gitignored per
24
+ # .gitignore L40 by design; save-state.sh L214 rationale — runtime
25
+ # markers should not commit)
26
+ # - Banner: prints "swept N entries" plus one line per file
27
+ # - Directory creation: mkdir -p completed/ on first call
28
+ # - Silent-fail: any I/O failure returns 0 (never crash caller)
29
+ #
30
+ # Postcondition (@luminary tony-hoare):
31
+ # - When rc=0 AND the pre-sweep count was ≤20, zero *-session-rescue.md
32
+ # files remain in docs/deferred-actions/ (both tracked and untracked
33
+ # sources move; tracked via git mv, untracked via plain mv).
34
+ #
35
+ # Consumers:
36
+ # - .claude/hooks/session-end.sh — calls after L207 (whereami check)
37
+ # when NEEDS_RESCUE=0 (all MUST-tier obligations satisfied)
38
+ #
39
+ # Rules governed by:
40
+ # - .claude/rules/testing-tier-config.md (Tier 0 strict TDD; lib/*.sh)
41
+ # - .claude/rules/bootstrap-pair-discipline.md (write path at
42
+ # session-end.sh L318 pairs with this sweep path)
43
+ # - .claude/rules/deferred-actions.md (Pattern B — work done, mv to completed)
44
+ #
45
+ # Anchor luminaries:
46
+ # - @luminary john-ousterhout — deep module
47
+ # - @luminary michael-feathers — characterization lead; tests pin observed shape
48
+ # - @luminary tony-hoare — postcondition contract
49
+ # - @luminary michael-nygard — soft cap = circuit-breaker
50
+ # - @luminary linus-torvalds — tracked-only = reversibility
51
+ # - @luminary jerome-saltzer-and-michael-schroeder — fail-safe defaults
52
+ # (deny sweep unless positive proof of clean state; caller supplies it)
53
+
54
+ # Soft cap — if more than this many rescue entries exist, warn and skip.
55
+ # Signals that something is wrong; operator should intervene manually.
56
+ _SESSION_RESCUE_SOFT_CAP=20
57
+
58
+ # sweep_resolved_rescue_entries <repo-root>
59
+ #
60
+ # Moves every tracked *-session-rescue.md file from
61
+ # <repo-root>/docs/deferred-actions/ to <repo-root>/docs/deferred-actions/completed/.
62
+ # Emits a banner with count + per-file list.
63
+ sweep_resolved_rescue_entries() {
64
+ local repo_root="${1:-}"
65
+ if [ -z "$repo_root" ] || [ ! -d "$repo_root" ]; then
66
+ return 0
67
+ fi
68
+
69
+ local active_dir="$repo_root/docs/deferred-actions"
70
+ if [ ! -d "$active_dir" ]; then
71
+ return 0
72
+ fi
73
+
74
+ # Enumerate rescue entries — nullglob-safe via find.
75
+ local entries
76
+ entries=$(find "$active_dir" -maxdepth 1 -name '*-session-rescue.md' -type f 2>/dev/null)
77
+ if [ -z "$entries" ]; then
78
+ return 0
79
+ fi
80
+
81
+ local count
82
+ count=$(printf '%s\n' "$entries" | wc -l | tr -d ' ')
83
+
84
+ # Nygard soft cap — refuse runaway sweep, print warning.
85
+ if [ "$count" -gt "$_SESSION_RESCUE_SOFT_CAP" ]; then
86
+ echo ""
87
+ echo "============================================"
88
+ echo "SESSION-RESCUE SWEEP SKIPPED (soft cap $_SESSION_RESCUE_SOFT_CAP)"
89
+ echo "============================================"
90
+ echo ""
91
+ echo "Found $count rescue entries in $active_dir."
92
+ echo "Soft cap prevents sweeping more than $_SESSION_RESCUE_SOFT_CAP at once."
93
+ echo "This many stale entries signal something is off — operator should"
94
+ echo "review and mv manually via: git mv docs/deferred-actions/*-session-rescue.md \\"
95
+ echo " docs/deferred-actions/completed/"
96
+ echo "============================================"
97
+ echo ""
98
+ return 0
99
+ fi
100
+
101
+ # Ensure completed/ subdir exists — silent-fail on error.
102
+ mkdir -p "$active_dir/completed" 2>/dev/null || return 0
103
+
104
+ local moved=0
105
+ local moved_paths=""
106
+
107
+ # Iterate — one file per line from find output.
108
+ # Rescue class matches .gitignore L40 by design. Typical file is
109
+ # untracked. Sweep tries git mv when tracked (rare); plain mv otherwise.
110
+ # Destination lives in completed/ which is NOT gitignored — operator
111
+ # can git add later to preserve the archive in history.
112
+ while IFS= read -r entry; do
113
+ [ -z "$entry" ] && continue
114
+ local base
115
+ base=$(basename "$entry")
116
+ local rel_active="docs/deferred-actions/$base"
117
+ local rel_done="docs/deferred-actions/completed/$base"
118
+
119
+ local tracked
120
+ tracked=$(git -C "$repo_root" ls-files --error-unmatch "$rel_active" 2>/dev/null)
121
+
122
+ if [ -n "$tracked" ]; then
123
+ # Tracked source — prefer git mv for audit-trail preservation.
124
+ if git -C "$repo_root" mv "$rel_active" "$rel_done" 2>/dev/null; then
125
+ moved=$((moved + 1))
126
+ moved_paths="${moved_paths} ${rel_active} → completed/ (git mv)\n"
127
+ fi
128
+ else
129
+ # Untracked source (gitignored per .gitignore L40) — plain mv.
130
+ if mv "$repo_root/$rel_active" "$repo_root/$rel_done" 2>/dev/null; then
131
+ moved=$((moved + 1))
132
+ moved_paths="${moved_paths} ${rel_active} → completed/ (plain mv; source gitignored)\n"
133
+ fi
134
+ fi
135
+ done <<< "$entries"
136
+
137
+ # Norman feedback — banner names count + each moved file.
138
+ if [ "$moved" -gt 0 ]; then
139
+ echo ""
140
+ echo "============================================"
141
+ echo "SESSION-RESCUE SWEEP"
142
+ echo "============================================"
143
+ echo ""
144
+ echo "Moved $moved resolved session-rescue entries to completed/:"
145
+ printf "$moved_paths"
146
+ echo "============================================"
147
+ echo ""
148
+ fi
149
+
150
+ return 0
151
+ }