@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.
- package/dist/cli.cjs +65 -0
- package/dist/cli.js +65 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lite/.claude/agents/architect.md +70 -0
- package/dist/lite/.claude/agents/builder.md +114 -0
- package/dist/lite/.claude/agents/designer.md +156 -0
- package/dist/lite/.claude/agents/reviewer.md +88 -0
- package/dist/lite/.claude/hooks/bassclef-sync.sh +85 -27
- package/dist/lite/.claude/hooks/longrun-prep-compounding-axis-check.sh +492 -0
- package/dist/lite/.claude/hooks/longrun-prep-render-check.sh +236 -0
- package/dist/lite/.claude/hooks/persona-name-scrub-check.sh +105 -0
- package/dist/lite/.claude/hooks/skill-step-list-check.sh +171 -0
- package/dist/lite/.claude/luminaries/alan-cooper.md +170 -0
- package/dist/lite/.claude/luminaries/alistair-cockburn.md +140 -0
- package/dist/lite/.claude/luminaries/amazon-pr-faq.md +34 -0
- package/dist/lite/.claude/luminaries/andreas-zeller.md +74 -0
- package/dist/lite/.claude/luminaries/ash-maurya.md +121 -0
- package/dist/lite/.claude/luminaries/bill-buxton.md +210 -0
- package/dist/lite/.claude/luminaries/charles-sanders-peirce.md +150 -0
- package/dist/lite/.claude/luminaries/david-ogilvy.md +192 -0
- package/dist/lite/.claude/luminaries/don-norman.md +173 -0
- package/dist/lite/.claude/luminaries/edward-tufte.md +179 -0
- package/dist/lite/.claude/luminaries/eric-evans.md +160 -0
- package/dist/lite/.claude/luminaries/frederick-brooks.md +50 -0
- package/dist/lite/.claude/luminaries/gang-of-four.md +157 -0
- package/dist/lite/.claude/luminaries/glenford-myers.md +230 -0
- package/dist/lite/.claude/luminaries/hunt-thomas.md +115 -0
- package/dist/lite/.claude/luminaries/hyrum-wright.md +94 -0
- package/dist/lite/.claude/luminaries/jason-fried-dhh.md +46 -0
- package/dist/lite/.claude/luminaries/jesse-james-garrett.md +154 -0
- package/dist/lite/.claude/luminaries/john-ousterhout.md +94 -0
- package/dist/lite/.claude/luminaries/karl-popper.md +132 -0
- package/dist/lite/.claude/luminaries/kent-beck.md +168 -0
- package/dist/lite/.claude/luminaries/linus-torvalds.md +218 -0
- package/dist/lite/.claude/luminaries/martin-fowler.md +164 -0
- package/dist/lite/.claude/luminaries/michael-feathers.md +125 -0
- package/dist/lite/.claude/luminaries/michael-nygard.md +118 -0
- package/dist/lite/.claude/luminaries/robert-c-martin.md +164 -0
- package/dist/lite/.claude/luminaries/saltzer-schroeder.md +194 -0
- package/dist/lite/.claude/luminaries/sophia-prater.md +193 -0
- package/dist/lite/.claude/luminaries/stephen-toulmin.md +131 -0
- package/dist/lite/.claude/luminaries/tony-hoare.md +170 -0
- package/dist/lite/.claude/luminaries/vaughn-vernon.md +50 -0
- package/dist/lite/.claude/luminaries/w-edwards-deming.md +158 -0
- package/dist/lite/.claude/rules/accessor-library-discipline.md +110 -0
- package/dist/lite/.claude/rules/adr-discipline.md +96 -0
- package/dist/lite/.claude/rules/api-conventions.md +115 -0
- package/dist/lite/.claude/rules/artifact-ingestion.md +137 -0
- package/dist/lite/.claude/rules/assert-only-after-verify.md +111 -0
- package/dist/lite/.claude/rules/blocked-items.md +139 -0
- package/dist/lite/.claude/rules/bootstrap-pair-discipline.md +106 -0
- package/dist/lite/.claude/rules/branching.md +28 -0
- package/dist/lite/.claude/rules/cold-adopter-harness-discipline.md +93 -0
- package/dist/lite/.claude/rules/commit-conventions.md +22 -0
- package/dist/lite/.claude/rules/compounding-axis-fresh-analysis.md +173 -0
- package/dist/lite/.claude/rules/compounding-sequence-fresh-analysis.md +173 -0
- package/dist/lite/.claude/rules/context-engineering.md +155 -0
- package/dist/lite/.claude/rules/context-management.md +85 -0
- package/dist/lite/.claude/rules/defensive-bash.md +52 -0
- package/dist/lite/.claude/rules/deferred-actions.md +233 -0
- package/dist/lite/.claude/rules/destructive-operations.md +69 -0
- package/dist/lite/.claude/rules/diagnosis.md +38 -0
- package/dist/lite/.claude/rules/github-issue-flash-tweet.md +114 -0
- package/dist/lite/.claude/rules/guardrails.md +73 -0
- package/dist/lite/.claude/rules/hook-wire-on-author.md +77 -0
- package/dist/lite/.claude/rules/identifier-leak-prevention.md +73 -0
- package/dist/lite/.claude/rules/iteration-bet-brief-completeness.md +43 -0
- package/dist/lite/.claude/rules/lite-manifest-schema-change-discipline.md +79 -0
- package/dist/lite/.claude/rules/longrun-prep-plan-doc-compression.md +77 -0
- package/dist/lite/.claude/rules/loop-discipline.md +71 -0
- package/dist/lite/.claude/rules/manual-prod-approval.md +100 -0
- package/dist/lite/.claude/rules/marker-enrichment-discipline.md +72 -0
- package/dist/lite/.claude/rules/mobile-ephemeral-session.md +109 -0
- package/dist/lite/.claude/rules/new-dependency-check.md +44 -0
- package/dist/lite/.claude/rules/oo-ad-entry-point.md +95 -0
- package/dist/lite/.claude/rules/operator-facing-prose.md +177 -0
- package/dist/lite/.claude/rules/option-label-discipline.md +82 -0
- package/dist/lite/.claude/rules/pattern-annotation.md +83 -0
- package/dist/lite/.claude/rules/persona-name-leak-scrub.md +90 -0
- package/dist/lite/.claude/rules/plain-english-discipline.md +132 -0
- package/dist/lite/.claude/rules/plan-enumeration-needs-value-props.md +177 -0
- package/dist/lite/.claude/rules/pr-body-shape.md +195 -0
- package/dist/lite/.claude/rules/pr-strategy.md +142 -0
- package/dist/lite/.claude/rules/pr-title-shape.md +90 -0
- package/dist/lite/.claude/rules/prototype-workflow.md +53 -0
- package/dist/lite/.claude/rules/reserved-skill-names.md +103 -0
- package/dist/lite/.claude/rules/schema-management.md +42 -0
- package/dist/lite/.claude/rules/sdlc-gates.md +149 -0
- package/dist/lite/.claude/rules/security.md +37 -0
- package/dist/lite/.claude/rules/session-artifacts.md +236 -0
- package/dist/lite/.claude/rules/skill-composition-declarations.md +105 -0
- package/dist/lite/.claude/rules/skill-description-clarity.md +196 -0
- package/dist/lite/.claude/rules/skill-procedure-step-list.md +119 -0
- package/dist/lite/.claude/rules/sprint-prep-preset-picker.md +76 -0
- package/dist/lite/.claude/rules/state-schema-validation.md +156 -0
- package/dist/lite/.claude/rules/stuck-signal-diagnostic.md +111 -0
- package/dist/lite/.claude/rules/substrate-config-schema.md +85 -0
- package/dist/lite/.claude/rules/test-list-discipline.md +129 -0
- package/dist/lite/.claude/rules/test-sufficiency.md +186 -0
- package/dist/lite/.claude/rules/testing-tier-config.md +125 -0
- package/dist/lite/.claude/rules/testing.md +38 -0
- package/dist/lite/.claude/rules/turn-estimate-grounding.md +82 -0
- package/dist/lite/.claude/rules/visual-hierarchy.md +427 -0
- package/dist/lite/.claude/rules/we-dont-break-adopters.md +103 -0
- package/dist/lite/.claude/rules/whereami-load-bearing.md +184 -0
- package/dist/lite/.claude/rules/writing-craft-discipline.md +72 -0
- package/dist/lite/.claude/rules/wu-sequencing-compounds.md +119 -0
- package/dist/lite/.claude/skills/build/SKILL.md +640 -0
- package/dist/lite/.claude/skills/chronicle/SKILL.md +55 -0
- package/dist/lite/.claude/skills/clean-artifacts/SKILL.md +249 -0
- package/dist/lite/.claude/skills/decompose/SKILL.md +280 -0
- package/dist/lite/.claude/skills/diagnose/SKILL.md +297 -0
- package/dist/lite/.claude/skills/feynman/SKILL.md +90 -0
- package/dist/lite/.claude/skills/howdoi/SKILL.md +105 -0
- package/dist/lite/.claude/skills/ia-model/SKILL.md +108 -0
- package/dist/lite/.claude/skills/interaction-design/SKILL.md +112 -0
- package/dist/lite/.claude/skills/interpret-input/SKILL.md +180 -0
- package/dist/lite/.claude/skills/journal/SKILL.md +209 -0
- package/dist/lite/.claude/skills/kiss/SKILL.md +449 -0
- package/dist/lite/.claude/skills/launch/SKILL.md +915 -0
- package/dist/lite/.claude/skills/lean-canvas/SKILL.md +332 -0
- package/dist/lite/.claude/skills/longrun/SKILL.md +512 -0
- package/dist/lite/.claude/skills/luminary/SKILL.md +481 -0
- package/dist/lite/.claude/skills/ogilvy-writing-audit/SKILL.md +177 -0
- package/dist/lite/.claude/skills/onboard-repo/SKILL.md +1640 -0
- package/dist/lite/.claude/skills/pattern-review/SKILL.md +99 -0
- package/dist/lite/.claude/skills/personas/SKILL.md +207 -0
- package/dist/lite/.claude/skills/promote/SKILL.md +283 -0
- package/dist/lite/.claude/skills/requirement/SKILL.md +98 -0
- package/dist/lite/.claude/skills/retro/SKILL.md +117 -0
- package/dist/lite/.claude/skills/riff/SKILL.md +114 -0
- package/dist/lite/.claude/skills/roadmap-reconcile/SKILL.md +163 -0
- package/dist/lite/.claude/skills/session-end/SKILL.md +309 -0
- package/dist/lite/.claude/skills/session-log/SKILL.md +299 -0
- package/dist/lite/.claude/skills/skills/SKILL.md +228 -0
- package/dist/lite/.claude/skills/spec/SKILL.md +105 -0
- package/dist/lite/.claude/skills/sprint/SKILL.md +418 -0
- package/dist/lite/.claude/skills/stage/SKILL.md +384 -0
- package/dist/lite/.claude/skills/state-a-problem/SKILL.md +185 -0
- package/dist/lite/.claude/skills/temperance/SKILL.md +108 -0
- package/dist/lite/.claude/skills/use-case/SKILL.md +417 -0
- package/dist/lite/.claude/skills/user-stories/SKILL.md +268 -0
- package/dist/lite/.claude/skills/value-prop/SKILL.md +251 -0
- package/dist/lite/.claude/skills/verify/SKILL.md +160 -0
- package/dist/lite/.claude/skills/visual-review/SKILL.md +503 -0
- package/dist/lite/.claude/skills/whats-the-plan/SKILL.md +202 -0
- package/dist/lite/.claude/skills/whereami/SKILL.md +307 -0
- package/dist/lite/AGENTS.md +79 -0
- package/dist/lite/CLAUDE-lite.md +85 -0
- package/dist/lite/CODE_OF_CONDUCT.md +28 -0
- package/dist/lite/CONTRIBUTING.md +177 -0
- package/dist/lite/README.md +173 -0
- package/dist/lite/SECURITY.md +19 -0
- package/dist/lite/architecture/decisions/ADR-029-release-pipeline.md +79 -0
- package/dist/lite/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +139 -0
- package/dist/lite/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +192 -0
- package/dist/lite/architecture/decisions/ADR-039-release-tagging-scheme.md +145 -0
- package/dist/lite/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +155 -0
- package/dist/lite/architecture/decisions/ADR-044-unified-skill-body-template.md +162 -0
- package/dist/lite/lib/clean-artifacts-sweep.sh +112 -0
- package/dist/lite/lib/code-comment-discipline.sh +144 -0
- package/dist/lite/lib/composer-preflight.sh +459 -0
- package/dist/lite/lib/deferred-actions.sh +146 -0
- package/dist/lite/lib/hook-header-parser.sh +61 -0
- package/dist/lite/lib/hook-heartbeat.sh +152 -0
- package/dist/lite/lib/hook-inject.sh +255 -0
- package/dist/lite/lib/hook-installer.sh +195 -0
- package/dist/lite/lib/luminary-pick.sh +96 -0
- package/dist/lite/lib/output-discipline.sh +143 -0
- package/dist/lite/lib/prose-scan-boundary.sh +171 -0
- package/dist/lite/lib/rewrite-check.sh +214 -0
- package/dist/lite/lib/session-rescue.sh +151 -0
- package/dist/lite/lib/source-graph-walker.sh +277 -0
- package/dist/lite/lib/state.sh +1372 -0
- package/dist/lite/lib/telemetry.sh +205 -0
- package/dist/lite/lib/tier-check.sh +187 -0
- package/dist/lite/lib/tier-dependency-audit.sh +1088 -0
- package/dist/lite/presence/install/bassclef-hook-connect.sh +178 -0
- package/dist/lite/presence/install/bassclef-sync.dispatcher.template.sh +841 -0
- package/dist/lite/presence/install/bassclef-sync.template.sh +2278 -0
- package/dist/lite/presence/install/schedule-auto-save.cron.sh +88 -0
- package/dist/lite/presence/install/schedule-auto-save.taskscheduler.md +122 -0
- package/dist/lite/scripts/aggregate-telemetry.sh +217 -0
- package/dist/lite/scripts/analyze-tier-dependencies.sh +239 -0
- package/dist/lite/scripts/generate-lite-manifest.sh +505 -0
- package/dist/lite/scripts/generate-tier-manifest.sh +28 -0
- package/dist/lite/scripts/intent-drift-check.sh +456 -0
- package/dist/lite/scripts/lite-manifest-drift-check.sh +146 -0
- package/dist/lite/scripts/render-lite-manifest-doc.sh +150 -0
- package/dist/lite/standards/adr-template.md +86 -0
- package/dist/lite/standards/api-conventions/nextjs.md +84 -0
- package/dist/lite/standards/artifact-composition.md +209 -0
- package/dist/lite/standards/bash-hook-safety.md +246 -0
- package/dist/lite/standards/bassclef-configs-schema.md +232 -0
- package/dist/lite/standards/bassclef-evolution.md +143 -0
- package/dist/lite/standards/bassclef-internal-jargon.md +244 -0
- package/dist/lite/standards/bassclef-managed-sentinel.md +96 -0
- package/dist/lite/standards/bassclef-source-config.md +228 -0
- package/dist/lite/standards/branch-stacking.md +408 -0
- package/dist/lite/standards/code-safety-principles.md +176 -0
- package/dist/lite/standards/composer-prerequisites.md +155 -0
- package/dist/lite/standards/deferred-actions-schema.md +204 -0
- package/dist/lite/standards/dependency-discipline/cargo.md +39 -0
- package/dist/lite/standards/dependency-discipline/gem.md +43 -0
- package/dist/lite/standards/dependency-discipline/go-mod.md +41 -0
- package/dist/lite/standards/dependency-discipline/npm.md +42 -0
- package/dist/lite/standards/dependency-discipline/pip.md +42 -0
- package/dist/lite/standards/deployment-topology/ec2-tailscale.md +225 -0
- package/dist/lite/standards/deployment-topology.md +69 -0
- package/dist/lite/standards/docs-sync-allowlist.md +76 -0
- package/dist/lite/standards/domain-and-dns.md +145 -0
- package/dist/lite/standards/frontend-stack.md +67 -0
- package/dist/lite/standards/frontmatter-schema.md +154 -0
- package/dist/lite/standards/graceful-exit.md +227 -0
- package/dist/lite/standards/hook-idempotency.md +102 -0
- package/dist/lite/standards/hook-injection-discipline.md +202 -0
- package/dist/lite/standards/hook-install-class.md +215 -0
- package/dist/lite/standards/input-handler-interface.md +152 -0
- package/dist/lite/standards/lite-manifest-schema-changes.md +180 -0
- package/dist/lite/standards/lite-manifest.json +2797 -0
- package/dist/lite/standards/luminary-matching.md +105 -0
- package/dist/lite/standards/luminary-problem-patterns.md +481 -0
- package/dist/lite/standards/migration-discipline/active-record.md +50 -0
- package/dist/lite/standards/migration-discipline/alembic.md +43 -0
- package/dist/lite/standards/migration-discipline/gorm.md +50 -0
- package/dist/lite/standards/migration-discipline/prisma.md +53 -0
- package/dist/lite/standards/migration-discipline/sqlalchemy.md +51 -0
- package/dist/lite/standards/mobile-ephemeral-session.md +167 -0
- package/dist/lite/standards/model-routing-discipline.md +160 -0
- package/dist/lite/standards/ogilvy-writing-rules.md +225 -0
- package/dist/lite/standards/opener-discipline.md +96 -0
- package/dist/lite/standards/operator-facing-prose-discipline.md +201 -0
- package/dist/lite/standards/persona-schema.md +229 -0
- package/dist/lite/standards/pluggable-luminaries.md +323 -0
- package/dist/lite/standards/pr-body-discipline.md +115 -0
- package/dist/lite/standards/preview-state-schema.md +189 -0
- package/dist/lite/standards/project-directory-layout.md +276 -0
- package/dist/lite/standards/release-tagging.md +137 -0
- package/dist/lite/standards/reserved-skill-names.md +120 -0
- package/dist/lite/standards/scannable-multi-option-output.md +265 -0
- package/dist/lite/standards/sdlc-compliance.md +286 -0
- package/dist/lite/standards/sdlc-gates/typescript.md +57 -0
- package/dist/lite/standards/secrets-lifecycle.md +210 -0
- package/dist/lite/standards/security-scanner-adapter.md +145 -0
- package/dist/lite/standards/session-board.md +256 -0
- package/dist/lite/standards/skill-output-discipline.md +121 -0
- package/dist/lite/standards/state-spine-contract.md +255 -0
- package/dist/lite/standards/state-spine.md +511 -0
- package/dist/lite/standards/steering-hints/kiss-words.md +11 -0
- package/dist/lite/standards/substrate-config-schema.md +267 -0
- package/dist/lite/standards/tech-stack-config.md +109 -0
- package/dist/lite/standards/tier-dependency-analysis.md +167 -0
- package/dist/lite/standards/tier-runtime-deps/lite.md +57 -0
- package/dist/lite/standards/tier-tag-schema.md +155 -0
- package/dist/lite/standards/two-layer-config.md +99 -0
- package/dist/lite/standards/use-case-format.md +292 -0
- package/dist/lite/standards/user-story-invest.md +268 -0
- package/dist/lite/standards/velocity-and-appetite.md +229 -0
- package/dist/lite/standards/voice-input-pattern.md +119 -0
- package/dist/lite/standards/whereami-schema.md +301 -0
- package/dist/lite/standards/worktree-management.md +211 -0
- package/dist/lite/standards/writing-guide.md +213 -0
- package/dist/lite/templates/chronicle-template.md +75 -0
- package/dist/lite/templates/deferred-action-template.md +45 -0
- package/dist/lite/templates/memory-proposal-template.md +77 -0
- package/dist/lite/templates/persona-template.md +200 -0
- package/dist/lite/templates/pr-faq.md +45 -0
- package/dist/lite/templates/secret-rotation-template.md +162 -0
- package/dist/lite/templates/spec-template.md +131 -0
- package/dist/lite/templates/use-case-template.md +194 -0
- package/dist/lite/templates/user-story-template.md +107 -0
- package/dist/lite/templates/whereami-template.md +101 -0
- package/package.json +1 -1
|
@@ -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
|
+
}
|